Skip to content

Commit 64fba30

Browse files
committed
loongarch64: pin the module region base from the hardware VA width
vm_map_base is 0 - (1 << cpu_vabits) and MODULES_VADDR is that plus PCI_IOSIZE + 2 pages, with nothing randomized in between — so a known width fixes the base exactly. Until now the quantity fell back to the validation band: 7e13 candidates for a value that is fully determined. The width comes from /proc/cpuinfo "Address Sizes ... bits virtual", which is cpu_vabits + 1 read from CPUCFG1.VALEN — the same field vm_map_base is computed from. Published unconditionally, unlike the x86_64 branch beside it which publishes only a width of 48: there a reported 57 is the CPU capability and the kernel may still run 4-level, so the width does not state the active layout. LoongArch has no such split. An mmap boundary probe cannot substitute. TASK_SIZE64 is 1 << min(cpu_vabits, VA_BITS), clamped to the kernel's page-table width, while vm_map_base uses cpu_vabits unclamped; a 16K/3-level kernel on cpu_vabits=48 hardware would probe 47 and place the region 128 TiB high. Recorded in the rule and the component so the asymmetry is not "corrected". Confirmed on loongarch64-alpine-6.18: the row resolves to a single candidate, 0xffff000002002000.
1 parent 2fe3568 commit 64fba30

6 files changed

Lines changed: 160 additions & 0 deletions

File tree

src/components/proc_cpuinfo.c

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

