Skip to content

Commit 52d7cb7

Browse files
committed
config_max_offset_bound: new PRE_COLLECTION plugin for KASLR window ceiling
Reads CONFIG_RANDOMIZE_BASE_MAX_OFFSET from the boot config and sets text_base_max = KASLR_BASE_MIN + max_offset. Reduces the default 4 GiB LoongArch search window to 256 slots (16 MiB / 64 KB alignment).
1 parent 1a07759 commit 52d7cb7

1 file changed

Lines changed: 125 additions & 0 deletions

File tree

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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

Comments
 (0)