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