Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When execution time of fork run is much longer than one persistent iteration, all fork execuions will timeout. #1545

Open
Mem2019 opened this issue Oct 6, 2022 · 0 comments
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed

Comments

@Mem2019
Copy link
Contributor

Mem2019 commented Oct 6, 2022

Describe the bug
In persistent mode, there are 2 ways to execute a test case:

  1. When child process is not created, fork server will call fork to create a process to execute the test case.
  2. When child process is created but being stopped at __afl_persistent_loop, fork server will send a SIGCONT signal to resume the process to execute the test case.

In general, forking a process should be slower than resuming the loop, and this is the reason why persistent mode is designed. For example, in aflpp_driver.c, before entering the persistent loop, 2 functions, LLVMFuzzerTestOneInput and __asan_poison_memory_region, are called, plus the overhead of fork:

__afl_manual_init();

// Call LLVMFuzzerTestOneInput here so that coverage caused by initialization
// on the first execution of LLVMFuzzerTestOneInput is ignored.
LLVMFuzzerTestOneInput(dummy_input, 4);

__asan_poison_memory_region(__afl_fuzz_ptr, MAX_FILE);
size_t prev_length = 0;

// for speed only insert asan functions if the target is linked with asan
if (__asan_region_is_poisoned) {

  while (__afl_persistent_loop(N)) { /*...*/ }

} else {

  while (__afl_persistent_loop(N)) {

    LLVMFuzzerTestOneInput(__afl_fuzz_ptr, *__afl_fuzz_len);

  }

}

However, if fork execution is so slow such that its execution time is longer than timeout limit (which is calculated from initial seed corpus), all following execution will timeout. The reason is that as long as one timeout occurs, the process executing the test case will be killed so the next execution must fork a new process, which results in timeout again and forever. This is not desirable.

To Reproduce

The following code can trigger the problem. Although this case seems to be very handcrafted, this problem do occur in real world fuzzing, which is the reason why I found this issue.

#include <memory.h>
#include <stdint.h>
#include <unistd.h>

int LLVMFuzzerTestOneInput(const uint8_t *input, size_t r)
{
	if (r == 4 &&
		input[0] == '#' && input[1] == '#' &&
		input[2] == 'S' && input[3] == 'I')
	{ // make `LLVMFuzzerTestOneInput(dummy_input, 4);` stuck
		sleep(1);
	}
	return 0;
}

My Solution

The reason why timeout limit can be lower than time required to execute fork run is that when calculating exec_us of a seed in calibration, average time of all calibration executions is calculated. If only one of the execution uses fork and others are just resuming persistent loop, the average value will be much lower than time of fork execution, so when calculating max_us, it can be lower than time of fork execution. Therefore, instead of using average time, we get a max time for each seed and there must be one of them being the time of fork execution.

#1544

@vanhauser-thc vanhauser-thc added enhancement New feature or request help wanted Extra attention is needed good first issue Good for newcomers labels Apr 19, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

2 participants