blob: d96dda6999a1584844ab15b898fa31ca08cbbb7d [file] [log] [blame]
// 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.
use crate::bpf::BpfMapHandle;
use crate::bpf::fs::get_bpf_object;
use crate::security;
use crate::task::{CurrentTask, CurrentTaskAndLocked, Kernel, register_delayed_release};
use crate::vfs::{FdNumber, OutputBuffer};
use ebpf::{
BPF_LDDW, BPF_PSEUDO_BTF_ID, BPF_PSEUDO_FUNC, BPF_PSEUDO_MAP_FD, BPF_PSEUDO_MAP_IDX,
BPF_PSEUDO_MAP_IDX_VALUE, BPF_PSEUDO_MAP_VALUE, EbpfError, EbpfInstruction, EbpfProgram,
EbpfProgramContext, StaticHelperSet, VerifiedEbpfProgram, VerifierLogger, link_program,
verify_program,
};
use ebpf_api::{AttachType, EbpfApiError, MapsContext, PinnedMap, ProgramType};
use fidl_fuchsia_ebpf as febpf;
use starnix_lifecycle::{AtomicU32Counter, ObjectReleaser, ReleaserAction};
use starnix_logging::{log_error, log_warn, track_stub};
use starnix_sync::{EbpfStateLock, LockBefore, Locked};
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_attr__bindgen_ty_4, errno, error};
use std::sync::atomic::Ordering;
use std::sync::{Arc, Weak};
use zx::{AsHandleRef as _, HandleBased};
#[derive(Clone, Debug)]
pub struct ProgramInfo {
pub program_type: ProgramType,
pub expected_attach_type: AttachType,
}
impl TryFrom<&bpf_attr__bindgen_ty_4> for ProgramInfo {
type Error = Errno;
fn try_from(info: &bpf_attr__bindgen_ty_4) -> Result<Self, Self::Error> {
Ok(Self {
program_type: info.prog_type.try_into().map_err(map_ebpf_api_error)?,
expected_attach_type: info.expected_attach_type.into(),
})
}
}
pub type ProgramId = u32;
static NEXT_PROGRAM_ID: AtomicU32Counter = AtomicU32Counter::new(1);
fn new_program_id() -> ProgramId {
NEXT_PROGRAM_ID.next()
}
#[derive(Debug)]
pub struct Program {
/// Program info specified during program initialization.
pub info: ProgramInfo,
/// Verified program.
program: VerifiedEbpfProgram,
/// eBPF maps used by the program. These should match the `maps` field in
/// the `program`.
maps: Vec<BpfMapHandle>,
/// Integer program ID. This is dinstinct from `fidl_id` because `fidl_id`
/// is 64-bit, while Linux uses 32-bit IDs.
id: ProgramId,
/// Handle used when the program is transferred over FIDL to other services.
fidl_handle: febpf::ProgramHandle,
/// ID of the program used in FIDL
fidl_id: febpf::ProgramId,
/// The service end of the `fidl_handle`. Should be moved to the BPF Service
/// once it's implemented.
#[allow(dead_code)]
service_handle: zx::EventPair,
/// Weak reference to the Kernel where this program is registered.
kernel: Weak<Kernel>,
/// The security state associated with this bpf Program.
pub security_state: security::BpfProgState,
}
fn map_ebpf_error(e: EbpfError) -> Errno {
log_error!("Failed to load eBPF program: {e:?}");
errno!(EINVAL)
}
fn map_ebpf_api_error(e: EbpfApiError) -> Errno {
log_error!("Failed to load eBPF program: {e:?}");
match e {
EbpfApiError::InvalidProgramType(_) | EbpfApiError::InvalidExpectedAttachType(_) => {
errno!(EINVAL)
}
EbpfApiError::UnsupportedProgramType(_) => errno!(ENOTSUP),
}
}
impl Program {
pub fn new<L>(
locked: &mut Locked<L>,
current_task: &CurrentTask,
info: ProgramInfo,
logger: &mut dyn OutputBuffer,
mut code: Vec<EbpfInstruction>,
) -> Result<ProgramHandle, Errno>
where
L: LockBefore<EbpfStateLock>,
{
Self::check_load_access(current_task, &info)?;
let maps = link_maps_fds(current_task, &mut code)?;
let maps_schema = maps.iter().map(|m| m.schema).collect();
let mut logger = BufferVeriferLogger::new(logger);
let calling_context = info
.program_type
.create_calling_context(info.expected_attach_type, maps_schema)
.map_err(map_ebpf_api_error)?;
let program = verify_program(code, calling_context, &mut logger).map_err(map_ebpf_error)?;
let (fidl_handle, service_handle) = zx::EventPair::create();
let fidl_id =
febpf::ProgramId { id: fidl_handle.get_koid().expect("Failed to get koid").raw_koid() };
let fidl_handle = febpf::ProgramHandle { handle: fidl_handle };
let program = ProgramHandle::new(
Self {
info,
program,
maps,
id: new_program_id(),
fidl_handle,
fidl_id,
service_handle,
kernel: Arc::downgrade(current_task.kernel()),
security_state: security::bpf_prog_alloc(current_task),
}
.into(),
);
current_task.kernel().ebpf_state.register_program(locked, &program);
Ok(program)
}
pub fn id(&self) -> ProgramId {
self.id
}
pub fn link<C: EbpfProgramContext<Map = PinnedMap> + StaticHelperSet>(
&self,
program_type: ProgramType,
) -> Result<EbpfProgram<C>, Errno>
where
for<'a> C::RunContext<'a>: MapsContext<'a>,
{
if program_type != self.info.program_type {
return error!(EINVAL);
}
let maps = self.maps.iter().map(|map| map.map.clone()).collect();
let program = link_program(&self.program, maps).map_err(map_ebpf_error)?;
Ok(program)
}
fn check_load_access(current_task: &CurrentTask, info: &ProgramInfo) -> Result<(), Errno> {
if matches!(info.program_type, ProgramType::CgroupSkb | ProgramType::SocketFilter)
&& current_task.kernel().disable_unprivileged_bpf.load(Ordering::Relaxed) == 0
{
return Ok(());
}
if security::is_task_capable_noaudit(current_task, CAP_SYS_ADMIN) {
return Ok(());
}
security::check_task_capable(current_task, CAP_BPF)?;
match info.program_type {
// Loading tracing program types additionally require the CAP_PERFMON capability.
ProgramType::Kprobe
| ProgramType::Tracepoint
| ProgramType::PerfEvent
| ProgramType::RawTracepoint
| ProgramType::RawTracepointWritable
| ProgramType::Tracing => security::check_task_capable(current_task, CAP_PERFMON),
// Loading networking program types additionally require the CAP_NET_ADMIN capability.
ProgramType::SocketFilter
| ProgramType::SchedCls
| ProgramType::SchedAct
| ProgramType::Xdp
| ProgramType::SockOps
| ProgramType::SkSkb
| ProgramType::SkMsg
| ProgramType::SkLookup
| ProgramType::SkReuseport
| ProgramType::FlowDissector
| ProgramType::Netfilter => security::check_task_capable(current_task, CAP_NET_ADMIN),
// No additional checks are necessary for other program types.
ProgramType::CgroupDevice
| ProgramType::CgroupSkb
| ProgramType::CgroupSock
| ProgramType::CgroupSockAddr
| ProgramType::CgroupSockopt
| ProgramType::CgroupSysctl
| ProgramType::Ext
| ProgramType::LircMode2
| ProgramType::Lsm
| ProgramType::LwtIn
| ProgramType::LwtOut
| ProgramType::LwtSeg6Local
| ProgramType::LwtXmit
| ProgramType::StructOps
| ProgramType::Syscall
| ProgramType::Unspec
| ProgramType::Fuse => Ok(()),
}
}
pub fn fidl_id(&self) -> febpf::ProgramId {
self.fidl_id
}
pub fn fidl_handle(&self) -> febpf::ProgramHandle {
let handle = self
.fidl_handle
.handle
.duplicate_handle(zx::Rights::TRANSFER | zx::Rights::SIGNAL | zx::Rights::WAIT)
.expect("Failed to duplicate handle");
febpf::ProgramHandle { handle }
}
}
impl Releasable for Program {
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_program(locked, self.id);
}
// Signal the FIDL handle to indicate that the program handle is defunct
// and should be closed.
self.fidl_handle
.handle
.signal_handle(
zx::Signals::NONE,
zx::Signals::from_bits_truncate(febpf::PROGRAM_DEFUNCT_SIGNAL),
)
.unwrap();
}
}
pub enum ProgramReleaserAction {}
impl ReleaserAction<Program> for ProgramReleaserAction {
fn release(program: ReleaseGuard<Program>) {
register_delayed_release(program);
}
}
pub type ProgramReleaser = ObjectReleaser<Program, ProgramReleaserAction>;
pub type ProgramHandle = Arc<ProgramReleaser>;
pub type WeakProgramHandle = Weak<ProgramReleaser>;
impl TryFrom<&Program> for febpf::VerifiedProgram {
type Error = Errno;
fn try_from(program: &Program) -> Result<febpf::VerifiedProgram, Errno> {
let mut maps = Vec::with_capacity(program.maps.len());
for map in program.maps.iter() {
maps.push(map.share().map_err(|_| errno!(EIO))?);
}
// SAFETY: EbpfInstruction is 64-bit, so it's safe to transmute it to u64.
let code = program.program.code();
#[allow(
clippy::undocumented_unsafe_blocks,
reason = "Force documented unsafe blocks in Starnix"
)]
let code_u64 =
unsafe { std::slice::from_raw_parts(code.as_ptr() as *const u64, code.len()) };
let struct_access_instructions = program
.program
.struct_access_instructions()
.iter()
.map(|v| febpf::StructAccess {
pc: v.pc.try_into().unwrap(),
struct_memory_id: v.memory_id.id(),
field_offset: v.field_offset.try_into().unwrap(),
is_32_bit_ptr_load: v.is_32_bit_ptr_load,
})
.collect();
Ok(febpf::VerifiedProgram {
code: Some(code_u64.to_vec()),
struct_access_instructions: Some(struct_access_instructions),
maps: Some(maps),
..Default::default()
})
}
}
/// Links maps referenced by FD, replacing them with by-index references.
fn link_maps_fds(
current_task: &CurrentTask,
code: &mut Vec<EbpfInstruction>,
) -> Result<Vec<BpfMapHandle>, Errno> {
let code_len = code.len();
let mut maps = Vec::<BpfMapHandle>::new();
for (pc, instruction) in code.iter_mut().enumerate() {
if instruction.code() == BPF_LDDW {
// BPF_LDDW requires 2 instructions.
if pc >= code_len - 1 {
return error!(EINVAL);
}
match instruction.src_reg() {
0 => {}
BPF_PSEUDO_MAP_FD | BPF_PSEUDO_MAP_VALUE => {
let lddw_type = if instruction.src_reg() == BPF_PSEUDO_MAP_FD {
BPF_PSEUDO_MAP_IDX
} else {
BPF_PSEUDO_MAP_IDX_VALUE
};
// If the instruction references a map fd, then we need to look up the map fd
// and create a reference from this program to that object.
instruction.set_src_reg(lddw_type);
let fd = FdNumber::from_raw(instruction.imm());
let object = get_bpf_object(current_task, fd)?;
let map: &BpfMapHandle = object.as_map()?;
// Find the map in `maps` or insert it otherwise.
let maybe_index = maps.iter().position(|v| Arc::ptr_eq(v, map));
let index = match maybe_index {
Some(index) => index,
None => {
let index = maps.len();
maps.push(map.clone());
index
}
};
instruction.set_imm(index.try_into().unwrap());
}
BPF_PSEUDO_MAP_IDX
| BPF_PSEUDO_MAP_IDX_VALUE
| BPF_PSEUDO_BTF_ID
| BPF_PSEUDO_FUNC => {
track_stub!(
TODO("https://fxbug.dev/378564467"),
"unsupported pseudo src for ldimm64",
instruction.src_reg()
);
return error!(ENOTSUP);
}
_ => {
return error!(EINVAL);
}
}
}
}
Ok(maps)
}
struct BufferVeriferLogger<'a> {
buffer: &'a mut dyn OutputBuffer,
full: bool,
}
impl BufferVeriferLogger<'_> {
fn new<'a>(buffer: &'a mut dyn OutputBuffer) -> BufferVeriferLogger<'a> {
BufferVeriferLogger { buffer, full: false }
}
}
impl VerifierLogger for BufferVeriferLogger<'_> {
fn log(&mut self, line: &[u8]) {
debug_assert!(line.is_ascii());
if self.full {
return;
}
if line.len() + 1 > self.buffer.available() {
self.full = true;
return;
}
match self.buffer.write(line) {
Err(e) => {
log_warn!("Unable to write verifier log: {e:?}");
self.full = true;
}
_ => {}
}
match self.buffer.write(b"\n") {
Err(e) => {
log_warn!("Unable to write verifier log: {e:?}");
self.full = true;
}
_ => {}
}
}
}