Details from the reporter, Rafal Wojtczuk
Attack steps (done by ring3 attacker)
- Map a frame at virtual address
(1<<47)-4096
- Any method to set the target of
sysret
to a non-canonical address can potentially be used. This includes
ptrace
,
sys_sigreturn
,
sigaction
,
execve
, possibly others. The best solution is to add a check for the address being non-canonical close before executing
sysret
. Note if the
syscall
handler ends with
iret
, then even if
iret
throws
#GP
,
rsp
is not controlled by the attacker, and such the situation can be handled safely.
- Place a
syscall
instruction at address
(1<<47)-2
- Place SOMETHING_MALICIOUS in general purpose registers
- Set
rsp
to AROUND_SOME_IMPORTANT_RING0_STRUCTURE
- Other scenarios are possible. Whenever the
#GP
handler runs with usermode
rsp
, or does not do
swapgs
correctly, code execution may be possible.
- Jump to
syscall
instruction at
(1<<47)-2
At the
syscall
handler entry,
rcx
will be set to
(1<<47)-2+instruction_len(syscall) = 1<<47
, which is non-canonical. Therefore, when the
syscall
handler terminates with
sysret
,
#GP
will be raised. This fault will be handled without a stack switch (assuming
#GP
entry in
IDT
does not include a nonzero IST index), as the faulting instruction is in ring0. Only Intel CPUs are affected. On AMD CPUs,
sysret
completes the switch to ring3 before throwing
#GP
, so the stack switch occurs. Also, immediately before
sysret
, the
syscall
handler must restore
rsp
to the value set by ring3 (because
syscall/sysret
do not set
rsp
). Therefore, the
#GP
handler will execute with
rsp
chosen by the attacker, so when
GPRs
are pushed on the stack in the
#GP
handler prologue, SOMETHING_MALICIOUS will be placed at AROUND_SOME_IMPORTANT_RING0_STRUCTURE. This write-anything-anywhere primitive could be enough to hijack execution in ring0. Additionally, in many cases,
gs
base is not swapped in the
#GP
prologue (as the fault originates in ring0), which may make the exploitation quite reliable and stable - overwrite
#PF IDT
entry via stack push, trigger
#PF
by
gs
access, repair IDT (from IDT table of other cpu) in the shellcode, return to usermode.
(azért másoltam be ide, mert a CERT eltávolította az oldaláról időközben ezt a leírást... ;)