Skip to main content

starnix_core/bpf/
program.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::bpf::BpfMapHandle;
6use crate::bpf::fs::get_bpf_object;
7use crate::security;
8use crate::task::{CurrentTask, CurrentTaskAndLocked, Kernel, register_delayed_release};
9use crate::vfs::{FdNumber, OutputBuffer};
10use ebpf::{
11    BPF_LDDW, BPF_PSEUDO_BTF_ID, BPF_PSEUDO_FUNC, BPF_PSEUDO_MAP_FD, BPF_PSEUDO_MAP_IDX,
12    BPF_PSEUDO_MAP_IDX_VALUE, BPF_PSEUDO_MAP_VALUE, EbpfInstruction, EbpfProgram,
13    EbpfProgramContext, StaticHelperSet, VerifiedEbpfProgram, VerifierLogger, link_program,
14    verify_program,
15};
16use ebpf_api::{AttachType, EbpfApiError, MapsContext, PinnedMap, ProgramType, StructId};
17use fidl_fuchsia_ebpf as febpf;
18use starnix_lifecycle::{AtomicCounter, ObjectReleaser, ReleaserAction};
19use starnix_logging::{log_warn, track_stub};
20use starnix_sync::{EbpfStateLock, LockBefore, Locked};
21use starnix_types::ownership::{Releasable, ReleaseGuard};
22use starnix_uapi::auth::{CAP_BPF, CAP_NET_ADMIN, CAP_PERFMON, CAP_SYS_ADMIN};
23use starnix_uapi::errors::Errno;
24use starnix_uapi::{bpf_attr__bindgen_ty_4, errno, error};
25use std::sync::{Arc, Weak};
26
27#[derive(Clone, Debug)]
28pub struct ProgramInfo {
29    pub program_type: ProgramType,
30    pub expected_attach_type: AttachType,
31}
32
33impl TryFrom<&bpf_attr__bindgen_ty_4> for ProgramInfo {
34    type Error = Errno;
35
36    fn try_from(info: &bpf_attr__bindgen_ty_4) -> Result<Self, Self::Error> {
37        Ok(Self {
38            program_type: info.prog_type.try_into().map_err(map_ebpf_api_error)?,
39            expected_attach_type: info.expected_attach_type.into(),
40        })
41    }
42}
43pub type ProgramId = u32;
44
45static NEXT_PROGRAM_ID: AtomicCounter<u32> = AtomicCounter::<u32>::new_const(1);
46fn new_program_id() -> ProgramId {
47    NEXT_PROGRAM_ID.next()
48}
49
50#[derive(Debug)]
51pub struct Program {
52    /// Program info specified during program initialization.
53    pub info: ProgramInfo,
54
55    /// Verified program.
56    program: VerifiedEbpfProgram,
57
58    /// eBPF maps used by the program. These should match the `maps` field in
59    /// the `program`.
60    maps: Vec<BpfMapHandle>,
61
62    /// Integer program ID. This is dinstinct from `fidl_id` because `fidl_id`
63    /// is 64-bit, while Linux uses 32-bit IDs.
64    id: ProgramId,
65
66    /// Handle used when the program is transferred over FIDL to other services.
67    fidl_handle: febpf::ProgramHandle,
68
69    /// ID of the program used in FIDL
70    fidl_id: febpf::ProgramId,
71
72    /// The service end of the `fidl_handle`. Should be moved to the BPF Service
73    /// once it's implemented.
74    #[allow(dead_code)]
75    service_handle: zx::EventPair,
76
77    /// Weak reference to the Kernel where this program is registered.
78    kernel: Weak<Kernel>,
79
80    /// The security state associated with this bpf Program.
81    pub security_state: security::BpfProgState,
82}
83
84fn map_ebpf_api_error(e: EbpfApiError) -> Errno {
85    match e {
86        EbpfApiError::InvalidProgramType(_) | EbpfApiError::InvalidExpectedAttachType(_) => {
87            errno!(EINVAL, e)
88        }
89        EbpfApiError::UnsupportedProgramType(_) => errno!(ENOTSUP, e),
90    }
91}
92
93impl Program {
94    pub fn new<L>(
95        locked: &mut Locked<L>,
96        current_task: &CurrentTask,
97        info: ProgramInfo,
98        logger: &mut dyn OutputBuffer,
99        mut code: Vec<EbpfInstruction>,
100    ) -> Result<ProgramHandle, Errno>
101    where
102        L: LockBefore<EbpfStateLock>,
103    {
104        Self::check_load_access(current_task, &info)?;
105        let maps = link_maps_fds(current_task, &mut code)?;
106        let maps_schema = maps.iter().map(|m| m.schema).collect();
107        let mut logger = BufferVeriferLogger::new(logger);
108        let calling_context = info
109            .program_type
110            .create_calling_context(info.expected_attach_type, maps_schema)
111            .map_err(map_ebpf_api_error)?;
112        let program = verify_program(code, calling_context, &mut logger)
113            .map_err(|err| errno!(EINVAL, err))?;
114
115        let (fidl_handle, service_handle) = zx::EventPair::create();
116        let fidl_id =
117            febpf::ProgramId { id: fidl_handle.koid().expect("Failed to get koid").raw_koid() };
118        let fidl_handle = febpf::ProgramHandle { handle: fidl_handle };
119
120        let program = ProgramHandle::new(
121            Self {
122                info,
123                program,
124                maps,
125                id: new_program_id(),
126                fidl_handle,
127                fidl_id,
128                service_handle,
129                kernel: Arc::downgrade(current_task.kernel()),
130                security_state: security::bpf_prog_alloc(current_task),
131            }
132            .into(),
133        );
134        current_task.kernel().ebpf_state.register_program(locked, &program);
135
136        Ok(program)
137    }
138
139    pub fn id(&self) -> ProgramId {
140        self.id
141    }
142
143    pub fn link<C: EbpfProgramContext<Map = PinnedMap> + StaticHelperSet>(
144        &self,
145        program_type: ProgramType,
146    ) -> Result<EbpfProgram<C>, Errno>
147    where
148        for<'a> C::RunContext<'a>: MapsContext<'a>,
149    {
150        if program_type != self.info.program_type {
151            return error!(EINVAL);
152        }
153
154        let maps = self.maps.iter().map(|map| map.get_inner()).collect();
155        let program = link_program(&self.program, maps).map_err(|err| errno!(EINVAL, err))?;
156
157        Ok(program)
158    }
159
160    fn check_load_access(current_task: &CurrentTask, info: &ProgramInfo) -> Result<(), Errno> {
161        if matches!(info.program_type, ProgramType::CgroupSkb | ProgramType::SocketFilter)
162            && current_task.kernel().allow_unprivileged_bpf()
163        {
164            return Ok(());
165        }
166        if security::is_task_capable_noaudit(current_task, CAP_SYS_ADMIN) {
167            return Ok(());
168        }
169        security::check_task_capable(current_task, CAP_BPF)?;
170        match info.program_type {
171            // Loading tracing program types additionally require the CAP_PERFMON capability.
172            ProgramType::Kprobe
173            | ProgramType::Tracepoint
174            | ProgramType::PerfEvent
175            | ProgramType::RawTracepoint
176            | ProgramType::RawTracepointWritable
177            | ProgramType::Tracing => security::check_task_capable(current_task, CAP_PERFMON),
178
179            // Loading networking program types additionally require the CAP_NET_ADMIN capability.
180            ProgramType::SocketFilter
181            | ProgramType::SchedCls
182            | ProgramType::SchedAct
183            | ProgramType::Xdp
184            | ProgramType::SockOps
185            | ProgramType::SkSkb
186            | ProgramType::SkMsg
187            | ProgramType::SkLookup
188            | ProgramType::SkReuseport
189            | ProgramType::FlowDissector
190            | ProgramType::Netfilter => security::check_task_capable(current_task, CAP_NET_ADMIN),
191
192            // No additional checks are necessary for other program types.
193            ProgramType::CgroupDevice
194            | ProgramType::CgroupSkb
195            | ProgramType::CgroupSock
196            | ProgramType::CgroupSockAddr
197            | ProgramType::CgroupSockopt
198            | ProgramType::CgroupSysctl
199            | ProgramType::Ext
200            | ProgramType::LircMode2
201            | ProgramType::Lsm
202            | ProgramType::LwtIn
203            | ProgramType::LwtOut
204            | ProgramType::LwtSeg6Local
205            | ProgramType::LwtXmit
206            | ProgramType::StructOps
207            | ProgramType::Syscall
208            | ProgramType::Unspec
209            | ProgramType::Fuse => Ok(()),
210        }
211    }
212
213    pub fn fidl_id(&self) -> febpf::ProgramId {
214        self.fidl_id
215    }
216
217    pub fn fidl_handle(&self) -> febpf::ProgramHandle {
218        let handle = self
219            .fidl_handle
220            .handle
221            .duplicate_handle(zx::Rights::TRANSFER | zx::Rights::SIGNAL | zx::Rights::WAIT)
222            .expect("Failed to duplicate handle");
223        febpf::ProgramHandle { handle }
224    }
225}
226
227impl Releasable for Program {
228    type Context<'a> = CurrentTaskAndLocked<'a>;
229
230    fn release<'a>(self, (locked, _current_task): CurrentTaskAndLocked<'a>) {
231        if let Some(kernel) = self.kernel.upgrade() {
232            kernel.ebpf_state.unregister_program(locked, self.id);
233        }
234
235        // Signal the FIDL handle to indicate that the program handle is defunct
236        // and should be closed.
237        self.fidl_handle
238            .handle
239            .signal(
240                zx::Signals::NONE,
241                zx::Signals::from_bits_truncate(febpf::PROGRAM_DEFUNCT_SIGNAL),
242            )
243            .unwrap();
244    }
245}
246
247pub enum ProgramReleaserAction {}
248impl ReleaserAction<Program> for ProgramReleaserAction {
249    fn release(program: ReleaseGuard<Program>) {
250        register_delayed_release(program);
251    }
252}
253pub type ProgramReleaser = ObjectReleaser<Program, ProgramReleaserAction>;
254pub type ProgramHandle = Arc<ProgramReleaser>;
255pub type WeakProgramHandle = Weak<ProgramReleaser>;
256
257impl TryFrom<&Program> for febpf::VerifiedProgram {
258    type Error = Errno;
259
260    fn try_from(program: &Program) -> Result<febpf::VerifiedProgram, Errno> {
261        let mut maps = Vec::with_capacity(program.maps.len());
262        for map in program.maps.iter() {
263            maps.push(map.share().map_err(|_| errno!(EIO))?);
264        }
265
266        let code_u64: &[u64] = zerocopy::transmute_ref!(program.program.code());
267
268        let mut struct_access_instructions =
269            Vec::with_capacity(program.program.struct_access_instructions().len());
270        for v in program.program.struct_access_instructions() {
271            let struct_id = StructId::try_from(&v.memory_id).map_err(|()| errno!(EINVAL))?.into();
272            struct_access_instructions.push(febpf::StructAccess {
273                pc: v.pc.try_into().unwrap(),
274                struct_id,
275                field_offset: v.field_offset.try_into().unwrap(),
276                is_32_bit_ptr_load: v.is_32_bit_ptr_load,
277            })
278        }
279        Ok(febpf::VerifiedProgram {
280            code: Some(code_u64.to_vec()),
281            struct_access_instructions: Some(struct_access_instructions),
282            maps: Some(maps),
283            ..Default::default()
284        })
285    }
286}
287
288/// Links maps referenced by FD, replacing them with by-index references.
289fn link_maps_fds(
290    current_task: &CurrentTask,
291    code: &mut Vec<EbpfInstruction>,
292) -> Result<Vec<BpfMapHandle>, Errno> {
293    let code_len = code.len();
294    let mut maps = Vec::<BpfMapHandle>::new();
295    for (pc, instruction) in code.iter_mut().enumerate() {
296        if instruction.code() == BPF_LDDW {
297            // BPF_LDDW requires 2 instructions.
298            if pc >= code_len - 1 {
299                return error!(EINVAL);
300            }
301
302            match instruction.src_reg() {
303                0 => {}
304                BPF_PSEUDO_MAP_FD | BPF_PSEUDO_MAP_VALUE => {
305                    let lddw_type = if instruction.src_reg() == BPF_PSEUDO_MAP_FD {
306                        BPF_PSEUDO_MAP_IDX
307                    } else {
308                        BPF_PSEUDO_MAP_IDX_VALUE
309                    };
310                    // If the instruction references a map fd, then we need to look up the map fd
311                    // and create a reference from this program to that object.
312                    instruction.set_src_reg(lddw_type);
313
314                    let fd = FdNumber::from_raw(instruction.imm());
315                    let object = get_bpf_object(current_task, fd)?;
316                    let map: &BpfMapHandle = object.as_map()?;
317
318                    // Find the map in `maps` or insert it otherwise.
319                    let maybe_index = maps.iter().position(|v| Arc::ptr_eq(v, map));
320                    let index = match maybe_index {
321                        Some(index) => index,
322                        None => {
323                            let index = maps.len();
324                            maps.push(map.clone());
325                            index
326                        }
327                    };
328
329                    instruction.set_imm(index.try_into().unwrap());
330                }
331                BPF_PSEUDO_MAP_IDX
332                | BPF_PSEUDO_MAP_IDX_VALUE
333                | BPF_PSEUDO_BTF_ID
334                | BPF_PSEUDO_FUNC => {
335                    track_stub!(
336                        TODO("https://fxbug.dev/378564467"),
337                        "unsupported pseudo src for ldimm64",
338                        instruction.src_reg()
339                    );
340                    return error!(ENOTSUP);
341                }
342                _ => {
343                    return error!(EINVAL);
344                }
345            }
346        }
347    }
348    Ok(maps)
349}
350
351struct BufferVeriferLogger<'a> {
352    buffer: &'a mut dyn OutputBuffer,
353    full: bool,
354}
355
356impl BufferVeriferLogger<'_> {
357    fn new<'a>(buffer: &'a mut dyn OutputBuffer) -> BufferVeriferLogger<'a> {
358        BufferVeriferLogger { buffer, full: false }
359    }
360}
361
362impl VerifierLogger for BufferVeriferLogger<'_> {
363    fn log(&mut self, line: &[u8]) {
364        debug_assert!(line.is_ascii());
365
366        if self.full {
367            return;
368        }
369        if line.len() + 1 > self.buffer.available() {
370            self.full = true;
371            return;
372        }
373        match self.buffer.write(line) {
374            Err(e) => {
375                log_warn!("Unable to write verifier log: {e:?}");
376                self.full = true;
377            }
378            _ => {}
379        }
380        match self.buffer.write(b"\n") {
381            Err(e) => {
382                log_warn!("Unable to write verifier log: {e:?}");
383                self.full = true;
384            }
385            _ => {}
386        }
387    }
388}