Repository navigation
x86-64: FSGSBASE is taken, and the switch carries each thread's FS and GS bases - #1264
Conversation
Review: #1264 at 98890f6 (
|
98890f6 to
e753534
Compare
… an AP takes its CPU declaration before its TSS Review round 1 of #1264, on #1262's round. The rebase onto #1262 also gave its new `#DB` entry `RDGSBASE` and `WRGSBASE`, as the NMI's has. - The switch reads the outgoing FS and GS bases only for a thread of a process; the idle context's were never read back, and the read sat on the wake path. - An AP applies its `control_regs` declaration before `load_gdt`, so from the `ltr` that gives the IST entries their stacks they also have `CR4.FSGSBASE` for their instructions; the record of the earlier span, before `ltr`, says only what is left in it. - `fsgs_bases_per_thread` starts its rounds once both threads are pinned, so each thread running across the other's is the test's premise and not a race. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-review: #1264 at e753534 (round 2, on #1262's 2fc799b)Scope: whether round 1's required findings are resolved, and whether the code that changed is correct. ArchitectureUnchanged from round 1 and still sound. The rebase gave The
The Ring 3 path never addresses GS. So a user-chosen kernel-half base never reaches memory from
Offsets are unchanged:
Round 1's findings
The suggestions taken are correct:
Required
Evidence and landing
SEND BACK |
… an AP takes its CPU declaration before its TSS Review round 1 of #1264, on #1262's round. The rebase onto #1262 also gave its new `#DB` entry `RDGSBASE` and `WRGSBASE`, as the NMI's has. - The switch reads the outgoing FS and GS bases only for a thread of a process; the idle context's were never read back, and the read sat on the wake path. - An AP applies its `control_regs` declaration before `load_gdt`, so from the `ltr` that gives the IST entries their stacks they also have `CR4.FSGSBASE` for their instructions; the record of the earlier span, before `ltr`, says only what is left in it. - `fsgs_bases_per_thread` starts its rounds once both threads are pinned, so each thread running across the other's is the test's premise and not a race. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
e753534 to
32a7b4d
Compare
…t, and the metal #1264 owes is recorded Review round 2 of #1264. The earlier rebase resolved `arch::entry`'s header and the gate macro's contract with this branch's text from before #1262's round 2, which said no `#DB` lands in the swapped spans and that every `iretq` to Ring 3 is `gate_iretq`'s. Both are #1262's again; this branch keeps only its `install_kernel_gs` in `WRGSBASE` form. The rebase onto #1262's round 3 gave the `#DB` entry's thread-ending path the `WRGSBASE` restore. The metal rows #1264 owes, with the benches down, are recorded in `issues/fsgsbase-is-taken.md`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Re-review: #1264 at 32a7b4d (round 3, on #1262's a530d5d)Scope: this review covers round 2's blocker and the ArchitectureThe architecture is unchanged and still sound. This branch's whole difference in the Round 2's blocker: resolved
The thread-ending path under FSGSBASE
Checked:
Checks I ran
Evidence
RequiredNone of this PR's own. It must rebase onto #1262 once #1262's record fix lands. That fix is records only, so the rebase owes no rerun. LAND, once the three queued runs read as required. Because this PR is stacked, it lands after #1262. |
… an AP takes its CPU declaration before its TSS Review round 1 of #1264, on #1262's round. The rebase onto #1262 also gave its new `#DB` entry `RDGSBASE` and `WRGSBASE`, as the NMI's has. - The switch reads the outgoing FS and GS bases only for a thread of a process; the idle context's were never read back, and the read sat on the wake path. - An AP applies its `control_regs` declaration before `load_gdt`, so from the `ltr` that gives the IST entries their stacks they also have `CR4.FSGSBASE` for their instructions; the record of the earlier span, before `ltr`, says only what is left in it. - `fsgs_bases_per_thread` starts its rounds once both threads are pinned, so each thread running across the other's is the test's premise and not a race. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
32a7b4d to
fde1fb4
Compare
…t, and the metal #1264 owes is recorded Review round 2 of #1264. The earlier rebase resolved `arch::entry`'s header and the gate macro's contract with this branch's text from before #1262's round 2, which said no `#DB` lands in the swapped spans and that every `iretq` to Ring 3 is `gate_iretq`'s. Both are #1262's again; this branch keeps only its `install_kernel_gs` in `WRGSBASE` form. The rebase onto restore. The metal rows #1264 owes, with the benches down, are recorded in `issues/fsgsbase-is-taken.md`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6f85150 to
6fd6739
Compare
… an AP takes its CPU declaration before its TSS Review round 1 of #1264, on #1262's round. The rebase onto #1262 also gave its new `#DB` entry `RDGSBASE` and `WRGSBASE`, as the NMI's has. - The switch reads the outgoing FS and GS bases only for a thread of a process; the idle context's were never read back, and the read sat on the wake path. - An AP applies its `control_regs` declaration before `load_gdt`, so from the `ltr` that gives the IST entries their stacks they also have `CR4.FSGSBASE` for their instructions; the record of the earlier span, before `ltr`, says only what is left in it. - `fsgs_bases_per_thread` starts its rounds once both threads are pinned, so each thread running across the other's is the test's premise and not a race. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fde1fb4 to
47f8d5b
Compare
…t, and the metal #1264 owes is recorded Review round 2 of #1264. The earlier rebase resolved `arch::entry`'s header and the gate macro's contract with this branch's text from before #1262's round 2, which said no `#DB` lands in the swapped spans and that every `iretq` to Ring 3 is `gate_iretq`'s. Both are #1262's again; this branch keeps only its `install_kernel_gs` in `WRGSBASE` form. The rebase onto restore. The metal rows #1264 owes, with the benches down, are recorded in `issues/fsgsbase-is-taken.md`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… an AP takes its CPU declaration before its TSS Review round 1 of #1264, on #1262's round. The rebase onto #1262 also gave its new `#DB` entry `RDGSBASE` and `WRGSBASE`, as the NMI's has. - The switch reads the outgoing FS and GS bases only for a thread of a process; the idle context's were never read back, and the read sat on the wake path. - An AP applies its `control_regs` declaration before `load_gdt`, so from the `ltr` that gives the IST entries their stacks they also have `CR4.FSGSBASE` for their instructions; the record of the earlier span, before `ltr`, says only what is left in it. - `fsgs_bases_per_thread` starts its rounds once both threads are pinned, so each thread running across the other's is the test's premise and not a race. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
47f8d5b to
01ec758
Compare
…t, and the metal #1264 owes is recorded Review round 2 of #1264. The earlier rebase resolved `arch::entry`'s header and the gate macro's contract with this branch's text from before #1262's round 2, which said no `#DB` lands in the swapped spans and that every `iretq` to Ring 3 is `gate_iretq`'s. Both are #1262's again; this branch keeps only its `install_kernel_gs` in `WRGSBASE` form. The rebase onto restore. The metal rows #1264 owes, with the benches down, are recorded in `issues/fsgsbase-is-taken.md`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review: #1264's rebase at 01ec758, on main ce202b2 (code identical to 9e28356)Scope: the conflict resolution against #1251, the new per-CPU GS record, the rebuilt mutation and the records-only commit. Settled design is not reopened. ArchitectureThe architecture is unchanged and still sound. The resolution takes #1251's switch exactly and adds GS beside FS:
Net change against main:
The per-CPU records, path by pathEach item below was checked against the code at 01ec758.
Required
Suggestions
Mutation 5313bcfIt is a meaningful control.
EvidenceChecks I ran at 01ec758, in a detached worktree with its own target directory, all exit 0:
The kernel library tests are the pure crate, which does not compile the x86-64 switch. The author's checks: Carry-over of 43081128403 (e753534) and 43094000903 (32a7b4d): holds.
New runs, at the time of review:
A non-canonical start owes neither run again: no row reaches it. Zeroing the register at CPU init changes boot instead, and the switch rows should then run on the fixed head. Metal is not a landing condition while the benches are down. What is owed is recorded in SEND BACK |
…d the readback test checks a thread's first base Review of #1264's rebase. `percpu::user_gs_base` started at `u64::MAX`, which is canonical, so a thread that had written that base and was first switched in on a fresh CPU kept whatever `IA32_KERNEL_GS_BASE` held there: nothing sets that register at boot since `clear_user_gs_base` went. The record now starts at `1 << 63`, which is not canonical; `WRGSBASE` and `WRMSR` refuse it, so no thread holds it, and a CPU's first switch into any thread writes that thread's base. `fsgs_bases_per_thread` reads each thread's GS base once after both are pinned and before either writes one, and asserts 0. A switch-out that failed to refresh the record could otherwise leave a thread that had not yet written its base on another thread's, and the rounds after would not see it. The GS-base defect's record drops a sentence about the fix being the owner's to choose, which its new lead had already answered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Orchestrator verification of the rebase review's fix at 0658715: the per-CPU GS record starts at the non-canonical |
…d GS bases Intel has FSGSBASE used only with a complete OS implementation: a switch and process-trace calls that manage the FS and GS bases apart from their selectors, and ring transitions insensitive to the GS base (Guidance for Enabling FSGSBASE, ID 767615). The previous commits made every transition insensitive to it, and ToyOS has no call that reads or writes another thread's registers, so what was left is the switch: - `CR4.FSGSBASE` joins `CR4_REQUIRED`: every CPU of the kernel's range (Intel since 2012, AMD since Zen) has it, so one without it is refused by name like one without SMEP, and `CR4_FORBIDDEN` goes. - The switch saves the outgoing thread's user GS base from `IA32_KERNEL_GS_BASE`, where the kernel keeps it while it runs, and writes the incoming one's (`arch::hw::UserBases`, which AArch64 defines empty: its one base register is the thread pointer). A thread's FS base moves as before, now by `RDFSBASE` and `WRFSBASE`. - NMI, `#MC` and `#DF` read and install GS's base with `RDGSBASE` and `WRGSBASE` instead of the MSR, clobbering only `rax`. No branch precedes the write, so no load through GS after it is speculated with another base. - The zero-base premise goes with them: a thread now writes any canonical GS base, a kernel-half one included, so `percpu::clear_user_gs_base` and the compile-time check of the user descriptors' bases are deleted. The switch writes every incoming thread's base before it can reach Ring 3. `fsgs_bases_per_thread` pins two threads to one CPU; each writes bases of its own with `WRFSBASE` and `WRGSBASE` (one GS base kernel-half), yields and reads them back. It is a shared-boot member on each bench; `a_threads_fs_and_gs_bases_stay_its_own` runs it under QEMU. Stage C of `issues/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.md` no longer brings back `gsbase_locked` or its `user-writable-gsbase` kernel: a user-written GS base is now the design, and the readback is its test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Found while moving the IST entries to `RDGSBASE`/`WRGSBASE`: an AP loads the IDT in its trampoline, and its TSS and `CR4` only in `init_ap`, so an IST vector in between has neither its stack nor, now, its instructions. Unchanged by this branch beyond the `#UD`, and recorded with its exit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… an AP takes its CPU declaration before its TSS Review round 1 of #1264, on #1262's round. The rebase onto #1262 also gave its new `#DB` entry `RDGSBASE` and `WRGSBASE`, as the NMI's has. - The switch reads the outgoing FS and GS bases only for a thread of a process; the idle context's were never read back, and the read sat on the wake path. - An AP applies its `control_regs` declaration before `load_gdt`, so from the `ltr` that gives the IST entries their stacks they also have `CR4.FSGSBASE` for their instructions; the record of the earlier span, before `ltr`, says only what is left in it. - `fsgs_bases_per_thread` starts its rounds once both threads are pinned, so each thread running across the other's is the test's premise and not a race. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t, and the metal #1264 owes is recorded Review round 2 of #1264. The earlier rebase resolved `arch::entry`'s header and the gate macro's contract with this branch's text from before #1262's round 2, which said no `#DB` lands in the swapped spans and that every `iretq` to Ring 3 is `gate_iretq`'s. Both are #1262's again; this branch keeps only its `install_kernel_gs` in `WRGSBASE` form. The rebase onto restore. The metal rows #1264 owes, with the benches down, are recorded in `issues/fsgsbase-is-taken.md`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… owes on metal #1251 brought the record to main, and #1262 then fixed the defect without touching it, so main's record still read as an open, unfixed hole. It now says what #1262 built, how the exit's guest test and its negative control came out under QEMU, and what the benches still owe before it closes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d the readback test checks a thread's first base Review of #1264's rebase. `percpu::user_gs_base` started at `u64::MAX`, which is canonical, so a thread that had written that base and was first switched in on a fresh CPU kept whatever `IA32_KERNEL_GS_BASE` held there: nothing sets that register at boot since `clear_user_gs_base` went. The record now starts at `1 << 63`, which is not canonical; `WRGSBASE` and `WRMSR` refuse it, so no thread holds it, and a CPU's first switch into any thread writes that thread's base. `fsgs_bases_per_thread` reads each thread's GS base once after both are pinned and before either writes one, and asserts 0. A switch-out that failed to refresh the record could otherwise leave a thread that had not yet written its base on another thread's, and the rounds after would not see it. The GS-base defect's record drops a sentence about the fix being the owner's to choose, which its new lead had already answered. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0658715 to
a72a3d8
Compare
|
Orchestrator verification at a72a3d8. The rebase onto 3c5c555 deleted the standalone row as I decided. #1271's shared rows judge The landing run 43147838891 passed both shared rows at this head ( The PR merges cleanly onto main ee15a2e; #1252 and #1258 touch neither the entry path nor the switch path. The metal debts are listed under "Owed on metal". Queueing. |
…he switch carries FS and GS (#1264) No conflicts. #1227 removed libc's `refuse_files`: every refusal now goes through `set_errno`, which reads `toyos::Failure`'s one table. A refused map of a file answers through it too. A file its server maps no pages of still answers `ENODEV`, as POSIX names that refusal of `mmap`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Based on main ce202b2, which carries #1262 (the swapgs entry), #1243 and #1251.
Why it needed #1243: once
WRGSBASEexists, a gate whoseswapgsis mispredicted as skipped speculates its GS loads through a kernel address the thread chose. On AMD the gates'LFENCEstops that only where it is dispatch-serializing (MSR C001_1029[1], AMD 70586 G-2), which #1243 sets.What changes
Intel allows FSGSBASE only with a complete OS implementation (Guidance for Enabling FSGSBASE, ID 767615):
#1262 made the transitions insensitive, and ToyOS has no call that reads or writes another thread's registers. What was left is the switch, and this PR takes it (
issues/fsgsbase-is-taken.md):CR4.FSGSBASEjoinsCR4_REQUIRED. Every CPU of the kernel's range has it, so a CPU without it is refused by name.CR4_FORBIDDENis deleted.IA32_KERNEL_GS_BASEand the FS base withRDFSBASE. It writes each of the incoming thread's only where it differs from the per-CPU record of what the register holds: A switch loads only what the incoming thread does not already have #1251's for FS, and a newpercpu::user_gs_basefor GS. Its start,1 << 63, is not canonical, so it equals no base a thread can hold, and a CPU's first switch into a thread always writes that thread's base. The per-thread value lives inarch::hw::UserBases, which AArch64 defines empty.#DB,#MCand#DFread and install GS's base withRDGSBASE/WRGSBASE, clobbering onlyrax.control_regsdeclaration beforeload_gdt. So from theltrthat gives the IST entries their stacks, they also haveCR4.FSGSBASEfor their instructions.percpu::clear_user_gs_baseand the descriptor-base assert.issues/the-guest-suite-runs-only-what-no-cheaper-tier-reaches.mdno longer brings backgsbase_lockedoruser-writable-gsbase.No AArch64 row is owed. The only shared change is
KernelCtx's new field, which is zero-sized on AArch64, and AArch64's switch writes nothing new.No manual change: the CPU declaration's page names no individual bit.
Test
fsgs_bases_per_threadpins two threads to one CPU and starts both behind a barrier. Each writes FS and GS bases of its own withWRFSBASE/WRGSBASE(one GS base is kernel-half), yields, and reads both back. Each counts how many of its rounds the other ran across, and that count must be above 0.It is a shared-boot member, judged by its own exit in
testcases_members_exit_0, the guest row #1271 made of thetestcasesboot. The standalone rowa_threads_fs_and_gs_bases_stay_its_own, which ran it alone, is gone: #1271's row covers it. That row's mutation run below remains the member's negative control.Control:
gsentry-fsgsbase-mutationhands the incoming thread the outgoing thread's GS base, and must red on the readback assertion.Checks and evidence
Head a72a3d8, on main 3c5c555. The rebase over #1271 conflicted only in
tests/toyos.rs, where the standalone row and its function are deleted.git range-diffshows every other commit unchanged, and no kernel line moved. At a72a3d8, the kernel build, x86-64 clippy andtoyos-checks(55) exit 0.Before that rebase the head was 0658715, on main ce202b2. Since 01ec758:
1 << 63rather than atu64::MAX, which is a base a thread can hold;fsgs_bases_per_threadasserts each thread's GS base is 0 before either writes one;At 0658715, the kernel build and the six x86-64 clippy shapes exit 0, and
fsgs_bases_per_threadbuilds.The rebase onto main conflicted in kernel code. #1251 landed first and rewrote the switch lines this PR changes, and added an
fpufield besideKernelCtx's new one.payload.rsanddriver.rskeep both fields.So
git range-diffshows the first commit changed. The runs at e753534 and 32a7b4d do not stand for the switch, and the rows it reaches are run again below.01ec758 adds a records-only commit: the GS-base defect's record, which #1251 brought to main, now says what #1262 built and what it owes on metal.
Host checks at 9e28356, whose code 01ec758 shares, all exit 0:
toyos-checks(53), the kernel library (496), the source gate;cargo run -- --build-only;fsgs_bases_per_thread,gs_segment_loadanddebug_trapbuilt through the harness's builder.Guest runs:
testcases_members_exit_0,shared_debug_members_exit_0: every shared-boot member under FSGSBASE,fsgs_bases_per_thread,gs_segment_load,debug_trapand thenmi-user-gsself-test among thema_ring3_gs_load_leaves_the_kernel_its_gs,vector_state_survives_a_switch,std_threads_count_the_cpus_and_give_back_ended_threads,affinity_pins_hold_on_every_cpu, plus the then-standalonea_threads_fs_and_gs_bases_stay_its_own. These are the rows the switch reaches. Only the record's start and records changed since, and the rebase moved no kernel line.a_threads_fs_and_gs_bases_stay_its_own, with the test's first-base checka_threads_fs_and_gs_bases_stay_its_ownnmi_under_a_user_gs_base,nested_nmi_is_loud,a_kernel_stack_overflow_names_its_stackring3_debug_traps_end_their_processThe earlier mutation run, 43081134180 at 3e34d08, red on the readback assertion against the pre-#1251 switch.
Owed on metal. The benches are down, so metal is not a landing condition. It is recorded in
issues/fsgsbase-is-taken.md. On the T14 and the AMD bench, main must show:fsgs_bases_per_thread: both threads read back their own bases, with both crossing counts above 0.RDGSBASE/WRGSBASEentries.pingpong.pipe.*onbenchcase. It decides nothing. Whatever FSGSBASE does to a switch's cost is not yet measured on metal.Unresolved
Hw::switchmay run withIFset. Its net cost is unmeasured until the T14 A/B.ltr, an AP's IST vectors have no TSS. Recorded inissues/an-ap-takes-ist-vectors-before-its-tss.md.issues/fsgsbase-is-taken.mdstays open until its runs are read on each proving machine and under TCG. The guest runner runs x86-64 under KVM.🤖 Generated with Claude Code