Skip to content

Every entry from Ring 3 clears branch history where the CPU's decision names it - #1279

Draft
Japabu wants to merge 2 commits into
mitigate-askfrom
mitigate-entry
Draft

Japabu wants to merge 2 commits into
mitigate-askfrom
mitigate-entry

Conversation

@Japabu

@Japabu Japabu commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #1278: the diff is this PR's commits alone, and it lands after #1278.

Problem

The decision names "BHB cleared on entry" for an Intel CPU with enhanced IBRS and no BHI_DIS_S (the T14), and for a guest whose IBRS or returns its hypervisor's hardware could undercut. No entry cleared it, so user code's branch history reached the kernel's first indirect branch. The record, issues/no-entry-or-switch-clears-the-bhb-fills-the-rsb-or-issues-an-ibpb.md, is renamed here to what remains: issues/no-gate-holds-the-bhb-clear-and-the-return-fill-to-their-vendors-sequences.md.

What changes

  • The sequence: arch::x86_64::mitigation::clear_branch_history is Intel's short sequence ("Branch History Injection and Intra-mode Branch Target Injection", Listing 1: "sufficient to clear the BHB on all processors prior to Alder Lake"). It sits between a save and a restore of the two registers it writes, and the site cites the listing. Intel's guidance puts it after every transition into CPL0, so it runs on every entry from Ring 3, not only SYSCALL.
  • Where the decision names it: only where Intel scopes Listing 1. That is a host whose row in Intel's affected-processor table gives BHI's mitigation as "Software" alone (the processors before Alder Lake), and a guest, whose VMM sets BHI_DIS_S under the short sequence ("Software Mitigations in Migration Pools").
    • A later host, or one the table omits, that enumerates no BHI_CTRL (microcode older than its BHI update) reports vulnerable: needs BHI_DIS_S; this CPU enumerates no BHI_DIS_S, never "mitigated".
    • The long sequence (Listing 2) is not built, and the record keeps that gap.
    • The T14 (Tiger Lake, a "Software" row) and the guest runner's guest decide as before.
  • SYSCALL: there are two entries, one of which calls the sequence as soon as the kernel's stack is under it. Each CPU's LSTAR names the entry its decision selects. A CPU that does not clear runs no added instruction, not even a branch. The build's entry-window gate now reads both entries.
  • The IDT: every gate the IDT names opens with a fixed 12-byte prefix: a test of the frame's CS, a short jz over the call, and the call.
    • idt::init builds AT_PREFIX, which enters each gate at the prefix, and PAST_PREFIX, which enters past it. It refuses any gate that does not open with the prefix's bytes.
    • Each CPU loads the table its decision selects. The boot CPU does so in init. An AP does so after its own decision, replacing the boot CPU's table that the trampoline loaded.
    • A jz mispredicted past the call reaches no load through GS and no indirect branch before the gate's own lfence or serializing wrmsr.
    • I chose this over a per-CPU flag tested inside each gate. User code could mistrain such a branch to skip the clear.
  • ITS and the sequence's returns: three of the sequence's returns sit in the lower halves of their lines. Each returns to a call the sequence made, at most seven deep, so the RSB predicts it. Intel's "Indirect Target Selection" ties a return's exposure to the alternate predictors an empty RSB falls back on, so these returns are outside it. The site and the thunks record say so, and the returns take no thunk.
  • #DB: a #DB that a MOV SS holds over an entry's first instruction is resumed at either entry of a gate or of SYSCALL.
  • The boot line says the clear is in force where the CPU enumerates it, since each CPU's entries are the ones its decision selects. in_force names this, with the on-request controls, as one by-construction set.
  • Test kernel only: the sequence counts its runs, and the new debug action BRANCH_HISTORY_CLEARS reads the count on a CPU whose decision names the clear.

