# Trinity: a Linux syscall fuzzer that builds semi-intelligent arguments

> Trinity is a C program that calls Linux system calls with arguments chosen to pass basic validation, and since the v1.9 tag most of its commits are generated autonomously by an LLM. It is interesting to read and to experiment with, but its own documentation calls it abandonware and warns that it may be broken on any given day.

**kernelslacker/trinity** — Linux system call fuzzer

- Repository: https://github.com/kernelslacker/trinity
- Stars: 909 · Forks: 244
- Language: C
- License: NOASSERTION
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/kernelslacker-trinity

## Trinity attacks the kernel argument, not the kernel entry point

The problem Trinity sets out to solve is narrow and specific. A naive syscall fuzzer fires garbage at every call and, in the words of the project, most of those calls get rejected very near the beginning of the function entry point when basic parameter validation runs. The kernel rejects them, nothing interesting happens, and the fuzzer learns nothing.

Trinity's answer is to spend effort constructing arguments that have a chance of being accepted. If a syscall wants a file descriptor, Trinity passes one. If it wants a flags field, Trinity passes a value from a list of the valid flags it knows about. If it takes a range, the random value is biased to fall inside that range. The point is to get past the early validation layer so the fuzzer reaches code that actually does something interesting.

It is a security research tool rather than an offensive one. There is no exploit chain, no shellcode and no target selection, just calls into the local kernel that are meant to find crashes. The tool dates back to the early 1990s idea of bombarding syscalls with garbage, and this implementation is written in C and targets Linux only. The audience is kernel developers and systems engineers who want to find panics, tainted warnings and out-of-bounds bugs in the syscall layer of the kernel they run.

## Where the file descriptors come from on the first run

The slowest part of a fresh run is generating file descriptors. Trinity tree-walks /sys, /proc and /dev to build a list of descriptors it can read from, then supplements that list with descriptors for network protocol sockets. The project notes that which protocols succeed and which fail is cached on the first run, which greatly increases the speed of later runs.

Two consequences follow for anyone planning a fuzzing campaign. The first run is slow and touches a lot of the filesystem, and the cache means subsequent runs behave differently in timing if the environment changed underneath. The second is that the tool walks real system trees by default, which is why the warnings about file corruption are not decorative.

The behaviour can be redirected. The --victims option, short form -V, sets the path that gets tree-walked instead of the three defaults, and it is currently limited to a single path. The --domain option, short form -P, picks the network domain used for sockets. Neither of these is explained in depth in the documentation, so they are best treated as blunt controls: point the walker somewhere harmless, keep the network domain on the local one, and observe what the tool does before letting it roam.

The documentation also mentions a pause flag, -p, that stops after making a syscall, which is the practical way to turn this into a step-through debugging session rather than a random walk.

## Reading the syscall table before you let it loose

There is a safe entry point. The --dry-run option parses the arguments and exits without fuzzing, and the --list option, short form -L, prints the known syscalls and their offsets. Both let you confirm the tool understands your kernel before it starts making a mess:

```bash
--dry-run
```

You should see the tool parse its own arguments and stop, with no system calls made. If that step fails, nothing later will work either.

The table itself is maintained automatically. The project states that updating the syscall definitions used to be one of the laborious parts of keeping up with modern kernels, and that it now tracks additions to the upstream mainline tree and picks up new definitions as they land. That is a genuine advantage over a fuzzer frozen against a 2019 syscall list, and it also means the tool knows about io_uring, bpf and other newer interfaces.

Two flags narrow the target. The -x option excludes a single syscall from being called, which the documentation recommends when a known kernel bug keeps getting hit. The -cN form concentrates the run on one specific syscall with random inputs, which is the useful mode when you have just added a new syscall definition and want to hammer it. The -g option runs only syscalls from a named group, and the documented group names are vfs, vm, net, ipc, process, signal, io_uring, bpf, sched and time. The -a option selects the 32 or 64 bit variant explicitly, and -I dumps all available ioctls.

## Kernel taint flags turn the output into a signal

Trinity watches kernel taint flags, and this is where the output becomes actionable rather than noisy. The --kernel_taint option, short form -T, controls which taint flags are considered. The supported names are PROPRIETARY_MODULE, FORCED_MODULE, UNSAFE_SMP, FORCED_RMMOD, MACHINE_CHECK, BAD_PAGE, USER, DIE, OVERRIDDEN_ACPI_TABLE, WARN, CRAP, FIRMWARE_WORKAROUND and OOT_MODULE.

