Skip to content
Open
Show file tree
Hide file tree
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
58 changes: 58 additions & 0 deletions fs/bpf_fs_kfuncs.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <linux/file.h>
#include <linux/kernfs.h>
#include <linux/mm.h>
#include <linux/namei.h>
#include <linux/xattr.h>

__bpf_kfunc_start_defs();
Expand Down Expand Up @@ -96,6 +97,61 @@ __bpf_kfunc int bpf_path_d_path(const struct path *path, char *buf, size_t buf__
return len;
}

/**
* bpf_kern_path - resolve a pathname to a struct path
* @pathname__str: pathname to resolve
* @flags: lookup flags (e.g., LOOKUP_FOLLOW)
*
* Resolve the pathname for the supplied *pathname__str* and return a pointer
* to a struct path. This is a wrapper around kern_path() that allocates and
* returns a struct path pointer on success.
*
* The returned struct path pointer must be released using bpf_path_put().
* Failing to call bpf_path_put() on the returned struct path pointer will
* result in the BPF program being rejected by the BPF verifier.
*
* This BPF kfunc may only be called from BPF LSM programs.
*
* Return: A pointer to an allocated struct path on success, NULL on error.
*/
__bpf_kfunc struct path *bpf_kern_path(const char *pathname__str, unsigned int flags)
{
struct path *path;
int ret;

path = kmalloc(sizeof(*path), GFP_KERNEL);
if (!path)
return NULL;

ret = kern_path(pathname__str, flags, path);
if (ret) {
kfree(path);
return NULL;
}

return path;
}

/**
* bpf_path_put - release a struct path reference
* @path: struct path pointer to release
*
* Release the struct path pointer that was acquired by bpf_kern_path().
* This BPF kfunc calls path_put() on the supplied *path* and then frees
* the allocated memory.
*
* Only struct path pointers acquired by bpf_kern_path() may be passed to
* this BPF kfunc. Attempting to pass any other pointer will result in the
* BPF program being rejected by the BPF verifier.
*
* This BPF kfunc may only be called from BPF LSM programs.
*/
__bpf_kfunc void bpf_path_put(struct path *path)
{
path_put(path);
kfree(path);
}

static bool match_security_bpf_prefix(const char *name__str)
{
return !strncmp(name__str, XATTR_NAME_BPF_LSM, XATTR_NAME_BPF_LSM_LEN);
Expand Down Expand Up @@ -363,6 +419,8 @@ BTF_ID_FLAGS(func, bpf_get_task_exe_file,
KF_ACQUIRE | KF_TRUSTED_ARGS | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_put_file, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_path_d_path, KF_TRUSTED_ARGS)
BTF_ID_FLAGS(func, bpf_kern_path, KF_TRUSTED_ARGS | KF_ACQUIRE | KF_SLEEPABLE | KF_RET_NULL)
BTF_ID_FLAGS(func, bpf_path_put, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_get_dentry_xattr, KF_SLEEPABLE | KF_TRUSTED_ARGS)
BTF_ID_FLAGS(func, bpf_get_file_xattr, KF_SLEEPABLE | KF_TRUSTED_ARGS)
BTF_ID_FLAGS(func, bpf_set_dentry_xattr, KF_SLEEPABLE | KF_TRUSTED_ARGS)
Expand Down
1 change: 1 addition & 0 deletions include/linux/btf.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ struct btf *btf_base_btf(const struct btf *btf);
bool btf_type_is_i32(const struct btf_type *t);
bool btf_type_is_i64(const struct btf_type *t);
bool btf_type_is_primitive(const struct btf_type *t);
bool btf_type_is_const_char_ptr(const struct btf *btf, const struct btf_type *t);
bool btf_member_is_reg_int(const struct btf *btf, const struct btf_type *s,
const struct btf_member *m,
u32 expected_offset, u32 expected_size);
Expand Down
33 changes: 33 additions & 0 deletions kernel/bpf/btf.c
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,25 @@ bool btf_type_is_primitive(const struct btf_type *t)
btf_is_any_enum(t);
}