Tests

  • branch_history_cleared_on_each_entry, a shared-debug member, holds the count of clears to at least 1000 over 1000 syscalls (through LSTAR's entry) and at least 256 over 256 page faults on fresh pages (through the IDT's).
    • On a CPU whose decision names no clear it says so and holds nothing: the AMD bench, and TCG.
    • It needs a guest because the sequence leaves nothing a program can read.
    • Targets: the T14, and the guest runner, whose guest's decision should name the clear. That is an inference from the words A pure function decides a CPU's speculation mitigations, and each boot says its verdict #1243's guest run logged (IBRS, no IBRS_ALL, RSBA by its signature in Intel's Table 5). It stays an inference until this round's run shows the member's "N clears" line and the guest's branch_history_injection: mitigated: BHB cleared on entry. If the guest names no clear, both runs prove nothing and the clear is owed on metal alone.
  • Host: the_short_sequence_is_named_only_where_intel_scopes_it. A patch that names the short sequence on every part reds it (patch and log in the evidence). The build gate's a_hold_in_the_clearing_entry_alone_is_refused.
  • The disassembled kernel shows each gate opening f6 44 24 xx 03 74 05 e8, the sequence as Intel's listing has it, and the clearing SYSCALL entry calling it right after the stack switch (round 1's evidence).

Checks

Head a9892c2a8, on #1278's 1ab7a9b0b. Round 2 answers review r1's B1 to B5 and suggestions S1, S2, S3 (the gate half) and S5 (the table's name, and the macro's doc; the macro's shape stays, since an optional operand cannot be left unnamed in naked_asm!).

  • Host: cargo run -- --ci host: exit 1, 122 of 123 steps green; the red is the build system, the recorded macOS llvm::tests failure (issues/the-llvm-environment-test-breaks-git-on-macos.md). The kernel's mitigation tests, the harness's checks, the build gate's tests and clippy (26 invocations) are clean.
  • Images: the shipping image and the testcases image on the test kernel both build. idt::init's prefix check runs at every boot.
  • Negative control: guest control run 43168191635, on branch mitigate-entry-control-inverted at this head plus an inverted selection (LSTAR and the IDT name the entries the decision does not select), must red branch_history_cleared_on_each_entry. It is queued.
  • Guest: run 43168172877 is queued: mitigations_in_a_guest, shared_debug_members_exit_0, testcases_members_exit_0, ring3_debug_traps_end_their_process, nmi_under_a_user_gs_base, nested_nmi_is_loud and a_user_copy_faults_in_mid_copy_or_ends_as_bad_address. Runs at earlier heads (43158101891, 43158150391, 43165362239, 43165381286) were cancelled. The heads moved once more after the host round: mitigations_in_a_guest now prints every line (masked as before), so a green run shows the guest's verdicts too; only tests/toyos.rs differs from the heads the host checks ran at. This PR is x86-64 only: AArch64 gains only a test-only stub, which clippy checks.

Evidence: round 1 (host), https://github.com/ToyOSOrg/evidence/tree/04f5890cbafd0189b8fe8a4f883cd89a4ce27598/r1. Round 2 (host), https://github.com/ToyOSOrg/evidence/tree/924ae7ba024f24e6a8ec26667ba57941020b391b/r2. The guest round follows.

Left (recorded)

  • An NMI or #MC that lands on an entry from Ring 3 before its clear finishes runs its handler on the user's branch history.
  • The long sequence is not built, so a later Intel host on old microcode reports the gap instead.
  • A guest that clears with the short sequence does not tell its VMM, through MSR_VIRTUAL_MITIGATION_CTRL, that it relies on it.
  • The decoder gate over kernel.elf belongs to issues/no-gate-decodes-kernel-elfs-instructions.md.

Owed on metal (benches down)

Recorded in issues/no-gate-holds-the-bhb-clear-and-the-return-fill-to-their-vendors-sequences.md:

  • branch_history_cleared_on_each_entry and speculation_on_request, members of main's shared-debug boot, on the T14 and the AMD bench;
  • the T14's mitigations row, metalcase boot of main, with branch_history_injection: mitigated: BHB cleared on entry on every CPU.

The clear's cost on the T14's syscall path is not measured on metal.

🤖 Generated with Claude Code

@Japabu

Japabu commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator Author

Review r1 of #1279 at 5a1d7c8

