#Centipede Intercept and trace calls to strncmp.

PiperOrigin-RevId: 634490034
diff --git a/centipede/puzzles/BUILD b/centipede/puzzles/BUILD
index 262579a..cca0e33 100644
--- a/centipede/puzzles/BUILD
+++ b/centipede/puzzles/BUILD
@@ -35,6 +35,7 @@
     "memcmp_4_may_inline",
     "memcmp_3",
     "strcmp",
+    "strncmp",
     "uint32_cmp_1",
     "oom",
     "per_input_timeout",
diff --git a/centipede/puzzles/autodictionary_stress.cc b/centipede/puzzles/autodictionary_stress.cc
index 1615fd3..297d5ee 100644
--- a/centipede/puzzles/autodictionary_stress.cc
+++ b/centipede/puzzles/autodictionary_stress.cc
@@ -14,7 +14,7 @@
 
 // Centipede puzzle: stress test for --use_auto_dictionary=1.
 // RUN: Run --use_auto_dictionary=1 --use_cmp_features=0 -j 5
-// RUN: ExpectInLog "Input bytes.*: abcdxyzVeryLongString"
+// RUN: ExpectInLog "Input bytes.*: abcdxyzVeryLongStringKeyword"
 
 // TODO(kcc): we currently use --use_cmp_features=0 because otherwise
 // the corpus gets too large and the puzzle does not get solved quickly.
@@ -54,8 +54,18 @@
     return false;
   };
 
+  auto strncmp_and_forward = [&](const char *str, size_t n) {
+    if (end - beg >= n &&
+        strncmp(reinterpret_cast<const char *>(beg), str, n) == 0) {
+      beg += n;
+      return true;
+    }
+    return false;
+  };
+
   if (memcmp_and_forward("abcd") && memcmp_and_forward("xyz") &&
-      strcmp_and_forward("VeryLongString")) {
+      strcmp_and_forward("VeryLongString") &&
+      strncmp_and_forward("KeywordAndStuff", 7)) {
     abort();
   }
 
diff --git a/centipede/puzzles/strncmp.cc b/centipede/puzzles/strncmp.cc
new file mode 100644
index 0000000..4668e11
--- /dev/null
+++ b/centipede/puzzles/strncmp.cc
@@ -0,0 +1,37 @@
+// Copyright 2023 The Centipede Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Centipede puzzle: one 4-byte strncmp. Check the output in the log.
+// Disable use_auto_dictionary so that we test other functionality.
+// RUN: Run --use_auto_dictionary=false && SolutionIs fUzZ
+// RUN: ExpectInLog "TEXT IN STDOUT"
+// RUN: ExpectInLog "TEXT IN STDERR"
+
+#include <cstdint>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
+  if (size >= 4 &&
+      strncmp(reinterpret_cast<const char *>(data), "fUzZ", 4) == 0) {
+    printf("TEXT IN STDOUT\n");
+    // abort() does not flush stdout, so if we don't flush it, the output
+    // may be lost after abort().
+    fflush(stdout);
+    fprintf(stderr, "TEXT IN STDERR\n");
+    abort();
+  }
+  return 0;
+}
diff --git a/centipede/runner_interceptors.cc b/centipede/runner_interceptors.cc
index 864f540..183c7ba 100644
--- a/centipede/runner_interceptors.cc
+++ b/centipede/runner_interceptors.cc
@@ -17,6 +17,7 @@
 #include <dlfcn.h>  // for dlsym()
 #include <pthread.h>
 
+#include <algorithm>
 #include <cstdint>
 #include <cstring>
 
@@ -110,6 +111,8 @@
 DECLARE_CENTIPEDE_ORIG_FUNC(int, memcmp,
                             (const void *s1, const void *s2, size_t n));
 DECLARE_CENTIPEDE_ORIG_FUNC(int, strcmp, (const char *s1, const char *s2));
+DECLARE_CENTIPEDE_ORIG_FUNC(int, strncmp,
+                            (const char *s1, const char *s2, size_t n));
 DECLARE_CENTIPEDE_ORIG_FUNC(int, pthread_create,
                             (pthread_t * thread, const pthread_attr_t *attr,
                              void *(*start_routine)(void *), void *arg));
@@ -137,15 +140,42 @@
   return NormalizeCmpResult(result);
 }
 
+// TODO(b/341111359): Investigate inefficiencies in the `strcmp`/`strncmp`
+// interceptors and `TraceMemCmp`.
+
 // strcmp interceptor.
 // Calls the real strcmp() and possibly modifies state.cmp_feature_set.
 extern "C" int strcmp(const char *s1, const char *s2) {
+  // Find the length of the shorter string, as this determines the actual number
+  // of bytes that are compared. Note that this is needed even if we call
+  // `strcmp_orig` because we're passing it to `TraceMemCmp()`.
   size_t len = 0;
   while (s1[len] && s2[len]) ++len;
   const int result =
       // Need to include one more byte than the shorter string length
       // when falling back to memcmp e.g. "foo" < "foobar".
       strcmp_orig ? strcmp_orig(s1, s2) : memcmp_fallback(s1, s2, len + 1);
+  // Pass `len` here to avoid storing the trailing '\0' in the dictionary.
+  tls.TraceMemCmp(reinterpret_cast<uintptr_t>(__builtin_return_address(0)),
+                  reinterpret_cast<const uint8_t *>(s1),
+                  reinterpret_cast<const uint8_t *>(s2), len, result == 0);
+  return NormalizeCmpResult(result);
+}
+
+// strncmp interceptor.
+// Calls the real strncmp() and possibly modifies state.cmp_feature_set.
+extern "C" int strncmp(const char *s1, const char *s2, size_t n) {
+  // Find the length of the shorter string, as this determines the actual number
+  // of bytes that are compared. Note that this is needed even if we call
+  // `strncmp_orig` because we're passing it to `TraceMemCmp()`.
+  size_t len = 0;
+  while (len < n && s1[len] && s2[len]) ++len;
+  // Need to include '\0' in the comparison if the shorter string is shorter
+  // than `n`, hence we add 1 to the length.
+  n = std::min(n, len + 1);
+  const int result =
+      strncmp_orig ? strncmp_orig(s1, s2, n) : memcmp_fallback(s1, s2, n);
+  // Pass `len` here to avoid storing the trailing '\0' in the dictionary.
   tls.TraceMemCmp(reinterpret_cast<uintptr_t>(__builtin_return_address(0)),
                   reinterpret_cast<const uint8_t *>(s1),
                   reinterpret_cast<const uint8_t *>(s2), len, result == 0);