-
Notifications
You must be signed in to change notification settings - Fork 109
/
syscalls_impl.rs
385 lines (354 loc) · 17.9 KB
/
syscalls_impl.rs
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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
//! Implements `Syscalls` for all types that implement `RawSyscalls`.
use crate::{
allow_ro, allow_rw, exit_id, exit_on_drop, return_variant, share, subscribe, syscall_class,
yield_id, AllowRo, AllowRw, CommandReturn, ErrorCode, RawSyscalls, Register, ReturnVariant,
Subscribe, Syscalls, Upcall, YieldNoWaitReturn,
};
impl<S: RawSyscalls> Syscalls for S {
// -------------------------------------------------------------------------
// Yield
// -------------------------------------------------------------------------
fn yield_no_wait() -> YieldNoWaitReturn {
let mut flag = core::mem::MaybeUninit::<YieldNoWaitReturn>::uninit();
unsafe {
// Flag can be uninitialized here because the kernel promises to
// only write to it, not read from it. MaybeUninit guarantees that
// it is safe to write a YieldNoWaitReturn into it.
Self::yield2([yield_id::NO_WAIT.into(), flag.as_mut_ptr().into()]);
// yield-no-wait guarantees it sets (initializes) flag before
// returning.
flag.assume_init()
}
}
fn yield_wait() {
// Safety: yield-wait does not return a value, which satisfies yield1's
// requirement. The yield-wait system call cannot trigger undefined
// behavior on its own in any other way.
unsafe {
Self::yield1([yield_id::WAIT.into()]);
}
}
// -------------------------------------------------------------------------
// Subscribe
// -------------------------------------------------------------------------
fn subscribe<
'share,
IDS: subscribe::SupportsId<DRIVER_NUM, SUBSCRIBE_NUM>,
U: Upcall<IDS>,
CONFIG: subscribe::Config,
const DRIVER_NUM: u32,
const SUBSCRIBE_NUM: u32,
>(
_subscribe: share::Handle<Subscribe<'share, Self, DRIVER_NUM, SUBSCRIBE_NUM>>,
upcall: &'share U,
) -> Result<(), ErrorCode> {
// The upcall function passed to the Tock kernel.
//
// Safety: data must be a reference to a valid instance of U.
unsafe extern "C" fn kernel_upcall<S: Syscalls, IDS, U: Upcall<IDS>>(
arg0: u32,
arg1: u32,
arg2: u32,
data: Register,
) {
let exit: exit_on_drop::ExitOnDrop<S> = Default::default();
let upcall: *const U = data.into();
unsafe { &*upcall }.upcall(arg0, arg1, arg2);
core::mem::forget(exit);
}
// Inner function that does the majority of the work. This is not
// monomorphized over DRIVER_NUM and SUBSCRIBE_NUM to keep code size
// small.
//
// Safety: upcall_fcn must be kernel_upcall<S, IDS, U> and upcall_data
// must be a reference to an instance of U that will remain valid as
// long as the 'scope lifetime is alive. Can only be called if a
// Subscribe<'scope, S, driver_num, subscribe_num> exists.
unsafe fn inner<S: Syscalls, CONFIG: subscribe::Config>(
driver_num: u32,
subscribe_num: u32,
upcall_fcn: Register,
upcall_data: Register,
) -> Result<(), ErrorCode> {
// Safety: syscall4's documentation indicates it can be used to call
// Subscribe. These arguments follow TRD104. kernel_upcall has the
// required signature. This function's preconditions mean that
// upcall is a reference to an instance of U that will remain valid
// until the 'scope lifetime is alive The existence of the
// Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM> guarantees
// that if this Subscribe succeeds then the upcall will be cleaned
// up before the 'scope lifetime ends, guaranteeing that upcall is
// still alive when kernel_upcall is invoked.
let [r0, r1, _, _] = unsafe {
S::syscall4::<{ syscall_class::SUBSCRIBE }>([
driver_num.into(),
subscribe_num.into(),
upcall_fcn,
upcall_data,
])
};
let return_variant: ReturnVariant = r0.as_u32().into();
// TRD 104 guarantees that Subscribe returns either Success with 2
// U32 or Failure with 2 U32. We check the return variant by
// comparing against Failure with 2 U32 for 2 reasons:
//
// 1. On RISC-V with compressed instructions, it generates smaller
// code. FAILURE_2_U32 has value 2, which can be loaded into a
// register with a single compressed instruction, whereas
// loading SUCCESS_2_U32 uses an uncompressed instruction.
// 2. In the event the kernel malfuctions and returns a different
// return variant, the success path is actually safer than the
// failure path. The failure path assumes that r1 contains an
// ErrorCode, and produces UB if it has an out of range value.
// Incorrectly assuming the call succeeded will not generate
// unsoundness, and will likely lead to the application
// hanging.
if return_variant == return_variant::FAILURE_2_U32 {
// Safety: TRD 104 guarantees that if r0 is Failure with 2 U32,
// then r1 will contain a valid error code. ErrorCode is
// designed to be safely transmuted directly from a kernel error
// code.
return Err(unsafe { core::mem::transmute(r1.as_u32()) });
}
// r0 indicates Success with 2 u32s. Confirm the null upcall was
// returned, and it if wasn't then call the configured function.
// We're relying on the optimizer to remove this branch if
// returned_nonnull_upcall is a no-op.
// Note: TRD 104 specifies that the null upcall has address 0,
// not necessarily a null pointer.
let returned_upcall: usize = r1.into();
if returned_upcall != 0usize {
CONFIG::returned_nonnull_upcall(driver_num, subscribe_num);
}
Ok(())
}
let upcall_fcn = (kernel_upcall::<S, IDS, U> as *const ()).into();
let upcall_data = (upcall as *const U).into();
// Safety: upcall's type guarantees it is a reference to a U that will
// remain valid for at least the 'scope lifetime. _subscribe is a
// reference to a Subscribe<'scope, Self, DRIVER_NUM, SUBSCRIBE_NUM>,
// proving one exists. upcall_fcn and upcall_data are derived in ways
// that satisfy inner's requirements.
unsafe { inner::<Self, CONFIG>(DRIVER_NUM, SUBSCRIBE_NUM, upcall_fcn, upcall_data) }
}
fn unsubscribe(driver_num: u32, subscribe_num: u32) {
unsafe {
// syscall4's documentation indicates it can be used to call
// Subscribe. The upcall pointer passed is the null upcall, which
// cannot cause undefined behavior on its own.
Self::syscall4::<{ syscall_class::SUBSCRIBE }>([
driver_num.into(),
subscribe_num.into(),
0usize.into(),
0usize.into(),
]);
}
}
// -------------------------------------------------------------------------
// Command
// -------------------------------------------------------------------------
fn command(driver_id: u32, command_id: u32, argument0: u32, argument1: u32) -> CommandReturn {
unsafe {
// syscall4's documentation indicates it can be used to call
// Command. The Command system call cannot trigger undefined
// behavior on its own.
let [r0, r1, r2, r3] = Self::syscall4::<{ syscall_class::COMMAND }>([
driver_id.into(),
command_id.into(),
argument0.into(),
argument1.into(),
]);
// Because r0 and r1 are returned directly from the kernel, we are
// guaranteed that if r0 represents a failure variant then r1 is an
// error code.
CommandReturn::new(r0.as_u32().into(), r1.as_u32(), r2.as_u32(), r3.as_u32())
}
}
// -------------------------------------------------------------------------
// Read-Write Allow
// -------------------------------------------------------------------------
fn allow_rw<'share, CONFIG: allow_rw::Config, const DRIVER_NUM: u32, const BUFFER_NUM: u32>(
_allow_rw: share::Handle<AllowRw<'share, Self, DRIVER_NUM, BUFFER_NUM>>,
buffer: &'share mut [u8],
) -> Result<(), ErrorCode> {
// Inner function that does the majority of the work. This is not
// monomorphized over DRIVER_NUM and BUFFER_NUM to keep code size small.
//
// Safety: A share::Handle<AllowRw<'share, S, driver_num, buffer_num>>
// must exist, and `buffer` must last for at least the 'share lifetime.
unsafe fn inner<S: Syscalls, CONFIG: allow_rw::Config>(
driver_num: u32,
buffer_num: u32,
buffer: &mut [u8],
) -> Result<(), ErrorCode> {
// Safety: syscall4's documentation indicates it can be used to call
// Read-Write Allow. These arguments follow TRD104.
let [r0, r1, r2, _] = unsafe {
S::syscall4::<{ syscall_class::ALLOW_RW }>([
driver_num.into(),
buffer_num.into(),
buffer.as_mut_ptr().into(),
buffer.len().into(),
])
};
let return_variant: ReturnVariant = r0.as_u32().into();
// TRD 104 guarantees that Read-Write Allow returns either Success
// with 2 U32 or Failure with 2 U32. We check the return variant by
// comparing against Failure with 2 U32 for 2 reasons:
//
// 1. On RISC-V with compressed instructions, it generates smaller
// code. FAILURE_2_U32 has value 2, which can be loaded into a
// register with a single compressed instruction, whereas
// loading SUCCESS_2_U32 uses an uncompressed instruction.
// 2. In the event the kernel malfuctions and returns a different
// return variant, the success path is actually safer than the
// failure path. The failure path assumes that r1 contains an
// ErrorCode, and produces UB if it has an out of range value.
// Incorrectly assuming the call succeeded will not generate
// unsoundness, and will likely lead to the application
// panicing.
if return_variant == return_variant::FAILURE_2_U32 {
// Safety: TRD 104 guarantees that if r0 is Failure with 2 U32,
// then r1 will contain a valid error code. ErrorCode is
// designed to be safely transmuted directly from a kernel error
// code.
return Err(unsafe { core::mem::transmute(r1.as_u32()) });
}
// r0 indicates Success with 2 u32s. Confirm a zero buffer was
// returned, and it if wasn't then call the configured function.
// We're relying on the optimizer to remove this branch if
// returned_nozero_buffer is a no-op.
let returned_buffer: (usize, usize) = (r1.into(), r2.into());
if returned_buffer != (0, 0) {
CONFIG::returned_nonzero_buffer(driver_num, buffer_num);
}
Ok(())
}
// Safety: The presence of the share::Handle<AllowRw<'share, ...>>
// guarantees that an AllowRw exists and will clean up this Allow ID
// before the 'share lifetime ends.
unsafe { inner::<Self, CONFIG>(DRIVER_NUM, BUFFER_NUM, buffer) }
}
fn unallow_rw(driver_num: u32, buffer_num: u32) {
unsafe {
// syscall4's documentation indicates it can be used to call
// Read-Write Allow. The buffer passed has 0 length, which cannot
// cause undefined behavior on its own.
Self::syscall4::<{ syscall_class::ALLOW_RW }>([
driver_num.into(),
buffer_num.into(),
0usize.into(),
0usize.into(),
]);
}
}
// -------------------------------------------------------------------------
// Read-Only Allow
// -------------------------------------------------------------------------
fn allow_ro<'share, CONFIG: allow_ro::Config, const DRIVER_NUM: u32, const BUFFER_NUM: u32>(
_allow_ro: share::Handle<AllowRo<'share, Self, DRIVER_NUM, BUFFER_NUM>>,
buffer: &'share [u8],
) -> Result<(), ErrorCode> {
// Inner function that does the majority of the work. This is not
// monomorphized over DRIVER_NUM and BUFFER_NUM to keep code size small.
//
// Security note: The syscall driver will retain read-only access to
// `*buffer` until this Allow ID is unallowed or overwritten via another
// Allow call. Therefore the caller must ensure the Allow ID is
// unallowed or overwritten before `*buffer` is deallocated, to avoid
// leaking newly-allocated information at the same address as `*buffer`.
fn inner<S: Syscalls, CONFIG: allow_ro::Config>(
driver_num: u32,
buffer_num: u32,
buffer: &[u8],
) -> Result<(), ErrorCode> {
// Safety: syscall4's documentation indicates it can be used to call
// Read-Only Allow. These arguments follow TRD104.
let [r0, r1, r2, _] = unsafe {
S::syscall4::<{ syscall_class::ALLOW_RO }>([
driver_num.into(),
buffer_num.into(),
buffer.as_ptr().into(),
buffer.len().into(),
])
};
let return_variant: ReturnVariant = r0.as_u32().into();
// TRD 104 guarantees that Read-Only Allow returns either Success
// with 2 U32 or Failure with 2 U32. We check the return variant by
// comparing against Failure with 2 U32 for 2 reasons:
//
// 1. On RISC-V with compressed instructions, it generates smaller
// code. FAILURE_2_U32 has value 2, which can be loaded into a
// register with a single compressed instruction, whereas
// loading SUCCESS_2_U32 uses an uncompressed instruction.
// 2. In the event the kernel malfuctions and returns a different
// return variant, the success path is actually safer than the
// failure path. The failure path assumes that r1 contains an
// ErrorCode, and produces UB if it has an out of range value.
// Incorrectly assuming the call succeeded will not generate
// unsoundness, and will likely lead to the application
// panicing.
if return_variant == return_variant::FAILURE_2_U32 {
// Safety: TRD 104 guarantees that if r0 is Failure with 2 U32,
// then r1 will contain a valid error code. ErrorCode is
// designed to be safely transmuted directly from a kernel error
// code.
return Err(unsafe { core::mem::transmute(r1.as_u32()) });
}
// r0 indicates Success with 2 u32s. Confirm a zero buffer was
// returned, and it if wasn't then call the configured function.
// We're relying on the optimizer to remove this branch if
// returned_nozero_buffer is a no-op.
let returned_buffer: (usize, usize) = (r1.into(), r2.into());
if returned_buffer != (0, 0) {
CONFIG::returned_nonzero_buffer(driver_num, buffer_num);
}
Ok(())
}
// Security: The presence of the share::Handle<AllowRo<'share, ...>>
// guarantees that an AllowRo exists and will clean up this Allow ID
// before the 'share lifetime ends.
inner::<Self, CONFIG>(DRIVER_NUM, BUFFER_NUM, buffer)
}
fn unallow_ro(driver_num: u32, buffer_num: u32) {
unsafe {
// syscall4's documentation indicates it can be used to call
// Read-Only Allow. The buffer passed has 0 length, which cannot
// cause undefined behavior on its own.
Self::syscall4::<{ syscall_class::ALLOW_RO }>([
driver_num.into(),
buffer_num.into(),
0usize.into(),
0usize.into(),
]);
}
}
// -------------------------------------------------------------------------
// Exit
// -------------------------------------------------------------------------
fn exit_terminate(exit_code: u32) -> ! {
unsafe {
// syscall2's documentation indicates it can be used to call Exit.
// The exit system call cannot trigger undefined behavior on its
// own.
Self::syscall2::<{ syscall_class::EXIT }>([
exit_id::TERMINATE.into(),
exit_code.into(),
]);
// TRD104 indicates that exit-terminate MUST always succeed and so
// never return.
core::hint::unreachable_unchecked()
}
}
fn exit_restart(exit_code: u32) -> ! {
unsafe {
// syscall2's documentation indicates it can be used to call Exit.
// The exit system call cannot trigger undefined behavior on its
// own.
Self::syscall2::<{ syscall_class::EXIT }>([exit_id::RESTART.into(), exit_code.into()]);
// TRD104 indicates that exit-restart MUST always succeed and so
// never return.
core::hint::unreachable_unchecked()
}
}
}