-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathsysfs_devicetree_elfcorehdr.c
More file actions
206 lines (195 loc) · 8.42 KB
/
Copy pathsysfs_devicetree_elfcorehdr.c
File metadata and controls
206 lines (195 loc) · 8.42 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
// This file is part of KASLD - https://github.com/bcoles/kasld
//
// Read physical addresses from the device tree "chosen" node properties
// that are set by the main kernel when loading a kdump crash kernel:
//
// linux,elfcorehdr — physical address and size of the ELF core header
// linux,usable-memory-range — physical memory ranges usable by crash kernel
//
// When a kdump-capable kernel loads a crash kernel via kexec -p, it records
// these physical addresses in the device tree passed to the crash kernel.
// The crash kernel (secondary kernel) then finds them at boot via the DT
// chosen node, which is exposed as:
//
// /sys/firmware/devicetree/base/chosen/linux,elfcorehdr (0444)
// /sys/firmware/devicetree/base/chosen/linux,usable-memory-range (0444)
//
// linux,elfcorehdr contains two big-endian u64 values: (address, size).
// The address is the physical location of the ELF core header that
// describes the crashed system's memory layout for makedumpfile/crash.
//
// linux,usable-memory-range contains one or more (base, size) u64 pairs
// describing the physical memory ranges the crash kernel may use.
// These are DRAM ranges the crash kernel is capped to and that its own
// image occupies, so they are emitted as REGION_RAM (image-occupiable),
// NOT as a forbidden reservation.
//
// All device tree sysfs properties are world-readable (0444); no capability
// check is performed. These properties are NOT sanitized after boot (unlike
// kaslr-seed and rng-seed, which are zeroed after reading).
//
// This component is only useful when:
// 1. Running on a device-tree platform (ARM64, RISC-V, MIPS, PowerPC)
// 2. Running as the kdump crash kernel (not the primary kernel)
// 3. kdump is configured and a crash occurred
//
// Leak primitive:
// Data leaked: physical DRAM addresses (ELF core header, crash kernel
// usable memory ranges)
// Kernel subsystem: drivers/of — /sys/firmware/devicetree/base/chosen/
// Data structure: device tree chosen node
// (linux,elfcorehdr / linux,usable-memory-range)
// Address type: physical (DRAM)
// Method: parsed (binary sysfs property)
// Status: unfixed (information exposure by design; crash kernel
// context only)
// Access check: none (world-readable sysfs attribute, 0444)
// Source:
// https://elixir.bootlin.com/linux/v6.12/source/arch/arm64/mm/init.c#L84
//
// Mitigations:
// CONFIG_OF=n removes device tree sysfs entirely. These properties only
// exist in the crash kernel's device tree; they are absent from the
// primary kernel's DT. On architectures with decoupled KASLR, physical
// addresses cannot derive the virtual text base.
//
// Requires:
// - CONFIG_OF (device tree support — ARM64, RISC-V, MIPS, PowerPC)
// - CONFIG_KEXEC_CORE / CONFIG_CRASH_DUMP
// - Running as the kdump crash kernel after a system crash
//
// References:
// https://elixir.bootlin.com/linux/v6.12/source/arch/arm64/mm/init.c#L84
// https://elixir.bootlin.com/linux/v6.12/source/arch/riscv/mm/init.c
// https://www.kernel.org/doc/Documentation/kdump/kdump.txt
// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-firmware-ofw
// ---
// <bcoles@gmail.com>
#include "include/kasld/api.h"
#include "include/kasld/cli.h"
#include "include/kasld/devicetree.h"
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
KASLD_EXPLAIN(
"Reads physical DRAM addresses from device tree chosen node properties "
"set by the primary kernel when loading a kdump crash kernel: "
"linux,elfcorehdr (physical address + size of ELF core header) and "
"linux,usable-memory-range (usable DRAM ranges for the crash kernel). "
"All DT sysfs properties are world-readable (0444). These properties "
"only exist in the crash kernel's device tree on DT platforms (ARM64, "
"RISC-V) after a system crash with kdump configured.");
KASLD_META("method:parsed\n"
"phase:inference\n"
"discloses:physical\n"
"config:CONFIG_OF\n"
"config:CONFIG_CRASH_DUMP\n");
/* Read a big-endian 64-bit value from raw bytes. */
static uint64_t read_be64(const unsigned char *p) {
return ((uint64_t)p[0] << 56) | ((uint64_t)p[1] << 48) |
((uint64_t)p[2] << 40) | ((uint64_t)p[3] << 32) |
((uint64_t)p[4] << 24) | ((uint64_t)p[5] << 16) |
((uint64_t)p[6] << 8) | (uint64_t)p[7];
}
int main(void) {
const char *bases[] = {"/sys/firmware/devicetree/base/chosen",
"/proc/device-tree/chosen", NULL};
const char *chosen = NULL;
char path[512];
unsigned char buf[256];
int n;
int count = 0;
/* Find the chosen node. A property that exists but is hidden is a different
* fact from one that is absent; keep it so the verdict can say which. */
int dt_denied = 0;
for (int i = 0; bases[i]; i++) {
snprintf(path, sizeof(path), "%s/linux,elfcorehdr", bases[i]);
FILE *f = kasld_fopen(path, "rb");
if (f) {
fclose(f);
chosen = bases[i];
break;
}
if (errno == EACCES || errno == EPERM)
dt_denied = 1;
/* Also check for linux,usable-memory-range as fallback probe */
snprintf(path, sizeof(path), "%s/linux,usable-memory-range", bases[i]);
f = kasld_fopen(path, "rb");
if (f) {
fclose(f);
chosen = bases[i];
break;
}
if (errno == EACCES || errno == EPERM)
dt_denied = 1;
}
if (!chosen) {
kasld_err(
"device tree chosen node not found or no kdump crash kernel "
"properties (linux,elfcorehdr / linux,usable-memory-range) present\n"
" (this component only works in the kdump crash kernel context)");
return dt_denied ? KASLD_EXIT_NOPERM : KASLD_EXIT_UNAVAILABLE;
}
/* --- linux,elfcorehdr: <u64 address> <u64 size> --- */
snprintf(path, sizeof(path), "%s/linux,elfcorehdr", chosen);
n = kasld_dt_read_blob(path, buf, sizeof(buf));
if (n >= 16) {
uint64_t ehdr_addr = read_be64(buf);
uint64_t ehdr_size = read_be64(buf + 8);
if (ehdr_addr) {
kasld_info("linux,elfcorehdr address: 0x%016llx size: 0x%llx",
(unsigned long long)ehdr_addr, (unsigned long long)ehdr_size);
if (ehdr_size) {
kasld_result_sized(KASLD_TYPE_PHYS, REGION_CRASHKERNEL,
(unsigned long)ehdr_addr, (unsigned long)ehdr_size,
"elfcorehdr", CONF_PARSED);
} else {
kasld_result_sample(KASLD_TYPE_PHYS, REGION_CRASHKERNEL,
(unsigned long)ehdr_addr, "elfcorehdr",
CONF_PARSED);
}
count++;
}
} else if (n > 0) {
kasld_err("linux,elfcorehdr: expected >= 16 bytes, got %d", n);
}
/* --- linux,usable-memory-range: array of <u64 base> <u64 size> pairs --- */
snprintf(path, sizeof(path), "%s/linux,usable-memory-range", chosen);
n = kasld_dt_read_blob(path, buf, sizeof(buf));
if (n >= 16) {
int npairs = n / 16;
for (int i = 0; i < npairs && i * 16 + 15 < n; i++) {
uint64_t base = read_be64(buf + i * 16);
uint64_t size = read_be64(buf + i * 16 + 8);
if (!base)
continue;
kasld_info("linux,usable-memory-range[%d]: base=0x%016llx size=0x%llx",
i, (unsigned long long)base, (unsigned long long)size);
/* usable-memory-range is the DRAM the crash kernel is capped to
* (memblock_cap_memory_range) — the RAM the running (crash) kernel image
* itself OCCUPIES, so it is REGION_RAM, NOT a forbidden reservation. The
* crash kernel's physical base lies inside this range; tagging it
* REGION_CRASHKERNEL (an is_phys_kernel_forbidden_region) would make
* phys_reservation_exclude carve out the true base. (linux,elfcorehdr
* above is a genuine memblock_reserve the image won't overlap, so it
* stays REGION_CRASHKERNEL.) */
if (size) {
kasld_result_sized(KASLD_TYPE_PHYS, REGION_RAM, (unsigned long)base,
(unsigned long)size, "usable-memory", CONF_PARSED);
} else {
kasld_result_sample(KASLD_TYPE_PHYS, REGION_RAM, (unsigned long)base,
"usable-memory", CONF_PARSED);
}
count++;
}
} else if (n > 0) {
kasld_err("linux,usable-memory-range: expected >= 16 bytes, got %d", n);
}
if (!count) {
kasld_err("no physical addresses found in crash kernel DT chosen node");
return KASLD_EXIT_UNAVAILABLE;
}
return 0;
}