Char buffer for serial input: char str[2] vs char* str = (char*) malloc(2)
First, a few general notes:

The difference between char str[2] and
char* str = (char*) malloc(2) is irrelevant to you problem. It
changes the place where the array is stored, the meaning of
sizeof str, and malloc() will blow up your RAM if you call it
repeatedly and forget to free().
In your code, sizeof(txt) is the constant 2: the actual size in
bytes of the array txt, irrespective of how much relevant data it
holds.
You want txt and indxto remember their contents across calls to
loop(). You should either make them global or qualify them as
static.
Avoid the String class if at all possible. Here, since you use it
only to compare two strings, you can use the traditional
strcmp() function instead.

Now, the bulk of my answer is just about expanding on what has already
been said about properly terminating strings.
A string is a variable-length list of characters. Whenever you are
dealing with a list that has a variable length, you need to somehow keep
track of this length. There are two common ways to do so:

either you store the length in a variable,
or you put a sentinel value (a value that would be invalid within
the list) after the last element to mark the end of the list.

Both methods are valid, and I am going to illustrate both of them below.
However, in the case of strings, the C++ language uses method 2,
with the ASCII “NUL” character (numeric value 0) as a sentinel.
Method 1:
void loop() {
  static char txt[2];
  static size_t indx = 0;

  while (s.available()) {
    // read input
    char c = s.read();
    txt[indx++] = c;

    // report
    Serial.print(&quot;Received: '&quot;);
    Serial.print(c);
    Serial.print(&quot;', string: \&quot;&quot;);
    Serial.write(txt, indx);
    Serial.print(&quot;\&quot;, size: &quot;);
    Serial.println(indx);
    if (indx == 2 &amp;&amp; memcmp(txt, &quot;OK&quot;, 2) == 0) {
      Serial.println(&quot;Correct message&quot;);
    }

    // reset index
    if (indx &gt;= sizeof txt) {
      indx = 0;
    }
  } // if(s.available())
}

Some things to note:

The indx variable holds the length of the string. It also happens to
be the index of the next available cell in the txt array, if any.
The string is printed using the
write(const char *buffer, size_t size) method, which accepts a size
as a second argument. Unlike print(const char *), it doesn't depend
on the string being NUL-terminated.
In order to check that we received “OK”, we have to check for both the
correct length (indx == 2) and correct contents. The contents is
checked using the memcmp() function, which can compare arbitrary
byte arrays and, again, does not depend on NUL-termination.
An important loop invariant here is idx &lt; sizeof txt. It is true at
initialization and is restored by the “reset index” section at the end
of the loop. The “read input” section depends on this invariant to not
overflow the buffer.

Method 2:
void loop() {
  static char txt[3];
  static size_t indx = 0;

  while (s.available()) {
    // read input
    char c = s.read();
    txt[indx++] = c;
    txt[indx] = '\0';

    // report
    Serial.print(&quot;Received: '&quot;);
    Serial.print(c);
    Serial.print(&quot;', string: \&quot;&quot;);
    Serial.print(txt);
    Serial.print(&quot;\&quot;, size: &quot;);
    Serial.println(indx);
    if (strcmp(txt, &quot;OK&quot;) == 0) {
      Serial.println(&quot;Correct message&quot;);
    }

    // reset index
    if (indx &gt;= sizeof txt - 1) {
      indx = 0;
      txt[indx] = '\0';
    }
  } // if(s.available())
}

Again, some notes:

This is actually an hybrid of methods 1 and 2, as the indx variable
is still used to keep track of where to insert the next incoming
character. This just happens to be more convenient and efficient than
putting that character into its own length-1 string and using
strcat().
The array is now one cell longer, to make room for the terminating
NUL.
There is another loop invariant now: correct string termination
(txt[indx] == '\0'). This invariant has to be enforced both on
reading and on resetting.
The string being NUL-terminated, it can now be printed with print()
and compared with strcmp(). The string literal &quot;OK&quot; is guaranteed
to be NUL-terminated by the compiler.

This second method is generally safer because there are a lot of
functions and methods in Arduino and C++ that expect strings to be
NUL-terminated. If your strings are not, you have to be extra careful to
not use any of these string-oriented functions.