Repository navigation
The loader is the program's, and the kernel parses no ELF - #1189
Conversation
Stage 2 of Move 1 of issues/the-kernel-still-parses-what-userland-writes.md. A program lays out its own thread-local storage (toyos::tls): one process-wide module list, the executable first, which every thread's DTV names so every copy of the toyos crate in a process finds it; each thread's area (DTV, the executable's block, the TCB) below its stack, released with it; a dlopen'd module's block made on first use by __tls_get_addr's slow path, in the process's own memory; a DTV that grows. The kernel's only part is SYS_SET_THREAD_POINTER (171), which points the caller's FS base or TPIDR_EL0 at a user-half address and answers its tid; a spawned thread starts with no thread pointer and names its own first. FSGSBASE stays forbidden, so x86-64 needs the call. libc's loader (sdk/libc/src/dl.rs over dlimage.rs) reads a library wherever it is served, lays it out in private memory, relocates it with toyos-elf against every published module (the executable first), gives each segment its protection and PT_GNU_RELRO read-only, publishes it and runs its constructors. dlopen, dlsym (RTLD_DEFAULT included), dlerror, dladdr and dl_iterate_phdr read the list; std's unwinder and the fork's backtrace crate read it too. An executable naming DT_NEEDED is no longer refused by toyos::relocate: it applies its RELATIVE entries, and its start loads the libraries and binds the rest before main (__toyos_load_needed). A C program always links the loader; std reaches it through a weak symbol, since libtoyos_c is built from std, and a Rust program naming a library links libtoyos_c itself or is refused at start by name, as std_tls (restored) and the track record. Retired: SYS_DLOPEN, SYS_DLSYM, SYS_DLCLOSE, SYS_TLS_ALLOC_BLOCK and SYS_QUERY_MODULES, with DlopenArgs, ModuleInfo, MAX_LIBRARIES and the ABI's TCB items (current_tid moves to toyos::tls). The kernel loses kernel/src/elf/, loader/tls.rs, ElfInfo, a thread's TLS mapping, the dynamic TLS blocks, KernelSlice, Rollback and OwnedAlloc's views: kernel/src goes from 223 files and 56,800 lines to 218 and 55,286. toyos-elf loses the static-TLS arithmetic only the kernel used. A program may protect its own memory read and execute (vm_prot), which dlopen already effectively allowed through any bytes it was handed; no protection allows a write and an execution at once, held by kernel/pure/vm.rs's no_protection_allows_a_write_and_an_execution, and an object's mapping still never becomes executable. libc's mmap and mprotect follow: read+exec answered, write+exec ENOTSUP. Closes the DTV-capacity defect (the DTV grows in the process's memory), the twice-zeroed TLS block, the contiguous dlopen run, dlopen's write_at contract, std's query-modules miscount, RTLD_DEFAULT's panic (207 holds it) and KernelSlice's lifetime. The crafted-ELF guest cases keep what the kernel still parses, the program headers; a library's are the program's loader's, held by toyos-libc-copies' dlimage tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g unbundled toyos::tls's start publishes the executable through the list's one writer rather than forgetting a Loading guard; libc's loader marks a library's memory published rather than forgetting its reservation. sourcegate's Send/Sync registry loses kernel/src/mm/region.rs, which is gone. The layout tests read alignment with is_multiple_of. libloading's fork names libtoyos_c unbundled (fbfc60c): a library build does not search the sysroot the final link does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…left Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The T14 testcases boot at f66dd9a red abuse_elf_loader's tls_template_2m and abuse_elf_segments's tls_huge: each asserted the kernel refuses a PT_TLS its TLS block allocation could not hold, and the kernel lays out no TLS now, so it spawns them and the child dies. tls_crowds_dtv went with them, a survival case over the kernel's DTV. The PT_TLS rules the program-header parse keeps (p_align, filesz > memsz, vaddr) stay asserted at the spawn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #1189 at
|
main allocates tids from the process's own pool; the thread's id no longer goes into its TCB, which the program now owns (toyos::tls::current_tid asks SYS_SET_THREAD_POINTER). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The loader moves out of libc into `toyos::dl`, beside the module list it publishes into (`toyos::tls`) and the self-relocation that precedes it. std's and libc's starts both call `toyos::dl::start`, so a Rust program that names a library no longer has to link libtoyos_c: the weak symbol, std's "links no loader" refusal and `std_tls`'s link workaround go. libc keeps `dlopen`, `dlsym`, `dlclose` and `dlerror` as wrappers over it, with errno and a per-thread `dlerror` text that names the reason. The libloading fork calls `toyos::dl` again; `toyos` depends on `toyos-elf`, which joins std's patched crates and the published crates ahead of `toyos`, and the rust fork's library lock carries it. A load is one set: the libraries named and every library they name not yet published, found breadth first, so a cycle of `DT_NEEDED` names loads once instead of recursing off the stack. Each is relocated against the published modules and the set, all are protected, and only then is the set published, so a refused load leaves nothing half-loaded; constructors run afterwards, each library's after those of the libraries it names. Nothing is skipped silently any more. Every relocation type but NONE that the loader does not apply (COPY, IRELATIVE, any unknown number) is refused by number; R_X86_64_64 and R_AARCH64_ABS64 are applied as S + A. A symbol nothing defines is refused by name, but a weak one, which is 0, and a call slot in an image not bound now, which is bound to a trap that fails at the call as a lazy loader would (a Rust library's `main`, which its std's `_start` names). A DT_NEEDED naming nothing, and a library segment both writable and executable, are refused by name. The executable relocates itself before its main thread's thread-local storage is copied from its template, so `toyos::relocate` no longer refuses a write into the template; the start's executable pass, which runs after that copy, writes what lands in the template into the main thread's block too. `RelaCounts`, whose only user was the kernel loader, goes with its comment about shared read-only pages. `abuse_kernel_addr` holds SYS_SET_THREAD_POINTER's refusal of a non-canonical, a kernel and the last address. The crafted-ELF cases' comments lose their history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fork's ToyOS backend (1eb1665) calls toyos::dl again instead of linking libtoyos_c, and toyos brings toyos-elf. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owner's decision: a parent lays out its child's program, the build lays out the kernel and the supervisor, and the ELF decoder is a module of `toyos`, so `toyos-elf` is no crate and is never published. Spawn: `SpawnArgs` carries the child's memory as ranges (`SpawnMap`), the entry, the image base and the build-id, in place of an image the kernel parsed. Each range is checked as an ordinary mapping is, all or nothing: whole pages of the user half, a `VmProt`, and a copy only of ordinary memory the object, held with `MAP`, holds from a page boundary; a range meeting another or the clock page, and an entry in no executable range, are refused. A copied range is the existing private kind whose pages are copied out of the object at their first touch, so pages are shared as before. `toyos::elf::program::lay_out` is the layout, which `Helper::spawn` and the build both use; the kernel's `file_backing` goes. Boot: the signed image's kernel section is `toyos_kimage`: the kernel at its own addresses, its RELATIVE relocations, its ranges and RELRO, a symbol table that resolves as the ELF table does, and the supervisor's program. The build writes it (`build::kimage`); the loader copies the image and applies the relocations; the kernel takes its page protections, its symbols and the supervisor's ranges from it. `toyos::elf` holds `lib/elf`'s source and tests. `lib/symbols` keeps what reads no ELF (frame records, the name budget); finding an image's symbol tables and build-id is `toyos::elf`'s, and `symbolize` names a recorded frame with it. The build's gates read the kernel with it too. `toyos-elf` leaves `PUBLISHED`, std's patched crates and the rust fork's lock (a189b3a6b33). The crafted-ELF guest tests go with the kernel's parse: their headers are the parent's layout, which the host tests hold. `abuse_spawn_maps` holds the kernel's new boundary, `start_refuses_itself` a program's refusal of itself, and `spawn_image_object` the object's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main reworked sys_query_modules, which this branch deletes, and moved the capability record into the manual, which drops this branch's edit to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lints The merge left vm.rs's conflict markers in, where main reworked code this branch deletes. toyos-kimage's writer is part of the crate rather than a feature the model-control gate would read as a control, and the kernel's symbol table, now a borrow of the section, needs no hand-written Send or Sync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
build_test_parts returned the memoized kernel ELF on a full memo hit, so the second boot of one config in a process signed an ELF into the slot's kernel section, which the loader refuses (not TOYOSKRN). Guest rows run 42080820355 red virt_random_differs's second boot so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Guest rows run 42089538985 stalled session_lifecycle so: the supervisor answered the compositor's ask with the desktop session whose owner had just exited, before it read the exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r3 of #1189 at
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The slot's kernel file is `toyos/kernel.img`: it holds the kernel section (`toyos_kimage`), not an ELF. The build and the loader name it through `slots::KERNEL_FILE` alone; the comments that called the handed bytes an ELF say what they are. - `toyos::elf::program::lay_out` refuses a segment both writable and executable by name, as the loader refuses a library's: one policy, and either protection would only have made the program fault later. - `toyos_kimage` owns the `e_machine` numbers the build, the loader and the kernel share, and records the supervisor section's length, which the kernel checks against the section the loader handed it. - `abuse_spawn_maps` refuses a copy from no page boundary of its object, and one whose end wraps, which unchecked would read past the object in the kernel at the first touch. - The spawn record drops `layout=`, which timed nothing any more. - `toyos-symbols` drops its dead `object` dev-dependency, and the comments that still named `toyos-elf`, `lib/elf`, `crate::name`, raw-pointer symbol tables or `spawn_maps` say what is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r4 of #1189 at
|
- `abuse_spawn_maps`' wrapping copy would panic the kernel at its first touch, where `SharedMemObject` bounds every read: the comment says so. - The loader track's stage 5 names `load_kernel` and the kernel section's parse, not the `load_kernel_elf` this change replaced. - `build::kimage`'s refusals say "the kernel's ELF", which names no file. - `toyos_kimage::EM_*`'s comment parses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Orchestrator verification of the r4 fix (6115248..842f595), which changes no behaviour:
Landing. |
Problem
The kernel laid out every thread's TLS block and DTV, loaded and relocated
dlopened libraries, answered the module list, and parsed every program's ELF at spawn and its own at boot. All of that is input parsed in Ring 0, most of it written by userland. This is Move 1 ofissues/the-kernel-still-parses-what-userland-writes.md. The owner's decision of 2026-11-05 extends it: nothing in the kernel parses an ELF, andtoyos-elfis not published.What changes
The kernel parses no ELF
SpawnArgscarries the child's memory asSpawnMaps: an address, a length, an object, an offset, the bytes copied and a protection. It also carries the entry, the image base and the build-id that a crash record names.VmProt, and a copy only of ordinary memory the object holds (MAP) from a page boundary.VmProtis writable and executable, so W^X holds by type.toyos::elf::program::lay_outdoes it.Helper::spawnuses it, and so does the build for the supervisor. It refuses a segment both writable and executable by name, as the library loader does.toyos_kimage, carried in the slot astoyos/kernel.img. It holds the kernel at its own addresses, itsRELATIVEs, its ranges and RELRO, a symbol table, and the supervisor's program with the length of the section it was laid out of. The build writes it (build::kimage). The loader copies the image and applies the relocations. The kernel takes its page protections, its symbols and the supervisor's ranges from it, and checks the supervisor section's length against the section the loader handed it.kernel/src/file_backing.rsare gone. The loader and the kernel link no ELF code.One ELF parser, in
toyostoyos::elfholdslib/elf's source and tests. The SDK's loader, the build's gates,build::kimageand/system/bin/symbolizeread with it.lib/symbolskeeps what reads no ELF: the frame records and the name budget. The kernel keeps using it.toyos-elfis no crate. It leavesPUBLISHED, std's patched crates and the rust fork's lock (a189b3a6b33), so nothing new needs publishing.TLS is the program's (
toyos::tls)SYS_SET_THREAD_POINTER. It refuses a non-canonical address, a kernel address andu64::MAX(abuse_kernel_addr).The loader is the SDK's (
toyos::dl)toyos::dl::start. libc'sdlopen,dlsym,dlcloseanddlerrorare thin wrappers, and libloading callstoyos::dl.DT_NEEDEDname.ABS64is applied asS + A.BIND_NOW, it is refused.Retired
SYS_DLOPEN,SYS_DLSYM,SYS_DLCLOSE,SYS_TLS_ALLOC_BLOCKandSYS_QUERY_MODULES.SpawnArgs::image.kernel/src/elf/,loader/tls.rs,file_backing.rs,KernelSliceandRollback.lib/elfandRelaCounts.Forks, all pushed to their branch heads:
maina189b3a6b33;742eb4b;toyos-sdk-0.121eb1665;61cf0af.Size
kernel/srcgoes from 224 files and 58,050 lines at the merge baseec8912181to 218 files and 56,392 lines (−6 files, −1,658 lines).Tests
toyos::elf: the moved corpus and fuzz, andelf_program, which compareslay_outwithobject's reading of lld's images and checks its refusals.toyos::dl: relocations, refusals, sets, cycles and constructor order.toyos-kimage: the format's round trip, its fences, symbol resolution, and stack placement.build::kimage: the table names what the ELF table names.symbolize's namer,toyos::relocate,kernel/pure's W^X and libc's copies.abuse_spawn_maps(new) holds the kernel's spawn boundary: every refusal above, including a copy from no page boundary and one whose end wraps,PermissionDeniedwithoutMAP, and an honest spawn after.start_refuses_itself(new) holds a real program refusing itself at start, for aRELRtable and an emptyDT_NEEDEDname.spawn_image_objectkeeps the object's rules.Evidence
All of it is published at https://github.com/ToyOSOrg/evidence/tree/faa22272c8eaa7e0900a2dc1cbc87dfce8798f71/pr-1189, with round 3 under
round3/.61152480a:--build-onlyexited 0.PT_LOAD. The command, its per-file output and a control that it detects one are inround4/wx-scan*.testcasesboot at this revision, image hash as staged, passed 16 of 16 (round4/metal-61152480a).abuse_spawn_mapsrefused both new casesInvalidArgument, and C cases 204, 206 and 207 passed.cargo run -- --ci hostatbb1e9ada6exited 0, all 108 steps green.--build-onlyatb36b16e12exited 0.b36b16e12, run 42080820355: 18 of 19.virt_random_differswas red because the loader refused a slot whose kernel was not a section ("not TOYOSKRN"). A second boot in one process tookbuild_test_parts' memo path, which returned the kernel's ELF. That was a defect of this change, fixed inbb1e9ada6.bb1e9ada6, run 42089538985: 18 of 19. Every aarch64 row is green,virt_random_differsamong them, and every x86-64 row but one.session_lifecyclestalled at the desktop reopening.issues/a-desktop-reopened-as-its-session-ends-is-handed-the-ended-session.md.libc_childrenandhosted_clang_helloamong them) and the nine aarch64virt_*rows.bb1e9ada6, both images' hashes as staged, passed 17 of 17 (evidence).testcases, 16 members: the loader tests,start_refuses_itself,abuse_spawn_maps(each refusal answered as named),spawn_image_object,mmap_stress,disk_backtrace, and C cases 204, 206 and 207.shared-debug:abuse_kernel_addr.125b8929fpassed 16 of 16.lay()ignores its vacancy check, on a never-pushed commitae8139783overbfb75f056, bootingabuse_spawn_mapsalone. The image hash was as staged. The member went red at "a range on the clock page": the spawn was not refused, the child ran (pid 16, exit 0), and the member exited 101. Every refusal before it was still answered (evidence).Risks
__tls_get_addrDTV layout changed (a three-word header)._start. Itsmainslot binds to the lazy trap.dlopencan return a library whose constructors are still running. This predates the PR and is recorded in an issue.toyos::elf::program::BASE, as before. There is no randomization.toyos/kernel.img) and holds a new format. An image older than this one cannot update in place to it: its updater writes the slot under the old name, and the loader on the ESP stays the old one, which parses an ELF. Such a machine is flashed. ToyOS keeps no compatibility, and the T14 is flashed for every run.🤖 Generated with Claude Code