Skip to content

Commit 5c4fedb

Browse files
committed
ppc64: pin the module region base from the MMU mode and page size
64-bit PowerPC defines no MODULES_VADDR, so modules come from vmalloc, whose base is one of three compile-time constants selected at boot. The band spans all of them, leaving Q_MODULE_BASE at 683 billion candidates for a value that is fully determined. Both selectors are observable without privilege: the translation mode from /proc/cpuinfo's "MMU" line, the page size from sysconf. Radix pins whatever the page size; hash needs both, since H_KERN_VIRT_START differs between hash-4k (0xc0003d0000000000) and hash-64k (0xc008000000000000). The mode is trustworthy for this: platform code prints it from radix_enabled(), and that same feature bit selects __vmalloc_start, so it states the live mode rather than a CPU capability. Positive evidence only. Book3E's base is not pinned — it is identified solely by the ABSENCE of the MMU line, which a restricted /proc/cpuinfo mimics exactly. An unrecognised mode or page size behaves the same way. The band bound remains in every declining case. Confirmed on ppc64le-alpine-6.12: the row resolves to a single candidate.
1 parent a766ff2 commit 5c4fedb

8 files changed

Lines changed: 232 additions & 2 deletions

File tree

docs/usage.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,9 @@ class.
152152
The module region exists everywhere, and is resolved from whichever directions
153153
the architecture affords. Where its placement is fully determined the row is a
154154
single address: a fixed segment base (MIPS), a base computed from the hardware
155-
virtual-address width (LoongArch), or one derived from a resolved `PAGE_OFFSET`
156-
(arm32, ppc32, riscv32). Where something is randomized or unknown it is a
155+
virtual-address width (LoongArch), one selected by the translation mode and
156+
page size (ppc64), or one derived from a resolved `PAGE_OFFSET` (arm32, ppc32,
157+
riscv32). Where something is randomized or unknown it is a
157158
window instead, narrowed by the architecture's module band, by the allocator's
158159
own placement window (on x86_64 the base sits within 1024 pages of
159160
`MODULES_VADDR`), by the resolved text base where the region is anchored to the

src/components/proc_cpuinfo.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,51 @@ static int detect_riscv_mmu(void) {
145145
}
146146
#endif /* riscv64 */
147147

148+
#if defined(__powerpc64__) || defined(__ppc64__)
149+
/* ppc64: "MMU : Radix" or "MMU : Hash".
150+
*
151+
* 64-bit PowerPC has no MODULES_VADDR — modules come from vmalloc, whose base
152+
* differs by translation mode and page size across a 32 TiB spread. This field
153+
* supplies the mode half; SF_PAGE_SIZE supplies the other, and a rule combines
154+
* them.
155+
*
156+
* Trustworthy for the purpose: the platform code prints it from
157+
* radix_enabled(), and that same runtime feature bit selects __vmalloc_start
158+
* (radix_pgtable.c sets RADIX_VMALLOC_START, hash_utils.c sets
159+
* H_VMALLOC_START). One bit drives both the report and the layout, so this is
160+
* the live mode, not a capability — unlike x86_64's "57 bits virtual", which
161+
* is what the CPU can do rather than what the kernel enabled.
162+
*
163+
* Emitted only on a POSITIVE match. The line comes from platform code
164+
* (pSeries, PowerNV), so Book3E parts do not print it at all — but a
165+
* restricted or absent /proc/cpuinfo looks identical, so absence is not
166+
* evidence of Book3E and nothing is inferred from it. */
167+
static int detect_ppc64_mmu(void) {
168+
char buf[256];
169+
char *val = cpuinfo_get("MMU", buf, sizeof(buf));
170+
171+
if (!val) {
172+
kasld_info("no MMU field in %s (Book3E, or a restricted /proc)",
173+
CPUINFO_PATH);
174+
return 0;
175+
}
176+
177+
unsigned long mode;
178+
if (strncmp(val, "Radix", 5) == 0) {
179+
mode = KASLD_PPC64_MMU_RADIX;
180+
} else if (strncmp(val, "Hash", 4) == 0) {
181+
mode = KASLD_PPC64_MMU_HASH;
182+
} else {
183+
kasld_err("unrecognised MMU mode '%s'; not emitting", val);
184+
return 0;
185+
}
186+
187+
kasld_info("MMU: %s", val);
188+
kasld_emit_scalar(SF_PPC64_MMU_MODE, mode, CONF_PARSED);
189+
return 1;
190+
}
191+
#endif /* ppc64 */
192+
148193
#if defined(__loongarch__) && __loongarch_grlen == 64
149194
/* loongarch64: "Address Sizes : N bits physical, M bits virtual" (note the
150195
* capitalisation, which differs from x86_64's "address sizes").
@@ -290,6 +335,10 @@ int main(void) {
290335
found |= detect_loongarch_address_sizes();
291336
#endif
292337

338+
#if defined(__powerpc64__) || defined(__ppc64__)
339+
found |= detect_ppc64_mmu();
340+
#endif
341+
293342
if (!found) {
294343
kasld_err("No actionable cpuinfo data found for this architecture.");
295344
return 0;

src/engine_rules.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ static const rule_fn k_rules[] = {
8484
rule_module_base_execmem_window,
8585
rule_module_base_from_text,
8686
rule_module_base_from_va_bits,
87+
rule_module_base_ppc64_vmalloc,
8788

8889
/* Multi-entry EFI_LOADER_CODE → Q_PHYS_IMAGE_BASE pin */
8990
rule_efi_loader_kernel_pick,

