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