Merge branch 'allow-referenced-dynptr-to-be-overwritten-when-siblings-exists'

Amery Hung says:

====================
Allow referenced dynptr to be overwritten when siblings exists

The patchset conditionally allow a referenced dynptr to be overwritten
when its siblings (original dynptr or dynptr clone) exist. Do it before
the verifier relation tracking refactor to mimimize verifier changes at
a time.
====================

Link: https://patch.msgid.link/20260406150548.1354271-1-ameryhung@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2026-04-07 18:20:49 -07:00
commit 5c662b1c17
2 changed files with 136 additions and 2 deletions

View File

@ -936,8 +936,27 @@ static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env,
spi = spi + 1;
if (dynptr_type_refcounted(state->stack[spi].spilled_ptr.dynptr.type)) {
verbose(env, "cannot overwrite referenced dynptr\n");
return -EINVAL;
int ref_obj_id = state->stack[spi].spilled_ptr.ref_obj_id;
int ref_cnt = 0;
/*
* A referenced dynptr can be overwritten only if there is at
* least one other dynptr sharing the same ref_obj_id,
* ensuring the reference can still be properly released.
*/
for (i = 0; i < state->allocated_stack / BPF_REG_SIZE; i++) {
if (state->stack[i].slot_type[0] != STACK_DYNPTR)
continue;
if (!state->stack[i].spilled_ptr.dynptr.first_slot)
continue;
if (state->stack[i].spilled_ptr.ref_obj_id == ref_obj_id)
ref_cnt++;
}
if (ref_cnt <= 1) {
verbose(env, "cannot overwrite referenced dynptr\n");
return -EINVAL;
}
}
mark_stack_slot_scratched(env, spi);

View File

@ -1993,3 +1993,118 @@ int test_dynptr_reg_type(void *ctx)
global_call_bpf_dynptr((const struct bpf_dynptr *)current);
return 0;
}
/* Overwriting a referenced dynptr is allowed if a clone still holds the ref */
SEC("?raw_tp")
__success
int dynptr_overwrite_ref_with_clone(void *ctx)
{
struct bpf_dynptr ptr, clone;
bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr);
bpf_dynptr_clone(&ptr, &clone);
/* Overwrite the original - clone still holds the ref */
*(volatile __u8 *)&ptr = 0;
bpf_ringbuf_discard_dynptr(&clone, 0);
return 0;
}
/* Overwriting the last referenced dynptr should still be rejected */
SEC("?raw_tp")
__failure __msg("cannot overwrite referenced dynptr")
int dynptr_overwrite_ref_last_clone(void *ctx)
{
struct bpf_dynptr ptr, clone;
bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr);
bpf_dynptr_clone(&ptr, &clone);
/* Overwrite the original - clone still holds the ref, OK */
*(volatile __u8 *)&ptr = 0;
/* Overwrite the last holder - this should fail */
*(volatile __u8 *)&clone = 0;
return 0;
}
/* Overwriting a clone should be allowed if the original still holds the ref */
SEC("?raw_tp")
__success
int dynptr_overwrite_clone_with_original(void *ctx)
{
struct bpf_dynptr ptr, clone;
bpf_ringbuf_reserve_dynptr(&ringbuf, 64, 0, &ptr);
bpf_dynptr_clone(&ptr, &clone);
/* Overwrite the clone - original still holds the ref */
*(volatile __u8 *)&clone = 0;
bpf_ringbuf_discard_dynptr(&ptr, 0);
return 0;
}
/* Data slices from the destroyed dynptr should be invalidated */
SEC("?raw_tp")
__failure __msg("invalid mem access 'scalar'")
int dynptr_overwrite_ref_invalidate_slice(void *ctx)
{
struct bpf_dynptr ptr, clone;
int *data;
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
data = bpf_dynptr_data(&ptr, 0, sizeof(val));
if (!data)
return 0;
bpf_dynptr_clone(&ptr, &clone);
/* Overwrite the original - clone holds the ref */
*(volatile __u8 *)&ptr = 0;
/* data was from the original dynptr, should be invalid now */
*data = 123;
return 0;
}
/*
* Data slices from a dynptr clone should remain valid after
* overwriting the original dynptr
*/
SEC("?raw_tp")
__success
int dynptr_overwrite_ref_clone_slice_valid(void *ctx)
{
struct bpf_dynptr ptr, clone;
int *data;
bpf_ringbuf_reserve_dynptr(&ringbuf, val, 0, &ptr);
bpf_dynptr_clone(&ptr, &clone);
data = bpf_dynptr_data(&clone, 0, sizeof(val));
if (!data) {
bpf_ringbuf_discard_dynptr(&clone, 0);
return 0;
}
/* Overwrite the original - clone holds the ref */
*(volatile __u8 *)&ptr = 0;
/* data is from the clone, should still be valid */
*data = 123;
bpf_ringbuf_discard_dynptr(&clone, 0);
return 0;
}