148+
#if defined(__loongarch__) && __loongarch_grlen == 64
149+
/* loongarch64: "Address Sizes : N bits physical, M bits virtual" (note the
150+
* capitalisation, which differs from x86_64's "address sizes").
151+
*
152+
* M is cpu_vabits + 1, read from CPUCFG1.VALEN by cpu_probe_addrbits(). This
153+
* is the value that determines the whole upper VA layout:
154+
*
155+
* vm_map_base = 0 - (1 << cpu_vabits)
156+
* MODULES_VADDR = vm_map_base + PCI_IOSIZE + 2 * PAGE_SIZE
157+
*
158+
* Published UNCONDITIONALLY, unlike the x86_64 case below which publishes only
159+
* a width of 48. That asymmetry is deliberate and must not be "fixed": on
160+
* x86_64 a reported 57 is the CPU *capability* and the kernel may still run
161+
* 4-level, so the width there does not state the active layout. LoongArch has
162+
* no such split — one CPUCFG field feeds both vm_map_base and this line, so
163+
* whatever it reports IS what the kernel used.
164+
*
165+
* Note also that an mmap boundary probe CANNOT substitute for this read.
166+
* TASK_SIZE64 is `1 << min(cpu_vabits, VA_BITS)`, clamped to the kernel's
167+
* page-table width, while vm_map_base uses cpu_vabits unclamped. A 16K/3-level
168+
* kernel on cpu_vabits=48 hardware has VA_BITS=47, so a probe would under-read
169+
* by a bit and place the module region 128 TiB too high. */
170+
static int detect_loongarch_address_sizes(void) {
171+
char buf[256];
172+
char *val = cpuinfo_get("Address Sizes", buf, sizeof(buf));
173+
174+
if (!val) {
175+
kasld_err("Could not read Address Sizes from %s", CPUINFO_PATH);
176+
return 0;
177+
}
178+
179+
unsigned int phys_bits = 0, virt_bits = 0;
180+
if (sscanf(val, "%u bits physical, %u bits virtual", &phys_bits,
181+
&virt_bits) != 2) {
182+
kasld_err("Could not parse Address Sizes: %s", val);
183+
return 0;
184+
}
185+
186+
kasld_info("Address sizes: %u bits physical, %u bits virtual", phys_bits,
187+
virt_bits);
188+
189+
/* Sanity-bound before publishing: a width outside what the architecture can
190+
* express is a parse artefact, not a measurement. */
191+
if (virt_bits < 32 || virt_bits > 64) {
192+
kasld_err("implausible virtual width %u; not emitting", virt_bits);
193+
return 0;
194+
}
195+
kasld_emit_scalar(SF_VIRT_ADDR_BITS, virt_bits, CONF_PARSED);
196+
return 1;
197+
}
198+
#endif /* loongarch64 */
199+
148200
#if defined(__x86_64__) || defined(__amd64__)
149201
/* x86_64: "address sizes : N bits physical, M bits virtual"
150202
* Virtual address width determines paging level:
@@ -234,6 +286,10 @@ int main(void) {
234286
found |= detect_x86_address_sizes();
235287
#endif
236288

289+
#if defined(__loongarch__) && __loongarch_grlen == 64
290+
found |= detect_loongarch_address_sizes();
291+
#endif
292+
237293
if (!found) {
238294
kasld_err("No actionable cpuinfo data found for this architecture.");
239295
return 0;

src/engine_rules.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ static const rule_fn k_rules[] = {
8383
rule_module_base_bounds,
8484
rule_module_base_execmem_window,
8585
rule_module_base_from_text,
86+
rule_module_base_from_va_bits,
8687

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

src/include/kasld/arch/loongarch64.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@
7979
// derived from the widest VA the kernel's page tables can describe, so it sits
8080
// at or below vm_map_base for every VALEN a running kernel could report.
8181
#define MODULES_BAND_EXACT 1
82+
83+
// The module region's placement is a pure function of the hardware VA width:
84+
// vm_map_base = 0 - (1 << cpu_vabits)
85+
// MODULES_VADDR = vm_map_base + PCI_IOSIZE + 2 * PAGE_SIZE (PCI_IOSIZE=32M)
86+
// Nothing randomizes it, so a resolved width pins the quantity exactly rather
87+
// than bounding it. Declared as an addend so the rule needs no loongarch
88+
// literals of its own; the width comes from SF_VIRT_ADDR_BITS.
89+
// https://elixir.bootlin.com/linux/v7.2/source/arch/loongarch/include/asm/pgtable.h#L98
90+
// https://elixir.bootlin.com/linux/v7.2/source/arch/loongarch/include/asm/addrspace.h#L141
91+
#define MODULES_BASE_FROM_VA_BITS_ADDEND (0x2000000ul + 2ul * PAGE_SIZE)
8292
#define MODULES_RELATIVE_TO_TEXT 0
8393

8494
// EFI_KIMG_ALIGN is SZ_2M, but KASLR offset uses << 16 = 64 KiB granularity.

src/include/kasld/engine_rules.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ R(module_text_bracket);
323323
R(module_base_bounds);
324324
R(module_base_execmem_window);
325325
R(module_base_from_text);
326+
R(module_base_from_va_bits);
326327

327328
/* Multi-entry EFI_LOADER_CODE → Q_PHYS_IMAGE_BASE pin (arm64/riscv64/x86_64) */
328329
R(efi_loader_kernel_pick);
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// This file is part of KASLD - https://github.com/bcoles/kasld
2+
//
3+
// Rule: pin Q_MODULE_BASE from a resolved virtual-address width, on arches
4+
// whose module region is placed by arithmetic on that width alone.
5+
//
6+
// Where MODULES_BASE_FROM_VA_BITS_ADDEND is declared, the kernel computes
7+
//
8+
// vm_map_base = 0 - (1 << va_bits)
9+
// MODULES_VADDR = vm_map_base + <addend>
10+
//
11+
// with nothing randomized in between, so a known width fixes the base
12+
// exactly -- an equality, not a window.
13+
//
14+
// The width must be the HARDWARE one (loongarch cpu_vabits, published by
15+
// proc_cpuinfo from "Address Sizes ... bits virtual"). An mmap boundary probe
16+
// measures TASK_SIZE = 1 << min(cpu_vabits, VA_BITS), clamped to the kernel's
17+
// page-table width, and feeding that here would place the region too high --
18+
// by a factor of two per clamped bit. SF_VIRT_ADDR_BITS is the contract that
19+
// says "the active virtual width", which is what this needs.
20+
// ---
21+
// <bcoles@gmail.com>
22+
23+
#include "include/kasld/engine_rules.h"
24+
#include "include/kasld/regions.h"
25+
26+
#include <string.h>
27+
28+
int rule_module_base_from_va_bits(const struct evidence_set *ev,
29+
const struct estimate *est,
30+
struct constraint *out, int out_max) {
31+
#if defined(MODULES_BASE_FROM_VA_BITS_ADDEND)
32+
(void)est;
33+
if (out_max < 1)
34+
return 0;
35+
36+
enum kasld_confidence conf = CONF_UNKNOWN;
37+
uint32_t src = 0;
38+
unsigned long bits =
39+
kasld_scalar_fact_value(ev, SF_VIRT_ADDR_BITS, &conf, &src);
40+
/* A width outside the addressable range would shift by an undefined amount;
41+
* treat it as no evidence rather than compute on it. */
42+
if (!bits || bits >= (sizeof(unsigned long) * 8))
43+
return 0;
44+
45+
unsigned long base =
46+
(0ul - (1ul << bits)) + (unsigned long)MODULES_BASE_FROM_VA_BITS_ADDEND;
47+
48+
struct constraint *c = &out[0];
49+
memset(c, 0, sizeof(*c));
50+
c->q = Q_MODULE_BASE;
51+
c->op = C_EQUALS;
52+
c->value = base;
53+
c->conf = conf;
54+
if (src) {
55+
c->derived_from[0] = src;
56+
c->lineage_count = 1;
57+
}
58+
snprintf(c->origin, ORIGIN_LEN, "module_base_from_va_bits");
59+
return 1;
60+
#else
61+
(void)ev;
62+
(void)est;
63+
(void)out;
64+
(void)out_max;
65+
return 0;
66+
#endif
67+
}

