Skip to content

Commit

Permalink
Support BPF_PROG_{QUERY,ATTACH,DETACH}
Browse files Browse the repository at this point in the history
Fixes #3652
  • Loading branch information
multun authored and rocallahan committed Nov 30, 2023
1 parent fc15969 commit 36a71dc
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 1 deletion.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,7 @@ set(BASIC_TESTS
block_open
bpf
bpf_map
bpf_query
brk
brk2
capget
Expand Down
6 changes: 6 additions & 0 deletions src/MemoryRange.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ class MemoryRange {
}
bool contains(remote_ptr<void> p) const { return start_ <= p && p < end_; }

template <typename T> bool contains(remote_ptr<T> ptr, size_t count = 1) const {
auto o_start_ = ptr.template cast<void>();
auto o_end_ = (ptr + count).template cast<void>();
return start_ <= o_start_ && o_end_ <= end_;
}

bool intersects(const MemoryRange& other) const {
remote_ptr<void> s = std::max(start_, other.start_);
remote_ptr<void> e = std::min(end_, other.end_);
Expand Down
25 changes: 24 additions & 1 deletion src/kernel_abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -1760,7 +1760,7 @@ struct BaseArch : public wordsize,
};
__u64 flags;
};
struct {
struct { /* used by BPF_PROG_LOAD */
__u32 prog_type;
__u32 insn_cnt;
ptr64<void> insns;
Expand All @@ -1781,6 +1781,29 @@ struct BaseArch : public wordsize,
aligned_u64 line_info;
__u32 line_info_cnt;
};

struct { /* anonymous struct used by BPF_PROG_QUERY command */
union {
__u32 target_fd; /* target object to query or ... */
__u32 target_ifindex; /* target ifindex */
};
__u32 attach_type;
__u32 query_flags;
__u32 attach_flags;
ptr64<__u32> prog_ids;
union {
__u32 prog_cnt;
__u32 count;
};
__u32 :32;
/* output: per-program attach_flags.
* not allowed to be set during effective query.
*/
ptr64<__u32> prog_attach_flags;
ptr64<__u32> link_ids;
ptr64<__u32> link_attach_flags;
__u64 revision;
} query;
};