bool btf_type_is_const_char_ptr(const struct btf *btf, const struct btf_type *t)
{
const char *tname;

/* The type chain has to be PTR->CONST->CHAR */
if (BTF_INFO_KIND(t->info) != BTF_KIND_PTR)
return false;

t = btf_type_by_id(btf, t->type);
if (BTF_INFO_KIND(t->info) != BTF_KIND_CONST)
return false;

t = btf_type_by_id(btf, t->type);
tname = btf_name_by_offset(btf, t->name_off);
if (tname && strcmp(tname, "char") == 0)
return true;
return false;
}

/*
* Check that given struct member is a regular int with expected
* offset and size.
Expand Down Expand Up @@ -6746,6 +6765,20 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
/* Default prog with MAX_BPF_FUNC_REG_ARGS args */
return true;
t = btf_type_by_id(btf, args[arg].type);

/*
* For const string, we need to match "const char *"
* exactly. Therefore, do the check before the skipping
* modifiers.
*/
if (btf_type_is_const_char_ptr(btf, t)) {
info->reg_type = PTR_TO_BTF_ID;
if (prog_args_trusted(prog))
info->reg_type |= PTR_TRUSTED;
info->btf = btf;
info->btf_id = args[arg].type;
return true;
}
}

/* skip modifiers */
Expand Down
51 changes: 39 additions & 12 deletions kernel/bpf/verifier.c
Original file line number Diff line number Diff line change
Expand Up @@ -9597,18 +9597,19 @@ static enum bpf_dynptr_type dynptr_get_type(struct bpf_verifier_env *env,
return state->stack[spi].spilled_ptr.dynptr.type;
}

