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

feat(aya): Add task storage map type (in the user-space) #1161

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

vadorovsky
Copy link
Member

@vadorovsky vadorovsky commented Feb 2, 2025

Task storage is a type of map which uses task_struct kernel type as a key. When the task (process) stops, the corresponding entry is automatically removed.

This change add support only in the user-space and tests the functionality with a C program.


This change is Reviewable

Copy link

netlify bot commented Feb 2, 2025

Deploy Preview for aya-rs-docs ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit c43d457
🔍 Latest deploy log https://app.netlify.com/sites/aya-rs-docs/deploys/67a384e2008d360008231e91
😎 Deploy Preview https://deploy-preview-1161--aya-rs-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@mergify mergify bot added aya This is about aya (userspace) test A PR that improves test cases or CI labels Feb 2, 2025
@vadorovsky vadorovsky force-pushed the task-storage-user-space branch from 66bdc42 to d57b707 Compare February 3, 2025 05:04
let store: TaskStorage<_, u32> =
TaskStorage::try_from(ebpf.map_mut("task_storage").unwrap()).unwrap();

let mut child = Command::new("sleep").arg("1").spawn().unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no sleep binary in our test userspace so this will fail.
You might want to consider using fork instead.
https://docs.rs/nix/latest/nix/unistd/fn.fork.html

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I noticed. I'm also trying to figure out other options:

  • using thread::spawn
  • using some other LSM hook and looking up for the pid of the testsuite

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to fork? Can't we just spawn a thread?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't, unfortunately.

Even though in Linux threads are just processes with shared resources, they share PID as well. Threads have the same PID and different TID. Proof:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=d65191a69be10325a543cad9c138e9ea

use std::thread;

fn main() -> anyhow::Result<()> {
    let handles = (0..10).map(|i| thread::spawn(move || {
        let pid = unsafe { libc::getpid() };
        let tid = unsafe { libc::gettid() };
        println!("thread {i}: pid: {pid}, tid: {tid}")
    }));
    
    for handle in handles {
        handle.join().unwrap();
    }
    
    Ok(())
} 
thread 0: pid: 13, tid: 33
thread 1: pid: 13, tid: 34
thread 2: pid: 13, tid: 35
thread 3: pid: 13, tid: 36
thread 4: pid: 13, tid: 37
thread 5: pid: 13, tid: 38
thread 6: pid: 13, tid: 39
thread 7: pid: 13, tid: 40
thread 8: pid: 13, tid: 41
thread 9: pid: 13, tid: 42

The only way to retrieve the entry from a task storage in the user-space is to use the pidfd - a file descriptor corresponding to a process differentiated by PID. There is nothing like that for TID.

I'm glad that you asked that question though, because it made me think of something I didn't consider before. On the eBPF side, my example creates/updates the entry every time we hit security_task_alloc, which happens for threads as well. task_struct should be unique for threads. Is the task storage on eBPF side smart enough to differentiate threads or does it differentiate only processes? 🤔 Also, if it does differentiate threads on eBPF side, it would mean inconsistency between eBPF and user-space. I need to check...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need to differentiate? Just store the thread ID in the map.

Copy link
Member Author

@vadorovsky vadorovsky Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're missing the point how task storage works. You can't just arbitrarily pick the key type.

In the eBPF code, you can only use task_struct as a key. bpf_task_storage_get and bpf_task_storage_delete take a task_struct pointer.

But in the user-space, you have to use the pidfd as a key. Nothing else works. See these examples:

https://elixir.bootlin.com/linux/v6.13.1/source/tools/testing/selftests/bpf/map_tests/task_storage_map.c
https://elixir.bootlin.com/linux/v6.13.1/source/tools/testing/selftests/bpf/prog_tests/task_local_storage.c#L283

In all of them, you need to issue the pidfd_open syscall, providing PID as an argument, and then use the returned file descriptor as the key.

Unfortunately, there is no concept of tidfd or anything which would let you use a TID as a key.

I will try to make it more clear in the docs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't you store something in the map on thread creation and use pidfd of the process to fetch it from the map?

Copy link
Member

@dave-tucker dave-tucker left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! I know you're still working through the integration test failures but adding some review comments too.

impl<T: Borrow<MapData>, V: Pod> TaskStorage<T, V> {
pub(crate) fn new(map: T) -> Result<Self, MapError> {
let data = map.borrow();
check_kv_size::<c_int, V>(data)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other map types use:

Suggested change
check_kv_size::<c_int, V>(data)?;
check_kv_size::<u32, V>(data)?;

If you can't use u32 - although I'm not sure why not - a comment to explain would be helpful.

TaskStorage::<_, u16>::try_from(&map),
Err(MapError::InvalidValueSize {
size: 2,
expected: 4
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming back to earlier comment - if you're expecting c_int to always be a 4 byte value, you may as well use u32.

use libc::pid_t;
use std::os::fd::{AsRawFd, RawFd};
/// A file descriptor of a process.
pub(crate) struct PidFd(RawFd);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub(crate) struct PidFd(RawFd);
pub(crate) struct PidFd(MockableFd);

or OwnedFd. We shouldn't be dealing with RawFd directly.

use crate::sys::{SysResult, Syscall};
use libc::pid_t;
use std::os::fd::{AsRawFd, RawFd};
/// A file descriptor of a process.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should document that this is already in Rust nightly:
https://doc.rust-lang.org/std/os/linux/process/struct.PidFd.html

We'll want to migrate once it stablizes

impl PidFd {
pub(crate) fn open(pid: u32, flags: u32) -> SysResult<Self> {
let pid_fd = pidfd_open(pid, flags)?;
Ok(Self(pid_fd as i32))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per previous comment, you'll need to create an OwnedFd from the syscall.

@vadorovsky vadorovsky force-pushed the task-storage-user-space branch from d57b707 to 7d45628 Compare February 3, 2025 13:29
@tamird tamird requested a review from dave-tucker February 3, 2025 17:14
Copy link
Member

@tamird tamird left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 9 of 10 files at r1, 1 of 1 files at r2, all commit messages.
Reviewable status: all files reviewed, 9 unresolved discussions (waiting on @dave-tucker and @vadorovsky)


aya/src/sys/pid_fd.rs line 1 at r2 (raw file):

use crate::sys::{SysResult, Syscall};

do we really need a file for this? seems like overkill to me


aya/src/sys/pid_fd.rs line 12 at r2 (raw file):

    }
}
impl AsRawFd for PidFd {

this is a smell


aya/src/sys/pid_fd.rs line 17 at r2 (raw file):

    }
}
impl Drop for PidFd {

you'd get this for free if you used OwnedFd or MockableFd.

let store: TaskStorage<_, u32> =
TaskStorage::try_from(ebpf.map_mut("task_storage").unwrap()).unwrap();

let mut child = Command::new("sleep").arg("1").spawn().unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to fork? Can't we just spawn a thread?

@vadorovsky vadorovsky force-pushed the task-storage-user-space branch 2 times, most recently from d6c7825 to 4b526d7 Compare February 5, 2025 15:30
Task storage is a type of map which uses `task_struct` kernel type as
a key. When the task (process) stops, the corresponding entry is
automatically removed.

This change add support only in the user-space and tests the
functionality with a C program.
@vadorovsky vadorovsky force-pushed the task-storage-user-space branch from 4b526d7 to c43d457 Compare February 5, 2025 15:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
aya This is about aya (userspace) test A PR that improves test cases or CI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants