Skip to content

Commit 85d166f

Browse files
committed
arch: make the page size an axis, so a PFN is never converted with a guess
A page-frame number counts the pages of the kernel that published it. Two sites converted one to a byte address by multiplying by api.h's compile-time PAGE_SIZE, which is 4 KiB. Four of the eight supported architecture families admit several page sizes — arm64 and loongarch64 4/16/64 KiB, mips 4/8/16/32/64 KiB, powerpc 4/16/64/256 KiB — so on those the product understates a physical address by up to 64x. That mattered most in directmap_page_offset_bounds, where the span bounds PAGE_OFFSET from below at CONF_INFERRED: understating it lifts the bound above the true base and the guaranteed window stops containing it. proc_zoneinfo emitted RAM bounds from the same conversion at CONF_PARSED. Declare PAGE_SIZE_MIN / PAGE_SIZE_MAX per architecture, each citing the HAVE_PAGE_SIZE_* selectors its answer comes from, and derive PAGE_SIZE_KNOWN_AT_BUILD from the pair the way PAGE_OFFSET_KNOWN_AT_BUILD is derived. pfn_to_phys() exists only under it, with the same self-enforcing #error the directmap projection carries, so a conversion that needs the target's runtime size cannot reach a constant instead — it does not compile. Where the size is not knowable the multiplier comes from the observed SF_PAGE_SIZE, the bound inherits its confidence, and absent both the rule emits nothing: declining costs a bound, guessing costs the guarantee. page_size gains live:1. sysconf answers for the kernel executing the process, so replaying an arm64 capture on an x86_64 host would otherwise have reported the host's 4 KiB as the target's page size — the same wrong answer by a different route. Rename PAGE_SIZE to KASLD_LAYOUT_GRANULE. It is the granule kernel layout arithmetic is written in, not a machine property, and the old name is one the C library also defines: musl declares PAGE_SIZE under _GNU_SOURCE on the targets whose ABI fixes one, so including <limits.h> anywhere near api.h redefined it. A macro named for a machine property also invites exactly the substitution above, which is how both sites came to be written. Kernel-source citations keep spelling it PAGE_SIZE, since they quote the kernel. The module band's slot pitch still uses the granule. A pitch smaller than the target's page overstates the slot count and so the residual entropy, which is documented as an upper bound on what KASLR retains — the conservative direction. Correcting it moves published figures on four architecture families and belongs with a regenerated matrix.
1 parent 59a68ed commit 85d166f

28 files changed

Lines changed: 548 additions & 52 deletions

