-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathdmesg_free_area_init_node.c
More file actions
166 lines (147 loc) · 6.05 KB
/
Copy pathdmesg_free_area_init_node.c
File metadata and controls
166 lines (147 loc) · 6.05 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
// This file is part of KASLD - https://github.com/bcoles/kasld
//
// Extracts physical memory addresses from mm_init boot messages in dmesg.
// Three related message groups are scanned, all from mm/mm_init.c:
//
// Zone ranges:
// DMA [mem 0x0000000000001000-0x0000000000ffffff]
// Normal [mem 0x0000000100000000-0x000000087fffffff]
//
// Early memory node ranges
// node 0: [mem 0x0000000000001000-0x000000000009ffff]
// node 0: [mem 0x0000000000100000-0x000000087e7fffff]
//
// Initmem setup node 0 [mem 0x0000000000001000-0x000000087fffffff]
//
// All three share the same [mem 0x...-0x...] format and provide
// physical DRAM range information.
//
// On systems with a known phys->virt offset mapping, the lowest
// address may be used to identify the kernel direct-map base.
//
// Leak primitive:
// Data leaked: physical DRAM address ranges (zone/node boundaries)
// Kernel subsystem: mm/mm_init — zone/node initialization messages
// Data structure: zone ranges, node ranges, initmem setup (physical)
// Address type: physical (DRAM)
// Method: parsed (dmesg string)
// Status: unfixed (printed unconditionally during boot)
// Access check: do_syslog() → check_syslog_permissions(); gated by
// dmesg_restrict
// Source:
// https://elixir.bootlin.com/linux/v6.1.1/source/mm/mm_init.c
//
// Mitigations:
// Access gated by dmesg_restrict (see dmesg.h for shared access gate
// details). Messages printed unconditionally. On decoupled architectures,
// physical addresses cannot derive the virtual text base.
//
// Requires:
// - kernel.dmesg_restrict = 0; or CAP_SYSLOG capabilities; or
// readable /var/log/dmesg.
//
// References:
// https://elixir.bootlin.com/linux/v6.1.1/source/mm/page_alloc.c#L7927
// https://elixir.bootlin.com/linux/v6.1.1/source/mm/mm_init.c
// https://www.kernel.org/doc/html/v5.3/vm/memory-model.html
// ---
// <bcoles@gmail.com>
#define _GNU_SOURCE
#include "include/dmesg.h"
#include "include/kasld/api.h"
#include "include/kasld/cli.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define range_ctx addr_range
KASLD_EXPLAIN(
"Extracts physical DRAM address ranges from mm_init zone setup "
"messages in dmesg. Messages like 'Zone ranges', 'early memory "
"node ranges', and '[mem ...]' report physical address boundaries "
"for each NUMA node. Access is gated by dmesg_restrict.");
KASLD_META("method:parsed\n"
"phase:inference\n"
"discloses:physical\n"
"sysctl:dmesg_restrict>=1\n"
"bypass:CAP_SYSLOG\n"
"fallback:/var/log/dmesg\n");
/* Parse "[mem 0x<start>-0x<end>]" and update lo/hi.
*
* Distinguishes parse failure from a real value of 0 via endptr: phys 0 is a
* legitimate "Initmem setup node 0 [mem 0x0-...]" lower edge on systems where
* RAM starts at the bottom of the address space. Rejecting it would drop the
* node/initmem ranges and leave only the higher zone lines (e.g. HighMem on
* ppc32 starting at 0x30000000), producing an unsound non-zero floor. */
static int on_mem_range(const char *line, void *ctx) {
struct range_ctx *r = ctx;
const char *p = strstr(line, "[mem ");
if (!p)
return 1;
const char *sp = p + 5;
const char *endptr;
unsigned long start;
if (!kasld_addr_parse(sp, 16, &start, &endptr)) {
if (kasld_addr_refused_wide(sp, endptr))
r->incomplete = 1;
return 1;
}
if (*endptr != '-')
return 1; /* genuine parse failure (no hex digits or missing '-') */
const char *ep = endptr + 1;
unsigned long end;
if (!kasld_addr_parse(ep, 16, &end, &endptr)) {
if (kasld_addr_refused_wide(ep, endptr))
r->incomplete = 1;
return 1;
}
if (end <= start)
return 1; /* genuine parse failure or zero-length range */
/* r->hi is the uninitialized sentinel (a valid range always has end > 0);
* r->lo doubles as a stored value, so checking r->lo == 0 would conflate
* "no range seen yet" with "lowest range starts at phys 0". */
if (r->hi == 0 || start < r->lo)
r->lo = start;
if (end > r->hi)
r->hi = end;
return 1; /* continue searching */
}
int main(void) {
struct range_ctx r = {0, 0, 0};
kasld_info("searching dmesg for mm_init physical memory info ...");
/* All three needles hit lines with the same [mem 0x...-0x...] format */
int ds = dmesg_search("Initmem setup node ", on_mem_range, &r);
if (ds < 0)
return KASLD_EXIT_NOPERM;
dmesg_search(" node ", on_mem_range, &r);
/* Zone lines: " DMA ", " DMA32 ", " Normal ", " HighMem " */
dmesg_search(" DMA", on_mem_range, &r);
dmesg_search(" Normal ", on_mem_range, &r);
dmesg_search(" HighMem ", on_mem_range, &r);
if (r.hi == 0) {
/* r.hi == 0 is the "no valid range seen" sentinel — see on_mem_range. */
kasld_err("no physical memory ranges found in dmesg");
return 0;
}
/* dmesg's zone/node ranges describe USER-ALLOCATABLE memory: the bottom
* of the lowest published zone is NOT necessarily the bottom of physical
* RAM. On systems where firmware reserves the low-phys range for the
* kernel image (e.g. ppc32 PowerMac with the kernel at phys 0 and dmesg
* zones starting at 0x30000000), treating r.lo as POS_BASE would feed
* dram_floor_bound a bogus high floor and exclude the actual text base.
* Emit as an interior SAMPLE — still a sound RAM witness, but not a
* floor pin. Authoritative floors come from sysfs_devicetree_memory and
* peer components that read the full memory map. r.hi IS sound as a TOP
* bound (the highest published zone end ≤ true top of RAM). */
kasld_info("lowest physical address: 0x%016lx", r.lo);
kasld_result_sample(KASLD_TYPE_PHYS, REGION_RAM, r.lo, NULL, CONF_PARSED);
if (r.hi && r.hi != r.lo && !r.incomplete) {
kasld_info("highest physical address: 0x%016lx", r.hi);
kasld_result_top(KASLD_TYPE_PHYS, REGION_RAM, r.hi, NULL, CONF_PARSED);
} else if (r.incomplete) {
kasld_err("a zone range is wider than this build's word; "
"DRAM ceiling suppressed");
}
return 0;
}