Skip to content

Commit

Permalink
program: add fd_from_pinned_path() method
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
vankoven committed Aug 24, 2024
1 parent 658277e commit 04c7d72
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 0 deletions.
6 changes: 6 additions & 0 deletions libbpf-rs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
30 changes: 30 additions & 0 deletions libbpf-rs/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 _;
Expand Down Expand Up @@ -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<P: AsRef<Path>>(path: P) -> Result<OwnedFd> {
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()) }
Expand Down
57 changes: 57 additions & 0 deletions libbpf-rs/src/util.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -92,6 +96,40 @@ pub fn validate_bpf_ret<T>(ptr: *mut T) -> Result<NonNull<T>> {
}
}

/// 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<BpfObjectType> {
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<T> {
Expand Down Expand Up @@ -120,6 +158,9 @@ impl<T> Deref for LazyLock<T> {
mod tests {
use super::*;

use std::io;
use std::os::fd::AsFd;

#[test]
fn test_roundup() {
for i in 1..=256 {
Expand Down Expand Up @@ -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");
}
}
41 changes: 41 additions & 0 deletions libbpf-rs/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 04c7d72

Please sign in to comment.