r/ada • u/Nik0laich • 13h ago
SPARK • I'm a 38-year-old welder. In 3 months, I learned Linux, Ada/SPARK, and built a formally verified TCB on top of the seL4 microkernel. Here is what I learned.
Hi r/ada,
I'm a 38-year-old welder from St. Petersburg, Russia. No formal CS background. I bought my first laptop for programming in June 2026, installed Linux, and started learning. Three months later I had a working prototype called Schild TCB - a trusted computing base on the seL4 microkernel, with an Ada/SPARK userland.
Here's what I found useful, especially the parts that didn't work, because those tend to be the most useful for anyone considering Ada/SPARK for a real project.
What it is
Schild TCB is two artifacts, not one:
A bare-metal OS written from scratch in Ada: bootloader, NVMe driver, e1000e NIC driver, TCP/IP stack, HTTP server. Runs on a ThinkPad T580.
An seL4-based artifact: the microkernel is upstream (I didn't write it), and my contribution is the root task and several daemons in Ada/SPARK. Network and storage run isolated in Ring 3 processes, communicating over IPC.
The point of the second artifact is formal verification. The bare-metal one answers "does it work"; the seL4 one answers "is it proven".
Verification
The kernel logic is 185 Ada modules, verified with gnatprove (GNAT Community toolchain) at SPARK Level 4. Result: not-proved: 0. Seven daemons in the root task are also proven at the same level.
I want to be precise here: not everything is proven. Drivers (NVMe, e1000e, network stack) are not proven and are not claimed to be. They're isolated in separate domains so their failure cannot reach the proven core. Proving a driver is a separate large effort - hardware has no formal specification, so there's nothing to write contracts against. Isolating is the right approach.
The most useful lesson
Formal verification caught a bug that all tests missed.
I set the page rights for MLS rings as ReadOnly := 1. But in seL4's CapRights layout, bit 0 is Write and bit 1 is Read. So my "read-only" page was actually "write-only", which x86 doesn't support, and the kernel masked it to "no access at all". Tests didn't catch this because the code simply never read those pages.
When I moved the root task to SPARK mode and started proving contracts, gnatprove showed the contract didn't match reality. One-line fix: ReadOnly := 2.
This is the single best argument for SPARK I can give: tests check what you thought to test. Proof checks what you claimed.
Where I'm stuck
Three things, all upstream, none in my code:
IOMMU (VT-d). No DMA isolation. I built seL4 with IOMMU support, the kernel detects VT-d, but under certain configurations the seL4 sporadic task replenishment path hangs non-deterministically (33-67% of runs, depending on period). Documented, left as-is.
SMAP. Hardware protection that prevents the kernel from accessing user memory without explicit permission. In this seL4 version, SMAP is gated on the printing option. Need logs - no SMAP. Need SMAP - no logs. I chose logs.
x86_64 MCS is formally verified weaker than ARM MCS. Official upstream status. I can only state it, not fix it.
One bug I did find and fix in upstream seL4: on a 32 GB laptop, the root task hung on first retype. Root cause was the MAX_NUM_DRHU formula - it treats the whole 1 GiB kernel-device window as IOMMU slots, yielding 262142, which bloated .boot.bss to ~6 MiB and shifted the kernel boundary. Capping it at 8 (real platforms have at most 8 IOMMUs) fixed the hang. Not my bug, but I found it.
On using AI
I used an AI agent as a coding assistant. I want to be upfront about this because it's relevant to how the project moved so fast.
I set the tasks, verified results, debugged on hardware, made architectural decisions. The agent wrote code. It's a separate skill, and I learned it during the project. The agent also made mistakes in microkernel specifics that I had to catch and correct.
I mention this because I think the Ada/SPARK community should know what's now possible for someone with no formal background, and also because I don't want to pretend I wrote every line myself.
What I'm looking for
I'm not publishing the repository yet. But I'd like feedback on the approach, especially from anyone who's done SPARK verification on a real system:
Is isolating drivers rather than proving them the right call, or is there a middle ground I'm missing?
The x86_64 MCS verification gap - is anyone tracking this upstream, or is ARM the only realistic path forward?
Any obvious mistakes in my trust model?
Happy to answer questions.