|
12 | 12 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4f134b89a24b965991e7c345b9a4591821f7c2a6 |
13 | 13 | // |
14 | 14 | // Leak primitive: |
15 | | -// Data leaked: kernel stack data (uninitialized upper bytes of syscall |
16 | | -// args) Kernel subsystem: fs/proc — /proc/<PID>/syscall (collect_syscall) |
17 | | -// Data structure: struct syscall_info → data.args[] (upper 32 bits on |
18 | | -// 32-bit) Address type: virtual (kernel stack) Method: parsed |
| 15 | +// Data leaked: kernel stack data (args[3..5], never written on 32-bit) |
| 16 | +// Kernel subsystem: fs/proc — /proc/<PID>/syscall (collect_syscall) |
| 17 | +// Data structure: struct syscall_info → data.args[] |
| 18 | +// Address type: virtual (kernel stack) |
| 19 | +// Method: parsed |
19 | 20 | // CVE: CVE-2020-28588 |
20 | 21 | // Patched: v5.10 (commit 4f134b89a24b) |
21 | 22 | // Status: fixed in v5.10 |
22 | 23 | // Access check: none pre-v5.10 (world-readable /proc/<PID>/syscall) |
23 | 24 | // Source: https://elixir.bootlin.com/linux/v5.9/source/fs/proc/base.c |
24 | 25 | // |
25 | 26 | // Mitigations: |
26 | | -// Patched in v5.10. No runtime sysctl could restrict access — the bug was |
27 | | -// in collect_syscall() failing to zero upper bytes of 64-bit arg fields on |
28 | | -// 32-bit systems. Only affects 32-bit kernels (ARM, x86_32, etc.). |
| 27 | +// Patched in v5.10. No runtime sysctl could restrict access — the bug is |
| 28 | +// that collect_syscall() stores six `unsigned long` into a __u64 args[6], |
| 29 | +// covering 24 of 48 bytes on 32-bit. Only affects 32-bit kernels (ARM, |
| 30 | +// x86_32, etc.). |
29 | 31 | // |
30 | 32 | // Requires: |
31 | 33 | // - CONFIG_HAVE_ARCH_TRACEHOOK=y |
|
36 | 38 | // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4f134b89a24b965991e7c345b9a4591821f7c2a6 |
37 | 39 | // https://cateee.net/lkddb/web-lkddb/HAVE_ARCH_TRACEHOOK.html |
38 | 40 | // |
39 | | -// 32-bit-kernel only — gated at compile time. The bug is in 32-bit kernels' |
40 | | -// collect_syscall() (the high 32 bits of 64-bit syscall_info args are not |
41 | | -// zeroed). 64-bit kernels follow a different code path and are not |
42 | | -// vulnerable. On 64-bit kernels the argument-register values exposed by |
43 | | -// /proc/<PID>/syscall are ordinary syscall numbers and pointers, none of |
44 | | -// which carry the kernel-stack residue the exploit reads on 32-bit, and |
45 | | -// misinterpreting a small integer (e.g. a syscall number) as a kernel |
46 | | -// address would produce a nonsense observation. |
| 41 | +// 32-bit-kernel only — gated at compile time. collect_syscall() fills |
| 42 | +// data.args[] by storing six `unsigned long`, but the array is __u64 args[6]: |
| 43 | +// on 32-bit that writes 24 of 48 bytes, so args[3..5] are never written at |
| 44 | +// all, while args[0..2] each carry two real arguments packed into one field. |
| 45 | +// 64-bit kernels run the SAME code with sizeof(long) == 8, where six stores |
| 46 | +// fill the array exactly and no residue remains — the values exposed there |
| 47 | +// are ordinary syscall numbers and pointers, and misinterpreting one as a |
| 48 | +// kernel address would produce a nonsense observation. |
47 | 49 | // --- |
48 | 50 | // <bcoles@gmail.com> |
49 | 51 |
|
|
64 | 66 |
|
65 | 67 | KASLD_EXPLAIN( |
66 | 68 | "Reads /proc/<PID>/syscall on a 32-bit kernel. The file reports six " |
67 | | - "64-bit argument registers, but on 32-bit only the lower 32 bits are " |
68 | | - "used. Before the v5.10 fix (CVE-2020-28588), the upper 32 bits were " |
69 | | - "not zeroed, leaking stale kernel stack data that often contains " |
70 | | - "kernel text or stack pointers."); |
| 69 | + "64-bit argument fields, but the kernel fills them with six 32-bit " |
| 70 | + "words. Before the v5.10 fix (CVE-2020-28588), the last three fields " |
| 71 | + "were left holding stale kernel stack data — a text pointer on x86_32, " |
| 72 | + "arm and riscv32, a direct-map pointer on powerpc and mips."); |
71 | 73 |
|
72 | 74 | KASLD_META("method:parsed\n" |
73 | 75 | "phase:inference\n" |
@@ -214,8 +216,22 @@ int main(void) { |
214 | 216 |
|
215 | 217 | kasld_info("lowest leaked address: %lx", addr); |
216 | 218 | kasld_info("possible kernel base: %lx", kasld_floor_text_base(addr)); |
| 219 | + /* The leaked word is a kernel address; nothing available here establishes |
| 220 | + * that it is TEXT. It is whatever the reading task's call chain left on |
| 221 | + * proc_pid_syscall()'s own stack frame, which differs by architecture: a |
| 222 | + * return address into text on x86_32, arm and riscv32; a direct-map pointer |
| 223 | + * (kernel stacks, lowmem objects) on powerpc and mips, where the image is |
| 224 | + * randomized above them and a direct-map word therefore lands BELOW _text. |
| 225 | + * |
| 226 | + * An interior-text sample implies image_base <= sample, so tagging a |
| 227 | + * direct-map word as text truncates the image-base window below the true |
| 228 | + * base. CONF_HEURISTIC keeps the observation under the sound floor, where |
| 229 | + * the engine's confidence gate drops it from the floored run entirely: it |
| 230 | + * shapes the likely window, and cannot bound the guaranteed one. Confirming |
| 231 | + * text membership needs a text band this component cannot see — it runs |
| 232 | + * before inference, and that band is the unknown being solved for. */ |
217 | 233 | kasld_result_sample(KASLD_TYPE_VIRT, REGION_KERNEL_TEXT, addr, NULL, |
218 | | - CONF_PARSED); |
| 234 | + CONF_HEURISTIC); |
219 | 235 |
|
220 | 236 | return 0; |
221 | 237 | } |
0 commit comments