-
Notifications
You must be signed in to change notification settings - Fork 52
Expand file tree
/
Copy pathmmap_arm64_va_bits.c
More file actions
106 lines (97 loc) · 4.07 KB
/
Copy pathmmap_arm64_va_bits.c
File metadata and controls
106 lines (97 loc) · 4.07 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
// This file is part of KASLD - https://github.com/bcoles/kasld
//
// arm64 active VA_BITS detection via an mmap boundary probe.
//
// PROBING-phase component. On arm64 TASK_SIZE = 1<<VA_BITS, so a one-page probe
// at (1<<c) - PAGE_SIZE is mappable iff c <= VA_BITS; probing the candidate
// ladder largest-first and taking the first that maps yields the exact ACTIVE
// VA_BITS. That is published as SF_VIRT_ADDR_BITS; arm64_va_bits_from_scalar
// pins Q_VA_BITS from it, and arm64_page_offset_from_va_bits then derives the
// exact PAGE_OFFSET = -(1<<VA_BITS) (not randomized on arm64). Emitting the
// width — rather than the direct PAGE_OFFSET — resolves Q_VA_BITS leak-free
// (which a REGION_PAGE_OFFSET landmark alone does not) and mirrors the x86_64
// probe.
//
// MAP_FIXED_NOREPLACE distinguishes "beyond TASK_SIZE" (ENOMEM/EINVAL → probe a
// smaller boundary) from "occupied" (EEXIST → the address is within TASK_SIZE)
// and never clobbers a live mapping. If the kernel returns an unrequested
// address (NOREPLACE not honoured, pre-v4.17) the probe is unreliable and emits
// nothing rather than guessing. Likewise an unexpected errno (RLIMIT_AS,
// seccomp) or a VA_BITS below the smallest supported candidate → no emission,
// leaving the engine's honest window (sound but wide).
//
// Detection via the mmap syscall; unprivileged, no sysctl gate. arm64 only.
// ---
// <bcoles@gmail.com>
#define _GNU_SOURCE
#include "include/kasld/api.h"
#include "include/kasld/cli.h"
#include <errno.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#ifndef MAP_FIXED_NOREPLACE
#define MAP_FIXED_NOREPLACE 0x100000
#endif
KASLD_EXPLAIN(
"Probes mmap(MAP_FIXED_NOREPLACE) at the 1<<VA_BITS boundaries on arm64 "
"(52/48/47/42/39/36): the largest that maps is the active VA_BITS. "
"Publishes "
"the width, from which PAGE_OFFSET = -(1<<VA_BITS) is derived (not "
"randomized on arm64). arm64 only; unprivileged.");
KASLD_META("method:inferred\n"
"phase:probing\n"
"live:1\n"
"discloses:facts\n");
int main(void) {
if (kasld_skip_live_probe("VA_BITS mmap"))
return 0;
/* Live mmap boundary probe of the running VA space. */
#if defined(__aarch64__)
/* The architecture's own candidate set, walked LARGEST first: the widest
* boundary that still maps is the active VA_BITS. Read from the header rather
* than restated here: a second copy would be two lists obliged to agree, with
* nothing to enforce it, and a width present in one and absent from the other
* is probed by neither. */
static const unsigned long cset[] = VA_BITS_CANDIDATES; /* smallest first */
const int ncands = (int)(sizeof(cset) / sizeof(cset[0]));
long pg = sysconf(_SC_PAGESIZE);
unsigned long page = (pg > 0) ? (unsigned long)pg : 0x1000ul;
unsigned long va_bits = 0;
for (int i = ncands - 1; i >= 0; i--) {
unsigned long c = cset[i];
void *want = (void *)((1UL << c) - page);
void *p = mmap(want, (size_t)page, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
if (p == want) { /* mapped exactly here -> within TASK_SIZE */
munmap(p, (size_t)page);
va_bits = c;
break;
}
if (p !=
MAP_FAILED) { /* NOREPLACE not honoured (old kernel) -> unreliable */
munmap(p, (size_t)page);
kasld_info("mmap returned an unrequested address; probe unreliable");
return 0;
}
if (errno == EEXIST) { /* occupied -> addressable -> within TASK_SIZE */
va_bits = c;
break;
}
if (errno == ENOMEM || errno == EINVAL)
continue; /* beyond this boundary; try a smaller VA_BITS */
kasld_info("mmap(1<<%lu): unexpected errno %d; not inferring", c, errno);
return 0; /* RLIMIT_AS / seccomp / etc. — don't guess */
}
if (va_bits == 0) {
kasld_info("VA_BITS below smallest supported candidate; not inferring");
return 0;
}
kasld_info("active VA_BITS=%lu (PAGE_OFFSET = %#lx)", va_bits,
arm64_page_offset_for(va_bits));
kasld_emit_scalar(SF_VIRT_ADDR_BITS, va_bits, CONF_INFERRED);
return 0;
#else
return 0;
#endif
}