-
Notifications
You must be signed in to change notification settings - Fork 37.7k
fuzz: Faster leak check when generating corpus #31481
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
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/31481. ReviewsSee the guideline for information on the review process.
If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update. ConflictsNo conflicts as of last run. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reference, asan has leak detection enabled by default, and it is also set in test/fuzz/test_runner.py
in ASAN_OPTIONS
.
For testing that leak detection still works, one can use a diff, something like:
diff --git a/src/test/fuzz/addition_overflow.cpp b/src/test/fuzz/addition_overflow.cpp
index 071e5fb029..a8c52d236a 100644
--- a/src/test/fuzz/addition_overflow.cpp
+++ b/src/test/fuzz/addition_overflow.cpp
@@ -44,6 +44,10 @@ void TestAdditionOverflow(FuzzedDataProvider& fuzzed_data_provider)
FUZZ_TARGET(addition_overflow)
{
+ const auto* leak = new uint8_t[777]{};
+ FuzzedDataProvider fdp(leak, 777);
+ TestAdditionOverflow<int64_t>(fdp);
+
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
TestAdditionOverflow<int64_t>(fuzzed_data_provider);
TestAdditionOverflow<uint64_t>(fuzzed_data_provider);
@@ -278,6 +278,7 @@ def job(command, t, t_env): | |||
use_value_profile = int(random.random() < .3) | |||
command = [ | |||
fuzz_bin, | |||
"-detect_leaks=0", # Disable leak detection on every iteration, it is still run on shutdown. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
libfuzzer will usually do this by itself, after some time:
...
INFO: libFuzzer disabled leak detection after every mutation.
Most likely the target function accumulates allocated
memory in a global state w/o actually leaking it.
You may try running this binary with -trace_malloc=[12] to get a trace of mallocs and frees.
If LeakSanitizer is enabled in this process it will still
run on the process shutdown.
...
However, it seems better to not rely on that and set the desired value from the start.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This message helped me find a bug recently (I was fuzzing something affecting global state and forgot to clean up after each mutation, so some global vector would grow with each iteration). While that was just a bug in my fuzzing code, I guess the same could happen for some remotely triggerable memory-exhaustion attacks.
So there may be some value in this info message itself (but obviously only if someone is actually looking at it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say the -rss_limit_mb
setting is a better tool to find OOM issues than the leak detector. I understand that the default setting in libfuzzer, or the setting in this file for rss_limit_mb
may not be effective at that.
Not sure what the best solution to this problem, but possibly rss_limit_mb
could be set on a per-fuzz-target basis, possibly with a scaling factor for sanitizer builds with higher overhead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, the message is hidden anyway by default in the fuzz runner, unless you specified -l DEBUG
.
fa10785
to
eeee163
Compare
rebased to drop the seed commit |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK eeee163
Performance increase
Changed -max_total_time=60
beneath the line this PR is modifying and ran with & without -detect_leaks=0
. The performance improvement is VERY stark. The number of fuzz inputs generated where either around 67 (2 tests) without, and with, it was a staggering 1937 (average from 2 runs). Around 28x faster.
What it actually does
Without this PR, fuzzing stops early after 3 leaking runs (using suggested diff adding intential leaks). With this PR, it completes before reporting leaks. This matches the docs at https://llvm.org/docs/LibFuzzer.html which state:
-detect_leaks
If 1 (default) and if LeakSanitizer is enabled try to detect memory leaks during fuzzing (i.e. not only at shut down).
PR title
Would prefer the title be renamed "fuzz: Faster leak check when generating corpus", or at least pointing out that it is this part which is optimized in the PR summary.
Test diff improvement
More correct size?
FuzzedDataProvider fdp(leak, 777);
@@ -278,6 +278,7 @@ def job(command, t, t_env): | |||
use_value_profile = int(random.random() < .3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(Comment about job
-function further up)
Suggested logging improvement in commit 3dd6ef5, here tested with early leak-detection still enabled.
Before
Generating corpus to foo
Running '['/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz', '-rss_limit_mb=8000', '-max_total_time=10', '-reload=0', '-use_value_profile=0', PosixPath('foo/addition_overflow')]'
Traceback (most recent call last):
File "/home/hodlinator/bitcoin/build_fuzz/test/fuzz/test_runner.py", line 412, in <module>
main()
File "/home/hodlinator/bitcoin/build_fuzz/test/fuzz/test_runner.py", line 180, in main
return generate_corpus(
^^^^^^^^^^^^^^^^
File "/home/hodlinator/bitcoin/build_fuzz/test/fuzz/test_runner.py", line 291, in generate_corpus
future.result()
File "/nix/store/wfbjq35kxs6x83c3ncpfxdyl5gbhdx4h-python3-3.12.6/lib/python3.12/concurrent/futures/_base.py", line 449, in result
return self.__get_result()
^^^^^^^^^^^^^^^^^^^
File "/nix/store/wfbjq35kxs6x83c3ncpfxdyl5gbhdx4h-python3-3.12.6/lib/python3.12/concurrent/futures/_base.py", line 401, in __get_result
raise self._exception
File "/nix/store/wfbjq35kxs6x83c3ncpfxdyl5gbhdx4h-python3-3.12.6/lib/python3.12/concurrent/futures/thread.py", line 58, in run
result = self.fn(*self.args, **self.kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/hodlinator/bitcoin/build_fuzz/test/fuzz/test_runner.py", line 262, in job
subprocess.run(
File "/nix/store/wfbjq35kxs6x83c3ncpfxdyl5gbhdx4h-python3-3.12.6/lib/python3.12/subprocess.py", line 571, in run
raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz', '-rss_limit_mb=8000', '-max_total_time=10', '-reload=0', '-use_value_profile=0', PosixPath('foo/addition_overflow')]' returned non-zero exit status 77.
After
Generating corpus to foo
Running '['/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz', '-rss_limit_mb=8000', '-max_total_time=10', '-reload=0', '-use_value_profile=0', PosixPath('foo/addition_overflow')]'
Command '['/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz', '-rss_limit_mb=8000', '-max_total_time=10', '-reload=0', '-use_value_profile=0', PosixPath('foo/addition_overflow')]' output:
'INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 168855887
INFO: Loaded 1 modules (1305991 inline 8-bit counters): 1305991 [0x561fd5b16200, 0x561fd5c54f87),
INFO: Loaded 1 PC tables (1305991 PCs): 1305991 [0x561fd5c54f88,0x561fd70427f8),
INFO: 0 files found in foo/addition_overflow
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED cov: 797 ft: 798 corp: 1/1b exec/s: 0 rss: 230Mb
==3390108==WARNING: invalid path to external symbolizer!
==3390108==WARNING: Failed to use and restart external symbolizer!
=================================================================
==3390108==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 777 byte(s) in 1 object(s) allocated from:
#0 0x561fca60f978 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5da0978)
#1 0x561fca6129c9 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5da39c9)
#2 0x561fca61c3c9 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5dad3c9)
#3 0x561fca61c01f (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5dad01f)
#4 0x561fca61b603 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5dac603)
#5 0x561fcc4984de (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x7c294de)
#6 0x561fcc493c83 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x7c24c83)
#7 0x561fcc49391f (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x7c2491f)
#8 0x561fca4b3ab8 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c44ab8)
#9 0x561fca4b66a0 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c476a0)
#10 0x561fca4b7acb (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c48acb)
#11 0x561fca4b7ce7 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c48ce7)
#12 0x561fca4986d1 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c296d1)
#13 0x561fca29cd72 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5a2dd72)
#14 0x7ff7486dd27d (/nix/store/sl141d1g77wvhr050ah87lcyz2czdxa3-glibc-2.40-36/lib/libc.so.6+0x2a27d) (BuildId: 6a788357de379aee7162f608d25a7692f7162b15)
Direct leak of 777 byte(s) in 1 object(s) allocated from:
#0 0x561fca60f978 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5da0978)
#1 0x561fca6129c9 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5da39c9)
#2 0x561fca61c3c9 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5dad3c9)
#3 0x561fca61c01f (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5dad01f)
#4 0x561fca61b603 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5dac603)
#5 0x561fcc4984de (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x7c294de)
#6 0x561fcc493c83 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x7c24c83)
#7 0x561fcc49391f (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x7c2491f)
#8 0x561fca4b3ab8 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c44ab8)
#9 0x561fca4b66a0 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c476a0)
#10 0x561fca4b72b0 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c482b0)
#11 0x561fca4b7ee7 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c48ee7)
#12 0x561fca4986d1 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c296d1)
#13 0x561fca29cd72 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5a2dd72)
#14 0x7ff7486dd27d (/nix/store/sl141d1g77wvhr050ah87lcyz2czdxa3-glibc-2.40-36/lib/libc.so.6+0x2a27d) (BuildId: 6a788357de379aee7162f608d25a7692f7162b15)
Direct leak of 777 byte(s) in 1 object(s) allocated from:
#0 0x561fca60f978 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5da0978)
#1 0x561fca6129c9 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5da39c9)
#2 0x561fca61c3c9 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5dad3c9)
#3 0x561fca61c01f (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5dad01f)
#4 0x561fca61b603 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5dac603)
#5 0x561fcc4984de (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x7c294de)
#6 0x561fcc493c83 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x7c24c83)
#7 0x561fcc49391f (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x7c2491f)
#8 0x561fca4b3ab8 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c44ab8)
#9 0x561fca4b76d4 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c486d4)
#10 0x561fca4b7ce7 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c48ce7)
#11 0x561fca4986d1 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5c296d1)
#12 0x561fca29cd72 (/home/hodlinator/bitcoin/build_fuzz/src/test/fuzz/fuzz+0x5a2dd72)
#13 0x7ff7486dd27d (/nix/store/sl141d1g77wvhr050ah87lcyz2czdxa3-glibc-2.40-36/lib/libc.so.6+0x2a27d) (BuildId: 6a788357de379aee7162f608d25a7692f7162b15)
SUMMARY: AddressSanitizer: 2331 byte(s) leaked in 3 allocation(s).
INFO: to ignore leaks on libFuzzer side use -detect_leaks=0.
MS: 1 ChangeBit-; base unit: adc83b19e793491b1c6ea0fd8b46cd9f32e592fc
0x8,
\010
artifact_prefix='./'; Test unit written to ./leak-8d883f1577ca8c334b7c6d75ccb71209d71ced13
Base64: CA==
'
Just for completeness, the performance increase should be mild overall, assuming that the leak detection is turned off automatically anyway for long runs, but I haven't confirmed this. If #31481 (comment) is too controversial, an alternative could be to just disable it when |
Yeah, if libfuzzer automatically stops leak detection after a while, I can see a case for closing this PR too, haven't seriously generated corpuses yet, so weigh my ACK accordingly. |
Currently, when running the fuzz targets under asan, leak detection may be expensive when global memory is involved on every iteration. For example when
SeedRandomStateForTest(SeedRand::ZEROS)
is called on every iteration.Fix it by disabling leak check on every iteration, but still do it at the end.