Skip to content

Commit 89547d0

Browse files
committed
arch: model the module band where the kernel actually puts it (ppc32, riscv32)
module_base_bounds emits the band edges at CONF_INFERRED, so a band that does not cover the real module region puts a guaranteed bound on Q_MODULE_BASE that excludes the truth -- and the region check rejects every genuine module leak on top of that. Two arches had one. riscv32 declared the band as the upper VAS, floor at PAGE_OFFSET. The kernel puts it below: MODULES_VADDR is VMALLOC_START, which is PAGE_OFFSET - VMALLOC_SIZE, and VMALLOC_SIZE is KERN_VIRT_SIZE >> 1. rv32 is sv32 only, so PGDIR_SHIFT 22 and PTRS_PER_PGD 1024 fix that at 512 MiB exactly, with no configuration to take a union over. The declared band and the real one were disjoint. ppc32 declared the 8xx / book3s32 window, PAGE_OFFSET - 256 MiB upward. Only those two platforms define MODULES_VADDR; every other one, PPC_85xx among them -- the only ppc32 platform that randomizes -- allocates from the shared vmalloc window instead, whose edges are runtime values above PAGE_OFFSET. Nothing at compile time distinguishes the two kernels, so the band is now the union of both. The projection clamped its floor to KERNEL_VIRT_VAS_START, which on both of these arches IS PAGE_OFFSET -- so a band below it collapsed to a point the moment PAGE_OFFSET was pinned, discarding what the arch headers had just been corrected to say. The floor only ever needed protecting against the wrap a subtracting relation produces on a low split, which is the test module_base_bounds already applies to the same edges. arm32 is untouched either way: its VAS floor is the lowest admissible split, not PAGE_OFFSET. The band test mirrored the clamp rather than asserting the relation, so it would have agreed with either behaviour. It now asserts the projected floor is the relation's own value, and fails on ppc32 and riscv32 -- not arm32 -- if the clamp returns.
1 parent 3b4fe25 commit 89547d0

4 files changed

Lines changed: 62 additions & 22 deletions

File tree

src/include/kasld/arch/ppc32.h

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,33 @@
3939
// Above this, addresses fall in the I/O or fixmap region.
4040
#define KERNEL_VIRT_TEXT_MAX 0xf0000000ul
4141

42-
// Modules are located below kernel: PAGE_OFFSET - 256MiB (0x10000000)
43-
// https://elixir.bootlin.com/linux/v6.1.1/source/arch/powerpc/include/asm/book3s/32/pgtable.h#L214
44-
// https://elixir.bootlin.com/linux/v6.1.1/source/arch/powerpc/include/asm/nohash/32/mmu-8xx.h#L173
45-
// Stated as a relation to PAGE_OFFSET rather than as fixed addresses, so the
46-
// band cannot drift from the definition it instantiates. ppc32 is
47-
// PAGE_OFFSET_INVARIANT, so the re-derivation the flag enables is a no-op here
48-
// -- it declares the relation, it does not predict movement.
42+
// ppc32 places modules in one of two entirely different regions, decided by
43+
// the platform, and the analysing binary cannot tell which kernel it faces --
44+
// so the band is the union of both.
45+
//
46+
// 8xx and BOOK3S_32 carve a dedicated window immediately below the linear map
47+
// (task_size_32.h): MODULES_END is PAGE_OFFSET (8xx) or PAGE_OFFSET rounded
48+
// down to 256 MiB (book3s32), with MODULES_VADDR = MODULES_END -
49+
// CONFIG_MODULES_SIZE * 1 MiB. That Kconfig is `range 1 256`, so 256 MiB below
50+
// PAGE_OFFSET is the lowest floor any such kernel can have.
51+
//
52+
// EVERY OTHER ppc32 platform -- including PPC_85xx/e500, the only one with
53+
// CONFIG_RANDOMIZE_BASE -- leaves MODULES_VADDR undefined and allocates from
54+
// the shared vmalloc window instead (mm/mem.c execmem_arch_setup: `#else start
55+
// = VMALLOC_START; end = VMALLOC_END;`). On nohash/32 VMALLOC_START derives
56+
// from runtime high_memory and so lies ABOVE PAGE_OFFSET, and VMALLOC_END is
57+
// ioremap_bot, which moves down from IOREMAP_TOP at runtime. Neither edge is a
58+
// compile-time constant, so the ceiling is the top of the address space.
59+
//
60+
// The union is therefore [PAGE_OFFSET - 256 MiB, top of VAS]. Only the floor
61+
// is a PAGE_OFFSET relation. ppc32 is PAGE_OFFSET_INVARIANT, so the
62+
// re-derivation the flag enables is a no-op here -- it declares the relation,
63+
// it does not predict movement.
64+
// https://elixir.bootlin.com/linux/v7.2/source/arch/powerpc/include/asm/task_size_32.h
65+
// https://elixir.bootlin.com/linux/v7.2/source/arch/powerpc/mm/mem.c
4966
#define MODULES_RELATIVE_TO_PAGE_OFFSET 1
5067
#define MODULES_START_FOR(po) ((po) - 0x10000000ul)
51-
#define MODULES_END_FOR(po) (po)
68+
#define MODULES_END_FOR(po) (KERNEL_VIRT_VAS_END)
5269
#define MODULES_START MODULES_START_FOR(PAGE_OFFSET) // 0xb0000000ul
5370
#define MODULES_END MODULES_END_FOR(PAGE_OFFSET)
5471
#define MODULES_RELATIVE_TO_TEXT 0

