Repository navigation
Executables relocate themselves, and the kernel reads no executable's dynamic section - #836
Conversation
…s block The kernel copies an executable's PT_TLS template out of the file at spawn and builds every thread's block from that copy, while it applies the executable's RELATIVE relocations to image pages as they fault in. A RELATIVE inside [PT_TLS vaddr, +filesz) relocates .tdata and never the copies. rust-lld writes one for a #[thread_local] static holding a &'static str (measured on a static PIE of toyos/tests/relocate/fixture.rs built with --cfg tls, both architectures). Measured absent from every shipped program, clang and LLD (roast-v4 §0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…amic section Stage 1 of Move 1 of issues/the-kernel-still-parses-what-userland-writes.md. toyos::relocate applies an image's own RELATIVE relocations, called first by std's and libc's _start with __ehdr_start's address, found PC-relatively, before anything reads a relocated word. A pure core over the program headers, the dynamic section and the RELA table, all byte slices, answers each write as (vaddr, value); one unsafe wrapper builds the slices from the running image and writes. The core is held to no panic path by clippy's indexing_slicing, panic, unwrap_used, expect_used and arithmetic_side_effects, since a panic formats through pointers that are not relocated yet. A refusal writes one literal to slot 2 and exits 127. It refuses by name DT_NEEDED, a DT_JMPREL with entries, DT_REL, DT_RELR, DT_TEXTREL and DF_TEXTREL, every type but the machine's RELATIVE (read off e_machine, so both architectures' constants build on every host), a write outside a writable PT_LOAD, and a write meeting the PT_TLS template, which the kernel copies from the file into every thread's block and which a write here would never reach: that closes issues/a-relocation-in-an-executables-tls-template-reaches-no-threads-block.md, filed in the commit before this one. The kernel loses the executable half of its loader: read_exe_tables and everything it read (PT_DYNAMIC, DT_RELA, DT_JMPREL, .dynsym, .dynstr, .gnu.hash, .symtab, the section-header search for .rela.dyn), the relocation index and its per-page application in the fault path, the fault record's relocation count, start-up DT_NEEDED loading and binding (loader/symbols.rs, resolve_lib_bind_relocs) and the executable's TLS relocations. The TLS layout at spawn is the executable's module alone, id 1; dlopen numbers from 2. toyos-elf loses the API only that path used (for_executable, ExeReservation, ExeRefusal, FillLattice, FILL_GRANULE, StraddlesFillPage, Rules::fill) and its tests, and the fuzz test its executable mode. The spawn record loses unresolved=, relocs= and deps=. No toolchain or target-spec change: every executable is already a PIE with no PT_INTERP whose relocations are RELATIVE alone. The interval it opens: an executable with a DT_NEEDED does not start, so std_tls is deleted and comes back with the userland loader, as the track now records; dlopen_dedup's two-spellings arm loses the kernel cache spelling it guarded and goes; abuse_elf_loader's spawn cases on tables the kernel no longer reads are answered either way and their child dies. The RELR defect keeps its dlopen half; the package-libraries defect is restated; RELRO staying writable is filed. The comment issues/comments-name-defects-no-issue-holds.md cited goes with case 12's index, and the tree no longer has what it named: KernelAllocator::alloc checks MAX_HEAP_ALLOC before it takes the dlmalloc lock. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…for an A/B /system/bin/shell -c '' returns as soon as it has started, so spawn to exit is the kernel's spawn, the program's own start (its relocation, which stage 1 moved there from the kernel's spawn and fault path) and its exit. The job prints the minimum and the median over 50 spawns after one that warms the file's pages; the row judges that it ran and said so, and the host reads the numbers, as syscall_cost's. Under QEMU a duration is no measurement, so the shared boot skips it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
…'s refusal stays its own Dynamic::strtab_table, Dynamic::needed with DT_NEEDED, SectionTable::rela_dyn and Layout::file_bytes_from had no caller left but their tests once the kernel stopped reading executables' tables. The two tests of the dynamic table's walk that read it through needed now read it through parse. The relocator's Refusal and exit status are private: nothing outside the module names them. The loader's header names the build-id note it still reads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
Seen once on this branch, on the run that first made the head's sysroot: workers holding the build lock shared waited on the sysroot key's lock while the worker that held it waited for the build lock exclusive. The same head ran 50 of 50 green once its sysroot was made. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
Log: unmutated-relocate (unmutated.final.log), part 1 of 1
|
Log: host-ci (ci-host.log), part 1 of 9
|
Log: host-ci (ci-host.log), part 2 of 9
|
Log: host-ci (ci-host.log), part 3 of 9
|
Log: host-ci (ci-host.log), part 4 of 9
|
Log: host-ci (ci-host.log), part 5 of 9
|
Log: host-ci (ci-host.log), part 6 of 9
|
Log: host-ci (ci-host.log), part 7 of 9
|
Log: host-ci (ci-host.log), part 8 of 9
|
Log: host-ci (ci-host.log), part 9 of 9
|
Log: guest-suite (guest-suite.log), part 1 of 1
|
Log: hosted-clang-as-written (hosted-clang-final-asis.log), part 1 of 7
|
Log: hosted-clang-as-written (hosted-clang-final-asis.log), part 2 of 7
|
Log: hosted-clang-as-written (hosted-clang-final-asis.log), part 3 of 7
|
Log: hosted-clang-as-written (hosted-clang-final-asis.log), part 4 of 7
|
Log: hosted-clang-as-written (hosted-clang-final-asis.log), part 5 of 7
|
Log: hosted-clang-as-written (hosted-clang-final-asis.log), part 6 of 7
|
Log: hosted-clang-as-written (hosted-clang-final-asis.log), part 7 of 7
|
Log: hosted-clang-include-free (hosted-clang-final-noinclude.log), part 1 of 1
|
Log: negative-control (hosted-clang-negative.log), part 1 of 1
|
Log: m1-std-start-skips-relocate (m1-std-start-skips-relocate.log), part 1 of 1
|
Log: m2-libc-start-skips-relocate (m2-libc-start-skips-relocate.log), part 1 of 1
|
Log: m3-addend-without-bias (m3-addend-without-bias.final.log), part 1 of 1
|
Log: m4-no-tls-template-refusal (m4-no-tls-template-refusal.final.log), part 1 of 1
|
Log: host-ci (host.log), part 3 of 9
|
Log: host-ci (host.log), part 4 of 9
|
Log: host-ci (host.log), part 5 of 9
|
Log: host-ci (host.log), part 6 of 9
|
Log: host-ci (host.log), part 7 of 9
|
Log: host-ci (host.log), part 8 of 9
|
Log: host-ci (host.log), part 9 of 9
|
Log: guest-suite (guest.log), part 1 of 2
|
Log: guest-suite (guest.log), part 2 of 2
|
Log: guest-suite-wedged (guest-wedged.log), part 1 of 2
|
Log: guest-suite-wedged (guest-wedged.log), part 2 of 2
|
Log: toyos-elf-tests (elf.log), part 1 of 1
|
Log: m10-old-fallback (m10.log), part 1 of 1
|
Log: m10-patch (m10-old-fallback.patch), part 1 of 1m10: the old extrapolating fallback restored inside --- a/toyos-elf/src/layout.rs
+++ b/toyos-elf/src/layout.rs
@@ -538,11 +538,22 @@
/// Where the file holds `range`, or `None` when no one segment's file
/// bytes hold all of it: past `p_filesz` a segment is zeroes, not the file.
pub fn file_offset_of(&self, range: ImageRange) -> Option<u64> {
- let seg = self.segments().iter().find(|seg| {
- seg.image.start <= range.start && range.end().get() <= seg.image.start.wrapping_add(seg.filesz)
- })?;
- // `p_offset + p_filesz` fits a `u64`, and `range` lies inside both.
- Some(seg.file_offset.wrapping_add(range.start.wrapping_sub(seg.image.start)))
+ let into = |seg: &Segment| range.start.checked_sub(seg.image.start);
+ for seg in self.segments() {
+ if let Some(within) = into(seg).filter(|&w| w < seg.filesz) {
+ return seg.file_offset.checked_add(within);
+ }
+ }
+ let mut best: Option<(&Segment, u64)> = None;
+ for seg in self.segments() {
+ if let Some(within) = into(seg) {
+ if best.is_none_or(|(_, w)| within < w) {
+ best = Some((seg, within));
+ }
+ }
+ }
+ let (seg, within) = best?;
+ seg.file_offset.checked_add(within)
}
}
# m10.sh
set -u
cd /Users/jan/Dev/jan/toyos-selfreloc
D=/Users/jan/.claude/jobs/2280e09e/tmp/scratchpad/orch/loader/stage1/r4
git apply --check $D/m10-old-fallback.patch && git apply $D/m10-old-fallback.patch || exit 2
cargo test -p toyos-elf --test crafted a_tls_template_starting_in_bss_has_no_file_offset
echo "m10 EXIT=$?"
git apply -R $D/m10-old-fallback.patch
git status --porcelain toyos-elf/src/layout.rs
git diff --stat |
…apolation, which are deleted Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
#846, #843, #849, #850, #840, #839, #837) into the batch: the icons' and wallpaper's digests hash with toyos-sha2-hw, and the loader's wall clock reads through its own UEFI bindings The batch's merge of main at f72d53d moved #813's wallpaper and #814's icon digest tests onto `toyos_sha2`. #833, already on main, had replaced the root package's `toyos-sha2` dependency with `toyos-sha2-hw`, so CI's merge of the two compiled no `toyos-build` lib test and both the build system's tests and clippy went red with E0432. Both tests now hash through `toyos_sha2_hw`, as every other SHA-256 the build takes does. #842's `bootloader/src/wallclock.rs` was written on the `uefi` crate, which #815 removes; it now calls `efi`'s `RuntimeServices::get_time`, whose `Time` fields are plain and whose error is the `Status` itself. `start_kernel` takes main's `wall_clock` and the batch's `SystemTable`; the batch's `armed_at` goes, as main replaced it with `wallclock::now`. Cargo.lock takes main's `ntapi`; `nonempty` goes with `gix`, which the batch removed and which was its only user (`cargo metadata --offline`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
, #836, #839, #840, #842 through #847, #849 and #850, into consent system.toml: sshserver and shell start both toyfetch (#843) and grants. tests/common/qemu.rs: Profile carries both Desktop and MetalAmdVi (#837). tests/toyos.rs: SCREEN_TESTS carries consent_prompt beside virt_wall_clock_utc (#842) and virt_low_ecam (#840). Beyond the conflict lines: #833 replaced the build's toyos-sha2 dependency with toyos-sha2-hw, so consent_prompt digests its job with toyos_sha2_hw::sha256_digest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
Main now carries the loader's relocation (#836), stat identity (#851), libc's realpath (#854), the kernel's directory identity (#855) and the second bench (#852). ALL_CONFIGS keeps consentcase and hostedclangcase; the host-tools issue takes main's os-release row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
… of them is closed #836 relocates an executable in userland, so the kernel reads no table its dynamic section names and allocates nothing a file declares: the loader issue's second half holds, and hosted_clang_hello gets past both spawns, its first. That issue was this branch's own and is cited nowhere else. toyos-builds-itself.md's M2 paragraph said no guest had run either binary. It now says what hosted_clang_hello shows, and what M2's exit still waits on: the driver's link, which starts LLD as a child, and pkg's install. The test's header cited an "M2a" the track never named; it cites M2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C
Stage 1 of Move 1 of
issues/the-kernel-still-parses-what-userland-writes.md: every executable applies its ownRELATIVErelocations first thing in_start, and the kernel stops reading, relocating and binding executables. clang and ld.lld, which the kernel refused at spawn (#830), start and produce the host's bytes.What changed, and why
toyos::relocate(new module,toyos/src/relocate/mod.rs; no new crate, since std and libc already depend ontoyos). A pure core over the program headers, the dynamic section and theDT_RELAtable, all byte slices, answers each write as(vaddr, value), and answers the bias too (Image::bias: where the header is found, less the link address of the header's segment); oneunsafewrapper,relocate_self, builds those slices from the running image and writes. Nothing in it reads a relocated word: no static holding a pointer, no&dyn, no formatting, and no panic path, held by#![deny(clippy::indexing_slicing, clippy::panic, clippy::unwrap_used, clippy::expect_used, clippy::arithmetic_side_effects)]on the module. A refusal writes one literal in place to slot 2 (no table of names, whose entries would be relocated words) and exits 127.R_X86_64_RELATIVEandR_AARCH64_RELATIVEasbias + addend; the type is read off the header'se_machine, so both architectures' constants build and are tested on every host and the module needs notarget_arch.DT_NEEDED; aDT_JMPRELwith entries, or with noDT_PLTRELSZto say it has none;DT_REL,DT_RELSZ,DT_RELENT;DT_RELR,DT_RELRSZ,DT_RELRENTand Android's threeRELRtags; AArch64's three signed-RELRtags (0x70000011–13, processor-specific, so refused in an AArch64 image only); Android's packedDT_ANDROID_REL/RELAand their sizes (AndroidPacked);DT_TEXTREL,DF_TEXTREL; every other type; a write outside a writablePT_LOAD; a write meeting thePT_TLStemplate (the kernel copies the template from the file into every thread's block, so a relocation there never reaches a thread). Also a header it does not know, a header or program-header table no non-writablePT_LOADat offset 0 holds, aPT_DYNAMICin no file bytes, and aDT_RELAtable of the wrong shape or in a writable segment (the core holds a&[u8]over it across the writes).__ehdr_startfound PC-relatively in_start(lea/adrp+add, which lld relaxes toadron AArch64), the program headers at__ehdr_start + e_phoff. Measured in the round-1 binaries:relocate_selfand itsapplyload nothing through.gotor.data.rel.roon either architecture (objdump -dof x86-64shelland AArch64supervisor: their only PC-relative operands are in.rodataand.text)._start(sdk/std/sys/pal/mod.rs,userland/libc/src/arch/{x86_64,aarch64}.rs) call it first; argc and argv are read from the entryspas before.read_exe_tablesand everything it read (PT_DYNAMIC,DT_RELA,DT_JMPREL,.dynsym,.dynstr,.gnu.hash,.symtab, the section-header search for.rela.dyn),MAX_GNU_HASH_BYTES,read_elf_table, the relocation index (elf/index.rs) and its per-page application in the fault path,PageFaultRecord::reloc_countwith itsRflag and print, start-upDT_NEEDEDloading and binding (loader/symbols.rs,load_needed_libs,map_libs,resolve_lib_bind_relocs,apply_tls_relocs), andLoadedLib::phys_base, which only that path read. The TLS layout at spawn is the executable's module alone, id 1, at offset 0;dlopennumbers from 2, as before. The spawn record losesunresolved=,relocs=anddeps=.toyos-elfloses what only that path called:for_executable,ExeReservation,ExeRefusal,FillLattice,FILL_GRANULE,Rules::fill,StraddlesFillPage,Dynamic::strtab_table,Dynamic::neededwithDT_NEEDED,SectionTable::rela_dyn,Layout::file_bytes_from, their tests, and the fuzz test's executable mode. With one static module,tls::place_module(its cursor was 0 at its one caller) andStatic's first alignment (equal to its maximum at every caller) go too, with the tests that held only them:Static::new(variant, total_memsz, align).Layout::vaddr_to_file_offsetextrapolated from the nearest segment below an address past every segment's file bytes, for theDT_*tags this branch stops reading; its one remaining caller, the kernel'sPT_TLStemplate read, turned a template starting in.bssinto a read of another segment's bytes or of bytes past the file's end. It is deleted with its two crafted tests;Layout::file_offset_oftakes the template'sImageRangeand answers only when one segment's file bytes hold all of it, and spawn refuses the binary otherwise (InvalidArgument, as before).real.rs's three mappings of the lld fixture hold unchanged through it.PT_INTERPwhose relocations areRELATIVEalone. No fork changes.spawn_cost(new guest binary,RUST_SKIP, aMETALrow, and a job of theTESTCASESboot that row reads):/system/bin/toybox echo, stdin and stdout null, spawned and waited for 50 times after one warm-up, the minimum and median printed for a same-session A/B on the T14. Round 1 left the job offTESTCASES, so the row never ran in either arm; it is on it now (235 jobs after, 8 before). Round 2 spawned/system/bin/shell, which notests/testcasesROOT carries, so the T14 readings at fd12ed1 panicked the job (spawn /system/bin/shell: entity not found) in every arm.toyboxis a[programs]row of that config, so it is on that ROOT, and of the programs there it carries the mostRELATIVEentries (relacount: toybox 1518, fileserver 1422, supervisor 1348, down to test-runner 664). Thattoyboxspawns from atestcasesjob is already read off the T14:toybox_file_toolsspawns/system/bin/cpand the other links to it, and passed in fd12ed1's after arm.The interval, and records
DT_NEEDEDno longer starts: its own_startrefuses it, exit 127 with the refusal's line on slot 2, or, holding no slot 2, the kernel's handle-fault exit 139 (the line names a handle it does not hold). Nothing shipped links one.std_tls(a startup library's TLS beside the executable's) is deleted and comes back with the stage that brings the userland loader; the track records it.dlopen_dedup's two-spellings arm guarded the kernel'sDT_NEEDEDcache spelling, which is deleted, so it tests nothing and goes.tests/checks/metal.rsandtests/checks.rsstop namingtest_rs_std_tlsas a fixture.issues/the-kernel-still-parses-what-userland-writes.mdrecords stage 1, the interval, the refusal's two exits, andTlsModule::base_offsetandStatic::tpoff's offset, 0 at every caller now, left to the userland-loader stage, which deletes them with the kernel's TLS build (they run throughdlopen's path, outside this fence).issues/a-relocation-in-an-executables-tls-template-reaches-no-threads-block.mdis filed (first commit) and closed by the second, with the host test on lld's own output. The RELR defect keeps itsdlopenhalf.issues/a-package-cannot-ship-its-own-libraries.mdis restated (nothing loads aDT_NEEDEDnow).issues/an-executables-relro-stays-writable.mdis filed.issues/comments-name-defects-no-issue-holds.mdis closed: the comment it cited goes with case 12's index, andKernelAllocator::allocchecksMAX_HEAP_ALLOCbefore it takes the dlmalloc lock.issues/a-suite-that-builds-its-sysroot-can-wedge-on-its-own-locks.mdfiles a harness wedge round 1 hit once.load_needed_libs(a-t14-boot-wedges-…),RelocationIndex::apply_to_page(clippy-stage-two-…),resolve_lib_bind_relocs(a-dlopened-library-never-binds-…), andecho-faulted-…'s second candidate, a page handed out half-relocated, which no longer has a mechanism.issues/the-program-loader-refuses-clang-and-lld-…is on hosted_clang_hello: ToyOS's own clang and ld.lld compile and link a C program in a guest, byte-identical to the host's, on demand #830's branch, notmain; this PR's positive check below meets its exit's first half ("hosted_clang_hellogets past both spawns"), and it is closed where it lands.Gates (5483c3a, which merges origin/main 819b308; the head edcc8a2 adds only an issue paragraph and deletes two guest-test comments that cited the deleted extrapolation, so its tests are 5483c3a's; rows naming another head say so)
cargo run -- --ci hostat 5483c3acargo test -p toyos-elfon that tree, and in the host runa_tls_template_starting_in_bss_has_no_file_offsetandonly_a_segments_file_bytes_have_a_file_offsetpasscargo test -p toyos relocateat fd12ed1, and in the host run at 5483c3acargo testat 5483c3a, run alone--ci hostcargo testat 5483c3avirtbootvirt_user_modein that suite: the supervisor runs at EL0 and spawnslogkeeper, throughR_AARCH64_RELATIVEcargo run -- --build-only, and with--arch aarch64, at fd12ed1; the guest suite builds both at 5483c3a, and the staging below built both at aabf349--metal --metal-readbackfor the before and after arms belowWhy the guest tests:
spawn_cost's product is a duration, which only metal measures; QEMU skips it.abuse_elf_loadercase 22 (new): two copies of the test's own std image, itsDT_DEBUGtag rewritten toDT_NEEDEDin one andDT_RELRin the other, spawned with stdout and stderr piped: each must exit 127 and say exactly the refusal's line; spawned again with no slot, each must exit 139. A copy that reachesmainpanics there, so a refusal that fell through cannot rerun the test. A host test cannot reach this: it would needrelocate_selfrunning on a live, mapped image as its own_start, the PC-relative__ehdr_start, and the kernel's answer to the slot-2 write. It is a shared-boot member, so the T14 runs it, the cheaper tier than a QEMU guest test.abuse_elf_loader's spawn cases on tables the kernel no longer reads (rela_below_image,shnum_past_heap,strsz_past_heap,rela_index_past_heap,too_many_needed_libs,reloc_straddles_fill_page,relative_addend_past_image,tpoff_addend_past_tls,export_past_image,tls_apply_spawn,tpoff_overflow_spawn,globdat_past_dynsym) assert that the kernel answers, lives, and the child it starts dies; the range-levelload_*/tls_*cases stay refusals. What they hold is a running kernel surviving a crafted spawn, which no type or host test reaches.High-risk checks
The loader, the ABI's entry contract and the fault path.
toyos/tests/relocate/fixture.rs(a table of&dynvtables, a table of&str, a table offn) linked by the sysroot'srust-lld-pie, and again--image-base=0x10000000 --apply-dynamic-relocsso lld writes every value at the same file offsets. The core run on the first at bias0x10000000writes, at each of the nineRELATIVEslots, lld's own word, on both architectures, and each word differs from the unrelocated file's. The-appliedfiles are linked at0x10000000, so they also hold the bias's subtraction: with the header found at its own link address the core's bias is 0 and every write is lld's word; found0x20000000higher, every write is lld's word plus that. The--cfg tlslink puts aRELATIVEinside.tdata(readelf -lr x86_64-tls.elf:TLS 0x000478 0x2478 … 0x10,R_X86_64_RELATIVEat0x2478; AArch64 at0x204e0) and is refusedInTlsTemplate.hosted_clang_hello(round 1, on a scratch merge of 8412441 withorigin/wt/toyos-selfhost-m2a6b2edd9, evidence only and never pushed): ToyOS's own clang and ld.lld, 210,187 and 81,609 of this relocator's writes, against the host's clang and LLD of the same LLVM commit.#include <stdio.h>reopenshello.c(#include nested too deeply): libc'sstatanswers onest_inofor every file (issues/libc-stat-answers-one-serial-number-for-every-file.md). Past the loader, and not this branch's.hello.cdeclaringprintfinstead of including<stdio.h>: exit 0.clang -c ended 0,ld.lld ended 0, the program prints its line,hello.ois the host's 1384 bytes andhellothe host's 1730632 bytes.hosted_clang_helloexit 1,clang -c did not start: out of memory, hosted_clang_hello: ToyOS's own clang and ld.lld compile and link a C program in a guest, byte-identical to the host's, on demand #830's spawn refusal._startskips the callcargo test --test toyos-build -- libc_sockets virt_user_mode0x378on x86-64 and AArch64_startskips the callcargo test --test toyos-build -- libc_socketsaddr_order ended Some(-1)addendwithoutbiascargo test -p toyos relocatecargo test -p toyos relocateJUMP_SLOTas a no-opcargo test -p toyos relocatebiaswithout the header's link addresscargo test -p toyos relocatethe_bias_is_where_the_header_is_found_less_its_link_addressredDT_JMPRELwithout a size falls throughcargo test -p toyos relocateevery_dynamic_form_it_does_not_apply_is_refused_by_nameredcargo test -p toyos relocatelet _ = unsafe { apply_to_self(header) };boot:testcases:abuse_elf_loadermust redtest_rs_abuse_elf_loaderred, exit 101file_offset_ofcargo test -p toyos-elf --test crafted a_tls_template_starting_in_bss_has_no_file_offsetSome(6144)whereNone(the offset of the other segment's bytes); restored in the same script (patch, log)The unmutated
cargo test -p toyos relocateat fd12ed1: exit 0, 13 passed.Metal
T14 readings at fd12ed1 (the orchestrator's): every
testcasesmember passed in the after arm,abuse_elf_loader's case 22 among them, and m9 reddedtest_rs_abuse_elf_loader(exit 101) in the mutation arm.spawn_costfailed in all three arms there, for round 2's defect above.T14 readings at aabf349 (the orchestrator's), each image's sha256 checked against its request (request-before, request-after) in the command that flashed it, every boot rc=0:
spawn_cost,/system/bin/toybox, 50 spawnsbeforespawn_cost.rs(byte for byte that of aabf349) and its threetests/toyos.rshunks, never pushedtestcases(8 jobs)50759a89…spawn_cost: exit 0, 1 of 1aftertestcases(235 jobs)c14a5238…,testcases-watchdog59a3dd55…spawn_cost boot:testcases: exit 0, 249 passed, 0 failedBoth arms sit on origin/main e3e21e4 and run the same
spawn_cost, so they differ by this branch alone: self-relocation adds 3 µs to the minimum and 7 µs to the median spawn oftoybox, 1518RELATIVEentries, under 1%. Round 4's change replaces one lookup on spawn's path, thePT_TLStemplate's file offset, and was not run on the T14.Unsure
applyscans the program headers twice per entry. Measured on the host (--release, the x86-64 fixture's 7 headers, 210,187 synthetic entries, best of 20): 873 µs as built, 73 µs with both scans deleted. So at clang's count the scans are ~0.8 ms of host time.toyboxcarries 1518RELAentries and 11 program headers, a few µs at that rate, which is the size of the A/B's difference. If that cost matters for clang's start, the scan is the fix.write_nonblockon an unheld handle reachesHandleError::refuse, thenprocess::handle_fault, thenexit(HANDLE_FAULT_EXIT_CODE). The T14 measured it at fd12ed1: case 22 passed in the after arm.relocate_selfreads the program headers at__ehdr_start + e_phoffbefore it can check that the header's segment holds them. The kernel refuses an image whose table noPT_LOADmaps, and lld writes it right behind the header; an image that put it in another segment would fault on that read in its own address space, or be refused.cargo run -- --ci hostruns beside it: round 1 and again round 4, recorded inissues/a-suite-that-builds-its-sysroot-can-wedge-on-its-own-locks.md; filed, not fixed. Round 4's suite run alone afterwards went 55 of 55.Logs
Each posted whole on this pull request; an excerpt above only points into one.
Round 4, at 5483c3a:
Round 3, head aabf349 unless the log says otherwise:
Round 2, head fd12ed1 unless the log says otherwise:
Round 1, at the heads its rows name:
🤖 Generated with Claude Code
https://claude.ai/code/session_017cSFvbD35xJ2kGANVdm23C