mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-29 22:10:51 -04:00
Refactor object relationship tracking in the verifier and fix a dynptr
use-after-free bug where file/skb dynptrs are not invalidated when the
parent referenced object is freed.
Add parent_id to bpf_reg_state to precisely track child-parent
relationships. A child object's parent_id points to the parent object's
id. This replaces the PTR_TO_MEM-specific dynptr_id.
Remove ref_obj_id from bpf_reg_state by folding its role into the
existing id field. Previously, id tracked pointer identity for null
checking while ref_obj_id tracked the owning reference for lifetime
management. These are now unified: acquire helpers and kfuncs set id
to the acquired reference id, and release paths use id directly.
Add reg_is_referenced() which checks if a register is referenced by
looking up its id in the reference array. This replaces all former
ref_obj_id checks.
For release_reference(), invalidating an object now also invalidates
all descendants by traversing the object tree. This is done using
stack-based DFS to avoid recursive call chains of release_reference() ->
unmark_stack_slots_dynptr() -> release_reference(). Referenced objects
encountered during tree traversal are reported as leaked references.
Add parent_id to bpf_reference_state to enable hierarchical reference
tracking. When acquiring a reference, a parent_id can be specified to
link the new reference to an existing one (e.g., referenced dynptrs
acquire a reference with parent_id linking to the parent object's
reference).
Pointer casting:
For pointer casting helpers (bpf_sk_fullsock, bpf_tcp_sock), instead of
propagating ref_obj_id, the cast result reuses the same reference id as
the source pointer. Since the cast may return NULL for a non-NULL input,
the NULL case is explored as a separate verifier branch. This allows
releasing any of the original or cast pointers to invalidate all others.
Referenced dynptrs:
When constructing a referenced dynptr, acquire a intermediate reference
with parent_id linking to the parent referenced object. The dynptr and
all clones share the same parent_id (pointing to the intermediate ref)
but get unique ids for independent slice tracking. Releasing a
referenced dynptr releases the parent reference, which in turn
invalidates all clones and their derived slices.
Owning to non-owning reference conversion:
After converting owning to non-owning by clearing id (e.g.,
object(id=1) -> object(id=0)), the verifier releases the reference
state via release_reference_nomark().
Note that the error message "reference has not been acquired before" in
the helper and kfunc release paths is removed. This message was already
unreachable. The verifier only calls release_reference() after
confirming the reference is valid, so the condition could never trigger
in practice.
Fixes: 870c28588a ("bpf: net_sched: Add basic bpf qdisc kfuncs")
Signed-off-by: Amery Hung <ameryhung@gmail.com>
Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Link: https://lore.kernel.org/r/20260529014936.2811085-6-ameryhung@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
130 lines
2.9 KiB
C
130 lines
2.9 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Copyright (c) 2023 Meta Platforms, Inc. and affiliates. */
|
|
|
|
#include "vmlinux.h"
|
|
#include <bpf/bpf_helpers.h>
|
|
#include "bpf_misc.h"
|
|
|
|
struct bpf_iter_testmod_seq {
|
|
u64 :64;
|
|
u64 :64;
|
|
};
|
|
|
|
extern int bpf_iter_testmod_seq_new(struct bpf_iter_testmod_seq *it, s64 value, int cnt) __ksym;
|
|
extern s64 *bpf_iter_testmod_seq_next(struct bpf_iter_testmod_seq *it) __ksym;
|
|
extern s64 bpf_iter_testmod_seq_value(int blah, struct bpf_iter_testmod_seq *it) __ksym;
|
|
extern void bpf_iter_testmod_seq_destroy(struct bpf_iter_testmod_seq *it) __ksym;
|
|
|
|
const volatile __s64 exp_empty = 0 + 1;
|
|
__s64 res_empty;
|
|
|
|
SEC("raw_tp/sys_enter")
|
|
__success __log_level(2)
|
|
__msg("fp-16=iter_testmod_seq(id=1,state=active,depth=0)")
|
|
__msg("fp-16=iter_testmod_seq(id=1,state=drained,depth=0)")
|
|
__msg("call bpf_iter_testmod_seq_destroy")
|
|
int testmod_seq_empty(const void *ctx)
|
|
{
|
|
__s64 sum = 0, *i;
|
|
|
|
bpf_for_each(testmod_seq, i, 1000, 0) sum += *i;
|
|
res_empty = 1 + sum;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const volatile __s64 exp_full = 1000000;
|
|
__s64 res_full;
|
|
|
|
SEC("raw_tp/sys_enter")
|
|
__success __log_level(2)
|
|
__msg("fp-16=iter_testmod_seq(id=1,state=active,depth=0)")
|
|
__msg("fp-16=iter_testmod_seq(id=1,state=drained,depth=0)")
|
|
__msg("call bpf_iter_testmod_seq_destroy")
|
|
int testmod_seq_full(const void *ctx)
|
|
{
|
|
__s64 sum = 0, *i;
|
|
|
|
bpf_for_each(testmod_seq, i, 1000, 1000) sum += *i;
|
|
res_full = sum;
|
|
|
|
return 0;
|
|
}
|
|
|
|
const volatile __s64 exp_truncated = 10 * 1000000;
|
|
__s64 res_truncated;
|
|
|
|
static volatile int zero = 0;
|
|
|
|
SEC("raw_tp/sys_enter")
|
|
__success __log_level(2)
|
|
__msg("fp-16=iter_testmod_seq(id=1,state=active,depth=0)")
|
|
__msg("fp-16=iter_testmod_seq(id=1,state=drained,depth=0)")
|
|
__msg("call bpf_iter_testmod_seq_destroy")
|
|
int testmod_seq_truncated(const void *ctx)
|
|
{
|
|
__s64 sum = 0, *i;
|
|
int cnt = zero;
|
|
|
|
bpf_for_each(testmod_seq, i, 10, 2000000) {
|
|
sum += *i;
|
|
cnt++;
|
|
if (cnt >= 1000000)
|
|
break;
|
|
}
|
|
res_truncated = sum;
|
|
|
|
return 0;
|
|
}
|
|
|
|
SEC("?raw_tp")
|
|
__failure
|
|
__msg("expected an initialized iter_testmod_seq as R2")
|
|
int testmod_seq_getter_before_bad(const void *ctx)
|
|
{
|
|
struct bpf_iter_testmod_seq it;
|
|
|
|
return bpf_iter_testmod_seq_value(0, &it);
|
|
}
|
|
|
|
SEC("?raw_tp")
|
|
__failure
|
|
__msg("expected an initialized iter_testmod_seq as R2")
|
|
int testmod_seq_getter_after_bad(const void *ctx)
|
|
{
|
|
struct bpf_iter_testmod_seq it;
|
|
s64 sum = 0, *v;
|
|
|
|
bpf_iter_testmod_seq_new(&it, 100, 100);
|
|
|
|
while ((v = bpf_iter_testmod_seq_next(&it))) {
|
|
sum += *v;
|
|
}
|
|
|
|
bpf_iter_testmod_seq_destroy(&it);
|
|
|
|
return sum + bpf_iter_testmod_seq_value(0, &it);
|
|
}
|
|
|
|
SEC("?socket")
|
|
__success __retval(1000000)
|
|
int testmod_seq_getter_good(const void *ctx)
|
|
{
|
|
struct bpf_iter_testmod_seq it;
|
|
s64 sum = 0, *v;
|
|
|
|
bpf_iter_testmod_seq_new(&it, 100, 100);
|
|
|
|
while ((v = bpf_iter_testmod_seq_next(&it))) {
|
|
sum += *v;
|
|
}
|
|
|
|
sum *= bpf_iter_testmod_seq_value(0, &it);
|
|
|
|
bpf_iter_testmod_seq_destroy(&it);
|
|
|
|
return sum;
|
|
}
|
|
|
|
char _license[] SEC("license") = "GPL";
|