Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement InodeStorage #916

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ebpf/aya-ebpf/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ unsafe impl<T> FromBtfArgument for *const T {
}
}

unsafe impl<T> FromBtfArgument for *mut T {
unsafe fn from_argument(ctx: *const c_void, n: usize) -> *mut T {
// BTF arguments are exposed as an array of `usize` where `usize` can
// either be treated as a pointer or a primitive type
*(ctx as *const usize).add(n) as _
}
}

/// Helper macro to implement [`FromBtfArgument`] for a primitive type.
macro_rules! unsafe_impl_from_btf_argument {
($type:ident) => {
Expand Down
151 changes: 151 additions & 0 deletions ebpf/aya-ebpf/src/maps/inode_storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use core::{cell::UnsafeCell, marker::PhantomData, mem, ptr::NonNull};

use aya_ebpf_bindings::{
bindings::{
bpf_map_def, bpf_map_type::BPF_MAP_TYPE_INODE_STORAGE, BPF_LOCAL_STORAGE_GET_F_CREATE,
},
helpers::{bpf_inode_storage_delete, bpf_inode_storage_get},
};
use aya_ebpf_cty::{c_int, c_void};

use crate::maps::PinningType;

/// A BPF map of type BPF_MAP_TYPE_INODE_STORAGE, used for attaching local storage data to an inode.
/// See bpf_inode_storage_get in bpf-helpers(7) for details.
#[repr(transparent)]
pub struct InodeStorage<V> {
def: UnsafeCell<bpf_map_def>,
_v: PhantomData<V>,
}

unsafe impl<T: Sync> Sync for InodeStorage<T> {}

impl<V> InodeStorage<V> {
/// Instantiate a [`InodeStorage`] map with the provided flags.
pub const fn new(flags: u32) -> InodeStorage<V> {
InodeStorage {
def: UnsafeCell::new(build_def::<V>(
BPF_MAP_TYPE_INODE_STORAGE,
flags,
PinningType::None,
)),
_v: PhantomData,
}
}

/// Instantiate a pinned [`InodeStorage`] map with the provided flags.
pub const fn pinned(flags: u32) -> InodeStorage<V> {
InodeStorage {
def: UnsafeCell::new(build_def::<V>(
BPF_MAP_TYPE_INODE_STORAGE,
flags,
PinningType::ByName,
)),
_v: PhantomData,
}
}

/// Get a local storage entry associated with this inode, or insert the provided value and get
/// the mutable reference to the information stored within the inode. Returns [`None`] if there
/// was an issue with inserting the new value.
///
/// ## Safety
///
/// This function is marked unsafe as accessing the same inode's local storage multiple times
/// would create multiple mutable references to the same data, which is not supported by Rust's
/// memory model.
#[inline]
pub unsafe fn get_or_insert(&self, inode: *mut c_void, initial: &V) -> Option<&mut V> {
self.get_or_insert_ptr(inode, initial).map(|p| &mut *p)
}

/// Get a pointer to the local storage entry associated with this inode, or insert the provided
/// value and get the mutable reference to the information stored within the inode. Returns
/// [`None`] if there was an issue with inserting the new value.
#[inline]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn get_or_insert_ptr(&self, inode: *mut c_void, initial: &V) -> Option<*mut V> {
unsafe {
let ptr = bpf_inode_storage_get(
self.def.get() as *mut c_void,
inode,
initial as *const V as *const c_void as *mut c_void,
u64::from(BPF_LOCAL_STORAGE_GET_F_CREATE),
);
NonNull::new(ptr as *mut V).map(|p| p.as_ptr())
}
}

/// Get a local storage entry associated with this inode, or [`None`] if no such value exists.
///
/// ## Safety
///
/// This function is marked unsafe as accessing the same inode's local storage immutably at the
/// same time as mutably (e.g., but way of [`InodeStorage::get_mut`]) is not supported by Rust's
/// memory model.
#[inline]
pub unsafe fn get(&self, inode: *mut c_void) -> Option<&V> {
self.get_ptr(inode).map(|p| &*p)
}

/// Mutably access a local storage entry associated with this inode, or [`None`] if no such
/// value exists.
///
/// ## Safety
///
/// This function is marked unsafe as accessing the same inode's local storage mutably multiple
/// times is not supported by Rust's memory model.
#[inline]
pub unsafe fn get_mut(&self, inode: *mut c_void) -> Option<&mut V> {
self.get_ptr_mut(inode).map(|p| &mut *p)
}

/// Get a pointer to the local storage entry associated with this inode, or [`None`] if no such
/// value exists.
#[inline]
pub fn get_ptr(&self, inode: *mut c_void) -> Option<*const V> {
self.get_ptr_mut(inode).map(|p| p as *const V)
}

/// Get a mutable pointer to the local storage entry associated with this inode, or [`None`] if
/// no such value exists. You are responsible for ensuring that at most one mutable reference to
/// the same inode local storage exists at a given time.
#[inline]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn get_ptr_mut(&self, inode: *mut c_void) -> Option<*mut V> {
unsafe {
let ptr = bpf_inode_storage_get(
self.def.get() as *mut c_void,
inode,
core::ptr::null_mut(),
0,
);
NonNull::new(ptr as *mut V).map(|p| p.as_ptr())
}
}

/// Remove a local storage entry associated with this inode. Returns `Err(-ENOENT)` if no such
/// value was present.
#[inline]
#[allow(clippy::not_unsafe_ptr_arg_deref)]
pub fn remove(&self, inode: *mut c_void) -> Result<(), c_int> {
let ret = unsafe { bpf_inode_storage_delete(self.def.get() as *mut c_void, inode) };
if ret == 0 {
Ok(())
} else {
Err(ret)
}
}
}

