From 00334186fb955a61026bd3a319503d1cabbbd44d Mon Sep 17 00:00:00 2001 From: Michal Rostecki <mrostecki@opensuse.org> Date: Thu, 9 Dec 2021 02:17:30 +0100 Subject: [PATCH] Support for fentry and fexit programs fentry and fexit programs are similar to kprobe and kretprobe, but they are newer and they have practically zero overhead to call before or after kernel function. Also, fexit programs are focused on access to arguments rather than the return value. Those kind of programs were introduced in the following patchset: https://lwn.net/Articles/804112/ Signed-off-by: Michal Rostecki <mrostecki@opensuse.org> --- aya/src/bpf.rs | 8 ++- aya/src/obj/mod.rs | 48 ++++++++++++++++ aya/src/programs/fentry.rs | 67 ++++++++++++++++++++++ aya/src/programs/fexit.rs | 67 ++++++++++++++++++++++ aya/src/programs/lsm.rs | 43 ++------------ aya/src/programs/mod.rs | 27 +++++++-- aya/src/programs/raw_trace_point.rs | 18 ++---- aya/src/programs/tp_btf.rs | 37 ++---------- aya/src/programs/utils.rs | 24 ++++++++ bpf/aya-bpf-macros/src/expand.rs | 58 +++++++++++++++++++ bpf/aya-bpf-macros/src/lib.rs | 88 ++++++++++++++++++++++++++++- bpf/aya-bpf/src/programs/fentry.rs | 43 ++++++++++++++ bpf/aya-bpf/src/programs/fexit.rs | 43 ++++++++++++++ bpf/aya-bpf/src/programs/mod.rs | 4 ++ 14 files changed, 481 insertions(+), 94 deletions(-) create mode 100644 aya/src/programs/fentry.rs create mode 100644 aya/src/programs/fexit.rs create mode 100644 aya/src/programs/utils.rs create mode 100644 bpf/aya-bpf/src/programs/fentry.rs create mode 100644 bpf/aya-bpf/src/programs/fexit.rs diff --git a/aya/src/bpf.rs b/aya/src/bpf.rs index 8e102dea..eea4bd55 100644 --- a/aya/src/bpf.rs +++ b/aya/src/bpf.rs @@ -21,9 +21,9 @@ use crate::{ Object, ParseError, ProgramSection, }, programs::{ - BtfTracePoint, CgroupSkb, CgroupSkbAttachType, KProbe, LircMode2, Lsm, PerfEvent, - ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier, SkMsg, - SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp, + BtfTracePoint, CgroupSkb, CgroupSkbAttachType, FEntry, FExit, KProbe, LircMode2, Lsm, + PerfEvent, ProbeKind, Program, ProgramData, ProgramError, RawTracePoint, SchedClassifier, + SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, UProbe, Xdp, }, sys::bpf_map_update_elem_ptr, util::{possible_cpus, POSSIBLE_CPUS}, @@ -314,6 +314,8 @@ impl<'a> BpfLoader<'a> { ProgramSection::BtfTracePoint { .. } => { Program::BtfTracePoint(BtfTracePoint { data }) } + ProgramSection::FEntry { .. } => Program::FEntry(FEntry { data }), + ProgramSection::FExit { .. } => Program::FExit(FExit { data }), }; (name, program) diff --git a/aya/src/obj/mod.rs b/aya/src/obj/mod.rs index b309b408..2eee874f 100644 --- a/aya/src/obj/mod.rs +++ b/aya/src/obj/mod.rs @@ -88,6 +88,8 @@ pub enum ProgramSection { RawTracePoint { name: String }, Lsm { name: String }, BtfTracePoint { name: String }, + FEntry { name: String }, + FExit { name: String }, } impl ProgramSection { @@ -112,6 +114,8 @@ impl ProgramSection { ProgramSection::RawTracePoint { name } => name, ProgramSection::Lsm { name } => name, ProgramSection::BtfTracePoint { name } => name, + ProgramSection::FEntry { name } => name, + ProgramSection::FExit { name } => name, } } } @@ -165,6 +169,8 @@ impl FromStr for ProgramSection { "perf_event" => PerfEvent { name }, "raw_tp" | "raw_tracepoint" => RawTracePoint { name }, "lsm" => Lsm { name }, + "fentry" => FEntry { name }, + "fexit" => FExit { name }, _ => { return Err(ParseError::InvalidProgramSection { section: section.to_owned(), @@ -1192,4 +1198,46 @@ mod tests { }) ); } + + #[test] + fn test_parse_section_fentry() { + let mut obj = fake_obj(); + + assert_matches!( + obj.parse_section(fake_section( + BpfSectionKind::Program, + "fentry/foo", + bytes_of(&fake_ins()) + )), + Ok(()) + ); + assert_matches!( + obj.programs.get("foo"), + Some(Program { + section: ProgramSection::FEntry { .. }, + .. + }) + ); + } + + #[test] + fn test_parse_section_fexit() { + let mut obj = fake_obj(); + + assert_matches!( + obj.parse_section(fake_section( + BpfSectionKind::Program, + "fexit/foo", + bytes_of(&fake_ins()) + )), + Ok(()) + ); + assert_matches!( + obj.programs.get("foo"), + Some(Program { + section: ProgramSection::FExit { .. }, + .. + }) + ); + } } diff --git a/aya/src/programs/fentry.rs b/aya/src/programs/fentry.rs new file mode 100644 index 00000000..f94cb439 --- /dev/null +++ b/aya/src/programs/fentry.rs @@ -0,0 +1,67 @@ +//! fentry programs. +use crate::{ + generated::{bpf_attach_type::BPF_TRACE_FENTRY, bpf_prog_type::BPF_PROG_TYPE_TRACING}, + obj::btf::{Btf, BtfKind}, + programs::{load_program, utils::attach_raw_tracepoint, LinkRef, ProgramData, ProgramError}, +}; + +/// A program that can be attached to the entry point of (almost) any kernel +/// function. +/// +/// [`FEntry`] programs are similar to [kprobes](crate::programs::KProbe), but +/// the difference is that fentry has practically zero overhead to call before +/// kernel function. Fentry programs can be also attaached to other eBPF +/// programs. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 5.5. +/// +/// # Examples +/// +/// ```no_run +/// # #[derive(thiserror::Error, Debug)] +/// # enum Error { +/// # #[error(transparent)] +/// # BtfError(#[from] aya::BtfError), +/// # #[error(transparent)] +/// # Program(#[from] aya::programs::ProgramError), +/// # #[error(transparent)] +/// # Bpf(#[from] aya::BpfError), +/// # } +/// # let mut bpf = Bpf::load_file("ebpf_programs.o")?; +/// use aya::{Bpf, programs::FEntry, BtfError, Btf}; +/// use std::convert::TryInto; +/// +/// let btf = Btf::from_sys_fs()?; +/// let program: &mut FEntry = bpf.program_mut("filename_lookup").unwrap().try_into()?; +/// program.load("filename_lookup", &btf)?; +/// program.attach()?; +/// # Ok::<(), Error>(()) +/// ``` +#[derive(Debug)] +#[doc(alias = "BPF_TRACE_FENTRY")] +#[doc(alias = "BPF_PROG_TYPE_TRACING")] +pub struct FEntry { + pub(crate) data: ProgramData, +} + +impl FEntry { + /// Loads the program inside the kernel. + /// + /// See also [`Program::load`](crate::programs::Program::load). + /// + /// Loads the program so it's executed when the kernel function `fn_name` + /// is entered. The `btf` argument must contain the BTF info for the + /// running kernel. + pub fn load(&mut self, fn_name: &str, btf: &Btf) -> Result<(), ProgramError> { + self.data.expected_attach_type = Some(BPF_TRACE_FENTRY); + self.data.attach_btf_id = Some(btf.id_by_type_name_kind(fn_name, BtfKind::Func)?); + load_program(BPF_PROG_TYPE_TRACING, &mut self.data) + } + + /// Attaches the program + pub fn attach(&mut self) -> Result<LinkRef, ProgramError> { + attach_raw_tracepoint(&mut self.data, None) + } +} diff --git a/aya/src/programs/fexit.rs b/aya/src/programs/fexit.rs new file mode 100644 index 00000000..85d3d2e9 --- /dev/null +++ b/aya/src/programs/fexit.rs @@ -0,0 +1,67 @@ +//! fexit programs. +use crate::{ + generated::{bpf_attach_type::BPF_TRACE_FEXIT, bpf_prog_type::BPF_PROG_TYPE_TRACING}, + obj::btf::{Btf, BtfKind}, + programs::{load_program, utils::attach_raw_tracepoint, LinkRef, ProgramData, ProgramError}, +}; + +/// A program that can be attached to the exit point of (almost) anny kernel +/// function. +/// +/// [`FExit`] programs are similar to [kretprobes](crate::programs::KProbe), +/// but the difference is that fexit has practically zero overhead to call +/// before kernel function. Fexit programs can be also attached to other eBPF +/// programs. +/// +/// # Minimum kernel version +/// +/// The minimum kernel version required to use this feature is 5.5. +/// +/// # Examples +/// +/// ```no_run +/// # #[derive(thiserror::Error, Debug)] +/// # enum Error { +/// # #[error(transparent)] +/// # BtfError(#[from] aya::BtfError), +/// # #[error(transparent)] +/// # Program(#[from] aya::programs::ProgramError), +/// # #[error(transparent)] +/// # Bpf(#[from] aya::BpfError), +/// # } +/// # let mut bpf = Bpf::load_file("ebpf_programs.o")?; +/// use aya::{Bpf, programs::FExit, BtfError, Btf}; +/// use std::convert::TryInto; +/// +/// let btf = Btf::from_sys_fs()?; +/// let program: &mut FExit = bpf.program_mut("filename_lookup").unwrap().try_into()?; +/// program.load("filename_lookup", &btf)?; +/// program.attach()?; +/// # Ok::<(), Error>(()) +/// ``` +#[derive(Debug)] +#[doc(alias = "BPF_TRACE_FEXIT")] +#[doc(alias = "BPF_PROG_TYPE_TRACING")] +pub struct FExit { + pub(crate) data: ProgramData, +} + +impl FExit { + /// Loads the program inside the kernel. + /// + /// See also [`Program::load`](crate::programs::Program::load). + /// + /// Loads the program so it's executed when the kernel function `fn_name` + /// is exited. The `btf` argument must contain the BTF info for the running + /// kernel. + pub fn load(&mut self, fn_name: &str, btf: &Btf) -> Result<(), ProgramError> { + self.data.expected_attach_type = Some(BPF_TRACE_FEXIT); + self.data.attach_btf_id = Some(btf.id_by_type_name_kind(fn_name, BtfKind::Func)?); + load_program(BPF_PROG_TYPE_TRACING, &mut self.data) + } + + /// Attaches the program + pub fn attach(&mut self) -> Result<LinkRef, ProgramError> { + attach_raw_tracepoint(&mut self.data, None) + } +} diff --git a/aya/src/programs/lsm.rs b/aya/src/programs/lsm.rs index ef057d9b..03c0a778 100644 --- a/aya/src/programs/lsm.rs +++ b/aya/src/programs/lsm.rs @@ -1,13 +1,8 @@ //! LSM probes. -use std::os::unix::io::RawFd; - -use thiserror::Error; - use crate::{ generated::{bpf_attach_type::BPF_LSM_MAC, bpf_prog_type::BPF_PROG_TYPE_LSM}, - obj::btf::{Btf, BtfError, BtfKind}, - programs::{load_program, FdLink, LinkRef, ProgramData, ProgramError}, - sys::bpf_raw_tracepoint_open, + obj::btf::{Btf, BtfKind}, + programs::{load_program, utils::attach_raw_tracepoint, LinkRef, ProgramData, ProgramError}, }; /// A program that attaches to Linux LSM hooks. Used to implement security policy and @@ -30,8 +25,6 @@ use crate::{ /// # #[derive(thiserror::Error, Debug)] /// # enum LsmError { /// # #[error(transparent)] -/// # LsmLoad(#[from] aya::programs::LsmLoadError), -/// # #[error(transparent)] /// # BtfError(#[from] aya::BtfError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), @@ -56,16 +49,6 @@ pub struct Lsm { pub(crate) data: ProgramData, } -/// Error type returned when loading LSM programs. -#[derive(Debug, Error)] -pub enum LsmLoadError { - #[error(transparent)] - Btf(#[from] BtfError), - - #[error(transparent)] - Program(#[from] ProgramError), -} - impl Lsm { /// Loads the program inside the kernel. /// @@ -75,32 +58,16 @@ impl Lsm { /// /// * `lsm_hook_name` - full name of the LSM hook that the program should /// be attached to - pub fn load(&mut self, lsm_hook_name: &str, btf: &Btf) -> Result<(), LsmLoadError> { + pub fn load(&mut self, lsm_hook_name: &str, btf: &Btf) -> Result<(), ProgramError> { self.data.expected_attach_type = Some(BPF_LSM_MAC); let type_name = format!("bpf_lsm_{}", lsm_hook_name); self.data.attach_btf_id = Some(btf.id_by_type_name_kind(type_name.as_str(), BtfKind::Func)?); - load_program(BPF_PROG_TYPE_LSM, &mut self.data).map_err(LsmLoadError::from) + load_program(BPF_PROG_TYPE_LSM, &mut self.data) } /// Attaches the program. pub fn attach(&mut self) -> Result<LinkRef, ProgramError> { - attach_btf_id(&mut self.data) + attach_raw_tracepoint(&mut self.data, None) } } - -/// Common logic for all BPF program types that attach to a BTF id. -pub(crate) fn attach_btf_id(program_data: &mut ProgramData) -> Result<LinkRef, ProgramError> { - let prog_fd = program_data.fd_or_err()?; - - // Attaching LSM programs doesn't require providing attach name. LSM - // programs are attached to LSM hook which is specified in the program. - let pfd = bpf_raw_tracepoint_open(None, prog_fd).map_err(|(_code, io_error)| { - ProgramError::SyscallError { - call: "bpf_raw_tracepoint_open".to_owned(), - io_error, - } - })? as RawFd; - - Ok(program_data.link(FdLink { fd: Some(pfd) })) -} diff --git a/aya/src/programs/mod.rs b/aya/src/programs/mod.rs index 6d32187d..5b10e3e8 100644 --- a/aya/src/programs/mod.rs +++ b/aya/src/programs/mod.rs @@ -37,6 +37,8 @@ //! [`Bpf::program_mut`]: crate::Bpf::program_mut //! [`maps`]: crate::maps mod cgroup_skb; +mod fentry; +mod fexit; mod kprobe; mod lirc_mode2; mod lsm; @@ -52,6 +54,7 @@ pub mod tc; mod tp_btf; mod trace_point; mod uprobe; +mod utils; mod xdp; use libc::{close, dup, ENOSPC}; @@ -68,9 +71,11 @@ use std::{ use thiserror::Error; pub use cgroup_skb::{CgroupSkb, CgroupSkbAttachType}; +pub use fentry::FEntry; +pub use fexit::FExit; pub use kprobe::{KProbe, KProbeError}; pub use lirc_mode2::LircMode2; -pub use lsm::{Lsm, LsmLoadError}; +pub use lsm::Lsm; use perf_attach::*; pub use perf_event::{PerfEvent, PerfEventScope, PerfTypeId, SamplePolicy}; pub use probe::ProbeKind; @@ -80,7 +85,7 @@ pub use sk_skb::{SkSkb, SkSkbKind}; pub use sock_ops::SockOps; pub use socket_filter::{SocketFilter, SocketFilterError}; pub use tc::{SchedClassifier, TcAttachType, TcError}; -pub use tp_btf::{BtfTracePoint, BtfTracePointError}; +pub use tp_btf::BtfTracePoint; pub use trace_point::{TracePoint, TracePointError}; pub use uprobe::{UProbe, UProbeError}; pub use xdp::{Xdp, XdpError, XdpFlags}; @@ -88,7 +93,7 @@ pub use xdp::{Xdp, XdpError, XdpFlags}; use crate::{ generated::{bpf_attach_type, bpf_prog_info, bpf_prog_type}, maps::MapError, - obj::{self, Function, KernelVersion}, + obj::{self, btf::BtfError, Function, KernelVersion}, sys::{bpf_load_program, bpf_pin_object, bpf_prog_detach, bpf_prog_query, BpfLoadProgramAttrs}, }; @@ -170,9 +175,9 @@ pub enum ProgramError { #[error(transparent)] TcError(#[from] TcError), - /// An error occurred while working with a BTF raw tracepoint program. + /// An error occurred while working with BTF. #[error(transparent)] - BtfTracePointError(#[from] BtfTracePointError), + Btf(#[from] BtfError), } pub trait ProgramFd { @@ -197,6 +202,8 @@ pub enum Program { RawTracePoint(RawTracePoint), Lsm(Lsm), BtfTracePoint(BtfTracePoint), + FEntry(FEntry), + FExit(FExit), } impl Program { @@ -233,6 +240,8 @@ impl Program { Program::RawTracePoint(_) => BPF_PROG_TYPE_RAW_TRACEPOINT, Program::Lsm(_) => BPF_PROG_TYPE_LSM, Program::BtfTracePoint(_) => BPF_PROG_TYPE_TRACING, + Program::FEntry(_) => BPF_PROG_TYPE_TRACING, + Program::FExit(_) => BPF_PROG_TYPE_TRACING, } } @@ -258,6 +267,8 @@ impl Program { Program::RawTracePoint(p) => &p.data, Program::Lsm(p) => &p.data, Program::BtfTracePoint(p) => &p.data, + Program::FEntry(p) => &p.data, + Program::FExit(p) => &p.data, } } @@ -278,6 +289,8 @@ impl Program { Program::RawTracePoint(p) => &mut p.data, Program::Lsm(p) => &mut p.data, Program::BtfTracePoint(p) => &mut p.data, + Program::FEntry(p) => &mut p.data, + Program::FExit(p) => &mut p.data, } } } @@ -605,6 +618,8 @@ impl_program_fd!( Lsm, RawTracePoint, BtfTracePoint, + FEntry, + FExit, ); macro_rules! impl_try_from_program { @@ -651,6 +666,8 @@ impl_try_from_program!( Lsm, RawTracePoint, BtfTracePoint, + FEntry, + FExit, ); /// Provides information about a loaded program, like name, id and statistics diff --git a/aya/src/programs/raw_trace_point.rs b/aya/src/programs/raw_trace_point.rs index 81b0e393..66794870 100644 --- a/aya/src/programs/raw_trace_point.rs +++ b/aya/src/programs/raw_trace_point.rs @@ -1,10 +1,9 @@ //! Raw tracepoints. -use std::{ffi::CString, os::unix::io::RawFd}; +use std::ffi::CString; use crate::{ generated::bpf_prog_type::BPF_PROG_TYPE_RAW_TRACEPOINT, - programs::{load_program, FdLink, LinkRef, ProgramData, ProgramError}, - sys::bpf_raw_tracepoint_open, + programs::{load_program, utils::attach_raw_tracepoint, LinkRef, ProgramData, ProgramError}, }; /// A program that can be attached at a pre-defined kernel trace point, but also @@ -47,16 +46,7 @@ impl RawTracePoint { /// Attaches the program to the given tracepoint. pub fn attach(&mut self, tp_name: &str) -> Result<LinkRef, ProgramError> { - let prog_fd = self.data.fd_or_err()?; - let name = CString::new(tp_name).unwrap(); - - let pfd = bpf_raw_tracepoint_open(Some(&name), prog_fd).map_err(|(_code, io_error)| { - ProgramError::SyscallError { - call: "bpf_raw_tracepoint_open".to_owned(), - io_error, - } - })? as RawFd; - - Ok(self.data.link(FdLink { fd: Some(pfd) })) + let tp_name_c = CString::new(tp_name).unwrap(); + attach_raw_tracepoint(&mut self.data, Some(&tp_name_c)) } } diff --git a/aya/src/programs/tp_btf.rs b/aya/src/programs/tp_btf.rs index 966414df..d0631498 100644 --- a/aya/src/programs/tp_btf.rs +++ b/aya/src/programs/tp_btf.rs @@ -1,13 +1,8 @@ //! BTF-enabled raw tracepoints. -use std::os::unix::io::RawFd; - -use thiserror::Error; - use crate::{ generated::{bpf_attach_type::BPF_TRACE_RAW_TP, bpf_prog_type::BPF_PROG_TYPE_TRACING}, - obj::btf::{Btf, BtfError, BtfKind}, - programs::{load_program, FdLink, LinkRef, ProgramData, ProgramError}, - sys::bpf_raw_tracepoint_open, + obj::btf::{Btf, BtfKind}, + programs::{load_program, utils::attach_raw_tracepoint, LinkRef, ProgramData, ProgramError}, }; /// Marks a function as a [BTF-enabled raw tracepoint][1] eBPF program that can be attached at @@ -27,8 +22,6 @@ use crate::{ /// # #[derive(thiserror::Error, Debug)] /// # enum Error { /// # #[error(transparent)] -/// # BtfTracePointError(#[from] aya::programs::BtfTracePointError), -/// # #[error(transparent)] /// # BtfError(#[from] aya::BtfError), /// # #[error(transparent)] /// # Program(#[from] aya::programs::ProgramError), @@ -54,14 +47,6 @@ pub struct BtfTracePoint { pub(crate) data: ProgramData, } -/// Error type returned when loading LSM programs. -#[derive(Debug, Error)] -pub enum BtfTracePointError { - /// An error occured while working with BTF. - #[error(transparent)] - Btf(#[from] BtfError), -} - impl BtfTracePoint { /// Loads the program inside the kernel. /// @@ -74,25 +59,13 @@ impl BtfTracePoint { pub fn load(&mut self, tracepoint: &str, btf: &Btf) -> Result<(), ProgramError> { self.data.expected_attach_type = Some(BPF_TRACE_RAW_TP); let type_name = format!("btf_trace_{}", tracepoint); - self.data.attach_btf_id = Some( - btf.id_by_type_name_kind(type_name.as_str(), BtfKind::Typedef) - .map_err(BtfTracePointError::from)?, - ); + self.data.attach_btf_id = + Some(btf.id_by_type_name_kind(type_name.as_str(), BtfKind::Typedef)?); load_program(BPF_PROG_TYPE_TRACING, &mut self.data) } /// Attaches the program. pub fn attach(&mut self) -> Result<LinkRef, ProgramError> { - let prog_fd = self.data.fd_or_err()?; - - // BTF programs specify their attach name at program load time - let pfd = bpf_raw_tracepoint_open(None, prog_fd).map_err(|(_code, io_error)| { - ProgramError::SyscallError { - call: "bpf_raw_tracepoint_open".to_owned(), - io_error, - } - })? as RawFd; - - Ok(self.data.link(FdLink { fd: Some(pfd) })) + attach_raw_tracepoint(&mut self.data, None) } } diff --git a/aya/src/programs/utils.rs b/aya/src/programs/utils.rs new file mode 100644 index 00000000..c864da93 --- /dev/null +++ b/aya/src/programs/utils.rs @@ -0,0 +1,24 @@ +//! Common functions shared between multiple eBPF program types. +use std::{ffi::CStr, os::unix::io::RawFd}; + +use crate::{ + programs::{FdLink, LinkRef, ProgramData, ProgramError}, + sys::bpf_raw_tracepoint_open, +}; + +/// Attaches the program to a raw tracepoint. +pub(crate) fn attach_raw_tracepoint( + program_data: &mut ProgramData, + tp_name: Option<&CStr>, +) -> Result<LinkRef, ProgramError> { + let prog_fd = program_data.fd_or_err()?; + + let pfd = bpf_raw_tracepoint_open(tp_name, prog_fd).map_err(|(_code, io_error)| { + ProgramError::SyscallError { + call: "bpf_raw_tracepoint_open".to_owned(), + io_error, + } + })? as RawFd; + + Ok(program_data.link(FdLink { fd: Some(pfd) })) +} diff --git a/bpf/aya-bpf-macros/src/expand.rs b/bpf/aya-bpf-macros/src/expand.rs index 39791e7a..ed10ca3f 100644 --- a/bpf/aya-bpf-macros/src/expand.rs +++ b/bpf/aya-bpf-macros/src/expand.rs @@ -529,6 +529,64 @@ impl SocketFilter { } } +pub struct FEntry { + item: ItemFn, + name: String, +} + +impl FEntry { + pub fn from_syn(mut args: Args, item: ItemFn) -> Result<FEntry> { + let name = name_arg(&mut args)?.unwrap_or_else(|| item.sig.ident.to_string()); + + Ok(FEntry { item, name }) + } + + pub fn expand(&self) -> Result<TokenStream> { + let section_name = format!("fentry/{}", self.name); + let fn_name = &self.item.sig.ident; + let item = &self.item; + Ok(quote! { + #[no_mangle] + #[link_section = #section_name] + fn #fn_name(ctx: *mut ::core::ffi::c_void) -> i32 { + let _ = #fn_name(::aya_bpf::programs::FEntryContext::new(ctx)); + return 0; + + #item + } + }) + } +} + +pub struct FExit { + item: ItemFn, + name: String, +} + +impl FExit { + pub fn from_syn(mut args: Args, item: ItemFn) -> Result<FExit> { + let name = name_arg(&mut args)?.unwrap_or_else(|| item.sig.ident.to_string()); + + Ok(FExit { item, name }) + } + + pub fn expand(&self) -> Result<TokenStream> { + let section_name = format!("fexit/{}", self.name); + let fn_name = &self.item.sig.ident; + let item = &self.item; + Ok(quote! { + #[no_mangle] + #[link_section = #section_name] + fn #fn_name(ctx: *mut ::core::ffi::c_void) -> i32 { + let _ = #fn_name(::aya_bpf::programs::FExitContext::new(ctx)); + return 0; + + #item + } + }) + } +} + #[cfg(test)] mod tests { use syn::parse_quote; diff --git a/bpf/aya-bpf-macros/src/lib.rs b/bpf/aya-bpf-macros/src/lib.rs index 08d2748a..4ae01f83 100644 --- a/bpf/aya-bpf-macros/src/lib.rs +++ b/bpf/aya-bpf-macros/src/lib.rs @@ -1,8 +1,9 @@ mod expand; use expand::{ - Args, BtfTracePoint, CgroupSkb, Lsm, Map, PerfEvent, Probe, ProbeKind, RawTracePoint, - SchedClassifier, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, Xdp, + Args, BtfTracePoint, CgroupSkb, FEntry, FExit, Lsm, Map, PerfEvent, Probe, ProbeKind, + RawTracePoint, SchedClassifier, SkMsg, SkSkb, SkSkbKind, SockOps, SocketFilter, TracePoint, + Xdp, }; use proc_macro::TokenStream; use syn::{parse_macro_input, ItemFn, ItemStatic}; @@ -344,3 +345,86 @@ pub fn socket_filter(attrs: TokenStream, item: TokenStream) -> TokenStream { .unwrap_or_else(|err| err.to_compile_error()) .into() } + +/// Marks a function as a fentry eBPF program that can be attached to almost +/// any function inside the kernel. The difference between fentry and kprobe +/// is that fexit has practically zero overhead to call before kernel function. +/// fentry programs can be also attached to other eBPF programs. +/// +/// # Minimumm kernel version +/// +/// The minimum kernel version required to use this feature is 5.5. +/// +/// # Examples +/// +/// ```no_run +/// use aya_bpf::{macros::fentry, programs::FEntryContext}; +/// use vmlinux::{filename, path}; +/// +/// #[fentry(name = "filename_lookup")] +/// fn filename_lookup(ctx: FEntryContext) -> i32 { +/// match { try_filename_lookup(ctx) } { +/// Ok(ret) => ret, +/// Err(ret) => ret, +/// } +/// } +/// +/// unsafe fn try_filename_lookup(ctx: FEntryContext) -> Result<u32, u32> { +/// let _f: *const filename = ctx.arg(1); +/// let _p: *const path = ctx.arg(3); +/// +/// Ok(0) +/// } +/// ``` +#[proc_macro_attribute] +pub fn fentry(attrs: TokenStream, item: TokenStream) -> TokenStream { + let args = parse_macro_input!(attrs as Args); + let item = parse_macro_input!(item as ItemFn); + + FEntry::from_syn(args, item) + .and_then(|u| u.expand()) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + +/// Marks a function as a fexit eBPF program that can be attached to almost +/// any function inside the kernel. The difference between fexit and kretprobe +/// is that fexit has practically zero overhead to call after kernel function +/// and it focuses on access to arguments rather than the return value. fexit +/// programs can be also attached to other eBPF programs +/// +/// # Minimumm kernel version +/// +/// The minimum kernel version required to use this feature is 5.5. +/// +/// # Examples +/// +/// ```no_run +/// use aya_bpf::{macros::fexit, programs::FExitContext}; +/// use vmlinux::{filename, path}; +/// +/// #[fexit(name = "filename_lookup")] +/// fn filename_lookup(ctx: FExitContext) -> i32 { +/// match { try_filename_lookup(ctx) } { +/// Ok(ret) => ret, +/// Err(ret) => ret, +/// } +/// } +/// +/// unsafe fn try_filename_lookup(ctx: FExitContext) -> Result<u32, u32> { +/// let _f: *const filename = ctx.arg(1); +/// let _p: *const path = ctx.arg(3); +/// +/// Ok(0) +/// } +/// ``` +#[proc_macro_attribute] +pub fn fexit(attrs: TokenStream, item: TokenStream) -> TokenStream { + let args = parse_macro_input!(attrs as Args); + let item = parse_macro_input!(item as ItemFn); + + FExit::from_syn(args, item) + .and_then(|u| u.expand()) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} diff --git a/bpf/aya-bpf/src/programs/fentry.rs b/bpf/aya-bpf/src/programs/fentry.rs new file mode 100644 index 00000000..56687539 --- /dev/null +++ b/bpf/aya-bpf/src/programs/fentry.rs @@ -0,0 +1,43 @@ +use core::ffi::c_void; + +use crate::{args::FromBtfArgument, BpfContext}; + +pub struct FEntryContext { + ctx: *mut c_void, +} + +impl FEntryContext { + pub fn new(ctx: *mut c_void) -> FEntryContext { + FEntryContext { ctx } + } + + /// Returns the `n`th argument to passed to the probe function, starting from 0. + /// + /// # Examples + /// + /// ```no_run + /// # #![allow(non_camel_case_types)] + /// # #![allow(dead_code)] + /// # use aya_bpf::{cty::c_int, programs::FEntryContext}; + /// # type pid_t = c_int; + /// # struct task_struct { + /// # pid: pid_t, + /// # } + /// unsafe fn try_fentry_try_to_wake_up(ctx: FEntryContext) -> Result<u32, u32> { + /// let tp: *const task_struct = ctx.arg(0); + /// + /// // Do something with tp + /// + /// Ok(0) + /// } + /// ``` + pub unsafe fn arg<T: FromBtfArgument>(&self, n: usize) -> T { + T::from_argument(self.ctx as *const _, n) + } +} + +impl BpfContext for FEntryContext { + fn as_ptr(&self) -> *mut c_void { + self.ctx + } +} diff --git a/bpf/aya-bpf/src/programs/fexit.rs b/bpf/aya-bpf/src/programs/fexit.rs new file mode 100644 index 00000000..1e52d733 --- /dev/null +++ b/bpf/aya-bpf/src/programs/fexit.rs @@ -0,0 +1,43 @@ +use core::ffi::c_void; + +use crate::{args::FromBtfArgument, BpfContext}; + +pub struct FExitContext { + ctx: *mut c_void, +} + +impl FExitContext { + pub fn new(ctx: *mut c_void) -> FExitContext { + FExitContext { ctx } + } + + /// Returns the `n`th argument to passed to the probe function, starting from 0. + /// + /// # Examples + /// + /// ```no_run + /// # #![allow(non_camel_case_types)] + /// # #![allow(dead_code)] + /// # use aya_bpf::{cty::c_int, programs::FExitContext}; + /// # type pid_t = c_int; + /// # struct task_struct { + /// # pid: pid_t, + /// # } + /// unsafe fn try_filename_lookup(ctx: FExitContext) -> Result<u32, u32> { + /// let tp: *const task_struct = ctx.arg(0); + /// + /// // Do something with tp + /// + /// Ok(0) + /// } + /// ``` + pub unsafe fn arg<T: FromBtfArgument>(&self, n: usize) -> T { + T::from_argument(self.ctx as *const _, n) + } +} + +impl BpfContext for FExitContext { + fn as_ptr(&self) -> *mut c_void { + self.ctx + } +} diff --git a/bpf/aya-bpf/src/programs/mod.rs b/bpf/aya-bpf/src/programs/mod.rs index 097d428d..8a022c18 100644 --- a/bpf/aya-bpf/src/programs/mod.rs +++ b/bpf/aya-bpf/src/programs/mod.rs @@ -1,3 +1,5 @@ +pub mod fentry; +pub mod fexit; pub mod lsm; pub mod perf_event; pub mod probe; @@ -9,6 +11,8 @@ pub mod tp_btf; pub mod tracepoint; pub mod xdp; +pub use fentry::FEntryContext; +pub use fexit::FExitContext; pub use lsm::LsmContext; pub use perf_event::PerfEventContext; pub use probe::ProbeContext;