Read: both commits against #1278's 50c9da201 and every changed file in full (23 files, +389/−126). About 70 of the syscall.rs lines are the existing entry re-indented into a macro, and 45 are the issue record. Also read, for callers and boundaries: common_entry/fatal_entry, device_irq, user_dev, smp::ap_entry, percpu::init_bsp, boot, microcode's temporary IDT, build/build.rs's entry-window gate, the pure decision's BHI arm, Intel's BHI article, the evidence at 04f5890c and the control commit d5ad439c6.

Architecture

Ownership and shape are right. Each CPU's choice is made where its decision is applied (arch::x86_64::mitigation, then idt::load and syscall::init), and the pure crate only reports it. The smallest representation that lets each CPU decide for itself, while a CPU that does not clear pays nothing, is two IDTs 12 bytes apart and two LSTAR targets. A flag would need GS before the gate's swapgs, or a branch on every CPU. Per-CPU tables or code patching cost more. The added cost is 4 KiB of table, 12 bytes per gate and 16 more per claim stub (about 3 KiB of text). IdtEntry::past_prefix's assert enforces the prefix on every gate at every boot, which is stronger than review. Action 33 follows the existing SYS_DEBUG pattern, with an AArch64 stub.

Correctness: verified

  • The sequence against Listing 1: ecx = 5, eax = 5, both .align 64 blocks, the jmp/nop inner loop and the closing lfence all match. The disassembly at 69e940512 has 2: and 3: 64-aligned (c6a80, c6ac0) and the fence before the pops. Below the gate frame the clear uses 72 bytes: the return address, two saves and six nested calls.

  • Clobbers: rax and rcx are saved; only the flags are clobbered. In a gate, nothing is live but the hardware frame. In SYSCALL, rcx comes back, r11 is untouched and the user's flags are in r11. The clear runs before cld/clac but uses no string instruction and touches no user memory.

  • [rsp + cs]:

    • 8 for every gate without an error code, every claim stub and device_irq (88 − 80).
    • 16 for #DF, #TS, #NP, #SS, #GP, #PF, #AC and #CP.
    • The numbered claim_entry keeps cs = 16 with no prefix, and common_entry keeps 24.
  • IST gates: the prefix's calls and pushes are balanced, so NMI's FIRST_PUSHES/RFLAGS_OFFSET and #DB's RIP_OFFSET and frame copy are unchanged. A nested NMI, or a #DB at CPL 0, is a Ring 0 frame and skips the clear. x86-64: every entry from Ring 3 swaps in the kernel's GS base, and NMI, #MC and #DF install it whatever GS holds #1262's rule (GS from GDTR, never from CS) is untouched: the prefix reads CS only to decide whether to clear.

  • A mispredicted skip: I traced each gate family past a skipped call. No GS load or indirect branch comes before the stopping instruction:

    gate family stopped by
    ring3_naked_asm gates their own lfence
    exception stubs common_entry's lfence
    #DF and #MC fatal_entry's wrmsr
    NMI, and #DB's Ring 0 path their wrmsr
    #DB's Ring 3 path it reads through GDTR and jumps directly to common_entry's lfence
    halt stub cli; hlt
    claim stubs claim_entry's lfence

    From Ring 0, a jz mispredicted as not taken only runs the clear speculatively.

  • Wrong IDT or LSTAR:

    • The boot CPU's words are recorded in after_console, before init_bsp and interrupts.
    • An AP loads its own table and LSTAR before it echoes, and before any user code. Until then it is under the trampoline's table, which sidt took from the boot CPU after init, and that table serves only Ring 0 entries.
    • amd_patch_load restores the table it found.
    • The tree has no hotplug and no S3 resume.
    • The BHI arm ignores smt, so bring-up and report agree.
  • The #DB a MOV SS holds: resumed at either entry of a gate or of SYSCALL, and is_gate_target reads both tables.

  • Test and control: d5ad439c6 is 69e940512 with both selections inverted. A guest whose decision names the clear would then run none, so the member must red. It counts SYSCALL entries only.

Findings

