Files
frostbyte/src/elfTest.c
T
2026-09-10 14:32:40 +02:00

180 lines
6.9 KiB
C

#include "ktest.h"
#include "elf.h"
#include "heap.h"
#include "string.h"
#include "paging.h"
/*
* elfSlotBase/elfSlotStackTop/elfSlotBrkStart are pure address arithmetic
* (no VFS, no scheduler, no paging), so unlike exec()/spawn() themselves
* (which only run for real via a live boot demo -- see proctest.c and
* tools/ci-test.sh needles execstart-ok/spawn-wait-ok/exec-pid-preserved-ok)
* this math is fully coverable here, deterministically, before the
* scheduler even starts.
*/
KTEST(elfSlotBaseFloorsToSlotBoundary)
{
KT_EQU(t, elfSlotBase(0x40000000ull), 0x40000000ull);
KT_EQU(t, elfSlotBase(0x40001000ull), 0x40000000ull);
KT_EQU(t, elfSlotBase(0x4FFFFFFFull), 0x40000000ull);
KT_EQU(t, elfSlotBase(0x50000000ull), 0x50000000ull);
KT_EQU(t, elfSlotBase(0xB0000ABCull), 0xB0000000ull);
}
KTEST(elfSlotStackAndBrkMatchBootTimeConvention)
{
/* kmain.c's spawnUser calls hardcode stackTop=base+0x08000000 and
brk=base+0x04000000 for every binary already spawned at boot (e.g.
sbrktest: base 0x60000000, stackTop 0x68000000, brk 0x64000000) --
the derived values must agree exactly, since exec()/spawn() reuse
the same slots and a mismatch would map a fresh stack on top of
code/data or vice versa. */
KT_EQU(t, elfSlotStackTop(0x60001000ull), 0x68000000ull);
KT_EQU(t, elfSlotBrkStart(0x60001000ull), 0x64000000ull);
KT_EQU(t, elfSlotStackTop(0x40000abcull), 0x48000000ull);
KT_EQU(t, elfSlotBrkStart(0x40000abcull), 0x44000000ull);
/* An entry point right at the top of its slot still derives the same
slot-relative stack/brk as one near the bottom. */
KT_EQU(t, elfSlotStackTop(0x0FFFFFFFull), 0x08000000ull);
KT_EQU(t, elfSlotBrkStart(0x0FFFFFFFull), 0x04000000ull);
}
/*
* elfBuildInitialStack (added alongside argv/argc support for exec()/
* spawn(): copyUserArgv in syscall.c, crt.S's _start now reading argc/argv
* off the stack instead of calling main() with nothing) does not care
* whether the memory it writes into is a real user slot or not -- it just
* needs [stackTop-stackSize, stackTop) to already be valid, writable
* memory, which a plain kmalloc buffer satisfies just as well as a
* pagingMapUserRange'd user stack. That makes the exact byte layout fully
* testable here, deterministically, without any live process at all --
* the live proof (kmain.c's proctest.c spawning argtest.c with a specific
* argv, needles "proctest: spawn with argv ok"/"argtest: argv-ok" in
* tools/ci-test.sh) only proves crt.S reads the same layout back
* correctly, not what the layout itself has to look like.
*/
KTEST(elfBuildInitialStackLaysOutArgcArgvAndStringsCorrectly)
{
uint64 stackSize = 4096;
uint8* stack = (uint8*)kmalloc(stackSize);
uint64 stackTop = (uint64)stack + stackSize;
const char* argv[3] = { "/bin/argtest", "alpha", "beta" };
uint64 rsp = 0;
KT_TRUE(t, elfBuildInitialStack(stackTop, stackSize, argv, 3, &rsp));
/* SysV process entry: rsp must land inside the stack it was given and
be 16-byte aligned so _start's own `call main` leaves main looking
at the usual post-call rsp%16==8. */
KT_GE(t, rsp, (uint64)stack);
KT_LT(t, rsp, stackTop);
KT_EQU(t, rsp % 16, 0);
const uint64* slots = (const uint64*)rsp;
KT_EQ(t, (int64)slots[0], 3);
KT_STREQ(t, (const char*)slots[1], "/bin/argtest");
KT_STREQ(t, (const char*)slots[2], "alpha");
KT_STREQ(t, (const char*)slots[3], "beta");
KT_EQU(t, slots[4], 0);
kfree(stack);
}
KTEST(elfBuildInitialStackWithZeroArgcStillTerminatesArgv)
{
uint64 stackSize = 256;
uint8* stack = (uint8*)kmalloc(stackSize);
uint64 stackTop = (uint64)stack + stackSize;
uint64 rsp = 0;
KT_TRUE(t, elfBuildInitialStack(stackTop, stackSize, nullptr, 0, &rsp));
KT_EQU(t, rsp % 16, 0);
const uint64* slots = (const uint64*)rsp;
KT_EQ(t, (int64)slots[0], 0);
KT_EQU(t, slots[1], 0);
kfree(stack);
}
KTEST(elfBuildInitialStackRejectsArgcAboveElfMaxArgs)
{
uint64 stackSize = 4096;
uint8* stack = (uint8*)kmalloc(stackSize);
uint64 stackTop = (uint64)stack + stackSize;
const char* argv[1] = { "x" };
uint64 rsp = 0;
KT_FALSE(t, elfBuildInitialStack(stackTop, stackSize, argv, elfMaxArgs + 1, &rsp));
KT_FALSE(t, elfBuildInitialStack(stackTop, stackSize, argv, -1, &rsp));
kfree(stack);
}
KTEST(elfBuildInitialStackRejectsArgvTooBigForStack)
{
/* Deliberately far too small to hold even the fixed argc/argv/NULL
area for a single argument, let alone the string itself -- must fail
cleanly (false, *outRsp untouched) rather than write below
stackTop-stackSize. */
uint64 stackSize = 8;
uint8* stack = (uint8*)kmalloc(stackSize);
uint64 stackTop = (uint64)stack + stackSize;
const char* argv[1] = { "/bin/argtest" };
uint64 rsp = 0xDEADBEEFull;
KT_FALSE(t, elfBuildInitialStack(stackTop, stackSize, argv, 1, &rsp));
KT_EQU(t, rsp, 0xDEADBEEFull);
kfree(stack);
}
/*
* elfSegmentPagingFlags is elfLoad's own per-segment W^X decision reduced
* to pure arithmetic on a p_flags value -- no paging table, no live
* process, so every PF_X/PF_W combination a real ELF linker could ever
* emit is checked here directly, deterministically, rather than only
* inferred indirectly from whether user/elftest.c's live boot demo (below)
* happens to fault where expected.
*/
KTEST(elfSegmentPagingFlagsReadExecuteIsWritableNeverAndAlwaysExecutable)
{
/* A real .text/.rodata PT_LOAD segment: PF_R|PF_X (5). Must come back
with neither pageWrite nor pageNoExecute -- read-only, executable. */
KT_EQU(t, elfSegmentPagingFlags(5), 0ull);
}
KTEST(elfSegmentPagingFlagsReadWriteIsWritableAndNeverExecutable)
{
/* A real .data/.bss PT_LOAD segment: PF_R|PF_W (6). Must come back
with pageWrite set and pageNoExecute set -- the actual W^X split
this session's change exists to enforce. */
KT_EQU(t, elfSegmentPagingFlags(6), pageWrite | pageNoExecute);
}
KTEST(elfSegmentPagingFlagsReadOnlyIsNeitherWritableNorExecutable)
{
/* PF_R alone (4) -- a real ELF can emit a read-only, non-executable
segment (e.g. .rodata split into its own PT_LOAD by some linkers,
even though this kernel's own user.ld keeps it with .text). Must
still come back non-writable AND non-executable -- PF_X is the only
bit that ever clears pageNoExecute, never PF_R. */
KT_EQU(t, elfSegmentPagingFlags(4), pageNoExecute);
}
KTEST(elfSegmentPagingFlagsReadWriteExecuteIsWritableAndExecutable)
{
/* PF_R|PF_W|PF_X (7) -- every real linker refuses to emit this for a
normal PT_LOAD, but the function must still do the honest thing
with it rather than assume it can never happen: writable (PF_W)
and executable (PF_X), i.e. neither hardening bit set. Confirms
PF_W and PF_X are decided independently of one another, not "if
not exactly PF_X, assume data". */
KT_EQU(t, elfSegmentPagingFlags(7), pageWrite);
}