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