|
| 1 | +// This file is part of KASLD - https://github.com/bcoles/kasld |
| 2 | +// |
| 3 | +// Inference plugin: CONFIG_RANDOMIZE_BASE_MAX_OFFSET ceiling (PRE_COLLECTION) |
| 4 | +// |
| 5 | +// On LoongArch and MIPS, the KASLR code masks the random offset: |
| 6 | +// |
| 7 | +// offset &= CONFIG_RANDOMIZE_BASE_MAX_OFFSET - 1; |
| 8 | +// |
| 9 | +// so the kernel text base falls in [KASLR_BASE_MIN, KASLR_BASE_MIN + |
| 10 | +// max_offset). Reading this option from the boot config tightens text_base_max: |
| 11 | +// |
| 12 | +// text_base_max = min(text_base_max, ctx->text_base_min + max_offset) |
| 13 | +// |
| 14 | +// LoongArch default: CONFIG_RANDOMIZE_BASE_MAX_OFFSET = 0x01000000 (16 MiB). |
| 15 | +// Default KASLR window without this plugin: 4 GiB (0x100000000 / KERNEL_ALIGN). |
| 16 | +// With 16 MiB max_offset: 256 slots (256 × 64 KiB). A 256× reduction. |
| 17 | +// |
| 18 | +// MIPS: same masking pattern; CONFIG_RANDOMIZE_BASE_MAX_OFFSET default is |
| 19 | +// arch/mips/Kconfig-defined. MIPS64 KASLR is not deployed in production |
| 20 | +// (MIPS H6 confirmed), so LoongArch is the primary target in practice. |
| 21 | +// |
| 22 | +// If the config file is unreadable or the option is absent, this plugin is a |
| 23 | +// no-op. The plugin is naturally a no-op on architectures that do not set |
| 24 | +// CONFIG_RANDOMIZE_BASE_MAX_OFFSET (x86, arm64, riscv64, s390). |
| 25 | +// |
| 26 | +// Phase: PRE_COLLECTION — runs before any component. |
| 27 | +// --- |
| 28 | +// <bcoles@gmail.com> |
| 29 | + |
| 30 | +#define _POSIX_C_SOURCE 200809L |
| 31 | + |
| 32 | +#include "../include/kasld_inference.h" |
| 33 | + |
| 34 | +#include <stdio.h> |
| 35 | +#include <stdlib.h> |
| 36 | +#include <string.h> |
| 37 | +#include <sys/utsname.h> |
| 38 | + |
| 39 | +/* Open the kernel config file at well-known paths, trying the release-specific |
| 40 | + * path before generic fallbacks. Returns an open FILE* or NULL. |
| 41 | + * Mirrors the search order used by boot-config.c. */ |
| 42 | +static FILE *open_boot_config(const char *release) { |
| 43 | + const char *fixed_paths[] = {"/boot/config", NULL}; |
| 44 | + |
| 45 | + for (int i = 0; fixed_paths[i]; i++) { |
| 46 | + FILE *fp = fopen(fixed_paths[i], "r"); |
| 47 | + if (fp) |
| 48 | + return fp; |
| 49 | + } |
| 50 | + |
| 51 | + const char *release_fmts[] = { |
| 52 | + "/boot/config-%s", |
| 53 | + "/lib/modules/%s/build/.config", |
| 54 | + "/lib/modules/%s/config", |
| 55 | + NULL, |
| 56 | + }; |
| 57 | + |
| 58 | + char path[256]; |
| 59 | + for (int i = 0; release_fmts[i]; i++) { |
| 60 | + snprintf(path, sizeof(path), release_fmts[i], release); |
| 61 | + FILE *fp = fopen(path, "r"); |
| 62 | + if (fp) |
| 63 | + return fp; |
| 64 | + } |
| 65 | + |
| 66 | + return NULL; |
| 67 | +} |
| 68 | + |
| 69 | +/* Parse CONFIG_RANDOMIZE_BASE_MAX_OFFSET=<hex> from an open config FILE*. |
| 70 | + * Returns the value, or 0 if absent or malformed. */ |
| 71 | +static unsigned long get_max_offset(FILE *fp) { |
| 72 | + const char *key = "CONFIG_RANDOMIZE_BASE_MAX_OFFSET="; |
| 73 | + const size_t keylen = strlen(key); |
| 74 | + char buf[256]; |
| 75 | + |
| 76 | + rewind(fp); |
| 77 | + while (fgets(buf, sizeof(buf), fp) != NULL) { |
| 78 | + if (strncmp(buf, key, keylen) == 0) { |
| 79 | + char *end; |
| 80 | + unsigned long val = strtoul(buf + keylen, &end, 0); |
| 81 | + if (end != buf + keylen && val > 0) |
| 82 | + return val; |
| 83 | + } |
| 84 | + } |
| 85 | + return 0; |
| 86 | +} |
| 87 | + |
| 88 | +static void config_max_offset_bound_run(struct kasld_analysis_ctx *ctx) { |
| 89 | + struct utsname uts; |
| 90 | + if (uname(&uts) != 0) |
| 91 | + return; |
| 92 | + |
| 93 | + FILE *fp = open_boot_config(uts.release); |
| 94 | + if (!fp) |
| 95 | + return; |
| 96 | + |
| 97 | + unsigned long max_offset = get_max_offset(fp); |
| 98 | + fclose(fp); |
| 99 | + |
| 100 | + if (max_offset == 0) |
| 101 | + return; |
| 102 | + |
| 103 | + /* At PRE_COLLECTION, ctx->text_base_min == KASLR_BASE_MIN (the first valid |
| 104 | + * slot). The KASLR offset is drawn from [0, max_offset), so the last valid |
| 105 | + * text base is KASLR_BASE_MIN + max_offset - kaslr_align, and the exclusive |
| 106 | + * upper bound is KASLR_BASE_MIN + max_offset. */ |
| 107 | + unsigned long new_max = ctx->text_base_min + max_offset; |
| 108 | + |
| 109 | + if (new_max > ctx->text_base_min && new_max < ctx->text_base_max) { |
| 110 | + if (verbose && !quiet) |
| 111 | + fprintf(stderr, |
| 112 | + "[layout] text_base_max tightened by config_max_offset_bound:" |
| 113 | + " %#lx -> %#lx (CONFIG_RANDOMIZE_BASE_MAX_OFFSET=%#lx)\n", |
| 114 | + ctx->text_base_max, new_max, max_offset); |
| 115 | + ctx->text_base_max = new_max; |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +static const struct kasld_inference config_max_offset_bound = { |
| 120 | + .name = "config_max_offset_bound", |
| 121 | + .phase = KASLD_INFER_PHASE_PRE_COLLECTION, |
| 122 | + .run = config_max_offset_bound_run, |
| 123 | +}; |
| 124 | + |
| 125 | +KASLD_REGISTER_INFERENCE(config_max_offset_bound); |
0 commit comments