Files
Snapintosh/tools/test-stage2.c
T
Acelogic 1510d8c6ce Snapintosh public release
An EL2 monitor/hypervisor and toolchain that boots macOS 27 on the ASUS A16
(Snapdragon X2 Elite), with an Adreno Metal plug-in and IOGPU kext, and
air2ir3, an AIR to Mesa ir3 shader compiler.

MIT, with GPL-2.0 components listed in LICENSE and THIRD_PARTY_NOTICES.md.
No Apple, Qualcomm, ASUS or Microsoft binaries are included.
2026-10-21 21:26:36 -04:00

397 lines
18 KiB
C

/* SPDX-License-Identifier: MIT */
/* The stage-2 map must hold exactly the ranges it was given, and nothing else.
* That is checkable directly: build it, then walk it with the same walker the
* shadow tests use and compare every page against the list. No hand-written
* table layout, so the test does not encode the same misunderstanding as the
* code it checks.
*
* cc -std=gnu11 -O1 -g -fsanitize=address,undefined -Ishadow \
* tools/test-stage2.c shadow/mmu.c shadow/shadow.c shadow/stage2.c -o build/test-stage2
*/
#include <stdio.h>
#include <string.h>
#include "stage2.h"
static int checks, failures;
static void check(int condition, const char *label)
{
if (condition) {
checks++;
} else {
failures++;
printf("FAIL: %s\n", label);
}
}
/* The tables live in a model of the work area. The addresses they map need no
* backing: an identity entry for 0xda000000 is a number in a descriptor. */
static uint64_t RAM_BASE = 0x82500000UL;
#define RAM_SIZE (2UL << 20)
static uint8_t ram[RAM_SIZE];
static uint64_t next_table;
static unsigned allocations;
static int ram_read(void *ctx, uint64_t pa, uint64_t *out)
{
(void)ctx;
if (pa < RAM_BASE || pa + 8 > RAM_BASE + RAM_SIZE) return -1;
memcpy(out, &ram[pa - RAM_BASE], 8);
return 0;
}
static int ram_write(void *ctx, uint64_t pa, uint64_t value)
{
(void)ctx;
if (pa < RAM_BASE || pa + 8 > RAM_BASE + RAM_SIZE) return -1;
memcpy(&ram[pa - RAM_BASE], &value, 8);
return 0;
}
static uint64_t ram_alloc(void *ctx)
{
(void)ctx;
if (next_table + 0x1000 > RAM_BASE + RAM_SIZE) return 0;
uint64_t table = next_table;
next_table += 0x1000;
memset(&ram[table - RAM_BASE], 0, 0x1000);
allocations++;
return table;
}
static void reset(struct shadow *s)
{
memset(ram, 0, sizeof(ram));
next_table = RAM_BASE;
allocations = 0;
s->root = ram_alloc(NULL);
}
#define TXSZ 28u /* 36-bit IPA, as VTCR_EL2 has it */
#define IPA_LIMIT (1UL << (64 - TXSZ))
/* The device's own layout: DRAM declared at 0xda000000 for 384 MiB with the
* 48 MiB reserve above it, the panel q1n1 reported, and 32 MiB of work area
* from q1n1's heap. The alignments differ on purpose -- 2 MiB, 128 KiB and
* 1 MiB -- so one list has to come out as blocks and pages together. */
static const struct stage2_range device[] = {
{0xda000000, 0xf5000000, S2_AP_RW | S2_ATTRS, 0}, /* DRAM + reserve */
{0xf9a20000, 0xfade4000, S2_AP_RW | S2_ATTRS, S2_XN}, /* panel */
{0x82500000, 0x84500000, S2_AP_RO | S2_ATTRS, S2_XN}, /* work area */
};
#define DEVICE_RANGES (sizeof(device) / sizeof(device[0]))
static const struct stage2_range *range_of(uint64_t ipa)
{
for (unsigned n = 0; n < DEVICE_RANGES; n++)
if (ipa >= device[n].lo && ipa < device[n].hi) return &device[n];
return NULL;
}
/* One page against the list: present exactly when a range holds it, mapped to
* itself, carrying that range's permission and execute bits. 0 when it agrees. */
static int page_agrees(const struct mmu_config *cfg, uint64_t ipa, const char *what)
{
struct mmu_leaf leaf;
if (mmu_walk(cfg, ipa, ram_read, NULL, &leaf) < 0) {
printf(" walk failed at %#llx (%s)\n", (unsigned long long)ipa, what);
return 1;
}
const struct stage2_range *r = range_of(ipa);
if (!r) {
if (!leaf.valid) return 0;
printf(" %#llx: mapped, but no range holds it (%s)\n", (unsigned long long)ipa, what);
return 1;
}
if (!leaf.valid) {
printf(" %#llx: absent, but %#llx..%#llx holds it (%s)\n", (unsigned long long)ipa,
(unsigned long long)r->lo, (unsigned long long)r->hi, what);
return 1;
}
if (leaf.pa != ipa) {
printf(" %#llx: resolves to %#llx, not itself (%s)\n", (unsigned long long)ipa,
(unsigned long long)leaf.pa, what);
return 1;
}
uint64_t low = leaf.descriptor & PT_LEAF_ATTRS, upper = leaf.descriptor & PT_LEAF_UPPER;
if (low != (r->low & PT_LEAF_ATTRS) || upper != r->upper) {
printf(" %#llx: attrs low %#llx upper %#llx, wanted %#llx %#llx (%s)\n",
(unsigned long long)ipa, (unsigned long long)low, (unsigned long long)upper,
(unsigned long long)(r->low & PT_LEAF_ATTRS), (unsigned long long)r->upper,
what);
return 1;
}
return 0;
}
/* Sweep [start, end) at `step`. Returns mismatches, stopping after a few. */
static int sweep(const struct mmu_config *cfg, uint64_t start, uint64_t end,
uint64_t step, const char *what)
{
int bad = 0;
for (uint64_t ipa = start; ipa < end; ipa += step) {
if (page_agrees(cfg, ipa, what)) bad++;
if (bad > 4) break;
}
return bad;
}
static int table_is_empty(uint64_t table)
{
for (unsigned n = 0; n < 512; n++) {
uint64_t d;
if (ram_read(NULL, table + 8UL * n, &d) || d) return 0;
}
return 1;
}
int main(void)
{
struct shadow s = {.txsz = TXSZ, .alloc = ram_alloc, .read = ram_read,
.write = ram_write, .ctx = NULL};
reset(&s);
check(s.root != 0, "stage-2 root allocated");
check(stage2_build(&s, device, DEVICE_RANGES) == 0, "the device's ranges build");
printf(" built %u ranges using %u tables\n", (unsigned)DEVICE_RANGES, allocations);
struct mmu_config map = {.granule_shift = 12, .txsz = TXSZ, .root = s.root};
/* Every 4K page of every range, and every page in a 2 MiB margin on each
* side of it. */
int bad = 0;
for (unsigned n = 0; n < DEVICE_RANGES; n++)
bad += sweep(&map, device[n].lo - 0x200000, device[n].hi + 0x200000, 0x1000,
"ranges and their margins");
check(bad == 0, "every page of every range maps to itself with that range's "
"permission, and the margins are absent");
/* The rest of the 36-bit space, coarsely: nothing there. */
check(sweep(&map, 0, IPA_LIMIT, 1UL << 21, "the whole IPA space at 2 MiB") == 0,
"nothing outside the list is mapped anywhere in the 36-bit space");
/* The edges by name, so a failure says which one. */
struct mmu_leaf leaf;
mmu_walk(&map, 0xd9fff000, ram_read, NULL, &leaf);
check(!leaf.valid, "the page below DRAM is absent");
mmu_walk(&map, 0xf5000000, ram_read, NULL, &leaf);
check(!leaf.valid, "the page above the reserve is absent");
mmu_walk(&map, 0xf4fff000, ram_read, NULL, &leaf);
check(leaf.valid && leaf.pa == 0xf4fff000, "the last page of the reserve is present");
mmu_walk(&map, 0, ram_read, NULL, &leaf);
check(!leaf.valid, "address zero is absent");
/* What each range has to be for the hardware. */
mmu_walk(&map, 0xda000000, ram_read, NULL, &leaf);
check((leaf.descriptor & S2_AP_RW) == S2_AP_RW && !(leaf.descriptor & S2_XN),
"DRAM is read-write and executable");
check(leaf.level == 2 && leaf.size == (2UL << 20), "DRAM is 2 MiB blocks, not a page per 4K");
mmu_walk(&map, 0x82500000, ram_read, NULL, &leaf);
check((leaf.descriptor & S2_AP_RW) == S2_AP_RO && (leaf.descriptor & S2_XN),
"the work area is read-only and execute-never");
mmu_walk(&map, 0xf9a20000, ram_read, NULL, &leaf);
check((leaf.descriptor & S2_AP_RW) == S2_AP_RW && (leaf.descriptor & S2_XN),
"the panel is read-write and execute-never");
check(leaf.level == 3, "the panel's 128 KiB-aligned start is pages, not a block "
"reaching below it");
check(allocations <= 8, "the whole map costs a handful of tables");
/* Refusals: the reason is the list, and nothing is written. */
struct shadow r = s;
reset(&r);
const struct stage2_range overlapping[] = {
{0xda000000, 0xf5000000, S2_AP_RW | S2_ATTRS, 0},
{0xe0000000, 0xe2000000, S2_AP_RO | S2_ATTRS, S2_XN}, /* inside the first */
};
check(stage2_build(&r, overlapping, 2) == STAGE2_BAD_RANGES,
"overlapping ranges are refused");
check(table_is_empty(r.root) && allocations == 1,
"...and nothing was written before the refusal");
const struct stage2_range unaligned[] = {{0xda000800, 0xf5000000, S2_AP_RW | S2_ATTRS, 0}};
check(stage2_build(&r, unaligned, 1) == STAGE2_BAD_RANGES, "an unaligned range is refused");
const struct stage2_range empty[] = {{0xda000000, 0xda000000, S2_AP_RW | S2_ATTRS, 0}};
check(stage2_build(&r, empty, 1) == STAGE2_BAD_RANGES, "an empty range is refused");
const struct stage2_range backwards[] = {{0xf5000000, 0xda000000, S2_AP_RW | S2_ATTRS, 0}};
check(stage2_build(&r, backwards, 1) == STAGE2_BAD_RANGES, "a backwards range is refused");
const struct stage2_range beyond[] = {
{IPA_LIMIT - 0x1000, IPA_LIMIT + 0x1000, S2_AP_RW | S2_ATTRS, 0}};
check(stage2_build(&r, beyond, 1) == STAGE2_BAD_RANGES,
"a range past the 36-bit IPA width is refused");
check(stage2_build(&r, device, 0) == STAGE2_BAD_RANGES,
"an empty list is refused, not built as a map of nothing");
check(table_is_empty(r.root) && allocations == 1, "no refusal left anything behind");
/* Out of tables is a different failure, and says so differently. */
next_table = RAM_BASE + RAM_SIZE;
check(stage2_build(&r, device, DEVICE_RANGES) == -1,
"running out of tables fails the build with -1, not with a partial map "
"reported as built");
/* The split: a guard needs one 4K page of a block to be its own entry. */
reset(&s);
check(stage2_build(&s, device, DEVICE_RANGES) == 0, "rebuilt");
map.root = s.root;
unsigned before = allocations;
uint64_t l3 = stage2_split(&s, 0xda001000);
check(l3 != 0, "a page inside a 2 MiB DRAM block can be split out");
check(allocations == before + 1, "the split costs one table");
check(stage2_split(&s, 0xda001000) == l3, "splitting again returns the same table");
check(allocations == before + 1, "...and costs nothing");
check(sweep(&map, 0xd9e00000, 0xda400000, 0x1000, "around the split") == 0,
"every page around the split still maps to itself with DRAM's attributes");
mmu_walk(&map, 0xda001000, ram_read, NULL, &leaf);
check(leaf.valid && leaf.level == 3 && leaf.size == 0x1000,
"the split page is a level-3 entry");
mmu_walk(&map, 0xda200000, ram_read, NULL, &leaf);
check(leaf.valid && leaf.level == 2, "the next block is still a block");
/* The entry a guard writes, and what closing it does. */
uint64_t slot = l3 + 8UL * ((0xda001000 >> 12) & 0x1ff), d = 0;
ram_read(NULL, slot, &d);
check((d & PT_OA_MASK) == 0xda001000 && (d & PT_TABLE) && (d & PT_VALID),
"the slot names the page");
ram_write(NULL, slot, d & ~PT_VALID);
mmu_walk(&map, 0xda001000, ram_read, NULL, &leaf);
check(!leaf.valid, "clearing the slot makes that one page absent");
mmu_walk(&map, 0xda000000, ram_read, NULL, &leaf);
check(leaf.valid, "...and its neighbour is untouched");
ram_write(NULL, slot, d);
/* Attributes survive a split: the work area stays read-only. */
l3 = stage2_split(&s, 0x83000000);
check(l3 != 0, "a work-area block can be split");
mmu_walk(&map, 0x83000000, ram_read, NULL, &leaf);
check(leaf.valid && (leaf.descriptor & S2_AP_RW) == S2_AP_RO && (leaf.descriptor & S2_XN),
"the split pieces keep read-only and execute-never");
check(sweep(&map, 0x82300000, 0x84700000, 0x1000, "work area after the split") == 0,
"the whole work area and its margins still agree with the list");
/* An absent page is not made present by asking to split it. */
before = allocations;
check(stage2_split(&s, 0xf5000000) == 0, "splitting an absent page returns nothing");
check(allocations == before, "...and allocates nothing");
mmu_walk(&map, 0xf5000000, ram_read, NULL, &leaf);
check(!leaf.valid, "...and the page is still absent");
check(stage2_split(&s, 0x10000000) == 0,
"splitting under an absent level-1 entry returns nothing");
/* A temporary execute permission change belongs to only one CPU. Build
* six maps with the real emitter, split the same page in each, and open
* each guard in turn while walking every other map independently. */
reset(&s);
struct shadow private[6];
uint64_t slots[6], descriptors[6];
const uint64_t guarded = 0xda001000;
for (unsigned cpu = 0; cpu < 6; cpu++) {
private[cpu] = s;
if (cpu) private[cpu].root = ram_alloc(NULL);
check(private[cpu].root != 0, "private stage-2 root allocated");
check(stage2_build(&private[cpu], device, DEVICE_RANGES) == 0,
"private stage-2 map builds");
uint64_t table = stage2_split(&private[cpu], guarded);
check(table != 0, "private guard split succeeds");
slots[cpu] = table + 8 * ((guarded >> 12) & 511);
check(ram_read(NULL, slots[cpu], &descriptors[cpu]) == 0,
"private guard descriptor readable");
check(ram_write(NULL, slots[cpu], descriptors[cpu] | S2_XN) == 0,
"private guard closed");
for (unsigned other = 0; other < cpu; other++)
check(slots[cpu] != slots[other], "private guard leaf storage differs");
}
for (unsigned cpu = 0; cpu < 6; cpu++) {
ram_write(NULL, slots[cpu], descriptors[cpu]);
for (unsigned other = 0; other < 6; other++) {
map.root = private[other].root;
uint64_t desc;
check(ram_read(NULL, slots[other], &desc) == 0,
"guard permissions readable");
check(!!(desc & S2_XN) == (other != cpu),
"opening one guard leaves all other CPUs execute-never");
check(page_agrees(&map, 0xf5000000, "private absent device") == 0,
"private map retains absent device hole");
check(page_agrees(&map, RAM_BASE, "private work permissions") == 0,
"private map retains read-only work permissions");
}
ram_write(NULL, slots[cpu], descriptors[cpu] | S2_XN);
}
/* Lockdown must remove writes across every CPU without changing guards,
* neighbouring pages, identity addresses or allocating during execution. */
const uint64_t rolo=0xda001000,rohi=0xda805000;
static uint8_t saved[RAM_SIZE];
for(unsigned cpu=0;cpu<6;cpu++) {
check(stage2_split(&private[cpu],rolo)!=0 &&
stage2_split(&private[cpu],rohi-4096)!=0,"RoRgn boundaries prepared");
}
memcpy(saved,ram,sizeof(ram));
for(unsigned cpu=0;cpu<6;cpu++)
check(!stage2_readonly(&private[cpu],rolo,rohi,1),"all-CPU seal preflight");
check(!memcmp(saved,ram,sizeof(ram)),"preflight never mutates mappings");
before=allocations;
for(unsigned cpu=0;cpu<6;cpu++) {
check(!stage2_readonly(&private[cpu],rolo,rohi,0),"private map sealed");
check(!stage2_readonly(&private[cpu],rolo,rohi,0),"sealing is idempotent");
map.root=private[cpu].root;
for(uint64_t at=rolo-4096;at<=rohi;at+=4096) {
check(!mmu_walk(&map,at,ram_read,NULL,&leaf) && leaf.valid && leaf.pa==at &&
(leaf.descriptor&PT_AP)==(at>=rolo && at<rohi ? S2_AP_RO:S2_AP_RW),
"sealed interior with writable neighbours and identity PA");
}
uint64_t desc;ram_read(NULL,slots[cpu],&desc);
check((desc&S2_XN) && !(desc&(1UL<<7)),"closed execute guard remains closed and read-only");
}
check(allocations==before,"sealing allocates no tables");
memcpy(saved,ram,sizeof(ram));
check(stage2_readonly(&private[0],rolo+1,rohi,0)!=0,"unaligned range refused");
check(stage2_readonly(&private[0],rohi,rolo,0)!=0,"reversed range refused");
check(stage2_readonly(&private[0],rohi,IPA_LIMIT+4096,0)!=0,"IPA overflow refused");
check(stage2_readonly(&private[0],0xf4fff000,0xf5001000,0)!=0,"partially absent range refused");
check(stage2_readonly(&private[0],0xdab01000,0xdac00000,0)!=0,"unprepared boundary refused");
check(!memcmp(saved,ram,sizeof(ram)),"invalid requests leave all descriptors unchanged");
ram_read(NULL,slots[0],&d);
ram_write(NULL,slots[0],d+4096);
memcpy(saved,ram,sizeof(ram));
check(stage2_readonly(&private[0],rolo,rohi,0)!=0,"non-identity mapping refused");
check(!memcmp(saved,ram,sizeof(ram)),"malformed map rejection has no writes");
/* The external monitor arena uses physical bits above 32 for both table
* storage and mapped addresses. Check the actual native descriptors, not
* just the loader's requested range list. Keep the entire private context
* absent even at the page next to the guest bootstrap reserve. */
const uint64_t work = 0xae1000000UL, tables = 0xae129e000UL;
const uint64_t work_end = 0xae9000000UL;
const struct stage2_range external[] = {
{0x8c0000000UL, work, S2_AP_RW | S2_ATTRS, 0},
{0xf9a20000, 0xfade8000, S2_AP_RW | S2_ATTRS, S2_XN},
{tables, work_end, S2_AP_RO | S2_ATTRS, S2_XN},
};
RAM_BASE = tables;
reset(&s);
check(!stage2_build(&s, external, 3), "high-memory external arena builds");
map.root = s.root;
for (uint64_t at = work; at < work_end + 4096; at += 4096) {
int rc = mmu_walk(&map, at, ram_read, NULL, &leaf);
int present = at >= tables && at < work_end;
check(!rc && !!leaf.valid == present,
"external work context and end guard are absent");
if (present)
check(leaf.pa == at && (leaf.descriptor & PT_AP) == S2_AP_RO &&
(leaf.descriptor & S2_XN),
"every external table page preserves high PA bits and RO/XN");
}
check(!mmu_walk(&map, work - 4096, ram_read, NULL, &leaf) && leaf.valid &&
leaf.pa == work - 4096 && (leaf.descriptor & PT_AP) == S2_AP_RW,
"last guest bootstrap page remains writable below private context");
const uint64_t absent[] = {0xb9e40000UL, 0xba340000UL,
tables & 0xffffffffUL, work_end & 0xffffffffUL};
for (unsigned i = 0; i < sizeof(absent) / sizeof(absent[0]); i++)
check(!mmu_walk(&map, absent[i], ram_read, NULL, &leaf) && !leaf.valid,
"q1n1 heap, locator and truncated high addresses remain absent");
check(allocations < RAM_SIZE / 4096, "external map fits bounded table storage");
if (failures) {
printf("FAILED: %d of %d checks\n", failures, checks + failures);
return 1;
}
printf("PASS: %d stage-2 map checks (native, sanitizers on)\n", checks);
return 0;
}