B1 issues/no-entry-or-switch-clears-the-bhb-fills-the-rsb-or-issues-an-ibpb.md:8: the slug and title are now false.

  • With A program that asks for isolated speculation runs with SSBD and STIBP, and an IBPB goes between it and another #1278, switches issue IBPBs and fill the return predictor; with this PR, entries clear the BHB, on every CPU whose decision names each.
  • issues/README.md says a slug "the tree has refuted is renamed by the work that takes it up, with every citation moved". This PR completes the refutation.
  • Rename it to what remains (the decoder gate, the metal runs and the VMM notice) and move its three citations:
    • issues/toyos-against-linux-on-both-benches-the-scoreboard.md:309
    • issues/no-gate-decodes-kernel-elfs-instructions.md:11
    • issues/the-kernel-is-at-least-as-secure-as-linux-on-every-machine-toyos-supports.md:29

B2 kernel/pure/mitigation.rs:487-490: the doc comment no longer describes the function.

B3 The PR body's Left, and issues/indirect-branches-and-returns-run-without-thunks.md: the body records something the issue does not.

  • The body says ITS's placement of the sequence's returns "belongs to" that issue, but the issue never mentions them. Its Exit would refuse any raw ret outside the thunks and entry.
  • The three new rets sit in the lower halves of their lines: c6a85, c6ad2 and c6ad8 (offsets 0x05, 0x12 and 0x18). The same offsets hold without the test kernel's count.
  • The T14's line says it is vulnerable to ITS.
  • Either record them there, noting the sequence's returns cannot be thunked, or show they are RSB-predicted and outside ITS and drop the Left line. Either way, no undisclosed compromise lands.

B4 The PR body's Tests, and tests/guest/rust-tests/src/bin/branch_history_cleared_per_syscall.rs:17-20: the guest evidence claim is unverified.

  • The body states as fact that the guest runner's guest decides to clear. No published log shows that guest's mitigations words yet.
  • The member exits 0 on a CPU whose decision names no clear. A green 43158101891 therefore establishes the clear only if its log shows:
    • the member's "N clears over 1000 syscalls" line;
    • branch_history_injection: mitigated: BHB cleared on entry.
  • 43158150391 must red on this member.
  • Until both show it, the body's sentence is an inference and should be labelled as one.

S1 kernel/src/arch/x86_64/syscall.rs:56-58, entry.rs:49-53: an NMI or #MC can run on the user's history.

  • One that lands before an entry's clear finishes arrives as a Ring 0 frame, skips the clear and runs its handler on the user's history. That window is SYSCALL's first four instructions, or any gate while its clear runs.
  • It is narrow and the user cannot time it, but the record's "every entry from Ring 3 runs … before any indirect branch" does not cover it.
  • Record it under Left, or make the NMI gate clear whenever its CPU does.
  • entry.rs's header lists the CPL-0 spans with a user's GS base live, and that list omits a gate's opening, which now holds the whole clear.

S2 build/build.rs:1788,1863: judge_entry_window reads only syscall_entry, but a CPU that clears runs syscall_entry_clearing_branch_history. Both share one macro today. If $($clear,)? moved above the stack switch, though, the image would ship. Reading both symbols takes a few lines.

S3 The test leaves two properties without a probe.

  • It counts only SYSCALL clears, so idt::load's selection rests on inspection. An AP that kept the boot CPU's table on a mixed machine would pass. Touching N fresh pages, as ring0_clears_ac does, would hold the gate clears too.
  • A CPU that does not clear gets NotSupported, so "runs none of it" has no probe. Returning the count everywhere would let the AMD bench hold zero growth.

S4 kernel/pure/mitigation.rs:885-891, with the scope that kernel/src/arch/x86_64/mitigation.rs:400 quotes: a newer part with old microcode would report mitigated with an insufficient sequence.

  • Intel scopes Listing 1 to processors before a named generation. For later P-cores without BHI_NO, it gives Listing 2.
  • The decision names the clear for any enhanced-IBRS CPU that does not enumerate BHI_CTRL. On a later part, that means microcode older than its BHI_DIS_S update, and ToyOS ships updates for only two signatures.
  • Record it in the BHB issue, or refuse that case.

S5 Readability.

  • IDT now names only the table of CPUs that clear; AT_PREFIX/PAST_PREFIX would say so.
  • syscall_entry! takes an arbitrary template line and operand for its one use. An optional clear token with fixed text is simpler, and its doc could drop "with a template line and its operand".

