|
| 1 | +// This file is part of KASLD - https://github.com/bcoles/kasld |
| 2 | +// |
| 3 | +// Rule: x86_64 direct-map KASLR-disabled pin (page_offset / vmalloc / vmemmap). |
| 4 | +// |
| 5 | +// arch/x86/mm/kaslr.c:kernel_randomize_memory() returns BEFORE touching any |
| 6 | +// base when !kaslr_memory_enabled(), and |
| 7 | +// |
| 8 | +// kaslr_memory_enabled() = kaslr_enabled() && !IS_ENABLED(CONFIG_KASAN) |
| 9 | +// |
| 10 | +// (identical in linux-6.6 … 7.0 and mainline). So page_offset_base / |
| 11 | +// vmalloc_base / vmemmap_base keep their compile-time defaults whenever KASLR |
| 12 | +// is off (SF_VIRT_KASLR_DISABLED) OR CONFIG_KASAN=y (SF_KASAN_ENABLED) — and on |
| 13 | +// x86_64 __PAGE_OFFSET / VMALLOC_START / VMEMMAP_START ARE those variables |
| 14 | +// unconditionally (page_64_types.h, pgtable_64_types.h), so the runtime base |
| 15 | +// equals the constant. The non-obvious case is KASAN with |
| 16 | +// CONFIG_RANDOMIZE_MEMORY=y: the config advertises a randomised direct map but |
| 17 | +// KASAN overrides it at runtime — common on syzkaller / CTF / debug kernels. |
| 18 | +// |
| 19 | +// On a positive disable signal, pin all three bases to the paging-level default |
| 20 | +// (L4 = VA 48, L5 = VA 57), chosen by SF_VIRT_ADDR_BITS (proc_cpuinfo's "bits |
| 21 | +// virtual", which tracks the active level — leak-free, so the pin fires without |
| 22 | +// any direct-map leak). CONFIG_RANDOMIZE_BASE is independent of the memory |
| 23 | +// randomisation, so kernel TEXT stays randomised; this pins only the |
| 24 | +// direct-map side. |
| 25 | +// |
| 26 | +// Soundness: |
| 27 | +// * Fires only on a positive disable signal AND a resolved VA width. |
| 28 | +// * The pinned values are exact, non-config-tunable kernel constants for the |
| 29 | +// resolved level, so no window-containment read is needed — and an |
| 30 | +// out-of-window C_EQUALS is dropped by the engine's meet as a conflict |
| 31 | +// anyway (so no est[Q] read, no self-edge). |
| 32 | +// * A higher-confidence real direct-map leak still wins via the resolver's |
| 33 | +// conflict handling. |
| 34 | +// --- |
| 35 | +// <bcoles@gmail.com> |
| 36 | + |
| 37 | +#include "../include/kasld/engine_rules.h" |
| 38 | + |
| 39 | +#include <string.h> |
| 40 | + |
| 41 | +int rule_directmap_kaslr_disabled_pin(const struct evidence_set *ev, |
| 42 | + const struct estimate *est, |
| 43 | + struct constraint *out, int out_max) { |
| 44 | + (void)est; |
| 45 | +#if defined(__x86_64__) || defined(__amd64__) |
| 46 | + uint32_t sig_id = 0, va_id = 0; |
| 47 | + enum kasld_confidence sig_conf = CONF_UNKNOWN, va_conf = CONF_UNKNOWN; |
| 48 | + unsigned long va_bits = 0; |
| 49 | + for (int i = 0; i < ev->n_obs; i++) { |
| 50 | + const struct observation *o = &ev->obs[i]; |
| 51 | + if (!o->valid || o->value_kind != OBS_SCALAR) |
| 52 | + continue; |
| 53 | + if ((o->scalar_fact == SF_KASAN_ENABLED || |
| 54 | + o->scalar_fact == SF_VIRT_KASLR_DISABLED) && |
| 55 | + o->scalar_value != 0) { |
| 56 | + if (sig_id == 0) { |
| 57 | + sig_id = o->id; |
| 58 | + sig_conf = o->conf; |
| 59 | + } |
| 60 | + } else if (o->scalar_fact == SF_VIRT_ADDR_BITS && o->scalar_value != 0) { |
| 61 | + va_bits = o->scalar_value; |
| 62 | + va_id = o->id; |
| 63 | + va_conf = o->conf; |
| 64 | + } |
| 65 | + } |
| 66 | + if (sig_id == 0 || va_bits == 0) |
| 67 | + return 0; |
| 68 | + |
| 69 | + /* Active paging level: 48-bit VA -> 4-level (L4), 57-bit -> 5-level (L5). */ |
| 70 | + int l5; |
| 71 | + if (va_bits <= 48) |
| 72 | + l5 = 0; |
| 73 | + else if (va_bits <= 57) |
| 74 | + l5 = 1; |
| 75 | + else |
| 76 | + return 0; /* unexpected width — don't pin. */ |
| 77 | + |
| 78 | + struct { |
| 79 | + enum kasld_quantity q; |
| 80 | + unsigned long value; |
| 81 | + } pins[3] = { |
| 82 | + {Q_PAGE_OFFSET, l5 ? PAGE_OFFSET_BASE_L5 : PAGE_OFFSET_BASE_L4}, |
| 83 | + {Q_VMALLOC_BASE, l5 ? VMALLOC_BASE_L5 : VMALLOC_BASE_L4}, |
| 84 | + {Q_VMEMMAP_BASE, l5 ? VMEMMAP_BASE_L5 : VMEMMAP_BASE_L4}, |
| 85 | + }; |
| 86 | + |
| 87 | + enum kasld_confidence conf = (sig_conf < va_conf) ? sig_conf : va_conf; |
| 88 | + int n = 0; |
| 89 | + for (int k = 0; k < 3 && n < out_max; k++) { |
| 90 | + struct constraint *c = &out[n++]; |
| 91 | + memset(c, 0, sizeof(*c)); |
| 92 | + c->q = pins[k].q; |
| 93 | + c->op = C_EQUALS; |
| 94 | + c->value = pins[k].value; |
| 95 | + c->conf = conf; |
| 96 | + c->derived_from[0] = sig_id; |
| 97 | + c->derived_from[1] = va_id; |
| 98 | + c->lineage_count = 2; |
| 99 | + snprintf(c->origin, ORIGIN_LEN, "directmap_kaslr_disabled_pin"); |
| 100 | + } |
| 101 | + return n; |
| 102 | +#else |
| 103 | + (void)ev; |
| 104 | + (void)out; |
| 105 | + (void)out_max; |
| 106 | + return 0; |
| 107 | +#endif |
| 108 | +} |
0 commit comments