src/include/kasld/api.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,12 @@ enum kasld_text_order {
10681068
* records via kasld_emit_scalar(); the engine consumes them as OBS_SCALAR.
10691069
* Closed vocabulary — add an entry, a wire token below, and a rule.
10701070
* ========================================================================= */
1071+
/* SF_PPC64_MMU_MODE values. The kernel prints this from radix_enabled(), the
1072+
* same runtime feature bit that selects __vmalloc_start, so it states the LIVE
1073+
* translation mode rather than a CPU capability. */
1074+
#define KASLD_PPC64_MMU_RADIX 1ul
1075+
#define KASLD_PPC64_MMU_HASH 2ul
1076+
10711077
enum kasld_scalar_fact {
10721078
SF_NONE = 0,
10731079
SF_PHYS_MEMTOTAL, /* total RAM bytes (/proc/meminfo) */
@@ -1116,6 +1122,11 @@ enum kasld_scalar_fact {
11161122
/* phys is off (e.g. EFI_RNG_PROTOCOL unavailable */
11171123
/* with virt randomization intact via DTB) emits */
11181124
/* this fact alone. */
1125+
/* 64-bit PowerPC translation mode, as KASLD_PPC64_MMU_* below. Which of the
1126+
* three module-region bases is live follows from this plus SF_PAGE_SIZE, so
1127+
* it is emitted as a raw measurement and interpreted by a rule. Values are
1128+
* non-zero because a scalar fact of 0 reads as absent. */
1129+
SF_PPC64_MMU_MODE,
11191130
SF_VIRT_KASLR_RANDOMIZATION_FAILED, /* 1 if the boot stub attempted */
11201131
/* virtual KASLR but could not produce a random virt offset (current */
11211132
/* emitters: arm64/riscv64 "lack of seed", arm64 "FDT remapping */
@@ -1207,6 +1218,7 @@ static const char *const kasld_scalar_fact_wire_table[SF__COUNT] = {
12071218
[SF_STRUCT_PAGE_BYTES] = "struct_page_bytes",
12081219
[SF_TEXT_ORDER] = "text_order",
12091220
[SF_VIRT_KERNEL_IMAGE_BASE] = "virt_kernel_image_base",
1221+
[SF_PPC64_MMU_MODE] = "ppc64_mmu_mode",
12101222
};
12111223
/* Adding an SF_* without a wire token shrinks this below SF__COUNT -> error. */
12121224
typedef char kasld_sf_wire_table_complete

src/include/kasld/arch/ppc64.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,24 @@
6464
// Usable as a BOUND: the floor is the lowest vmalloc base of any 64-bit
6565
// PowerPC MMU configuration, and the ceiling the highest vmalloc end.
6666
#define MODULES_BAND_EXACT 1
67+
68+
// The live vmalloc base -- and so the module region's base -- is decided by the
69+
// translation mode and, for hash, the page size. All three values are
70+
// compile-time constants in the kernel; only the SELECTION is runtime, and both
71+
// selectors are observable unprivileged (SF_PPC64_MMU_MODE from /proc/cpuinfo,
72+
// SF_PAGE_SIZE from sysconf). module_base_ppc64_vmalloc turns the pair into a
73+
// pin.
74+
//
75+
// Book3E (KERN_VIRT_START = 0xc000100000000000) is deliberately absent: it is
76+
// identified only by the ABSENCE of the MMU line, which a restricted /proc
77+
// mimics exactly, so it stays unpinned rather than inferred from a missing
78+
// signal. The band floor above still covers it.
79+
// https://elixir.bootlin.com/linux/v7.2/source/arch/powerpc/include/asm/book3s/64/radix.h
80+
// https://elixir.bootlin.com/linux/v7.2/source/arch/powerpc/include/asm/book3s/64/hash-64k.h
81+
// https://elixir.bootlin.com/linux/v7.2/source/arch/powerpc/include/asm/book3s/64/hash-4k.h
82+
#define MODULES_BASE_PPC64_RADIX 0xc008000000000000ul
83+
#define MODULES_BASE_PPC64_HASH_64K 0xc008000000000000ul
84+
#define MODULES_BASE_PPC64_HASH_4K 0xc0003d0000000000ul
6785
#define MODULES_RELATIVE_TO_TEXT 0
6886

6987
// Plausible physical address range for kernel image

src/include/kasld/engine_rules.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ R(module_base_bounds);
324324
R(module_base_execmem_window);
325325
R(module_base_from_text);
326326
R(module_base_from_va_bits);
327+
R(module_base_ppc64_vmalloc);
327328

328329
/* Multi-entry EFI_LOADER_CODE → Q_PHYS_IMAGE_BASE pin (arm64/riscv64/x86_64) */
329330
R(efi_loader_kernel_pick);
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// This file is part of KASLD - https://github.com/bcoles/kasld
2+
//
3+
// Rule: pin Q_MODULE_BASE on 64-bit PowerPC from the live translation mode and
4+
// the page size.
5+
//
6+
// ppc64 defines no MODULES_VADDR, so modules are allocated from vmalloc, and
7+
// VMALLOC_START is a runtime variable set to one of three compile-time
8+
// constants depending on how the kernel booted:
9+
//
10+
// Radix RADIX_VMALLOC_START = 0xc008000000000000
11+
// Hash, 64K pages H_VMALLOC_START = 0xc008000000000000
12+
// Hash, 4K pages H_VMALLOC_START = 0xc0003d0000000000
13+
//
14+
// Both selectors are observable without privilege: the mode from
15+
// /proc/cpuinfo's "MMU" line (SF_PPC64_MMU_MODE), the page size from sysconf
16+
// (SF_PAGE_SIZE). Knowing both fixes the base exactly, collapsing a band that
17+
// otherwise spans 32 TiB.
18+
//
19+
// POSITIVE EVIDENCE ONLY. Every branch requires a fact to be present and to
20+
// match a value this rule understands; nothing is concluded from a fact being
21+
// absent. That matters most for Book3E, whose base (0xc000100000000000) is NOT
22+
// listed above: Book3E is distinguished only by printing no "MMU" line, and a
23+
// restricted or unreadable /proc/cpuinfo is indistinguishable from that. An
24+
// unfamiliar page size is treated the same way — no pin, rather than a guess.
25+
// The band bound from module_base_bounds remains in either case.
26+
// ---
27+
// <bcoles@gmail.com>
28+
29+
#include "include/kasld/engine_rules.h"
30+
#include "include/kasld/regions.h"
31+
32+
#include <string.h>
33+
34+
int rule_module_base_ppc64_vmalloc(const struct evidence_set *ev,
35+
const struct estimate *est,
36+
struct constraint *out, int out_max) {
37+
#if defined(MODULES_BASE_PPC64_RADIX)
38+
(void)est;
39+
if (out_max < 1)
40+
return 0;
41+
42+
enum kasld_confidence mconf = CONF_UNKNOWN;
43+
uint32_t msrc = 0;
44+
unsigned long mode =
45+
kasld_scalar_fact_value(ev, SF_PPC64_MMU_MODE, &mconf, &msrc);
46+
if (!mode)
47+
return 0;
48+
49+
unsigned long base;
50+
enum kasld_confidence conf = mconf;
51+
uint32_t src = msrc;
52+
53+
if (mode == KASLD_PPC64_MMU_RADIX) {
54+
/* Radix places vmalloc at one address whatever the page size. */
55+
base = (unsigned long)MODULES_BASE_PPC64_RADIX;
56+
} else if (mode == KASLD_PPC64_MMU_HASH) {
57+
enum kasld_confidence pconf = CONF_UNKNOWN;
58+
uint32_t psrc = 0;
59+
unsigned long pagesz =
60+
kasld_scalar_fact_value(ev, SF_PAGE_SIZE, &pconf, &psrc);
61+
if (pagesz == 65536ul) {
62+
base = (unsigned long)MODULES_BASE_PPC64_HASH_64K;
63+
} else if (pagesz == 4096ul) {
64+
base = (unsigned long)MODULES_BASE_PPC64_HASH_4K;
65+
} else {
66+
return 0; /* no page size, or one this rule does not model */
67+
}
68+
/* Two facts, so the pin is only as good as the weaker of them. */
69+
conf = kasld_conf_min(mconf, pconf);
70+
src = psrc ? psrc : msrc;
71+
} else {
72+
return 0; /* a mode this rule does not model */
73+
}
74+
75+
struct constraint *c = &out[0];
76+
memset(c, 0, sizeof(*c));
77+
c->q = Q_MODULE_BASE;
78+
c->op = C_EQUALS;
79+
c->value = base;
80+
c->conf = conf;
81+
if (src) {
82+
c->derived_from[0] = src;
83+
c->lineage_count = 1;
84+
}
85+
snprintf(c->origin, ORIGIN_LEN, "module_base_ppc64_vmalloc");
86+
return 1;
87+
#else
88+
(void)ev;
89+
(void)est;
90+
(void)out;
91+
(void)out_max;
92+
return 0;
93+
#endif
94+
}

tests/test_engine.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7169,6 +7169,59 @@ static void test_module_base_from_va_bits(void) {
71697169
#endif
71707170
}
71717171

7172+
/* ppc64 has no MODULES_VADDR: modules come from vmalloc, whose base is one of
7173+
* three constants selected at boot by the translation mode and page size. Both
7174+
* selectors are observable, so knowing them pins the quantity. Every branch
7175+
* requires POSITIVE evidence -- Book3E is identified only by printing no MMU
7176+
* line, which a restricted /proc mimics, so it must not be inferred. */
7177+
static void test_module_base_ppc64_vmalloc(void) {
7178+
struct engine e;
7179+
const rule_fn rules[] = {rule_module_base_ppc64_vmalloc};
7180+
struct estimate top;
7181+
quantities[Q_MODULE_BASE].init_top(&top);
7182+
7183+
/* No MMU fact at all: silent, not a Book3E guess. */
7184+
engine_init(&e);
7185+
struct observation ps = mk_scalar(SF_PAGE_SIZE, 4096, CONF_PARSED);
7186+
evidence_add(&e.ev, &ps);
7187+
engine_run(&e, rules, 1);
7188+
assert(e.est[Q_MODULE_BASE].lo == top.lo &&
7189+
e.est[Q_MODULE_BASE].hi == top.hi);
7190+
7191+
#if defined(MODULES_BASE_PPC64_RADIX)
7192+
/* Radix pins regardless of page size. */
7193+
engine_init(&e);
7194+
struct observation rx =
7195+
mk_scalar(SF_PPC64_MMU_MODE, KASLD_PPC64_MMU_RADIX, CONF_PARSED);
7196+
evidence_add(&e.ev, &rx);
7197+
engine_run(&e, rules, 1);
7198+
assert(e.est[Q_MODULE_BASE].lo == (unsigned long)MODULES_BASE_PPC64_RADIX &&
7199+
e.est[Q_MODULE_BASE].hi == (unsigned long)MODULES_BASE_PPC64_RADIX);
7200+
7201+
/* Hash needs the page size too; 4K and 64K select different bases. */
7202+
engine_init(&e);
7203+
struct observation hs =
7204+
mk_scalar(SF_PPC64_MMU_MODE, KASLD_PPC64_MMU_HASH, CONF_PARSED);
7205+
evidence_add(&e.ev, &hs);
7206+
engine_run(&e, rules, 1);
7207+
assert(e.est[Q_MODULE_BASE].lo == top.lo); /* mode alone is not enough */
7208+
7209+
engine_init(&e);
7210+
evidence_add(&e.ev, &hs);
7211+
struct observation p4 = mk_scalar(SF_PAGE_SIZE, 4096, CONF_PARSED);
7212+
evidence_add(&e.ev, &p4);
7213+
engine_run(&e, rules, 1);
7214+
assert(e.est[Q_MODULE_BASE].lo == (unsigned long)MODULES_BASE_PPC64_HASH_4K);
7215+
7216+
engine_init(&e);
7217+
evidence_add(&e.ev, &hs);
7218+
struct observation p64 = mk_scalar(SF_PAGE_SIZE, 65536, CONF_PARSED);
7219+
evidence_add(&e.ev, &p64);
7220+
engine_run(&e, rules, 1);
7221+
assert(e.est[Q_MODULE_BASE].lo == (unsigned long)MODULES_BASE_PPC64_HASH_64K);
7222+
#endif
7223+
}
7224+
71727225
/* text_pin_from_observation (declared above): a POS_BASE VIRT/KERNEL_TEXT
71737226
* observation pins Q_VIRT_IMAGE_BASE; a POS_BASE PHYS/KERNEL_TEXT observation
71747227
* pins Q_PHYS_IMAGE_BASE. Arch-independent. */
@@ -7912,6 +7965,7 @@ int main(void) {
79127965
RUN(test_module_base_execmem_window);
79137966
RUN(test_module_base_from_text);
79147967
RUN(test_module_base_from_va_bits);
7968+
RUN(test_module_base_ppc64_vmalloc);
79157969

79167970
BEGIN_CATEGORY("EFI Loader Code disambiguation");
79177971
RUN(test_efi_loader_kernel_pick_single_aligned);

0 commit comments

Comments
 (0)