Evidence

  • Host: --ci host exited 1 at 69e940512, with 122 of 123 steps green. The red is llvm::tests::the_caller_s_environment_reaches_neither_the_build_nor_the_key, issues/the-llvm-environment-test-breaks-git-on-macos.md.
  • Checks: the mitigation tests, the harness checks and 26 clippy invocations are clean, and both images built.
  • Reuse: 5a1d7c8e6 changes one doc comment, so the evidence carries over.
  • CPU models: the commits, the evidence and the body name none. The routine's doc and the body quote Intel's own caption for Listing 1, which names a generation, not a bench.
  • Guest: the guest runs are queued; B4 says what they must show.

SEND BACK

Japabu added a commit that referenced this pull request Nov 12, 2026
Review r1 of #1279 (B2): the on-request controls are in force wherever the
decision names them and the CPU enumerates them, which no register shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Nov 12, 2026
…d the record renamed to what is left

- The decision names the short BHB-clearing sequence, Listing 1, only
  where Intel scopes it: hosts its table gives BHI's mitigation as
  "Software" alone, the processors before Alder Lake, and guests, whose VMM
  sets BHI_DIS_S under it. A later or unlisted host without BHI_CTRL, whose
  microcode predates BHI_DIS_S, says vulnerable and lacking BHI_DIS_S
  rather than mitigated; the record keeps the gap (B5). The T14 and the
  guest runner's guest decide as before.
- in_force names one set, BY_CONSTRUCTION, for what the kernel applies
  wherever the decision names it and no register shows: the on-request
  controls and the entries' clear (B2).
- clear_branch_history's returns each go back to a call it made, at most
  seven deep, so the RSB predicts them and Indirect Target Selection, which
  reaches a return only through an empty RSB's alternate predictors, does
  not; the site and the thunks record say so (B3).
- The BHB/RSB/IBPB record is renamed to what remains, the gate, the metal
  runs and the VMM notice, with its three citations moved, and records the
  NMI/#MC window before a clear finishes (B1, S1).
- The build's entry-window gate reads both SYSCALL entries (S2).
- The member, now branch_history_cleared_on_each_entry, also holds a clear
  per page fault on a fresh page, through the IDT's gates (S3).
