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