struct file_handle {
Expand Down
44 changes: 44 additions & 0 deletions src/record_syscall.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2169,6 +2169,8 @@ static Switchable prepare_bpf(RecordTask* t,
case BPF_MAP_UPDATE_ELEM:
case BPF_MAP_DELETE_ELEM:
case BPF_BTF_LOAD:
case BPF_PROG_DETACH:
case BPF_PROG_ATTACH:
break;
case BPF_OBJ_GET:
return ALLOW_SWITCH;
Expand All @@ -2194,6 +2196,48 @@ static Switchable prepare_bpf(RecordTask* t,
monitor->key_size());
break;
}
case BPF_PROG_QUERY: {
auto attr_size = t->regs().arg3();
auto attr_begin = syscall_state.reg_parameter(2, attr_size, IN_OUT);
auto attr_buf = MemoryRange(attr_begin, attr_size);
auto attrp = attr_begin.cast<typename Arch::bpf_attr>();

// if this assert fails, we should check what fields were added to the
// query ABI, update the structure and track pointers to output arrays
ASSERT(t, attr_size <= sizeof(((typename Arch::bpf_attr*)nullptr)->query));

// if the offset of the prog_cnt is out of the buffer,
// the syscall will fail and we can't track anything
auto prog_cnt_p = REMOTE_PTR_FIELD(attrp, query.prog_cnt);
if (!attr_buf.contains(prog_cnt_p)) {
break;
}
auto prog_cnt = t->read_mem(prog_cnt_p);
auto buf_size = prog_cnt * sizeof(__u32);

// for each output array, only track changes if the field is
// within the bounds of the user provided buffer
auto prog_ids_p = REMOTE_PTR_FIELD(attrp, query.prog_ids);
if (attr_buf.contains(prog_ids_p)) {
syscall_state.mem_ptr_parameter(prog_ids_p, buf_size);
}

auto prog_attach_flags_p = REMOTE_PTR_FIELD(attrp, query.prog_attach_flags);
if (attr_buf.contains(prog_attach_flags_p)) {
syscall_state.mem_ptr_parameter(prog_attach_flags_p, buf_size);
}

auto link_ids_p = REMOTE_PTR_FIELD(attrp, query.link_ids);
if (attr_buf.contains(link_ids_p)) {
syscall_state.mem_ptr_parameter(link_ids_p, buf_size);
}

auto link_attach_flags_p = REMOTE_PTR_FIELD(attrp, query.link_attach_flags);
if (attr_buf.contains(link_attach_flags_p)) {
syscall_state.mem_ptr_parameter(link_attach_flags_p, buf_size);
}
break;
}
default:
syscall_state.expect_errno = EINVAL;
break;
Expand Down
101 changes: 101 additions & 0 deletions src/test/bpf_query.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* -*- Mode: C; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#include "util.h"
#include "nsutils.h"

#include <stdint.h>
#include <sched.h>
#include <linux/bpf.h>


#define MAX_PROG_CNT 1
#define ATTACH_TYPE BPF_FLOW_DISSECTOR
#define PROG_TYPE BPF_PROG_TYPE_FLOW_DISSECTOR


int bpf(int cmd, union bpf_attr *attr, unsigned int size) {
return syscall(__NR_bpf, cmd, attr, size);
}

const struct bpf_insn bpf_program[] = {
// mov r0, 0
{ .code = BPF_ALU | BPF_MOV | BPF_K, .dst_reg = 0, .imm = 0 },
// exit
{ .code = BPF_JMP | BPF_EXIT },
};

int main(void) {
__u32 prog_ids[MAX_PROG_CNT];

if (try_setup_ns(CLONE_NEWNET)) {
atomic_puts("EXIT-SUCCESS");
return 0;
}

int netns_fd = open("/proc/self/ns/net", O_RDONLY);
test_assert(netns_fd > 0);

union bpf_attr query_attr = {
.query = {
.prog_ids = (uintptr_t)&prog_ids,
.target_fd = netns_fd,
},
};


// query cgroups bpf programs. at first, no programs are attached
query_attr.query.prog_cnt = 2;
query_attr.query.attach_type = ATTACH_TYPE;
if (bpf(BPF_PROG_QUERY, &query_attr, sizeof(query_attr.query)) != 0) {
if (errno == ENOSYS) {
atomic_puts("Skipping test because bpf is not supported");
atomic_puts("EXIT-SUCCESS");
return 0;
}
if (errno == EPERM) {
// we have to check for EPERM again as a kernel can be built with
// user namespaces, so unshare can succeed regardless of CAP_SYS_ADMIN
atomic_puts("Skipping test because it requires CAP_SYS_ADMIN");
atomic_puts("EXIT-SUCCESS");
return 0;
}
test_assert(!"bpf(BPF_PROG_QUERY) failed");
}
test_assert(query_attr.query.prog_cnt == 0);

// load and add a program to the cgroup
static char log_buf[4096] = {};
union bpf_attr prog_attr = {
.insn_cnt = 2,
.insns = (uintptr_t)bpf_program,
.license = (uintptr_t)"MIT",
.prog_type = PROG_TYPE,
.expected_attach_type = ATTACH_TYPE,
.log_size = sizeof(log_buf),
.log_buf = (uintptr_t)log_buf,
.log_level = 1,
};
size_t prog_attr_size = offsetof(union bpf_attr, core_relo_rec_size) + sizeof(__u32);

union bpf_attr attach_attr = {
.attach_type = ATTACH_TYPE,
};
size_t attach_attr_size = offsetof(union bpf_attr, replace_bpf_fd) + sizeof(__u32);

int prog = bpf(BPF_PROG_LOAD, &prog_attr, prog_attr_size);
if (prog < 0) {
atomic_puts(log_buf);
test_assert(!"failed to load program");
}
test_assert(prog > 0);
attach_attr.attach_bpf_fd = prog;
test_assert(bpf(BPF_PROG_ATTACH, &attach_attr, attach_attr_size) == 0);

// query again
query_attr.query.prog_cnt = 1;
query_attr.query.attach_type = ATTACH_TYPE;
test_assert(bpf(BPF_PROG_QUERY, &query_attr, sizeof(query_attr.query)) == 0);
test_assert(query_attr.query.prog_cnt == 1); // the kernel sets this field

atomic_puts("EXIT-SUCCESS");
return 0;
}

0 comments on commit 36a71dc

Please sign in to comment.