src/include/kasld/arch/riscv32.h

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,26 @@
3737
// Above this, addresses fall in the fixmap/vmalloc region.
3838
#define KERNEL_VIRT_TEXT_MAX 0xf0000000ul
3939

40-
// riscv32 has no separate module window: modules share the upper VAS, whose
41-
// floor is the linear-map base and whose ceiling is the top of the address
42-
// space regardless of where that base sits. Only the floor is a PAGE_OFFSET
43-
// relation; riscv32 is PAGE_OFFSET_INVARIANT, so nothing moves in practice.
40+
// Modules share the vmalloc window, which on rv32 sits immediately BELOW the
41+
// linear map, not above it:
42+
//
43+
// MODULES_VADDR = VMALLOC_START = PAGE_OFFSET - VMALLOC_SIZE
44+
// MODULES_END = VMALLOC_END = PAGE_OFFSET
45+
//
46+
// VMALLOC_SIZE is KERN_VIRT_SIZE >> 1, and KERN_VIRT_SIZE is
47+
// (PTRS_PER_PGD / 2 * PGDIR_SIZE) / 2. rv32 is sv32 only, so PGDIR_SHIFT is
48+
// 22 (PGDIR_SIZE 4 MiB) and PTRS_PER_PGD is 4096/4 = 1024, giving
49+
// KERN_VIRT_SIZE = 1 GiB and VMALLOC_SIZE = 512 MiB. Exact, with no
50+
// configuration variance to take a union over.
51+
//
52+
// Stated as a relation to PAGE_OFFSET rather than as fixed addresses so the
53+
// band cannot drift from the definition it instantiates. riscv32 is
54+
// PAGE_OFFSET_INVARIANT, so the re-derivation the flag enables is a no-op
55+
// here -- it declares the relation, it does not predict movement.
56+
// https://elixir.bootlin.com/linux/v7.2/source/arch/riscv/include/asm/pgtable.h
4457
#define MODULES_RELATIVE_TO_PAGE_OFFSET 1
45-
#define MODULES_START_FOR(po) (po)
46-
#define MODULES_END_FOR(po) (0xfffffffful)
58+
#define MODULES_START_FOR(po) ((po) - 0x20000000ul)
59+
#define MODULES_END_FOR(po) (po)
4760
#define MODULES_START MODULES_START_FOR(PAGE_OFFSET)
4861
#define MODULES_END MODULES_END_FOR(PAGE_OFFSET)
4962
#define MODULES_RELATIVE_TO_TEXT 0

src/orchestrator.c

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3960,12 +3960,16 @@ static void engine_sync_authoritative(const struct engine *e) {
39603960
if (po->lo && po->lo <= po->hi && (pinned || default_excluded)) {
39613961
unsigned long lo = MODULES_START_FOR(po->lo);
39623962
unsigned long hi = MODULES_END_FOR(po->hi);
3963-
/* The floor can underflow past the bottom of the kernel VAS when the
3964-
* window reaches down to the lowest admissible split; clamp rather than
3965-
* draw a band below the address space the map covers. */
3966-
if (lo < (unsigned long)KERNEL_VIRT_VAS_START)
3967-
lo = (unsigned long)KERNEL_VIRT_VAS_START;
3968-
if (hi > lo) {
3963+
/* MODULES_START_FOR subtracts, so a PAGE_OFFSET window reaching down to
3964+
* the lowest admissible split can wrap the floor past 0 and leave it
3965+
* near ULONG_MAX. Reject a wrapped floor; do NOT clamp it to
3966+
* KERNEL_VIRT_VAS_START. On every arch that carves the module band out
3967+
* of vmalloc the band legitimately sits BELOW PAGE_OFFSET, and on those
3968+
* KERNEL_VIRT_VAS_START *is* PAGE_OFFSET -- clamping there would discard
3969+
* the whole band and reject every genuine module leak, the exact failure
3970+
* the union contract above exists to prevent. Same wrap test that
3971+
* rules/module_base_bounds.c applies to these edges. */
3972+
if (lo <= po->lo && hi > lo) {
39693973
mod_union_lo = lo;
39703974
mod_union_hi = hi;
39713975
layout.modules_start = lo;

tests/test_kasld.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1916,10 +1916,16 @@ static void test_engine_sync_module_band_follows_page_offset(void) {
19161916
engine_sync_authoritative(&e);
19171917

19181918
#if MODULES_RELATIVE_TO_PAGE_OFFSET
1919+
/* The projected floor is the relation's own value, NOT clamped up to
1920+
* KERNEL_VIRT_VAS_START. Arches that carve the module band out of vmalloc
1921+
* put it below PAGE_OFFSET, and on those KERNEL_VIRT_VAS_START *is*
1922+
* PAGE_OFFSET -- clamping there would collapse the band and reject every
1923+
* genuine module leak. Only a wrapped floor (a PAGE_OFFSET window reaching
1924+
* below the band's own width) is rejected, and `moved` is above the
1925+
* compile-time PAGE_OFFSET so it cannot wrap here. */
19191926
unsigned long want_lo = MODULES_START_FOR(moved);
19201927
unsigned long want_hi = MODULES_END_FOR(moved);
1921-
if (want_lo < (unsigned long)KERNEL_VIRT_VAS_START)
1922-
want_lo = (unsigned long)KERNEL_VIRT_VAS_START;
1928+
assert(want_lo <= moved); /* no wrap in this fixture */
19231929
assert(want_hi > want_lo);
19241930
assert(layout.modules_start == want_lo);
19251931
assert(layout.modules_end == want_hi);

0 commit comments

Comments
 (0)