-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathdmesg_acpi_dynamic_ssdt.c
More file actions
172 lines (156 loc) · 6.27 KB
/
Copy pathdmesg_acpi_dynamic_ssdt.c
File metadata and controls
172 lines (156 loc) · 6.27 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
// This file is part of KASLD - https://github.com/bcoles/kasld
//
// The ACPI subsystem prints table headers via acpi_tb_print_table_header()
// whenever a table is installed. For statically-discovered tables (XSDT,
// FACP, DSDT, ...) the kernel prints the *physical* address the firmware
// placed the table at:
//
// ACPI: SSDT 0x000000002823FB28 00046D (v01 SataRe SataTabl ...)
//
// For *dynamically* loaded OEM tables — most commonly the SSDTs that
// firmware hands to the kernel during early boot to describe CPU power
// management (Cpu0Ist, Cpu0Cst, ApIst, ApCst), loaded by ACPICA via
// acpi_tb_install_and_load_table() — the kernel prints the *virtual*
// address where it mapped the freshly-allocated table:
//
// ACPI: Dynamic OEM Table Load:
// ACPI: SSDT 0xFFFF8881010B6000 0005DC (v02 PmRef Cpu0Ist ...)
//
// These dynamic-load addresses land in the kernel direct-map region
// (virt_page_offset_base + phys), so they leak a direct-map virtual address
// and bound virt_page_offset_base to KASLR granularity (1 GiB on x86_64).
// The static-table lines are filtered out by range-checking the parsed
// address against the direct-map region.
//
// On any Intel x86 system with acpi_processor / intel_pstate drivers the
// four P-state/C-state SSDTs are loaded unconditionally at boot, so this
// pattern is present on the vast majority of real-world Linux installs,
// not just those with exotic firmware.
//
// Leak primitive:
// Data leaked: direct-map virtual address of a dynamically-loaded
// ACPI table (bounds virt_page_offset_base / physmap base)
// Kernel subsystem: drivers/acpi/acpica — acpi_tb_print_table_header()
// Data structure: ACPI SSDT (or other dynamic OEM table) header
// Address type: virtual (direct-map / virt_page_offset)
// Method: parsed (dmesg string)
// Status: unfixed (printed unconditionally when dynamic OEM
// tables are loaded)
// Access check: do_syslog() → check_syslog_permissions(); gated by
// dmesg_restrict
// Source:
// https://elixir.bootlin.com/linux/v6.12/source/drivers/acpi/acpica/tbprint.c
//
// Mitigations:
// Access gated by dmesg_restrict (see dmesg.h for shared access gate
// details). The address format (virtual vs physical) cannot be
// controlled from userspace — it reflects where ACPICA mapped the
// table, which is direct-map for dynamically-allocated tables.
//
// Requires:
// - kernel.dmesg_restrict = 0; or CAP_SYSLOG capabilities; or
// readable /var/log/dmesg.
// - ACPI enabled (CONFIG_ACPI); an ACPI-using architecture (typically
// x86_64, x86_32, arm64, ia64).
// - Firmware that supplies dynamic OEM tables (nearly all Intel x86
// systems via acpi_processor P-state/C-state SSDTs).
//
// References:
// https://elixir.bootlin.com/linux/v6.12/source/drivers/acpi/acpica/tbprint.c
// https://elixir.bootlin.com/linux/v6.12/source/drivers/acpi/acpica/tbxfload.c
// ---
// <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>
struct ssdt_ctx {
unsigned long addr;
char label[32];
};
KASLD_EXPLAIN(
"Searches dmesg for ACPI dynamic OEM table load messages. When the "
"kernel loads dynamic tables (most commonly the four CPU P-state / "
"C-state SSDTs on Intel systems), it prints the direct-map virtual "
"address of the mapped table. This bounds virt_page_offset_base / the "
"direct-map base to KASLR granularity. Access is gated by "
"dmesg_restrict.");
KASLD_META("method:parsed\n"
"phase:inference\n"
"discloses:virtual\n"
"sysctl:dmesg_restrict>=1\n"
"bypass:CAP_SYSLOG\n"
"fallback:/var/log/dmesg\n");
static int on_match(const char *line, void *ctx) {
struct ssdt_ctx *s = ctx;
/* Expected forms:
* ACPI: SSDT 0xFFFF8881010B6000 0005DC (v02 PmRef Cpu0Ist ...)
* ACPI: SSDT 0x000000002823FB28 00046D (v01 SataRe SataTabl ...)
* The second (physical) form is filtered out by range check below. */
const char *p = strstr(line, " 0x");
if (!p)
return 1;
unsigned long addr;
const char *endptr;
if (!kasld_addr_parse(p + 1, 16, &addr, &endptr) || !addr)
return 1;
/* Direct-map addresses land between PAGE_OFFSET (VAS upper-half start)
* and KERNEL_VIRT_TEXT_MIN (start of kernel text region). Physical addresses
* printed for static tables are well below PAGE_OFFSET and get rejected. */
if (!kasld_addr_is_directmap(addr))
return 1;
/* Capture OEM table id (e.g. "Cpu0Ist", "ApCst") for the result label.
* Format: "... (vNN OEMID OEMTABLEID ..." — the OEMTABLEID is the
* 3rd whitespace-separated token inside the parens. */
const char *paren = strchr(endptr, '(');
if (paren) {
const char *tok = paren + 1;
/* skip version token */
while (*tok && *tok != ' ')
tok++;
while (*tok == ' ')
tok++;
/* skip OEM id token */
while (*tok && *tok != ' ')
tok++;
while (*tok == ' ')
tok++;
/* copy OEM table id token */
int i = 0;
while (*tok && *tok != ' ' && (size_t)i < sizeof(s->label) - 1)
s->label[i++] = *tok++;
s->label[i] = '\0';
}
/* First match wins; all dynamic SSDTs live in the same direct-map
* region, so one address fully constrains virt_page_offset_base. */
s->addr = addr;
return 0;
}
int main(void) {
struct ssdt_ctx s = {0, {0}};
kasld_info("searching dmesg for ACPI dynamic OEM table loads ...");
int ds = dmesg_search("ACPI: SSDT 0x", on_match, &s);
if (!s.addr) {
kasld_err("no ACPI dynamic OEM table load with a direct-map virtual "
"address found in dmesg");
if (ds < 0)
return KASLD_EXIT_NOPERM;
return 0;
}
if (s.label[0])
kasld_info("ACPI dynamic SSDT direct-map virtual address: 0x%016lx (%s)",
s.addr, s.label);
else
kasld_info("ACPI dynamic SSDT direct-map virtual address: 0x%016lx",
s.addr);
/* Pass the OEM table id (e.g. "Cpu0Ist", "ApIst") as the specific
* instance name so the table reads "acpi_table:Cpu0Ist". */
kasld_result_sample(KASLD_TYPE_VIRT, REGION_ACPI_TABLE, s.addr, s.label,
CONF_PARSED);
return 0;
}