static int check_reg_const_str(struct bpf_verifier_env *env,
struct bpf_reg_state *reg, u32 regno)
/*
* Check for const string saved in a bpf map. The caller is responsible
* to check reg->type == PTR_TO_MAP_VALUE.
*/
static int check_reg_const_str_in_map(struct bpf_verifier_env *env,
struct bpf_reg_state *reg, u32 regno)
{
struct bpf_map *map = reg->map_ptr;
int err;
int map_off;
u64 map_addr;
char *str_ptr;

if (reg->type != PTR_TO_MAP_VALUE)
return -EINVAL;

if (!bpf_map_is_rdonly(map)) {
verbose(env, "R%d does not point to a readonly map'\n", regno);
return -EACCES;
Expand Down Expand Up @@ -9645,6 +9646,26 @@ static int check_reg_const_str(struct bpf_verifier_env *env,
return 0;
}

/* Check for const string passed in as input to the bpf program. */
static int check_reg_const_str_arg(struct bpf_reg_state *reg)
{
const struct btf *btf;
const struct btf_type *t;
const char *tname;

if (base_type(reg->type) != PTR_TO_BTF_ID)
return -EINVAL;

btf = reg->btf;
t = btf_type_by_id(btf, reg->btf_id);
if (!t)
return -EINVAL;

if (btf_type_is_const_char_ptr(btf, t))
return 0;
return -EINVAL;
}

/* Returns constant key value in `value` if possible, else negative error */
static int get_constant_map_key(struct bpf_verifier_env *env,
struct bpf_reg_state *key,
Expand Down Expand Up @@ -9963,7 +9984,9 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
break;
case ARG_PTR_TO_CONST_STR:
{
err = check_reg_const_str(env, reg, regno);
if (reg->type != PTR_TO_MAP_VALUE)
return -EINVAL;
err = check_reg_const_str_in_map(env, reg, regno);
if (err)
return err;
break;
Expand Down Expand Up @@ -13625,13 +13648,17 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_
meta->arg_btf_id = reg->btf_id;
break;
case KF_ARG_PTR_TO_CONST_STR:
if (reg->type != PTR_TO_MAP_VALUE) {
verbose(env, "arg#%d doesn't point to a const string\n", i);
return -EINVAL;
if (reg->type == PTR_TO_MAP_VALUE) {
ret = check_reg_const_str_in_map(env, reg, regno);
if (ret)
return ret;
} else {
ret = check_reg_const_str_arg(reg);
if (ret) {
verbose(env, "arg#%d doesn't point to a const string\n", i);
return ret;
}
}
ret = check_reg_const_str(env, reg, regno);
if (ret)
return ret;
break;
case KF_ARG_PTR_TO_WORKQUEUE:
if (reg->type != PTR_TO_MAP_VALUE) {
Expand Down
4 changes: 4 additions & 0 deletions tools/testing/selftests/bpf/bpf_experimental.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ extern void bpf_put_file(struct file *file) __ksym;
*/
extern int bpf_path_d_path(const struct path *path, char *buf, size_t buf__sz) __ksym;

extern struct path *bpf_kern_path(const char *pathname, unsigned int flags) __ksym;
extern void bpf_path_put(struct path *path) __ksym;
extern int bpf_path_d_path(const struct path *path, char *buf, size_t buf__sz) __ksym;

/* This macro must be used to mark the exception callback corresponding to the
* main program. For example:
*
Expand Down
82 changes: 82 additions & 0 deletions tools/testing/selftests/bpf/prog_tests/kern_path.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. */

#include <test_progs.h>
#include <sys/mount.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include "test_kern_path.skel.h"
#include "verifier_kern_path.skel.h"
#include "verifier_kern_path_fail.skel.h"

static void __test_kern_path(void (*trigger)(void))
{
struct test_kern_path *skel;
int err;

skel = test_kern_path__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_kern_path__open_and_load"))
return;

skel->bss->monitored_pid = getpid();

err = test_kern_path__attach(skel);
if (!ASSERT_OK(err, "test_kern_path__attach"))
goto cleanup;

trigger();

/* Verify the bpf_path_d_path worked */
ASSERT_GT(skel->bss->path_len, 0, "path_len > 0");

cleanup:
test_kern_path__destroy(skel);
}

static void trigger_file_open(void)
{
int fd;

fd = open("/dev/null", O_RDONLY);
if (!ASSERT_OK_FD(fd, "open /dev/null"))
return;
close(fd);
}

static void trigger_sb_mount(void)
{
char tmpdir[] = "/tmp/bpf_kern_path_test_XXXXXX";
int err;

if (!ASSERT_OK_PTR(mkdtemp(tmpdir), "mkdtemp"))
return;

err = mount("/tmp", tmpdir, NULL, MS_BIND, NULL);
if (!ASSERT_OK(err, "bind mount"))
goto rmdir;

umount(tmpdir);
rmdir:
rmdir(tmpdir);
}

void test_kern_path(void)
{
if (test__start_subtest("file_open"))
__test_kern_path(trigger_file_open);

if (test__start_subtest("sb_mount"))
__test_kern_path(trigger_sb_mount);
}

void test_verifier_kern_path(void)
{
RUN_TESTS(verifier_kern_path);
}

void test_verifier_kern_path_fail(void)
{
RUN_TESTS(verifier_kern_path_fail);
}
56 changes: 56 additions & 0 deletions tools/testing/selftests/bpf/progs/test_kern_path.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. */

#include "vmlinux.h"
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
#include "bpf_experimental.h"

#define MAX_PATH_LEN 256

char buf[MAX_PATH_LEN];
int path_len = 0;
u32 monitored_pid = 0;

SEC("lsm.s/file_open")
int BPF_PROG(test_kern_path_basic, struct file *file)
{
struct path *p;
int ret;

if (bpf_get_current_pid_tgid() >> 32 != monitored_pid)
return 0;

p = bpf_kern_path("/proc/self/exe", 0);
if (p) {
ret = bpf_path_d_path(p, buf, MAX_PATH_LEN);
if (ret > 0)
path_len = ret;
bpf_path_put(p);
}

return 0;
}

SEC("lsm.s/sb_mount")
int BPF_PROG(test_kern_path_from_sb_mount, const char *dev_name, const struct path *path,
const char *type, unsigned long flags, void *data)
{
struct path *p;
int ret;

if (bpf_get_current_pid_tgid() >> 32 != monitored_pid)
return 0;

p = bpf_kern_path(dev_name, 0);
if (p) {
ret = bpf_path_d_path(p, buf, MAX_PATH_LEN);
if (ret > 0)
path_len = ret;
bpf_path_put(p);
}

return 0;
}

char _license[] SEC("license") = "GPL";
Loading
Loading