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 worksx86 privilege levels (rings) separate kernel code (ring 0) from user code (ring 3). Only ring 0 may program MMU tables, disable interrupts, or access device ports directly. Syscalls are deliberate gateways upward.
In protected mode teaching:
For example, a user program executing cli faults with #GP because interrupt control is ring-0 only.
Ring 0 versus Ring 3 is the hardware boundary that made the Meltdown vulnerability so severe in 2018: it broke the assumption that Ring 3 code could never read Ring 0 memory, forcing every OS vendor to ship KPTI (kernel page-table isolation) patches with a real performance cost. Hypervisors like KVM add a Ring -1 on top of this exact model, which is why understanding CPL transitions here also explains how virtual machines stay isolated from each other.
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 map which operations in your mini-kernel require ring 0 and which libc calls trigger privilege elevation. This exercise requires tying GDT entries from an earlier task to effective privilege checks.
Model the hardware rules that separate ring 3 (user) from ring 0 (kernel) on x86-64: which instructions each ring may execute, how control enters and leaves the kernel, and which memory accesses the MMU allows. The memory rules include the kernel-side protections SMEP (the kernel may not execute user pages) and SMAP (the kernel may not touch user pages unless it has run stac).
cLoading…
The start state is CPL 3, IOPL 0, SMEP on, SMAP on, AC 0. CR0.WP is always set, so even ring 0 cannot write read-only pages.
| Instruction | Rule |
|---|---|
hlt, lgdt, mov cr3, rdmsr, wrmsr, invlpg | CPL 0 only, otherwise #GP(0): privileged instruction needs CPL 0 |
cli, sti, in PORT, out PORT | Allowed if CPL <= IOPL, otherwise #GP(0): CPL c > IOPL i |
stac / clac | CPL 0 only (sets/clears AC, prints AC = n), otherwise #UD: stac is only valid at CPL 0 |
syscall | kernel entry via MSR_LSTAR, CPL c -> 0 |
int N | Gates 3, 4 and 0x80 have DPL 3, all others DPL 0. If CPL > DPL: #GP(0xE): gate N has DPL d < CPL c, where E = N*8+2. Otherwise through IDT gate N, CPL c -> 0 |
sysret | CPL 0 only. Always returns to CPL 3 |
iret | CPL 0 only. Returns to the CPL saved by the matching syscall/int (0 if there is none) |
rdtsc, cpuid, nop | ok |
| anything else | #UD: unknown instruction |
Entering the kernel saves the old CPL and clears AC. The #GP in the table for sysret/iret reads #GP(0): sysret needs CPL 0.
Check in this order, and report the first failure as #PF error 0xE: reason. The error code has bit 0 set if the page is present, bit 1 for a write, bit 2 if CPL is 3, and bit 4 for an instruction fetch.
page not presentuser access to supervisor page (CPL 3 on a kernel page)write to read-only page (CR0.WP is set)instruction fetch from NX pageSMEP: kernel may not execute user pages (CPL 0 fetch from a user page, with SMEP on)SMAP: kernel access to user page without stac (CPL 0 read or write of a user page, with SMAP on and AC 0)Otherwise the result is ok (user rw page), ok (supervisor ro page) and so on.
cLoading…
An unknown set key prints set: unknown setting X.
Input:
cLoading…
Output:
cLoading…
Hidden tests cover IOPL changes, in/out and cli in both rings, nested int/iret returning to the right CPL, int 3 from user mode, privileged gates, SMEP and SMAP switched off, stac/clac around user copies, NX kernel pages, supervisor read-only writes, missing pages, and unknown instructions and settings.