-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathsysfs_devicetree_initrd.c
More file actions
149 lines (134 loc) · 4.97 KB
/
Copy pathsysfs_devicetree_initrd.c
File metadata and controls
149 lines (134 loc) · 4.97 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
// This file is part of KASLD - https://github.com/bcoles/kasld
//
// Read physical initrd/initramfs address from device tree sysfs.
// On device tree platforms, the bootloader passes the initrd location
// via the "chosen" node properties:
//
// /sys/firmware/devicetree/base/chosen/linux,initrd-start
// /sys/firmware/devicetree/base/chosen/linux,initrd-end
//
// These properties contain raw big-endian physical addresses. Unlike
// kaslr-seed and rng-seed (which are zeroed after boot), the initrd
// properties are NOT sanitized and persist in the live device tree.
//
// The initrd is loaded by the bootloader into physical DRAM, so the
// address falls within the usable DRAM range.
//
// Not available on x86/x86_64 (no device tree). Only present when
// the bootloader passes a separate initrd via device tree (common on
// U-Boot, QEMU -initrd, GRUB-EFI on ARM/RISC-V).
//
// Analogous to dmesg_check_for_initrd but works without dmesg access.
//
// Leak primitive:
// Data leaked: physical initrd load address (start and end)
// Kernel subsystem: drivers/of —
// /sys/firmware/devicetree/base/chosen/linux,initrd-* Data structure: device
// tree chosen node (linux,initrd-start / linux,initrd-end) Address type:
// physical (DRAM) Method: parsed (binary sysfs property) Status:
// unfixed (information exposure by design)
// Access check: none (world-readable sysfs attribute, 0444)
// Source: https://elixir.bootlin.com/linux/v6.12/source/drivers/of/fdt.c#L785
//
// Mitigations:
// CONFIG_OF=n removes device tree sysfs. CONFIG_BLK_DEV_INITRD=n prevents
// the property from existing. The property is world-readable (0444);
// no runtime sysctl can restrict access. On decoupled architectures,
// physical addresses cannot derive the virtual text base.
//
// Requires:
// - CONFIG_OF (device tree support)
// - CONFIG_BLK_DEV_INITRD
// - Bootloader must pass initrd via device tree
//
// References:
// https://elixir.bootlin.com/linux/v6.12/source/drivers/of/fdt.c#L785
// https://elixir.bootlin.com/linux/v6.12/source/drivers/of/kobj.c#L65
// ---
// <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 the physical initrd/initramfs address from the device tree "
"sysfs chosen node (/sys/firmware/devicetree/base/chosen/"
"linux,initrd-start). This world-readable binary property contains "
"the physical address where the bootloader placed the initrd in "
"RAM. Only present on device tree platforms with an initrd.");
KASLD_META("method:parsed\n"
"phase:inference\n"
"discloses:physical\n"
"config:CONFIG_OF\n"
"config:CONFIG_BLK_DEV_INITRD\n");
/* Read a big-endian value of 4 or 8 bytes into unsigned long. */
static unsigned long read_addr(const unsigned char *buf, int len) {
if (len == 8) {
uint64_t hi = kasld_dt_be32(buf);
uint64_t lo = kasld_dt_be32(buf + 4);
return (unsigned long)((hi << 32) | lo);
}
if (len == 4) {
return (unsigned long)kasld_dt_be32(buf);
}
return 0;
}
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[8];
int n;
/* A base that exists but is hidden is a different fact from one that is
* absent; keep it so the verdict below can say which. */
int dt_denied = 0;
for (int i = 0; bases[i]; i++) {
snprintf(path, sizeof(path), "%s/linux,initrd-start", bases[i]);
FILE *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 initrd properties");
return dt_denied ? KASLD_EXIT_NOPERM : KASLD_EXIT_UNAVAILABLE;
}
kasld_info("trying %s/linux,initrd-{start,end} ...", chosen);
/* Read linux,initrd-start */
snprintf(path, sizeof(path), "%s/linux,initrd-start", chosen);
n = kasld_dt_read_blob(path, buf, sizeof(buf));
if (n != 4 && n != 8) {
kasld_err("failed to read %s (got %d bytes)", path, n);
return 0;
}
unsigned long start = read_addr(buf, n);
/* Read linux,initrd-end */
snprintf(path, sizeof(path), "%s/linux,initrd-end", chosen);
n = kasld_dt_read_blob(path, buf, sizeof(buf));
unsigned long end = 0;
if (n == 4 || n == 8) {
end = read_addr(buf, n);
}
if (!start) {
kasld_err("initrd-start is zero");
return 0;
}
kasld_info("initrd physical start: 0x%016lx", start);
if (end && end > start) {
kasld_info("initrd physical end: 0x%016lx", end);
kasld_result_range(KASLD_TYPE_PHYS, REGION_INITRD, start, end, NULL,
CONF_PARSED);
} else {
kasld_result_base(KASLD_TYPE_PHYS, REGION_INITRD, start, NULL, CONF_PARSED);
}
return 0;
}