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: Option<Instant> = None;
741 let mut last_throttle_time: Option<Instant> = None;
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 last_throttle = last_throttle_time.get_or_insert(now);
874 let last_yield = last_yield_time.get_or_insert(now);
875 let should_throttle = now > *last_throttle
876 && now.duration_since(*last_throttle) >= timeslice.max_run_time_before_throttle;
877 let should_yield = now > *last_yield
878 && now.duration_since(*last_yield) >= timeslice.max_run_time_before_yield;
879 if should_throttle || should_yield || pending_async_task.is_some() {
880 if should_throttle {
883 if !did_throttle {
884 did_throttle = true;
885 tracing::warn!("throttling program");
886 }
887 timeslicer.sleep(timeslice.throttle_duration).await;
888 let now = Instant::now();
889 last_throttle_time = Some(now);
890 last_yield_time = Some(now);
891 let task = self.unbound.event_listener.did_throttle(&mut helper_scope);
892 if let Some(task) = task {
893 task.await;
894 }
895 } else if should_yield {
896 timeslicer.yield_now().await;
897 let now = Instant::now();
898 last_yield_time = Some(now);
899 self.unbound.event_listener.did_yield();
900 }
901
902 if let Some(mut pending_async_task) = pending_async_task {
904 let output =
911 match pending_async_task.poll_unpin(&mut Context::from_waker(noop_waker_ref())) {
912 Poll::Ready(output) => output,
913 Poll::Pending => {
914 let async_start = Instant::now();
917 let output = pending_async_task.await;
918 let async_dur = async_start.elapsed();
919 if let Some(last_throttle_time) = &mut last_throttle_time {
920 *last_throttle_time += async_dur;
921 }
922 if let Some(last_yield_time) = &mut last_yield_time {
923 *last_yield_time += async_dur;
924 }
925 output
926 }
927 };
928 prev_async_task_output = Some((helper_name, output));
929 }
930 }
931 }
932 };
933
934 Ok(program_ret as i64)
935 }
936}
937
938struct Vm(NonNull<crate::ubpf::ubpf_vm>);
939
940impl Vm {
941 fn new(cage: &PointerCage) -> Self {
942 let vm = NonNull::new(unsafe { crate::ubpf::ubpf_create() }).expect("failed to create ubpf_vm");
943 unsafe {
944 crate::ubpf::ubpf_toggle_bounds_check(vm.as_ptr(), false);
945 crate::ubpf::ubpf_set_jit_pointer_mask_and_offset(vm.as_ptr(), cage.mask(), cage.offset());
946 }
947 Self(vm)
948 }
949}
950
951impl Drop for Vm {
952 fn drop(&mut self) {
953 unsafe {
954 crate::ubpf::ubpf_destroy(self.0.as_ptr());
955 }
956 }
957}
958
959struct LoaderValidationScope {
960 previous: *const ProgramLoader,
961}
962
963impl LoaderValidationScope {
964 fn new(loader: &ProgramLoader) -> Self {
965 let previous = LOADING_PROGRAM_LOADER.with(|x| {
966 let previous = x.get();
967 x.set(loader as *const _);
968 previous
969 });
970 Self { previous }
971 }
972}
973
974impl Drop for LoaderValidationScope {
975 fn drop(&mut self) {
976 LOADING_PROGRAM_LOADER.with(|x| x.set(self.previous));
977 }
978}
979
980impl ProgramLoader {
981 pub fn new(
983 rng: &mut impl rand::Rng,
984 event_listener: Arc<dyn ProgramEventListener>,
985 raw_helpers: &[&[(&'static str, Helper)]],
986 ) -> Self {
987 let helper_id_xor = rng.gen::<u16>();
988 let mut helpers_inverse: HashMap<&'static str, i32> = HashMap::new();
989 let mut shuffled_helpers = raw_helpers
991 .iter()
992 .flat_map(|x| x.iter().copied())
993 .collect::<HashMap<_, _>>()
994 .into_iter()
995 .collect::<Vec<_>>();
996 shuffled_helpers.shuffle(rng);
997 let mut helpers: Vec<(u16, &'static str, Helper)> = Vec::with_capacity(shuffled_helpers.len());
998
999 assert!(shuffled_helpers.len() <= 65535);
1000
1001 for (i, (name, helper)) in shuffled_helpers.into_iter().enumerate() {
1002 let entropy = rng.gen::<u16>() & 0x7fff;
1003 helpers.push((entropy, name, helper));
1004 helpers_inverse.insert(
1005 name,
1006 (((entropy as usize) << 16) | ((i + 1) ^ (helper_id_xor as usize))) as i32,
1007 );
1008 }
1009
1010 tracing::info!(?helpers_inverse, "generated helper table");
1011 Self {
1012 helper_id_xor,
1013 helpers: Arc::new(helpers),
1014 helpers_inverse,
1015 event_listener,
1016 code_size_limit: DEFAULT_CODE_SIZE_LIMIT,
1017 require_static_regions: false,
1018 }
1019 }
1020
1021 pub fn require_static_region_analysis(mut self, require: bool) -> Self {
1028 self.require_static_regions = require;
1029 self
1030 }
1031
1032 pub fn with_code_size_limit(mut self, limit: usize) -> Self {
1045 assert!(
1046 limit > 0 && limit % (64 * 1024) == 0,
1047 "code size limit must be a non-zero multiple of 64 KiB"
1048 );
1049 assert!(
1050 limit <= u32::MAX as usize,
1051 "code size limit must fit in u32"
1052 );
1053 self.code_size_limit = limit;
1054 self
1055 }
1056
1057 pub fn load(&self, rng: &mut impl rand::Rng, elf: &[u8]) -> Result<UnboundProgram, Error> {
1059 self._load(rng, elf).map_err(Error)
1060 }
1061
1062 fn _load(&self, rng: &mut impl rand::Rng, elf: &[u8]) -> Result<UnboundProgram, RuntimeError> {
1063 let start_time = Instant::now();
1064 let cage = PointerCage::new(rng, SHADOW_STACK_SIZE, elf.len())?;
1065 let vm = Vm::new(&cage);
1066
1067 let code_sections = {
1069 let mut data = cage
1073 .safe_deref_for_read(cage.data_bottom(), elf.len())
1074 .unwrap();
1075 let data = unsafe { data.as_mut() };
1076 data.copy_from_slice(elf);
1077
1078 link_elf(data, cage.data_bottom(), &self.helpers_inverse).map_err(RuntimeError::Linker)?
1079 };
1080 cage.freeze_data();
1081
1082 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
1083 if page_size < 0 {
1084 return Err(RuntimeError::PlatformError("failed to get page size"));
1085 }
1086 let page_size = page_size as usize;
1087
1088 let guard_size_before = rng.gen_range(16..128) * page_size;
1090 let mut guard_size_after = rng.gen_range(16..128) * page_size;
1091
1092 let code_len_allocated = self.code_size_limit;
1093 let code_mem = MmapRaw::from(
1094 MmapOptions::new()
1095 .len(code_len_allocated + guard_size_before + guard_size_after)
1096 .map_anon()
1097 .map_err(|_| RuntimeError::PlatformError("failed to allocate code memory"))?,
1098 );
1099
1100 unsafe {
1101 if crate::ubpf::ubpf_register_external_dispatcher(
1102 vm.0.as_ptr(),
1103 Some(tls_dispatcher),
1104 Some(std_validator),
1105 ) != 0
1106 {
1107 return Err(RuntimeError::PlatformError(
1108 "ubpf: failed to register external dispatcher",
1109 ));
1110 }
1111 if libc::mprotect(
1112 code_mem.as_mut_ptr() as *mut _,
1113 guard_size_before,
1114 libc::PROT_NONE,
1115 ) != 0
1116 || libc::mprotect(
1117 code_mem
1118 .as_mut_ptr()
1119 .offset((guard_size_before + code_len_allocated) as isize) as *mut _,
1120 guard_size_after,
1121 libc::PROT_NONE,
1122 ) != 0
1123 {
1124 return Err(RuntimeError::PlatformError("failed to protect guard pages"));
1125 }
1126 }
1127
1128 let mut entrypoints: HashMap<String, Entrypoint> = HashMap::new();
1129
1130 unsafe {
1131 let mut code_slice = std::slice::from_raw_parts_mut(
1133 code_mem.as_mut_ptr().offset(guard_size_before as isize),
1134 code_len_allocated,
1135 );
1136 for (section_name, code_vaddr_size) in code_sections {
1137 if code_slice.is_empty() {
1138 return Err(RuntimeError::InvalidArgument(
1139 "no space left for jit compilation",
1140 ));
1141 }
1142
1143 crate::ubpf::ubpf_unload_code(vm.0.as_ptr());
1144
1145 let mut errmsg_ptr = std::ptr::null_mut();
1146 let code = cage
1147 .safe_deref_for_read(code_vaddr_size.0, code_vaddr_size.1)
1148 .unwrap();
1149 let code_bytes = std::slice::from_raw_parts(code.as_ptr() as *const u8, code.len());
1150 validate_local_call_graph(code_bytes).map_err(|err| {
1151 RuntimeError::InvalidArgumentOwned(format!(
1152 "local call graph validation failed in {section_name}: {err}"
1153 ))
1154 })?;
1155 let ret = {
1156 let validation_scope = LoaderValidationScope::new(self);
1157 let ret = crate::ubpf::ubpf_load(
1158 vm.0.as_ptr(),
1159 code.as_ptr() as *const _,
1160 code.len() as u32,
1161 &mut errmsg_ptr,
1162 );
1163 drop(validation_scope);
1164 ret
1165 };
1166 if ret != 0 {
1167 let errmsg = if errmsg_ptr.is_null() {
1168 "".to_string()
1169 } else {
1170 CStr::from_ptr(errmsg_ptr).to_string_lossy().into_owned()
1171 };
1172 if !errmsg_ptr.is_null() {
1173 libc::free(errmsg_ptr as _);
1174 }
1175 tracing::error!(section_name, error = errmsg, "failed to load code");
1176 return Err(RuntimeError::InvalidArgumentOwned(format!(
1177 "ubpf: code load failed: {errmsg}"
1178 )));
1179 }
1180
1181 let region_analysis = crate::region_analysis::analyze(
1187 code_bytes,
1188 cage.data_bottom() as u64,
1189 cage.data_top() as u64,
1190 );
1191 if self.require_static_regions && !region_analysis.unresolved.is_empty() {
1192 return Err(RuntimeError::InvalidArgumentOwned(format!(
1193 "static region analysis failed in {section_name}: {} memory access(es) could not be \
1194 routed to a single region (instruction slots {:?})",
1195 region_analysis.unresolved.len(),
1196 region_analysis.unresolved,
1197 )));
1198 }
1199 let region_hints = region_analysis.hints;
1200 crate::ubpf::ubpf_set_region_hints(
1201 vm.0.as_ptr(),
1202 region_hints.as_ptr(),
1203 region_hints.len(),
1204 );
1205
1206 let mut written_len = code_slice.len();
1207 let ret = crate::ubpf::ubpf_translate_ex(
1208 vm.0.as_ptr(),
1209 code_slice.as_mut_ptr(),
1210 &mut written_len,
1211 &mut errmsg_ptr,
1212 crate::ubpf::JitMode_ExtendedJitMode,
1213 );
1214 crate::ubpf::ubpf_set_region_hints(vm.0.as_ptr(), std::ptr::null(), 0);
1217 if ret != 0 {
1218 let errmsg = if errmsg_ptr.is_null() {
1219 "".to_string()
1220 } else {
1221 CStr::from_ptr(errmsg_ptr).to_string_lossy().into_owned()
1222 };
1223 if !errmsg_ptr.is_null() {
1224 libc::free(errmsg_ptr as _);
1225 }
1226 tracing::error!(section_name, error = errmsg, "failed to translate code");
1227 return Err(RuntimeError::InvalidArgumentOwned(format!(
1228 "ubpf: code translation failed: {errmsg}"
1229 )));
1230 }
1231
1232 assert!(written_len <= code_slice.len());
1233 entrypoints.insert(
1234 section_name,
1235 Entrypoint {
1236 code_ptr: code_mem.as_ptr() as usize + guard_size_before + code_len_allocated
1237 - code_slice.len(),
1238 code_len: written_len,
1239 },
1240 );
1241 code_slice = &mut code_slice[written_len..];
1242 }
1243
1244 let unpadded_code_len = code_len_allocated - code_slice.len();
1246 if std::env::var("JIT_DUMP").is_ok() {
1247 eprintln!(
1248 "[JITSIZE] elf={} native_unpadded={} buffer={}",
1249 elf.len(),
1250 unpadded_code_len,
1251 code_len_allocated
1252 );
1253 let native = std::slice::from_raw_parts(
1254 code_mem.as_ptr().offset(guard_size_before as isize),
1255 unpadded_code_len,
1256 );
1257 let mut hex = String::with_capacity(native.len() * 2);
1258 for b in native {
1259 hex.push_str(&format!("{b:02x}"));
1260 }
1261 eprintln!("[JITHEX] {hex}");
1262 }
1263 let code_len = (unpadded_code_len + page_size - 1) & !(page_size - 1);
1264 assert!(code_len <= code_len_allocated);
1265
1266 if libc::mprotect(
1269 code_mem.as_mut_ptr().offset(guard_size_before as isize) as *mut _,
1270 code_len,
1271 libc::PROT_READ | libc::PROT_EXEC,
1272 ) != 0
1273 || (code_len < code_len_allocated
1274 && libc::mprotect(
1275 code_mem
1276 .as_mut_ptr()
1277 .offset((guard_size_before + code_len) as isize) as *mut _,
1278 code_len_allocated - code_len,
1279 libc::PROT_NONE,
1280 ) != 0)
1281 {
1282 return Err(RuntimeError::PlatformError("failed to protect code memory"));
1283 }
1284
1285 guard_size_after += code_len_allocated - code_len;
1286
1287 tracing::info!(
1288 elf_size = elf.len(),
1289 native_code_addr = ?code_mem.as_ptr(),
1290 native_code_size = code_len,
1291 native_code_size_unpadded = unpadded_code_len,
1292 guard_size_before,
1293 guard_size_after,
1294 duration = ?start_time.elapsed(),
1295 cage_ptr = ?cage.region().as_ptr(),
1296 cage_mapped_size = cage.region().len(),
1297 "jit compiled program"
1298 );
1299
1300 Ok(UnboundProgram {
1301 id: NEXT_PROGRAM_ID.fetch_add(1, Ordering::Relaxed),
1302 _code_mem: code_mem,
1303 cage,
1304 helper_id_xor: self.helper_id_xor,
1305 helpers: self.helpers.clone(),
1306 event_listener: self.event_listener.clone(),
1307 entrypoints,
1308 })
1309 }
1310 }
1311}
1312
1313#[derive(Default)]
1314struct Dispatch {
1315 async_preemption: bool,
1316 memory_access_error: Option<usize>,
1317
1318 index: u32,
1319 arg1: u64,
1320 arg2: u64,
1321 arg3: u64,
1322 arg4: u64,
1323 arg5: u64,
1324}
1325
1326unsafe extern "C" fn tls_dispatcher(
1327 arg1: u64,
1328 arg2: u64,
1329 arg3: u64,
1330 arg4: u64,
1331 arg5: u64,
1332 index: std::os::raw::c_uint,
1333 _cookie: *mut std::os::raw::c_void,
1334) -> u64 {
1335 let yielder = ACTIVE_JIT_CODE_ZONE
1336 .with(|x| x.yielder.get())
1337 .expect("no yielder");
1338 let yielder = yielder.as_ref();
1339 let ret = yielder.suspend(Dispatch {
1340 async_preemption: false,
1341 memory_access_error: None,
1342 index,
1343 arg1,
1344 arg2,
1345 arg3,
1346 arg4,
1347 arg5,
1348 });
1349 ret
1350}
1351
1352unsafe extern "C" fn std_validator(
1353 index: std::os::raw::c_uint,
1354 _vm: *const crate::ubpf::ubpf_vm,
1355) -> bool {
1356 let loader = LOADING_PROGRAM_LOADER.with(|x| x.get());
1357 if loader.is_null() {
1358 return false;
1359 }
1360 let loader = &*loader;
1361 let entropy = (index >> 16) & 0xffff;
1362 let index = (((index & 0xffff) as u16) ^ loader.helper_id_xor).wrapping_sub(1);
1363 loader.helpers.get(index as usize).map(|x| x.0) == Some(entropy as u16)
1364}
1365
1366#[cfg(all(target_arch = "x86_64", target_os = "linux"))]
1367unsafe fn program_counter(uctx: *mut libc::ucontext_t) -> usize {
1368 (*uctx).uc_mcontext.gregs[libc::REG_RIP as usize] as usize
1369}
1370
1371#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
1372unsafe fn program_counter(uctx: *mut libc::ucontext_t) -> usize {
1373 (*uctx).uc_mcontext.pc as usize
1374}
1375
1376unsafe extern "C" fn sigsegv_handler(
1377 _sig: i32,
1378 siginfo: *mut libc::siginfo_t,
1379 uctx: *mut libc::ucontext_t,
1380) {
1381 let fail = || restore_default_signal_handler(libc::SIGSEGV);
1382
1383 let Some((jit_code_zone, pointer_cage, yielder)) = ACTIVE_JIT_CODE_ZONE.with(|x| {
1384 if x.valid.load(Ordering::Relaxed) {
1385 compiler_fence(Ordering::Acquire);
1386 Some((
1387 x.code_range.get(),
1388 x.pointer_cage_protected_range.get(),
1389 x.yielder.get(),
1390 ))
1391 } else {
1392 None
1393 }
1394 }) else {
1395 return fail();
1396 };
1397
1398 let pc = program_counter(uctx);
1399
1400 if pc < jit_code_zone.0 || pc >= jit_code_zone.1 {
1401 return fail();
1402 }
1403
1404 if (*siginfo).si_code != 1 && (*siginfo).si_code != 2 {
1406 return fail();
1407 }
1408
1409 let si_addr = (*siginfo).si_addr() as usize;
1410 if si_addr < pointer_cage.0 || si_addr >= pointer_cage.1 {
1411 return fail();
1412 }
1413
1414 let yielder = yielder.expect("no yielder").as_ref();
1415 yielder.suspend(Dispatch {
1416 memory_access_error: Some(si_addr),
1417 ..Default::default()
1418 });
1419}
1420
1421unsafe extern "C" fn sigusr1_handler(
1422 _sig: i32,
1423 _siginfo: *mut libc::siginfo_t,
1424 uctx: *mut libc::ucontext_t,
1425) {
1426 SIGUSR1_COUNTER.with(|x| x.set(x.get() + 1));
1427
1428 let Some((jit_code_zone, yielder)) = ACTIVE_JIT_CODE_ZONE.with(|x| {
1429 if x.valid.load(Ordering::Relaxed) {
1430 compiler_fence(Ordering::Acquire);
1431 Some((x.code_range.get(), x.yielder.get()))
1432 } else {
1433 None
1434 }
1435 }) else {
1436 return;
1437 };
1438 let pc = program_counter(uctx);
1439 if pc < jit_code_zone.0 || pc >= jit_code_zone.1 {
1440 return;
1441 }
1442
1443 let yielder = yielder.expect("no yielder").as_ref();
1444 yielder.suspend(Dispatch {
1445 async_preemption: true,
1446 ..Default::default()
1447 });
1448}
1449
1450unsafe fn restore_default_signal_handler(signum: i32) {
1451 let act = libc::sigaction {
1452 sa_sigaction: libc::SIG_DFL,
1453 sa_flags: libc::SA_SIGINFO,
1454 sa_mask: std::mem::zeroed(),
1455 sa_restorer: None,
1456 };
1457 if libc::sigaction(signum, &act, std::ptr::null_mut()) != 0 {
1458 libc::abort();
1459 }
1460}
1461
1462fn get_blocked_sigset() -> libc::sigset_t {
1463 unsafe {
1464 let mut s: libc::sigset_t = std::mem::zeroed();
1465 libc::sigaddset(&mut s, libc::SIGUSR1);
1466 libc::sigaddset(&mut s, libc::SIGSEGV);
1467 s
1468 }
1469}