Skip to content

kxbox: three boot profiles, and the kasan one cannot exist - #63

Merged
tamnd merged 1 commit into
mainfrom
kxbox-boot-profiles
Sep 6, 2026
Merged

kxbox: three boot profiles, and the kasan one cannot exist#63
tamnd merged 1 commit into
mainfrom
kxbox-boot-profiles

Conversation

@tamnd

@tamnd tamnd commented Sep 6, 2026

Copy link
Copy Markdown
Owner

The M1 row for this reads "Three boot profiles: teaching, lockdep and kasan". Two of the three are here, the third one cannot exist, and finding out why turned up a config bug this repository has been shipping since the lockdep fragment was written.

The three profiles

kxbox/profiles.py is the list of kernels a lesson may ask for by name.

profile   built as    gives you                                                            costs
teaching  A-full      ftrace, kprobes, BTF, every /proc file the book reads, and modules   nothing beyond its size, which is what makes it the default
lockdep   D-lockdep   lockdep, lock statistics, and the sleeping in atomic context checks  every lock the kernel takes is recorded and checked, so timings mean nothing
memcheck  E-memcheck  KFENCE, page poisoning, unmapped freed pages, and the slab debugger  freed pages are unmapped and rewritten, so allocation timings mean nothing

There are two different things in this project called a profile and this file is where they are joined. A build profile is a row in kxbox/kernel/pin.toml, there are six of them, they are named A-full through E-memcheck and they exist so somebody building kernels can settle the kill criterion. A boot profile is what a lesson writes. A lesson author should not have to know that the teaching kernel is currently built as A-full, or that it might be built as C-longterm next month if the first one turns out to be too big for a tab.

A name nothing builds now raises

boot(profile=...) took a free string that nothing compared to anything. The string was carried around and printed in the banner, so this worked:

box = kxbox.boot("lockdpe")   # before this PR

It printed a banner saying lockdep and ran the teaching kernel. Nothing after that would have failed. The lesson would boot, run, find no lock ordering problem, and a reader would conclude that the code they are looking at has none. A clean answer that means nothing is worse than a crash, because a crash gets fixed.

Unknown: no profile called 'lockdpe', there is teaching, lockdep, memcheck

The banner also says what the profile turns on and what it takes for it, which is the line that stops somebody timing a function on the lockdep kernel and reporting the number:

kxbox: corpus backend, lockdep profile
       no recordings for this profile yet
       lockdep gives you lockdep, lock statistics, and the sleeping in atomic context checks
       and costs every lock the kernel takes is recorded and checked, so timings mean nothing
       not a running kernel: KXBOX_DISABLE is set
       nothing here is evidence

There is no kasan profile

The milestone asked for one. arch/x86/Kconfig on the pinned kernel has this, and it is the whole answer:

	select HAVE_ARCH_KASAN			if X86_64
	select HAVE_ARCH_KASAN_VMALLOC		if X86_64
	select HAVE_ARCH_KFENCE

Tier 0 is 32 bit x86 because v86 is a 32 bit emulator. menuconfig KASAN depends on HAVE_ARCH_KASAN, nothing selects that on i386, so CONFIG_KASAN is not a symbol that exists on this build. It does not appear in menuconfig and it cannot be set. There is a second reason on top of the architecture that would apply on a 64 bit build too: KASAN also depends on !SLUB_TINY, and the Tier 0 base is tinyconfig, which sets it.

KFENCE is selected with no condition on it, so 32 bit x86 does get that. It is the sampling cousin of KASAN and catches a subset of the same bugs, with out of band redzones on a rotating handful of allocations rather than a shadow byte for every eight bytes of memory. config/memcheck.config is KFENCE plus page poisoning, DEBUG_PAGEALLOC, the slab debugger and the object and list checks, and that is the memory checking a 32 bit kernel can actually do.

The profile is named memcheck rather than kasan because calling it kasan would be the same lie in a different file.

The bug this turned up

kxbox/kernel/config/lockdep.config has been asking for this since the day it was written:

CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y

The kernel that came out of it has neither. Every KASAN line in the generated config for that profile:

$ grep KASAN kxbox/kernel/build/D-lockdep/config
1831:CONFIG_CC_HAS_KASAN_GENERIC=y

That one line says the compiler could have done it if the architecture had asked. There is no CONFIG_KASAN line at all, not even a # CONFIG_KASAN is not set, because a symbol whose dependencies are unmet is not turned off, it is dropped, and Kconfig drops it without printing anything. The build succeeded, the image booted, the measurements in RESULTS.md are honest, and the fragment went on claiming a feature no kernel here has ever had. The failure was invisible from every direction at once.

tools/kconfig now catches it, without a toolchain and in about a second:

kxbox/kernel/pin.toml#D-lockdep: sets CONFIG_KASAN on a 32 bit profile, and corpora/source/pinned/arch/x86/Kconfig.excerpt has the architecture selecting HAVE_ARCH_KASAN only if X86_64, so the symbol does not exist here and Kconfig will drop it without a word

The condition is not written into the checker. gates() reads it off the committed excerpt, so a kernel that starts supporting KASAN on i386 turns the check off by replacing a file rather than by somebody remembering this exists. The check is skipped entirely unless the merged fragments say the build is 32 bit, which they say in either of two ways, # CONFIG_64BIT is not set or CONFIG_X86_32=y.

Two new corpus artefacts