- The table that enters each gate at its prefix is AT_PREFIX (S5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit to ToyOSOrg/evidence that referenced this pull request Nov 12, 2026
…6b24f30a5152f: the short sequence scoped, the record renamed, both entries gated; masked
Japabu and others added 2 commits November 12, 2026 08:25
…n names it

The decision named the BHB cleared on entry for a CPU with enhanced IBRS
and no BHI_DIS_S, or a guest whose IBRS or returns could be undercut, and
no entry cleared it: user code's branch history reached the kernel's first
indirect branch.

- arch::mitigation::clear_branch_history is Intel's short sequence ("Branch
  History Injection and Intra-mode Branch Target Injection", Listing 1),
  between a save and a restore of the two registers it writes.
- SYSCALL has two entries, one calling it as soon as the kernel's stack is
  under it. Each CPU's LSTAR names the one its decision selects, so a CPU
  that does not clear runs no added instruction, not even a branch.
- Every gate the IDT names opens with a fixed 12-byte prefix: a test of the
  frame's CS, a short jz over the call, the call. idt::init builds the table
  that enters each gate there and a second that enters past it, refusing a
  gate that does not open with the prefix; each CPU loads the one its
  decision selects (the boot CPU in init, an AP after its own decision, over
  the boot CPU's table the trampoline loaded). A jz mispredicted past the
  call meets no load through GS and no indirect branch before the gate's
  own lfence or serializing wrmsr.
- The #DB that a MOV SS holds over an entry's first instruction is resumed
  at either entry of a gate or of SYSCALL.
- The line says the clear in force where the CPU enumerates it, as each
  CPU's entries are the ones its decision selects. On the test kernel the
  routine counts its runs, and SYS_DEBUG's BRANCH_HISTORY_CLEARS reads them
  where this CPU clears; branch_history_cleared_per_syscall holds at least
  one per syscall.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d the record renamed to what is left

- The decision names the short BHB-clearing sequence, Listing 1, only
  where Intel scopes it: hosts its table gives BHI's mitigation as
  "Software" alone, the processors before Alder Lake, and guests, whose VMM
  sets BHI_DIS_S under it. A later or unlisted host without BHI_CTRL, whose
  microcode predates BHI_DIS_S, says vulnerable and lacking BHI_DIS_S
  rather than mitigated; the record keeps the gap (B5). The T14 and the
  guest runner's guest decide as before.
- in_force names one set, BY_CONSTRUCTION, for what the kernel applies
  wherever the decision names it and no register shows: the on-request
  controls and the entries' clear (B2).
- clear_branch_history's returns each go back to a call it made, at most
  seven deep, so the RSB predicts them and Indirect Target Selection, which
  reaches a return only through an empty RSB's alternate predictors, does
  not; the site and the thunks record say so (B3).
- The BHB/RSB/IBPB record is renamed to what remains, the gate, the metal
  runs and the VMM notice, with its three citations moved, and records the
  NMI/#MC window before a clear finishes (B1, S1).
- The build's entry-window gate reads both SYSCALL entries (S2).
- The member, now branch_history_cleared_on_each_entry, also holds a clear
  per page fault on a fresh page, through the IDT's gates (S3).
- The table that enters each gate at its prefix is AT_PREFIX (S5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Nov 12, 2026
Review r1 of #1279 (B2): the on-request controls are in force wherever the
decision names them and the CPU enumerates them, which no register shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator Author

Review r2 of #1279 at a9892c2

Architecture. The architecture is unchanged since r1. The round-2 commit (+232/−128 over 13 files) does five things:

  • adds the BHI_SOFTWARE column and the scoped arm;
  • folds the by-construction set into in_force;
  • makes the build gate read both SYSCALL entries;
  • rewrites the member to count page faults too;
  • renames the record and updates its citations.

Round 1

  • B1 resolved. git grep finds no reference to the old slug or the old member name anywhere in the tree.

  • B2 resolved. BY_CONSTRUCTION holds the on-request controls and BhbClear, and the doc now describes what in_force returns.

  • B3 resolved. I checked the argument against Intel's "Indirect Target Selection" page, read through an archived copy because intel.com refuses my client.

    • The page says the issue "only applies to indirect branch instructions (including RETs) which have their last byte in the first 32 bytes", and that the affected processors "also enumerate RRSBA, and so … RET instructions may be predicted using alternate predictors and thus may be affected". So a RET is exposed only when it is predicted by an alternate predictor, which RRSBA permits only on an empty RSB.
    • The calls, traced: the prefix's call is depth 1 and call 2f is depth 2. jnz 2b then re-enters call 3f five times without returning, reaching depth 7.
    • The seven returns pop those seven entries in order. Intel's RSBs hold more than seven, so every one of the sequence's returns is RSB-predicted.
    • The one way to empty the RSB mid-sequence is an NMI deep enough to wrap it, and that falls inside the recorded NMI/#MC window.
    • The site comment and the thunks record state this accurately.
  • B4: the run has now reported. See B2.

  • B5, the scoping against Intel's table: verified against Intel's machine-readable list (intel/Intel-affected-processor-list, Intel_affected_processor_list.csv at current main).

    • Every row the code marks BHI_SOFTWARE reads "Software" in Intel's BHI column. Every other BHI row reads "MCU+Software" or "Hardware+Software": Sapphire Rapids, and Alder Lake onwards.
    • Intel's BHI paper scopes Listing 1 to "all processors prior to Alder Lake".
    • On hosts, no false "mitigated" remains. The host test and the mutant hold this.
  • B5, whether "guests" is justified: yes, for a guest that enumerates IBRS_ALL without BHI_CTRL. Intel says:

    • "When BHI_DIS_S is not enumerated to a guest but IBRS is enumerated and enabled, guest OSes can assume that the VMM will cause BHI_DIS_S to be set on the underlying hardware to support use of the short software sequence";
    • VMMs "are expected to set the BHB_CLEAR_SEQ_S_USED bit by default".

    B3 below covers the guests this arm never reaches.

  • S1, S2, S3 (the gate half) and S5 taken.

Findings

B1 Rebasing onto main 29c5f2dfa removes the stop that r1's mispredicted-skip argument relies on, for three gate families.

B2 The guest runner's guest names no clear, so the statements that it does are false.

  • Run 43168172877 at this head printed branch_history_injection: not affected for the guest.
    • branch_history_cleared_on_each_entry therefore held nothing there.
    • Inverted control 43168191635 cannot red the member. Its CPUs do run the clearing entries, but the debug action still refuses on a CPU whose decision names no clear.
  • tests/toyos.rs:74-76: the registration declares no targets. It should declare the T14 as where the member holds, and the AMD bench, the guest runner and TCG as where it says so and holds nothing.
  • issues/no-gate-holds-the-bhb-clear-and-the-return-fill-to-their-vendors-sequences.md:63: "A guest on the guest runner is one" is false.
  • PR body's Tests (the orchestrator's): the inference is refuted, and until metal there is no runtime evidence that the clear runs. If the control's boot is green, it still shows the clearing entries running a whole guest boot correctly. That is worth stating, though it does not probe the member.

B3 The decision trusts a guest's signature to say "not affected", and distrusts it when choosing the sequence.

  • kernel/pure/mitigation.rs:883 returns NotAffected wherever Intel's table lists the signature as BHI Not Affected. It runs before both guest arms, at :894 and :899.
  • The guest runner's guest enumerates IBRS, RSBA and no BHI_NO. So its "not affected" came from the table.
  • What Intel prescribes:
    • The BHI guidance for operating systems says that "when running in virtualized environments where IBRS is enumerated but IBRS_ALL is not enumerated, guests may need to apply mitigations (since the VMM may run the guest on hardware with IBRS_ALL support)". It gives the short sequence where the OS relies on IBRS, or relies on retpoline with RSBA or RRSBA.
    • Its migration-pool section says a guest's family, model and stepping may not reflect the hardware. That is the premise of this round's _ if f.hypervisor arm.
  • The consequence: :899, which is A pure function decides a CPU's speculation mitigations, and each boot says its verdict #1243's encoding of that guidance, never runs for a guest whose signature the table lists as BHI Not Affected. Such guests report "not affected" where Intel prescribes the clear, and the guest runner's guest is one of them.
  • The defect is A pure function decides a CPU's speculation mitigations, and each boot says its verdict #1243's, but it sits in the arm this PR rewrites for that very premise.
    • The fix is one condition (for example !f.hypervisor && f.intel_lists(BHI) == Some(false)) plus a host case.
    • It would also make the guest runner a real target for the member and its inverted control.
    • If the orchestrator keeps it out of this PR, it must be recorded, with this run as its evidence.

S1 kernel/src/arch/x86_64/mitigation.rs:481 — the other half of r1's S3. If BRANCH_HISTORY_CLEARS answered on every CPU, a CPU whose decision names no clear could hold zero growth, and the inverted control would red it. That gives the guest a probe of the selection even if B3 is not taken.

S2 issues/no-gate-holds-…md:52-57 and kernel/pure/mitigation.rs:1563 — the omitted-part example, 0x50657, is Cascade Lake.

  • Intel's affected list has no row for it.
  • Its ITS page lists it (06_55H, stepping 7), and the BHI paper's Listing 1 covers it, since it predates Alder Lake.
  • The decision now leaves it uncleared where round 1 cleared it. That is honest: the verdict is assumed and is no false "mitigated".
  • But the record's "the short sequence is not Intel's for it" is wrong for an omitted part that predates Alder Lake. "May not be" would be accurate.

Notes for the orchestrator

Evidence

  • Host round 2: run at c07ac2ef3, which differs from this head only in tests/toyos.rs's print. Exit 1, 122 of 123 steps green; the only red is the recorded macOS llvm::tests failure.
  • Mutant: naming the short sequence everywhere reds the_short_sequence_is_named_only_where_intel_scopes_it (b2-mutant-short-everywhere.log).
  • Build gate and clippy: clean.
  • Guest: 43168172877 is as above. Control 43168191635 is queued; per B2, it cannot red the member on this runner.

SEND BACK

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant