Merge branch 'support-freplace-prog-from-user-namespace'

Mykyta Yatsenko says:

====================
Support freplace prog from user namespace

From: Mykyta Yatsenko <yatsenko@meta.com>

Freplace programs can't be loaded from user namespace, as
bpf_program__set_attach_target() requires searching for target prog BTF,
which is locked under CAP_SYS_ADMIN.
This patch set enables this use case by:
1. Relaxing capable check in bpf's BPF_BTF_GET_FD_BY_ID, check for CAP_BPF
instead of CAP_SYS_ADMIN, support BPF token in attr argument.
2. Pass BPF token around libbpf from bpf_program__set_attach_target() to
bpf syscall where capable check is.
3. Validate positive/negative scenarios in selftests

This patch set is enabled by the recent libbpf change[1], that
introduced bpf_object__prepare() API. Calling bpf_object__prepare() for
freplace program before bpf_program__set_attach_target() initializes BPF
token, which is then passed to bpf syscall by libbpf.

[1] https://lore.kernel.org/all/20250303135752.158343-1-mykyta.yatsenko5@gmail.com/
====================

Link: https://patch.msgid.link/20250317174039.161275-1-mykyta.yatsenko5@gmail.com
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
This commit is contained in:
Andrii Nakryiko
2025-03-17 13:45:12 -07:00
11 changed files with 160 additions and 17 deletions

View File

@@ -1652,6 +1652,7 @@ union bpf_attr {
};
__u32 next_id;
__u32 open_flags;
__s32 fd_by_id_token_fd;
};
struct { /* anonymous struct used by BPF_OBJ_GET_INFO_BY_FD */

View File

@@ -4732,6 +4732,8 @@ static int bpf_prog_get_info_by_fd(struct file *file,
info.recursion_misses = stats.misses;
info.verified_insns = prog->aux->verified_insns;
if (prog->aux->btf)
info.btf_id = btf_obj_id(prog->aux->btf);
if (!bpf_capable()) {
info.jited_prog_len = 0;
@@ -4878,8 +4880,6 @@ static int bpf_prog_get_info_by_fd(struct file *file,
}
}
if (prog->aux->btf)
info.btf_id = btf_obj_id(prog->aux->btf);
info.attach_btf_id = prog->aux->attach_btf_id;
if (attach_btf)
info.attach_btf_obj_id = btf_obj_id(attach_btf);
@@ -5120,15 +5120,34 @@ static int bpf_btf_load(const union bpf_attr *attr, bpfptr_t uattr, __u32 uattr_
return btf_new_fd(attr, uattr, uattr_size);
}
#define BPF_BTF_GET_FD_BY_ID_LAST_FIELD btf_id
#define BPF_BTF_GET_FD_BY_ID_LAST_FIELD fd_by_id_token_fd
static int bpf_btf_get_fd_by_id(const union bpf_attr *attr)
{
struct bpf_token *token = NULL;
if (CHECK_ATTR(BPF_BTF_GET_FD_BY_ID))
return -EINVAL;
if (!capable(CAP_SYS_ADMIN))
if (attr->open_flags & ~BPF_F_TOKEN_FD)
return -EINVAL;
if (attr->open_flags & BPF_F_TOKEN_FD) {
token = bpf_token_get_from_fd(attr->fd_by_id_token_fd);
if (IS_ERR(token))
return PTR_ERR(token);
if (!bpf_token_allow_cmd(token, BPF_BTF_GET_FD_BY_ID)) {
bpf_token_put(token);
token = NULL;
}
}
if (!bpf_token_capable(token, CAP_SYS_ADMIN)) {
bpf_token_put(token);
return -EPERM;
}
bpf_token_put(token);
return btf_get_fd_by_id(attr->btf_id);
}

View File

@@ -1652,6 +1652,7 @@ union bpf_attr {
};
__u32 next_id;
__u32 open_flags;
__s32 fd_by_id_token_fd;
};
struct { /* anonymous struct used by BPF_OBJ_GET_INFO_BY_FD */

View File

@@ -1097,7 +1097,7 @@ int bpf_map_get_fd_by_id(__u32 id)
int bpf_btf_get_fd_by_id_opts(__u32 id,
const struct bpf_get_fd_by_id_opts *opts)
{
const size_t attr_sz = offsetofend(union bpf_attr, open_flags);
const size_t attr_sz = offsetofend(union bpf_attr, fd_by_id_token_fd);
union bpf_attr attr;
int fd;
@@ -1107,6 +1107,7 @@ int bpf_btf_get_fd_by_id_opts(__u32 id,
memset(&attr, 0, attr_sz);
attr.btf_id = id;
attr.open_flags = OPTS_GET(opts, open_flags, 0);
attr.fd_by_id_token_fd = OPTS_GET(opts, token_fd, 0);
fd = sys_bpf_fd(BPF_BTF_GET_FD_BY_ID, &attr, attr_sz);
return libbpf_err_errno(fd);

View File

@@ -487,9 +487,10 @@ LIBBPF_API int bpf_link_get_next_id(__u32 start_id, __u32 *next_id);
struct bpf_get_fd_by_id_opts {
size_t sz; /* size of this struct for forward/backward compatibility */
__u32 open_flags; /* permissions requested for the operation on fd */
__u32 token_fd;
size_t :0;
};
#define bpf_get_fd_by_id_opts__last_field open_flags
#define bpf_get_fd_by_id_opts__last_field token_fd
LIBBPF_API int bpf_prog_get_fd_by_id(__u32 id);
LIBBPF_API int bpf_prog_get_fd_by_id_opts(__u32 id,

View File

@@ -1619,12 +1619,18 @@ struct btf *btf_get_from_fd(int btf_fd, struct btf *base_btf)
return btf;
}
struct btf *btf__load_from_kernel_by_id_split(__u32 id, struct btf *base_btf)
struct btf *btf_load_from_kernel(__u32 id, struct btf *base_btf, int token_fd)
{
struct btf *btf;
int btf_fd;
LIBBPF_OPTS(bpf_get_fd_by_id_opts, opts);
btf_fd = bpf_btf_get_fd_by_id(id);
if (token_fd) {
opts.open_flags |= BPF_F_TOKEN_FD;
opts.token_fd = token_fd;
}
btf_fd = bpf_btf_get_fd_by_id_opts(id, &opts);
if (btf_fd < 0)
return libbpf_err_ptr(-errno);
@@ -1634,6 +1640,11 @@ struct btf *btf__load_from_kernel_by_id_split(__u32 id, struct btf *base_btf)
return libbpf_ptr(btf);
}
struct btf *btf__load_from_kernel_by_id_split(__u32 id, struct btf *base_btf)
{
return btf_load_from_kernel(id, base_btf, 0);
}
struct btf *btf__load_from_kernel_by_id(__u32 id)
{
return btf__load_from_kernel_by_id_split(id, NULL);

View File

@@ -10024,7 +10024,7 @@ int libbpf_find_vmlinux_btf_id(const char *name,
return libbpf_err(err);
}
static int libbpf_find_prog_btf_id(const char *name, __u32 attach_prog_fd)
static int libbpf_find_prog_btf_id(const char *name, __u32 attach_prog_fd, int token_fd)
{
struct bpf_prog_info info;
__u32 info_len = sizeof(info);
@@ -10044,7 +10044,7 @@ static int libbpf_find_prog_btf_id(const char *name, __u32 attach_prog_fd)
pr_warn("The target program doesn't have BTF\n");
goto out;
}
btf = btf__load_from_kernel_by_id(info.btf_id);
btf = btf_load_from_kernel(info.btf_id, NULL, token_fd);
err = libbpf_get_error(btf);
if (err) {
pr_warn("Failed to get BTF %d of the program: %s\n", info.btf_id, errstr(err));
@@ -10127,7 +10127,7 @@ static int libbpf_find_attach_btf_id(struct bpf_program *prog, const char *attac
pr_warn("prog '%s': attach program FD is not set\n", prog->name);
return -EINVAL;
}
err = libbpf_find_prog_btf_id(attach_name, attach_prog_fd);
err = libbpf_find_prog_btf_id(attach_name, attach_prog_fd, prog->obj->token_fd);
if (err < 0) {
pr_warn("prog '%s': failed to find BPF program (FD %d) BTF ID for '%s': %s\n",
prog->name, attach_prog_fd, attach_name, errstr(err));
@@ -12923,7 +12923,7 @@ struct bpf_link *bpf_program__attach_freplace(const struct bpf_program *prog,
if (target_fd) {
LIBBPF_OPTS(bpf_link_create_opts, target_opts);
btf_id = libbpf_find_prog_btf_id(attach_func_name, target_fd);
btf_id = libbpf_find_prog_btf_id(attach_func_name, target_fd, prog->obj->token_fd);
if (btf_id < 0)
return libbpf_err_ptr(btf_id);
@@ -13744,7 +13744,7 @@ int bpf_program__set_attach_target(struct bpf_program *prog,
if (attach_prog_fd) {
btf_id = libbpf_find_prog_btf_id(attach_func_name,
attach_prog_fd);
attach_prog_fd, prog->obj->token_fd);
if (btf_id < 0)
return libbpf_err(btf_id);
} else {

View File

@@ -409,6 +409,7 @@ int libbpf__load_raw_btf(const char *raw_types, size_t types_len,
int btf_load_into_kernel(struct btf *btf,
char *log_buf, size_t log_sz, __u32 log_level,
int token_fd);
struct btf *btf_load_from_kernel(__u32 id, struct btf *base_btf, int token_fd);
struct btf *btf_get_from_fd(int btf_fd, struct btf *base_btf);
void btf_get_kernel_prefix_kind(enum bpf_attach_type attach_type,

View File

@@ -19,6 +19,7 @@
#include "priv_prog.skel.h"
#include "dummy_st_ops_success.skel.h"
#include "token_lsm.skel.h"
#include "priv_freplace_prog.skel.h"
static inline int sys_mount(const char *dev_name, const char *dir_name,
const char *type, unsigned long flags,
@@ -788,6 +789,84 @@ static int userns_obj_priv_prog(int mnt_fd, struct token_lsm *lsm_skel)
return 0;
}
static int userns_obj_priv_freplace_setup(int mnt_fd, struct priv_freplace_prog **fr_skel,
struct priv_prog **skel, int *tgt_fd)
{
LIBBPF_OPTS(bpf_object_open_opts, opts);
int err;
char buf[256];
/* use bpf_token_path to provide BPF FS path */
snprintf(buf, sizeof(buf), "/proc/self/fd/%d", mnt_fd);
opts.bpf_token_path = buf;
*skel = priv_prog__open_opts(&opts);
if (!ASSERT_OK_PTR(*skel, "priv_prog__open_opts"))
return -EINVAL;
err = priv_prog__load(*skel);
if (!ASSERT_OK(err, "priv_prog__load"))
return -EINVAL;
*fr_skel = priv_freplace_prog__open_opts(&opts);
if (!ASSERT_OK_PTR(*skel, "priv_freplace_prog__open_opts"))
return -EINVAL;
*tgt_fd = bpf_program__fd((*skel)->progs.xdp_prog1);
return 0;
}
/* Verify that freplace works from user namespace, because bpf token is loaded
* in bpf_object__prepare
*/
static int userns_obj_priv_freplace_prog(int mnt_fd, struct token_lsm *lsm_skel)
{
struct priv_freplace_prog *fr_skel = NULL;
struct priv_prog *skel = NULL;
int err, tgt_fd;
err = userns_obj_priv_freplace_setup(mnt_fd, &fr_skel, &skel, &tgt_fd);
if (!ASSERT_OK(err, "setup"))
goto out;
err = bpf_object__prepare(fr_skel->obj);
if (!ASSERT_OK(err, "freplace__prepare"))
goto out;
err = bpf_program__set_attach_target(fr_skel->progs.new_xdp_prog2, tgt_fd, "xdp_prog1");
if (!ASSERT_OK(err, "set_attach_target"))
goto out;
err = priv_freplace_prog__load(fr_skel);
ASSERT_OK(err, "priv_freplace_prog__load");
out:
priv_freplace_prog__destroy(fr_skel);
priv_prog__destroy(skel);
return err;
}
/* Verify that replace fails to set attach target from user namespace without bpf token */
static int userns_obj_priv_freplace_prog_fail(int mnt_fd, struct token_lsm *lsm_skel)
{
struct priv_freplace_prog *fr_skel = NULL;
struct priv_prog *skel = NULL;
int err, tgt_fd;
err = userns_obj_priv_freplace_setup(mnt_fd, &fr_skel, &skel, &tgt_fd);
if (!ASSERT_OK(err, "setup"))
goto out;
err = bpf_program__set_attach_target(fr_skel->progs.new_xdp_prog2, tgt_fd, "xdp_prog1");
if (ASSERT_ERR(err, "attach fails"))
err = 0;
else
err = -EINVAL;
out:
priv_freplace_prog__destroy(fr_skel);
priv_prog__destroy(skel);
return err;
}
/* this test is called with BPF FS that doesn't delegate BPF_BTF_LOAD command,
* which should cause struct_ops application to fail, as BTF won't be uploaded
* into the kernel, even if STRUCT_OPS programs themselves are allowed
@@ -1004,12 +1083,28 @@ void test_token(void)
if (test__start_subtest("obj_priv_prog")) {
struct bpffs_opts opts = {
.cmds = bit(BPF_PROG_LOAD),
.progs = bit(BPF_PROG_TYPE_KPROBE),
.progs = bit(BPF_PROG_TYPE_XDP),
.attachs = ~0ULL,
};
subtest_userns(&opts, userns_obj_priv_prog);
}
if (test__start_subtest("obj_priv_freplace_prog")) {
struct bpffs_opts opts = {
.cmds = bit(BPF_BTF_LOAD) | bit(BPF_PROG_LOAD) | bit(BPF_BTF_GET_FD_BY_ID),
.progs = bit(BPF_PROG_TYPE_EXT) | bit(BPF_PROG_TYPE_XDP),
.attachs = ~0ULL,
};
subtest_userns(&opts, userns_obj_priv_freplace_prog);
}
if (test__start_subtest("obj_priv_freplace_prog_fail")) {
struct bpffs_opts opts = {
.cmds = bit(BPF_BTF_LOAD) | bit(BPF_PROG_LOAD) | bit(BPF_BTF_GET_FD_BY_ID),
.progs = bit(BPF_PROG_TYPE_EXT) | bit(BPF_PROG_TYPE_XDP),
.attachs = ~0ULL,
};
subtest_userns(&opts, userns_obj_priv_freplace_prog_fail);
}
if (test__start_subtest("obj_priv_btf_fail")) {
struct bpffs_opts opts = {
/* disallow BTF loading */

View File

@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
char _license[] SEC("license") = "GPL";
SEC("freplace/xdp_prog1")
int new_xdp_prog2(struct xdp_md *xd)
{
return XDP_DROP;
}

View File

@@ -6,8 +6,8 @@
char _license[] SEC("license") = "GPL";
SEC("kprobe")
int kprobe_prog(void *ctx)
SEC("xdp")
int xdp_prog1(struct xdp_md *xdp)
{
return 1;
return XDP_DROP;
}