Merge branch 'misc-bug-fixes-part-4'

Kumar Kartikeya Dwivedi says:

====================
Misc bug fixes - part 4

A set of miscellaneous fixes for bugs reported by Nicholas, and GPT-5.6
when analyzing those fixes, batched together again. See commit logs for
details. Related rhtab fixes from Yuan Chen and Nuoqi Gui have been
folded into the series.
====================

Link: https://patch.msgid.link/20260904104203.345917-1-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2026-09-04 12:24:25 -07:00
commit 519f858317
9 changed files with 525 additions and 21 deletions

View File

@ -2868,16 +2868,6 @@ static int rhtab_map_alloc_check(union bpf_attr *attr)
return htab_map_alloc_check(attr);
}
static void rhtab_check_and_free_fields(struct bpf_rhtab *rhtab,
struct rhtab_elem *elem)
{
if (IS_ERR_OR_NULL(rhtab->map.record))
return;
bpf_obj_free_fields(rhtab->map.record,
rhtab_elem_value(elem, rhtab->map.key_size));
}
static void rhtab_mem_dtor(void *obj, void *ctx)
{
struct htab_btf_record *hrec = ctx;
@ -2967,8 +2957,8 @@ static int rhtab_delete_elem(struct bpf_rhtab *rhtab, struct rhtab_elem *elem, v
rhtab_read_elem_value(&rhtab->map, copy, elem, flags);
check_and_init_map_value(&rhtab->map, copy);
}
/* Release internal structs: kptr, bpf_timer, task_work, wq */
rhtab_check_and_free_fields(rhtab, elem);
bpf_obj_cancel_fields(&rhtab->map,
rhtab_elem_value(elem, rhtab->map.key_size));
bpf_mem_cache_free_rcu(&rhtab->ma, elem);
return 0;
}
@ -3009,7 +2999,6 @@ static int rhtab_map_lookup_and_delete_elem(struct bpf_map *map, void *key, void
static long rhtab_map_update_existing(struct bpf_map *map, struct rhtab_elem *elem, void *value,
u64 map_flags)
{
struct bpf_rhtab *rhtab = container_of(map, struct bpf_rhtab, map);
void *old_val = rhtab_elem_value(elem, map->key_size);
if (map_flags & BPF_NOEXIST)
@ -3029,7 +3018,7 @@ static long rhtab_map_update_existing(struct bpf_map *map, struct rhtab_elem *el
* kptrs/etc. still sit in the slot. Cancel them after the copy
* to match arraymap's update semantics.
*/
rhtab_check_and_free_fields(rhtab, elem);
bpf_obj_cancel_fields(map, old_val);
return 0;
}
@ -3070,7 +3059,6 @@ static long rhtab_map_update_elem(struct bpf_map *map, void *key, void *value, u
memcpy(elem->data, key, map->key_size);
copy_map_value(map, rhtab_elem_value(elem, map->key_size), value);
check_and_init_map_value(map, rhtab_elem_value(elem, map->key_size));
/* Prevent deadlock for NMI programs attempting to take bucket lock */
bpf_disable_instrumentation();

View File

@ -4708,8 +4708,15 @@ static int check_map_kptr_access(struct bpf_verifier_env *env,
return ret;
} else if (class == BPF_STX) {
val_reg = reg_state(env, value_regno);
if (!bpf_register_is_null(val_reg) &&
map_kptr_match_type(env, kptr_field, val_reg, value_regno))
if (bpf_register_is_null(val_reg)) {
/*
* This store is valid only because the scalar is known to be
* zero. Mark it precise so another scalar cannot be pruned
* against this state.
*/
return mark_chain_precision(env, value_regno);
}
if (map_kptr_match_type(env, kptr_field, val_reg, value_regno))
return -EACCES;
} else if (class == BPF_ST) {
if (insn->imm) {
@ -10011,10 +10018,12 @@ int map_set_for_each_callback_args(struct bpf_verifier_env *env,
callee->regs[BPF_REG_2].type = PTR_TO_MAP_KEY;
__mark_reg_known_zero(&callee->regs[BPF_REG_2]);
callee->regs[BPF_REG_2].map_ptr = caller->regs[BPF_REG_1].map_ptr;
callee->regs[BPF_REG_2].map_uid = caller->regs[BPF_REG_1].map_uid;
callee->regs[BPF_REG_3].type = PTR_TO_MAP_VALUE;
__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
callee->regs[BPF_REG_3].map_ptr = caller->regs[BPF_REG_1].map_ptr;
callee->regs[BPF_REG_3].map_uid = caller->regs[BPF_REG_1].map_uid;
/* pointer to stack or null */
callee->regs[BPF_REG_4] = caller->regs[BPF_REG_3];
@ -10092,6 +10101,7 @@ static int set_timer_callback_state(struct bpf_verifier_env *env,
int insn_idx)
{
struct bpf_map *map_ptr = caller->regs[BPF_REG_1].map_ptr;
u32 map_uid = caller->regs[BPF_REG_1].map_uid;
/* bpf_timer_set_callback(struct bpf_timer *timer, void *callback_fn);
* callback_fn(struct bpf_map *map, void *key, void *value);
@ -10099,14 +10109,17 @@ static int set_timer_callback_state(struct bpf_verifier_env *env,
callee->regs[BPF_REG_1].type = CONST_PTR_TO_MAP;
__mark_reg_known_zero(&callee->regs[BPF_REG_1]);
callee->regs[BPF_REG_1].map_ptr = map_ptr;
callee->regs[BPF_REG_1].map_uid = map_uid;
callee->regs[BPF_REG_2].type = PTR_TO_MAP_KEY;
__mark_reg_known_zero(&callee->regs[BPF_REG_2]);
callee->regs[BPF_REG_2].map_ptr = map_ptr;
callee->regs[BPF_REG_2].map_uid = map_uid;
callee->regs[BPF_REG_3].type = PTR_TO_MAP_VALUE;
__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
callee->regs[BPF_REG_3].map_ptr = map_ptr;
callee->regs[BPF_REG_3].map_uid = map_uid;
/* unused */
bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]);
@ -10206,6 +10219,7 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env,
int insn_idx)
{
struct bpf_map *map_ptr = caller->regs[BPF_REG_3].map_ptr;
u32 map_uid = caller->regs[BPF_REG_3].map_uid;
/*
* callback_fn(struct bpf_map *map, void *key, void *value);
@ -10213,14 +10227,17 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env,
callee->regs[BPF_REG_1].type = CONST_PTR_TO_MAP;
__mark_reg_known_zero(&callee->regs[BPF_REG_1]);
callee->regs[BPF_REG_1].map_ptr = map_ptr;
callee->regs[BPF_REG_1].map_uid = map_uid;
callee->regs[BPF_REG_2].type = PTR_TO_MAP_KEY;
__mark_reg_known_zero(&callee->regs[BPF_REG_2]);
callee->regs[BPF_REG_2].map_ptr = map_ptr;
callee->regs[BPF_REG_2].map_uid = map_uid;
callee->regs[BPF_REG_3].type = PTR_TO_MAP_VALUE;
__mark_reg_known_zero(&callee->regs[BPF_REG_3]);
callee->regs[BPF_REG_3].map_ptr = map_ptr;
callee->regs[BPF_REG_3].map_uid = map_uid;
/* unused */
bpf_mark_reg_not_init(env, &callee->regs[BPF_REG_4]);

View File

@ -172,6 +172,12 @@ void test_rhash(void)
if (test__start_subtest("test_rhash_delete_nonexistent"))
rhash_run("test_rhash_delete_nonexistent");
if (test__start_subtest("test_rhash_kptr_update"))
rhash_run("test_rhash_kptr_update");
if (test__start_subtest("test_rhash_kptr_delete"))
rhash_run("test_rhash_kptr_delete");
if (test__start_subtest("test_rhash_map_extra_presize"))
rhash_map_extra_presize();

View File

@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <sched.h>
#include <test_progs.h>
#include "rhash_timer.skel.h"
#define MAX_ATTEMPTS 256
#define RCU_SYNC_INTERVAL 64
static int pin_to_first_cpu(cpu_set_t *old_mask)
{
cpu_set_t new_mask;
int cpu;
if (sched_getaffinity(0, sizeof(*old_mask), old_mask))
return -errno;
for (cpu = 0; cpu < CPU_SETSIZE; cpu++)
if (CPU_ISSET(cpu, old_mask))
break;
if (cpu == CPU_SETSIZE)
return -EINVAL;
CPU_ZERO(&new_mask);
CPU_SET(cpu, &new_mask);
if (sched_setaffinity(0, sizeof(new_mask), &new_mask))
return -errno;
return 0;
}
static int update_timer_map(int map_fd, __u64 key)
{
__u64 value[3] = {};
return bpf_map_update_elem(map_fd, &key, value, BPF_NOEXIST);
}
static int run_prog(int prog_fd, struct bpf_test_run_opts *opts)
{
int err;
err = bpf_prog_test_run_opts(prog_fd, opts);
if (err)
return err;
return opts->retval;
}
void test_rhash_timer(void)
{
LIBBPF_OPTS(bpf_test_run_opts, opts);
struct rhash_timer *skel = NULL;
cpu_set_t old_mask;
int map_fd = -1, arm_fd, cancel_fd;
bool affinity_set = false;
__u64 key = 1;
int attempt, err;
err = pin_to_first_cpu(&old_mask);
if (!ASSERT_OK(err, "pin_to_first_cpu"))
return;
affinity_set = true;
skel = rhash_timer__open_and_load();
if (!ASSERT_OK_PTR(skel, "open_and_load"))
goto out;
map_fd = bpf_map__fd(skel->maps.timer_map);
if (!ASSERT_GE(map_fd, 0, "timer_map fd"))
goto out;
arm_fd = bpf_program__fd(skel->progs.arm_deleted_timer);
if (!ASSERT_GE(arm_fd, 0, "arm_deleted_timer fd"))
goto out;
cancel_fd = bpf_program__fd(skel->progs.cancel_recycled_timer);
if (!ASSERT_GE(cancel_fd, 0, "cancel_recycled_timer fd"))
goto out;
err = update_timer_map(map_fd, key);
if (!ASSERT_OK(err, "seed_timer_map"))
goto out;
for (attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
err = run_prog(arm_fd, &opts);
if (err) {
ASSERT_OK(err, "arm_deleted_timer");
goto out;
}
if (skel->bss->armed != attempt + 1) {
ASSERT_EQ(skel->bss->armed, attempt + 1, "armed");
goto out;
}
if (skel->bss->timer_init_err) {
ASSERT_OK(skel->bss->timer_init_err, "timer_init_err");
goto out;
}
if (skel->bss->timer_set_callback_err) {
ASSERT_OK(skel->bss->timer_set_callback_err,
"timer_set_callback_err");
goto out;
}
if (skel->bss->timer_start_err) {
ASSERT_OK(skel->bss->timer_start_err, "timer_start_err");
goto out;
}
if ((attempt + 1) % RCU_SYNC_INTERVAL == 0) {
err = kern_sync_rcu();
if (err) {
ASSERT_OK(err, "kern_sync_rcu");
goto out;
}
}
err = update_timer_map(map_fd, ++key);
if (err) {
ASSERT_OK(err, "replace_timer_map");
goto out;
}
err = run_prog(cancel_fd, &opts);
if (err) {
ASSERT_OK(err, "cancel_recycled_timer");
goto out;
}
if (skel->bss->timer_cancel_err) {
ASSERT_OK(skel->bss->timer_cancel_err, "timer_cancel_err");
goto out;
}
if (skel->bss->cancelled)
break;
}
ASSERT_GT(skel->bss->cancelled, 0, "preserved timer");
out:
if (map_fd >= 0)
bpf_map_delete_elem(map_fd, &key);
rhash_timer__destroy(skel);
if (affinity_set)
sched_setaffinity(0, sizeof(old_mask), &old_mask);
}

View File

@ -59,10 +59,32 @@ void serial_test_timer_mim(void)
int err;
old_print_fn = libbpf_set_print(NULL);
timer_reject_skel = timer_mim_reject__open_and_load();
libbpf_set_print(old_print_fn);
if (!ASSERT_ERR_PTR(timer_reject_skel, "timer_reject_skel_load"))
timer_reject_skel = timer_mim_reject__open();
if (!ASSERT_OK_PTR(timer_reject_skel, "timer_reject_skel_open"))
goto cleanup;
bpf_program__set_autoload(timer_reject_skel->progs.test1, true);
err = timer_mim_reject__load(timer_reject_skel);
ASSERT_ERR(err, "timer_reject_skel_load");
timer_mim_reject__destroy(timer_reject_skel);
timer_reject_skel = timer_mim_reject__open();
if (!ASSERT_OK_PTR(timer_reject_skel, "callback_reject_skel_open"))
goto cleanup;
bpf_program__set_autoload(timer_reject_skel->progs.callback_map_uid_mismatch, true);
err = timer_mim_reject__load(timer_reject_skel);
ASSERT_ERR(err, "callback_reject_skel_load");
timer_mim_reject__destroy(timer_reject_skel);
timer_reject_skel = timer_mim_reject__open();
if (!ASSERT_OK_PTR(timer_reject_skel, "callback_accept_skel_open"))
goto cleanup;
bpf_program__set_autoload(timer_reject_skel->progs.callback_map_uid_match, true);
err = timer_mim_reject__load(timer_reject_skel);
if (!ASSERT_OK(err, "callback_accept_skel_load"))
goto cleanup;
timer_mim_reject__destroy(timer_reject_skel);
timer_reject_skel = NULL;
libbpf_set_print(old_print_fn);
timer_skel = timer_mim__open_and_load();
if (!timer_skel && errno == EOPNOTSUPP) {
@ -75,6 +97,7 @@ void serial_test_timer_mim(void)
err = timer_mim(timer_skel);
ASSERT_OK(err, "timer_mim");
cleanup:
libbpf_set_print(old_print_fn);
timer_mim__destroy(timer_skel);
timer_mim_reject__destroy(timer_reject_skel);
}

View File

@ -409,4 +409,41 @@ int reject_scalar_store_to_kptr(struct __sk_buff *ctx)
return 0;
}
SEC("?tc")
__description("reject imprecise scalar store to kptr after state pruning")
__failure __msg("invalid kptr access, R7 type=scalar")
__naked void reject_imprecise_scalar_store_to_kptr(void)
{
asm volatile (
"r0 = 0;"
"*(u32 *)(r10 - 4) = r0;"
"r2 = r10;"
"r2 += -4;"
"r1 = %[array_map] ll;"
"call %[bpf_map_lookup_elem];"
"if r0 == 0 goto l2_%=;"
"r6 = r0;"
"r9 = *(u64 *)(r6 + 0);"
"if r9 != 0 goto l0_%=;"
"r7 = 0;"
".rept 10;"
"r5 = 1;"
".endr;"
"goto l1_%=;"
"l0_%=:"
"r7 = 0x4141414141414141 ll;"
".rept 10;"
"r5 = 1;"
".endr;"
"l1_%=:"
"*(u64 *)(r6 + 8) = r7;"
"l2_%=:"
"r0 = 0;"
"exit;"
:
: __imm(bpf_map_lookup_elem),
__imm_addr(array_map)
: __clobber_all);
}
char _license[] SEC("license") = "GPL";

View File

@ -19,6 +19,11 @@ struct elem {
int val;
};
struct special_elem {
struct task_struct __kptr *task;
int val;
};
struct {
__uint(type, BPF_MAP_TYPE_RHASH);
__uint(map_flags, BPF_F_NO_PREALLOC);
@ -27,6 +32,17 @@ struct {
__type(value, struct elem);
} rhmap SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_RHASH);
__uint(map_flags, BPF_F_NO_PREALLOC);
__uint(max_entries, 1);
__type(key, int);
__type(value, struct special_elem);
} special_fields SEC(".maps");
extern struct task_struct *bpf_task_acquire(struct task_struct *p) __ksym;
extern void bpf_task_release(struct task_struct *p) __ksym;
SEC("syscall")
int test_rhash_lookup_update(void *ctx)
{
@ -246,3 +262,99 @@ int test_rhash_delete_nonexistent(void *ctx)
err = 0;
return 0;
}
SEC("syscall")
int test_rhash_kptr_update(void *ctx)
{
struct special_elem val1 = { .val = 1 };
struct special_elem val2 = { .val = 2 };
struct task_struct *task, *old;
struct special_elem *elem;
int key = 0;
err = 1;
if (bpf_map_update_elem(&special_fields, &key, &val1, BPF_NOEXIST))
return 1;
err = 2;
elem = bpf_map_lookup_elem(&special_fields, &key);
if (!elem)
return 2;
err = 3;
task = bpf_task_acquire(bpf_get_current_task_btf());
if (!task)
return 3;
err = 4;
old = bpf_kptr_xchg(&elem->task, task);
if (old) {
bpf_task_release(old);
return 4;
}
err = 5;
if (bpf_map_update_elem(&special_fields, &key, &val2, BPF_EXIST))
return 5;
err = 6;
elem = bpf_map_lookup_elem(&special_fields, &key);
if (!elem || elem->val != 2)
return 6;
err = 7;
old = bpf_kptr_xchg(&elem->task, NULL);
if (!old)
return 7;
bpf_task_release(old);
err = 8;
if (bpf_map_delete_elem(&special_fields, &key))
return 8;
err = 0;
return 0;
}
SEC("syscall")
int test_rhash_kptr_delete(void *ctx)
{
struct special_elem val = {};
struct task_struct *task, *old;
struct special_elem *elem;
int key = 0;
err = 1;
if (bpf_map_update_elem(&special_fields, &key, &val, BPF_NOEXIST))
return 1;
err = 2;
elem = bpf_map_lookup_elem(&special_fields, &key);
if (!elem)
return 2;
err = 3;
task = bpf_task_acquire(bpf_get_current_task_btf());
if (!task)
return 3;
err = 4;
old = bpf_kptr_xchg(&elem->task, task);
if (old) {
bpf_task_release(old);
return 4;
}
err = 5;
if (bpf_map_delete_elem(&special_fields, &key))
return 5;
err = 6;
old = bpf_kptr_xchg(&elem->task, NULL);
if (!old)
return 6;
bpf_task_release(old);
err = 0;
return 0;
}

View File

@ -0,0 +1,98 @@
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include <errno.h>
#include <bpf/bpf_helpers.h>
#define CLOCK_MONOTONIC 1
#define TIMER_NSEC (60ULL * 1000 * 1000 * 1000)
struct timer_value {
struct bpf_timer timer;
u64 data;
};
struct {
__uint(type, BPF_MAP_TYPE_RHASH);
__uint(map_flags, BPF_F_NO_PREALLOC);
__uint(max_entries, 1);
__type(key, u64);
__type(value, struct timer_value);
} timer_map SEC(".maps");
u64 armed;
u64 cancelled;
long timer_init_err;
long timer_set_callback_err;
long timer_start_err;
long timer_cancel_err;
static int timer_cb(void *map, u64 *key, struct timer_value *value)
{
return 0;
}
static long arm_timer_cb(struct bpf_map *map, u64 *key,
struct timer_value *value, void *ctx)
{
u64 key_copy = *key;
long err;
err = bpf_map_delete_elem(map, &key_copy);
if (err)
return 1;
err = bpf_timer_init(&value->timer, map, CLOCK_MONOTONIC);
if (err) {
timer_init_err = err;
return 1;
}
err = bpf_timer_set_callback(&value->timer, timer_cb);
if (err) {
timer_set_callback_err = err;
return 1;
}
err = bpf_timer_start(&value->timer, TIMER_NSEC, BPF_F_TIMER_CPU_PIN);
if (err) {
timer_start_err = err;
return 1;
}
__sync_fetch_and_add(&armed, 1);
return 1;
}
static long cancel_timer_cb(struct bpf_map *map, u64 *key,
struct timer_value *value, void *ctx)
{
long err;
err = bpf_timer_cancel(&value->timer);
if (err == -EINVAL)
return 1;
if (err < 0) {
timer_cancel_err = err;
return 1;
}
__sync_fetch_and_add(&cancelled, 1);
return 1;
}
SEC("syscall")
int arm_deleted_timer(void *ctx)
{
bpf_for_each_map_elem(&timer_map, arm_timer_cb, NULL, 0);
return 0;
}
SEC("syscall")
int cancel_recycled_timer(void *ctx)
{
bpf_for_each_map_elem(&timer_map, cancel_timer_cb, NULL, 0);
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@ -43,7 +43,7 @@ static int timer_cb(void *map, int *key, struct hmap_elem *val)
return 0;
}
SEC("fentry/bpf_fentry_test1")
SEC("?fentry/bpf_fentry_test1")
int BPF_PROG(test1, int a)
{
struct hmap_elem init = {};
@ -72,3 +72,85 @@ int BPF_PROG(test1, int a)
err |= 8;
return 0;
}
struct callback_ctx {
void *map;
};
static int mismatch_iter_cb(void *map, int *key, struct hmap_elem *val, struct callback_ctx *ctx)
{
bpf_timer_init(&val->timer, ctx->map, CLOCK_MONOTONIC);
return 0;
}
static int timer_mismatch_cb(void *map, int *key, struct hmap_elem *val)
{
struct callback_ctx ctx = { .map = map };
struct bpf_map *inner_map2;
int array_key2 = ARRAY_KEY2;
inner_map2 = bpf_map_lookup_elem(&outer_arr, &array_key2);
if (!inner_map2)
return 0;
bpf_for_each_map_elem(inner_map2, mismatch_iter_cb, &ctx, 0);
return 0;
}
static int match_iter_cb(void *map, int *key, struct hmap_elem *val, struct callback_ctx *ctx)
{
bpf_timer_init(&val->timer, map, CLOCK_MONOTONIC);
return 0;
}
static int timer_match_cb(void *map, int *key, struct hmap_elem *val)
{
struct callback_ctx ctx = {};
struct bpf_map *inner_map2;
int array_key2 = ARRAY_KEY2;
inner_map2 = bpf_map_lookup_elem(&outer_arr, &array_key2);
if (!inner_map2)
return 0;
bpf_for_each_map_elem(inner_map2, match_iter_cb, &ctx, 0);
return 0;
}
SEC("?fentry/bpf_fentry_test1")
int BPF_PROG(callback_map_uid_mismatch, int a)
{
struct hmap_elem *val;
struct bpf_map *inner_map;
int array_key = ARRAY_KEY;
int hash_key = HASH_KEY;
inner_map = bpf_map_lookup_elem(&outer_arr, &array_key);
if (!inner_map)
return 0;
val = bpf_map_lookup_elem(inner_map, &hash_key);
if (!val)
return 0;
bpf_timer_init(&val->timer, inner_map, CLOCK_MONOTONIC);
bpf_timer_set_callback(&val->timer, timer_mismatch_cb);
return 0;
}
SEC("?fentry/bpf_fentry_test1")
int BPF_PROG(callback_map_uid_match, int a)
{
struct hmap_elem *val;
struct bpf_map *inner_map;
int array_key = ARRAY_KEY;
int hash_key = HASH_KEY;
inner_map = bpf_map_lookup_elem(&outer_arr, &array_key);
if (!inner_map)
return 0;
val = bpf_map_lookup_elem(inner_map, &hash_key);
if (!val)
return 0;
bpf_timer_init(&val->timer, inner_map, CLOCK_MONOTONIC);
bpf_timer_set_callback(&val->timer, timer_match_cb);
return 0;
}