1use std::{
5 any::{Any, TypeId},
6 cell::{Cell, RefCell},
7 collections::HashMap,
8 ffi::CStr,
9 marker::PhantomData,
10 mem::ManuallyDrop,
11 ops::{Deref, DerefMut},
12 pin::Pin,
13 ptr::NonNull,
14 rc::Rc,
15 sync::{
16 atomic::{compiler_fence, AtomicBool, AtomicU64, Ordering},
17 Arc, Once,
18 },
19 thread::ThreadId,
20 time::{Duration, Instant},
21};
22
23use corosensei::{
24 stack::{DefaultStack, Stack},
25 Coroutine, CoroutineResult, ScopedCoroutine, Yielder,
26};
27use futures::{Future, FutureExt};
28use memmap2::{MmapOptions, MmapRaw};
29use parking_lot::{Condvar, Mutex};
30use rand::prelude::SliceRandom;
31
32use crate::{
33 error::{Error, RuntimeError},
34 helpers::Helper,
35 linker::link_elf,
36 pointer_cage::PointerCage,
37 util::nonnull_bytes_overlap,
38};
39
40const NATIVE_STACK_SIZE: usize = 16384;
41const SHADOW_STACK_SIZE: usize = 4096;
42const MAX_CALLDATA_SIZE: usize = 512;
43const MAX_MUTABLE_DEREF_REGIONS: usize = 4;
44const MAX_IMMUTABLE_DEREF_REGIONS: usize = 16;
45
46pub struct InvokeScope {
48 data: HashMap<TypeId, Box<dyn Any + Send>>,
49}
50
51impl InvokeScope {
52 pub fn data_mut<T: Default + Send + 'static>(&mut self) -> &mut T {
54 let ty = TypeId::of::<T>();
55 self
56 .data
57 .entry(ty)
58 .or_insert_with(|| Box::new(T::default()))
59 .downcast_mut()
60 .expect("InvokeScope::data_mut: downcast failed")
61 }
62}
63
64pub struct HelperScope<'a, 'b> {
66 pub program: &'a Program,
68 pub invoke: RefCell<&'a mut InvokeScope>,
70 resources: RefCell<&'a mut [&'b mut dyn Any]>,
71 mutable_dereferenced_regions: [Cell<Option<NonNull<[u8]>>>; MAX_MUTABLE_DEREF_REGIONS],
72 immutable_dereferenced_regions: [Cell<Option<NonNull<[u8]>>>; MAX_IMMUTABLE_DEREF_REGIONS],
73 can_post_task: bool,
74}
75
76pub struct MutableUserMemory<'a, 'b, 'c> {
78 _scope: &'c HelperScope<'a, 'b>,
79 region: NonNull<[u8]>,
80}
81
82impl<'a, 'b, 'c> Deref for MutableUserMemory<'a, 'b, 'c> {
83 type Target = [u8];
84
85 fn deref(&self) -> &Self::Target {
86 unsafe { self.region.as_ref() }
87 }
88}
89
90impl<'a, 'b, 'c> DerefMut for MutableUserMemory<'a, 'b, 'c> {
91 fn deref_mut(&mut self) -> &mut Self::Target {
92 unsafe { self.region.as_mut() }
93 }
94}
95
96impl<'a, 'b> HelperScope<'a, 'b> {
97 pub fn post_task(
99 &self,
100 task: impl Future<Output = impl FnOnce(&HelperScope) -> Result<u64, ()> + 'static> + 'static,
101 ) {
102 if !self.can_post_task {
103 panic!("HelperScope::post_task() called in a context where posting task is not allowed");
104 }
105
106 PENDING_ASYNC_TASK.with(|x| {
107 let mut x = x.borrow_mut();
108 if x.is_some() {
109 panic!("post_task called while another task is pending");
110 }
111 *x = Some(async move { Box::new(task.await) as AsyncTaskOutput }.boxed_local());
112 });
113 }
114
115 pub fn with_resource_mut<'c, T: 'static, R>(
117 &'c self,
118 callback: impl FnOnce(Result<&mut T, ()>) -> R,
119 ) -> R {
120 let mut resources = self.resources.borrow_mut();
121 let Some(res) = resources
122 .iter_mut()
123 .filter_map(|x| x.downcast_mut::<T>())
124 .next()
125 else {
126 tracing::warn!(resource_type = ?TypeId::of::<T>(), "resource not found");
127 return callback(Err(()));
128 };
129
130 callback(Ok(res))
131 }
132
133 pub fn user_memory(&self, ptr: u64, size: u64) -> Result<&[u8], ()> {
135 let Some(region) = self
136 .program
137 .unbound
138 .cage
139 .safe_deref_for_read(ptr as usize, size as usize)
140 else {
141 tracing::warn!(ptr, size, "invalid read");
142 return Err(());
143 };
144
145 if size != 0 {
146 if self
148 .mutable_dereferenced_regions
149 .iter()
150 .filter_map(|x| x.get())
151 .any(|x| nonnull_bytes_overlap(x, region))
152 {
153 tracing::warn!(ptr, size, "read overlapped with previous write");
154 return Err(());
155 }
156
157 let Some(slot) = self
159 .immutable_dereferenced_regions
160 .iter()
161 .find(|x| x.get().is_none())
162 else {
163 tracing::warn!(ptr, size, "too many reads");
164 return Err(());
165 };
166 slot.set(Some(region));
167 }
168
169 Ok(unsafe { region.as_ref() })
170 }
171
172 pub fn user_memory_mut<'c>(
174 &'c self,
175 ptr: u64,
176 size: u64,
177 ) -> Result<MutableUserMemory<'a, 'b, 'c>, ()> {
178 let Some(region) = self
179 .program
180 .unbound
181 .cage
182 .safe_deref_for_write(ptr as usize, size as usize)
183 else {
184 tracing::warn!(ptr, size, "invalid write");
185 return Err(());
186 };
187
188 if size != 0 {
189 if self
191 .mutable_dereferenced_regions
192 .iter()
193 .chain(self.immutable_dereferenced_regions.iter())
194 .filter_map(|x| x.get())
195 .any(|x| nonnull_bytes_overlap(x, region))
196 {
197 tracing::warn!(ptr, size, "write overlapped with previous read/write");
198 return Err(());
199 }
200
201 let Some(slot) = self
203 .mutable_dereferenced_regions
204 .iter()
205 .find(|x| x.get().is_none())
206 else {
207 tracing::warn!(ptr, size, "too many writes");
208 return Err(());
209 };
210 slot.set(Some(region));
211 }
212
213 Ok(MutableUserMemory {
214 _scope: self,
215 region,
216 })
217 }
218}
219
220#[derive(Copy, Clone)]
221struct AssumeSend<T>(T);
222unsafe impl<T> Send for AssumeSend<T> {}
223
224struct ExecContext {
225 native_stack: DefaultStack,
226 copy_stack: Box<[u8; SHADOW_STACK_SIZE]>,
227}
228
229impl ExecContext {
230 fn new() -> Self {
231 Self {
232 native_stack: DefaultStack::new(NATIVE_STACK_SIZE)
233 .expect("failed to initialize native stack"),
234 copy_stack: Box::new([0u8; SHADOW_STACK_SIZE]),
235 }
236 }
237}
238
239pub type PendingAsyncTask = Pin<Box<dyn Future<Output = AsyncTaskOutput>>>;
241pub type AsyncTaskOutput = Box<dyn FnOnce(&HelperScope) -> Result<u64, ()>>;
243
244static NEXT_PROGRAM_ID: AtomicU64 = AtomicU64::new(1);
245
246#[derive(Copy, Clone, Debug)]
247enum PreemptionState {
248 Inactive,
249 Active(usize),
250 Shutdown,
251}
252
253type PreemptionStateSignal = (Mutex<PreemptionState>, Condvar);
254
255thread_local! {
256 static RUST_TID: ThreadId = std::thread::current().id();
257 static SIGUSR1_COUNTER: Cell< u64> = Cell::new(0);
258 static ACTIVE_JIT_CODE_ZONE: ActiveJitCodeZone = ActiveJitCodeZone::default();
259 static EXEC_CONTEXT_POOL: RefCell<Vec<ExecContext>> = Default::default();
260 static PENDING_ASYNC_TASK: RefCell<Option<PendingAsyncTask>> = RefCell::new(None);
261 static PREEMPTION_STATE: Arc<PreemptionStateSignal> = Arc::new((Mutex::new(PreemptionState::Inactive), Condvar::new()));
262 static LOADING_PROGRAM_LOADER: Cell<*const ProgramLoader> = const { Cell::new(std::ptr::null()) };
263}
264
265struct BorrowedExecContext {
266 ctx: ManuallyDrop<ExecContext>,
267}
268
269impl BorrowedExecContext {
270 fn new() -> Self {
271 let mut me = Self {
272 ctx: ManuallyDrop::new(
273 EXEC_CONTEXT_POOL.with(|x| x.borrow_mut().pop().unwrap_or_else(ExecContext::new)),
274 ),
275 };
276 me.ctx.copy_stack.fill(0x8e);
277 me
278 }
279}
280
281impl Drop for BorrowedExecContext {
282 fn drop(&mut self) {
283 let ctx = unsafe { ManuallyDrop::take(&mut self.ctx) };
284 EXEC_CONTEXT_POOL.with(|x| x.borrow_mut().push(ctx));
285 }
286}
287
288#[derive(Default)]
289struct ActiveJitCodeZone {
290 valid: AtomicBool,
291 code_range: Cell<(usize, usize)>,
292 pointer_cage_protected_range: Cell<(usize, usize)>,
293 yielder: Cell<Option<NonNull<Yielder<u64, Dispatch>>>>,
294}
295
296pub trait ProgramEventListener: Send + Sync + 'static {
298 fn did_async_preempt(&self, _scope: &HelperScope) {}
300 fn did_yield(&self) {}
302 fn did_throttle(&self, _scope: &HelperScope) -> Option<Pin<Box<dyn Future<Output = ()>>>> {
304 None
305 }
306 fn did_save_shadow_stack(&self) {}
308 fn did_restore_shadow_stack(&self) {}
310}
311
312pub struct DummyProgramEventListener;
314impl ProgramEventListener for DummyProgramEventListener {}
315
316pub const DEFAULT_CODE_SIZE_LIMIT: usize = 1 << 20;
318
319pub struct ProgramLoader {
321 helpers_inverse: HashMap<&'static str, i32>,
322 event_listener: Arc<dyn ProgramEventListener>,
323 helper_id_xor: u16,
324 helpers: Arc<Vec<(u16, &'static str, Helper)>>,
325 code_size_limit: usize,
326}
327
328pub struct UnboundProgram {
330 id: u64,
331 _code_mem: MmapRaw,
332 cage: PointerCage,
333 helper_id_xor: u16,
334 helpers: Arc<Vec<(u16, &'static str, Helper)>>,
335 event_listener: Arc<dyn ProgramEventListener>,
336 entrypoints: HashMap<String, Entrypoint>,
337}
338
339pub struct Program {
341 unbound: UnboundProgram,
342 data: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
343 t: ThreadEnv,
344}
345
346#[derive(Copy, Clone)]
347struct Entrypoint {
348 code_ptr: usize,
349 code_len: usize,
350}
351
352#[derive(Clone, Debug)]
354pub struct TimesliceConfig {
355 pub max_run_time_before_yield: Duration,
357 pub max_run_time_before_throttle: Duration,
359 pub throttle_duration: Duration,
361}
362
363pub trait Timeslicer {
365 fn sleep(&self, duration: Duration) -> impl Future<Output = ()>;
367 fn yield_now(&self) -> impl Future<Output = ()>;
369}
370
371#[derive(Copy, Clone)]
373pub struct GlobalEnv(());
374
375#[derive(Copy, Clone)]
377pub struct ThreadEnv {
378 _not_send_sync: std::marker::PhantomData<*const ()>,
379}
380
381impl GlobalEnv {
382 pub unsafe fn new() -> Self {
387 static INIT: Once = Once::new();
388
389 INIT.call_once(|| {
404 let sa_mask = get_blocked_sigset();
405
406 for (sig, handler) in [
407 (libc::SIGUSR1, sigusr1_handler as *const () as usize),
408 (libc::SIGSEGV, sigsegv_handler as *const () as usize),
409 ] {
410 let act = libc::sigaction {
411 sa_sigaction: handler,
412 sa_flags: libc::SA_SIGINFO,
413 sa_mask,
414 sa_restorer: None,
415 };
416 if libc::sigaction(sig, &act, std::ptr::null_mut()) != 0 {
417 panic!("failed to setup handler for signal {}", sig);
418 }
419 }
420 });
421
422 Self(())
423 }
424
425 pub fn init_thread(self, async_preemption_interval: Duration) -> ThreadEnv {
427 struct DeferDrop(Arc<PreemptionStateSignal>);
428 impl Drop for DeferDrop {
429 fn drop(&mut self) {
430 let x = &self.0;
431 *x.0.lock() = PreemptionState::Shutdown;
432 x.1.notify_one();
433 }
434 }
435
436 thread_local! {
437 static WATCHER: RefCell<Option<DeferDrop>> = RefCell::new(None);
438 }
439
440 if WATCHER.with(|x| x.borrow().is_some()) {
441 return ThreadEnv {
442 _not_send_sync: PhantomData,
443 };
444 }
445
446 let preemption_state = PREEMPTION_STATE.with(|x| x.clone());
447
448 unsafe {
449 let tgid = libc::getpid();
450 let tid = libc::gettid();
451
452 std::thread::Builder::new()
453 .name("preempt-watcher".to_string())
454 .spawn(move || {
455 let mut state = preemption_state.0.lock();
456 loop {
457 match *state {
458 PreemptionState::Shutdown => break,
459 PreemptionState::Inactive => {
460 preemption_state.1.wait(&mut state);
461 }
462 PreemptionState::Active(_) => {
463 let timeout = preemption_state.1.wait_while_for(
464 &mut state,
465 |x| matches!(x, PreemptionState::Active(_)),
466 async_preemption_interval,
467 );
468 if timeout.timed_out() {
469 let ret = libc::syscall(libc::SYS_tgkill, tgid, tid, libc::SIGUSR1);
470 if ret != 0 {
471 break;
472 }
473 }
474 }
475 }
476 }
477 })
478 .expect("failed to spawn preemption watcher");
479
480 WATCHER.with(|x| {
481 x.borrow_mut()
482 .replace(DeferDrop(PREEMPTION_STATE.with(|x| x.clone())));
483 });
484
485 ThreadEnv {
486 _not_send_sync: PhantomData,
487 }
488 }
489 }
490}
491
492impl UnboundProgram {
493 pub fn pin_to_current_thread(self, t: ThreadEnv) -> Program {
495 Program {
496 unbound: self,
497 data: RefCell::new(HashMap::new()),
498 t,
499 }
500 }
501}
502
503pub struct PreemptionEnabled(());
504
505impl PreemptionEnabled {
506 pub fn new(_: ThreadEnv) -> Self {
507 PREEMPTION_STATE.with(|x| {
508 let mut notify = false;
509 {
510 let mut st = x.0.lock();
511 let next = match *st {
512 PreemptionState::Inactive => {
513 notify = true;
514 PreemptionState::Active(1)
515 }
516 PreemptionState::Active(n) => PreemptionState::Active(n + 1),
517 PreemptionState::Shutdown => unreachable!(),
518 };
519 *st = next;
520 }
521
522 if notify {
523 x.1.notify_one();
524 }
525 });
526 Self(())
527 }
528}
529
530impl Drop for PreemptionEnabled {
531 fn drop(&mut self) {
532 PREEMPTION_STATE.with(|x| {
533 let mut st = x.0.lock();
534 let next = match *st {
535 PreemptionState::Active(1) => PreemptionState::Inactive,
536 PreemptionState::Active(n) => {
537 assert!(n > 1);
538 PreemptionState::Active(n - 1)
539 }
540 PreemptionState::Inactive | PreemptionState::Shutdown => unreachable!(),
541 };
542 *st = next;
543 });
544 }
545}
546
547impl Program {
548 pub fn id(&self) -> u64 {
550 self.unbound.id
551 }
552
553 pub fn thread_env(&self) -> ThreadEnv {
554 self.t
555 }
556
557 pub fn data<T: Default + 'static>(&self) -> Rc<T> {
559 let mut data = self.data.borrow_mut();
560 let entry = data.entry(TypeId::of::<T>());
561 let entry = entry.or_insert_with(|| Rc::new(T::default()));
562 entry.clone().downcast().unwrap()
563 }
564
565 pub fn has_section(&self, name: &str) -> bool {
566 self.unbound.entrypoints.contains_key(name)
567 }
568
569 pub async fn run(
571 &self,
572 timeslice: &TimesliceConfig,
573 timeslicer: &impl Timeslicer,
574 entrypoint: &str,
575 resources: &mut [&mut dyn Any],
576 calldata: &[u8],
577 preemption: &PreemptionEnabled,
578 ) -> Result<i64, Error> {
579 self
580 ._run(
581 timeslice, timeslicer, entrypoint, resources, calldata, preemption,
582 )
583 .await
584 .map_err(Error)
585 }
586
587 async fn _run(
588 &self,
589 timeslice: &TimesliceConfig,
590 timeslicer: &impl Timeslicer,
591 entrypoint: &str,
592 resources: &mut [&mut dyn Any],
593 calldata: &[u8],
594 _: &PreemptionEnabled,
595 ) -> Result<i64, RuntimeError> {
596 let Some(entrypoint) = self.unbound.entrypoints.get(entrypoint).copied() else {
597 return Err(RuntimeError::InvalidArgument("entrypoint not found"));
598 };
599
600 let entry = unsafe {
601 std::mem::transmute::<
602 _,
603 unsafe extern "C" fn(ctx: usize, mem_len: usize, stack: usize, stack_len: usize) -> u64,
604 >(entrypoint.code_ptr)
605 };
606 struct CoDropper<'a, Input, Yield, Return, DefaultStack: Stack>(
607 ScopedCoroutine<'a, Input, Yield, Return, DefaultStack>,
608 );
609 impl<'a, Input, Yield, Return, DefaultStack: Stack> Drop
610 for CoDropper<'a, Input, Yield, Return, DefaultStack>
611 {
612 fn drop(&mut self) {
613 unsafe {
621 self.0.force_reset();
622 }
623 }
624 }
625
626 let mut ectx = BorrowedExecContext::new();
627
628 if calldata.len() > MAX_CALLDATA_SIZE {
629 return Err(RuntimeError::InvalidArgument("calldata too large"));
630 }
631 ectx.ctx.copy_stack[SHADOW_STACK_SIZE - calldata.len()..].copy_from_slice(calldata);
632 let calldata_len = calldata.len();
633
634 let program_ret: u64 = {
635 let shadow_stack_top = self.unbound.cage.stack_top();
636 let shadow_stack_bottom = self.unbound.cage.stack_bottom();
637 let shadow_stack_ptr = AssumeSend(
638 self
639 .unbound
640 .cage
641 .safe_deref_for_write(self.unbound.cage.stack_bottom(), SHADOW_STACK_SIZE)
642 .unwrap(),
643 );
644 let ctx = &mut *ectx.ctx;
645
646 let mut co = AssumeSend(CoDropper(Coroutine::with_stack(
647 &mut ctx.native_stack,
648 move |yielder, _input| unsafe {
649 ACTIVE_JIT_CODE_ZONE.with(|x| {
650 x.yielder.set(NonNull::new(yielder as *const _ as *mut _));
651 });
652 let calldata_start = shadow_stack_top - calldata_len;
653 let stack_top = calldata_start & !0x7;
654 let stack_len = stack_top - shadow_stack_bottom;
655 entry(
656 calldata_start,
657 calldata_start,
658 shadow_stack_bottom,
659 stack_len,
660 )
661 },
662 )));
663
664 let mut last_yield_time = Instant::now();
665 let mut last_throttle_time = Instant::now();
666 let mut yielder: Option<AssumeSend<NonNull<Yielder<u64, Dispatch>>>> = None;
667 let mut resume_input: u64 = 0;
668 let mut did_throttle = false;
669 let mut shadow_stack_saved = true;
670 let mut rust_tid_sigusr1_counter = (RUST_TID.with(|x| *x), SIGUSR1_COUNTER.with(|x| x.get()));
671 let mut prev_async_task_output: Option<(&'static str, AsyncTaskOutput)> = None;
672 let mut invoke_scope = InvokeScope {
673 data: HashMap::new(),
674 };
675
676 loop {
677 ACTIVE_JIT_CODE_ZONE.with(|x| {
678 x.code_range.set((
679 entrypoint.code_ptr,
680 entrypoint.code_ptr + entrypoint.code_len,
681 ));
682 x.yielder.set(yielder.map(|x| x.0));
683 x.pointer_cage_protected_range
684 .set(self.unbound.cage.protected_range_without_margins());
685 compiler_fence(Ordering::Release);
686 x.valid.store(true, Ordering::Relaxed);
687 });
688
689 if shadow_stack_saved {
690 shadow_stack_saved = false;
691
692 unsafe {
694 std::ptr::copy_nonoverlapping(
695 ctx.copy_stack.as_ptr() as *const u8,
696 shadow_stack_ptr.0.as_ptr() as *mut u8,
697 SHADOW_STACK_SIZE,
698 );
699 }
700
701 self.unbound.event_listener.did_restore_shadow_stack();
702 }
703
704 if let Some((helper_name, prev_async_task_output)) = prev_async_task_output.take() {
706 resume_input = prev_async_task_output(&HelperScope {
707 program: self,
708 invoke: RefCell::new(&mut invoke_scope),
709 resources: RefCell::new(resources),
710 mutable_dereferenced_regions: unsafe { std::mem::zeroed() },
711 immutable_dereferenced_regions: unsafe { std::mem::zeroed() },
712 can_post_task: false,
713 })
714 .map_err(|_| RuntimeError::AsyncHelperError(helper_name))?;
715 }
716
717 let ret = co.0 .0.resume(resume_input);
718 ACTIVE_JIT_CODE_ZONE.with(|x| {
719 x.valid.store(false, Ordering::Relaxed);
720 compiler_fence(Ordering::Release);
721 yielder = x.yielder.get().map(AssumeSend);
722 });
723
724 let dispatch: Dispatch = match ret {
725 CoroutineResult::Return(x) => break x,
726 CoroutineResult::Yield(x) => x,
727 };
728
729 if dispatch.memory_access_error.is_some() || dispatch.async_preemption {
731 unsafe {
732 let unblock = get_blocked_sigset();
733 libc::sigprocmask(libc::SIG_UNBLOCK, &unblock, std::ptr::null_mut());
734 }
735 }
736
737 if let Some(si_addr) = dispatch.memory_access_error {
738 let vaddr = si_addr - self.unbound.cage.offset();
739 return Err(RuntimeError::MemoryFault(vaddr));
740 }
741
742 PENDING_ASYNC_TASK.with(|x| x.borrow_mut().take());
744 let mut helper_name: &'static str = "";
745
746 let mut helper_scope = HelperScope {
747 program: self,
748 invoke: RefCell::new(&mut invoke_scope),
749 resources: RefCell::new(resources),
750 mutable_dereferenced_regions: unsafe { std::mem::zeroed() },
751 immutable_dereferenced_regions: unsafe { std::mem::zeroed() },
752 can_post_task: false,
753 };
754
755 if dispatch.async_preemption {
756 self
757 .unbound
758 .event_listener
759 .did_async_preempt(&mut helper_scope);
760 } else {
761 let Some((_, got_helper_name, helper)) = self
763 .unbound
764 .helpers
765 .get(
766 ((dispatch.index & 0xffff) as u16 ^ self.unbound.helper_id_xor).wrapping_sub(1)
767 as usize,
768 )
769 .copied()
770 else {
771 panic!("unknown helper index: {}", dispatch.index);
772 };
773 helper_name = got_helper_name;
774
775 helper_scope.can_post_task = true;
776 resume_input = helper(
777 &mut helper_scope,
778 dispatch.arg1,
779 dispatch.arg2,
780 dispatch.arg3,
781 dispatch.arg4,
782 dispatch.arg5,
783 )
784 .map_err(|()| RuntimeError::HelperError(helper_name))?;
785 helper_scope.can_post_task = false;
786 }
787
788 let pending_async_task = PENDING_ASYNC_TASK.with(|x| x.borrow_mut().take());
789
790 let new_rust_tid_sigusr1_counter =
792 (RUST_TID.with(|x| *x), SIGUSR1_COUNTER.with(|x| x.get()));
793 if new_rust_tid_sigusr1_counter == rust_tid_sigusr1_counter && pending_async_task.is_none()
794 {
795 continue;
796 }
797
798 rust_tid_sigusr1_counter = new_rust_tid_sigusr1_counter;
799
800 let now = Instant::now();
801 let should_throttle = now > last_throttle_time
802 && now.duration_since(last_throttle_time) >= timeslice.max_run_time_before_throttle;
803 let should_yield = now > last_yield_time
804 && now.duration_since(last_yield_time) >= timeslice.max_run_time_before_yield;
805 if should_throttle || should_yield || pending_async_task.is_some() {
806 shadow_stack_saved = true;
808 unsafe {
809 std::ptr::copy_nonoverlapping(
810 shadow_stack_ptr.0.as_ptr() as *const u8,
811 ctx.copy_stack.as_mut_ptr() as *mut u8,
812 SHADOW_STACK_SIZE,
813 );
814 }
815 self.unbound.event_listener.did_save_shadow_stack();
816
817 if should_throttle {
820 if !did_throttle {
821 did_throttle = true;
822 tracing::warn!("throttling program");
823 }
824 timeslicer.sleep(timeslice.throttle_duration).await;
825 let now = Instant::now();
826 last_throttle_time = now;
827 last_yield_time = now;
828 let task = self.unbound.event_listener.did_throttle(&mut helper_scope);
829 if let Some(task) = task {
830 task.await;
831 }
832 } else if should_yield {
833 timeslicer.yield_now().await;
834 let now = Instant::now();
835 last_yield_time = now;
836 self.unbound.event_listener.did_yield();
837 }
838
839 if let Some(pending_async_task) = pending_async_task {
841 let async_start = Instant::now();
842 prev_async_task_output = Some((helper_name, pending_async_task.await));
843 let async_dur = async_start.elapsed();
844 last_throttle_time += async_dur;
845 last_yield_time += async_dur;
846 }
847 }
848 }
849 };
850
851 Ok(program_ret as i64)
852 }
853}
854
855struct Vm(NonNull<crate::ubpf::ubpf_vm>);
856
857impl Vm {
858 fn new(cage: &PointerCage) -> Self {
859 let vm = NonNull::new(unsafe { crate::ubpf::ubpf_create() }).expect("failed to create ubpf_vm");
860 unsafe {
861 crate::ubpf::ubpf_toggle_bounds_check(vm.as_ptr(), false);
862 crate::ubpf::ubpf_set_jit_pointer_mask_and_offset(vm.as_ptr(), cage.mask(), cage.offset());
863 }
864 Self(vm)
865 }
866}
867
868impl Drop for Vm {
869 fn drop(&mut self) {
870 unsafe {
871 crate::ubpf::ubpf_destroy(self.0.as_ptr());
872 }
873 }
874}
875
876struct LoaderValidationScope {
877 previous: *const ProgramLoader,
878}
879
880impl LoaderValidationScope {
881 fn new(loader: &ProgramLoader) -> Self {
882 let previous = LOADING_PROGRAM_LOADER.with(|x| {
883 let previous = x.get();
884 x.set(loader as *const _);
885 previous
886 });
887 Self { previous }
888 }
889}
890
891impl Drop for LoaderValidationScope {
892 fn drop(&mut self) {
893 LOADING_PROGRAM_LOADER.with(|x| x.set(self.previous));
894 }
895}
896
897impl ProgramLoader {
898 pub fn new(
900 rng: &mut impl rand::Rng,
901 event_listener: Arc<dyn ProgramEventListener>,
902 raw_helpers: &[&[(&'static str, Helper)]],
903 ) -> Self {
904 let helper_id_xor = rng.gen::<u16>();
905 let mut helpers_inverse: HashMap<&'static str, i32> = HashMap::new();
906 let mut shuffled_helpers = raw_helpers
908 .iter()
909 .flat_map(|x| x.iter().copied())
910 .collect::<HashMap<_, _>>()
911 .into_iter()
912 .collect::<Vec<_>>();
913 shuffled_helpers.shuffle(rng);
914 let mut helpers: Vec<(u16, &'static str, Helper)> = Vec::with_capacity(shuffled_helpers.len());
915
916 assert!(shuffled_helpers.len() <= 65535);
917
918 for (i, (name, helper)) in shuffled_helpers.into_iter().enumerate() {
919 let entropy = rng.gen::<u16>() & 0x7fff;
920 helpers.push((entropy, name, helper));
921 helpers_inverse.insert(
922 name,
923 (((entropy as usize) << 16) | ((i + 1) ^ (helper_id_xor as usize))) as i32,
924 );
925 }
926
927 tracing::info!(?helpers_inverse, "generated helper table");
928 Self {
929 helper_id_xor,
930 helpers: Arc::new(helpers),
931 helpers_inverse,
932 event_listener,
933 code_size_limit: DEFAULT_CODE_SIZE_LIMIT,
934 }
935 }
936
937 pub fn with_code_size_limit(mut self, limit: usize) -> Self {
950 assert!(
951 limit > 0 && limit % (64 * 1024) == 0,
952 "code size limit must be a non-zero multiple of 64 KiB"
953 );
954 assert!(limit <= u32::MAX as usize, "code size limit must fit in u32");
955 self.code_size_limit = limit;
956 self
957 }
958
959 pub fn load(&self, rng: &mut impl rand::Rng, elf: &[u8]) -> Result<UnboundProgram, Error> {
961 self._load(rng, elf).map_err(Error)
962 }
963
964 fn _load(&self, rng: &mut impl rand::Rng, elf: &[u8]) -> Result<UnboundProgram, RuntimeError> {
965 let start_time = Instant::now();
966 let cage = PointerCage::new(rng, SHADOW_STACK_SIZE, elf.len())?;
967 let vm = Vm::new(&cage);
968
969 let code_sections = {
971 let mut data = cage
975 .safe_deref_for_read(cage.data_bottom(), elf.len())
976 .unwrap();
977 let data = unsafe { data.as_mut() };
978 data.copy_from_slice(elf);
979
980 link_elf(data, cage.data_bottom(), &self.helpers_inverse).map_err(RuntimeError::Linker)?
981 };
982 cage.freeze_data();
983
984 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
985 if page_size < 0 {
986 return Err(RuntimeError::PlatformError("failed to get page size"));
987 }
988 let page_size = page_size as usize;
989
990 let guard_size_before = rng.gen_range(16..128) * page_size;
992 let mut guard_size_after = rng.gen_range(16..128) * page_size;
993
994 let code_len_allocated = self.code_size_limit;
995 let code_mem = MmapRaw::from(
996 MmapOptions::new()
997 .len(code_len_allocated + guard_size_before + guard_size_after)
998 .map_anon()
999 .map_err(|_| RuntimeError::PlatformError("failed to allocate code memory"))?,
1000 );
1001
1002 unsafe {
1003 if crate::ubpf::ubpf_register_external_dispatcher(
1004 vm.0.as_ptr(),
1005 Some(tls_dispatcher),
1006 Some(std_validator),
1007 ) != 0
1008 {
1009 return Err(RuntimeError::PlatformError(
1010 "ubpf: failed to register external dispatcher",
1011 ));
1012 }
1013 if libc::mprotect(
1014 code_mem.as_mut_ptr() as *mut _,
1015 guard_size_before,
1016 libc::PROT_NONE,
1017 ) != 0
1018 || libc::mprotect(
1019 code_mem
1020 .as_mut_ptr()
1021 .offset((guard_size_before + code_len_allocated) as isize) as *mut _,
1022 guard_size_after,
1023 libc::PROT_NONE,
1024 ) != 0
1025 {
1026 return Err(RuntimeError::PlatformError("failed to protect guard pages"));
1027 }
1028 }
1029
1030 let mut entrypoints: HashMap<String, Entrypoint> = HashMap::new();
1031
1032 unsafe {
1033 let mut code_slice = std::slice::from_raw_parts_mut(
1035 code_mem.as_mut_ptr().offset(guard_size_before as isize),
1036 code_len_allocated,
1037 );
1038 for (section_name, code_vaddr_size) in code_sections {
1039 if code_slice.is_empty() {
1040 return Err(RuntimeError::InvalidArgument(
1041 "no space left for jit compilation",
1042 ));
1043 }
1044
1045 crate::ubpf::ubpf_unload_code(vm.0.as_ptr());
1046
1047 let mut errmsg_ptr = std::ptr::null_mut();
1048 let code = cage
1049 .safe_deref_for_read(code_vaddr_size.0, code_vaddr_size.1)
1050 .unwrap();
1051 let ret = {
1052 let validation_scope = LoaderValidationScope::new(self);
1053 let ret = crate::ubpf::ubpf_load(
1054 vm.0.as_ptr(),
1055 code.as_ptr() as *const _,
1056 code.len() as u32,
1057 &mut errmsg_ptr,
1058 );
1059 drop(validation_scope);
1060 ret
1061 };
1062 if ret != 0 {
1063 let errmsg = if errmsg_ptr.is_null() {
1064 "".to_string()
1065 } else {
1066 CStr::from_ptr(errmsg_ptr).to_string_lossy().into_owned()
1067 };
1068 if !errmsg_ptr.is_null() {
1069 libc::free(errmsg_ptr as _);
1070 }
1071 tracing::error!(section_name, error = errmsg, "failed to load code");
1072 return Err(RuntimeError::PlatformError("ubpf: code load failed"));
1073 }
1074
1075 let mut written_len = code_slice.len();
1076 let ret = crate::ubpf::ubpf_translate_ex(
1077 vm.0.as_ptr(),
1078 code_slice.as_mut_ptr(),
1079 &mut written_len,
1080 &mut errmsg_ptr,
1081 crate::ubpf::JitMode_ExtendedJitMode,
1082 );
1083 if ret != 0 {
1084 let errmsg = if errmsg_ptr.is_null() {
1085 "".to_string()
1086 } else {
1087 CStr::from_ptr(errmsg_ptr).to_string_lossy().into_owned()
1088 };
1089 if !errmsg_ptr.is_null() {
1090 libc::free(errmsg_ptr as _);
1091 }
1092 tracing::error!(section_name, error = errmsg, "failed to translate code");
1093 return Err(RuntimeError::PlatformError("ubpf: code translation failed"));
1094 }
1095
1096 assert!(written_len <= code_slice.len());
1097 entrypoints.insert(
1098 section_name,
1099 Entrypoint {
1100 code_ptr: code_mem.as_ptr() as usize + guard_size_before + code_len_allocated
1101 - code_slice.len(),
1102 code_len: written_len,
1103 },
1104 );
1105 code_slice = &mut code_slice[written_len..];
1106 }
1107
1108 let unpadded_code_len = code_len_allocated - code_slice.len();
1110 if std::env::var("JIT_DUMP").is_ok() {
1111 eprintln!(
1112 "[JITSIZE] elf={} native_unpadded={} buffer={}",
1113 elf.len(),
1114 unpadded_code_len,
1115 code_len_allocated
1116 );
1117 let native = std::slice::from_raw_parts(
1118 code_mem.as_ptr().offset(guard_size_before as isize),
1119 unpadded_code_len,
1120 );
1121 let mut hex = String::with_capacity(native.len() * 2);
1122 for b in native {
1123 hex.push_str(&format!("{b:02x}"));
1124 }
1125 eprintln!("[JITHEX] {hex}");
1126 }
1127 let code_len = (unpadded_code_len + page_size - 1) & !(page_size - 1);
1128 assert!(code_len <= code_len_allocated);
1129
1130 if libc::mprotect(
1133 code_mem.as_mut_ptr().offset(guard_size_before as isize) as *mut _,
1134 code_len,
1135 libc::PROT_READ | libc::PROT_EXEC,
1136 ) != 0
1137 || (code_len < code_len_allocated
1138 && libc::mprotect(
1139 code_mem
1140 .as_mut_ptr()
1141 .offset((guard_size_before + code_len) as isize) as *mut _,
1142 code_len_allocated - code_len,
1143 libc::PROT_NONE,
1144 ) != 0)
1145 {
1146 return Err(RuntimeError::PlatformError("failed to protect code memory"));
1147 }
1148
1149 guard_size_after += code_len_allocated - code_len;
1150
1151 tracing::info!(
1152 elf_size = elf.len(),
1153 native_code_addr = ?code_mem.as_ptr(),
1154 native_code_size = code_len,
1155 native_code_size_unpadded = unpadded_code_len,
1156 guard_size_before,
1157 guard_size_after,
1158 duration = ?start_time.elapsed(),
1159 cage_ptr = ?cage.region().as_ptr(),
1160 cage_mapped_size = cage.region().len(),
1161 "jit compiled program"
1162 );
1163
1164 Ok(UnboundProgram {
1165 id: NEXT_PROGRAM_ID.fetch_add(1, Ordering::Relaxed),
1166 _code_mem: code_mem,
1167 cage,
1168 helper_id_xor: self.helper_id_xor,
1169 helpers: self.helpers.clone(),
1170 event_listener: self.event_listener.clone(),
1171 entrypoints,
1172 })
1173 }
1174 }
1175}
1176
1177#[derive(Default)]
1178struct Dispatch {
1179 async_preemption: bool,
1180 memory_access_error: Option<usize>,
1181
1182 index: u32,
1183 arg1: u64,
1184 arg2: u64,
1185 arg3: u64,
1186 arg4: u64,
1187 arg5: u64,
1188}
1189
1190unsafe extern "C" fn tls_dispatcher(
1191 arg1: u64,
1192 arg2: u64,
1193 arg3: u64,
1194 arg4: u64,
1195 arg5: u64,
1196 index: std::os::raw::c_uint,
1197 _cookie: *mut std::os::raw::c_void,
1198) -> u64 {
1199 let yielder = ACTIVE_JIT_CODE_ZONE
1200 .with(|x| x.yielder.get())
1201 .expect("no yielder");
1202 let yielder = yielder.as_ref();
1203 let ret = yielder.suspend(Dispatch {
1204 async_preemption: false,
1205 memory_access_error: None,
1206 index,
1207 arg1,
1208 arg2,
1209 arg3,
1210 arg4,
1211 arg5,
1212 });
1213 ret
1214}
1215
1216unsafe extern "C" fn std_validator(
1217 index: std::os::raw::c_uint,
1218 _vm: *const crate::ubpf::ubpf_vm,
1219) -> bool {
1220 let loader = LOADING_PROGRAM_LOADER.with(|x| x.get());
1221 if loader.is_null() {
1222 return false;
1223 }
1224 let loader = &*loader;
1225 let entropy = (index >> 16) & 0xffff;
1226 let index = (((index & 0xffff) as u16) ^ loader.helper_id_xor).wrapping_sub(1);
1227 loader.helpers.get(index as usize).map(|x| x.0) == Some(entropy as u16)
1228}
1229
1230#[cfg(all(target_arch = "x86_64", target_os = "linux"))]
1231unsafe fn program_counter(uctx: *mut libc::ucontext_t) -> usize {
1232 (*uctx).uc_mcontext.gregs[libc::REG_RIP as usize] as usize
1233}
1234
1235#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
1236unsafe fn program_counter(uctx: *mut libc::ucontext_t) -> usize {
1237 (*uctx).uc_mcontext.pc as usize
1238}
1239
1240unsafe extern "C" fn sigsegv_handler(
1241 _sig: i32,
1242 siginfo: *mut libc::siginfo_t,
1243 uctx: *mut libc::ucontext_t,
1244) {
1245 let fail = || restore_default_signal_handler(libc::SIGSEGV);
1246
1247 let Some((jit_code_zone, pointer_cage, yielder)) = ACTIVE_JIT_CODE_ZONE.with(|x| {
1248 if x.valid.load(Ordering::Relaxed) {
1249 compiler_fence(Ordering::Acquire);
1250 Some((
1251 x.code_range.get(),
1252 x.pointer_cage_protected_range.get(),
1253 x.yielder.get(),
1254 ))
1255 } else {
1256 None
1257 }
1258 }) else {
1259 return fail();
1260 };
1261
1262 let pc = program_counter(uctx);
1263
1264 if pc < jit_code_zone.0 || pc >= jit_code_zone.1 {
1265 return fail();
1266 }
1267
1268 if (*siginfo).si_code != 2 {
1270 return fail();
1271 }
1272
1273 let si_addr = (*siginfo).si_addr() as usize;
1274 if si_addr < pointer_cage.0 || si_addr >= pointer_cage.1 {
1275 return fail();
1276 }
1277
1278 let yielder = yielder.expect("no yielder").as_ref();
1279 yielder.suspend(Dispatch {
1280 memory_access_error: Some(si_addr),
1281 ..Default::default()
1282 });
1283}
1284
1285unsafe extern "C" fn sigusr1_handler(
1286 _sig: i32,
1287 _siginfo: *mut libc::siginfo_t,
1288 uctx: *mut libc::ucontext_t,
1289) {
1290 SIGUSR1_COUNTER.with(|x| x.set(x.get() + 1));
1291
1292 let Some((jit_code_zone, yielder)) = ACTIVE_JIT_CODE_ZONE.with(|x| {
1293 if x.valid.load(Ordering::Relaxed) {
1294 compiler_fence(Ordering::Acquire);
1295 Some((x.code_range.get(), x.yielder.get()))
1296 } else {
1297 None
1298 }
1299 }) else {
1300 return;
1301 };
1302 let pc = program_counter(uctx);
1303 if pc < jit_code_zone.0 || pc >= jit_code_zone.1 {
1304 return;
1305 }
1306
1307 let yielder = yielder.expect("no yielder").as_ref();
1308 yielder.suspend(Dispatch {
1309 async_preemption: true,
1310 ..Default::default()
1311 });
1312}
1313
1314unsafe fn restore_default_signal_handler(signum: i32) {
1315 let act = libc::sigaction {
1316 sa_sigaction: libc::SIG_DFL,
1317 sa_flags: libc::SA_SIGINFO,
1318 sa_mask: std::mem::zeroed(),
1319 sa_restorer: None,
1320 };
1321 if libc::sigaction(signum, &act, std::ptr::null_mut()) != 0 {
1322 libc::abort();
1323 }
1324}
1325
1326fn get_blocked_sigset() -> libc::sigset_t {
1327 unsafe {
1328 let mut s: libc::sigset_t = std::mem::zeroed();
1329 libc::sigaddset(&mut s, libc::SIGUSR1);
1330 libc::sigaddset(&mut s, libc::SIGSEGV);
1331 s
1332 }
1333}