const fn build_def<V>(ty: u32, flags: u32, pin: PinningType) -> bpf_map_def {
bpf_map_def {
type_: ty,
key_size: mem::size_of::<c_int>() as u32,
value_size: mem::size_of::<V>() as u32,
max_entries: 1,
map_flags: flags,
id: 0,
pinning: pin as u32,
}
}
1 change: 1 addition & 0 deletions ebpf/aya-ebpf/src/maps/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub(crate) enum PinningType {
pub mod array;
pub mod bloom_filter;
pub mod hash_map;
pub mod inode_storage;
pub mod lpm_trie;
pub mod per_cpu_array;
pub mod perf;
Expand Down
2 changes: 1 addition & 1 deletion ebpf/aya-ebpf/src/programs/lsm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ impl LsmContext {
///
/// [1]: https://elixir.bootlin.com/linux/latest/source/include/linux/lsm_hook_defs.h
pub unsafe fn arg<T: FromBtfArgument>(&self, n: usize) -> T {
T::from_argument(self.ctx as *const _, n)
T::from_argument(self.ctx, n)
}
}

Expand Down
4 changes: 4 additions & 0 deletions test/integration-ebpf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ path = "src/log.rs"
name = "map_test"
path = "src/map_test.rs"

[[bin]]
name = "inode_storage"
path = "src/inode_storage.rs"

[[bin]]
name = "name_test"
path = "src/name_test.rs"
Expand Down
49 changes: 49 additions & 0 deletions test/integration-ebpf/src/inode_storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! The purpose of this test is to identify if we can prevent a tmpfile from being linked to by
//! storing information in inode storage.

#![no_std]
#![no_main]

use aya_ebpf::{
bindings::BPF_F_NO_PREALLOC,
cty::c_void,
macros::{lsm, map},
maps::inode_storage::InodeStorage,
programs::LsmContext,
};
use aya_log_ebpf::warn;

#[map]
static TMP_INODE_STORE: InodeStorage<usize> = InodeStorage::new(BPF_F_NO_PREALLOC);

#[lsm(hook = "inode_post_create_tmpfile")]
pub fn inode_post_create_tmpfile(ctx: LsmContext) -> i32 {
unsafe { try_inode_post_create_tmpfile(ctx) }.unwrap_or_else(|ret| ret)
}

unsafe fn try_inode_post_create_tmpfile(ctx: LsmContext) -> Result<i32, i32> {
let tmpfile: *mut c_void = ctx.arg(1);
if TMP_INODE_STORE.get_or_insert_ptr(tmpfile, &0).is_none() {
warn!(&ctx, "Couldn't add information that we deleted a tmp node!");
}
Ok(0)
}

#[lsm(hook = "inode_link")]
pub fn inode_link(ctx: LsmContext) -> i32 {
unsafe { try_inode_link(ctx) }.unwrap_or_else(|ret| ret)
}

unsafe fn try_inode_link(ctx: LsmContext) -> Result<i32, i32> {
let maybe_tmpfile: *mut c_void = ctx.arg(0);
if TMP_INODE_STORE.get(maybe_tmpfile).is_some() {
return Err(130);
}
Ok(0)
}

#[cfg(not(test))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}
2 changes: 2 additions & 0 deletions test/integration-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ pub const TEXT_64_64_RELOC: &[u8] =

pub const LOG: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/log"));
pub const MAP_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/map_test"));
pub const INODE_STORAGE_TEST: &[u8] =
include_bytes_aligned!(concat!(env!("OUT_DIR"), "/inode_storage"));
pub const NAME_TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/name_test"));
pub const PASS: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/pass"));
pub const TEST: &[u8] = include_bytes_aligned!(concat!(env!("OUT_DIR"), "/test"));
Expand Down
1 change: 1 addition & 0 deletions test/integration-test/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mod bpf_probe_read;
mod btf_relocations;
mod elf;
mod inode_storage;
mod load;
mod log;
mod rbpf;
Expand Down
49 changes: 49 additions & 0 deletions test/integration-test/src/tests/inode_storage.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use std::{
error::Error,
fs::OpenOptions,
os::{fd::IntoRawFd, unix::fs::OpenOptionsExt},
};

use aya::{programs::Lsm, Ebpf};
use aya_obj::btf::Btf;
use libc::{linkat, AT_EMPTY_PATH, AT_FDCWD, O_TMPFILE};

use crate::INODE_STORAGE_TEST;

#[test]
fn no_link_to_tmp() -> Result<(), Box<dyn Error>> {
let mut bpf = Ebpf::load(INODE_STORAGE_TEST)?;
let btf = Btf::from_sys_fs()?;

let rename: &mut Lsm = bpf
.program_mut("inode_post_create_tmpfile")
.unwrap()
.try_into()?;
rename.load("inode_post_create_tmpfile", &btf)?;
rename.attach()?;

let link: &mut Lsm = bpf.program_mut("inode_link").unwrap().try_into()?;
link.load("inode_link", &btf)?;
link.attach()?;

// create a temporary file
let tmpfile = OpenOptions::new()
.custom_flags(O_TMPFILE)
.create_new(true)
.open("/tmp/")?;

let fd = tmpfile.into_raw_fd();
let res = unsafe {
linkat(
fd,
c"".as_ptr(),
AT_FDCWD,
c"/tmp/blah".as_ptr(),
AT_EMPTY_PATH,
)
};

assert_eq!(130, res);

Ok(())
}
34 changes: 34 additions & 0 deletions xtask/public-api/aya-ebpf.txt
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,40 @@ impl<T> core::borrow::BorrowMut<T> for aya_ebpf::maps::hash_map::PerCpuHashMap<K
pub fn aya_ebpf::maps::hash_map::PerCpuHashMap<K, V>::borrow_mut(&mut self) -> &mut T
impl<T> core::convert::From<T> for aya_ebpf::maps::hash_map::PerCpuHashMap<K, V>
pub fn aya_ebpf::maps::hash_map::PerCpuHashMap<K, V>::from(t: T) -> T
pub mod aya_ebpf::maps::inode_storage
#[repr(transparent)] pub struct aya_ebpf::maps::inode_storage::InodeStorage<V>
impl<V> aya_ebpf::maps::inode_storage::InodeStorage<V>
pub unsafe fn aya_ebpf::maps::inode_storage::InodeStorage<V>::get(&self, inode: *mut aya_ebpf_cty::c_void) -> core::option::Option<&V>
pub unsafe fn aya_ebpf::maps::inode_storage::InodeStorage<V>::get_mut(&self, inode: *mut aya_ebpf_cty::c_void) -> core::option::Option<&mut V>
pub unsafe fn aya_ebpf::maps::inode_storage::InodeStorage<V>::get_or_insert(&self, inode: *mut aya_ebpf_cty::c_void, initial: &V) -> core::option::Option<&mut V>
pub fn aya_ebpf::maps::inode_storage::InodeStorage<V>::get_or_insert_ptr(&self, inode: *mut aya_ebpf_cty::c_void, initial: &V) -> core::option::Option<*mut V>
pub fn aya_ebpf::maps::inode_storage::InodeStorage<V>::get_ptr(&self, inode: *mut aya_ebpf_cty::c_void) -> core::option::Option<*const V>
pub fn aya_ebpf::maps::inode_storage::InodeStorage<V>::get_ptr_mut(&self, inode: *mut aya_ebpf_cty::c_void) -> core::option::Option<*mut V>
pub const fn aya_ebpf::maps::inode_storage::InodeStorage<V>::new(flags: u32) -> aya_ebpf::maps::inode_storage::InodeStorage<V>
pub const fn aya_ebpf::maps::inode_storage::InodeStorage<V>::pinned(flags: u32) -> aya_ebpf::maps::inode_storage::InodeStorage<V>
pub fn aya_ebpf::maps::inode_storage::InodeStorage<V>::remove(&self, inode: *mut aya_ebpf_cty::c_void) -> core::result::Result<(), aya_ebpf_cty::ad::c_int>
impl<T: core::marker::Sync> core::marker::Sync for aya_ebpf::maps::inode_storage::InodeStorage<T>
impl<V> !core::marker::Freeze for aya_ebpf::maps::inode_storage::InodeStorage<V>
impl<V> core::marker::Send for aya_ebpf::maps::inode_storage::InodeStorage<V> where V: core::marker::Send
impl<V> core::marker::Unpin for aya_ebpf::maps::inode_storage::InodeStorage<V> where V: core::marker::Unpin
impl<V> !core::panic::unwind_safe::RefUnwindSafe for aya_ebpf::maps::inode_storage::InodeStorage<V>
impl<V> core::panic::unwind_safe::UnwindSafe for aya_ebpf::maps::inode_storage::InodeStorage<V> where V: core::panic::unwind_safe::UnwindSafe
impl<T, U> core::convert::Into<U> for aya_ebpf::maps::inode_storage::InodeStorage<V> where U: core::convert::From<T>
pub fn aya_ebpf::maps::inode_storage::InodeStorage<V>::into(self) -> U
impl<T, U> core::convert::TryFrom<U> for aya_ebpf::maps::inode_storage::InodeStorage<V> where U: core::convert::Into<T>
pub type aya_ebpf::maps::inode_storage::InodeStorage<V>::Error = core::convert::Infallible
pub fn aya_ebpf::maps::inode_storage::InodeStorage<V>::try_from(value: U) -> core::result::Result<T, <T as core::convert::TryFrom<U>>::Error>
impl<T, U> core::convert::TryInto<U> for aya_ebpf::maps::inode_storage::InodeStorage<V> where U: core::convert::TryFrom<T>
pub type aya_ebpf::maps::inode_storage::InodeStorage<V>::Error = <U as core::convert::TryFrom<T>>::Error
pub fn aya_ebpf::maps::inode_storage::InodeStorage<V>::try_into(self) -> core::result::Result<U, <U as core::convert::TryFrom<T>>::Error>
impl<T> core::any::Any for aya_ebpf::maps::inode_storage::InodeStorage<V> where T: 'static + core::marker::Sized
pub fn aya_ebpf::maps::inode_storage::InodeStorage<V>::type_id(&self) -> core::any::TypeId
impl<T> core::borrow::Borrow<T> for aya_ebpf::maps::inode_storage::InodeStorage<V> where T: core::marker::Sized
pub fn aya_ebpf::maps::inode_storage::InodeStorage<V>::borrow(&self) -> &T
impl<T> core::borrow::BorrowMut<T> for aya_ebpf::maps::inode_storage::InodeStorage<V> where T: core::marker::Sized
pub fn aya_ebpf::maps::inode_storage::InodeStorage<V>::borrow_mut(&mut self) -> &mut T
impl<T> core::convert::From<T> for aya_ebpf::maps::inode_storage::InodeStorage<V>
pub fn aya_ebpf::maps::inode_storage::InodeStorage<V>::from(t: T) -> T
pub mod aya_ebpf::maps::lpm_trie
#[repr(packed)] pub struct aya_ebpf::maps::lpm_trie::Key<K>
pub aya_ebpf::maps::lpm_trie::Key::data: K
Expand Down
Loading