CONTRIBUTING.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -690,7 +690,7 @@ and [Cross-region derivation](docs/architecture.md#cross-region-derivation).
690690
A new architecture is one header under `src/include/kasld/arch/`. It answers a
691691
fixed set of questions about how that architecture lays memory out, and `api.h`
692692
refuses to compile a header that leaves any of the mandatory ones unanswered —
693-
the seven listed under [Mandatory axes](#api-reference) below. Nothing here is
693+
the eight listed under [Mandatory axes](#api-reference) below. Nothing here is
694694
inferred from a neighbouring header: an answer copied from the closest-looking
695695
architecture is the failure this section exists to prevent.
696696
@@ -725,6 +725,19 @@ typical distro ships. Equal values mean the analysing binary knows the target's
725725
base, which is what gates the compile-time projection macros; a bracket that is
726726
too narrow excludes a legitimate kernel from its own window.
727727
728+
**`PAGE_SIZE_MIN` / `PAGE_SIZE_MAX`** — the bracket containing every page size
729+
the architecture admits, taken from the `HAVE_PAGE_SIZE_*` selectors in the
730+
kernel's own `arch/<arch>/Kconfig` rather than from what a distro ships. Six of
731+
the twelve supported architectures fix one size and six do not: arm64,
732+
loongarch64 and mips reach 64 KiB, and 32-bit powerpc reaches 256 KiB. Equal
733+
values mean the analysing binary knows the target's page size, which is what
734+
gates `pfn_to_phys()` — a page-frame number counts the *target* kernel's pages,
735+
so converting one to a byte address with this build's constant is wrong by up to
736+
64x where the two differ. Where they differ the multiplier must come from the
737+
`SF_PAGE_SIZE` observation, and a rule with neither declines rather than
738+
guessing: the quantities built on such a span are bounds, and an understated
739+
span moves a bound past the truth it is supposed to contain.
740+
728741
**`LINEAR_MAP_ANCHOR`** — where the physical address the kernel maps at
729742
`PAGE_OFFSET` comes from, since a rule pairing a direct-map virtual with a
730743
physical reconstructs the base as `virt - phys + anchor`. `LM_ANCHOR_PHYS_OFFSET`
@@ -854,6 +867,7 @@ separate question: a permissive answer to one does not license the others.
854867
| Symbol | Values | Answers |
855868
|---|---|---|
856869
| `PAGE_OFFSET_MIN` / `PAGE_OFFSET_MAX` | literal addresses | Which linear-map bases the architecture admits — equal values mean this build knows the target's base |
870+
| `PAGE_SIZE_MIN` / `PAGE_SIZE_MAX` | literal sizes | Which page sizes the architecture admits — equal values mean this build knows the target's page size, and only then may a page-frame number be converted with a constant |
857871
| `LINEAR_MAP_ANCHOR` | `LM_ANCHOR_PHYS_OFFSET` / `LM_ANCHOR_DRAM_BASE` / `LM_ANCHOR_UNKNOWABLE` | Where the physical address that maps to `PAGE_OFFSET` comes from |
858872
| `MODULES_ANCHOR` | `MOD_ANCHOR_FIXED` / `MOD_ANCHOR_PAGE_OFFSET` / `MOD_ANCHOR_TEXT` / `MOD_ANCHOR_BRACKETS_TEXT` | What the module band's position is fixed to |
859873
| `TEXT_TRACKS_DIRECTMAP` | 0 / 1 | Whether kernel text slides with the linear map |

src/components/dmesg_backtrace.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,8 @@ static void on_line(char *line, void *vctx) {
315315
const char *p = cr3 + 4;
316316
unsigned long v;
317317
if (next_addr_token(&p, &v) && v) {
318-
v &= ~(unsigned long)(PAGE_SIZE - 1); /* strip PCID/ASID low bits */
318+
v &= ~(unsigned long)(KASLD_LAYOUT_GRANULE -
319+
1); /* strip PCID/ASID low bits */
319320
if (!c->cr3 || v < c->cr3) {
320321
c->cr3 = v;
321322
c->cr3_swapper = c->ctx_known ? c->ctx_swapper : 0;

src/components/dmesg_last_pfn.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@
1010
// The second is the ceiling below 4 GiB (e820__end_of_low_ram_pfn).
1111
// Both are always printed on x86 / x86_64.
1212
//
13-
// Multiplying last_pfn by PAGE_SIZE (0x1000) gives the physical end of RAM:
13+
// Multiplying last_pfn by KASLD_LAYOUT_GRANULE (0x1000) gives the physical end
14+
// of RAM:
1415
// 0x340000 * 0x1000 = 0x340000000 (~13 GiB)
1516
//
1617
// Leak primitive:
1718
// Data leaked: physical RAM ceiling (last page frame number)
1819
// Kernel subsystem: arch/x86/kernel/e820 — e820__end_of_ram_pfn()
1920
// Data structure: last_pfn, max_arch_pfn (page frame numbers)
20-
// Address type: physical (DRAM, as PFN × PAGE_SIZE)
21+
// Address type: physical (DRAM, as PFN × 4 KiB)
2122
// Method: parsed (dmesg string)
2223
// Status: unfixed (printed unconditionally during boot)
2324
// Access check: do_syslog() → check_syslog_permissions(); gated by
@@ -56,7 +57,7 @@
5657
KASLD_EXPLAIN(
5758
"Searches dmesg for x86 last_pfn and max_arch_pfn values from "
5859
"e820__end_of_ram_pfn(). Multiplying the page frame number by "
59-
"PAGE_SIZE (4096) gives the physical RAM ceiling. x86 only. Access "
60+
"the 4 KiB page size gives the physical RAM ceiling. x86 only. Access "
6061
"is gated by dmesg_restrict.");
6162

6263
KASLD_META("method:parsed\n"
@@ -82,9 +83,11 @@ static int on_match(const char *line, void *ctx) {
8283
return 1;
8384

8485
/* last_pfn is the first invalid PFN (one past the end of RAM); subtract 1 for
85-
* the last valid byte. Compute in 64-bit: on i386 (PAE) pfn * PAGE_SIZE can
86+
* the last valid byte. Compute in 64-bit: on i386 (PAE) pfn * the granule
87+
* can
8688
* exceed 32 bits. */
87-
unsigned long long last_byte = pfn * (unsigned long long)PAGE_SIZE - 1;
89+
unsigned long long last_byte =
90+
pfn * (unsigned long long)KASLD_LAYOUT_GRANULE - 1;
8891

8992
match_count++;
9093
/* The kernel prints two "last_pfn = ..." lines at boot:

src/components/kernelsnitch.c

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@
6161
#error "Architecture is not supported"
6262
#endif
6363

64+
/* The comments below describe the kernel's futex and slab arithmetic in the
65+
* kernel's own terms, where the unit is PAGE_SIZE. The code spells that unit
66+
* KASLD_LAYOUT_GRANULE, and the two are the same number here: x86_64 admits
67+
* one page size, 4 KiB, which is why this component is gated to it above. */
68+
6469
#define _GNU_SOURCE
6570
#include "include/kasld/api.h"
6671
#include "include/kasld/cli.h"
@@ -230,8 +235,8 @@ static inline uint32_t jhash2_4(const uint32_t *k, uint32_t initval) {
230235
static inline uint32_t futex_bucket(unsigned long mm, unsigned long uaddr,
231236
unsigned int hashsize) {
232237
uint32_t k[4];
233-
unsigned long page_addr = uaddr & ~(PAGE_SIZE - 1);
234-
unsigned int offset = (unsigned int)(uaddr & (PAGE_SIZE - 1));
238+
unsigned long page_addr = uaddr & ~(KASLD_LAYOUT_GRANULE - 1);
239+
unsigned int offset = (unsigned int)(uaddr & (KASLD_LAYOUT_GRANULE - 1));
235240
k[0] = (uint32_t)(mm & 0xffffffff);
236241
k[1] = (uint32_t)((uint64_t)mm >> 32);
237242
k[2] = (uint32_t)(page_addr & 0xffffffff);
@@ -415,7 +420,7 @@ static int find_collisions(unsigned long *collisions, int *num_collisions,
415420
unsigned long num_probes = (unsigned long)hashsize * MAX_COLLISIONS * 4;
416421
if (num_probes < 65536)
417422
num_probes = 65536;
418-
unsigned long max_probes = FUTEX_REGION_SZ / PAGE_SIZE;
423+
unsigned long max_probes = FUTEX_REGION_SZ / KASLD_LAYOUT_GRANULE;
419424
if (num_probes > max_probes)
420425
num_probes = max_probes;
421426

@@ -434,9 +439,9 @@ static int find_collisions(unsigned long *collisions, int *num_collisions,
434439
uint64_t confirm_threshold = baseline * COLLISION_MULT;
435440

436441
unsigned long stride = FUTEX_REGION_SZ / num_probes;
437-
stride = (stride + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
438-
if (stride < PAGE_SIZE)
439-
stride = PAGE_SIZE;
442+
stride = (stride + KASLD_LAYOUT_GRANULE - 1) & ~(KASLD_LAYOUT_GRANULE - 1);
443+
if (stride < KASLD_LAYOUT_GRANULE)
444+
stride = KASLD_LAYOUT_GRANULE;
440445

441446
fprintf(stderr,
442447
"[.] probe: baseline=%lu cycles, num_probes=%lu, "
@@ -543,15 +548,17 @@ static void *search_fn(void *arg) {
543548
unsigned long local_count = 0;
544549

545550
/* Precompute loop-invariant hash inputs for pile_addr. */
546-
unsigned long p_page = ctx->pile_addr & ~(unsigned long)(PAGE_SIZE - 1);
547-
uint32_t p_off = (uint32_t)(ctx->pile_addr & (PAGE_SIZE - 1));
551+
unsigned long p_page =
552+
ctx->pile_addr & ~(unsigned long)(KASLD_LAYOUT_GRANULE - 1);
553+
uint32_t p_off = (uint32_t)(ctx->pile_addr & (KASLD_LAYOUT_GRANULE - 1));
548554
uint32_t p_plo = (uint32_t)(p_page & 0xffffffffUL);
549555
uint32_t p_phi = (uint32_t)((uint64_t)p_page >> 32);
550556
uint32_t p_base = JHASH_INITVAL + 16u + p_off;
551557

552558
/* Precompute loop-invariant hash inputs for collisions[0]. */
553-
unsigned long c0_page = ctx->collisions[0] & ~(unsigned long)(PAGE_SIZE - 1);
554-
uint32_t c0_off = (uint32_t)(ctx->collisions[0] & (PAGE_SIZE - 1));
559+
unsigned long c0_page =
560+
ctx->collisions[0] & ~(unsigned long)(KASLD_LAYOUT_GRANULE - 1);
561+
uint32_t c0_off = (uint32_t)(ctx->collisions[0] & (KASLD_LAYOUT_GRANULE - 1));
555562
uint32_t c0_plo = (uint32_t)(c0_page & 0xffffffffUL);
556563
uint32_t c0_phi = (uint32_t)((uint64_t)c0_page >> 32);
557564
uint32_t c0_base = JHASH_INITVAL + 16u + c0_off;
@@ -644,7 +651,7 @@ static unsigned long brute_force_mm(unsigned long *collisions,
644651
* so valid mm offsets (mod PAGE_SIZE) cycle through
645652
* {k*slab_size mod PAGE_SIZE}. Stepping by gcd(slab_size, PAGE_SIZE)
646653
* hits every such residue. */
647-
unsigned long step = gcd_ul(mm_step, (unsigned long)PAGE_SIZE);
654+
unsigned long step = gcd_ul(mm_step, (unsigned long)KASLD_LAYOUT_GRANULE);
648655
unsigned long mm_start = POB_MIN;
649656
unsigned long mm_end = POB_MAX + phys_mem;
650657
unsigned long total_iters = (mm_end - mm_start) / step;

src/components/page_size.c

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,26 @@
55
// ---
66
// <bcoles@gmail.com>
77
#include "include/kasld/api.h"
8+
#include "include/kasld/cli.h"
89
#include <unistd.h>
910

1011
KASLD_EXPLAIN("Emits the kernel page size (sysconf _SC_PAGESIZE) as a scalar "
1112
"fact; the arm64 physical-alignment rule consumes it.");
13+
/* live:1 — sysconf answers for the kernel executing this process. A captured
14+
* tree carries no page size, and the analysing host's is not the captured
15+
* target's: replaying an arm64 bundle on an x86_64 host would otherwise report
16+
* 4 KiB as though it were the target's page size, and the rules that multiply a
17+
* page-frame number by it would silently compute the wrong physical address.
18+
* Skipped under KASLD_SYSROOT, leaving those rules to decline instead. */
1219
KASLD_META("method:parsed\n"
1320
"phase:inference\n"
14-
"discloses:facts\n");
21+
"discloses:facts\n"
22+
"live:1\n");
1523

16-
int main(void) {
24+
int main(int argc, char *argv[]) {
25+
kasld_cli(argc, argv);
26+
if (kasld_skip_live_probe("page size"))
27+
return 0;
1728
long p = sysconf(_SC_PAGESIZE);
1829
if (p > 0)
1930
kasld_emit_scalar(SF_PAGE_SIZE, (unsigned long)p, CONF_PARSED);

src/components/proc_zoneinfo.c

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
#include <stdio.h>
7878
#include <stdlib.h>
7979
#include <string.h>
80+
#include <unistd.h>
8081

8182
KASLD_EXPLAIN(
8283
"Reads /proc/zoneinfo to extract the start_pfn and spanned page "
@@ -140,16 +141,52 @@ int main(void) {
140141
return 0;
141142
}
142143

143-
/* Clamp PFNs before the byte conversion: on a 32-bit (PAE) kernel a PFN
144-
* above ULONG_MAX / PAGE_SIZE wraps unsigned long and would yield a bogus
145-
* too-low RAM bound (and a wrapped direct-map projection below). */
146144
unsigned long hi_use = hi_end_pfn > hi_pfn ? hi_end_pfn : hi_pfn;
147-
if (lo_pfn > ULONG_MAX / PAGE_SIZE)
148-
lo_pfn = ULONG_MAX / PAGE_SIZE;
149-
if (hi_use > ULONG_MAX / PAGE_SIZE)
150-
hi_use = ULONG_MAX / PAGE_SIZE;
151-
unsigned long lo = lo_pfn * PAGE_SIZE;
152-
unsigned long hi = hi_use * PAGE_SIZE - 1;
145+
146+
/* A zoneinfo PFN counts the pages of the kernel that published the file, so
147+
* the multiplier must be that kernel's page size.
148+
*
149+
* Where the architecture admits exactly one size that constant is it. Where
150+
* it admits several -- arm64, mips, powerpc, loongarch64 -- a compile-time
151+
* constant would be wrong by up to 64x, so the size is asked of the running
152+
* kernel. That answer describes the running kernel and nothing else: under
153+
* KASLD_SYSROOT the file came from a captured tree whose page size nothing
154+
* here knows, so the conversion is declined rather than made with the
155+
* analysing host's.
156+
*
157+
* The multiply is written out rather than routed through pfn_to_phys()
158+
* because the two want different answers at the top of the range: this site
159+
* CLAMPS an out-of-range PFN to keep a bound, where pfn_to_phys() returns 0
160+
* and declines. Clamping is what keeps a 32-bit (PAE) kernel's high PFNs
161+
* from wrapping unsigned long into a bogus too-low RAM bound. */
162+
#if PAGE_SIZE_KNOWN_AT_BUILD
163+
unsigned long page_size = (unsigned long)PAGE_SIZE_MIN;
164+
#else
165+
unsigned long page_size = 0;
166+
if (!kasld_sysroot()) {
167+
long p = sysconf(_SC_PAGESIZE);
168+
if (p > 0)
169+
page_size = (unsigned long)p;
170+
}
171+
/* Taken as reported, without a plausibility bracket: sysconf answers for the
172+
* kernel running this process, which is more authoritative about that
173+
* kernel's page size than this build's PAGE_SIZE_MIN / PAGE_SIZE_MAX axis
174+
* is. A value outside that bracket would mean the axis is wrong, and
175+
* rejecting it here would hide that rather than surface it. The engine rules
176+
* that read the same quantity off the wire do check it, because there it has
177+
* been through the tagged-line protocol and can be an artefact. */
178+
if (!page_size)
179+
return kasld_disp_absent("page size unknown for a captured tree on an "
180+
"architecture that admits several; zone PFNs "
181+
"cannot be converted to addresses");
182+
#endif
183+
184+
if (lo_pfn > ULONG_MAX / page_size)
185+
lo_pfn = ULONG_MAX / page_size;
186+
if (hi_use > ULONG_MAX / page_size)
187+
hi_use = ULONG_MAX / page_size;
188+
unsigned long lo = lo_pfn * page_size;
189+
unsigned long hi = hi_use * page_size - 1;
153190

154191
/* lo: the start of the lowest published zone — a sound RAM witness but
155192
* NOT a floor pin (reserved low memory below the lowest zone is

src/components/qemu_tcg_iret.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ static uint64_t get_kernel_text_addr_using_qemu_tcg_iret(void) {
324324
// a fallback stack for the path where iretq pops the landmark frame and
325325
// jumps to 0x133a000 instead of leaking; this growsdown mapping at
326326
// 0x1338000 is adjacent to that target.
327-
mmap((void *)0x1338000, PAGE_SIZE * 2, PROT_READ | PROT_WRITE,
327+
mmap((void *)0x1338000, KASLD_LAYOUT_GRANULE * 2, PROT_READ | PROT_WRITE,
328328
MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_POPULATE,
329329
-1, 0);
330330

src/include/kasld/api.h

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,25 @@
1313
#ifndef KASLD_API_H
1414
#define KASLD_API_H
1515

16-
#define PAGE_SIZE 0x1000ul
16+
/* The 4 KiB granule the kernel's own layout arithmetic is written in: the unit
17+
* behind expressions lifted from kernel source, such as x86_64's module
18+
* randomization span of `1024 * PAGE_SIZE`. It is a fixed number that stays 4
19+
* KiB whatever the analysed machine runs.
20+
*
21+
* It is NOT the target's page size, and must never stand in for one. Four of
22+
* the eight supported architecture families admit several page sizes -- arm64
23+
* and loongarch64 4/16/64 KiB, mips 4/8/16/32/64 KiB, powerpc 4/16/64/256 KiB
24+
* -- so on those a page-frame number converted with this constant is wrong by
25+
* up to 64x. Where the target's page size is genuinely needed, it comes from
26+
* the SF_PAGE_SIZE observation at runtime; pfn_to_phys() below exists only on
27+
* the architectures where the two coincide, so a conversion that needs the
28+
* runtime value cannot reach this constant by accident.
29+
*
30+
* Deliberately not spelled PAGE_SIZE: that name belongs to the C library on
31+
* some targets (musl defines it under _GNU_SOURCE wherever the ABI fixes a
32+
* page size), and a macro named for a machine property invites exactly the
33+
* substitution the paragraph above forbids. */
34+
#define KASLD_LAYOUT_GRANULE 0x1000ul
1735
#define KB 0x400ul
1836
#define MB 0x100000ul
1937
#define GB 0x40000000ul
@@ -383,6 +401,14 @@ __extension__ _Static_assert((unsigned long)KERNEL_PHYS_MAX >
383401
*
384402
* Treating them as one flag would silently pick a side on whichever arch
385403
* arrives first. */
404+
#ifndef PAGE_SIZE_MIN
405+
#error \
406+
"arch header must define PAGE_SIZE_MIN / PAGE_SIZE_MAX (the page sizes the arch admits)"
407+
#endif
408+
#ifndef PAGE_SIZE_MAX
409+
#error \
410+
"arch header must define PAGE_SIZE_MIN / PAGE_SIZE_MAX (the page sizes the arch admits)"
411+
#endif
386412
#ifndef DIRECTMAP_STATIC
387413
#error "arch header must define DIRECTMAP_STATIC (0 or 1)"
388414
#endif
@@ -778,6 +804,55 @@ static inline unsigned long kasld__directmap_virt_to_phys(unsigned long v) {
778804
"the compile-time directmap projection requires PAGE_OFFSET_KNOWN_AT_BUILD"
779805
#endif
780806

807+
/* =========================================================================
808+
* Page-frame number conversion
809+
*
810+
* 1 iff this architecture admits exactly one page size, so a page-frame number
811+
* -- which counts the TARGET kernel's pages -- can be converted to a byte
812+
* address with a compile-time constant. Derived from the arch header's pair,
813+
* never declared: an arch cannot then claim a page size it does not admit, and
814+
* a mistyped axis value fails to compile instead of expanding to 0.
815+
*
816+
* Where the edges differ the multiplier is not knowable at build time, and the
817+
* error is not small: mips and loongarch64 reach 64 KiB and 32-bit powerpc
818+
* reaches 256 KiB, so a PFN converted at 4 KiB understates a physical address
819+
* by up to 64x. Understating the top of physical memory is precisely how a
820+
* bound derived from it lands past truth, which is why this is a gate rather
821+
* than a comment.
822+
* ========================================================================= */
823+
#define PAGE_SIZE_KNOWN_AT_BUILD (PAGE_SIZE_MIN == PAGE_SIZE_MAX)
824+
825+
#if PAGE_SIZE_KNOWN_AT_BUILD
826+
/* pfn_to_phys(pfn): first byte of the frame. phys_to_pfn(p): the frame holding
827+
* `p`. Defined ONLY where the page size is single-valued, so a conversion that
828+
* needs the target's runtime size cannot silently reach a constant instead:
829+
* the call does not compile on the arches where the constant would be wrong.
830+
* Callers must #ifdef and take SF_PAGE_SIZE on the other path.
831+
*
832+
* pfn_to_phys returns 0 on overflow rather than a wrapped address, so a caller
833+
* bounding a window from it cannot be handed a small number for a huge frame.
834+
*/
835+
static inline unsigned long kasld__pfn_to_phys(unsigned long pfn) {
836+
if (pfn > (unsigned long)-1 / (unsigned long)PAGE_SIZE_MIN)
837+
return 0;
838+
return pfn * (unsigned long)PAGE_SIZE_MIN;
839+
}
840+
static inline unsigned long kasld__phys_to_pfn(unsigned long p) {
841+
return p / (unsigned long)PAGE_SIZE_MIN;
842+
}
843+
#define pfn_to_phys(pfn) kasld__pfn_to_phys((unsigned long)(pfn))
844+
#define phys_to_pfn(p) kasld__phys_to_pfn((unsigned long)(p))
845+
#endif
846+
847+
/* Self-enforcing restatement of the gate above, in the same shape as the
848+
* directmap projection's. Trivially satisfied as written; widening the
849+
* condition to a predicate that does not imply PAGE_SIZE_KNOWN_AT_BUILD breaks
850+
* the build on the arches where the multiplier would be a guess, rather than
851+
* quietly producing physical addresses computed from the wrong page size. */
852+
#if defined(pfn_to_phys) && !PAGE_SIZE_KNOWN_AT_BUILD
853+
#error "pfn_to_phys requires PAGE_SIZE_KNOWN_AT_BUILD"
854+
#endif
855+
781856
/* The linear-map base where this build genuinely knows it, 0 where it does not.
782857
*
783858
* For presentation. A renderer stating an address is asserting it, so it may

src/include/kasld/arch/arm32.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@
3030
// (arch/arm/Kconfig: VMSPLIT_3G / 3G_OPT / 2G / 1G). arm32 has no 2G_OPT.
3131
#define PAGE_OFFSET_CANDIDATES \
3232
{0xc0000000ul, 0xb0000000ul, 0x80000000ul, 0x40000000ul}
33+
// Admissible kernel page sizes on this architecture. PAGE_SIZE_KNOWN_AT_BUILD
34+
// is derived from the pair in api.h and gates pfn_to_phys(); a page-frame
35+
// number may only be converted with a compile-time constant where the two
36+
// edges coincide. Where they differ the runtime SF_PAGE_SIZE observation is
37+
// the only sound multiplier.
38+
// arm32 fixes the base page at 4 KiB; arch/arm selects
39+
// HAVE_PAGE_SIZE_4KB and offers no alternative.
40+
#define PAGE_SIZE_MIN 0x1000ul
41+
#define PAGE_SIZE_MAX 0x1000ul
42+
3343
#define PAGE_OFFSET_MIN 0x40000000ul
3444
#define PAGE_OFFSET_MAX 0xc0000000ul
3545

0 commit comments

Comments
 (0)