From 462e13eaf119cef47c728ad8b87cc7c2c1ac5710 Mon Sep 17 00:00:00 2001 From: Ivan Koveshnikov Date: Thu, 22 Aug 2024 03:39:50 +0200 Subject: [PATCH] program: add fd_from_pinned_path() method After a eBPF program is loaded to the kernel and the loader process exited, a user still may want to attach this program to PROG_ARRAY of another one. Add an API to recover the file descriptor from a pinned path. Signed-off-by: Ivan Koveshnikov --- libbpf-rs/CHANGELOG.md | 6 +++++ libbpf-rs/src/program.rs | 30 +++++++++++++++++++++ libbpf-rs/src/util.rs | 57 ++++++++++++++++++++++++++++++++++++++++ libbpf-rs/tests/test.rs | 41 +++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+) diff --git a/libbpf-rs/CHANGELOG.md b/libbpf-rs/CHANGELOG.md index b9bce70d..c85a0e96 100644 --- a/libbpf-rs/CHANGELOG.md +++ b/libbpf-rs/CHANGELOG.md @@ -1,3 +1,9 @@ +Unreleased +---------- +- Added `Program::fd_from_pinned_path` method for restoring program descriptor + from a pinned path + + 0.24.0 ------ - Split `{Open,}{Map,Program}` into `{Open,}{Map,Program}` (for shared diff --git a/libbpf-rs/src/program.rs b/libbpf-rs/src/program.rs index f3e7726f..62d373ca 100644 --- a/libbpf-rs/src/program.rs +++ b/libbpf-rs/src/program.rs @@ -26,6 +26,7 @@ use libbpf_sys::bpf_func_id; use crate::util; use crate::util::validate_bpf_ret; +use crate::util::BpfObjectType; use crate::AsRawLibbpf; use crate::Error; use crate::ErrorExt as _; @@ -624,6 +625,35 @@ impl<'obj> Program<'obj> { Ok(prog_info.id) } + /// Returns fd of a previously pinned program + /// + /// Returns error, if the pinned path doesn't represent an eBPF program. + pub fn fd_from_pinned_path>(path: P) -> Result { + let path_c = util::path_to_cstring(&path)?; + let path_ptr = path_c.as_ptr(); + + let fd = unsafe { libbpf_sys::bpf_obj_get(path_ptr) }; + let fd = util::parse_ret_i32(fd).with_context(|| { + format!( + "failed to retrieve BPF object from pinned path `{}`", + path.as_ref().display() + ) + })?; + let fd = unsafe { OwnedFd::from_raw_fd(fd) }; + + // A pinned path may represent an object of any kind, including map + // and link. This may cause unexpected behaviour for following functions, + // like bpf_*_get_info_by_fd(), which allow objects of any type. + let fd_type = util::object_type_from_fd(fd.as_fd())?; + match fd_type { + BpfObjectType::Program => Ok(fd), + other => Err(Error::with_invalid_data(format!( + "retrieved BPF fd is not a program fd: {:#?}", + other + ))), + } + } + /// Returns flags that have been set for the program. pub fn flags(&self) -> u32 { unsafe { libbpf_sys::bpf_program__flags(self.ptr.as_ptr()) } diff --git a/libbpf-rs/src/util.rs b/libbpf-rs/src/util.rs index c304db5f..bfe3f6c2 100644 --- a/libbpf-rs/src/util.rs +++ b/libbpf-rs/src/util.rs @@ -1,12 +1,16 @@ use std::ffi::CStr; use std::ffi::CString; +use std::fs; use std::mem::transmute; use std::ops::Deref; +use std::os::fd::AsRawFd; +use std::os::fd::BorrowedFd; use std::os::raw::c_char; use std::path::Path; use std::ptr::NonNull; use std::sync::OnceLock; +use crate::error::IntoError; use crate::Error; use crate::Result; @@ -92,6 +96,40 @@ pub fn validate_bpf_ret(ptr: *mut T) -> Result> { } } +/// An enum describing type of eBPF object. +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum BpfObjectType { + /// The object is a map. + Map, + /// The object is a program. + Program, + /// The object is a BPF link. + Link, +} + +/// Get type of BPF object by fd. +/// +/// This information is not exported directly by bpf_*_get_info_by_fd() functions, +/// as kernel relies on the userspace code to know what kind of object it +/// queries. The type of object can be recovered by fd only from the proc +/// filesystem. The same approach is used in bpftool. +pub fn object_type_from_fd(fd: BorrowedFd<'_>) -> Result { + let fd_link = format!("/proc/self/fd/{}", fd.as_raw_fd()); + let link_type = fs::read_link(fd_link) + .map_err(|e| Error::with_invalid_data(format!("can't read fd link: {}", e)))?; + let link_type = link_type + .to_str() + .ok_or_invalid_data(|| "can't convert PathBuf to str")?; + + match link_type { + "anon_inode:bpf-link" => Ok(BpfObjectType::Link), + "anon_inode:bpf-map" => Ok(BpfObjectType::Map), + "anon_inode:bpf-prog" => Ok(BpfObjectType::Program), + other => Err(Error::with_invalid_data(format!( + "unknown type of BPF fd: {other}" + ))), + } +} // Fix me, If std::sync::LazyLock is stable(https://github.com/rust-lang/rust/issues/109736). pub(crate) struct LazyLock { @@ -120,6 +158,9 @@ impl Deref for LazyLock { mod tests { use super::*; + use std::io; + use std::os::fd::AsFd; + #[test] fn test_roundup() { for i in 1..=256 { @@ -165,4 +206,20 @@ mod tests { let slice = ['a' as _, 'b' as _, 'c' as _]; assert_eq!(c_char_slice_to_cstr(&slice), None); } + + /// Check that object_type_from_fd() doesn't allow descriptors of usual + /// files to be used. Testing with BPF objects requires BPF to be + /// loaded. + #[test] + fn test_object_type_from_fd_with_unexpected_fds() { + let path = "/tmp/libbpf-rs_not_a_bpf_object"; + let not_object = fs::File::create(path).expect("failed to create a plain file"); + + let _ = object_type_from_fd(not_object.as_fd()) + .expect_err("a common file was treated as a BPF object"); + let _ = object_type_from_fd(io::stdout().as_fd()) + .expect_err("the stdout fd was treated as a BPF object"); + + fs::remove_file(path).expect("failed to remove temporary file"); + } } diff --git a/libbpf-rs/tests/test.rs b/libbpf-rs/tests/test.rs index 6b6d2a82..0d143d0b 100644 --- a/libbpf-rs/tests/test.rs +++ b/libbpf-rs/tests/test.rs @@ -748,6 +748,47 @@ fn test_object_loading_pinned_map_from_path() { ); } +#[tag(root)] +#[test] +fn test_program_loading_fd_from_pinned_path() { + bump_rlimit_mlock(); + + let path = "/sys/fs/bpf/myprog_test_pin_to_load_from_path"; + let prog_name = "handle__sched_switch"; + + let mut obj = get_test_object("runqslower.bpf.o"); + let mut prog = get_prog_mut(&mut obj, prog_name); + prog.pin(path).expect("pinning prog failed"); + let prog_id = Program::get_id_by_fd(prog.as_fd()).expect("failed to determine prog id"); + + let pinned_prog_fd = + Program::fd_from_pinned_path(path).expect("failed to get fd of pinned prog"); + let pinned_prog_id = + Program::get_id_by_fd(pinned_prog_fd.as_fd()).expect("failed to determine pinned prog id"); + + assert_eq!(prog_id, pinned_prog_id); + + prog.unpin(path).expect("unpinning program failed"); +} + +#[tag(root)] +#[test] +fn test_program_loading_fd_from_pinned_path_with_wrong_pin_type() { + bump_rlimit_mlock(); + + let path = "/sys/fs/bpf/mymap_test_pin_to_load_from_path"; + let map_name = "events"; + + let mut obj = get_test_object("runqslower.bpf.o"); + let mut map = get_map_mut(&mut obj, map_name); + map.pin(path).expect("pinning map failed"); + + // Must fail, as the pinned path points to a map, not program. + let _ = Program::fd_from_pinned_path(path).expect_err("program fd obtained from pinned map"); + + map.unpin(path).expect("unpinning program failed"); +} + #[tag(root)] #[test] fn test_object_loading_loaded_map_from_id() {