diff --git a/centipede/puzzles/BUILD b/centipede/puzzles/BUILD index 6486063c7..d384ff4f7 100644 --- a/centipede/puzzles/BUILD +++ b/centipede/puzzles/BUILD @@ -43,4 +43,5 @@ package(default_visibility = ["@com_google_fuzztest//centipede/puzzles:__subpack "autodictionary_stress", "paths", "thread_uint32_cmp_1", + "pthread_uint32_cmp_1", ]] diff --git a/centipede/puzzles/pthread_uint32_cmp_1.cc b/centipede/puzzles/pthread_uint32_cmp_1.cc new file mode 100644 index 000000000..3a79bdcf9 --- /dev/null +++ b/centipede/puzzles/pthread_uint32_cmp_1.cc @@ -0,0 +1,52 @@ +// 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 cmp, in a separate thread using pthread +// interface. We should be able to solve it w/o cmp features *or* w/o auto +// dictionary. +// +// RUN: Run && SolutionIs Fuzz +// RUN: Run --use_auto_dictionary=0 && SolutionIs Fuzz +// RUN: Run --use_cmp_features=0 && SolutionIs Fuzz + +#include + +#include +#include +#include + +// non-const, to avoid compiler optimization. +static char expected_data[] = "Fuzz"; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + pthread_t pt; + struct ThreadArg { + const uint8_t *data; + size_t size; + } arg = {.data = data, .size = size}; + auto pt_entry = +[](const ThreadArg *thread_arg) { + uint32_t value, expected_value; + if (thread_arg->size == sizeof(value)) { + memcpy(&value, thread_arg->data, sizeof(value)); + memcpy(&expected_value, expected_data, sizeof(expected_value)); + if (value == expected_value) abort(); + pthread_exit(nullptr); + } + }; + if (pthread_create(&pt, nullptr, + reinterpret_cast(pt_entry), &arg) != 0) + return 1; + if (pthread_join(pt, nullptr) != 0) return 1; + return 0; +} diff --git a/centipede/puzzles/thread_uint32_cmp_1.cc b/centipede/puzzles/thread_uint32_cmp_1.cc index f2be45a2a..a58904770 100644 --- a/centipede/puzzles/thread_uint32_cmp_1.cc +++ b/centipede/puzzles/thread_uint32_cmp_1.cc @@ -16,9 +16,7 @@ // We should be able to solve it w/o cmp features *or* w/o auto dictionary. // RUN: Run && SolutionIs Fuzz // RUN: Run --use_auto_dictionary=0 && SolutionIs Fuzz -// TODO(b/295378866): Centipede runner currently fails to collect dictionary -// entries from merged threads, so if we disable cmp features we can't crack -// this puzzle. Enable "Run --use_cmp_features=0 && SolutionIs Fuzz" +// RUN: Run --use_cmp_features=0 && SolutionIs Fuzz #include #include diff --git a/centipede/runner.cc b/centipede/runner.cc index c4d871610..e12027f11 100644 --- a/centipede/runner.cc +++ b/centipede/runner.cc @@ -75,6 +75,17 @@ size_t LengthOfCommonPrefix(const void *s1, const void *s2, size_t n) { return n; } +class ThreadTerminationDetector { + public: + // A dummy method to trigger the construction and make sure that the + // destructor will be called on the thread termination. + __attribute__((optnone)) void EnsureAlive() {} + + ~ThreadTerminationDetector() { tls.OnThreadStop(); } +}; + +thread_local ThreadTerminationDetector termination_detector; + } // namespace // Use of the fixed init priority allows to call CentipedeRunnerMain @@ -126,6 +137,7 @@ void ThreadLocalRunnerState::TraceMemCmp(uintptr_t caller_pc, const uint8_t *s1, } void ThreadLocalRunnerState::OnThreadStart() { + termination_detector.EnsureAlive(); tls.lowest_sp = tls.top_frame_sp = reinterpret_cast(__builtin_frame_address(0)); tls.call_stack.Reset(state.run_time_flags.callstack_level); @@ -151,6 +163,15 @@ void ThreadLocalRunnerState::OnThreadStop() { prev_tls->next = next_tls; if (next_tls != nullptr) next_tls->prev = prev_tls; } + tls.next = tls.prev = nullptr; + if (tls.ignore) return; + // Create a detached copy on heap and add it to detached_tls_list to + // collect its coverage later. + ThreadLocalRunnerState *detached_tls = new ThreadLocalRunnerState(tls); + auto *old_list = state.detached_tls_list; + detached_tls->next = old_list; + state.detached_tls_list = detached_tls; + if (old_list != nullptr) old_list->prev = detached_tls; } static size_t GetPeakRSSMb() { @@ -233,6 +254,16 @@ static void CheckWatchdogLimits() { } } +void GlobalRunnerState::CleanUpDetachedTls() { + LockGuard lock(tls_list_mu); + ThreadLocalRunnerState *it_next = nullptr; + for (auto *it = detached_tls_list; it; it = it_next) { + it_next = it->next; + delete it; + } + detached_tls_list = nullptr; +} + void GlobalRunnerState::StartWatchdogThread() { if (state.run_time_flags.timeout_per_input == 0 && state.run_time_flags.timeout_per_batch == 0 && @@ -313,6 +344,7 @@ static void WriteFeaturesToFile(FILE *file, const feature_t *features, __attribute__((noinline)) // so that we see it in profile. static void PrepareCoverage(bool full_clear) { + state.CleanUpDetachedTls(); if (state.run_time_flags.path_level != 0) { state.ForEachTls([](ThreadLocalRunnerState &tls) { tls.path_ring_buffer.Reset(state.run_time_flags.path_level); @@ -929,6 +961,8 @@ GlobalRunnerState::~GlobalRunnerState() { StartSendingOutputsToEngine(outputs_blobseq); FinishSendingOutputsToEngine(outputs_blobseq); } + // Always clean up detached TLSs to avoid leakage. + CleanUpDetachedTls(); } // If HasFlag(:shmem:), state.arg1 and state.arg2 are the names diff --git a/centipede/runner.h b/centipede/runner.h index 9d57c2f33..ad71cb2f8 100644 --- a/centipede/runner.h +++ b/centipede/runner.h @@ -83,9 +83,9 @@ struct ThreadLocalRunnerState { // Guarded by state.tls_list_mu. ThreadLocalRunnerState *next, *prev; - // The pthread_create() interceptor calls OnThreadStart()/OnThreadStop() - // before/after the thread callback. - // The main thread calls OnThreadStart(). + // The pthread_create() interceptor calls OnThreadStart()before the thread + // callback. The main thread also calls OnThreadStart(). OnThreadStop() will + // be called when thread termination is detected internally - see runner.cc void OnThreadStart(); void OnThreadStop(); @@ -111,6 +111,7 @@ struct ThreadLocalRunnerState { CmpTrace<0, 64> cmp_traceN; // Set this to true if the thread needs to be ignored in ForEachTLS. + // It should be always false if the state is in the global detached_tls_list. bool ignore; }; @@ -191,7 +192,9 @@ struct GlobalRunnerState { // Doubly linked list of TLSs of all live threads. ThreadLocalRunnerState *tls_list; - pthread_mutex_t tls_list_mu; // Guards tls_list. + // Doubly linked list of detached TLSs. + ThreadLocalRunnerState *detached_tls_list; + pthread_mutex_t tls_list_mu; // Guards tls_list and detached_tls_list. // Iterates all TLS objects under tls_list_mu, except those with `ignore` set. // Calls `callback()` on every TLS. template @@ -200,8 +203,14 @@ struct GlobalRunnerState { for (auto *it = tls_list; it; it = it->next) { if (!it->ignore) callback(*it); } + for (auto *it = detached_tls_list; it; it = it->next) { + callback(*it); + } } + // Reclaims all TLSs in detached_tls_list and cleans up the list. + void CleanUpDetachedTls(); + // Computed by DlInfo(). // Usually, the main object is the executable binary containing main() // and most of the executable code (we assume that the target is diff --git a/centipede/runner_interceptors.cc b/centipede/runner_interceptors.cc index bf39bd85d..da7575fcc 100644 --- a/centipede/runner_interceptors.cc +++ b/centipede/runner_interceptors.cc @@ -58,7 +58,6 @@ void *MyThreadStart(void *arg) { auto *args = static_cast(arg); tls.OnThreadStart(); void *retval = args->start_routine(args->arg); - tls.OnThreadStop(); delete args; // allocated in the pthread_create wrapper. return retval; }