Skip to main content

starnix_core/bpf/
map.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::mm::memory::MemoryObject;
9use crate::security;
10use crate::task::{CurrentTask, Kernel, register_delayed_release};
11use ebpf::MapSchema;
12use ebpf_api::{Map, MapError, PinnedMap};
13use starnix_lifecycle::{ObjectReleaser, ReleaserAction};
14use starnix_sync::{EbpfMapStateLevel, LockDepGuard, LockDepMutex};
15use starnix_types::ownership::{Releasable, ReleaseGuard};
16use starnix_uapi::auth::{CAP_BPF, CAP_NET_ADMIN, CAP_PERFMON, CAP_SYS_ADMIN};
17use starnix_uapi::errors::Errno;
18use starnix_uapi::{
19    bpf_map_type_BPF_MAP_TYPE_ARRAY_OF_MAPS, bpf_map_type_BPF_MAP_TYPE_BLOOM_FILTER,
20    bpf_map_type_BPF_MAP_TYPE_CGROUP_STORAGE, bpf_map_type_BPF_MAP_TYPE_CGRP_STORAGE,
21    bpf_map_type_BPF_MAP_TYPE_CPUMAP, bpf_map_type_BPF_MAP_TYPE_DEVMAP,
22    bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH, bpf_map_type_BPF_MAP_TYPE_HASH_OF_MAPS,
23    bpf_map_type_BPF_MAP_TYPE_INODE_STORAGE, bpf_map_type_BPF_MAP_TYPE_LPM_TRIE,
24    bpf_map_type_BPF_MAP_TYPE_LRU_HASH, bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH,
25    bpf_map_type_BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE, bpf_map_type_BPF_MAP_TYPE_QUEUE,
26    bpf_map_type_BPF_MAP_TYPE_SK_STORAGE, bpf_map_type_BPF_MAP_TYPE_SOCKHASH,
27    bpf_map_type_BPF_MAP_TYPE_SOCKMAP, bpf_map_type_BPF_MAP_TYPE_STACK,
28    bpf_map_type_BPF_MAP_TYPE_STACK_TRACE, bpf_map_type_BPF_MAP_TYPE_STRUCT_OPS,
29    bpf_map_type_BPF_MAP_TYPE_TASK_STORAGE, bpf_map_type_BPF_MAP_TYPE_XSKMAP, errno, error,
30};
31use std::ops::Deref;
32use std::sync::atomic::{AtomicU32, Ordering};
33use std::sync::{Arc, Weak};
34
35pub type BpfMapId = u32;
36
37/// Counter for map identifiers.
38static MAP_IDS: AtomicU32 = AtomicU32::new(1);
39fn new_map_id() -> BpfMapId {
40    MAP_IDS.fetch_add(1, Ordering::Relaxed)
41}
42
43pub(crate) fn map_error_to_errno(e: MapError) -> Errno {
44    match e {
45        MapError::InvalidParam => errno!(EINVAL),
46        MapError::InvalidKey => errno!(ENOENT),
47        MapError::EntryExists => errno!(EEXIST),
48        MapError::NoMemory => errno!(ENOMEM),
49        MapError::SizeLimit => errno!(E2BIG),
50        MapError::MapTypeNotSupported | MapError::NotSupported => errno!(ENOSYS),
51        MapError::InvalidVmo | MapError::Internal => errno!(EIO),
52    }
53}
54
55fn check_map_create_access(current_task: &CurrentTask, schema: &MapSchema) -> Result<(), Errno> {
56    if security::is_task_capable_noaudit(current_task, CAP_SYS_ADMIN) {
57        return Ok(());
58    }
59    let cap_bpf_always_required = matches!(
60        schema.map_type,
61        bpf_map_type_BPF_MAP_TYPE_LPM_TRIE
62            | bpf_map_type_BPF_MAP_TYPE_LRU_HASH
63            | bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH
64            | bpf_map_type_BPF_MAP_TYPE_QUEUE
65            | bpf_map_type_BPF_MAP_TYPE_STACK
66            | bpf_map_type_BPF_MAP_TYPE_ARRAY_OF_MAPS
67            | bpf_map_type_BPF_MAP_TYPE_HASH_OF_MAPS
68            | bpf_map_type_BPF_MAP_TYPE_BLOOM_FILTER
69            | bpf_map_type_BPF_MAP_TYPE_SK_STORAGE
70            | bpf_map_type_BPF_MAP_TYPE_INODE_STORAGE
71            | bpf_map_type_BPF_MAP_TYPE_TASK_STORAGE
72            | bpf_map_type_BPF_MAP_TYPE_CGROUP_STORAGE
73            | bpf_map_type_BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE
74            | bpf_map_type_BPF_MAP_TYPE_CGRP_STORAGE
75    );
76
77    if cap_bpf_always_required || !current_task.kernel().allow_unprivileged_bpf() {
78        security::check_task_capable(current_task, CAP_BPF)?;
79    }
80
81    match schema.map_type {
82        bpf_map_type_BPF_MAP_TYPE_DEVMAP
83        | bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH
84        | bpf_map_type_BPF_MAP_TYPE_CPUMAP
85        | bpf_map_type_BPF_MAP_TYPE_SOCKMAP
86        | bpf_map_type_BPF_MAP_TYPE_SOCKHASH
87        | bpf_map_type_BPF_MAP_TYPE_XSKMAP => {
88            security::check_task_capable(current_task, CAP_NET_ADMIN)?;
89        }
90        bpf_map_type_BPF_MAP_TYPE_STACK_TRACE => {
91            security::check_task_capable(current_task, CAP_PERFMON)?;
92        }
93        bpf_map_type_BPF_MAP_TYPE_STRUCT_OPS => {
94            return error!(EPERM);
95        }
96        _ => {}
97    }
98    Ok(())
99}
100
101#[derive(Debug, Default)]
102struct BpfMapState {
103    memory_object: Option<Arc<MemoryObject>>,
104    is_frozen: bool,
105}
106
107/// A BPF map and Starnix-specific metadata.
108#[derive(Debug)]
109pub struct BpfMap {
110    id: BpfMapId,
111    map: PinnedMap,
112
113    /// The internal state of the map object.
114    state: LockDepMutex<BpfMapState, EbpfMapStateLevel>,
115
116    /// The security state associated with this bpf Map.
117    pub security_state: security::BpfMapState,
118
119    /// Reference to the `Kernel`. Used to unregister `self` on drop.
120    kernel: Weak<Kernel>,
121}
122
123impl Deref for BpfMap {
124    type Target = PinnedMap;
125    fn deref(&self) -> &PinnedMap {
126        &self.map
127    }
128}
129
130impl BpfMap {
131    pub fn new(
132        current_task: &CurrentTask,
133        schema: MapSchema,
134        name: &str,
135        security_state: security::BpfMapState,
136    ) -> Result<BpfMapHandle, Errno> {
137        check_map_create_access(current_task, &schema)?;
138
139        let map = Map::new(schema, name).map_err(map_error_to_errno)?;
140        let map = BpfMapHandle::new(
141            Self {
142                id: new_map_id(),
143                map,
144                state: Default::default(),
145                security_state,
146                kernel: Arc::downgrade(current_task.kernel()),
147            }
148            .into(),
149        );
150        current_task.kernel().ebpf_state.register_map(&map);
151        Ok(map)
152    }
153
154    pub fn id(&self) -> BpfMapId {
155        self.id
156    }
157
158    pub(crate) fn frozen<'a>(&'a self) -> impl Deref<Target = bool> + 'a {
159        let guard = self.state.lock();
160        LockDepGuard::map(guard, |s| &mut s.is_frozen)
161    }
162
163    pub(crate) fn freeze(&self) -> Result<(), Errno> {
164        let mut state = self.state.lock();
165        if state.is_frozen {
166            return Ok(());
167        }
168        if let Some(memory) = state.memory_object.take() {
169            // The memory has been computed, check whether it is still in use.
170            if let Err(memory) = Arc::try_unwrap(memory) {
171                // There is other user of the memory. freeze must fail.
172                state.memory_object = Some(memory);
173                return error!(EBUSY);
174            }
175        }
176        state.is_frozen = true;
177        return Ok(());
178    }
179
180    pub(crate) fn get_inner(&self) -> PinnedMap {
181        self.map.clone()
182    }
183
184    pub(crate) fn get_memory<F>(&self, factory: F) -> Result<Arc<MemoryObject>, Errno>
185    where
186        F: FnOnce() -> Result<Arc<MemoryObject>, Errno>,
187    {
188        let mut state = self.state.lock();
189        if state.is_frozen {
190            return error!(EPERM);
191        }
192        if let Some(memory) = state.memory_object.as_ref() {
193            return Ok(memory.clone());
194        }
195        let memory = factory()?;
196        state.memory_object = Some(memory.clone());
197        Ok(memory)
198    }
199}
200
201impl Releasable for BpfMap {
202    type Context<'a> = &'a CurrentTask;
203
204    fn release<'a>(self, _current_task: &'a CurrentTask) {
205        if let Some(kernel) = self.kernel.upgrade() {
206            kernel.ebpf_state.unregister_map(self.id);
207        }
208    }
209}
210
211pub enum BpfMapReleaserAction {}
212impl ReleaserAction<BpfMap> for BpfMapReleaserAction {
213    fn release(map: ReleaseGuard<BpfMap>) {
214        register_delayed_release(map);
215    }
216}
217pub type BpfMapReleaser = ObjectReleaser<BpfMap, BpfMapReleaserAction>;
218pub type BpfMapHandle = Arc<BpfMapReleaser>;
219pub type WeakBpfMapHandle = Weak<BpfMapReleaser>;