#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);