Monitoring only the flags you care about turns a wall of taint output into a short list. The documentation's own example restricts the tool to three:

```bash
-T BAD,WARN,MACHINE_CHECK
```

That argument limits reporting to BAD_PAGE, WARN and MACHINE_CHECK, so a WARN taint is a signal rather than one line among dozens. By default the tool prints only essential messages, namely errors, taint detection and syscall counts, and -v raises verbosity with -vv giving full debug output.

Reproducibility is the weak point here. The -sN form sets a random seed, and without it the time of day is used. The project is explicit that a few bugs mean no two runs are guaranteed identical with the same seed, and points to a TODO for the details. That is a real limit for anyone who wants a crash to reproduce from a recorded seed, and it means a saved run log plus a saved machine state is closer to reproducibility than a seed alone.

For debugging a segfault in Trinity itself, the -D flag enables debug mode. The documentation explains why it exists: by default the child processes ignore those signals, so -D is what lets you catch a core dump.

## The harnesses under scripts/ decide how the run is shaped

Several test harnesses live under scripts/, described as running Trinity in various modes and taking care of things like CPU affinity and making sure it runs from the tmp directory. The individual scripts are test-multi.sh, test-vm.sh, test-all-syscalls-parallel.sh and test-all-syscalls-sequentially.sh.

The tmp directory note is a warning in disguise. The documentation mentions that running from the tmp directory is handy for cleaning up any garbage named files, with the cleanup being a recursive removal of the tmp directory afterwards:

```bash
rm -rf tmp
```

So the intended operating pattern is throwaway: fuzz in a scratch directory, then delete it. That is the sane way to use a tool that the documentation says may seriously corrupt your files, including any that may be writable on mounted network file shares.

The network side deserves the same caution. The warnings state that Trinity may create network packets that may cause disruption on the local network, and that it may generate a selection of syscalls that start sending random network packets to other hosts. The intent is to restrict traffic to local LAN, multicast and broadcast addresses, and the documentation explicitly says care should be taken not to let those packets go out onto the internet. A fuzzing host on a shared or production network is the wrong place to run this.

The --children option, short form -C, sets the number of child processes, and the harnesses handle affinity for you if you use them. That is the knob to turn first when you want more throughput on a multi-core machine, but each child is another set of fuzzed syscalls against the same kernel.

## Memory budgets keep a worker from taking the parent with it

Long fuzzing runs need bounds, and the newer options are about bounding resource use rather than about finding bugs. The --memory-max option sets a total Trinity memory budget, split into a per-child limit written as children/memory.max=<this>-parent_high plus a small parent reservation, where parent_high is the lesser of 200M and this divided by 16. The stated reason is that a worker hitting OOM should not take the parent down with it.

The value accepts several forms: the literal max, a percentage of MemTotal, or a byte count with a K, M or G suffix. The default is 60 percent of memory.

The companion option, --memory-high, sets the children/memory.high back-pressure threshold in the workers cgroup, and accepts the same value forms. High is the soft limit where the kernel starts reclaiming and throttling, max is the hard limit. Setting these correctly is what keeps a fuzzer that deliberately provokes memory corruption from turning into a machine that reboots mid-run.

Runtime is bounded by --max-runtime, which takes an optional suffix of s, m, h or d, for example 30s, 10m, 2h or 1d, and which the documentation says overrides --epoch-timeout. Combined with the memory options, this gives you a run that ends on a predictable schedule and leaves a machine in a usable state afterwards. Given the file corruption and network warnings, treating every run as a bounded, disposable experiment is the intended design rather than a cautious reading of it.

## Most of this code is written by an LLM, and the author says so

The most important thing to know about Trinity is stated plainly in its own documentation. The author says he has not had time to work on it much in the last decade, that you should consider it abandonware for the most part, and that nearly every commit after the v1.9 tag is generated autonomously by an LLM. He also says he seeded it with TODO items and had it explore the codebase for its own fuzzing ideas, then picks up the latest commits, runs it, and feeds back when it introduces new bugs, which it fixes in the next cycle.

