Skip to main content

starnix_core/bpf/
fs.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
5// TODO(https://github.com/rust-lang/rust/issues/39371): remove
6#![allow(non_upper_case_globals)]
7
8use crate::bpf::syscalls::BpfTypeFormat;
9use crate::bpf::{BpfMapHandle, ProgramHandle};
10use crate::mm::memory::MemoryObject;
11use crate::mm::{DesiredAddress, MappingOptions, PAGE_SIZE, ProtectionFlags};
12use crate::security::{self, PermissionFlags};
13use crate::task::{
14    CurrentTask, EventHandler, SignalHandler, SignalHandlerInner, Task, WaitCanceler, Waiter,
15};
16use crate::vfs::buffers::{InputBuffer, OutputBuffer};
17use crate::vfs::{
18    CacheMode, CheckAccessReason, FdNumber, FileObject, FileOps, FileSystem, FileSystemHandle,
19    FileSystemOps, FileSystemOptions, FsNode, FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr,
20    MemoryDirectoryFile, MemoryXattrStorage, NamespaceNode, RenameContext, XattrStorage as _,
21    default_mmap, fileops_impl_nonseekable, fileops_impl_noop_sync, fs_node_impl_not_dir,
22    fs_node_impl_xattr_delegate,
23};
24use bstr::BStr;
25use ebpf::{MapFlags, MapSchema};
26use ebpf_api::{RINGBUF_SIGNAL, compute_map_storage_size};
27use starnix_logging::track_stub;
28use starnix_types::vfs::default_statfs;
29use starnix_uapi::auth::FsCred;
30use starnix_uapi::device_id::DeviceId;
31use starnix_uapi::errors::Errno;
32use starnix_uapi::file_mode::{FileMode, mode};
33use starnix_uapi::math::round_up_to_increment;
34use starnix_uapi::open_flags::OpenFlags;
35use starnix_uapi::user_address::UserAddress;
36use starnix_uapi::vfs::FdEvents;
37use starnix_uapi::{
38    BPF_FS_MAGIC, bpf_map_type_BPF_MAP_TYPE_ARRAY, bpf_map_type_BPF_MAP_TYPE_RINGBUF, errno, error,
39    statfs,
40};
41use std::sync::Arc;
42
43/// A reference to a BPF object that can be stored in either an FD or an entry in the /sys/fs/bpf
44/// filesystem.
45#[derive(Debug, Clone)]
46pub enum BpfHandle {
47    Program(ProgramHandle),
48
49    // Stub used to fake loading of programs of unknown types.
50    ProgramStub(u32),
51
52    Map(BpfMapHandle),
53    BpfTypeFormat(Arc<BpfTypeFormat>),
54}
55
56impl BpfHandle {
57    pub fn as_map(&self) -> Result<&BpfMapHandle, Errno> {
58        match self {
59            Self::Map(map) => Ok(map),
60            _ => error!(EINVAL),
61        }
62    }
63    pub fn as_program(&self) -> Result<&ProgramHandle, Errno> {
64        match self {
65            Self::Program(program) => Ok(program),
66            _ => error!(EINVAL),
67        }
68    }
69
70    pub fn into_program(self) -> Result<ProgramHandle, Errno> {
71        match self {
72            Self::Program(program) => Ok(program),
73            _ => error!(EINVAL),
74        }
75    }
76
77    // Returns VMO and schema if this handle references a map.
78    fn get_map_vmo(&self) -> Result<(&Arc<zx::Vmo>, MapSchema), Errno> {
79        match self {
80            Self::Map(map) => Ok((map.vmo(), map.schema)),
81            _ => error!(ENODEV),
82        }
83    }
84
85    pub fn type_name(&self) -> &'static str {
86        match self {
87            Self::Map(_) => "bpf-map",
88            Self::Program(_) | Self::ProgramStub(_) => "bpf-prog",
89            Self::BpfTypeFormat(_) => "bpf-type",
90        }
91    }
92
93    /// Performs security-related checks when opening a BPF map. If
94    /// `permission_flags` is `None`, then they are inferred from the map's
95    /// schema. `permission_flags` is ignored for programs.
96    pub(super) fn security_check_open_fd(
97        &self,
98        current_task: &CurrentTask,
99        permission_flags: Option<PermissionFlags>,
100    ) -> Result<(), Errno> {
101        match self {
102            Self::Map(bpf_map) => security::check_bpf_map_access(
103                current_task,
104                &bpf_map.security_state,
105                permission_flags.unwrap_or_else(|| bpf_map.schema.flags.into()),
106            ),
107            Self::Program(program) => {
108                security::check_bpf_prog_access(current_task, &program.security_state)
109            }
110            _ => Ok(()),
111        }
112    }
113}
114
115impl From<ProgramHandle> for BpfHandle {
116    fn from(program: ProgramHandle) -> Self {
117        Self::Program(program)
118    }
119}
120
121impl From<BpfMapHandle> for BpfHandle {
122    fn from(map: BpfMapHandle) -> Self {
123        Self::Map(map)
124    }
125}
126
127impl From<BpfTypeFormat> for BpfHandle {
128    fn from(format: BpfTypeFormat) -> Self {
129        Self::BpfTypeFormat(Arc::new(format))
130    }
131}
132
133impl FileOps for BpfHandle {
134    fileops_impl_nonseekable!();
135    fileops_impl_noop_sync!();
136    fn read(
137        &self,
138        _file: &FileObject,
139        _current_task: &crate::task::CurrentTask,
140        _offset: usize,
141        _data: &mut dyn OutputBuffer,
142    ) -> Result<usize, Errno> {
143        track_stub!(TODO("https://fxbug.dev/322874229"), "bpf handle read");
144        error!(EINVAL)
145    }
146    fn write(
147        &self,
148        _file: &FileObject,
149        _current_task: &crate::task::CurrentTask,
150        _offset: usize,
151        _data: &mut dyn InputBuffer,
152    ) -> Result<usize, Errno> {
153        track_stub!(TODO("https://fxbug.dev/322873841"), "bpf handle write");
154        error!(EINVAL)
155    }
156
157    fn get_memory(
158        &self,
159        _file: &FileObject,
160        _current_task: &CurrentTask,
161        length: Option<usize>,
162        prot: ProtectionFlags,
163    ) -> Result<Arc<MemoryObject>, Errno> {
164        let (vmo, schema) = self.get_map_vmo()?;
165
166        // Because of the specific condition needed to map this object, the size must be known.
167        let length = length.ok_or_else(|| errno!(EINVAL))?;
168
169        // This cannot be mapped executable.
170        if prot.contains(ProtectionFlags::EXEC) {
171            return error!(EPERM);
172        }
173
174        match schema.map_type {
175            bpf_map_type_BPF_MAP_TYPE_RINGBUF => {
176                let page_size = *PAGE_SIZE as usize;
177                // Starting from the second page, this cannot be mapped writable.
178                if length > page_size {
179                    if prot.contains(ProtectionFlags::WRITE) {
180                        return error!(EPERM);
181                    }
182                    // This cannot be mapped outside of the 2 control pages and the 2 data sections.
183                    if length > 2 * page_size + 2 * schema.max_entries as usize {
184                        return error!(EINVAL);
185                    }
186                }
187
188                self.as_map()?.get_memory(|| {
189                    // The first page of the ring buffer VMO is not visible to
190                    // user-space processes. Return a VMO slice that doesn't
191                    // include the first page.
192                    let clone_size = 2 * page_size + schema.max_entries as usize;
193                    let vmo_dup = vmo
194                        .create_child(
195                            zx::VmoChildOptions::SLICE,
196                            page_size as u64,
197                            clone_size as u64,
198                        )
199                        .map_err(|_| errno!(EIO))?
200                        .into();
201                    Ok(Arc::new(MemoryObject::RingBuf(vmo_dup)))
202                })
203            }
204
205            bpf_map_type_BPF_MAP_TYPE_ARRAY => {
206                if !schema.flags.contains(MapFlags::Mmapable) {
207                    return error!(EPERM);
208                }
209
210                let array_size = round_up_to_increment(
211                    compute_map_storage_size(&schema).map_err(|_| errno!(EINVAL))?,
212                    *PAGE_SIZE as usize,
213                )?;
214                if length > array_size {
215                    return error!(EINVAL);
216                }
217
218                self.as_map()?.get_memory(|| {
219                    let vmo_dup: zx::Vmo = vmo
220                        .as_handle_ref()
221                        .duplicate_handle(zx::Rights::SAME_RIGHTS)
222                        .map_err(|_| errno!(EIO))?
223                        .into();
224                    Ok(Arc::new(MemoryObject::from(vmo_dup)))
225                })
226            }
227
228            // Other maps cannot be mmap'ed.
229            _ => error!(ENODEV),
230        }
231    }
232
233    fn mmap(
234        &self,
235        file: &FileObject,
236        current_task: &CurrentTask,
237        addr: DesiredAddress,
238        memory_offset: u64,
239        length: usize,
240        prot_flags: ProtectionFlags,
241        options: MappingOptions,
242        filename: NamespaceNode,
243    ) -> Result<UserAddress, Errno> {
244        let BpfHandle::Map(bpf_map) = &self else {
245            return error!(EINVAL);
246        };
247        security::check_bpf_map_access(
248            current_task,
249            &bpf_map.security_state,
250            PermissionFlags::READ | PermissionFlags::WRITE,
251        )?;
252        default_mmap(file, current_task, addr, memory_offset, length, prot_flags, options, filename)
253    }
254
255    fn wait_async(
256        &self,
257        _file: &FileObject,
258        _current_task: &CurrentTask,
259        waiter: &Waiter,
260        events: FdEvents,
261        handler: EventHandler,
262    ) -> Option<WaitCanceler> {
263        let (vmo, schema) = self.get_map_vmo().ok()?;
264
265        // Only ringbuffers can be polled for POLLIN.
266        if schema.map_type != bpf_map_type_BPF_MAP_TYPE_RINGBUF
267            || !events.contains(FdEvents::POLLIN)
268        {
269            return Some(WaitCanceler::new_noop());
270        }
271
272        let handler = SignalHandler {
273            inner: SignalHandlerInner::ZxHandle(|signals| {
274                if signals.contains(RINGBUF_SIGNAL) { FdEvents::POLLIN } else { FdEvents::empty() }
275            }),
276            event_handler: handler,
277            err_code: None,
278        };
279
280        // Reset the signal before waiting. The case when the ring buffer already has some data
281        // is handled by the caller: it should call `query_events` after starting the waiter.
282        vmo.as_handle_ref()
283            .signal(RINGBUF_SIGNAL, zx::Signals::empty())
284            .expect("Failed to set signal or a ring buffer VMO");
285
286        let canceler = waiter
287            .wake_on_zircon_signals(&vmo.as_handle_ref(), RINGBUF_SIGNAL, handler)
288            .expect("Failed to wait for signals on ringbuf VMO");
289        Some(WaitCanceler::new_port(canceler))
290    }
291
292    fn query_events(
293        &self,
294        _file: &FileObject,
295        _current_task: &CurrentTask,
296    ) -> Result<FdEvents, Errno> {
297        match self {
298            Self::Map(map) => {
299                let events = match map.can_read() {
300                    Some(true) => FdEvents::POLLIN,
301                    Some(false) => FdEvents::empty(),
302                    None => FdEvents::POLLERR,
303                };
304                Ok(events)
305            }
306            _ => error!(EPERM),
307        }
308    }
309}
310
311pub fn get_bpf_object(task: &Task, fd: FdNumber) -> Result<BpfHandle, Errno> {
312    Ok((*task.files()?.get(fd)?.downcast_file::<BpfHandle>().ok_or_else(|| errno!(EBADF))?).clone())
313}
314pub struct BpfFs;
315impl BpfFs {
316    pub fn new_fs(
317        current_task: &CurrentTask,
318        options: FileSystemOptions,
319    ) -> Result<FileSystemHandle, Errno> {
320        let kernel = current_task.kernel();
321        let fs = FileSystem::new(kernel, CacheMode::Permanent, BpfFs, options)?;
322        let root_ino = fs.allocate_ino();
323        fs.create_root_with_info(
324            root_ino,
325            BpfFsDir::new(),
326            FsNodeInfo::new(mode!(IFDIR, 0o777) | FileMode::ISVTX, FsCred::root()),
327        );
328        Ok(fs)
329    }
330}
331
332impl FileSystemOps for BpfFs {
333    fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
334        Ok(default_statfs(BPF_FS_MAGIC))
335    }
336    fn name(&self) -> &'static FsStr {
337        "bpf".into()
338    }
339
340    fn rename(
341        &self,
342        _fs: &FileSystem,
343        _current_task: &CurrentTask,
344        _context: &mut RenameContext<'_>,
345        _old_name: &FsStr,
346        _new_name: &FsStr,
347    ) -> Result<(), Errno> {
348        Ok(())
349    }
350}
351
352pub struct BpfFsDir {
353    xattrs: MemoryXattrStorage,
354}
355
356impl BpfFsDir {
357    fn new() -> Self {
358        Self { xattrs: MemoryXattrStorage::default() }
359    }
360
361    pub fn register_pin(
362        &self,
363        current_task: &CurrentTask,
364        node: &NamespaceNode,
365        name: &FsStr,
366        object: BpfHandle,
367    ) -> Result<(), Errno> {
368        node.entry.create_entry(current_task, &node.mount, name, |dir, _mount, _name| {
369            Ok(dir.fs().create_node_and_allocate_node_id(
370                BpfFsObject::new(object),
371                FsNodeInfo::new(mode!(IFREG, 0o600), current_task.current_fscred()),
372            ))
373        })?;
374        Ok(())
375    }
376}
377
378impl FsNodeOps for BpfFsDir {
379    fs_node_impl_xattr_delegate!(self, self.xattrs);
380
381    fn create_file_ops(
382        &self,
383        _node: &FsNode,
384        _current_task: &CurrentTask,
385        _flags: OpenFlags,
386    ) -> Result<Box<dyn FileOps>, Errno> {
387        Ok(Box::new(MemoryDirectoryFile::new()))
388    }
389
390    fn mkdir(
391        &self,
392        node: &FsNode,
393        _current_task: &CurrentTask,
394        _name: &FsStr,
395        mode: FileMode,
396        owner: FsCred,
397    ) -> Result<FsNodeHandle, Errno> {
398        Ok(node.fs().create_node_and_allocate_node_id(
399            BpfFsDir::new(),
400            FsNodeInfo::new(mode | FileMode::ISVTX, owner),
401        ))
402    }
403
404    fn mknod(
405        &self,
406        _node: &FsNode,
407        _current_task: &CurrentTask,
408        _name: &FsStr,
409        _mode: FileMode,
410        _dev: DeviceId,
411        _owner: FsCred,
412    ) -> Result<FsNodeHandle, Errno> {
413        error!(EPERM)
414    }
415
416    fn create_symlink(
417        &self,
418        _node: &FsNode,
419        _current_task: &CurrentTask,
420        _name: &FsStr,
421        _target: &FsStr,
422        _owner: FsCred,
423    ) -> Result<FsNodeHandle, Errno> {
424        error!(EPERM)
425    }
426
427    fn link(
428        &self,
429        _node: &FsNode,
430        _current_task: &CurrentTask,
431        _name: &FsStr,
432        _child: &FsNodeHandle,
433    ) -> Result<(), Errno> {
434        Ok(())
435    }
436
437    fn unlink(
438        &self,
439        _node: &FsNode,
440        _current_task: &CurrentTask,
441        _name: &FsStr,
442        _child: &FsNodeHandle,
443    ) -> Result<(), Errno> {
444        Ok(())
445    }
446}
447
448pub struct BpfFsObject {
449    pub handle: BpfHandle,
450    xattrs: MemoryXattrStorage,
451}
452
453impl BpfFsObject {
454    fn new(handle: BpfHandle) -> Self {
455        Self { handle, xattrs: MemoryXattrStorage::default() }
456    }
457}
458
459impl FsNodeOps for BpfFsObject {
460    fs_node_impl_not_dir!();
461    fs_node_impl_xattr_delegate!(self, self.xattrs);
462
463    fn create_file_ops(
464        &self,
465        _node: &FsNode,
466        _current_task: &CurrentTask,
467        _flags: OpenFlags,
468    ) -> Result<Box<dyn FileOps>, Errno> {
469        error!(EIO)
470    }
471}
472
473/// Resolves a pinned BPF object from a path, returning the underlying handle.
474/// Performs DAC and MAC checks using the specified `open_flags `. Also updates
475/// atime unless `NOATIME` flag is set.
476pub fn resolve_pinned_bpf_object(
477    current_task: &CurrentTask,
478    path: &BStr,
479    open_flags: OpenFlags,
480) -> Result<BpfHandle, Errno> {
481    let node = current_task.lookup_path_from_root(path.as_ref())?;
482
483    let permission_flags = PermissionFlags::from(open_flags);
484    node.check_access(current_task, permission_flags, CheckAccessReason::Access)?;
485
486    let object = node.entry.node.downcast_ops::<BpfFsObject>().ok_or_else(|| errno!(EPERM))?;
487    object.handle.security_check_open_fd(current_task, Some(permission_flags))?;
488
489    if !open_flags.contains(OpenFlags::NOATIME) {
490        node.update_atime();
491    }
492
493    Ok(object.handle.clone())
494}