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 futures::{task::noop_waker_ref, Future, FutureExt};
25use memmap2::{MmapOptions, MmapRaw};
26use parking_lot::{Condvar, Mutex};
27use rand::prelude::SliceRandom;
28
29use crate::{
30 coroutine::{
31 stack::{DefaultStack, Stack},
32 Coroutine, CoroutineResult, ScopedCoroutine, Yielder,
33 },
34 error::{Error, RuntimeError},
35 function_analysis::{analyze_functions, FunctionLayout},
36 helpers::Helper,
37 linker::{link_elf, validate_local_call_graph},
38 pointer_cage::PointerCage,
39 region_analysis::PointerSignature,
40 util::nonnull_bytes_overlap,
41};
42
43const NATIVE_STACK_SIZE: usize = 16384;
44const SHADOW_STACK_SIZE: usize = 32768;
45const MAX_CALLDATA_SIZE: usize = 512;
46const MAX_MUTABLE_DEREF_REGIONS: usize = 4;
47const MAX_IMMUTABLE_DEREF_REGIONS: usize = 16;
48
49#[cfg(all(
50 target_arch = "x86_64",
51 any(target_os = "linux", target_os = "openbsd")
52))]
53std::arch::global_asm!(
54 r#"
55.global async_ebpf_entry_trampoline
56.type async_ebpf_entry_trampoline,@function
57async_ebpf_entry_trampoline:
58 mov r10, rdi
59 mov rax, [rsp + 8]
60 push rbp
61 push rbx
62 push r12
63 push r13
64 push r14
65 push r15
66 mov r15, rcx
67 add r15, r8
68 mov r11, rcx
69 mov rdi, rsi
70 mov rsi, rdx
71 mov rdx, r11
72 mov rcx, r8
73 mov r8, r9
74 mov r11, rdi
75 sub rsp, 8
76 mov rbp, rsp
77 sub rsp, 32
78 mov [rbp - 8], rax
79 call r10
80 mov rsp, rbp
81 add rsp, 8
82 pop r15
83 pop r14
84 pop r13
85 pop r12
86 pop rbx
87 pop rbp
88 ret
89.size async_ebpf_entry_trampoline, . - async_ebpf_entry_trampoline
90"#
91);
92
93#[cfg(all(
94 target_arch = "aarch64",
95 any(target_os = "linux", target_os = "openbsd")
96))]
97std::arch::global_asm!(
98 r#"
99.global async_ebpf_entry_trampoline
100.type async_ebpf_entry_trampoline,%function
101async_ebpf_entry_trampoline:
102 bti c
103 mov x17, x0
104 sub sp, sp, #16
105 stp x29, x30, [sp]
106 sub sp, sp, #64
107 stp x19, x20, [sp, #0]
108 stp x21, x22, [sp, #16]
109 stp x23, x24, [sp, #32]
110 stp x25, x26, [sp, #48]
111 mov x29, sp
112 add x23, x3, x4
113 mov x0, x1
114 mov x1, x2
115 mov x2, x3
116 mov x3, x4
117 mov x4, x5
118 sub sp, sp, #16
119 str x6, [x29, #-8]
120 mov x26, x0
121 blr x17
122 mov x0, x5
123 mov sp, x29
124 ldp x19, x20, [sp, #0]
125 ldp x21, x22, [sp, #16]
126 ldp x23, x24, [sp, #32]
127 ldp x25, x26, [sp, #48]
128 add sp, sp, #64
129 ldp x29, x30, [sp]
130 add sp, sp, #16
131 ret
132.size async_ebpf_entry_trampoline, . - async_ebpf_entry_trampoline
133"#
134);
135
136extern "C" {
137 fn async_ebpf_entry_trampoline(
138 target: usize,
139 ctx: usize,
140 mem_len: usize,
141 stack: usize,
142 stack_len: usize,
143 reserved: usize,
144 memory: usize,
145 ) -> u64;
146}
147
148pub struct InvokeScope {
150 data: HashMap<TypeId, Box<dyn Any + Send>>,
151}
152
153impl InvokeScope {
154 pub fn data_mut<T: Default + Send + 'static>(&mut self) -> &mut T {
156 let ty = TypeId::of::<T>();
157 self
158 .data
159 .entry(ty)
160 .or_insert_with(|| Box::new(T::default()))
161 .downcast_mut()
162 .expect("InvokeScope::data_mut: downcast failed")
163 }
164}
165
166pub struct HelperScope<'a, 'b> {
168 pub program: &'a Program,
170 pub invoke: RefCell<&'a mut InvokeScope>,
172 resources: RefCell<&'a mut [&'b mut dyn Any]>,
173 memory: &'a JitMemory,
174 mutable_dereferenced_regions: [Cell<Option<NonNull<[u8]>>>; MAX_MUTABLE_DEREF_REGIONS],
175 immutable_dereferenced_regions: [Cell<Option<NonNull<[u8]>>>; MAX_IMMUTABLE_DEREF_REGIONS],
176 can_post_task: bool,
177}
178
179pub struct MutableUserMemory<'a, 'b, 'c> {
181 _scope: &'c HelperScope<'a, 'b>,
182 region: NonNull<[u8]>,
183}
184
185impl<'a, 'b, 'c> Deref for MutableUserMemory<'a, 'b, 'c> {
186 type Target = [u8];
187
188 fn deref(&self) -> &Self::Target {
189 unsafe { self.region.as_ref() }
190 }
191}
192
193impl<'a, 'b, 'c> DerefMut for MutableUserMemory<'a, 'b, 'c> {
194 fn deref_mut(&mut self) -> &mut Self::Target {
195 unsafe { self.region.as_mut() }
196 }
197}
198
199impl<'a, 'b> HelperScope<'a, 'b> {
200 pub fn post_task(
202 &self,
203 task: impl Future<Output = impl FnOnce(&HelperScope) -> Result<u64, ()> + 'static> + 'static,
204 ) {
205 if !self.can_post_task {
206 panic!("HelperScope::post_task() called in a context where posting task is not allowed");
207 }
208
209 PENDING_ASYNC_TASK.with(|x| {
210 let mut x = x.borrow_mut();
211 if x.is_some() {
212 panic!("post_task called while another task is pending");
213 }
214 *x = Some(async move { Box::new(task.await) as AsyncTaskOutput }.boxed_local());
215 });
216 }
217
218 pub fn with_resource_mut<'c, T: 'static, R>(
220 &'c self,
221 callback: impl FnOnce(Result<&mut T, ()>) -> R,
222 ) -> R {
223 let mut resources = self.resources.borrow_mut();
224 let Some(res) = resources
225 .iter_mut()
226 .filter_map(|x| x.downcast_mut::<T>())
227 .next()
228 else {
229 tracing::warn!(resource_type = ?TypeId::of::<T>(), "resource not found");
230 return callback(Err(()));
231 };
232
233 callback(Ok(res))
234 }
235
236 pub fn user_memory(&self, ptr: u64, size: u64) -> Result<&[u8], ()> {
238 let Some(region) = self.memory.safe_deref_for_read(ptr as usize, size as usize) else {
239 tracing::warn!(ptr, size, "invalid read");
240 return Err(());
241 };
242
243 if size != 0 {
244 if self
246 .mutable_dereferenced_regions
247 .iter()
248 .filter_map(|x| x.get())
249 .any(|x| nonnull_bytes_overlap(x, region))
250 {
251 tracing::warn!(ptr, size, "read overlapped with previous write");
252 return Err(());
253 }
254
255 let Some(slot) = self
257 .immutable_dereferenced_regions
258 .iter()
259 .find(|x| x.get().is_none())
260 else {
261 tracing::warn!(ptr, size, "too many reads");
262 return Err(());
263 };
264 slot.set(Some(region));
265 }
266
267 Ok(unsafe { region.as_ref() })
268 }
269
270 pub fn user_memory_mut<'c>(
272 &'c self,
273 ptr: u64,
274 size: u64,
275 ) -> Result<MutableUserMemory<'a, 'b, 'c>, ()> {
276 let Some(region) = self
277 .memory
278 .safe_deref_for_write(ptr as usize, size as usize)
279 else {
280 tracing::warn!(ptr, size, "invalid write");
281 return Err(());
282 };
283
284 if size != 0 {
285 if self
287 .mutable_dereferenced_regions
288 .iter()
289 .chain(self.immutable_dereferenced_regions.iter())
290 .filter_map(|x| x.get())
291 .any(|x| nonnull_bytes_overlap(x, region))
292 {
293 tracing::warn!(ptr, size, "write overlapped with previous read/write");
294 return Err(());
295 }
296
297 let Some(slot) = self
299 .mutable_dereferenced_regions
300 .iter()
301 .find(|x| x.get().is_none())
302 else {
303 tracing::warn!(ptr, size, "too many writes");
304 return Err(());
305 };
306 slot.set(Some(region));
307 }
308
309 Ok(MutableUserMemory {
310 _scope: self,
311 region,
312 })
313 }
314}
315
316#[derive(Copy, Clone)]
317struct AssumeSend<T>(T);
318unsafe impl<T> Send for AssumeSend<T> {}
319
320struct ExecContext {
321 native_stack: DefaultStack,
322 guest_stack: Box<[u8; SHADOW_STACK_SIZE]>,
323}
324
325impl ExecContext {
326 fn new() -> Self {
327 Self {
328 native_stack: DefaultStack::new(NATIVE_STACK_SIZE)
329 .expect("failed to initialize native stack"),
330 guest_stack: Box::new([0u8; SHADOW_STACK_SIZE]),
331 }
332 }
333}
334
335#[repr(C)]
336struct JitMemory {
337 stack_guest_bottom: usize,
338 stack_guest_top: usize,
339 stack_native_base: usize,
340 data_guest_bottom: usize,
341 data_guest_top: usize,
342 data_native_base: usize,
343}
344
345impl JitMemory {
346 fn checked_region(
347 guest: usize,
348 size: usize,
349 guest_bottom: usize,
350 guest_top: usize,
351 native_base: usize,
352 ) -> Option<NonNull<[u8]>> {
353 if size == 0 {
354 return Some(NonNull::slice_from_raw_parts(NonNull::dangling(), 0));
355 }
356
357 let end = guest.checked_add(size)?;
358 if guest < guest_bottom || end > guest_top {
359 return None;
360 }
361 let native = native_base.checked_add(guest - guest_bottom)? as *mut u8;
362 unsafe {
363 Some(NonNull::new_unchecked(std::ptr::slice_from_raw_parts_mut(
364 native, size,
365 )))
366 }
367 }
368
369 fn safe_deref_for_write(&self, guest: usize, size: usize) -> Option<NonNull<[u8]>> {
370 Self::checked_region(
371 guest,
372 size,
373 self.stack_guest_bottom,
374 self.stack_guest_top,
375 self.stack_native_base,
376 )
377 }
378
379 fn safe_deref_for_read(&self, guest: usize, size: usize) -> Option<NonNull<[u8]>> {
380 Self::checked_region(
381 guest,
382 size,
383 self.stack_guest_bottom,
384 self.stack_guest_top,
385 self.stack_native_base,
386 )
387 .or_else(|| {
388 Self::checked_region(
389 guest,
390 size,
391 self.data_guest_bottom,
392 self.data_guest_top,
393 self.data_native_base,
394 )
395 })
396 }
397}
398
399pub type PendingAsyncTask = Pin<Box<dyn Future<Output = AsyncTaskOutput>>>;
401pub type AsyncTaskOutput = Box<dyn FnOnce(&HelperScope) -> Result<u64, ()>>;
403
404static NEXT_PROGRAM_ID: AtomicU64 = AtomicU64::new(1);
405
406#[derive(Copy, Clone, Debug)]
407enum PreemptionState {
408 Inactive,
409 Armed(usize),
410 Shutdown,
411}
412
413type PreemptionStateSignal = (Mutex<PreemptionState>, Condvar);
414
415thread_local! {
416 static RUST_TID: ThreadId = std::thread::current().id();
417 static SIGUSR1_COUNTER: Cell< u64> = Cell::new(0);
418 static ACTIVE_JIT_CODE_ZONE: ActiveJitCodeZone = ActiveJitCodeZone::default();
419 static EXEC_CONTEXT_POOL: RefCell<Vec<ExecContext>> = Default::default();
420 static PENDING_ASYNC_TASK: RefCell<Option<PendingAsyncTask>> = RefCell::new(None);
421 static PREEMPTION_STATE: Arc<PreemptionStateSignal> = Arc::new((Mutex::new(PreemptionState::Inactive), Condvar::new()));
422 static LOADING_PROGRAM_LOADER: Cell<*const ProgramLoader> = const { Cell::new(std::ptr::null()) };
423 static ACTIVE_PROGRAM: Cell<*const Program> = const { Cell::new(std::ptr::null()) };
424}
425
426struct BorrowedExecContext {
427 ctx: ManuallyDrop<ExecContext>,
428}
429
430impl BorrowedExecContext {
431 fn new() -> Self {
432 let mut me = Self {
433 ctx: ManuallyDrop::new(
434 EXEC_CONTEXT_POOL.with(|x| x.borrow_mut().pop().unwrap_or_else(ExecContext::new)),
435 ),
436 };
437 me.ctx.guest_stack.fill(0x8e);
438 me
439 }
440}
441
442impl Drop for BorrowedExecContext {
443 fn drop(&mut self) {
444 let ctx = unsafe { ManuallyDrop::take(&mut self.ctx) };
445 EXEC_CONTEXT_POOL.with(|x| x.borrow_mut().push(ctx));
446 }
447}
448
449#[derive(Default)]
450struct ActiveJitCodeZone {
451 valid: AtomicBool,
452 code_range: Cell<(usize, usize)>,
453 pointer_cage_protected_range: Cell<(usize, usize)>,
454 yielder: Cell<Option<NonNull<Yielder<u64, Dispatch>>>>,
455}
456
457pub trait ProgramEventListener: Send + Sync + 'static {
459 fn did_async_preempt(&self, _scope: &HelperScope) {}
461 fn did_yield(&self) {}
463 fn did_throttle(&self, _scope: &HelperScope) -> Option<Pin<Box<dyn Future<Output = ()>>>> {
465 None
466 }
467}
468
469pub struct DummyProgramEventListener;
471impl ProgramEventListener for DummyProgramEventListener {}
472
473pub const DEFAULT_CODE_SIZE_LIMIT: usize = 1 << 20;
475
476pub struct ProgramLoader {
478 helpers_inverse: HashMap<&'static str, i32>,
479 event_listener: Arc<dyn ProgramEventListener>,
480 helper_id_xor: u16,
481 helpers: Arc<Vec<(u16, &'static str, Helper)>>,
482 code_size_limit: usize,
483 require_static_regions: bool,
484}
485
486pub struct UnboundProgram {
488 id: u64,
489 _code_mem: MmapRaw,
490 code_base: usize,
491 code_size: usize,
492 page_size: usize,
493 code_arena: RefCell<CodeArena>,
494 cage: PointerCage,
495 helper_id_xor: u16,
496 helpers: Arc<Vec<(u16, &'static str, Helper)>>,
497 event_listener: Arc<dyn ProgramEventListener>,
498 require_static_regions: bool,
499 entrypoints: HashMap<String, usize>,
500 sections: RefCell<Vec<Section>>,
501 resolvers: RefCell<HashMap<u32, ResolverInfo>>,
502 next_resolver_id: Cell<u32>,
503}
504
505pub struct Program {
507 unbound: UnboundProgram,
508 data: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
509 t: ThreadEnv,
510}
511
512#[derive(Copy, Clone)]
513struct Entrypoint {
514 code_ptr: usize,
515}
516
517struct CodeArena {
518 used: usize,
519}
520
521struct Section {
522 vm: Vm,
523 code_vaddr: usize,
524 code_len: usize,
525 layout: FunctionLayout,
526 functions: Vec<FunctionState>,
527}
528
529#[derive(Default)]
530struct FunctionState {
531 compiled: HashMap<PointerSignature, FunctionCompilation>,
532 #[cfg(test)]
533 compile_attempts: usize,
534}
535
536#[derive(Clone)]
537enum FunctionCompilation {
538 Succeeded(Entrypoint),
539 Failed(RuntimeError),
540}
541
542impl FunctionCompilation {
543 fn result(&self) -> Result<Entrypoint, RuntimeError> {
544 match self {
545 Self::Succeeded(entrypoint) => Ok(*entrypoint),
546 Self::Failed(err) => Err(err.clone()),
547 }
548 }
549
550 fn entrypoint(&self) -> Option<Entrypoint> {
551 match self {
552 Self::Succeeded(entrypoint) => Some(*entrypoint),
553 Self::Failed(_) => None,
554 }
555 }
556}
557
558#[derive(Clone, Copy)]
559struct ResolverInfo {
560 section_index: usize,
561 function_index: usize,
562 signature: PointerSignature,
563}
564
565#[derive(Clone, Debug)]
567pub struct TimesliceConfig {
568 pub max_run_time_before_yield: Duration,
570 pub max_run_time_before_throttle: Duration,
572 pub throttle_duration: Duration,
574}
575
576pub trait Timeslicer {
578 fn sleep(&self, duration: Duration) -> impl Future<Output = ()>;
580 fn yield_now(&self) -> impl Future<Output = ()>;
582}
583
584#[derive(Copy, Clone)]
586pub struct GlobalEnv(());
587
588#[derive(Copy, Clone)]
590pub struct ThreadEnv {
591 _not_send_sync: std::marker::PhantomData<*const ()>,
592}
593
594#[cfg(target_os = "linux")]
595type NativeThread = (libc::pid_t, libc::pid_t);
596#[cfg(target_os = "openbsd")]
597type NativeThread = libc::pthread_t;
598
599#[cfg(target_os = "linux")]
600unsafe fn current_native_thread() -> NativeThread {
601 (libc::getpid(), libc::gettid())
602}
603
604#[cfg(target_os = "openbsd")]
605unsafe fn current_native_thread() -> NativeThread {
606 libc::pthread_self()
607}
608
609#[cfg(target_os = "linux")]
610unsafe fn signal_native_thread(thread: NativeThread, signal: i32) -> i32 {
611 libc::syscall(libc::SYS_tgkill, thread.0, thread.1, signal) as i32
612}
613
614#[cfg(target_os = "openbsd")]
615unsafe fn signal_native_thread(thread: NativeThread, signal: i32) -> i32 {
616 libc::pthread_kill(thread, signal)
617}
618
619impl GlobalEnv {
620 pub unsafe fn new() -> Self {
625 static INIT: Once = Once::new();
626
627 INIT.call_once(|| {
642 let sa_mask = get_blocked_sigset();
643
644 for (sig, handler) in [
645 (libc::SIGUSR1, sigusr1_handler as *const () as usize),
646 (libc::SIGSEGV, sigsegv_handler as *const () as usize),
647 ] {
648 let mut act: libc::sigaction = std::mem::zeroed();
649 act.sa_sigaction = handler;
650 act.sa_flags = libc::SA_SIGINFO;
651 act.sa_mask = sa_mask;
652 if libc::sigaction(sig, &act, std::ptr::null_mut()) != 0 {
653 panic!("failed to setup handler for signal {}", sig);
654 }
655 }
656 });
657
658 Self(())
659 }
660
661 pub fn init_thread(self, async_preemption_interval: Duration) -> ThreadEnv {
663 RUST_TID.with(|_| {});
668 SIGUSR1_COUNTER.with(|_| {});
669 ACTIVE_JIT_CODE_ZONE.with(|_| {});
670
671 struct DeferDrop(Arc<PreemptionStateSignal>);
672 impl Drop for DeferDrop {
673 fn drop(&mut self) {
674 let x = &self.0;
675 *x.0.lock() = PreemptionState::Shutdown;
676 x.1.notify_one();
677 }
678 }
679
680 thread_local! {
681 static WATCHER: RefCell<Option<DeferDrop>> = RefCell::new(None);
682 }
683
684 if WATCHER.with(|x| x.borrow().is_some()) {
685 return ThreadEnv {
686 _not_send_sync: PhantomData,
687 };
688 }
689
690 let preemption_state = PREEMPTION_STATE.with(|x| x.clone());
691
692 unsafe {
693 let target_thread = current_native_thread();
694 let (ready_tx, ready_rx) = std::sync::mpsc::sync_channel(0);
695
696 std::thread::Builder::new()
697 .name("preempt-watcher".to_string())
698 .spawn(move || {
699 let mut state = preemption_state.0.lock();
700 let _ = ready_tx.send(());
701 loop {
702 match *state {
703 PreemptionState::Shutdown => break,
704 PreemptionState::Inactive => {
705 preemption_state.1.wait(&mut state);
706 }
707 PreemptionState::Armed(_) => {
708 let timeout = preemption_state.1.wait_while_for(
709 &mut state,
710 |x| matches!(x, PreemptionState::Armed(_)),
711 async_preemption_interval,
712 );
713 if timeout.timed_out() {
714 match *state {
715 PreemptionState::Armed(0) => {
716 *state = PreemptionState::Inactive;
717 }
718 PreemptionState::Armed(_) => {
719 if signal_native_thread(target_thread, libc::SIGUSR1) != 0 {
720 break;
721 }
722 }
723 PreemptionState::Inactive => {}
724 PreemptionState::Shutdown => break,
725 }
726 }
727 }
728 }
729 }
730 })
731 .expect("failed to spawn preemption watcher");
732 ready_rx
733 .recv()
734 .expect("preemption watcher stopped during startup");
735
736 WATCHER.with(|x| {
737 x.borrow_mut()
738 .replace(DeferDrop(PREEMPTION_STATE.with(|x| x.clone())));
739 });
740
741 ThreadEnv {
742 _not_send_sync: PhantomData,
743 }
744 }
745 }
746}
747
748impl UnboundProgram {
749 pub fn pin_to_current_thread(self, t: ThreadEnv) -> Program {
751 Program {
752 unbound: self,
753 data: RefCell::new(HashMap::new()),
754 t,
755 }
756 }
757}
758
759pub struct PreemptionEnabled(());
760
761impl PreemptionEnabled {
762 pub fn new(_: ThreadEnv) -> Self {
763 PREEMPTION_STATE.with(|x| {
764 let mut notify = false;
765 {
766 let mut st = x.0.lock();
767 let next = match *st {
768 PreemptionState::Inactive => {
769 notify = true;
770 PreemptionState::Armed(1)
771 }
772 PreemptionState::Armed(n) => PreemptionState::Armed(n + 1),
773 PreemptionState::Shutdown => unreachable!(),
774 };
775 *st = next;
776 }
777
778 if notify {
779 x.1.notify_one();
780 }
781 });
782 Self(())
783 }
784}
785
786impl Drop for PreemptionEnabled {
787 fn drop(&mut self) {
788 PREEMPTION_STATE.with(|x| {
789 let mut st = x.0.lock();
790 let next = match *st {
791 PreemptionState::Armed(1) => PreemptionState::Armed(0),
792 PreemptionState::Armed(n) => {
793 assert!(n > 1);
794 PreemptionState::Armed(n - 1)
795 }
796 PreemptionState::Inactive | PreemptionState::Shutdown => unreachable!(),
797 };
798 *st = next;
799 });
800 }
801}
802
803impl Program {
804 pub fn id(&self) -> u64 {
806 self.unbound.id
807 }
808
809 pub fn thread_env(&self) -> ThreadEnv {
810 self.t
811 }
812
813 pub fn data<T: Default + 'static>(&self) -> Rc<T> {
815 let mut data = self.data.borrow_mut();
816 let entry = data.entry(TypeId::of::<T>());
817 let entry = entry.or_insert_with(|| Rc::new(T::default()));
818 entry.clone().downcast().unwrap()
819 }
820
821 pub fn has_section(&self, name: &str) -> bool {
822 self.unbound.entrypoints.contains_key(name)
823 }
824
825 #[cfg(test)]
826 pub(crate) fn compiled_function_count_for_tests(&self) -> usize {
827 self
828 .unbound
829 .sections
830 .borrow()
831 .iter()
832 .map(|section| {
833 section
834 .functions
835 .iter()
836 .map(|function| {
837 function
838 .compiled
839 .values()
840 .filter(|compilation| compilation.entrypoint().is_some())
841 .count()
842 })
843 .sum::<usize>()
844 })
845 .sum()
846 }
847
848 #[cfg(test)]
849 pub(crate) fn failed_function_count_for_tests(&self) -> usize {
850 self
851 .unbound
852 .sections
853 .borrow()
854 .iter()
855 .map(|section| {
856 section
857 .functions
858 .iter()
859 .map(|function| {
860 function
861 .compiled
862 .values()
863 .filter(|compilation| compilation.entrypoint().is_none())
864 .count()
865 })
866 .sum::<usize>()
867 })
868 .sum()
869 }
870
871 #[cfg(test)]
872 pub(crate) fn function_compile_attempt_count_for_tests(&self) -> usize {
873 self
874 .unbound
875 .sections
876 .borrow()
877 .iter()
878 .map(|section| {
879 section
880 .functions
881 .iter()
882 .map(|function| function.compile_attempts)
883 .sum::<usize>()
884 })
885 .sum()
886 }
887
888 #[cfg(test)]
889 pub(crate) fn code_arena_used_for_tests(&self) -> usize {
890 self.unbound.code_arena.borrow().used
891 }
892
893 #[cfg(test)]
897 pub(crate) fn function_variant_counts_for_tests(&self) -> Vec<usize> {
898 self
899 .unbound
900 .sections
901 .borrow()
902 .iter()
903 .flat_map(|section| {
904 section.functions.iter().map(|function| {
905 function
906 .compiled
907 .values()
908 .filter(|compilation| compilation.entrypoint().is_some())
909 .count()
910 })
911 })
912 .collect()
913 }
914
915 fn compile_entrypoint(&self, section_index: usize) -> Result<Entrypoint, RuntimeError> {
916 self.compile_function(section_index, 0, PointerSignature::entry())
917 }
918
919 fn compile_resolver(&self, resolver_id: u32) -> Result<Entrypoint, RuntimeError> {
920 let Some(info) = self.unbound.resolvers.borrow().get(&resolver_id).copied() else {
921 return Err(RuntimeError::InvalidArgument(
922 "local call resolver not found",
923 ));
924 };
925 self.compile_function(info.section_index, info.function_index, info.signature)
926 }
927
928 fn cached_resolver_target(&self, resolver_id: u32) -> Option<usize> {
929 let info = self.unbound.resolvers.borrow().get(&resolver_id).copied()?;
930 let sections = self.unbound.sections.borrow();
931 let section = sections.get(info.section_index)?;
932 section
933 .functions
934 .get(info.function_index)?
935 .compiled
936 .get(&info.signature)
937 .and_then(|compilation| compilation.entrypoint())
938 .map(|entrypoint| entrypoint.code_ptr)
939 }
940
941 fn protect_code_pages(&self, executable_len: usize) -> Result<(), RuntimeError> {
942 let executable_len =
943 (executable_len + self.unbound.page_size - 1) & !(self.unbound.page_size - 1);
944 unsafe {
945 if executable_len > 0
946 && libc::mprotect(
947 self.unbound.code_base as *mut _,
948 executable_len,
949 libc::PROT_READ | libc::PROT_EXEC,
950 ) != 0
951 {
952 return Err(RuntimeError::PlatformError(
953 "failed to protect executable code",
954 ));
955 }
956 if executable_len < self.unbound.code_size
957 && libc::mprotect(
958 (self.unbound.code_base + executable_len) as *mut _,
959 self.unbound.code_size - executable_len,
960 libc::PROT_NONE,
961 ) != 0
962 {
963 return Err(RuntimeError::PlatformError("failed to protect unused code"));
964 }
965 }
966 Ok(())
967 }
968
969 fn compile_function(
970 &self,
971 section_index: usize,
972 function_index: usize,
973 signature: PointerSignature,
974 ) -> Result<Entrypoint, RuntimeError> {
975 {
976 let sections = self.unbound.sections.borrow();
977 let Some(section) = sections.get(section_index) else {
978 return Err(RuntimeError::InvalidArgument("section not found"));
979 };
980 let Some(function) = section.functions.get(function_index) else {
981 return Err(RuntimeError::InvalidArgument("function not found"));
982 };
983 if let Some(compilation) = function.compiled.get(&signature) {
984 return compilation.result();
985 }
986 }
987
988 let mut sections = self.unbound.sections.borrow_mut();
989 let section = sections
990 .get_mut(section_index)
991 .ok_or(RuntimeError::InvalidArgument("section not found"))?;
992 if function_index >= section.functions.len() {
993 return Err(RuntimeError::InvalidArgument("function not found"));
994 }
995 if let Some(compilation) = section.functions[function_index].compiled.get(&signature) {
996 return compilation.result();
997 }
998 #[cfg(test)]
999 {
1000 section.functions[function_index].compile_attempts += 1;
1001 }
1002 let function = section.layout.functions[function_index].clone();
1003 let code = self
1004 .unbound
1005 .cage
1006 .safe_deref_for_read(section.code_vaddr, section.code_len)
1007 .unwrap();
1008 let code_bytes = unsafe { std::slice::from_raw_parts(code.as_ptr() as *const u8, code.len()) };
1009 let region_analysis = crate::region_analysis::analyze_function(
1010 code_bytes,
1011 function.start_pc,
1012 function.end_pc,
1013 signature,
1014 self.unbound.cage.data_bottom() as u64,
1015 self.unbound.cage.data_top() as u64,
1016 );
1017 if self.unbound.require_static_regions && !region_analysis.unresolved.is_empty() {
1018 let err = RuntimeError::InvalidArgumentOwned(format!(
1019 "static region analysis failed in function [{}, {}): {} memory access(es) could not be \
1020 routed to a single region (instruction slots {:?})",
1021 function.start_pc,
1022 function.end_pc,
1023 region_analysis.unresolved.len(),
1024 region_analysis.unresolved,
1025 ));
1026 section.functions[function_index]
1027 .compiled
1028 .insert(signature, FunctionCompilation::Failed(err.clone()));
1029 return Err(err);
1030 }
1031
1032 let mut resolver_ids = vec![0u32; code_bytes.len() / 8];
1037 let mut pending_resolvers: Vec<(u32, ResolverInfo)> = Vec::new();
1038 let mut next_resolver_id = self.unbound.next_resolver_id.get();
1039 for (&call_pc, &callee_signature) in ®ion_analysis.call_signatures {
1040 let target_pc = local_call_target(code_bytes, call_pc);
1041 let callee_index = section.layout.pc_to_func[target_pc];
1042 let resolver_id = next_resolver_id;
1043 let Some(advanced) = resolver_id.checked_add(1) else {
1044 let err = RuntimeError::InvalidArgument("too many local call resolvers");
1045 section.functions[function_index]
1046 .compiled
1047 .insert(signature, FunctionCompilation::Failed(err.clone()));
1048 return Err(err);
1049 };
1050 next_resolver_id = advanced;
1051 resolver_ids[call_pc] = resolver_id;
1052 pending_resolvers.push((
1053 resolver_id,
1054 ResolverInfo {
1055 section_index,
1056 function_index: callee_index,
1057 signature: callee_signature,
1058 },
1059 ));
1060 }
1061
1062 let mut arena = self.unbound.code_arena.borrow_mut();
1063 if arena.used >= self.unbound.code_size {
1064 let err = RuntimeError::InvalidArgument("no space left for jit compilation");
1065 section.functions[function_index]
1066 .compiled
1067 .insert(signature, FunctionCompilation::Failed(err.clone()));
1068 return Err(err);
1069 }
1070
1071 unsafe {
1072 if libc::mprotect(
1073 self.unbound.code_base as *mut _,
1074 self.unbound.code_size,
1075 libc::PROT_READ | libc::PROT_WRITE,
1076 ) != 0
1077 {
1078 let err = RuntimeError::PlatformError("failed to make code writable");
1079 section.functions[function_index]
1080 .compiled
1081 .insert(signature, FunctionCompilation::Failed(err.clone()));
1082 return Err(err);
1083 }
1084 }
1085
1086 let code_ptr = self.unbound.code_base + arena.used;
1087 let mut written_len = self.unbound.code_size - arena.used;
1088 let mut errmsg_ptr = std::ptr::null_mut();
1089
1090 let ret = unsafe {
1091 crate::ubpf::ubpf_set_region_hints(
1092 section.vm.0.as_ptr(),
1093 region_analysis.hints.as_ptr(),
1094 region_analysis.hints.len(),
1095 );
1096 crate::ubpf::ubpf_set_lazy_local_call_resolver(
1097 section.vm.0.as_ptr(),
1098 Some(tls_local_call_resolver),
1099 resolver_ids.as_ptr(),
1100 resolver_ids.len(),
1101 );
1102 let ret = crate::ubpf::ubpf_translate_function_ex(
1103 section.vm.0.as_ptr(),
1104 code_ptr as *mut u8,
1105 &mut written_len,
1106 &mut errmsg_ptr,
1107 crate::ubpf::JitMode_ExtendedJitMode,
1108 function.start_pc as u32,
1109 function.end_pc as u32,
1110 );
1111 crate::ubpf::ubpf_set_region_hints(section.vm.0.as_ptr(), std::ptr::null(), 0);
1112 crate::ubpf::ubpf_set_lazy_local_call_resolver(
1113 section.vm.0.as_ptr(),
1114 None,
1115 std::ptr::null(),
1116 0,
1117 );
1118 ret
1119 };
1120
1121 if ret != 0 {
1122 let errmsg = unsafe {
1123 if errmsg_ptr.is_null() {
1124 "".to_string()
1125 } else {
1126 CStr::from_ptr(errmsg_ptr).to_string_lossy().into_owned()
1127 }
1128 };
1129 if !errmsg_ptr.is_null() {
1130 unsafe { libc::free(errmsg_ptr as _) };
1131 }
1132 let _ = self.protect_code_pages(arena.used);
1133 let err =
1134 RuntimeError::InvalidArgumentOwned(format!("ubpf: code translation failed: {errmsg}"));
1135 section.functions[function_index]
1136 .compiled
1137 .insert(signature, FunctionCompilation::Failed(err.clone()));
1138 return Err(err);
1139 }
1140 if !errmsg_ptr.is_null() {
1141 unsafe { libc::free(errmsg_ptr as _) };
1142 }
1143
1144 unsafe {
1145 crate::ubpf::ubpf_clear_instruction_cache(code_ptr as *mut u8, written_len);
1146 }
1147
1148 let new_used = arena.used + written_len;
1154 if let Err(err) = self.protect_code_pages(new_used) {
1155 section.functions[function_index]
1156 .compiled
1157 .insert(signature, FunctionCompilation::Failed(err.clone()));
1158 return Err(err);
1159 }
1160 arena.used = new_used;
1161
1162 self.unbound.next_resolver_id.set(next_resolver_id);
1164 {
1165 let mut resolvers = self.unbound.resolvers.borrow_mut();
1166 for (resolver_id, info) in pending_resolvers {
1167 resolvers.insert(resolver_id, info);
1168 }
1169 }
1170
1171 let entrypoint = Entrypoint { code_ptr };
1172 section.functions[function_index]
1173 .compiled
1174 .insert(signature, FunctionCompilation::Succeeded(entrypoint));
1175 tracing::debug!(
1176 section_index,
1177 function_index,
1178 start_pc = function.start_pc,
1179 end_pc = function.end_pc,
1180 native_code_addr = ?(code_ptr as *const u8),
1181 native_code_size = written_len,
1182 "jit compiled function"
1183 );
1184 Ok(entrypoint)
1185 }
1186
1187 pub async fn run(
1189 &self,
1190 timeslice: &TimesliceConfig,
1191 timeslicer: &impl Timeslicer,
1192 entrypoint: &str,
1193 resources: &mut [&mut dyn Any],
1194 calldata: &[u8],
1195 preemption: &PreemptionEnabled,
1196 ) -> Result<i64, Error> {
1197 self
1198 ._run(
1199 timeslice, timeslicer, entrypoint, resources, calldata, preemption,
1200 )
1201 .await
1202 .map_err(Error)
1203 }
1204
1205 async fn _run(
1206 &self,
1207 timeslice: &TimesliceConfig,
1208 timeslicer: &impl Timeslicer,
1209 entrypoint: &str,
1210 resources: &mut [&mut dyn Any],
1211 calldata: &[u8],
1212 _: &PreemptionEnabled,
1213 ) -> Result<i64, RuntimeError> {
1214 let Some(section_index) = self.unbound.entrypoints.get(entrypoint).copied() else {
1215 return Err(RuntimeError::InvalidArgument("entrypoint not found"));
1216 };
1217 let entrypoint = self.compile_entrypoint(section_index)?;
1218 struct CoDropper<'a, Input, Yield, Return, DefaultStack: Stack>(
1219 ScopedCoroutine<'a, Input, Yield, Return, DefaultStack>,
1220 );
1221 impl<'a, Input, Yield, Return, DefaultStack: Stack> Drop
1222 for CoDropper<'a, Input, Yield, Return, DefaultStack>
1223 {
1224 fn drop(&mut self) {
1225 unsafe {
1233 self.0.force_reset();
1234 }
1235 }
1236 }
1237
1238 let mut ectx = BorrowedExecContext::new();
1239
1240 if calldata.len() > MAX_CALLDATA_SIZE {
1241 return Err(RuntimeError::InvalidArgument("calldata too large"));
1242 }
1243 ectx.ctx.guest_stack[SHADOW_STACK_SIZE - calldata.len()..].copy_from_slice(calldata);
1244 let calldata_len = calldata.len();
1245
1246 let program_ret: u64 = {
1247 let guest_stack_top = self.unbound.cage.stack_top();
1248 let guest_stack_bottom = self.unbound.cage.stack_bottom();
1249 let ctx = &mut *ectx.ctx;
1250 let memory = JitMemory {
1251 stack_guest_bottom: guest_stack_bottom,
1252 stack_guest_top: guest_stack_top,
1253 stack_native_base: ctx.guest_stack.as_mut_ptr() as usize,
1254 data_guest_bottom: self.unbound.cage.data_bottom(),
1255 data_guest_top: self.unbound.cage.data_top(),
1256 data_native_base: self.unbound.cage.data_native_base(),
1257 };
1258 let memory_ptr = &memory as *const JitMemory as usize;
1259
1260 let mut co = AssumeSend(CoDropper(Coroutine::with_stack(
1261 &mut ctx.native_stack,
1262 move |yielder, _input| unsafe {
1263 ACTIVE_JIT_CODE_ZONE.with(|x| {
1264 x.yielder.set(NonNull::new(yielder as *const _ as *mut _));
1265 });
1266 let calldata_start = guest_stack_top - calldata_len;
1267 let stack_top = calldata_start & !0x7;
1268 let stack_len = stack_top - guest_stack_bottom;
1269 async_ebpf_entry_trampoline(
1270 entrypoint.code_ptr,
1271 calldata_start,
1272 calldata_start,
1273 guest_stack_bottom,
1274 stack_len,
1275 0,
1276 memory_ptr,
1277 )
1278 },
1279 )));
1280
1281 let mut last_yield_time: Option<Instant> = None;
1282 let mut last_throttle_time: Option<Instant> = None;
1283 let mut yielder: Option<AssumeSend<NonNull<Yielder<u64, Dispatch>>>> = None;
1284 let mut resume_input: u64 = 0;
1285 let mut did_throttle = false;
1286 let mut rust_tid_sigusr1_counter = (RUST_TID.with(|x| *x), SIGUSR1_COUNTER.with(|x| x.get()));
1287 let mut prev_async_task_output: Option<(&'static str, AsyncTaskOutput)> = None;
1288 let mut invoke_scope = InvokeScope {
1289 data: HashMap::new(),
1290 };
1291
1292 loop {
1293 ACTIVE_JIT_CODE_ZONE.with(|x| {
1294 x.code_range.set((
1295 self.unbound.code_base,
1296 self.unbound.code_base + self.unbound.code_size,
1297 ));
1298 x.yielder.set(yielder.map(|x| x.0));
1299 x.pointer_cage_protected_range.set((0, 4096));
1300 compiler_fence(Ordering::Release);
1301 x.valid.store(true, Ordering::Relaxed);
1302 });
1303 ACTIVE_PROGRAM.with(|x| x.set(self as *const _));
1304
1305 if let Some((helper_name, prev_async_task_output)) = prev_async_task_output.take() {
1307 resume_input = prev_async_task_output(&HelperScope {
1308 program: self,
1309 invoke: RefCell::new(&mut invoke_scope),
1310 resources: RefCell::new(resources),
1311 memory: &memory,
1312 mutable_dereferenced_regions: unsafe { std::mem::zeroed() },
1313 immutable_dereferenced_regions: unsafe { std::mem::zeroed() },
1314 can_post_task: false,
1315 })
1316 .map_err(|_| RuntimeError::AsyncHelperError(helper_name))?;
1317 }
1318
1319 let ret = co.0 .0.resume(resume_input);
1320 ACTIVE_PROGRAM.with(|x| x.set(std::ptr::null()));
1321 ACTIVE_JIT_CODE_ZONE.with(|x| {
1322 x.valid.store(false, Ordering::Relaxed);
1323 compiler_fence(Ordering::Release);
1324 yielder = x.yielder.get().map(AssumeSend);
1325 });
1326
1327 let dispatch: Dispatch = match ret {
1328 CoroutineResult::Return(x) => break x,
1329 CoroutineResult::Yield(x) => x,
1330 };
1331
1332 if dispatch.memory_access_error.is_some() || dispatch.async_preemption {
1334 unsafe {
1335 let unblock = get_blocked_sigset();
1336 libc::sigprocmask(libc::SIG_UNBLOCK, &unblock, std::ptr::null_mut());
1337 }
1338 }
1339
1340 if let Some(si_addr) = dispatch.memory_access_error {
1341 let vaddr = if si_addr >= memory.stack_native_base
1342 && si_addr < memory.stack_native_base + SHADOW_STACK_SIZE
1343 {
1344 memory.stack_guest_bottom + (si_addr - memory.stack_native_base)
1345 } else if si_addr >= memory.data_native_base
1346 && si_addr
1347 < memory.data_native_base + (memory.data_guest_top - memory.data_guest_bottom)
1348 {
1349 memory.data_guest_bottom + (si_addr - memory.data_native_base)
1350 } else {
1351 0
1352 };
1353 return Err(RuntimeError::MemoryFault(vaddr));
1354 }
1355
1356 if let Some(resolver_id) = dispatch.lazy_local_call {
1357 resume_input = self.compile_resolver(resolver_id)?.code_ptr as u64;
1358 continue;
1359 }
1360
1361 PENDING_ASYNC_TASK.with(|x| x.borrow_mut().take());
1363 let mut helper_name: &'static str = "";
1364
1365 let mut helper_scope = HelperScope {
1366 program: self,
1367 invoke: RefCell::new(&mut invoke_scope),
1368 resources: RefCell::new(resources),
1369 memory: &memory,
1370 mutable_dereferenced_regions: unsafe { std::mem::zeroed() },
1371 immutable_dereferenced_regions: unsafe { std::mem::zeroed() },
1372 can_post_task: false,
1373 };
1374
1375 if dispatch.async_preemption {
1376 self
1377 .unbound
1378 .event_listener
1379 .did_async_preempt(&mut helper_scope);
1380 } else {
1381 let Some((_, got_helper_name, helper)) = self
1383 .unbound
1384 .helpers
1385 .get(
1386 ((dispatch.index & 0xffff) as u16 ^ self.unbound.helper_id_xor).wrapping_sub(1)
1387 as usize,
1388 )
1389 .copied()
1390 else {
1391 panic!("unknown helper index: {}", dispatch.index);
1392 };
1393 helper_name = got_helper_name;
1394
1395 helper_scope.can_post_task = true;
1396 resume_input = helper(
1397 &mut helper_scope,
1398 dispatch.arg1,
1399 dispatch.arg2,
1400 dispatch.arg3,
1401 dispatch.arg4,
1402 dispatch.arg5,
1403 )
1404 .map_err(|()| RuntimeError::HelperError(helper_name))?;
1405 helper_scope.can_post_task = false;
1406 }
1407
1408 let pending_async_task = PENDING_ASYNC_TASK.with(|x| x.borrow_mut().take());
1409
1410 let new_rust_tid_sigusr1_counter =
1412 (RUST_TID.with(|x| *x), SIGUSR1_COUNTER.with(|x| x.get()));
1413 if new_rust_tid_sigusr1_counter == rust_tid_sigusr1_counter && pending_async_task.is_none()
1414 {
1415 continue;
1416 }
1417
1418 rust_tid_sigusr1_counter = new_rust_tid_sigusr1_counter;
1419
1420 let now = Instant::now();
1421 let last_throttle = last_throttle_time.get_or_insert(now);
1422 let last_yield = last_yield_time.get_or_insert(now);
1423 let should_throttle = now > *last_throttle
1424 && now.duration_since(*last_throttle) >= timeslice.max_run_time_before_throttle;
1425 let should_yield = now > *last_yield
1426 && now.duration_since(*last_yield) >= timeslice.max_run_time_before_yield;
1427 if should_throttle || should_yield || pending_async_task.is_some() {
1428 if should_throttle {
1431 if !did_throttle {
1432 did_throttle = true;
1433 tracing::warn!("throttling program");
1434 }
1435 timeslicer.sleep(timeslice.throttle_duration).await;
1436 let now = Instant::now();
1437 last_throttle_time = Some(now);
1438 last_yield_time = Some(now);
1439 let task = self.unbound.event_listener.did_throttle(&mut helper_scope);
1440 if let Some(task) = task {
1441 task.await;
1442 }
1443 } else if should_yield {
1444 timeslicer.yield_now().await;
1445 let now = Instant::now();
1446 last_yield_time = Some(now);
1447 self.unbound.event_listener.did_yield();
1448 }
1449
1450 if let Some(mut pending_async_task) = pending_async_task {
1452 let output =
1459 match pending_async_task.poll_unpin(&mut Context::from_waker(noop_waker_ref())) {
1460 Poll::Ready(output) => output,
1461 Poll::Pending => {
1462 let async_start = Instant::now();
1465 let output = pending_async_task.await;
1466 let async_dur = async_start.elapsed();
1467 if let Some(last_throttle_time) = &mut last_throttle_time {
1468 *last_throttle_time += async_dur;
1469 }
1470 if let Some(last_yield_time) = &mut last_yield_time {
1471 *last_yield_time += async_dur;
1472 }
1473 output
1474 }
1475 };
1476 prev_async_task_output = Some((helper_name, output));
1477 }
1478 }
1479 }
1480 };
1481
1482 Ok(program_ret as i64)
1483 }
1484}
1485
1486struct Vm(NonNull<crate::ubpf::ubpf_vm>);
1487unsafe impl Send for Vm {}
1488
1489impl Vm {
1490 fn new(cage: &PointerCage) -> Self {
1491 let vm = NonNull::new(unsafe { crate::ubpf::ubpf_create() }).expect("failed to create ubpf_vm");
1492 unsafe {
1493 crate::ubpf::ubpf_toggle_bounds_check(vm.as_ptr(), false);
1494 crate::ubpf::ubpf_set_jit_pointer_mask_and_offset(vm.as_ptr(), cage.mask(), cage.offset());
1495 }
1496 Self(vm)
1497 }
1498}
1499
1500impl Drop for Vm {
1501 fn drop(&mut self) {
1502 unsafe {
1503 crate::ubpf::ubpf_destroy(self.0.as_ptr());
1504 }
1505 }
1506}
1507
1508struct LoaderValidationScope {
1509 previous: *const ProgramLoader,
1510}
1511
1512impl LoaderValidationScope {
1513 fn new(loader: &ProgramLoader) -> Self {
1514 let previous = LOADING_PROGRAM_LOADER.with(|x| {
1515 let previous = x.get();
1516 x.set(loader as *const _);
1517 previous
1518 });
1519 Self { previous }
1520 }
1521}
1522
1523impl Drop for LoaderValidationScope {
1524 fn drop(&mut self) {
1525 LOADING_PROGRAM_LOADER.with(|x| x.set(self.previous));
1526 }
1527}
1528
1529fn local_call_target(code: &[u8], pc: usize) -> usize {
1530 let offset = pc * 8;
1531 let imm = i32::from_le_bytes([
1532 code[offset + 4],
1533 code[offset + 5],
1534 code[offset + 6],
1535 code[offset + 7],
1536 ]);
1537 (pc as i64 + imm as i64 + 1) as usize
1538}
1539
1540impl ProgramLoader {
1541 pub fn new(
1543 rng: &mut impl rand::Rng,
1544 event_listener: Arc<dyn ProgramEventListener>,
1545 raw_helpers: &[&[(&'static str, Helper)]],
1546 ) -> Self {
1547 let helper_id_xor = rng.gen::<u16>();
1548 let mut helpers_inverse: HashMap<&'static str, i32> = HashMap::new();
1549 let mut shuffled_helpers = raw_helpers
1551 .iter()
1552 .flat_map(|x| x.iter().copied())
1553 .collect::<HashMap<_, _>>()
1554 .into_iter()
1555 .collect::<Vec<_>>();
1556 shuffled_helpers.shuffle(rng);
1557 let mut helpers: Vec<(u16, &'static str, Helper)> = Vec::with_capacity(shuffled_helpers.len());
1558
1559 assert!(shuffled_helpers.len() <= 65535);
1560
1561 for (i, (name, helper)) in shuffled_helpers.into_iter().enumerate() {
1562 let entropy = rng.gen::<u16>() & 0x7fff;
1563 helpers.push((entropy, name, helper));
1564 helpers_inverse.insert(
1565 name,
1566 (((entropy as usize) << 16) | ((i + 1) ^ (helper_id_xor as usize))) as i32,
1567 );
1568 }
1569
1570 tracing::info!(?helpers_inverse, "generated helper table");
1571 Self {
1572 helper_id_xor,
1573 helpers: Arc::new(helpers),
1574 helpers_inverse,
1575 event_listener,
1576 code_size_limit: DEFAULT_CODE_SIZE_LIMIT,
1577 require_static_regions: false,
1578 }
1579 }
1580
1581 pub fn require_static_region_analysis(mut self, require: bool) -> Self {
1598 self.require_static_regions = require;
1599 self
1600 }
1601
1602 pub fn with_code_size_limit(mut self, limit: usize) -> Self {
1615 assert!(
1616 limit > 0 && limit % (64 * 1024) == 0,
1617 "code size limit must be a non-zero multiple of 64 KiB"
1618 );
1619 assert!(
1620 limit <= u32::MAX as usize,
1621 "code size limit must fit in u32"
1622 );
1623 self.code_size_limit = limit;
1624 self
1625 }
1626
1627 pub fn load(&self, rng: &mut impl rand::Rng, elf: &[u8]) -> Result<UnboundProgram, Error> {
1629 self._load(rng, elf).map_err(Error)
1630 }
1631
1632 fn _load(&self, rng: &mut impl rand::Rng, elf: &[u8]) -> Result<UnboundProgram, RuntimeError> {
1633 let start_time = Instant::now();
1634 let cage = PointerCage::new(rng, SHADOW_STACK_SIZE, elf.len())?;
1635
1636 let code_sections = {
1637 let mut data = cage
1638 .safe_deref_for_read(cage.data_bottom(), elf.len())
1639 .unwrap();
1640 let data = unsafe { data.as_mut() };
1641 data.copy_from_slice(elf);
1642
1643 link_elf(data, cage.data_bottom(), &self.helpers_inverse).map_err(RuntimeError::Linker)?
1644 };
1645 cage.freeze_data();
1646
1647 let page_size = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
1648 if page_size < 0 {
1649 return Err(RuntimeError::PlatformError("failed to get page size"));
1650 }
1651 let page_size = page_size as usize;
1652
1653 let guard_size_before = rng.gen_range(16..128) * page_size;
1654 let guard_size_after = rng.gen_range(16..128) * page_size;
1655
1656 let code_len_allocated = self.code_size_limit;
1657 let code_mem = MmapRaw::from(
1658 MmapOptions::new()
1659 .len(code_len_allocated + guard_size_before + guard_size_after)
1660 .map_anon()
1661 .map_err(|_| RuntimeError::PlatformError("failed to allocate code memory"))?,
1662 );
1663 let code_base = code_mem.as_ptr() as usize + guard_size_before;
1664
1665 unsafe {
1666 if libc::mprotect(
1667 code_mem.as_mut_ptr() as *mut _,
1668 guard_size_before,
1669 libc::PROT_NONE,
1670 ) != 0
1671 || libc::mprotect(
1672 code_mem
1673 .as_mut_ptr()
1674 .offset((guard_size_before + code_len_allocated) as isize) as *mut _,
1675 guard_size_after,
1676 libc::PROT_NONE,
1677 ) != 0
1678 || libc::mprotect(
1679 code_mem.as_mut_ptr().offset(guard_size_before as isize) as *mut _,
1680 code_len_allocated,
1681 libc::PROT_NONE,
1682 ) != 0
1683 {
1684 return Err(RuntimeError::PlatformError("failed to protect code memory"));
1685 }
1686 }
1687
1688 let mut entrypoints = HashMap::new();
1689 let mut sections = Vec::new();
1690 let resolvers = HashMap::new();
1691 let next_resolver_id = 1u32;
1692
1693 for (section_name, code_vaddr_size) in code_sections {
1694 let vm = Vm::new(&cage);
1695 unsafe {
1696 if crate::ubpf::ubpf_register_external_dispatcher(
1697 vm.0.as_ptr(),
1698 Some(tls_dispatcher),
1699 Some(std_validator),
1700 ) != 0
1701 {
1702 return Err(RuntimeError::PlatformError(
1703 "ubpf: failed to register external dispatcher",
1704 ));
1705 }
1706 }
1707
1708 let mut errmsg_ptr = std::ptr::null_mut();
1709 let code = cage
1710 .safe_deref_for_read(code_vaddr_size.0, code_vaddr_size.1)
1711 .unwrap();
1712 let code_bytes =
1713 unsafe { std::slice::from_raw_parts(code.as_ptr() as *const u8, code.len()) };
1714 validate_local_call_graph(code_bytes).map_err(|err| {
1715 RuntimeError::InvalidArgumentOwned(format!(
1716 "local call graph validation failed in {section_name}: {err}"
1717 ))
1718 })?;
1719 let layout = analyze_functions(code_bytes).map_err(|err| {
1720 RuntimeError::InvalidArgumentOwned(format!(
1721 "local function analysis failed in {section_name}: {err}"
1722 ))
1723 })?;
1724 let ret = unsafe {
1725 let validation_scope = LoaderValidationScope::new(self);
1726 let ret = crate::ubpf::ubpf_load(
1727 vm.0.as_ptr(),
1728 code.as_ptr() as *const _,
1729 code.len() as u32,
1730 &mut errmsg_ptr,
1731 );
1732 drop(validation_scope);
1733 ret
1734 };
1735 if ret != 0 {
1736 let errmsg = unsafe {
1737 if errmsg_ptr.is_null() {
1738 "".to_string()
1739 } else {
1740 CStr::from_ptr(errmsg_ptr).to_string_lossy().into_owned()
1741 }
1742 };
1743 if !errmsg_ptr.is_null() {
1744 unsafe { libc::free(errmsg_ptr as _) };
1745 }
1746 tracing::error!(section_name, error = errmsg, "failed to load code");
1747 return Err(RuntimeError::InvalidArgumentOwned(format!(
1748 "ubpf: code load failed: {errmsg}"
1749 )));
1750 }
1751
1752 let section_index = sections.len();
1753
1754 entrypoints.insert(section_name, section_index);
1755 let functions = (0..layout.functions.len())
1756 .map(|_| FunctionState::default())
1757 .collect();
1758 sections.push(Section {
1759 vm,
1760 code_vaddr: code_vaddr_size.0,
1761 code_len: code_vaddr_size.1,
1762 layout,
1763 functions,
1764 });
1765 }
1766
1767 tracing::info!(
1768 elf_size = elf.len(),
1769 native_code_addr = ?code_mem.as_ptr(),
1770 native_code_size_limit = code_len_allocated,
1771 guard_size_before,
1772 guard_size_after,
1773 duration = ?start_time.elapsed(),
1774 cage_ptr = ?cage.region().as_ptr(),
1775 cage_mapped_size = cage.region().len(),
1776 "loaded program for lazy jit"
1777 );
1778
1779 Ok(UnboundProgram {
1780 id: NEXT_PROGRAM_ID.fetch_add(1, Ordering::Relaxed),
1781 _code_mem: code_mem,
1782 code_base,
1783 code_size: code_len_allocated,
1784 page_size,
1785 code_arena: RefCell::new(CodeArena { used: 0 }),
1786 cage,
1787 helper_id_xor: self.helper_id_xor,
1788 helpers: self.helpers.clone(),
1789 event_listener: self.event_listener.clone(),
1790 require_static_regions: self.require_static_regions,
1791 entrypoints,
1792 sections: RefCell::new(sections),
1793 resolvers: RefCell::new(resolvers),
1794 next_resolver_id: Cell::new(next_resolver_id),
1795 })
1796 }
1797}
1798
1799#[derive(Default)]
1800struct Dispatch {
1801 async_preemption: bool,
1802 memory_access_error: Option<usize>,
1803 lazy_local_call: Option<u32>,
1804
1805 index: u32,
1806 arg1: u64,
1807 arg2: u64,
1808 arg3: u64,
1809 arg4: u64,
1810 arg5: u64,
1811}
1812
1813unsafe extern "C" fn tls_dispatcher(
1814 arg1: u64,
1815 arg2: u64,
1816 arg3: u64,
1817 arg4: u64,
1818 arg5: u64,
1819 index: std::os::raw::c_uint,
1820 _cookie: *mut std::os::raw::c_void,
1821) -> u64 {
1822 let yielder = ACTIVE_JIT_CODE_ZONE
1823 .with(|x| x.yielder.get())
1824 .expect("no yielder");
1825 let yielder = yielder.as_ref();
1826 let ret = yielder.suspend(Dispatch {
1827 async_preemption: false,
1828 memory_access_error: None,
1829 lazy_local_call: None,
1830 index,
1831 arg1,
1832 arg2,
1833 arg3,
1834 arg4,
1835 arg5,
1836 });
1837 ret
1838}
1839
1840unsafe extern "C" fn tls_local_call_resolver(resolver_id: std::os::raw::c_uint) -> u64 {
1841 let program = ACTIVE_PROGRAM.with(|x| x.get());
1842 if !program.is_null() {
1843 if let Some(ptr) = (*program).cached_resolver_target(resolver_id) {
1844 return ptr as u64;
1845 }
1846 }
1847
1848 let yielder = ACTIVE_JIT_CODE_ZONE
1849 .with(|x| x.yielder.get())
1850 .expect("no yielder");
1851 let yielder = yielder.as_ref();
1852 yielder.suspend(Dispatch {
1853 lazy_local_call: Some(resolver_id),
1854 ..Default::default()
1855 })
1856}
1857
1858unsafe extern "C" fn std_validator(
1859 index: std::os::raw::c_uint,
1860 _vm: *const crate::ubpf::ubpf_vm,
1861) -> bool {
1862 let loader = LOADING_PROGRAM_LOADER.with(|x| x.get());
1863 if loader.is_null() {
1864 return false;
1865 }
1866 let loader = &*loader;
1867 let entropy = (index >> 16) & 0xffff;
1868 let index = (((index & 0xffff) as u16) ^ loader.helper_id_xor).wrapping_sub(1);
1869 loader.helpers.get(index as usize).map(|x| x.0) == Some(entropy as u16)
1870}
1871
1872#[cfg(all(target_arch = "x86_64", target_os = "linux"))]
1873unsafe fn program_counter(uctx: *mut libc::ucontext_t) -> usize {
1874 (*uctx).uc_mcontext.gregs[libc::REG_RIP as usize] as usize
1875}
1876
1877#[cfg(all(target_arch = "aarch64", target_os = "linux"))]
1878unsafe fn program_counter(uctx: *mut libc::ucontext_t) -> usize {
1879 (*uctx).uc_mcontext.pc as usize
1880}
1881
1882#[cfg(all(target_arch = "x86_64", target_os = "openbsd"))]
1883unsafe fn program_counter(uctx: *mut libc::ucontext_t) -> usize {
1884 (*uctx).sc_rip as usize
1885}
1886
1887#[cfg(all(target_arch = "aarch64", target_os = "openbsd"))]
1888unsafe fn program_counter(uctx: *mut libc::ucontext_t) -> usize {
1889 (*uctx).sc_elr as usize
1890}
1891
1892unsafe extern "C" fn sigsegv_handler(
1893 _sig: i32,
1894 siginfo: *mut libc::siginfo_t,
1895 uctx: *mut libc::ucontext_t,
1896) {
1897 let fail = || restore_default_signal_handler(libc::SIGSEGV);
1898
1899 let Some((jit_code_zone, pointer_cage, yielder)) = ACTIVE_JIT_CODE_ZONE.with(|x| {
1900 if x.valid.load(Ordering::Relaxed) {
1901 compiler_fence(Ordering::Acquire);
1902 Some((
1903 x.code_range.get(),
1904 x.pointer_cage_protected_range.get(),
1905 x.yielder.get(),
1906 ))
1907 } else {
1908 None
1909 }
1910 }) else {
1911 return fail();
1912 };
1913
1914 let pc = program_counter(uctx);
1915
1916 if pc < jit_code_zone.0 || pc >= jit_code_zone.1 {
1917 return fail();
1918 }
1919
1920 if (*siginfo).si_code != 1 && (*siginfo).si_code != 2 {
1922 return fail();
1923 }
1924
1925 let si_addr = (*siginfo).si_addr() as usize;
1926 if si_addr < pointer_cage.0 || si_addr >= pointer_cage.1 {
1927 return fail();
1928 }
1929
1930 let yielder = yielder.expect("no yielder").as_ref();
1931 yielder.suspend(Dispatch {
1932 memory_access_error: Some(si_addr),
1933 ..Default::default()
1934 });
1935}
1936
1937unsafe extern "C" fn sigusr1_handler(
1938 _sig: i32,
1939 _siginfo: *mut libc::siginfo_t,
1940 uctx: *mut libc::ucontext_t,
1941) {
1942 SIGUSR1_COUNTER.with(|x| x.set(x.get() + 1));
1943
1944 let Some((jit_code_zone, yielder)) = ACTIVE_JIT_CODE_ZONE.with(|x| {
1945 if x.valid.load(Ordering::Relaxed) {
1946 compiler_fence(Ordering::Acquire);
1947 Some((x.code_range.get(), x.yielder.get()))
1948 } else {
1949 None
1950 }
1951 }) else {
1952 return;
1953 };
1954 let pc = program_counter(uctx);
1955 if pc < jit_code_zone.0 || pc >= jit_code_zone.1 {
1956 return;
1957 }
1958
1959 let Some(yielder) = yielder else {
1962 return;
1963 };
1964 yielder.as_ref().suspend(Dispatch {
1965 async_preemption: true,
1966 ..Default::default()
1967 });
1968}
1969
1970unsafe fn restore_default_signal_handler(signum: i32) {
1971 let mut act: libc::sigaction = std::mem::zeroed();
1972 act.sa_sigaction = libc::SIG_DFL;
1973 act.sa_flags = libc::SA_SIGINFO;
1974 if libc::sigaction(signum, &act, std::ptr::null_mut()) != 0 {
1975 libc::abort();
1976 }
1977}
1978
1979fn get_blocked_sigset() -> libc::sigset_t {
1980 unsafe {
1981 let mut s: libc::sigset_t = std::mem::zeroed();
1982 libc::sigaddset(&mut s, libc::SIGUSR1);
1983 libc::sigaddset(&mut s, libc::SIGSEGV);
1984 s
1985 }
1986}