tests/test_engine.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7140,6 +7140,30 @@ static void test_module_base_from_text(void) {
71407140
#endif
71417141
}
71427142

7143+
/* Where the module region is placed by arithmetic on the VA width alone, a
7144+
* resolved width pins the quantity outright. The width must be the hardware
7145+
* one: an mmap probe measures TASK_SIZE = 1 << min(cpu_vabits, VA_BITS), and
7146+
* feeding a clamped width here would place the region a factor of two too high
7147+
* per clamped bit. */
7148+
static void test_module_base_from_va_bits(void) {
7149+
struct engine e;
7150+
engine_init(&e);
7151+
const rule_fn rules[] = {rule_module_base_from_va_bits};
7152+
struct observation vb = mk_scalar(SF_VIRT_ADDR_BITS, 48, CONF_PARSED);
7153+
evidence_add(&e.ev, &vb);
7154+
engine_run(&e, rules, 1);
7155+
#if defined(MODULES_BASE_FROM_VA_BITS_ADDEND)
7156+
unsigned long want =
7157+
(0ul - (1ul << 48)) + (unsigned long)MODULES_BASE_FROM_VA_BITS_ADDEND;
7158+
assert(e.est[Q_MODULE_BASE].lo == want && e.est[Q_MODULE_BASE].hi == want);
7159+
#else
7160+
struct estimate top;
7161+
quantities[Q_MODULE_BASE].init_top(&top);
7162+
assert(e.est[Q_MODULE_BASE].lo == top.lo &&
7163+
e.est[Q_MODULE_BASE].hi == top.hi);
7164+
#endif
7165+
}
7166+
71437167
/* text_pin_from_observation (declared above): a POS_BASE VIRT/KERNEL_TEXT
71447168
* observation pins Q_VIRT_IMAGE_BASE; a POS_BASE PHYS/KERNEL_TEXT observation
71457169
* pins Q_PHYS_IMAGE_BASE. Arch-independent. */
@@ -7882,6 +7906,7 @@ int main(void) {
78827906
RUN(test_module_base_band_bounds);
78837907
RUN(test_module_base_execmem_window);
78847908
RUN(test_module_base_from_text);
7909+
RUN(test_module_base_from_va_bits);
78857910

78867911
BEGIN_CATEGORY("EFI Loader Code disambiguation");
78877912
RUN(test_efi_loader_kernel_pick_single_aligned);

0 commit comments

Comments
 (0)