Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 44 additions & 19 deletions get_stack_offset.bpf.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,53 @@
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <linux/types.h>
#include <stdbool.h>

#include "get_stack_offset.h"

/* ------------------------------------------------------------------
* Universal helpers for locating task_pt_regs() on kernels both
* with and without FRED stack padding (0 or 16 bytes).
* Works on every x86‑64 kernel from 4.14 → latest.
* ------------------------------------------------------------------ */

/* Sign‑bit test that is true for any canonical kernel pointer */
#define IS_KERNEL_PTR(ptr) ((long)(ptr) < 0)

static const __u8 fred_pads[] = { 0, 16 }; /* legacy & FRED paddings */

static __always_inline bool regs_match(struct pt_regs *regs)
{
__u64 si, dx;

/* Ignore faults: the pointer may be garbage */
(void)bpf_probe_read(&si, sizeof(si), &regs->si);
(void)bpf_probe_read(&dx, sizeof(dx), &regs->dx);

return si == SI_VALUE && dx == DX_VALUE;
}

/*
* Attempt to interpret @base_ptr as the bottom of a kernel stack and return
* byte‑offset within task_struct if its pt_regs frame matches our sentinel.
* Returns ≥ 0 when found, or ‑1 if not found for any known padding.
*/
static __always_inline int find_regs(const __u64 *base_ptr, __u32 index)
{
#pragma unroll
for (int p = 0; p < (int)(sizeof(fred_pads) / sizeof(fred_pads[0])); p++) {
struct pt_regs *r = (struct pt_regs *)((unsigned long)base_ptr +
THREAD_SIZE - fred_pads[p]) - 1;
if (regs_match(r))
return index * sizeof(__u64);
}
return -1;
}

#define NUM_TAIL_CALLS 26
#define TOTAL_ITERS (MAX_TASK_STRUCT / sizeof(__u64))
#define ITERS_PER_PROG (TOTAL_ITERS / NUM_TAIL_CALLS)

// this is correct for x86_64 unless 5-level page tables are enabled (in which case, the address
// is lower, but I don't think we'll encounter it any time soon)
// this is 0xffff800000000000, see "Canonical form addresses" in https://en.wikipedia.org/wiki/X86-64#Virtual_address_space_details
#define MIN_CANONICAL_KERNEL_ADDRESS (~1UL - ((1UL << 47) - 2))

// key - zero
// value - struct output
struct {
Expand Down Expand Up @@ -117,28 +151,19 @@ int do_write(struct pt_regs *ctx)
goto out;
}

// make sure it's canonical (and in kernel space), otherwise we might get a WARN_ONCE
// (see ex_handler_uaccess() in the kernel, happens on 5.4).
if ((unsigned long)maybe_stack <= MIN_CANONICAL_KERNEL_ADDRESS) {
/* Quickly discard non‑canonical or user addresses */
if (!IS_KERNEL_PTR(maybe_stack)) {
continue;
}

// implementing task_pt_regs() for x86_64 here.
struct pt_regs *regs = (struct pt_regs*)((unsigned long)maybe_stack + THREAD_SIZE - TOP_OF_KERNEL_STACK_PADDING) - 1;

__u64 pt_regs_si;
__u64 pt_regs_dx;
// ignore errors, "pointer" may not be a pointer at all.
(void)bpf_probe_read(&pt_regs_si, sizeof(pt_regs_si), &regs->si);
(void)bpf_probe_read(&pt_regs_dx, sizeof(pt_regs_dx), &regs->dx);

if (pt_regs_si== SI_VALUE && pt_regs_dx == DX_VALUE) {
int off = find_regs(maybe_stack, base + i);
if (off >= 0) {
if (out.status != STATUS_NOTFOUND) {
out.status = STATUS_DUP;
goto out;
}

out.offset = (base + i) * sizeof(__u64);
out.offset = off;
out.status = STATUS_OK;
// continue searching, check for dups
}
Expand Down