Skip to content

Commit 246db88

Browse files
committed
tests: add sysfs / ACPI / device-tree leak component tests
1 parent f9ae54e commit 246db88

1 file changed

Lines changed: 331 additions & 0 deletions

File tree

tests/test_sysfs_parsers.c

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,331 @@
1+
// This file is part of KASLD - https://github.com/bcoles/kasld
2+
//
3+
// Parser unit tests for the sysfs / ACPI / device-tree leak components. Each
4+
// component is #included with its main() (and its static read_file_line, where
5+
// present) renamed so the parser is in scope, then driven over a staged
6+
// KASLD_SYSROOT tree of hand-built fixture files. The fixtures reproduce the
7+
// exact text/byte format the kernel exposes for each interface, so a test
8+
// failing here means the parser no longer matches the kernel ABI it targets:
9+
//
10+
// acpi_mrrm /sys/firmware/acpi/memory_ranges/rangeN/base
11+
// sysfs_cbmem_address /sys/bus/coreboot/devices/cbmem-*/address
12+
// sysfs_cxl_region /sys/bus/cxl/devices/regionN/resource
13+
// sysfs_qcom_rmtfs_mem /sys/class/rmtfs/qcom_rmtfs_memN/phys_addr
14+
// sysfs_iommu_reserved_.. /sys/kernel/iommu_groups/N/reserved_regions
15+
// sysfs_efi_runtime_map /sys/firmware/efi/runtime-map/N/{virt,phys}_addr
16+
// sysfs_devicetree_elf.. /sys/firmware/devicetree/base/chosen/...
17+
//
18+
// The components route their directory and file reads through the KASLD_SYSROOT
19+
// wrappers, so the fixture tree is read in place of the live system. Each
20+
// parser's main() is captured on stdout (the wire channel) and the emitted
21+
// P/V record is checked.
22+
// ---
23+
// <bcoles@gmail.com>
24+
#define _GNU_SOURCE
25+
26+
/* Pull in the public API once, then neutralise the two ELF-section macros so
27+
* the per-component KASLD_EXPLAIN/KASLD_META definitions do not collide when
28+
* several components are included into this single translation unit. */
29+
#include "../src/include/kasld/api.h"
30+
#undef KASLD_EXPLAIN
31+
#undef KASLD_META
32+
/* Expand to a uniquely-named file-scope declaration (not a definition) so
33+
* several components coexist in one TU without colliding ELF-section arrays or
34+
* redundant redeclarations, and the trailing ';' after each KASLD_EXPLAIN(...)
35+
* / KASLD_META(...) is absorbed. __COUNTER__ makes every name distinct. */
36+
#define KASLD_CAT_(a, b) a##b
37+
#define KASLD_CAT(a, b) KASLD_CAT_(a, b)
38+
#define KASLD_EXPLAIN(t) \
39+
extern char KASLD_CAT(kasld_explain_unused_, __COUNTER__)[]
40+
#define KASLD_META(t) extern char KASLD_CAT(kasld_meta_unused_, __COUNTER__)[]
41+
42+
/* Forward declarations for the renamed component entry points (avoids
43+
* -Wmissing-prototypes; the includes below define them). */
44+
int efi_main(void);
45+
int acpi_main(void);
46+
int cbmem_main(void);
47+
int cxl_main(void);
48+
int qcom_main(void);
49+
int iommu_main(void);
50+
int dt_main(void);
51+
int nd_main(void);
52+
int uio_main(void);
53+
int iscsi_main(void);
54+
55+
#define main efi_main
56+
#define read_file_line efi_read_file_line
57+
#include "../src/components/sysfs_efi_runtime_map.c"
58+
#undef read_file_line
59+
#undef main
60+
61+
#define main acpi_main
62+
#define read_file_line acpi_read_file_line
63+
#include "../src/components/acpi_mrrm.c"
64+
#undef read_file_line
65+
#undef main
66+
67+
#define main cbmem_main
68+
#define read_file_line cbmem_read_file_line
69+
#include "../src/components/sysfs_cbmem_address.c"
70+
#undef read_file_line
71+
#undef main
72+
73+
#define main cxl_main
74+
#define read_file_line cxl_read_file_line
75+
#include "../src/components/sysfs_cxl_region.c"
76+
#undef read_file_line
77+
#undef main
78+
79+
#define main qcom_main
80+
#define read_file_line qcom_read_file_line
81+
#include "../src/components/sysfs_qcom_rmtfs_mem.c"
82+
#undef read_file_line
83+
#undef main
84+
85+
#define main iommu_main
86+
#include "../src/components/sysfs_iommu_reserved_regions.c"
87+
#undef main
88+
89+
#define main dt_main
90+
#include "../src/components/sysfs_devicetree_elfcorehdr.c"
91+
#undef main
92+
93+
#define main nd_main
94+
#include "../src/components/sysfs_nd_region.c"
95+
#undef main
96+
97+
#define main uio_main
98+
#define read_file_line uio_read_file_line
99+
#include "../src/components/sysfs_uio_map.c"
100+
#undef read_file_line
101+
#undef main
102+
103+
#define main iscsi_main
104+
#include "../src/components/sysfs_iscsi_transport_handle.c"
105+
#undef main
106+
107+
#include "test_harness.h"
108+
109+
#include <assert.h>
110+
#include <fcntl.h>
111+
#include <stdint.h>
112+
#include <sys/stat.h>
113+
#include <sys/types.h>
114+
#include <unistd.h>
115+
116+
static char g_root[256];
117+
118+
/* mkdir -p of the parent directory of <g_root><rel>, then return the full
119+
* path in `out`. */
120+
static void full_path(const char *rel, char *out, size_t n) {
121+
snprintf(out, n, "%s%s", g_root, rel);
122+
}
123+
124+
static void mkparents(const char *path) {
125+
char buf[512];
126+
snprintf(buf, sizeof(buf), "%s", path);
127+
for (char *p = buf + 1; *p; p++) {
128+
if (*p == '/') {
129+
*p = '\0';
130+
mkdir(buf, 0755);
131+
*p = '/';
132+
}
133+
}
134+
}
135+
136+
/* Write `len` bytes to the sysroot-relative path. */
137+
static void stage(const char *rel, const void *data, size_t len) {
138+
char path[512];
139+
full_path(rel, path, sizeof(path));
140+
mkparents(path);
141+
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
142+
assert(fd >= 0);
143+
assert(write(fd, data, len) == (ssize_t)len);
144+
close(fd);
145+
}
146+
147+
static void stage_text(const char *rel, const char *text) {
148+
stage(rel, text, strlen(text));
149+
}
150+
151+
static void put_be64(unsigned char *p, uint64_t v) {
152+
for (int i = 0; i < 8; i++)
153+
p[i] = (unsigned char)(v >> (56 - 8 * i));
154+
}
155+
156+
/* Run a renamed component main(), capturing its stdout (the wire channel) into
157+
* `cap`. Diagnostics go to stderr and are left alone. */
158+
static char cap[16384];
159+
static void run_capture(int (*fn)(void)) {
160+
fflush(stdout);
161+
char tmpl[] = "/tmp/kasld_parser_capXXXXXX";
162+
int fd = mkstemp(tmpl);
163+
assert(fd >= 0);
164+
int saved = dup(1);
165+
dup2(fd, 1);
166+
/* Silence the component's stderr diagnostics (kasld_info / kasld_err /
167+
* kasld_found) so they do not bleed into the test harness output. */
168+
fflush(stderr);
169+
int saved_err = dup(2);
170+
int devnull = open("/dev/null", O_WRONLY);
171+
if (devnull >= 0)
172+
dup2(devnull, 2);
173+
fn();
174+
fflush(stdout);
175+
fflush(stderr);
176+
dup2(saved, 1);
177+
close(saved);
178+
dup2(saved_err, 2);
179+
close(saved_err);
180+
if (devnull >= 0)
181+
close(devnull);
182+
lseek(fd, 0, SEEK_SET);
183+
ssize_t n = read(fd, cap, sizeof(cap) - 1);
184+
cap[n > 0 ? n : 0] = '\0';
185+
close(fd);
186+
unlink(tmpl);
187+
}
188+
189+
/* --- ACPI MRRM: base is "0x%llx" text ----------------------------------- */
190+
static void test_acpi_mrrm_base(void) {
191+
stage_text("/sys/firmware/acpi/memory_ranges/range0/base", "0x100000000\n");
192+
run_capture(acpi_main);
193+
assert(strstr(cap, "P pmem:range0") != NULL);
194+
assert(strstr(cap, "sample=0x100000000") != NULL);
195+
}
196+
197+
/* --- coreboot CBMEM: address is "0x%llx" text --------------------------- */
198+
static void test_cbmem_address(void) {
199+
stage_text("/sys/bus/coreboot/devices/cbmem-00000abc/address",
200+
"0x100000000\n");
201+
run_capture(cbmem_main);
202+
assert(strstr(cap, "P reserved_mem:cbmem-00000abc") != NULL);
203+
assert(strstr(cap, "sample=0x100000000") != NULL);
204+
}
205+
206+
/* --- CXL region: resource is "%#llx" text; -1 means unallocated ---------- */
207+
static void test_cxl_region(void) {
208+
stage_text("/sys/bus/cxl/devices/region0/resource", "0x100000000\n");
209+
/* An unallocated region reports 0xff..ff and must be skipped. */
210+
stage_text("/sys/bus/cxl/devices/region1/resource", "0xffffffffffffffff\n");
211+
run_capture(cxl_main);
212+
assert(strstr(cap, "sample=0x100000000") != NULL);
213+
assert(strstr(cap, "ffffffffffffffff") == NULL);
214+
}
215+
216+
/* --- Qualcomm RMTFS: phys_addr is "%pa" text ("0x%llx") ------------------ */
217+
static void test_qcom_rmtfs(void) {
218+
stage_text("/sys/class/rmtfs/qcom_rmtfs_mem0/phys_addr", "0x100000000\n");
219+
run_capture(qcom_main);
220+
assert(strstr(cap, "P reserved_mem:qcom_rmtfs_mem0") != NULL);
221+
assert(strstr(cap, "sample=0x100000000") != NULL);
222+
}
223+
224+
/* --- IOMMU reserved_regions: "0x%016llx 0x%016llx <type>" lines.
225+
* "msi" entries are skipped by type (even at a DRAM address); "reserved"
226+
* entries in plausible DRAM are emitted. ---------------------------------- */
227+
static void test_iommu_reserved_regions(void) {
228+
stage_text("/sys/kernel/iommu_groups/0/reserved_regions",
229+
"0x0000000100000000 0x000000010000ffff msi\n"
230+
"0x0000000200000000 0x000000020000ffff reserved\n");
231+
run_capture(iommu_main);
232+
/* the reserved DRAM range is emitted (start and end) */
233+
assert(strstr(cap, "sample=0x200000000") != NULL);
234+
/* the msi range is skipped despite its DRAM address */
235+
assert(strstr(cap, "0x100000000") == NULL);
236+
}
237+
238+
/* --- device-tree elfcorehdr: two big-endian u64 (address, size) --------- */
239+
static void test_devicetree_elfcorehdr(void) {
240+
unsigned char blob[16];
241+
put_be64(blob + 0, 0x100000000ULL); /* address */
242+
put_be64(blob + 8, 0x10000ULL); /* size */
243+
stage("/sys/firmware/devicetree/base/chosen/linux,elfcorehdr", blob,
244+
sizeof(blob));
245+
run_capture(dt_main);
246+
assert(strstr(cap, "P crashkernel:elfcorehdr") != NULL);
247+
/* big-endian decode: address 0x100000000, hi = addr + size - 1 */
248+
assert(strstr(cap, "lo=0x100000000 hi=0x10000ffff") != NULL);
249+
}
250+
251+
/* --- EFI runtime-map: virt_addr / phys_addr "0x%llx" text; the parser
252+
* derives virt_page_offset = virt - phys for a direct-map virtual address.
253+
* Uses host-arch direct-map constants (this interface is x86-only). ------- */
254+
static void test_efi_runtime_map(void) {
255+
unsigned long virt = 0xffff888000001000UL;
256+
unsigned long phys = 0x1000UL;
257+
/* Only meaningful where the chosen virt is in the host's direct-map window
258+
* and virt-phys lands in the page-offset window (true on x86_64). */
259+
if (!kasld_addr_is_directmap(virt))
260+
return;
261+
stage_text("/sys/firmware/efi/runtime-map/0/virt_addr",
262+
"0xffff888000001000\n");
263+
stage_text("/sys/firmware/efi/runtime-map/0/phys_addr", "0x1000\n");
264+
run_capture(efi_main);
265+
assert(strstr(cap, "V virt_page_offset") != NULL);
266+
char want[64];
267+
snprintf(want, sizeof(want), "sample=0x%lx", virt - phys);
268+
assert(strstr(cap, want) != NULL);
269+
}
270+
271+
/* --- libnvdimm nd_region: resource is "%#llx" text ---------------------- */
272+
static void test_nd_region(void) {
273+
stage_text("/sys/bus/nd/devices/ndregion0/resource", "0x4000000000\n");
274+
run_capture(nd_main);
275+
assert(strstr(cap, "P pmem:ndregion0") != NULL);
276+
assert(strstr(cap, "sample=0x4000000000") != NULL);
277+
}
278+
279+
/* --- UIO map: maps/mapN/addr is "%pa" text ("0x%llx"); region defaults to
280+
* mmio when /proc/iomem does not place the address in System RAM. ---------- */
281+
static void test_uio_map(void) {
282+
stage_text("/sys/class/uio/uio0/maps/map0/addr", "0x90000000\n");
283+
stage_text("/sys/class/uio/uio0/maps/map0/name", "uio-mem\n");
284+
run_capture(uio_main);
285+
assert(strstr(cap, "P mmio:uio0/map0") != NULL);
286+
assert(strstr(cap, "sample=0x90000000") != NULL);
287+
}
288+
289+
/* --- iSCSI transport handle (CVE-2021-27363): the "handle" attribute is a
290+
* decimal kernel pointer. KASLD_SYSROOT short-circuits the netlink autoload,
291+
* so the fixture is read directly. Uses a host-arch kernel-text address. --- */
292+
static void test_iscsi_transport_handle(void) {
293+
unsigned long addr =
294+
0xffffffff81000000UL; /* in the host kernel-text window */
295+
if (!kasld_addr_is_kernel_text(addr))
296+
return;
297+
char dec[32];
298+
snprintf(dec, sizeof(dec), "%lu\n", addr);
299+
stage_text("/sys/class/iscsi_transport/iser/handle", dec);
300+
stage_text("/sys/class/iscsi_transport/tcp/handle", dec);
301+
run_capture(iscsi_main);
302+
assert(strstr(cap, "V kernel_data:iscsi_iser_transport") != NULL);
303+
char want[64];
304+
snprintf(want, sizeof(want), "sample=0x%lx", addr);
305+
assert(strstr(cap, want) != NULL);
306+
}
307+
308+
int main(void) {
309+
/* One sysroot for the whole suite: each parser reads a distinct path, and
310+
* kasld_sysroot() caches its value process-wide, so a single root must be
311+
* set before any component runs. */
312+
char tmpl[] = "/tmp/kasld_parser_rootXXXXXX";
313+
char *r = mkdtemp(tmpl);
314+
assert(r != NULL);
315+
snprintf(g_root, sizeof(g_root), "%s", r);
316+
setenv("KASLD_SYSROOT", g_root, 1);
317+
318+
TEST_SUITE("test_sysfs_parsers");
319+
BEGIN_CATEGORY("sysfs / ACPI / DT leak parsers");
320+
RUN(test_acpi_mrrm_base);
321+
RUN(test_cbmem_address);
322+
RUN(test_cxl_region);
323+
RUN(test_qcom_rmtfs);
324+
RUN(test_iommu_reserved_regions);
325+
RUN(test_devicetree_elfcorehdr);
326+
RUN(test_efi_runtime_map);
327+
RUN(test_nd_region);
328+
RUN(test_uio_map);
329+
RUN(test_iscsi_transport_handle);
330+
return TEST_DONE();
331+
}

0 commit comments

Comments
 (0)