Stateful eBPF

Cross-syscall state tracking

In the previous exercise, we captured all read() calls. But what if we only care about reads from a specific file, like /tmp/password?

The problem: read() operates on file descriptors, not filenames. When a process calls open("/tmp/password"), the kernel returns a file descriptor (fd) - a per-process unique number (like 4). Subsequent read calls use that number: read(4, buf, ...).

By the time we see the read() syscall, we only have access to the fd. The filename is gone.

To filter reads by filename, we need to correlate the open() and read() syscalls: track which fds correspond to /tmp/password at open time, then use that information when intercepting reads.

Correlating open and read

We need to hook four events and pass information between them:

When a process opens /tmp/password:

  1. open entry - We see the pathname and can check if it matches our target
  2. open exit - We get the fd that was assigned to this file

When that same process reads from the fd:

  1. read entry - We see which fd is being read from and the destination buffer
  2. read exit - We see how many bytes were written

The challenge is connecting these dots: how do we know in step 3 that fd 4 is the one we care about from step 1?

Tracking with three maps

We’ll use three maps to pass information through this pipeline.

Temporary marker during open:

At open entry, we mark PIDs that are opening /tmp/password in open_curr_fd_interesting. At exit, we check this mark to know if the returned fd is interesting.

We can safely use PID as the key because the thread is blocked during the syscall - it can’t make another open() call until this one completes.

Persistent tracking of interesting fds:

Once we know that PID 123 got fd 4 for /tmp/password, we need to remember this association. Later, when we see read(4, ...), we can check if (123, 4) is in our map.

This requires our first composite key:

struct pid_fd_key {
    u64 pid;
    u32 fd;
};

struct {
    __uint(type, BPF_MAP_TYPE_HASH);
    __uint(max_entries, 1024);
    __type(key, struct pid_fd_key);
    __type(value, u8);   // marker
} open_interesting_fds SEC(".maps");

We need both PID and FD in the key because a process can have multiple file descriptors open simultaneously. Fd 4 might be /tmp/password while fd 5 is /etc/config.

Temporary storage during read:

Just like in the previous exercise, we’ll define read_curr_fd_buf to save the buffer pointer from read entry to read exit, using PID as the key.

The complete flow:

OpenEnter TPSyscallExit TPstorecheckopen_curr_fdpidvoidUserspaceFDReadEnter TPSyscallExit TPstorecheckFD, bufread_curr_fdpidbuf addrinteresting_fds{pid, fd}voidstorecheck

The challenge

A program opens and reads multiple files. Only /tmp/password contains the password.

Your task

Implement the four eBPF programs to correlate the open and read syscalls. The starter code has the map definitions and TODOs marking where to add your logic.

Quick reference
Get current process and thread ID
Function bpf_get_current_pid_tgid Full documentation
Returns Upper 32 bits are PID, lower 32 bits are TID.
Compare two strings for equality
Function bpf_strncmp Full documentation
Args:
char* bufdynamic buffer to compare
u32 buf_szlength of dynamic buffer
const char* buf2literal string to compare against
Returns 0 if strings match, non-zero if they differ
Insert or update map entry
Function bpf_map_update_elem Full documentation
Args:
void* mappointer to map
const void* keypointer to key
const void* valuepointer to value
u64 flagsBPF_ANY (create or update), BPF_NOEXIST (create only), or BPF_EXIST (update only)
On success, returns 0
On error, returns negative error code
Get value from map by key
Function bpf_map_lookup_elem Full documentation
Args:
void* mappointer to map
const void* keypointer to key
Returns pointer to value if found, NULL otherwise
Remove entry from map
Function bpf_map_delete_elem Full documentation
Args:
void* mappointer to map
const void* keypointer to key to delete
On success, returns 0
On error, returns negative error code if key not found
Run your code to see execution events here