Both are real files out of the pinned tarball rather than anything written for this.

corpora/source/pinned/arch/x86/Kconfig.excerpt is 32 lines: the opening of config X86 and the run of HAVE_ARCH_ selects. The full file is 3138 lines and almost none of it is about this project. The kernel's own comment three lines above config X86 is kept in the excerpt because it makes the same point, which is that the if X86_64 markers are about what has been ported rather than about what is possible.

corpora/source/pinned/lib/Kconfig.kfence is the whole 99 line file, because it is the declaration of everything the memcheck profile turns on.

Three of its lines came back unparsed, which is the useful part. if KFENCE at line 26, range 1 65535 at line 41 and endif # KFENCE at line 99. Rather than write the number down as acceptable, kxray/source/kconfig.py learned to read them.

if and endif are a condition stack now, not presentation like menu is. Six of the eight KFENCE symbols sit inside if KFENCE and five of those six have no depends on line of their own, so reading one of those symbols on its own tells you it has no conditions at all and leaves you unable to say why it is missing from a build. Symbol.within is what the block said and Symbol.depends is what the symbol said, and Symbol.conditions puts them back together with the block first, because the block is the outer condition and reads that way on the page.

range is a property now. KFENCE_NUM_OBJECTS carries range 1 65535, and that is the only place in the tree where the kernel says out loud what that number is allowed to be.

The baseline moves from 39 artefacts and 7837 lines to 41 and 7968. That is 32 plus 99, which is exactly the two new files and nothing else.

What is not in here

Running tools.kconfig --verify against the built D-lockdep config reports 11 more problems beyond the 2 KASAN ones fixed here, and they are all genuine. CONFIG_MATH_EMULATION, CONFIG_VIRTIO, CONFIG_VIRTIO_PCI, CONFIG_NET_9P, CONFIG_NET_9P_VIRTIO, CONFIG_9P_FS, CONFIG_9P_FS_POSIX_ACL, CONFIG_PAHOLE_HAS_SPLIT_BTF, CONFIG_SCHED_DEBUG, CONFIG_TASK_DELAY_ACCT and CONFIG_MEMCG. Two lines of the built config explain most of them: # CONFIG_NET is not set at line 748 and # CONFIG_CGROUPS is not set at line 159.

They are not fixed here on purpose. This PR is about one row of the milestone, and the consequences of turning CONFIG_NET on cannot be checked without rebuilding four kernels and measuring them again. A follow up issue with the evidence is better than a config change nobody can verify.

E-memcheck is in pin.toml and has not been built yet, so its row in RESULTS.md says not measured rather than a number somebody guessed.

Checks

Every gate, locally: ruff check and format, 1475 pytest tests, 7 skipped, the node tests, lintprose across 37 files, and the check runs of diagrams, nbbuild, sitebuild, kxbox, vendor, baseline, claimledger, coverage, bpc, kconfig, refcheck, lintnb and kxmanim. tools.kconfig reports 7.2.2 pinned, 6 profile(s) clean.

A lesson asks for a kernel by name. That name used to be a free string that
nothing ever compared to anything, so boot("lockdpe") worked, printed a banner
saying lockdep, and ran the teaching kernel. A lesson about lock ordering would
then find no lock ordering problems and a reader would conclude the code has
none. kxbox/profiles.py is the registry of the three names that exist, boot()
raises on anything else and says what the three are, and the banner prints what
the profile turns on and what it costs.

The third profile is called memcheck and not kasan. arch/x86/Kconfig on the
pinned kernel selects HAVE_ARCH_KASAN only if X86_64 and selects
HAVE_ARCH_KFENCE with no condition, and Tier 0 is 32 bit because v86 is a 32 bit
emulator. KASAN also depends on !SLUB_TINY and the base here is tinyconfig,
which sets it. So the profile is built out of KFENCE, page poisoning,
DEBUG_PAGEALLOC and the slab debugger.

Working that out turned up that config/lockdep.config had been setting
CONFIG_KASAN=y and CONFIG_KASAN_GENERIC=y since it was written, and no kernel
this project has built ever had them. build/D-lockdep/config has no CONFIG_KASAN
line at all. Kconfig drops a symbol whose dependencies are unmet and says
nothing, so the build succeeded and the image booted and the fragment went on
claiming a feature that was never there. The two lines are gone and
tools/kconfig now fails a 32 bit profile that asks for an architecture gated
symbol, reading the condition off a committed excerpt of arch/x86/Kconfig rather
than having it written into the checker, so a kernel that starts supporting
KASAN on i386 turns the check off by itself.

Two new corpus artefacts, both real files out of the pinned tarball: the excerpt
above, and the whole of lib/Kconfig.kfence. Three lines of the kfence file were
unparsed, which is what taught kxray/source/kconfig.py to read if and endif as a
condition stack and range as a property. Six of the eight KFENCE symbols sit
inside if KFENCE and five of those have no depends on line of their own, so a
reader looking at one symbol alone cannot say why it is off. Symbol.conditions
is where the block condition and the symbol's own dependencies come back
together.

Baseline moves from 39 artefacts and 7837 lines to 41 and 7968, which is the two
new files and nothing else.
@tamnd
tamnd merged commit 533a9f6 into main Sep 6, 2026
3 checks passed
@tamnd
tamnd deleted the kxbox-boot-profiles branch September 6, 2026 09:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant