-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathsysfs_iommu_reserved_regions.c
More file actions
225 lines (203 loc) · 8.55 KB
/
Copy pathsysfs_iommu_reserved_regions.c
File metadata and controls
225 lines (203 loc) · 8.55 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
// This file is part of KASLD - https://github.com/bcoles/kasld
//
// Read physical DRAM addresses from IOMMU group reserved regions.
//
// When an IOMMU is active (Intel VT-d or AMD-Vi), the kernel exposes
// the reserved memory regions for each IOMMU group at:
//
// /sys/kernel/iommu_groups/N/reserved_regions (0444 — world-readable)
//
// Each line describes a physical address range and its type:
//
// 0x<start_16hex> 0x<end_16hex> <type>
//
// Types are lowercase: "direct", "direct-relaxable", "reserved", "msi"
// (from iommu_group_resv_type_string[] in drivers/iommu/iommu.c;
// IOMMU_RESV_SW_MSI also renders as "msi").
//
// On Intel VT-d systems with Reserved Memory Region Reporting (RMRR),
// firmware (via the DMAR ACPI table) declares physical DRAM ranges that
// hardware — USB 2.0 controllers, integrated GPUs, Intel ME — require for
// DMA. The VT-d driver fetches these from the DMAR table
// (intel_iommu_get_resv_regions → iommu_alloc_resv_region) and registers
// them as IOMMU_RESV_RESERVED ("reserved") entries. These are physical DRAM
// addresses, not MMIO: a DMA region must be in system RAM.
//
// "direct" entries (IOMMU_RESV_DIRECT) mark regions that are identity-mapped
// through the IOMMU; they may also include pre-allocated DRAM buffers (e.g.
// display stolen memory).
//
// Both "reserved" and "direct" entries with addresses in plausible DRAM ranges
// are emitted as P/dram witnesses to bound the system's physical memory layout.
// "msi" entries are always MMIO (interrupt delivery ranges) and are skipped.
//
// The attribute is created world-readable, no capability check, not gated by
// kptr_restrict:
//
// static IOMMU_GROUP_ATTR(reserved_regions, 0444, ...)
//
// Typical output on an Intel VT-d system with USB RMRR:
//
// iommu_group 0: reserved 0x000000007e300000 - 0x000000007e31ffff
// P reserved_mem:0 pos=base conf=parsed lo=0x7e300000 hi=0x7e31ffff
//
// Leak primitive:
// Data leaked: physical DRAM addresses (IOMMU group reserved / direct
// mapped regions — firmware RMRR + pre-allocated DMA)
// Kernel subsystem: drivers/iommu —
// /sys/kernel/iommu_groups/N/reserved_regions Data structure: struct
// iommu_resv_region → start / end Address type: physical (DRAM) Method:
// parsed (sysfs text attribute) Status: unfixed (information
// exposure by design) Access check: none (world-readable via S_IRUGO /
// IOMMU_GROUP_ATTR) Source:
// https://elixir.bootlin.com/linux/v6.12/source/drivers/iommu/iommu.c#L3030
//
// Mitigations:
// CONFIG_IOMMU_API=n removes IOMMU group sysfs entirely. On x86_64 with
// CONFIG_RANDOMIZE_MEMORY enabled, physical addresses do not directly
// reveal the virtual text base. Requires an active IOMMU with populated
// reserved or direct-mapped regions.
//
// Requires:
// - CONFIG_IOMMU_API
// - An active IOMMU (Intel VT-d or AMD-Vi) with reserved/direct regions
// - At least one IOMMU group with a physical DRAM reserved region
//
// References:
// https://elixir.bootlin.com/linux/v6.12/source/drivers/iommu/iommu.c#L3030
// https://elixir.bootlin.com/linux/v6.12/source/include/linux/iommu.h#L168
// https://elixir.bootlin.com/linux/v6.12/source/drivers/iommu/intel/iommu.c
// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-kernel-iommu_groups
// ---
// <bcoles@gmail.com>
#include "include/kasld/api.h"
#include "include/kasld/cli.h"
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
KASLD_EXPLAIN(
"Reads physical DRAM addresses from IOMMU group reserved regions at "
"/sys/kernel/iommu_groups/N/reserved_regions. On Intel VT-d systems with "
"Reserved Memory Region Reporting (RMRR), firmware declares physical DRAM "
"ranges that USB controllers, integrated GPUs, and similar hardware "
"require "
"for DMA. These appear as 'reserved' (RMRR) or 'direct' (identity-mapped "
"DMA) entries and bound the physical RAM layout. The attribute is "
"world-readable (S_IRUGO, 0444) with no capability check. Requires "
"CONFIG_IOMMU_API and an active IOMMU with populated reserved regions.");
// Untested: no hardware with an active IOMMU available for testing.
KASLD_META("method:parsed\n"
"phase:inference\n"
"discloses:physical\n"
"config:CONFIG_IOMMU_API\n");
/* Physical DRAM range heuristic.
*
* Returns 1 if the address is plausibly in system DRAM rather than MMIO.
* RMRR regions are definitionally DRAM (DMA into physical memory), but
* "Direct" entries can include MMIO pass-through on some systems.
*
* Heuristic: accept addresses in [1 MB, 3 GB) — typical low DRAM — or
* at or above 4 GB — typical high DRAM. Reject the 3–4 GB PCI hole
* and known MMIO hotspots (IOAPIC 0xfec00000, HPET 0xfed00000, LAPIC
* 0xfee00000) regardless of type annotation.
*
* This is an approximation; on unusual systems the PCI hole may differ.
* The KASLD inference layer discards results outside E820/firmware RAM
* ranges anyway, so false positives here are harmless.
*/
static int is_likely_dram(unsigned long long addr) {
if (addr < 0x100000ULL)
return 0; /* below 1 MB: legacy BIOS area, VGA ROM, EBDA */
if (addr >= 0xc0000000ULL && addr < 0x100000000ULL)
return 0; /* 3 GB – 4 GB: PCI hole / MMIO (typical x86) */
if (addr >= 0xfec00000ULL && addr < 0xff000000ULL)
return 0; /* IOAPIC / HPET / LAPIC MMIO hot-spot */
return 1;
}
int main(void) {
const char *base = "/sys/kernel/iommu_groups";
DIR *d;
struct dirent *ent;
char path[512];
char line[256];
int group_count = 0;
int found = 0;
kasld_info("searching %s for IOMMU group reserved region physical addresses "
"...",
base);
d = kasld_opendir(base);
if (!d) {
if (errno == ENOENT) {
kasld_err("%s: not present (no active IOMMU or CONFIG_IOMMU_API=n)",
base);
return KASLD_EXIT_UNAVAILABLE;
}
perror("[-] opendir");
return (errno == EACCES || errno == EPERM) ? KASLD_EXIT_NOPERM
: KASLD_EXIT_UNAVAILABLE;
}
while ((ent = readdir(d)) != NULL) {
if (ent->d_name[0] == '.')
continue;
/* Only process numeric group directories (0, 1, 2, …). */
char *endptr;
strtoul(ent->d_name, &endptr, 10);
if (*endptr != '\0')
continue;
snprintf(path, sizeof(path), "%s/%s/reserved_regions", base, ent->d_name);
FILE *f = kasld_fopen(path, "r");
if (!f)
continue;
group_count++;
while (fgets(line, sizeof(line), f)) {
unsigned long long start = 0, end_addr = 0;
char type[64] = {0};
if (sscanf(line, "0x%llx 0x%llx %63[^\n]", &start, &end_addr, type) != 3)
continue;
if (!start || !end_addr)
continue;
/* Skip "msi" entries — always interrupt-delivery MMIO. The kernel
* emits the type string in lowercase (IOMMU_RESV_MSI and
* IOMMU_RESV_SW_MSI both render as "msi"); match case-insensitively
* so older capitalised spellings are also caught. */
if (strncasecmp(type, "msi", 3) == 0)
continue;
/* Accept "direct", "direct-relaxable", and "reserved" entries that
* pass the DRAM plausibility check. */
if (!is_likely_dram(start))
continue;
found++;
kasld_info("iommu_group %s: %s 0x%016llx - 0x%016llx", ent->d_name, type,
start, end_addr);
/* Each reserved_regions line is one contiguous reserved range fully
* spanning [start, end_addr], so emit it as a bounded range: the engine
* can then exclude the whole forbidden band (phys_reservation_exclude),
* which two disconnected interior points cannot drive (each point has
* hi==lo and is skipped). Not a covering — these are forbidden bands, not
* a RAM map, so the gaps between them are NOT known-empty: range, not
* extent. */
if (end_addr > start)
kasld_result_range(KASLD_TYPE_PHYS, REGION_RESERVED_MEM,
(unsigned long)start, (unsigned long)end_addr,
ent->d_name, CONF_PARSED);
else
kasld_result_sample(KASLD_TYPE_PHYS, REGION_RESERVED_MEM,
(unsigned long)start, ent->d_name, CONF_PARSED);
}
fclose(f);
}
closedir(d);
if (!group_count) {
kasld_err("no IOMMU groups found in %s (no active IOMMU or empty)", base);
return KASLD_EXIT_UNAVAILABLE;
}
if (!found) {
kasld_err("no DRAM-range reserved regions found across %d IOMMU group(s)",
group_count);
return KASLD_EXIT_UNAVAILABLE;
}
return 0;
}