Skip to main content

async_ebpf/
program.rs

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