Repository navigation
Replies: 5 comments 5 replies
|
Hello @pocopepe, please take a look on the above plan and feel free to suggest any changes. If you agree with everything just reply here that everything looks ok. |
Phase 1 report1. Fuzzing in Go, and how other projects do it1.1 Go's native fuzzingRead up on go’s native fuzzing tool chain off of Go's fuzzing docs, it needs no extra installs. Read up on how targets are set and how seeds are derived and mutated. A failing input seems to be saved inside I used the CNCF Fuzzing Handbook as my reference. (link) It separates panics from logic bugs. In Go, logic bugs are most of what's left to find, so a target is only as good as what it asserts. Property-based testing fits in the same place. A fuzz target that asserts a property is a property-based test whose inputs come from coverage feedback instead of pure randomness. Go's older property-testing package, 1.2 Long-running fuzzing
1.3 runc and containerdI read how the two closest projects fuzz themselves. The links go to the files I read. runc. Its in-repo fuzzing is a single legacy-style target, containerd. Everything is native How they check results. runc's targets are all crash-only: each one calls the code and throws the result away, so the only thing it can report is a panic or a crash. containerd is mostly the same. Its CRI target catches a panic and logs the sequence of operations that led to it. Only two targets check a real property: the local content store checks that a copy writes exactly as many bytes as it was given, and the docker fetcher checks that it returns exactly the bytes that were served. In memory-safe Go, a target that only waits for a panic rarely finds anything, so for urunc I'd assert properties instead (Section 2.3). What I'd take from this for urunc:
2. urunc's inputs2.1 Inputs and who controls themHere's every input I found in urunc's execution flow, and who controls it.
The spec and containerd's output are what the targets feed into urunc: they are its inputs, so they're exactly what gets fuzzed. What I'm not doing is treating them as hostile. Following #797, whoever writes the spec can already run arbitrary hooks, so defending against a deliberately malicious spec buys nothing. Instead I generate well-formed values, including the odd but legal ones (spaces, newlines, dots, empty strings, very long values), and check that urunc handles every one of them correctly. That rules out targets whose only job is rejecting garbage, like 2.2 The output side
Each row is a format urunc writes and another program parses. Your tests cover each side on its own, where tests exist, but nothing checks the contract between them, so that's where most of Section 4's targets sit. Where urunc interprets a value it validates it ( 2.3 Properties that should holdrunc and containerd mostly wait for a panic (Section 1.3). Go is memory safe, so panics are rare, and a target that only waits for one finds little. So these are the oracles I'm planning to add: properties each target asserts on every input.
3. The current test suite3.1 Unit test coverage by packageI measured unit-test coverage on upstream
How I got these numbers. On a clean checkout of go test -count=1 -coverprofile=cover.out $(go list ./... | grep -v /tests/)How the percentages are calculated. It's Go's statement coverage, counted by Where the gaps are. Two things the table doesn't show. 3.2 End-to-end tests
I measured e2e coverage the way
Each cell is statements run (and the percentage of the package's total), counted as in Section 3.1. In the last column a statement counts once if either kind of test runs it. Measured separately, the plain run covers 2,681 statements (64.4%) and the libcontainer run 2,798 (67.2%). The unit tests add 179 statements the e2e runs never reach, 133 of them in How I got these numbers. Same GOFLAGS="-cover -covermode=atomic -coverpkg=github.com/urunc-dev/urunc/..." make urunc_static shim_staticThen I set What neither reaches. 1,110 statements. About half are small error-return or logging blocks. The larger untested paths are:
The first three need new e2e scenarios. The namespace branches can be unit-tested directly, since 3.3 What gets fuzzed and what gets unit-testedI'd split the work by what the risk looks like. Fuzzing, where the shape of the input is the risk. urunc takes values it doesn't control the shape of and turns them into something another program reads: env and args into the guest command line or urunit's config, memory and the command into each monitor's argv, annotations into config, config into the state file and back, mount options into flags. Those inputs are open-ended strings and maps, and the bugs hide in awkward but legal shapes: a space, a newline, a dot in a name, an empty value, a very long one. The check can be written once, as a property that has to hold for every input: an env entry stays one variable, a round trip loses nothing, every backend agrees. Hand-written cases only cover the shapes someone thought of. The fuzzer tries the rest.
Unit tests, where the right answer is known exactly. Most of urunc's logic is per backend or per guest type: the argv qemu gets for a given config, the command line a unikraft guest gets, which rootfs a set of annotations selects, what the monitor spec looks like. For a given input there's exactly one right output, so a table of cases with the expected output checks it more precisely than random inputs can, and doubles as documentation of the intended behaviour. It's also the direct way to reach specific combinations (this monitor, this guest type, this rootfs) that a fuzzer would take a long time to hit. This covers the per-backend and per-guest output, the lifecycle helpers ( How the two fit together.
4. Fuzzing and testing targets4.1 Targets, by importanceThis is the order I'd work through them in Phase 2. I ranked them by who controls the input (image before orchestrator before host), how many guests or monitors it reaches, and how little the current suite checks it.
Known upstream issues on these surfaces should each get a regression target: the open ones once they're fixed (#754, #818, #867, #897), and the subnet-mask reports alongside them (#909 and #963, both closed as duplicates). I'm not planning to fuzz How I'd judge each target:
4.2 OSS-Fuzz and ClusterFuzzLiteI read OSS-Fuzz's own docs: how it accepts new projects, the new project guide and its Go page, and the bug disclosure guidelines. For ClusterFuzzLite I read its build integration and GitHub Actions pages. I also looked at how runc and containerd are set up there (Section 1.3). Getting urunc into OSS-Fuzz:
The lighter route is runc's: keep the targets in ClusterFuzzLite uses the same three build files, kept in 4.3 What it would take on urunc's side
5. What would change in uruncHere's what the plan above would touch, and how big each change is. Most of it is new test files. The code changes are small and internal. 5.1 New files only
5.2 Small changes, to make code testable
I'd put these tests on what the create phase gathers ( 5.3
|
|
Hello @pocopepe , please submit the first PRs for the introduction of the fuzzing skeleton. We will iterate over it multiple times. |
|
The order I plan to send the fuzzing work in. I'll tick these off as they merge.
|
|
Hi @cmainas, Target runs a bundle (
|
Uh oh!
There was an error while loading. Please reload this page.
Mentorship plan: Fuzzing and robustness testing for urunc
This discussion outlines the proposed plan for the CNCF mentorship project "Fuzzing and robustness testing for urunc”.
The plan is structured into three phases. Each phase has clear goals and specific outcomes, along with suggested tasks and sub-tasks to help guide the work. The listed sub-tasks are meant as guidance and reference points, they are not strict requirements. The exact order of tasks within each phase can be adjusted as long as the main outcomes are achieved.
Phase 1
The goal of Phase 1 is to build the necessary background on fuzzing in Go, to map the input surface of urunc and to establish where the current testing suite stands. This phase is expected to last up to 3 weeks (07/09/2026 – 27/09/2026). During this time, the focus will be on understanding what fuzzing can and can not do for a container runtime, identifying every input that reaches urunc and from where, and validating the background with a first set of fuzz targets on the parts of the code that are easy to reach.
Tentative tasks and sub-tasks
unittesttarget.Outcome
Deadline: Completed no later than 27/09/2026 (AoE).
Description: A report with:
urunc, who controls each one and the properties that should holdPhase 2
The goal of Phase 2 is to turn the list of the previous phase into an actual testing and fuzzing suite. This phase is expected to last up to 5 weeks (28/09/2026 – 01/11/2026). During this time, the focus will be on writing the targets, fixing what they find and getting everything merged and running in the CI.
Tentative tasks and sub-tasks
d from the CI run
Outcomes
Deadline: Completed no later than 01/11/2026 (AoE).
Description: The following items:
Phase 3
The goal of Phase 3 is to make the fuzzing run continuously and ensure its extensibility and maintenance. This phase is expected to last up to 4 weeks (02/11/2026 – 27/11/2026). During this time, the focus will be on the continuous fuzzing setup and on the documentation.
Tentative tasks and sub-tasks
Continuous fuzzing:
is in place
Documentation:
Robustness beyond fuzzing (time permitting):
Outcomes
Deadline: Completed no later than 27/11/2026 (AoE).
Description: The following items:
All reactions