Repository navigation
fix(testcomp): keep the run below its cgroup memory limit; no fuzzing target outlives afl-fuzz - #94
Merged
Merged
Conversation
The Test-Comp 2027 pre-run (900 s, 3 GB) ended 1,747 runs as OUT OF MEMORY. BenchExec enforces the limit on the whole run, tmpfs included, and none of the engines knew it: - 821 runs died in KLEE's seeding loop, which never checks --max-memory; - about 200 with KLEE at its fixed 2,000 MB cap plus the files in tmpfs; - 239 in the first fuzzer phase: afl-fuzz runs its target unlimited. Map2Check now reads the run's limit from the cgroup (memory.max, visible inside the BenchExec container; MAP2CHECK_MEMORY_LIMIT_MB overrides it): - KLEE's --max-memory is 40% of the limit, at most KLEE's default 2,000 MB, so a 15 GB run keeps the cap it had (MAP2CHECK_KLEE_MAX_MEMORY still wins); - the KLEE watchdog reads memory.current every half second, seeding included: SIGINT at 80% of the limit (KLEE halts and writes its tests), SIGKILL at 92%. A phase stopped this way counts as incomplete; - the afl-fuzz target (-m) and the native replays (ulimit -v) get a third of the limit. With no limit found, nothing changes.
afl-fuzz starts each forkserver with setsid() (afl-forkserver.c, 4.40c), so its targets sit in sessions of their own, outside the process group `timeout` signals. When `timeout -k` SIGKILLs afl-fuzz, a running target survives and keeps spending the run's CPU time after Map2Check is done: a 900 s BenchExec run ended with 870 s of wall time, 908 s of CPU time and TIMEOUT. A persistent-mode target stopped in raise(SIGSTOP) is also left in an orphaned process group. When afl-fuzz returns, every process whose argv[0] is this program's fuzzing or CmpLog binary (the names carry the program's hash) is killed. test_fuzzer_leftovers.sh: a fake afl-fuzz leaves such a process in a new session and exits. RED: it is still running after map2check; GREEN: gone.
…limit Under BenchExec with one core a run's CPU time came out up to 22 s above its wall time (888 s of CPU for 866 s of wall, 900 s / 3 GB). With a 20 s margin the run ended 12 s from a TIMEOUT.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The Test-Comp 2027 pre-run (
map2check.2026-10-29_21-07-49: 900 s, 3 GB, 1 core) ended 1,747 of 16,288 runs as OUT OF MEMORY, and 34 with no measurements (FAILED, killed by signal 1). BenchExec enforces the limit on the run's whole cgroup, tmpfs included, and none of the engines knew it. From the official logs:checkMemoryUsage()there, so--max-memoryhas no effect.(The 14,507 ERROR runs all end with
DONE. They are fixed by sosy-lab/benchexec#1352, merged on Nov 2.)Changes
utils/memory_budget.hpp(new): reads the run's limit from its cgroup v2memory.max, walking up to the closest cgroup that has one. Inside BenchExec's container this is the namespace root.MAP2CHECK_MEMORY_LIMIT_MBoverrides it. With no limit found, nothing changes.--max-memoryis 40% of the limit, at most KLEE's default of 2,000 MB. A 15 GB run keeps the cap it had, andMAP2CHECK_KLEE_MAX_MEMORYstill wins.runKleeWatchedreadsmemory.currentevery 0.5 s, during seeding too. At 80% of the limit it sends SIGINT, so KLEE halts and writes its tests. At 92% it sends SIGKILL. A phase stopped this way counts as incomplete (stoppedForMemory), never TRUE.-m) and the replays of vectors (ulimit -v) get a third of the limit, at least 256 MB.setsid()(afl-forkserver.c:1066, 4.40c), so whentimeout -kSIGKILLs it, a running target survives. Measured on one run: 871 s of wall time, 909 s of CPU time, TIMEOUT. With the cleanup the same task endsdoneat 863 s of CPU for 860 s of wall.Tests
MemoryBudgetTest(8 unit tests): fake cgroup trees (namespace root, nested, no limit, no cgroupfs), the env override, and the KLEE, native and halt/kill thresholds.test_fuzzer_leftovers.sh(new, added to CI): a fake afl-fuzz leaves a process named like the target in a new session. RED: it survives map2check. GREEN: it is gone.install_mginside a 3 GB container, so with the governor active.test_benchexec_toolinfo.py: 24/24.done. The final archive passes the smoke test on a cleanubuntu:24.04. Details and caveats are indocs/reports/tacas-experiment-log.md(2026-11-07).