Skip to content

Commit 0b5028e

Browse files
CopilotAlan-JowettCopilot
authored
Add timeout to prevent verifier hang on malformed programs (#763)
* Add timeout mechanism to prevent verifier infinite loops Add a 5-second timeout around prevail::analyze() to prevent the fuzzer from hanging on malformed BPF programs that cause the verifier to enter infinite loops (e.g., nested loops which are a known limitation of prevail). The timeout uses a separate thread with std::future::wait_for() to detect when verification takes too long. If the timeout is reached, the verification thread is detached and the function returns false (verification failed). This fix addresses CI failures where the fuzzer would timeout after 60+ seconds on programs that trigger verifier infinite loops. Co-authored-by: Alan-Jowett <20480683+Alan-Jowett@users.noreply.github.com> * Update documentation with implementation notes Co-authored-by: Alan-Jowett <20480683+Alan-Jowett@users.noreply.github.com> * Revert "Update documentation with implementation notes" This reverts commit e1c27a1. * Move verification state to heap to fix use-after-free on timeout Address review feedback: - Move all shared state (program, result, promise, completion flag) to heap-allocated shared_ptrs so detached thread cannot access destroyed stack locals on timeout. - Add safe_set_value wrapper to guard against set_value throwing when promise is already satisfied or has no state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Alan-Jowett <20480683+Alan-Jowett@users.noreply.github.com> Co-authored-by: Alan Jowett <alanjo@microsoft.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 026ec21 commit 0b5028e

1 file changed

Lines changed: 57 additions & 6 deletions

File tree

libfuzzer/libfuzz_harness.cc

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
#include <array>
1616
#include <cinttypes>
1717
#include <regex>
18+
#include <thread>
19+
#include <future>
20+
#include <chrono>
1821

1922
#include "libfuzzer_config.h"
2023

@@ -384,19 +387,67 @@ try {
384387
prevail::InstructionSeq& prog = std::get<prevail::InstructionSeq>(prog_or_error);
385388

386389
// Convert the instruction sequence to a control-flow graph.
387-
const prevail::Program program = prevail::Program::from_sequence(prog, info, options);
390+
// Heap-allocated so lifetime is safe if verification thread is detached on timeout.
391+
auto program = std::make_shared<prevail::Program>(prevail::Program::from_sequence(prog, info, options));
392+
393+
// Verify the program with a timeout to prevent infinite loops (e.g., nested loops)
394+
// or crashes. Use a future with a timeout to abort verification that takes too long or crashes.
395+
constexpr int verification_timeout_seconds = 5;
396+
auto result_promise = std::make_shared<std::promise<bool>>();
397+
std::future<bool> result_future = result_promise->get_future();
398+
auto result = std::make_shared<prevail::AnalysisResult>();
399+
auto verification_completed = std::make_shared<bool>(false);
400+
401+
std::thread verification_thread([program, result, verification_completed, result_promise]() {
402+
auto safe_set_value = [&result_promise](bool value) {
403+
try {
404+
result_promise->set_value(value);
405+
} catch (...) {
406+
// Suppress exceptions from set_value (e.g., promise already satisfied or no state).
407+
}
408+
};
409+
410+
try {
411+
*result = prevail::analyze(*program);
412+
*verification_completed = true;
413+
safe_set_value(true);
414+
} catch (const std::exception& ex) {
415+
// Verification threw an exception (e.g., null pointer dereference)
416+
*verification_completed = false;
417+
safe_set_value(false);
418+
} catch (...) {
419+
// Unknown exception during verification
420+
*verification_completed = false;
421+
safe_set_value(false);
422+
}
423+
});
424+
425+
// Wait for the verification to complete or timeout
426+
if (result_future.wait_for(std::chrono::seconds(verification_timeout_seconds)) == std::future_status::timeout) {
427+
// Verification timed out - detach the thread and return false.
428+
// Shared state (program, result, promise) remains alive via shared_ptr until thread exits.
429+
verification_thread.detach();
430+
return false;
431+
}
432+
433+
// Get the result and check if verification completed successfully
434+
bool success = result_future.get();
435+
verification_thread.join();
436+
437+
if (!success || !*verification_completed) {
438+
// Verification failed or threw an exception
439+
return false;
440+
}
388441

389-
// Verify the program.
390-
const prevail::AnalysisResult result = prevail::analyze(program);
391-
stored_invariants = result;
442+
stored_invariants = *result;
392443

393444
if (g_ubpf_fuzzer_options.get("UBPF_FUZZER_PRINT_VERIFIER_REPORT")) {
394445
std::ostringstream error_stream;
395-
prevail::print_invariants(error_stream, program, false, result);
446+
prevail::print_invariants(error_stream, *program, false, *result);
396447
std::cout << error_stream.str() << std::endl;
397448
}
398449

399-
return !result.failed;
450+
return !result->failed;
400451
} catch (const std::exception& ex) {
401452
return false;
402453
}

0 commit comments

Comments
 (0)