The consequence is stated just as plainly: on any given day it might be completely broken. Pull requests and emailed diffs are explicitly not wanted, because the activity is an experiment in how far an LLM can take the project.

That framing changes what this repository is. It is a long-running experiment and a readable reference implementation, not a supported tool. The syscall table upkeep is the part that clearly works, since tracking upstream mainline is exactly the kind of repetitive task the author delegated.

The repository root shows a substantial C codebase with directories for args, child, dispatch, fds, ioctls, mm, net, objects, persist, rand, strategy, struct_catalog, syscalls, tables and tests, plus a Makefile with a version string of 2026.05. There are no GitHub releases, so there is no version history to pin against, only commits on the master branch.

## Where Trinity is the wrong tool, and what to use instead

Three situations argue against adopting Trinity as a project dependency. If you need coverage-guided fuzzing, Trinity is not that: it is a random walk with informed arguments, and it has no instrumentation loop, no corpus triage and no minimisation story in the documentation. If you need old enterprise distributions, the project says it no longer supports them outside the ones the author uses, and asks that fixes for such distributions be kept in your own distro package because the LLM will likely break them again. If you need a tool that will still work next quarter, the abandonware statement settles it.

The alternative the documentation itself points to is upstream fuzzing, described as actively maintained and supported. The difference in approach is not one of features but of stewardship. Trinity is the experimental, LLM-driven, self-fixing branch that prioritises covering new syscalls as the kernel grows, and it accepts breakage in exchange for that. Upstream fuzzing is the conservative choice: a project with a maintainer behind it, which is what you want when a fuzzing campaign has to run for weeks and produce findings you can act on.

The README also states that a stable syscall fuzzer is available upstream, which is the clearest signal of intent about the two projects. If you only want to read how a syscall fuzzer builds plausible arguments, Trinity is unusually instructive, and the source directories for rand, strategy and struct_catalog show how the argument synthesis is put together. If you intend to run it, a disposable VM, a scratch tmp directory and the scripts under scripts/ are the minimum sensible setup.

## Conclusion

Trinity suits an engineer who wants to read a working syscall fuzzer, tinker with its argument generation, and fuzz a kernel on a machine they can lose, ideally in a VM with the scripts under scripts/ driving it. It does not suit a team that needs a supported fuzzing tool, a reproducible corpus, or coverage guidance, because the project describes itself as abandonware with commits written by an LLM and warns that it may be broken on any given day. Before adopting it, check the licence in COPYING, since the licence field on the repository says Other, and confirm your kernel version matches the syscall definitions the tool tracks upstream, because the project states it no longer supports older distributions.

## FAQ

### What does Trinity do differently from a fuzzer that sends random garbage to syscalls?

Trinity builds arguments that have a chance of passing the kernel's early validation. It passes real file descriptors gathered from /sys, /proc and /dev, known valid flags for flags fields, and range-biased random values for bounded arguments.

### Is Trinity still maintained enough to rely on?

The project's own documentation says to consider it abandonware for the most part, notes that nearly every commit after the v1.9 tag is generated autonomously by an LLM, and warns that on any given day it might be completely broken. It points to upstream fuzzing for a stable and supported fuzzer.

### How do I limit Trinity to specific kernel taint flags?

Use the --kernel_taint option, short form -T, with a comma separated list of flag names. The documented example is -T BAD,WARN,MACHINE_CHECK, which reports only the BAD_PAGE, WARN and MACHINE_CHECK taints.

### What can Trinity do to my machine while it fuzzes?

The warnings state that it may seriously corrupt your files, including files writable on mounted network file shares, and that it may create network packets that can disrupt the local network. The intent is to keep packets to local LAN, multicast and broadcast addresses, so do not let them reach the internet.

### How do I run Trinity in a single syscall?

The -cN form runs one syscall with random inputs, and the -x option excludes a syscall you would rather not hit. The --group option, short form -g, restricts the run to a group such as vfs, vm, net, ipc, process, signal, io_uring, bpf, sched or time.

## Sources

- [Issues](https://github.com/kernelslacker/trinity/issues)
- [kernelslacker/trinity on GitHub](https://github.com/kernelslacker/trinity)
- [README](https://github.com/kernelslacker/trinity/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kernelslacker-trinity
