Repository navigation
Expand file tree
/
Copy pathexec.cpp
More file actions
239 lines (220 loc) · 11.8 KB
/
Copy pathexec.cpp
File metadata and controls
239 lines (220 loc) · 11.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
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#include "sys.h"
#include "handle.h"
#include <openkal/exec.h>
#include <openkal/map.h>
namespace okl { okl_ulong auxval(okl_ulong key); }
// openkal.exec on this kernel.
//
// A mapping obtained writable and made executable afterwards. The kernel places
// no condition upon either step, so this implementation provides the interface
// unconditionally --- unlike one of the other systems, where the same interface
// is available only to a program produced in a particular way, which clause 6.5
// covers and which is not this system's case.
//
// The reservation is not made executable at the outset even though this kernel
// would permit it. Two of the three environments the specification targets
// refuse a mapping that is both, so an implementation that returned one here
// would be offering a program a shape it could not use elsewhere --- and the
// program would discover that only on the other system. The interface states
// the narrower contract and this implementation keeps to it.
namespace {
// The kernel's page, as it told this program at inception (AT_PAGESZ). Four
// kibibytes was written here, and an aarch64 kernel built for sixteen or
// sixty-four rounds every protection to its own page, so a part published at a
// four-kibibyte boundary would have taken its neighbour with it.
okl_uptr page() {
static okl_uptr p = 0;
if (p == 0) {
const okl_ulong a = okl::auxval(6 /* AT_PAGESZ */);
p = (a != 0 && (a & (a - 1)) == 0) ? static_cast<okl_uptr>(a) : 4096;
}
return p;
}
okl_uptr round_up(okl_uptr n, okl_uptr to) { return (n + to - 1) & ~(to - 1); }
} // namespace
namespace {
// Whether this kernel has `mseal': it answers a range of no length with zero,
// and a kernel without it ENOSYS.
bool sealing() {
static int known = -1;
if (known < 0) known = okl::failed(okl::sys(okl::nr_mseal, 0, 0, 0)) ? 0 : 1;
return known == 1;
}
// Whether instructions mapped PROT_BTI are held to branch targets: the
// processor states it in AT_HWCAP2 (HWCAP2_BTI), and only on aarch64.
bool branch_targets() {
#if defined(__aarch64__)
return (okl::auxval(26 /* AT_HWCAP2 */) & (okl_ulong{1} << 17)) != 0;
#else
return false;
#endif
}
} // namespace
extern "C" {
void* kal_exec_alloc(kal_uintptr size) {
if (size == 0) return nullptr;
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), page());
const okl_long r = okl::sys(okl::nr_mmap, 0, static_cast<okl_long>(bytes),
okl::prot_read | okl::prot_write,
okl::map_private | okl::map_anonymous, -1, 0);
if (okl::failed(r)) return nullptr;
return reinterpret_cast<void*>(r);
}
// AT AN ADDRESS (0.23): MAP_FIXED_NOREPLACE, which refuses rather than replaces
// what is there. A kernel before 4.17 does not know the flag and reads the
// address as a hint, so the answer is compared and a mapping placed elsewhere
// released: the address is either given or refused, never approximated.
void* kal_exec_alloc_at(void* at, kal_uintptr size) {
const okl_uptr where = reinterpret_cast<okl_uptr>(at);
if (size == 0 || where == 0 || (where & (page() - 1)) != 0) return nullptr;
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), page());
const okl_long r = okl::sys(okl::nr_mmap, static_cast<okl_long>(where), static_cast<okl_long>(bytes),
okl::prot_read | okl::prot_write,
okl::map_private | okl::map_anonymous | 0x100000 /* MAP_FIXED_NOREPLACE */,
-1, 0);
if (okl::failed(r)) return nullptr;
if (static_cast<okl_uptr>(r) != where) {
okl::sys(okl::nr_munmap, r, static_cast<okl_long>(bytes));
return nullptr;
}
return at;
}
int kal_exec_publish(void* p, kal_uintptr size) {
if (p == nullptr || size == 0) return kal_err_invalid;
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), page());
const okl_long r = okl::sys(okl::nr_mprotect, reinterpret_cast<okl_long>(p),
static_cast<okl_long>(bytes),
okl::prot_read | okl::prot_exec);
if (okl::failed(r)) return okl::translate(r);
// THE INSTRUCTION CACHE IS NOT INVALIDATED HERE, AND ONE OF THE THREE
// IMPLEMENTATIONS DOES INVALIDATE IT. THE ASYMMETRY HAS A RULE.
//
// A processor with separate caches for data and instructions has just had
// bytes written through the data path that it is about to fetch through the
// instruction path, and nothing in the protection call makes the second path
// observe the first's writes.
//
// THE SPECIFICATION PLACES THE MAINTENANCE UPON THE PROGRAM, and the
// conformance suite performs it itself and says why: the program is the
// party that knows which bytes it wrote. So an implementation that performs
// it is being helpful rather than conforming, and one that does not is not
// deficient.
//
// AND THE ONLY MEANS AVAILABLE HERE IS ONE THIS IMPLEMENTATION MAY NOT
// USE. `__builtin___clear_cache' expands to nothing on x86_64 and becomes a
// CALL into the compiler's support library on the other two architectures
// --- `__riscv_flush_icache' on riscv64. This implementation is linked into
// programs that carry no other runtime, so acquiring that dependency to
// perform an operation the specification does not require of it is not a
// trade worth making.
//
// MEASURED, AND NOT ON THIS SYSTEM. openkal-macos added the builtin on
// the reading that aarch64 would expand it inline, and its own independence
// check reported within the hour:
//
// obj/exec.o references a symbol it must not: ___clear_cache
//
// ⇒ The three implementations share a rule rather than an accident:
//
// an implementation performs the maintenance where its environment
// offers it as a CALL of the environment's own --- openkal-windows has
// `FlushInstructionCache' --- and does not where the only means is a
// compiler builtin that becomes a dependency upon the compiler's
// support library.
//
// All three agree about what the PROGRAM must do, which is what the
// specification actually states.
return kal_ok;
}
// PART OF A REGION (0.17): the kernel protects per page, so a part is a run of
// whole pages, and a part that is not is refused rather than rounded --- a
// rounded part would take writable data beside it out of reach.
int kal_exec_publish_part(void* region, kal_uintptr offset, kal_uintptr size) {
const okl_uptr g = page();
if (region == nullptr || size == 0 || (offset & (g - 1)) != 0 || (size & (g - 1)) != 0
|| (reinterpret_cast<okl_uptr>(region) & (g - 1)) != 0)
return kal_err_invalid;
const okl_long r = okl::sys(okl::nr_mprotect,
reinterpret_cast<okl_long>(static_cast<char*>(region) + offset),
static_cast<okl_long>(size), okl::prot_read | okl::prot_exec);
return okl::failed(r) ? okl::translate(r) : kal_ok;
}
// SEALED READ-ONLY (0.23): the protection, and, for ever, `mseal' (Linux 6.10)
// over it, after which the kernel refuses to change the part's protection or to
// unmap it --- `mprotect' and `munmap' of it report EPERM, which this
// implementation reports as kal_err_permission.
int kal_exec_seal_part(void* region, kal_uintptr offset, kal_uintptr size, kal_uintptr flags) {
const okl_uptr g = page();
if (region == nullptr || size == 0 || (offset & (g - 1)) != 0 || (size & (g - 1)) != 0
|| (reinterpret_cast<okl_uptr>(region) & (g - 1)) != 0 || (flags & ~KAL_EXEC_SEAL_FOREVER) != 0)
return kal_err_invalid;
const okl_long at = reinterpret_cast<okl_long>(static_cast<char*>(region) + offset);
const okl_long r = okl::sys(okl::nr_mprotect, at, static_cast<okl_long>(size), okl::prot_read);
if (okl::failed(r)) return okl::translate(r);
if ((flags & KAL_EXEC_SEAL_FOREVER) && sealing()) {
const okl_long m = okl::sys(okl::nr_mseal, at, static_cast<okl_long>(size), 0);
if (okl::failed(m)) return okl::translate(m);
}
return kal_ok;
}
kal_uintptr kal_exec_granularity(void) { return page(); }
// A PART THAT IS A FILE'S (0.21): the file's pages mapped over the part,
// private, and executable where asked. MAP_FIXED replaces what the part held in
// one call, so there is no moment at which the addresses are free for another
// mapping to take. The arguments are checked first: a refused part leaves the
// region as it was.
int kal_exec_map_part(void* region, kal_uintptr offset, kal_uintptr size, kal_file f, kal_u64 at,
kal_uintptr how) {
const okl_uptr g = page();
const int fd = okl::unpack(f.h);
if (region == nullptr || size == 0 || fd < 0 || (offset & (g - 1)) != 0 || (size & (g - 1)) != 0
|| (reinterpret_cast<okl_uptr>(region) & (g - 1)) != 0 || at % KAL_MAP_ALIGNMENT != 0
|| (how & ~(KAL_EXEC_MAP_EXECUTE | KAL_EXEC_MAP_PREFETCH | KAL_EXEC_MAP_BTI | KAL_EXEC_MAP_WRITE)) != 0
|| ((how & KAL_EXEC_MAP_WRITE) && (how & KAL_EXEC_MAP_EXECUTE)))
return kal_err_invalid;
okl::kstat st{};
const okl_long s = okl::sys(okl::nr_fstat, fd, reinterpret_cast<okl_long>(&st));
if (okl::failed(s)) return okl::translate(s);
if ((st.mode & okl::s_ifmt) != okl::s_ifreg) return kal_err_not_supported;
const kal_u64 length = st.size > 0 ? static_cast<kal_u64>(st.size) : 0;
if (at > length || static_cast<kal_u64>(size) > length - at) return kal_err_invalid;
// READ IN NOW (0.23): MAP_POPULATE reads the part and fills its page
// tables in the one call, where each page would otherwise be a fault of its
// own on its first touch. BRANCH TARGETS: PROT_BTI, where the processor
// holds them.
const bool bti = (how & KAL_EXEC_MAP_BTI) && (how & KAL_EXEC_MAP_EXECUTE) && branch_targets();
// WRITTEN PRIVATELY (0.23): the mapping is private already, so a page the
// program writes is its own copy and the file is not changed.
const okl_long prot = okl::prot_read | ((how & KAL_EXEC_MAP_EXECUTE) ? okl::prot_exec : 0)
| ((how & KAL_EXEC_MAP_WRITE) ? okl::prot_write : 0)
| (bti ? 0x10 /* PROT_BTI */ : 0);
const okl_long populate = (how & KAL_EXEC_MAP_PREFETCH) ? 0x8000 /* MAP_POPULATE */ : 0;
const okl_long r = okl::sys(okl::nr_mmap,
reinterpret_cast<okl_long>(static_cast<char*>(region) + offset),
static_cast<okl_long>(size), prot, okl::map_private | okl::map_fixed | populate, fd,
static_cast<okl_long>(at));
if (okl::failed(r)) {
// ENODEV: the file system does not map. EPERM: its files are not
// executed (a volume mounted noexec). The resource refuses, not the
// implementation (exec.h).
if (-r == okl::e_nodev || -r == okl::e_perm) return kal_err_not_supported;
return okl::translate(r);
}
return kal_ok;
}
void kal_exec_free(void* p, kal_uintptr size) {
if (p == nullptr || size == 0) return;
const okl_uptr bytes = round_up(static_cast<okl_uptr>(size), page());
okl::sys(okl::nr_munmap, reinterpret_cast<okl_long>(p),
static_cast<okl_long>(bytes));
}
// A published region may be reserved for writing again: this kernel's
// protection call is not one-way. The position is set accordingly, and a
// caller that must change published bytes need not abandon the region.
kal_uintptr kal_exec_props(void) {
// Executable memory is available to every artifact on this kernel: nothing
// here is granted only to a program produced in a particular way.
return KAL_EXEC_PROP_REPUBLISH | KAL_EXEC_PROP_AVAILABLE | KAL_EXEC_PROP_FILE | KAL_EXEC_PROP_PLACE
| (sealing() ? KAL_EXEC_PROP_SEAL_FOREVER : 0u) | (branch_targets() ? KAL_EXEC_PROP_BTI : 0u);
}
} // extern "C"