33// Parse ELF notes from /sys/kernel/notes for leaked kernel pointers on
44// x86(_64) kernels.
55//
6- // Xen ELF notes on kernels with CONFIG_XEN=y contain KASLR-adjusted virtual
7- // addresses generated via _ASM_PTR in arch/x86/xen/xen-head.S:
8- // - Type 1 (XEN_ELFNOTE_ENTRY): startup_xen virtual address
9- // - Type 2 (XEN_ELFNOTE_HYPERCALL_PAGE): hypercall_page virtual address
10- // - Type 18 (XEN_ELFNOTE_PHYS32_ENTRY): virtual KASLR offset (pvh_start_xen -
11- // __START_KERNEL_map)
6+ // Xen ELF notes embed KASLR-adjusted virtual addresses generated via _ASM_PTR
7+ // in the kernel image. The gating config differs per note:
8+ // - Type 1 (XEN_ELFNOTE_ENTRY): startup_xen VA [CONFIG_XEN_PV]
9+ // - Type 2 (XEN_ELFNOTE_HYPERCALL_PAGE): hypercall_page VA [CONFIG_XEN_PV]
10+ // - Type 18 (XEN_ELFNOTE_PHYS32_ENTRY): pvh_start_xen - [CONFIG_PVH]
11+ // __START_KERNEL_map (a virtual
12+ // offset)
13+ // So it is not merely CONFIG_XEN: the PV notes need CONFIG_XEN_PV and the PVH
14+ // note needs CONFIG_PVH (a KVM/QEMU PVH guest enables CONFIG_PVH without
15+ // CONFIG_XEN). CVE-2024-26816 is scoped to CONFIG_XEN_PV; on a distro kernel
16+ // CONFIG_XEN=y pulls in both. Sources: arch/x86/xen/xen-head.S,
17+ // arch/x86/platform/pvh/head.S.
1218//
1319// Also performs a generic scan of all remaining note descriptors for
1420// pointer-sized values in the kernel text virtual address range.
3238//
3339// Leak primitive:
3440// Data leaked: kernel text virtual address (startup_xen, hypercall_page)
41+ // CVE: CVE-2024-26816
3542// Kernel subsystem: arch/x86/xen — /sys/kernel/notes (ELF notes)
3643// Data structure: Xen ELF notes (XEN_ELFNOTE_ENTRY,
3744// XEN_ELFNOTE_HYPERCALL_PAGE) Address type: virtual (kernel text) Method:
38- // exact (ELF note parsing) Patched: v6.9 (commit aaa8736370db);
39- // hardened v6.13 (223abe96ac0d) Status: fixed in v6.9
40- // Access check: none (world-readable /sys/kernel/notes, 0444)
45+ // exact (ELF note parsing) Patched: v6.9 (CVE-2024-26816,
46+ // aaa8736370db); hardened v6.13 (223abe96ac0d) Status: fixed in
47+ // v6.9 Access check: none (world-readable /sys/kernel/notes, 0444)
4148// Source:
4249// https://elixir.bootlin.com/linux/v6.7.3/source/arch/x86/xen/xen-head.S#L118
4350//
4451// Mitigations:
4552// Patched in v6.9 (relocations in .notes skipped). Further hardened in
46- // v6.13 (place-relative relocations). Requires CONFIG_XEN=y.
47- // /sys/kernel/notes is world-readable (0444); no runtime sysctl
48- // can restrict access.
53+ // v6.13 (place-relative relocations). Requires CONFIG_XEN_PV and/or
54+ // CONFIG_PVH (per the notes above). /sys/kernel/notes is world-readable
55+ // (0444); no runtime sysctl can restrict access.
4956//
5057// Requires:
5158// - Readable /sys/kernel/notes
52- // - CONFIG_XEN=y (for Xen-specific notes; generic scan works without it)
59+ // - CONFIG_XEN_PV (startup_xen, hypercall_page) and/or CONFIG_PVH
60+ // (pvh_start_xen)
61+ // for the Xen notes; the generic note scan works without either
5362//
54- // Patched in v6.9-rc1~164^2~8 (aaa8736370db) — relocations in .notes section
55- // are skipped, so values no longer reflect the KASLR-adjusted addresses.
56- // Further hardened in v6.13-rc1~202^2~2 (223abe96ac0d) — Xen ELF notes use
57- // place-relative relocations to prevent leaking the KASLR base.
63+ // Patched in v6.9-rc1~164^2~8 (aaa8736370db, CVE-2024-26816) — relocations in
64+ // the .notes section are skipped, so values no longer reflect the
65+ // KASLR-adjusted addresses (they become identical to System.map). Backported to
66+ // the 2024-03-27 stable batch
67+ // (6.6.23, 6.1.83, 5.15.153, 5.10.214, 5.4.273, 4.19.311, ...), so a build's
68+ // version does not determine whether it is affected; the staleness check below
69+ // distinguishes patched from vulnerable notes at runtime. Further hardened in
70+ // v6.13-rc1~202^2~2 (223abe96ac0d) — Xen ELF notes use place-relative
71+ // relocations to prevent leaking the KASLR base.
5872//
5973// References:
6074// https://cateee.net/lkddb/web-lkddb/XEN.html
7589#include <fcntl.h>
7690#include <stdint.h>
7791#include <stdio.h>
78- #include <stdlib.h>
7992#include <string.h>
8093#include <sys/types.h>
8194#include <unistd.h>
@@ -96,8 +109,9 @@ KASLD_EXPLAIN("On Xen PV and PVH guests, /sys/kernel/notes contains ELF notes "
96109KASLD_META ("method:parsed\n"
97110 "phase:inference\n"
98111 "addr:virtual\n"
112+ "cve:CVE-2024-26816\n"
99113 "patch:v6.9\n"
100- "config:CONFIG_XEN \n" );
114+ "config:CONFIG_XEN_PV \n" );
101115
102116/* Check if /proc/kallsyms contains xen_elfnote_* global symbols,
103117 * indicating v6.13+ place-relative encoding where Xen ELF note values
@@ -127,6 +141,18 @@ static int has_xen_elfnote_symbols(void) {
127141 return 0 ;
128142}
129143
144+ /* The image base is recovered from these notes by alignment. On x86_64 _text is
145+ * 2 MiB-aligned (CONFIG_PHYSICAL_ALIGN, the KASLR step), and the PHYS32_ENTRY
146+ * note resolves to pvh_start_xen, which sits within a few KiB of _text (_text+0
147+ * .. _text+0x5f0 across 5.x/6.x). The orchestrator floors the lowest interior
148+ * sample emitted below to KASLR_VIRT_ALIGN, landing on _text.
149+ *
150+ * pvh_start_xen is the anchor for that floor. hypercall_page sits at _text +
151+ * 0x1000 on <= 5.x (inside the 2 MiB window), but 6.x moves it to .noinstr.text
152+ * ~16-19 MiB past _text, so its floor overshoots _text by ~16 MiB; startup_xen
153+ * (.init.text) is ~25-40 MiB out. All three are emitted as interior samples;
154+ * the engine bounds the base from the lowest. */
155+
130156int main (void ) {
131157 int fd ;
132158 uint32_t hdr [3 ]; /* namesz, descsz, type */
@@ -143,7 +169,7 @@ int main(void) {
143169
144170 fd = kasld_open ("/sys/kernel/notes" , O_RDONLY );
145171 if (fd < 0 ) {
146- perror ( "[-] open( /sys/kernel/notes) " );
172+ kasld_err ( " /sys/kernel/notes unavailable " );
147173 return (errno == EACCES || errno == EPERM ) ? KASLD_EXIT_NOPERM
148174 : KASLD_EXIT_UNAVAILABLE ;
149175 }
@@ -286,12 +312,13 @@ int main(void) {
286312 }
287313 if (xen_phys32 ) {
288314 /* PHYS32_ENTRY stores pvh_start_xen - __START_KERNEL_map, not a
289- * hardware physical address. On x86_64, the kernel text is mapped
290- * at __START_KERNEL_map + virt_offset, so this value IS the virtual
291- * KASLR offset. Adding __START_KERNEL_map recovers the virtual
292- * address of pvh_start_xen, which sits at or very near _stext.
293- * The hardware physical load address is independently randomized
294- * and is not recoverable from this note. */
315+ * hardware physical address. On x86_64 the kernel text is mapped at
316+ * __START_KERNEL_map + virt_offset, so this value IS the virtual KASLR
317+ * offset; adding __START_KERNEL_map recovers pvh_start_xen's virtual
318+ * address, which sits at or very near _text — the sample the
319+ * orchestrator floors to KASLR_VIRT_ALIGN to recover _text (see the
320+ * note above main()). The hardware physical load address is
321+ * independently randomized and is not recoverable from this note. */
295322 unsigned long virt = KERNEL_VIRT_TEXT_MIN + xen_phys32 ;
296323 if (kasld_addr_is_kernel_text (virt )) {
297324 kasld_found ("Xen PHYS32_ENTRY -> virtual: %lx" , virt );
@@ -308,14 +335,5 @@ int main(void) {
308335 if (!found )
309336 kasld_err ("no kernel addresses found in ELF notes" );
310337
311- // NOTE: On kernels <= 5.x, hypercall_page was in .pushsection .text
312- // (at _text + 0x1000), so addr & -KASLR_VIRT_ALIGN recovered _text exactly.
313- // In 6.x, hypercall_page moved to .pushsection .noinstr.text for
314- // instrumentation isolation. The linker places .noinstr.text after the
315- // bulk of kernel code, putting hypercall_page millions of bytes past
316- // _text. addr & -KASLR_VIRT_ALIGN then overshoots by ~16+ MiB.
317- // The orchestrator handles this correctly since it does not assume a
318- // fixed symbol-to-base offset.
319-
320338 return 0 ;
321339}
0 commit comments