blob: 52754a0cf672bb9b1a57f4787228059baa5ae13b [file]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(https://github.com/rust-lang/rust/issues/39371): remove
#![allow(non_upper_case_globals)]
use crate::mm::memory::MemoryObject;
use crate::security;
use crate::task::{CurrentTask, CurrentTaskAndLocked, Kernel, register_delayed_release};
use ebpf::MapSchema;
use ebpf_api::{Map, MapError, PinnedMap};
use starnix_lifecycle::{ObjectReleaser, ReleaserAction};
use starnix_sync::{
EbpfMapStateLevel, EbpfStateLock, LockBefore, Locked, MutexGuard, OrderedMutex,
};
use starnix_types::ownership::{Releasable, ReleaseGuard};
use starnix_uapi::auth::{CAP_BPF, CAP_NET_ADMIN, CAP_PERFMON, CAP_SYS_ADMIN};
use starnix_uapi::errors::Errno;
use starnix_uapi::{
bpf_map_type_BPF_MAP_TYPE_ARRAY_OF_MAPS, bpf_map_type_BPF_MAP_TYPE_BLOOM_FILTER,
bpf_map_type_BPF_MAP_TYPE_CGROUP_STORAGE, bpf_map_type_BPF_MAP_TYPE_CGRP_STORAGE,
bpf_map_type_BPF_MAP_TYPE_CPUMAP, bpf_map_type_BPF_MAP_TYPE_DEVMAP,
bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH, bpf_map_type_BPF_MAP_TYPE_HASH_OF_MAPS,
bpf_map_type_BPF_MAP_TYPE_INODE_STORAGE, bpf_map_type_BPF_MAP_TYPE_LPM_TRIE,
bpf_map_type_BPF_MAP_TYPE_LRU_HASH, bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH,
bpf_map_type_BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE, bpf_map_type_BPF_MAP_TYPE_QUEUE,
bpf_map_type_BPF_MAP_TYPE_SK_STORAGE, bpf_map_type_BPF_MAP_TYPE_SOCKHASH,
bpf_map_type_BPF_MAP_TYPE_SOCKMAP, bpf_map_type_BPF_MAP_TYPE_STACK,
bpf_map_type_BPF_MAP_TYPE_STACK_TRACE, bpf_map_type_BPF_MAP_TYPE_STRUCT_OPS,
bpf_map_type_BPF_MAP_TYPE_TASK_STORAGE, bpf_map_type_BPF_MAP_TYPE_XSKMAP, errno, error,
};
use std::ops::Deref;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, Weak};
pub type BpfMapId = u32;
/// Counter for map identifiers.
static MAP_IDS: AtomicU32 = AtomicU32::new(1);
fn new_map_id() -> BpfMapId {
MAP_IDS.fetch_add(1, Ordering::Relaxed)
}
pub(crate) fn map_error_to_errno(e: MapError) -> Errno {
match e {
MapError::InvalidParam => errno!(EINVAL),
MapError::InvalidKey => errno!(ENOENT),
MapError::EntryExists => errno!(EEXIST),
MapError::NoMemory => errno!(ENOMEM),
MapError::SizeLimit => errno!(E2BIG),
MapError::MapTypeNotSupported | MapError::NotSupported => errno!(ENOSYS),
MapError::InvalidVmo | MapError::Internal => errno!(EIO),
}
}
fn check_map_create_access(current_task: &CurrentTask, schema: &MapSchema) -> Result<(), Errno> {
if security::is_task_capable_noaudit(current_task, CAP_SYS_ADMIN) {
return Ok(());
}
let cap_bpf_always_required = matches!(
schema.map_type,
bpf_map_type_BPF_MAP_TYPE_LPM_TRIE
| bpf_map_type_BPF_MAP_TYPE_LRU_HASH
| bpf_map_type_BPF_MAP_TYPE_LRU_PERCPU_HASH
| bpf_map_type_BPF_MAP_TYPE_QUEUE
| bpf_map_type_BPF_MAP_TYPE_STACK
| bpf_map_type_BPF_MAP_TYPE_ARRAY_OF_MAPS
| bpf_map_type_BPF_MAP_TYPE_HASH_OF_MAPS
| bpf_map_type_BPF_MAP_TYPE_BLOOM_FILTER
| bpf_map_type_BPF_MAP_TYPE_SK_STORAGE
| bpf_map_type_BPF_MAP_TYPE_INODE_STORAGE
| bpf_map_type_BPF_MAP_TYPE_TASK_STORAGE
| bpf_map_type_BPF_MAP_TYPE_CGROUP_STORAGE
| bpf_map_type_BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE
| bpf_map_type_BPF_MAP_TYPE_CGRP_STORAGE
);
if cap_bpf_always_required || !current_task.kernel().allow_unprivileged_bpf() {
security::check_task_capable(current_task, CAP_BPF)?;
}
match schema.map_type {
bpf_map_type_BPF_MAP_TYPE_DEVMAP
| bpf_map_type_BPF_MAP_TYPE_DEVMAP_HASH
| bpf_map_type_BPF_MAP_TYPE_CPUMAP
| bpf_map_type_BPF_MAP_TYPE_SOCKMAP
| bpf_map_type_BPF_MAP_TYPE_SOCKHASH
| bpf_map_type_BPF_MAP_TYPE_XSKMAP => {
security::check_task_capable(current_task, CAP_NET_ADMIN)?;
}
bpf_map_type_BPF_MAP_TYPE_STACK_TRACE => {
security::check_task_capable(current_task, CAP_PERFMON)?;
}
bpf_map_type_BPF_MAP_TYPE_STRUCT_OPS => {
return error!(EPERM);
}
_ => {}
}
Ok(())
}
#[derive(Debug, Default)]
struct BpfMapState {
memory_object: Option<Arc<MemoryObject>>,
is_frozen: bool,
}
/// A BPF map and Starnix-specific metadata.
#[derive(Debug)]
pub struct BpfMap {
id: BpfMapId,
map: PinnedMap,
/// The internal state of the map object.
state: OrderedMutex<BpfMapState, EbpfMapStateLevel>,
/// The security state associated with this bpf Map.
pub security_state: security::BpfMapState,
/// Reference to the `Kernel`. Used to unregister `self` on drop.
kernel: Weak<Kernel>,
}
impl Deref for BpfMap {
type Target = PinnedMap;
fn deref(&self) -> &PinnedMap {
&self.map
}
}
impl BpfMap {
pub fn new<L>(
locked: &mut Locked<L>,
current_task: &CurrentTask,
schema: MapSchema,
name: &str,
security_state: security::BpfMapState,
) -> Result<BpfMapHandle, Errno>
where
L: LockBefore<EbpfStateLock>,
{
check_map_create_access(current_task, &schema)?;
let map = Map::new(schema, name).map_err(map_error_to_errno)?;
let map = BpfMapHandle::new(
Self {
id: new_map_id(),
map,
state: Default::default(),
security_state,
kernel: Arc::downgrade(current_task.kernel()),
}
.into(),
);
current_task.kernel().ebpf_state.register_map(locked, &map);
Ok(map)
}
pub fn id(&self) -> BpfMapId {
self.id
}
pub(crate) fn frozen<'a, L>(
&'a self,
locked: &'a mut Locked<L>,
) -> (impl Deref<Target = bool> + 'a, &'a mut Locked<EbpfMapStateLevel>)
where
L: LockBefore<EbpfMapStateLevel>,
{
let (guard, locked) = self.state.lock_and(locked);
(MutexGuard::map(guard, |s| &mut s.is_frozen), locked)
}
pub(crate) fn freeze<L>(&self, locked: &mut Locked<L>) -> Result<(), Errno>
where
L: LockBefore<EbpfMapStateLevel>,
{
let mut state = self.state.lock(locked);
if state.is_frozen {
return Ok(());
}
if let Some(memory) = state.memory_object.take() {
// The memory has been computed, check whether it is still in use.
if let Err(memory) = Arc::try_unwrap(memory) {
// There is other user of the memory. freeze must fail.
state.memory_object = Some(memory);
return error!(EBUSY);
}
}
state.is_frozen = true;
return Ok(());
}
pub(crate) fn get_inner(&self) -> PinnedMap {
self.map.clone()
}
pub(crate) fn get_memory<L, F>(
&self,
locked: &mut Locked<L>,
factory: F,
) -> Result<Arc<MemoryObject>, Errno>
where
L: LockBefore<EbpfMapStateLevel>,
F: FnOnce() -> Result<Arc<MemoryObject>, Errno>,
{
let mut state = self.state.lock(locked);
if state.is_frozen {
return error!(EPERM);
}
if let Some(memory) = state.memory_object.as_ref() {
return Ok(memory.clone());
}
let memory = factory()?;
state.memory_object = Some(memory.clone());
Ok(memory)
}
}
impl Releasable for BpfMap {
type Context<'a> = CurrentTaskAndLocked<'a>;
fn release<'a>(self, (locked, _current_task): CurrentTaskAndLocked<'a>) {
if let Some(kernel) = self.kernel.upgrade() {
kernel.ebpf_state.unregister_map(locked, self.id);
}
}
}
pub enum BpfMapReleaserAction {}
impl ReleaserAction<BpfMap> for BpfMapReleaserAction {
fn release(map: ReleaseGuard<BpfMap>) {
register_delayed_release(map);
}
}
pub type BpfMapReleaser = ObjectReleaser<BpfMap, BpfMapReleaserAction>;
pub type BpfMapHandle = Arc<BpfMapReleaser>;
pub type WeakBpfMapHandle = Weak<BpfMapReleaser>;