Skip to content

Test expansion - #23

Merged
tzussman merged 23 commits into
mainfrom
test-expansion
Oct 10, 2026
Merged

tzussman merged 23 commits into
mainfrom
test-expansion

Conversation

@tzussman

Copy link
Copy Markdown
Owner

No description provided.

The shell script could only grep for a line of output, needed sleeps and
sudo gymnastics to sequence things, and left modules loaded when a test
failed. tests/harness.py drives kmodleak and parses its report into
stacks, allocations and frames so that tests can assert on them
directly, and the fixtures in conftest.py build the sample modules as
the invoking user, unload whatever a test left behind, and attach dmesg
to failures. Run it as root with: sudo python3 -m pytest tests
Tests should not depend on the exact number of stacks in a report or on
the names of kernel internals, which vary across kernel versions and
with what the kernel allocates on a module's behalf. Report.direct_stacks()
finds the allocations made directly by a named module function, with
only allocator and tracepoint frames above it, and trace() wraps the
load and unload sequence.
The parameter callback allocates after init, from text that stays mapped
until unload, so this exercises attribution and symbol resolution from
the kallsyms snapshot rather than the in-kernel init symbols. The
snapshot is taken when userspace sees the module_load event, which a
fast insmod and rmmod can beat, so the variant without a pause is an
expected failure until module symbols are resolved in the kernel too.
A private cache cannot be leaked across unload, since kmem_cache_destroy()
refuses to destroy a cache with live objects, so the object is held until
exit and observed through the report printed on SIGINT.
The module is freed from do_init_module()'s failure path without an exit
function, and the kretprobe on load_module() fires after module_free.
insmod leak-dash.ko loads a module named leak_dash, but kmodleak compares
the name given on the command line byte for byte.
kfree_rcu() batches the free for up to several seconds, past module_free,
and completes it through kmem_cache_free_bulk(), which fires no kfree
tracepoint, so kmodleak reports the object as leaked.
The kernel derives a module's name from KBUILD_MODNAME, which replaces
dashes in the file name with underscores, and modprobe and rmmod accept
either form. kmodleak compared the name byte for byte, so a dashed name
never matched the loaded module and the already-loaded check never
found it. Also enable the test for it.
module_loaded() took a non-zero core text size as the sign that the
traced module had been loaded. A module with no functions outside
__init, such as one without an exit function, has an empty MOD_TEXT
region on x86, so nothing was ever attributed to it. arm64 hid this
because its PLT and ftrace trampoline sections make the region
non-empty. Use a flag set by the module_load handler instead, and
rename the event pointers in the module handlers that shadowed it.

Found by test_leak_from_failing_init in CI.
@tzussman
tzussman merged commit 19bc19a into main Oct 10, 2026
17 of 18 checks passed
@tzussman
tzussman deleted the test-expansion branch October 10, 2026 17:05
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