Reading a kernel, Part 2: where the interesting bits live
Once you know the layout, you can start asking the useful question: where does trust change hands? That is where the interesting code lives, and it is where careful reading pays off.
In Part 1 I wrote about orientation: how to read the directory structure as a design document, how to find the system call table and use it as an entry point, how to begin mapping subsystem boundaries. This part is about what you do once you have the map — specifically, how you find the places where the map matters most.
The most useful question to ask of any kernel path is: where does trust change? Where does the code transition from treating input as potentially malicious to treating it as verified? Where does a request that arrives with user-level permissions end up having kernel-level effects? Those transitions are where the interesting code lives, because they are where mistakes are consequential.
Privilege transitions
The most visible privilege transitions are explicit: a setuid binary executing, a system call crossing the user-kernel boundary, a process requesting an operation that requires a privilege check. The check functions — priv_check(9) in FreeBSD, the credential-checking code in OpenBSD, capable() in Linux — are documented, named, and relatively easy to audit.
The less visible transitions are the interesting ones. A file descriptor passed across a socket pair. A shared memory segment mapped into two processes at different privilege levels. A signal handler invoked with different credentials than the thread that registered it. These are places where the kernel's model of what is happening and what is actually happening can drift apart, and where that drift sometimes matters.
Reading for these transitions means following data, not just code. Pick a piece of data — a filename, a file descriptor number, a length field — and trace where it goes. Which functions receive it? Which validate it? Which pass it on without checking? The answer to that last question is often "most of them", and that is not necessarily wrong — you do not want every function in the kernel validating every input — but it means the question of which function is responsible for validation has to have a clear answer. When it does not, you have found something worth looking at.
Input parsing at interface boundaries
Every place where the kernel accepts data from outside itself — from userspace, from the network, from hardware — is an interface boundary. Interface boundaries are where parsing happens, and parsing is historically where things go wrong.
In the OpenBSD kernel, the unveil(2) implementation is a readable example of this kind of boundary code. It accepts a path from userspace and must validate, store, and later enforce restrictions based on that path. Reading kern_unveil.c — which I have had occasion to read rather carefully — shows how a careful implementation handles the boundary: explicit copying from userspace using copyinstr(), checking lengths, normalising paths before comparison. It also shows, in an #if 0 block that existed for some years, how a guard intended to prevent privilege escalation can be present in the source but absent from execution. The #if 0 does not make the code run. It documents intent without providing protection.
The 2am quality of attention
I want to say something honest about when this kind of reading is actually productive. It is not in two-hour blocks during the working day with meetings on either side. The reading that finds things happens in longer, quieter stretches — when the specific quality of attention that comes from continuous immersion in a codebase has had time to build up, when you have been in the same mental space long enough that the conventions feel natural and the anomalies register as wrong without your having to reason about why.
I do a lot of this work at 2am in Whitby. The town is quiet in a specific way at that hour — not silent, because there is always the sea, but quiet enough that the only thing demanding attention is the screen. I have found things in kernel source at 2am that I am confident I would not have found at 2pm, not because the code is different but because I am.
That is not a mystical claim about night-time. It is a practical observation about the relationship between extended attention and pattern recognition. If you want to read kernel code productively, protect the conditions that make extended attention possible.
The interesting bits in a kernel are rarely where the documentation points. They are where two subsystems meet and each assumes the other has done a check neither has actually done. Reading for that gap requires patience, a working map, and enough uninterrupted time for the conventions to become instinct. It is a particular kind of listening — and occasionally, something answers.