Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
818cefd
File: a relocation in an executable's TLS template reaches no thread'…
Japabu Oct 10, 2026
4c11657
Executables relocate themselves; the kernel reads no executable's dyn…
Japabu Oct 10, 2026
1d0455d
spawn_cost: how long a spawn of shell takes to its exit, a metal row …
Japabu Oct 10, 2026
8412441
The relocator's six rust-lld fixtures are declared, ours
Japabu Oct 10, 2026
0829cc0
toyos-elf loses what only the executable loader called; the relocator…
Japabu Oct 10, 2026
5b9bf90
File: a suite that builds its own sysroot can wedge on its own locks
Japabu Oct 10, 2026
2b4d492
Merge remote-tracking branch 'origin/main' into wt/toyos-selfreloc
Japabu Oct 10, 2026
82f74a8
spawn_cost runs on the testcases boot its row reads
Japabu Oct 10, 2026
b602df0
The relocator reads its bias off the header's link address, and refus…
Japabu Oct 10, 2026
e9f61b6
abuse_elf_loader: a std image whose own _start refuses its relocation…
Japabu Oct 10, 2026
2a65bef
The static TLS block is the executable's alone: place_module and Stat…
Japabu Oct 10, 2026
fd12ed1
Issues stop citing what executables relocating themselves deleted
Japabu Oct 10, 2026
1daf910
Merge remote-tracking branch 'origin/main' into wt/toyos-selfreloc
Japabu Oct 10, 2026
aabf349
spawn_cost spawns toybox, which the testcases ROOT ships
Japabu Oct 10, 2026
b7b7aeb
Merge remote-tracking branch 'origin/main' into wt/toyos-selfreloc
Japabu Oct 10, 2026
5483c3a
A file offset is only a segment's file bytes: the PT_TLS template's w…
Japabu Oct 10, 2026
b874b2a
The sysroot wedge recurred on round 4's guest suite; its issue record…
Japabu Oct 10, 2026
edcc8a2
Two guest-test comments stop citing vaddr_to_file_offset and its extr…
Japabu Oct 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion bootloader/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,6 @@ fn load_kernel_elf(kernel_elf_bytes: &[u8]) -> LoadedKernel {
let rules = rela::Rules {
extent,
window: (0, mem_size as u64),
fill: None,
tls: None,
};
let mut reloc_count = 0u64;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,10 @@ opened: 2026-09-29

`resolve_dlopen_relocs` (`kernel/src/elf/reloc.rs`) resolves a `dlopen`ed
module's `GLOB_DAT`/`JUMP_SLOT` slots against the other loaded libraries only.
Its startup sibling, `resolve_lib_bind_relocs`, tries the executable's
dynamic exports first (`kernel/src/loader/mod.rs` builds `exe_sym_map` for
it). glibc's `dlopen` searches the global scope, which starts with the
glibc's `dlopen` searches the global scope, which starts with the
executable, so on ToyOS a plugin that calls back into a symbol its host
exports is left with an unresolved slot and faults when it uses it.

**Exit**: `resolve_dlopen_relocs` binds a slot against the executable's dynamic
exports before the other loaded libraries, as `resolve_lib_bind_relocs` does,
and a guest case `dlopen`s a library that calls a function its executable
exports before the other loaded libraries, and a guest case `dlopen`s a library that calls a function its executable
exports, and the call lands.
11 changes: 6 additions & 5 deletions issues/a-package-cannot-ship-its-own-libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ opened: 2026-09-27
# A package cannot ship its own libraries

A program on a file server is spawned from a memory image its parent read
(`SpawnArgs::image`), and `kernel/src/loader/mod.rs`'s `load_needed_libs` then
finds its `DT_NEEDED` libraries in `/system/lib` alone: the kernel opens no path
a file server holds. A package under `/apps/<name>/` whose binary needs a
library it carries beside it fails to spawn, where before the file servers
moved out of the kernel the executable's own directory was searched first.
(`SpawnArgs::image`), and nothing loads a `DT_NEEDED` library for it: the
kernel loads none at spawn, and an executable that names one is refused at its
own start (`toyos::relocate`) until the userland loader of
`issues/the-kernel-still-parses-what-userland-writes.md` loads them. A package
under `/apps/<name>/` whose binary needs a library it carries beside it fails
to start.

## Exit condition

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
status: open
kind: tooling
opened: 2026-10-10
---

# A suite that builds its own sysroot can wedge on its own locks

`src/buildlock.rs` orders a sysroot key's lock before the worktree build lock,
and takes the key's lock with the worktree lock put down. One run of the guest
suite (`cargo test --test toyos-build`, 12 wide) on a head whose sysroot no
earlier build had made held both the other way round, inside one harness
process, and stopped:

- at 11:11:53 every worker waited on `building sysroot 697f7bee2b30245e`, and
one built it;
- at 11:14:26 the waiters acquired it in turn, one began `clean crate targets
against changed external deps` and waited for the build lock exclusive,
`held by other builds in this tree`, while workers that held it shared for a
test binary waited on `using sysroot 697f7bee2b30245e — held, but the holder
left no readable note`;
- 450 s later six tests (`https_fetch`, `iommu_virtio_platform`,
`machine_shutdown`, `nested_nmi_is_loud`, `netstack_streams`,
`netstack_streams_e1000e`) had not started, `lsof` showed the key's lock file
open in the harness alone, and the run was stopped. The same head, its
sysroot made, ran 50 of 50 green in 49 s.

`cargo run -- --ci host` ran beside it in the same worktree; whether it took
part is not established.

It recurred on #836's round 4, the same shape on the host the other agents'
suites loaded: the harness built `fa7a35ac0091a05b`'s std by 14:35:56, ran its
other tests, and from 14:43:03 one worker alone waited on `using sysroot
fa7a35ac0091a05b — the holder left no readable note`, 930 s by 14:48:17, with
`lsof` showing the key's lock file open twice in the harness and nowhere else.
`--ci host` again ran beside it and had finished at 14:45:47.

**Exit condition**: no path holds the worktree build lock while it waits on a
key's lock, and a suite started on a head whose sysroot is not yet made
finishes.
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ ran.

**The VFS lock is not in the window.** Run 20 also shows `vfs::lock()` held
across a 32 s stick write with other CPUs at 200M spins, and a spawn does take
that lock. But it takes it at `loader/mod.rs:370` and in `load_needed_libs`, both
that lock. But it took it at `loader/mod.rs:370` and in `load_needed_libs` (deleted
since: an executable relocates itself, and nothing loads a `DT_NEEDED`), both
*before* the `ELF: … relocations indexed` and `spawn: TLS … modules` records
that every hung boot wrote; everything after them reads the already-open backing
and takes no VFS lock. So it is not this.
Expand Down
22 changes: 22 additions & 0 deletions issues/an-executables-relro-stays-writable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
status: open
kind: defect
opened: 2026-10-10
---

# An executable's RELRO stays writable for its whole life

`PT_GNU_RELRO` names what a program's relocations write once and nothing
writes after: `.data.rel.ro`, `.dynamic`, `.got`, every vtable and every table
of function pointers. The kernel maps it as the writable `PT_LOAD` that holds
it, reads no `PT_GNU_RELRO` (`rg -i relro kernel/src toyos-elf/src` matches
nothing), and gives a program no way to take the write away once
`toyos::relocate` has run. A stray or hostile write in a running program can
rewrite a vtable.

Owner: the userland loader of
`issues/the-kernel-still-parses-what-userland-writes.md`.

Exit: once a program has relocated itself its `PT_GNU_RELRO` range is
read-only, through a protection primitive the program calls or a mapping
granular enough to hold it apart, and a guest test's write to it is refused.
2 changes: 1 addition & 1 deletion issues/clippy-stage-two-is-lints-one-at-a-time.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ before adopting:
| `arch/` | 107 | **adopted 2026-08-22 — the last area, and the highest-risk one.** 107 findings (112 `unsafe` blocks in all); **30 removed, 77 documented, 3 filed.** Per file below. |
| root files (`user_ptr.rs`, `process.rs`, `preempt.rs`, `inbox.rs`, `main.rs`, `symbols.rs`, `hw.rs`, `file_backing.rs`, `sync.rs`, `scheduler.rs`, `pipe.rs`, `page_cache.rs`, `bcachefs_adapter.rs`) | 76 | **adopted** — 13 removed, 63 documented, 4 filed. Per file and per finding below. |
| `mm/` | 35 | **adopted** — every site now carries a `SAFETY:` comment; two (`object::shm::Pages`, `mm::paging::AddressSpace`'s `Send`/`Sync` impls) turned out to be vestigial and were deleted, and `src/sourcegate.rs`'s `AUTO_TRAIT_IMPLS` now names every hand-written one the kernel keeps |
| `elf/` | 23 | **adopted** — every site now carries a `SAFETY:` comment; writing the justification found two functions (`elf::read_backing_into`, `elf::index::RelocationIndex::apply_to_page`) that write through a raw pointer without being `unsafe fn`, filed rather than fixed. Both take a `mm::KernelSlice` now (2026-08-22), so the extent is the allocation's and the file is closed. |
| `elf/` | 23 | **adopted** — every site now carries a `SAFETY:` comment; writing the justification found two functions (`elf::read_backing_into`, `elf::index::RelocationIndex::apply_to_page`) that write through a raw pointer without being `unsafe fn`, filed rather than fixed. Both took a `mm::KernelSlice` (2026-08-22), so the extent is the allocation's and the file is closed; the second is deleted since, with the fault-time relocation index. |
| `sched/` | 8 | **adopted 2026-08-22**, under the reduction ruling. 8 findings (26 `unsafe` blocks in all); **4 removed, 5 documented, 1 filed** — the five sites of the same shape outside this area, since taken by the root-file and `arch/` sweeps, so that file is closed too. |
| `loader/` | 8 | **adopted** — every site now carries a `SAFETY:` comment (one finding shared with `elf/`, above) |
| `iommu/` | 8 | **adopted 2026-08-22**, under the reduction ruling. 8 findings (8 `unsafe` blocks in all); **5 removed, 3 documented, 0 filed** — the area's whole remainder is two window constructions and a `clflush`. |
Expand Down
18 changes: 0 additions & 18 deletions issues/comments-name-defects-no-issue-holds.md

This file was deleted.

10 changes: 6 additions & 4 deletions issues/echo-faulted-after-the-fault-arms.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ explained either way: it is a userland fault the kernel handled correctly.

**What to read if it happens again.** `err=0x4` is a user-mode read of a
not-present page, so it is `/system/bin/echo`'s own dereference of 1 and not the
loader's. Two candidates and one run cannot separate them: a `Command::output()`
path whose child inherits something the fault arms disturbed, and demand paging
handing the binary a page it had not finished relocating (`relocs=2600` is on the
trace). Capture the guest with QMP rather than letting the harness kill it —
loader's. One run named two candidates: a `Command::output()` path whose child
inherits something the fault arms disturbed, and demand paging handing the binary
a page it had not finished relocating (`relocs=2600` is on the trace). The second
is gone from the tree: an executable applies every relocation in its own `_start`
before it reads a word (`toyos::relocate`), the fault path relocates nothing, and
the trace carries no `relocs=`. Capture the guest with QMP rather than letting the harness kill it —
`BootOptions { qmp: true }` leaves the socket, and `info registers -a` says
whether any other CPU is still running.

Expand Down
15 changes: 15 additions & 0 deletions issues/the-kernel-still-parses-what-userland-writes.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ binary can only corrupt itself. Five syscalls retire, and
the crafted-ELF corpus becomes the negative gate against a kernel that no longer
parses most of it.

**Stage 1 is built: an executable relocates itself** (`toyos::relocate`,
called first by std's and libc's `_start`), and the kernel reads no
executable's dynamic section, symbol tables or relocations, and loads no
library at spawn. Left in Ring 0: `dlopen` and its four sibling syscalls, the
TLS block, and the program-header parse. **The interval it opens, owed by the
stage that brings the userland loader:** an executable with a `DT_NEEDED`
does not start (its own `_start` refuses it: exit 127 and a line on slot 2,
or, holding no slot 2, the kernel's handle-fault exit), so `std_tls`, the
guest test of a startup library's TLS beside the executable's, is deleted at
the commit that lands stage 1 and comes back with that stage. Nothing shipped
links a library at load time. The kernel's static TLS block holds the
executable's module alone, at offset 0, so `TlsModule::base_offset` and
`Static::tpoff`'s offset are 0 at every caller; that stage places modules in
userland and deletes them with the kernel's TLS build.

The evidence that it is worth doing: nine dated commits over eleven days fixed
userland-reachable kernel defects in this code, six of them machine-wide panics;
seven of the loader's twelve bounds are over quantities a workload sets and two
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,20 @@ kind: defect
opened: 2026-09-26
---

# The program loader runs a binary whose `DT_RELR` table it never reads
# `dlopen` loads a library whose `DT_RELR` table it never reads

`toyos_elf::dynamic::Dynamic::parse` decodes the tags the loader acts on and
drops every other one (`_ => {}`), so a program or library linked with
`toyos_elf::dynamic::Dynamic::parse` decodes the tags the kernel's `dlopen`
acts on and drops every other one (`_ => {}`), so a library linked with
`-z pack-relative-relocs` loads with its relative relocations — the ones
`DT_RELR`/`DT_RELRSZ` name, and that `DT_RELA` then no longer holds — never
applied. `DT_REL` and `DT_TEXTREL` are dropped the same way. The process runs
with every pointer in its data still the link-time one and dies somewhere
after, or does not die.
with every pointer in the library's data still the link-time one and dies
somewhere after, or does not die.

Nothing in the tree links that way today: rustc passes `rust-lld` no
`--pack-dyn-relocs`, and LLD's default is none. The bootloader refuses the
kernel's equivalent (`SHT_RELR`) by name; the program loader has no such
refusal, and a binary is input from outside the kernel.
`--pack-dyn-relocs`, and LLD's default is none. An executable carrying any of
the three is refused at its own start (`toyos::relocate`); a library is input
from outside the kernel, and `dlopen` has no such refusal.

Exit: a dynamic section naming a relocation form the loader does not apply is
refused at spawn and at `dlopen`, and a crafted binary carrying `DT_RELR` is the
test.
Exit: a dynamic section naming a relocation form `dlopen` does not apply is
refused at `dlopen`, and a crafted library carrying `DT_RELR` is the test.
1 change: 0 additions & 1 deletion kernel/src/elf/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ impl Snapshot {
LoadedLib {
memory,
user_base,
phys_base: self.image.phys(),
image: self.image,
dynsym: self.dynsym,
dynstr: self.dynstr,
Expand Down
Loading
Loading