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