Cloudanix VulnDB

CVE-2026-99018

High · 7.8

CVSS

7.8

EPSS

0.2%

Published

Oct 23, 2026

CISA due date

—

Cloudanix analysis

Next patch cycle SSVC: Track

A mistake in how the operating system decides when to refresh a security feature on certain processors lets a program running on the machine take control of the whole system.

Exploitation
Local Privilege
Attacker needs
Low Privilege
Complexity
Low
Impact
Full Compromise
Component
OS kernel
Class
Improper Input Validation
Remediation
Upgrade
Effort
Medium
Patch risk
Low

What it actually does

The kernel's shadow call stack reload decision for RISC-V exceptions relies on a user-writable register comparison instead of the hardware SPP bit, allowing a local attacker to retain a user-controlled gp value that turns every non-leaf function's sd/ld ra instructions into an arbitrary kernel write and return-oriented control-flow hijack.

Why it matters to the business

A compromised local process can escalate to full kernel control, enabling theft of credentials, installation of persistent malware, or lateral movement across the system with high urgency given the two-day-old disclosure.

How an attack unfolds

An attacker running as an unprivileged user on a RISC-V CONFIG_SHADOW_CALL_STACK kernel repeatedly writes the linear address of its own task_struct into the tp register, forces a trap from userspace, and thereby prevents the kernel from reloading the legitimate shadow call stack pointer; the poisoned gp is then used for writes and returns on every subsequent kernel entry and is persisted back into task_struct.

How to fix it

Apply the upstream patch that replaces the tp comparison with a check of the sstatus.SPP bit already present in s1; rebuild and reboot into a kernel containing the fix for riscv: entry: gate shadow call stack reload on sstatus.SPP, not tp.

How to confirm the fix worked

Confirm the running kernel contains the corrected handle_exception logic that inspects sstatus.SPP rather than comparing prev against tp; verify by inspecting the assembly at the scs_load_current decision point or by running a reproducer that attempts the tp poisoning and observes whether gp is still reloaded.

Typical attack path

  1. Obtain unprivileged local code execution on a vulnerable RISC-V Linux system
  2. Brute-force or calculate the linear-mapped task_struct address under KASLR
  3. Write that address into the tp register before issuing a syscall or fault
  4. Trigger any exception from userspace so the flawed comparison succeeds
  5. Execute non-leaf kernel functions that dereference the attacker-controlled gp
  6. Achieve arbitrary kernel memory write and hijack control flow

How to detect it

Search kernel logs, auditd records, and syscall traces for unusual tp register values at exception entry points on RISC-V; look for repeated faults or syscalls from the same task followed by anomalous kernel memory writes or unexpected returns through attacker-controlled addresses.

What your security team should do

  • Prioritize patching all RISC-V systems with CONFIG_SHADOW_CALL_STACK enabled within the CISA-style remediation window
  • Rebuild kernels from a tree that includes the SPP-based reload change and deploy via normal update channels
  • Restrict local unprivileged access on exposed RISC-V hosts until patches are applied
  • Monitor for new EPSS or exploit maturity increases over the next 30 days

The record

In the Linux kernel, the following vulnerability has been resolved: riscv: entry: gate shadow call stack reload on sstatus.SPP, not tp Under CONFIG_SHADOW_CALL_STACK, handle_exception() reloads the kernel shadow call stack pointer (gp) only when the trap came from userspace. It made that decision with scs_load_current_if_task_changed, which compares the trap-time tp (read back from sscratch into s5) against the current tp: beq \prev, tp, _skip_scs scs_load_current _skip_scs: tp is a user-writable register. A user task that sets tp to the linear-map address of its own task_struct (&current) before trapping makes the comparison succeed (can achieve by brute force with KASLR enable under our test), so the kernel skips scs_load_current and keeps running with the gp that userspace left in place. Under CONFIG_SHADOW_CALL_STACK gp is the shadow call stack pointer and load_global_pointer is a no-op, so the compiler-emitted "sd ra, -8(gp)" / "ld ra, -8(gp)" in every non-leaf function then write to, and return through, an attacker-controlled address -- an unprivileged kernel arbitrary-write and control-flow-hijack primitive. The poisoned gp is saved to task_struct.scs_sp on return, so the reload is skipped on every subsequent entry as well. Decide "came from userspace" from the hardware sstatus.SPP bit (already in s1 at this point) instead of from tp, mirroring the check that ret_from_exception already uses a few instructions later. SPP is written by hardware on trap entry and cannot be forged from U-mode. Legitimate behaviour is unchanged: user entries (SPP=0) reload the task shadow call stack, nested kernel entries (SPP=1) skip it.

Affected packages

Ecosystem Package Introduced Fixed