-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathboot_params_e820.c
More file actions
312 lines (272 loc) · 13.3 KB
/
Copy pathboot_params_e820.c
File metadata and controls
312 lines (272 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// This file is part of KASLD - https://github.com/bcoles/kasld
//
// Read physical memory map and initrd address from
// /sys/kernel/boot_params/data.
//
// Since Linux 3.10 (commit a6b2a69a0f77), the full struct boot_params passed
// from the bootloader is exposed as a 4096-byte binary sysfs file:
//
// /sys/kernel/boot_params/data (0444 — world-readable, no config gate)
//
// The interface is implemented in arch/x86/kernel/ksysfs.c and compiled
// unconditionally for x86/x86_64. Two regions of the struct yield physical
// addresses invisible to plain-text interfaces:
//
// 1. E820 physical memory map (BIOS-provided firmware memory table):
// e820_entries u8 @ boot_params+0x1e8 — number of entries
// e820_table[] @ boot_params+0x2d0 — array of boot_e820_entry
// Each 20-byte entry: { u64 addr; u64 size; u32 type; } __packed
// type 1 (E820_TYPE_RAM) entries are usable DRAM.
//
// 2. initrd (ramdisk) physical address set by the bootloader:
// hdr.ramdisk_image u32 LE @ boot_params+0x218 — low 32 bits of phys
// ext_ramdisk_image u32 LE @ boot_params+0x0c0 — high 32 bits of phys
// hdr.ramdisk_size u32 LE @ boot_params+0x21c — low 32 bits of size
// ext_ramdisk_size u32 LE @ boot_params+0x0c4 — high 32 bits of size
//
// This component provides the same E820 data as dmesg_e820_memory_map.c but
// without requiring dmesg access (works when dmesg_restrict=1). The initrd
// physical address is the x86 counterpart to sysfs_devicetree_initrd.c
// (which serves ARM/RISC-V device tree platforms).
//
// Leak primitive:
// Data leaked: physical memory map (E820 BIOS table) + initrd address
// Kernel subsystem: arch/x86/kernel — ksysfs.c (boot_params_data_attr)
// Data structure: struct boot_params / struct boot_e820_entry
// Address type: physical (DRAM)
// Method: parsed (binary sysfs attribute, struct boot_params)
// Status: unfixed (information exposure by design)
// Access check: none (S_IRUGO — world-readable, no sysctl gate)
// Source:
// https://elixir.bootlin.com/linux/v6.12/source/arch/x86/kernel/ksysfs.c#L30
//
// Mitigations:
// No kernel runtime sysctl restricts access to this file. The file is
// unconditionally present on x86/x86_64 (ksysfs.c is always compiled).
// On x86-64 (TEXT_TRACKS_DIRECTMAP=0), physical addresses cannot be used
// to derive the virtual kernel text base directly.
//
// Requires:
// x86 or x86_64 architecture (boot_params is x86-specific)
//
// References:
// https://elixir.bootlin.com/linux/v6.12/source/arch/x86/kernel/ksysfs.c
// https://elixir.bootlin.com/linux/v6.12/source/arch/x86/include/uapi/asm/bootparam.h
// https://elixir.bootlin.com/linux/v6.12/source/arch/x86/include/uapi/asm/setup_data.h
// https://www.kernel.org/doc/html/latest/arch/x86/boot.html
// ---
// <bcoles@gmail.com>
#define _POSIX_C_SOURCE 200809L
#include "include/kasld/api.h"
#include "include/kasld/cli.h"
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#if !defined(__i386__) && !defined(__x86_64__) && !defined(__amd64__)
#error "Architecture is not supported"
#endif
KASLD_EXPLAIN(
"Reads the x86 E820 physical memory map and initrd physical address "
"directly from /sys/kernel/boot_params/data - a world-readable "
"(0444) 4096-byte binary sysfs file exposing the full struct "
"boot_params passed from the bootloader. No dmesg access is "
"required. The E820 table yields physical DRAM bounds; the "
"ramdisk_image field yields the initrd load address in DRAM. "
"x86/x86_64 only; always present (no CONFIG gate).");
KASLD_META("method:parsed\n"
"phase:inference\n"
"discloses:physical\n");
/* Path and size of the boot_params sysfs binary attribute. */
#define BOOT_PARAMS_PATH "/sys/kernel/boot_params/data"
#define BOOT_PARAMS_SIZE 4096u
/* boot_params field offsets (absolute byte positions, x86 boot protocol).
* setup_header starts at boot_params+0x1f1; e820_table is after the header. */
#define OFF_EXT_RAMDISK_IMAGE \
0x0c0ul /* u32 LE: high 32 bits of initrd phys addr */
#define OFF_EXT_RAMDISK_SIZE \
0x0c4ul /* u32 LE: high 32 bits of initrd byte count */
#define OFF_E820_ENTRIES 0x1e8ul /* u8: number of populated E820 entries */
#define OFF_RAMDISK_IMAGE \
0x218ul /* u32 LE: low 32 bits of initrd phys addr \
*/
#define OFF_RAMDISK_SIZE \
0x21cul /* u32 LE: low 32 bits of initrd byte count \
*/
#define OFF_E820_TABLE 0x2d0ul /* boot_e820_entry[128]: E820 memory map */
/* E820 region types. RAM is where the kernel image loads; ACPI data / ACPI NVS
* are firmware-reserved DRAM the image provably cannot occupy (the boot KASLR
* code places the image only in E820_TYPE_RAM). */
#define E820_TYPE_RAM 1u
#define E820_TYPE_ACPI 3u /* "ACPI Tables" — reclaimable ACPI data */
#define E820_TYPE_NVS 4u /* "ACPI Non-volatile Storage" */
/* struct boot_e820_entry { u64 addr; u64 size; u32 type; } __packed;
* Byte offsets within each 20-byte table entry: */
#define E820_BYTES_PER_ENTRY 20u
#define E820_OFF_ADDR 0u /* u64 LE: physical start address */
#define E820_OFF_SIZE 8u /* u64 LE: region byte count */
#define E820_OFF_TYPE 16u /* u32 LE: E820 memory type */
/* Maximum entries in the zero-page E820 table (E820_MAX_ENTRIES_ZEROPAGE). */
#define E820_MAX_ENTRIES 128u
static inline uint32_t read_le32(const uint8_t *p) {
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) |
((uint32_t)p[3] << 24);
}
static inline uint64_t read_le64(const uint8_t *p) {
return (uint64_t)read_le32(p) | ((uint64_t)read_le32(p + 4) << 32);
}
/* Emit an ACPI data / NVS E820 entry as a forbidden physical band so
* phys_reservation_exclude carves it from the candidate base set (the image
* never loads outside E820_TYPE_RAM). Skipped when: size is zero or one byte;
* the band is not representable in unsigned long (32-bit / PAE truncation would
* corrupt it); or it lies entirely below KASLR_PHYS_MIN — the image is never
* that low, so such a band excludes nothing and is the only one that could
* perturb a memtotal-derived DRAM floor. ACPI regions are firmware-reserved
* DRAM at high addresses, so this never lowers a DRAM floor below the true RAM
* base. A forbidden band, not a RAM-map member: range, never a covering. */
static void emit_acpi_band(uint32_t type, uint64_t start, uint64_t size) {
if (size == 0)
return;
uint64_t end = start + size - 1; /* inclusive last byte */
if ((unsigned long)start != start || (unsigned long)end != end)
return;
/* `<=` not `<`: a band ending at-or-below the floor is equally useless to
* carve, and `<=` avoids a -Wtype-limits tautology on arch headers where
* KASLR_PHYS_MIN folds to 0 (x86_32). */
if (end <= start || end <= (uint64_t)KASLR_PHYS_MIN)
return;
enum kasld_region region =
(type == E820_TYPE_NVS) ? REGION_ACPI_NVS : REGION_ACPI_TABLE;
kasld_result_range(KASLD_TYPE_PHYS, region, (unsigned long)start,
(unsigned long)end, NULL, CONF_PARSED);
}
int main(void) {
static uint8_t buf[BOOT_PARAMS_SIZE];
kasld_info("reading E820 memory map and initrd address from " BOOT_PARAMS_PATH
" ...");
int fd = kasld_open(BOOT_PARAMS_PATH, O_RDONLY);
if (fd < 0) {
int saved_errno = errno;
perror("[-] open " BOOT_PARAMS_PATH);
return (saved_errno == EACCES || saved_errno == EPERM)
? KASLD_EXIT_NOPERM
: KASLD_EXIT_UNAVAILABLE;
}
ssize_t n = pread(fd, buf, sizeof(buf), 0);
close(fd);
if (n != (ssize_t)sizeof(buf)) {
kasld_err("short read from " BOOT_PARAMS_PATH " (%zd of %u bytes)", n,
BOOT_PARAMS_SIZE);
return KASLD_EXIT_UNAVAILABLE;
}
/* ------------------------------------------------------------------ */
/* E820 physical memory map */
/* ------------------------------------------------------------------ */
uint8_t e820_entries = buf[OFF_E820_ENTRIES];
if (e820_entries > E820_MAX_ENTRIES)
e820_entries = E820_MAX_ENTRIES;
if (e820_entries == 0) {
kasld_err("E820 table is empty");
} else {
unsigned long lo = ~0ul;
unsigned long hi = 0;
unsigned int ram_count = 0;
int covering_ok = 1; /* clears if any RAM entry exceeds unsigned long */
for (unsigned int i = 0; i < (unsigned int)e820_entries; i++) {
const uint8_t *entry = buf + OFF_E820_TABLE + i * E820_BYTES_PER_ENTRY;
uint32_t type = read_le32(entry + E820_OFF_TYPE);
if (type == E820_TYPE_ACPI || type == E820_TYPE_NVS) {
emit_acpi_band(type, read_le64(entry + E820_OFF_ADDR),
read_le64(entry + E820_OFF_SIZE));
continue;
}
if (type != E820_TYPE_RAM)
continue;
uint64_t start = read_le64(entry + E820_OFF_ADDR);
uint64_t size = read_le64(entry + E820_OFF_SIZE);
if (size == 0)
continue;
uint64_t end = start + size - 1; /* inclusive last byte */
/* A covering extent silently truncated to unsigned long (32-bit / PAE)
* would fabricate a false gap; if any RAM entry would truncate, suppress
* the covering entirely (an incomplete map is worse than none). */
if ((unsigned long)start != start || (unsigned long)end != end)
covering_ok = 0;
kasld_info("E820 RAM: 0x%016llx - 0x%016llx", (unsigned long long)start,
(unsigned long long)end);
/* Skip physical address 0: trivially known, no KASLR information. */
if (start != 0 && (unsigned long)start < lo)
lo = (unsigned long)start;
if ((unsigned long)end > hi)
hi = (unsigned long)end;
ram_count++;
}
if (ram_count == 0) {
kasld_err("no E820 RAM entries found");
} else {
if (lo != ~0ul) {
kasld_found("leaked E820 DRAM low: 0x%016lx", lo);
kasld_result_base(KASLD_TYPE_PHYS, REGION_RAM, lo, NULL, CONF_PARSED);
}
if (hi) {
kasld_found("leaked E820 DRAM high: 0x%016lx", hi);
kasld_result_top(KASLD_TYPE_PHYS, REGION_RAM, hi, NULL, CONF_PARSED);
}
/* Emit the whole RAM map as a covering: each E820 type-RAM entry is one
* RAM extent, and the gaps between a source's extents are non-RAM the
* kernel image cannot occupy (ram_map_phys_exclude /
* firmware_memmap_holes carve them). boot_params holds the complete,
* authoritative E820 table — the zero-page is hard-capped at
* E820_MAX_ENTRIES, so it is never a partial leak — making this a sound
* covering, and the only one available when CONFIG_FIRMWARE_MEMMAP=n
* hides /sys/firmware/memmap. Every RAM entry must be emitted (including
* any at address 0): a skipped extent would synthesize a false gap. The
* covering goes out-of-band into coverings[] (pos=extent), complementing
* the base/top envelope above. */
if (covering_ok) {
for (unsigned int i = 0; i < (unsigned int)e820_entries; i++) {
const uint8_t *e = buf + OFF_E820_TABLE + i * E820_BYTES_PER_ENTRY;
if (read_le32(e + E820_OFF_TYPE) != E820_TYPE_RAM)
continue;
uint64_t start = read_le64(e + E820_OFF_ADDR);
uint64_t size = read_le64(e + E820_OFF_SIZE);
if (size == 0)
continue;
kasld_result_extent(KASLD_TYPE_PHYS, REGION_RAM, (unsigned long)start,
(unsigned long)(start + size - 1), NULL,
CONF_PARSED);
}
}
}
}
/* ------------------------------------------------------------------ */
/* initrd (ramdisk) physical address */
/* ------------------------------------------------------------------ */
/* Combine low and high 32-bit halves into a 64-bit physical address.
* ext_ramdisk_image (high bits) is non-zero only when the initrd is
* placed above 4 GiB — uncommon but possible on large-RAM systems. */
uint32_t lo_img = read_le32(buf + OFF_RAMDISK_IMAGE);
uint32_t hi_img = read_le32(buf + OFF_EXT_RAMDISK_IMAGE);
uint32_t lo_sz = read_le32(buf + OFF_RAMDISK_SIZE);
uint32_t hi_sz = read_le32(buf + OFF_EXT_RAMDISK_SIZE);
/* Combine 32-bit halves into 64-bit values using uint64_t arithmetic.
* Casting hi_img to unsigned long before shifting would be UB on i386
* (shift count equals the type width). */
uint64_t initrd_start = ((uint64_t)hi_img << 32) | lo_img;
uint64_t initrd_size = ((uint64_t)hi_sz << 32) | lo_sz;
if (!initrd_start || !initrd_size) {
kasld_err("no initrd found in boot_params");
return 0;
}
uint64_t initrd_end = initrd_start + initrd_size - 1;
kasld_found("leaked initrd physical start: 0x%016llx",
(unsigned long long)initrd_start);
kasld_found("leaked initrd physical end: 0x%016llx",
(unsigned long long)initrd_end);
kasld_result_range(KASLD_TYPE_PHYS, REGION_INITRD,
(unsigned long)initrd_start, (unsigned long)initrd_end,
NULL, CONF_PARSED);
return 0;
}