Master the fundamental concepts of system calls & kernel interface through this focused micro-challenge.
You have read the whole brief, and the concepts above stay free on every task. Writing and running the code needs a plan.
Three hints are available for this task, revealed one at a time inside the code workspace so you can struggle productively before seeing them.
Every task includes starter code, theory, and hidden tests so you can implement and verify locally in the browser.
How it worksKernel syscall handlers must validate every user pointer and length. Fuzzers send garbage arguments (negative lengths, unmapped addresses, wrong fd types) hunting for panics or privilege leaks.
Effective campaigns:
For example, calling read(-1, (void*)1, SIZE_MAX) should return EBADF or EFAULT, never crash the kernel.
Google's syzkaller has found thousands of real Linux kernel bugs this way, including use-after-free and out-of-bounds vulnerabilities that earned CVEs, precisely by fuzzing syscall arguments like the ones you generate here (NULL pointers, negative fds, boundary sizes). The crash-catching pattern with sigsetjmp/siglongjmp mirrors how real fuzzing harnesses keep running after a target syscall corrupts memory instead of taking down the entire fuzzer process.
Before you call the implementation done, walk failure modes on purpose. Test empty structures, single-element edge cases, maximum concurrency, and errno paths that must not crash the program. OS code usually fails in production when happy-path tests pass but invariants break under contention or memory pressure.
Keep structures small and name fields after kernel counterparts when possible. That lets you read man pages and kernel source side by side while you work. Print observable events during development; remove noisy logs once tests pass reliably.
You will implement a userspace harness that mutates syscall arguments and records outcomes. The task asks you to classify results into expected errno vs unexpected signals.
Build a small system-call fuzzer. It picks a target syscall at random, fills each argument from a pool of boundary values, calls a model kernel, and classifies the result. When the fuzzer finds a crash, it minimises the reproducer. Everything is driven by a seeded PRNG, so a run can be replayed exactly, just as real fuzzers record their seeds.
cLoading…
The state starts as splitmix64(S) (or 1 if that is 0). Each draw is xorshift64*:
cLoading…
Each iteration draws the target as targets[next() % count], in the order the targets were listed (a repeated target takes another slot). It then draws each argument, left to right, as pool[next() % size].
| Type | Pool, in order |
|---|---|
| fd | 0, 1, 2, 3, -1, 1024, 2147483647 |
| ptr | 0x7fff0000, 0, 0x1000, 0xffff800000000000, 0x7ffffffffff0 |
| len | 0, 1, 4096, 4097, 65536, 0x7ffff001, -1 (as unsigned) |
| off | 0, 1, -1, 4096, INT64_MAX, INT64_MIN |
| whence | 0, 1, 2, 3, -1 |
Open fds are 1, 2 and 3. A user range is valid if buf >= 0x10000, buf < 0x800000000000, and len <= 0x800000000000 - buf.
min(count, 0x7ffff000).len > 4096 gives a KERNEL OOPS (the planted bug: a missing bounds check). Otherwise it returns len.cLoading…
unknown target NAME, and no targets (then stop).Input:
cLoading…
Output:
cLoading…
buf + len can wrap, so compare len against 0x800000000000 - buf instead.Hidden tests cover a long quiet run over all three targets with crashes and minimisation, an unknown target name, a repeated target, and a single-iteration run.