Kernel probes

Reading TCP packets

In the previous exercise, we used kprobes to track TCP connection completion. Now we’ll intercept HTTP data as it’s sent.

We could attach to the sendto() syscall, but that also intercepts vsock transfers, which includes DEBUG_* calls, making for a pretty awkward experience.

Also, it wouldn’t fit the kprobe chapter at all!

Instead, let’s attach to tcp_sendmsg() which is only called for TCP traffic.

Reading data from tcp_sendmsg

We can find the signature for tcp_sendmsg in net/ipv4/tcp.c, which is:

int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size);

And we can access the userspace buffer from the msg parameter, but it’s a bit involved.

A simplified definition of struct msghdr:

struct msghdr {
    struct iov_iter msg_iter;  // Iterator containing buffer info
    // ...
};

struct iov_iter {
    union {
        struct iovec __ubuf_iovec;  // Userspace buffer base/len
        // ...
    };
};

struct iovec {
    void *iov_base;
    u64   iov_len;
};

So we’ll read the userspace buffer whose address is stored in msg->msg_iter->__ubuf_iovec.iov_base.

Remember that we can’t dereference pointers directly, so you’ll manually need to read through each indirection level. Keep in mind the address space for each pointer.

HTTP request structure

HTTP requests are text-based:

POST /api/data HTTP/1.1
Host: example.com
Authorization: Bearer secret_token_here
Content-Type: application/json

Headers are separated by \r\n (CRLF). Each header is Name: Value.

In the past, operating on any string-based protocol in eBPF was extremely tedious. Lucky you, since Jun 2025, we can do some string operations directly.

We’ll be using two functions in this exercise:

  • bpf_strstr(char *haystack, char *needle): Find substring, returns index or negative on error
  • bpf_strchr(char* str, char c): Find character, returns index or negative on error

For example,

int auth_pos = bpf_strstr(buf, "Authorization: Bearer ");

auth_pos will contain the position of the needle, if found.

With a little bit of math, you can find where the token is supposed to start, and read from there to the end of the line (marked by \r).

To get to the end, you can use

int token_pos = bpf_strchr(&buf[token_start], '\r');

which will give you the position for the first \r, starting from token_ptr.

The challenge

A program makes an HTTP request with an Authorization: Bearer <token> header.

Your task

Submit the token from the header.

Quick reference
Extract the Nth parameter from kprobe context
Function PT_REGS_PARM{1-8} Full documentation
Args:
struct pt_regs* ctxkprobe context containing CPU registers
Returns value of Nth function parameter
Read string from user space into kernel buffer
Function bpf_probe_read_user_str Full documentation
Args:
void* dstkernel buffer to read into
u32 sizemaximum bytes to read
const void* srcuser space pointer to string
On success, returns number of bytes read (including null terminator)
On error, returns negative error code
Read bytes from user space into kernel buffer
Function bpf_probe_read_user Full documentation
Args:
void* dstkernel buffer to read into
u32 sizebytes to read
const void* srcuser space pointer
On success, returns 0
On error, returns negative error code
Read bytes from kernel space into kernel buffer
Function bpf_probe_read_kernel Full documentation
Args:
void* dstkernel buffer to read into
u32 sizebytes to read
const void* srckernel space pointer
On success, returns 0
On error, returns negative error code
Read string from kernel space into kernel buffer
Function bpf_probe_read_kernel_str Full documentation
Args:
void* dstkernel buffer to read into
u32 sizemaximum bytes to read
const void* srckernel space pointer to string
On success, returns number of bytes read (including null terminator)
On error, returns negative error code
Run your code to see execution events here