Linux kernel syscall — source review
TL;DR: Linux kernel syscalls are the user/kernel boundary. Audit risks: missing
copy_from_user/copy_to_user(raw user-pointer deref), TOCTOU between check and use, integer overflow in length math, missingcapable()/ namespace checks, unchecked file-descriptor lookups, and reference-count errors in object lifecycle. This is the OSEE/Pwn2Own-adjacent Linux audit floor. Companion to linux-kernel-architecture and windows-driver-ioctl-audit.
Tooling for the read
- Cross-reference:
cscope,git grep,bootlin elixir,lxr, or VSCode + clangd. - Build with
compile_commands.jsonso clangd resolves macros (kernel uses heavy macro work). - A local checkout of the kernel matching your target version.
1
2
make defconfig
make compile_commands.json
The syscall entry
1
2
3
SYSCALL_DEFINE3(my_op, int, fd, const void __user *, buf, size_t, count) {
...
}
The __user annotation marks user-space pointers; sparse / clang static analysis flags missing copies. Audit:
1
grep -rn '__user' fs/ kernel/ drivers/your-driver/
Bug class 1 — raw user-pointer deref
1
2
3
4
SYSCALL_DEFINE2(badsysc, void __user *, buf, size_t, len) {
char tmp[64];
memcpy(tmp, buf, len); // BAD: buf is user pointer, len attacker-controlled
}
buf is a user-space address. memcpy will follow it as a kernel pointer — possibly reading kernel memory if the user supplied a kernel address; possibly crashing.
Correct:
1
2
if (copy_from_user(tmp, buf, min(len, sizeof(tmp))))
return -EFAULT;
copy_from_user checks address validity and handles faults.
Bug class 2 — integer overflow
1
2
3
size_t total = count * sizeof(struct entry);
struct entry *e = kmalloc(total, GFP_KERNEL);
if (copy_from_user(e, ubuf, total)) ...
Same as user-mode pattern. Wrap-around total allocates small; the copy_from_user crashes with EFAULT — or succeeds against a smaller-than-expected region and corrupts adjacent slab.
Mitigation: kmalloc_array(count, sizeof(struct entry), GFP_KERNEL) checks the multiplication. Or check_mul_overflow.
Bug class 3 — signed/unsigned
1
2
3
4
5
SYSCALL_DEFINE2(bad, void __user *, p, int, sz) {
if (sz > MAX) return -EINVAL;
void *kbuf = kmalloc(sz, GFP_KERNEL); // sz is int; negative passes check, becomes huge cast to size_t
...
}
Use size_t or bound sz >= 0.
Bug class 4 — TOCTOU between check and use
1
2
if (!user_can_access(file_path)) return -EACCES;
fd = open(file_path); // path may have changed (symlink swap)
Inside the kernel this is rare in syscall code (most do the operation once) but appears in code that re-resolves names after permission decisions. Use file-descriptor-based APIs once the check has passed.
Bug class 5 — file-descriptor lookup races
1
2
3
4
struct fd f = fdget(fd);
if (!f.file) return -EBADF;
... // operations on f.file
fdput(f);
Patterns to flag:
fdget(fd)without matchingfdput(f)— refcount leak.- Touching
f.fileafterfdput— UAF. - Skipping
fdget_posfor ops that need the position lock.
Bug class 6 — capable() in the wrong place
1
2
3
4
static int handle(...) {
if (!capable(CAP_SYS_ADMIN)) return -EPERM; // OK
return do_thing(...);
}
vs.
1
2
3
4
5
static int handle(...) {
int ret = do_thing(...); // BAD: side effect before check
if (!capable(CAP_SYS_ADMIN)) return -EPERM;
return ret;
}
The capability check must precede any side effect. In a user namespace, you often want ns_capable(user_ns, CAP_*), not the bare capable. Containers escalate via the difference.
Bug class 7 — namespace confusion
1
2
3
if (capable(CAP_NET_ADMIN)) { // BAD: in a user namespace, this evaluates true for namespace owner
do_root_thing();
}
In a user namespace, the namespace creator has all capabilities within the namespace. Operations that affect the host kernel should use ns_capable(&init_user_ns, ...). See user-namespace-attacks.
Bug class 8 — reference counting
kref, get_task_struct / put_task_struct, dget / dput. Any missing put is a leak; missing get + put-after-free is UAF.
1
2
3
get_task_struct(t);
... // critical section
put_task_struct(t);
Audit:
- Every get paired with a put.
- Cleanup paths (error returns) also put.
- Locks held across allocations released before sleep paths.
Bug class 9 — sleeping under a spinlock
kmalloc with GFP_KERNEL can sleep. Under spin_lock, sleeping kills the system. Static checkers (sparse / smatch) flag; reviewers should too.
1
2
spin_lock(&l);
p = kmalloc(sz, GFP_KERNEL); // BAD: can sleep
Use GFP_ATOMIC (smaller pool) or move alloc outside the lock.
Bug class 10 — uninitialised stack / kernel heap
1
2
struct ctx c;
copy_to_user(out, &c, sizeof(c)); // BAD: c is uninitialised → infoleak
Bound with memset(&c, 0, sizeof(c)). Same for kmalloc — prefer kzalloc when the buffer will be copied out.
Audit workflow
- Pick a subsystem (filesystem, net stack, ioctl path, BPF, etc.).
- List syscalls + ioctls exposed.
- For each, trace: user input → conversion → operation → return.
- Note every
copy_from_user/get_userfor length and target validation. - Note every
capable()for namespace correctness. - Note every alloc/free for refcount/UAF.
- Note every length arithmetic for overflow.
Tools for finding bugs after the read
- Smatch / sparse — static analysers tuned for kernel idioms.
- CodeQL — increasingly popular for kernel audit; Anthropic / GP project ecosystems share queries.
- syzkaller — coverage-guided fuzzer with syscall descriptions; finds bugs you missed at scale.
- KASAN / UBSAN — runtime sanitisers for kernel; turn on in test builds.
OSEE relevance and beyond
Modern kernel exploitation requires bypassing SMEP, SMAP, KPTI, KASLR, and CFI. The audit phase is finding the bug; the exploitation phase is converting it into a primitive that survives mitigations. See linux-kernel-architecture and kernel-exploits-linux.
Source-audit checklist
- Every user pointer accessed via
copy_from_user/copy_to_user/get_user/put_user. - Length arithmetic uses overflow-checked helpers (
check_mul_overflow,kmalloc_array). - Lengths held as
size_tor bounded forint. capable()precedes the side effect, namespace-aware variant where appropriate.- Every fd-get paired with put; refcount errors free of bugs.
- Buffers copied out are zeroed before populate.
- No sleeping under spinlocks.