Merge branch 'refactor-verifier-object-relationship-tracking'

Amery Hung says:

====================
Refactor verifier object relationship tracking

Hi all,

This patchset cleans up dynptr handling, refactors object relationship
tracking in the verifier by introducing parent_id and folding ref_obj_id
into id, and fixes dynptr use-after-free bugs where file/skb dynptrs
are not invalidated when the parent referenced object is freed.

* Motivation *

In BPF qdisc programs, an skb can be freed through kfuncs. However,
since dynptr does not track the parent referenced object (e.g., skb),
the verifier does not invalidate the dynptr after the skb is freed,
resulting in use-after-free. The same issue also affects file dynptr.

The figure below shows the current state of object tracking. The
verifier tracks objects using three fields: id for nullness tracking,
ref_obj_id for lifetime tracking, and dynptr_id for tracking the parent
dynptr of a slice (PTR_TO_MEM only). While dynptr_id links slices to
their parent dynptr, there is no field that links a dynptr back to its
parent skb. When the skb is freed via release_reference(ref_obj_id=1),
only objects with ref_obj_id=1 are invalidated. Since skb dynptr is
non-referenced (ref_obj_id=0), the dynptr and its derived slices remain
accessible.

Current: object (id, ref_obj_id, dynptr_id)
  id         = unique id of the object (for nullness tracking)
  ref_obj_id = id of the referenced object (for lifetime tracking)
  dynptr_id  = id of the parent dynptr (only for PTR_TO_MEM slices)

                      skb (0,1,0)
                             ^^
                          ! No link from dynptr to skb !
                             |+------------------------------+
                             |           bpf_dynptr_clone    |
                 dynptr A (2,0,0)                dynptr C (4,0,0)
                           ^                               ^
        bpf_dynptr_slice   |                               |
                           |                               |
              slice B (3,0,2)                 slice D (5,0,4)

* Why not simply use ref_obj_id to track the parent? *

A natural first approach is to link dynptr to its parent by sharing
the parent's ref_obj_id and propagating it to slices. Now, releasing
the skb via release_reference(ref_obj_id=1) correctly invalidates all
derived objects.

Attempted fix: share parent's ref_obj_id

                      skb (0,1,0)
                             ^^
                             ||
                             |+------------------------------+
                             |           bpf_dynptr_clone    |
                 dynptr A (2,1,0)                dynptr C (4,1,0)
                           ^                               ^
        bpf_dynptr_slice   |                               |
                           |                               |
              slice B (3,1,2)                 slice D (5,1,4)

However, this approach does not generalize to all dynptr types.
Referenced dynptrs such as file dynptr acquire their own ref_obj_id to
track the dynptr's lifetime. Since ref_obj_id is already used for the
dynptr's own reference, it cannot also be used to point to the parent
file object. While it is possible to add specialized handling for
individual dynptr types [0], it adds complexity and does not generalize.

An alternative approach is to avoid introducing a new field and instead
repurpose ref_obj_id as parent_id by folding lifetime tracking into id
[1]. In this design, each object is represented as (id, ref_obj_id)
where id is used for both nullness and lifetime tracking, and ref_obj_id
tracks the parent object's id.

Attempted: object (id, ref_obj_id)
  id         = id of the object (for nullness and lifetime tracking)
  ref_obj_id = id of the parent object
  '          = id is referenced

                        skb (1',0)
                             ^^
                             ||
        bpf_dynptr_from_skb  |+------------------------------+
                             |      bpf_dynptr_clone(A, C)   |
                 dynptr A (2,1')                 dynptr C (4,1')
                           ^                               ^
        bpf_dynptr_slice   |                               |
                           |                               |
                slice B (3,2)                   slice D (5,4)

However, this design cannot express the relationship between referenced
socket pointers and their casted counterparts. After pointer casting,
the original and casted pointers need the same lifetime (same ref_obj_id
in the current design) but different nullness (different id). The casted
pointer may be NULL even if the original is valid. With id serving as
the only field for both nullness and lifetime, and ref_obj_id repurposed
as parent, there is no way to express "different identity, same
lifetime."

Referenced socket pointer (expressed using current design):

                                C = ptr_casting_function(A)
                ptr A (1,1,0)                     ptr C (2,1,0)
                         ^                                 ^
                         |                                 |
                        ptr C may be NULL even if ptr A is valid
                        but they have the same lifetime

* New Design: parent_id with branch splitting and intermediate reference *

The patchset folds ref_obj_id into id and adds parent_id to
bpf_reg_state (patch 5). A child object's parent_id points to the
parent object's id. This replaces the PTR_TO_MEM-specific dynptr_id.
Whether a register is referenced is determined by checking if its id
appears in the reference array via reg_is_referenced() rather than
reading a dedicated ref_obj_id field.

Pointer casting:

The challenge with pointer casting is that a cast result may be NULL
even when the source is valid, requiring distinct identity but shared
lifetime. This is solved using branch splitting: when a helper like
bpf_sk_fullsock() is called with a referenced pointer, the verifier
pushes an explicit NULL branch and assigns the cast result the same id
as the source. 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,
while avoiding the need for a separate tracking mechanism.

Referenced dynptrs:

The challenge with referenced dynptrs is that clones of a referenced
dynptr have the same lifetime but different identities. When a
referenced dynptr is overwritten, only slices derived from it will be
invalidated. To solve this, the verifier creates an intermediate
reference. This reference serves as a shared lifetime anchor for the
dynptr and all its clones. All clones share the same parent_id but get
unique ids for independent slice tracking. Releasing a referenced dynptr
releases the intermediate reference, which in turn invalidates all
clones and their derived slices. If the parent object is released while
the intermediate reference still exists, it is reported as a leaked
reference.

Release cascading:

When releasing an object, release_reference() performs a stack-based DFS
to invalidate all descendants. It walks the object tree via parent_id
links, invalidating registers and dynptr stack slots. Child references
encountered during traversal are reported as leaked references.

parent_id is also added to bpf_reference_state to enable intermediate
reference. When acquiring a reference, a parent_id can be specified to
link the new reference to an existing one (e.g., file dynptr's
intermediate reference has parent_id linking to the file's reference).

Final: object (id, parent_id)
  id        = unique id of the object (for nullness and lifetime
              tracking)
  parent_id = id of the parent object (for object relationship
              tracking)
  I         = intermediate reference serving as lifetime anchor in
              acquired_refs
  '         = id is referenced (appears in reference array)

                          skb (1',0)
                               ^^
                               ||
          bpf_dynptr_from_skb  |+------------------------------+
                               |      bpf_dynptr_clone(A, C)   |
                   dynptr A (2,1')                 dynptr C (4,1')
                             ^                               ^
          bpf_dynptr_slice   |                               |
                             |                               |
                  slice B (3,2)                   slice D (5,4)

* Preserving reg->id after null-check *

For parent_id tracking to work, child objects need to refer to the
parent's id. This requires two preparatory changes: assigning reg->id
when reading referenced kptrs from program context (patch 3), and
preserving reg->id of pointer objects after null-check (patch 4).
Previously, null-check would clear reg->id, making it impossible for
children to reference the parent afterward. The latter causes a slight
increase in verified states for some programs. One selftest object
sees +19 states (+5.01%). For Meta BPF objects, the increase is
also minor, with the largest being +34 states (+3.63%).

* Object relationship in different scenarios (for reference) *

The figures below show how the final design handles all four
combinations of referenced/non-referenced dynptr with
referenced/non-referenced parent.

(1) Non-referenced dynptr with referenced parent (e.g., skb in Qdisc):

                          skb (1',0)
                               ^^
                               ||
          bpf_dynptr_from_skb  |+------------------------------+
                               |      bpf_dynptr_clone(A, C)   |
                   dynptr A (2,1')                 dynptr C (4,1')

                         dynptr A and C live independently

(2) Non-referenced dynptr with non-referenced parent (e.g., skb in TC,
    always valid):

      bpf_dynptr_from_skb
                                  bpf_dynptr_clone(A, C)
             dynptr A (1,0)                    dynptr C (2,0)

                         dynptr A and C live independently

(3) Referenced dynptr with referenced parent:

                     file (1',0)
                           ^
     bpf_dynptr_from_file  |
                     I (2',1')  <-- intermediate reference
                        ^^
                        ||
                        |+-------------------------------+
                        |       bpf_dynptr_clone(A, C)   |
            dynptr A (3,2')                  dynptr C (4,2')

                        dynptr A and C have the same lifetime

  Releasing either dynptr releases I, invalidating both.
  Releasing file (1') detects I as a leaked reference.

(4) Referenced dynptr with non-referenced parent:

 bpf_ringbuf_reserve_dynptr
                     I (1',0)  <-- intermediate reference
                        ^^
                        ||
                        |+--------------------------------+
                        |       bpf_dynptr_clone(A, C)    |
            dynptr A (2,1')                   dynptr C (3,1')

                      dynptr A and C have the same lifetime

[0] https://lore.kernel.org/bpf/20250414161443.1146103-2-memxor@gmail.com/
[1] https://github.com/ameryhung/bpf/commits/obj_relationship_v2_no_parent_id/

Changelog:

v5 -> v6
  - Squash "bpf: Fold ref_obj_id into id and introduce virtual references"
    (v5 patch 9) into "bpf: Refactor object relationship tracking and
    fix dynptr UAF bug" (now patch 5). ref_obj_id is removed in the same
    patch that introduces parent_id, eliminating the intermediate state
    where both coexist (Eduard)
  - Drop virtual references for pointer casting. Instead, cast results
    reuse the source pointer's id and use branch splitting to explore
    the NULL case as a separate verifier branch. This avoids adding
    virtual reference infrastructure for a case that can be handled more
    simply (Eduard, Andrii)
  - Address nit from Eduard
  Link: https://lore.kernel.org/bpf/20260519181314.2731658-1-ameryhung@gmail.com/

v4 -> v5
  - Add patch 9 folding ref_obj_id into id and introducing virtual
    references for pointer casting and referenced dynptr clones (Eduard, Andrii)
  - Add patch 10 fixing dynptr ref counting to scan all call frames
    instead of only the current frame (Eduard)
  - Add utility function validate_ref_obj() (Eduard)
  Link: https://lore.kernel.org/bpf/20260506142709.2298255-1-ameryhung@gmail.com/

v3 -> v4
  - Add patch 1 clean up mark_stack_slot_obj_read() and callers
    (to address v3 ignoring err returned from mark_dynptr_read) (Andrii)
  - Fix release_reference() and move the logic allowing destroying a
    referenced object when refcnt > 1 from
    destroy_if_stack_slots_dynptr() to release_reference() (Mykyta)
  - Add patch 7 introducing ref_obj_desc and unifying ref_obj handling
    (to address Eduard's concern about unclear meta->{id,ref_obj_id}
    initialization/use and confusing function arguments of
    process_dynptr_func())
  - Add patch 8 unifying release_regno handling so that bpf_kptr_xchg
    also use release_reference()
  Link: https://lore.kernel.org/bpf/20260421221016.2967924-1-ameryhung@gmail.com/

v2 -> v3
  - Rebase to bpf-next/master
  - Update veristat numbers
  - Update commit msg to explain multiple dropped checks (Mykyta, Andrii)
  - Reuse idmap as idstack in release_reference() and check for
    duplicate id (Mykyta, Andrii)
  - Change to use RUN_TEST for qdisc dynptr selftest (Eduard)
  Link: https://lore.kernel.org/bpf/20260307064439.3247440-1-ameryhung@gmail.com/

v1 -> v2
  - Redesign: Use object (id, ref_obj_id, parent_id) instead of
    (id, ref_obj_id) as it cannot express ptr casting without
    introducing specialized code to handle the case
  - Use stack-based DFS to release objects to avoid recursion (Andrii)
  - Keep reg->id after null check
  - Add dynptr cleanup
  - Fix dynptr kfunc arg type determination
  - Add a file dynptr UAF selftest
  Link: https://lore.kernel.org/bpf/20260202214817.2853236-1-ameryhung@gmail.com/
---
====================

Link: https://patch.msgid.link/20260529014936.2811085-1-ameryhung@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Alexei Starovoitov 2026-06-01 18:31:42 -07:00
commit 3d781fffdc
30 changed files with 958 additions and 746 deletions

View File

@ -1062,7 +1062,7 @@ struct bpf_insn_access_aux {
struct {
struct btf *btf;
u32 btf_id;
u32 ref_obj_id;
u32 ref_id;
};
};
struct bpf_verifier_log *log; /* for verbose logs */
@ -1631,7 +1631,7 @@ struct bpf_ctx_arg_aux {
enum bpf_reg_type reg_type;
struct btf *btf;
u32 btf_id;
u32 ref_obj_id;
u32 ref_id;
bool refcounted;
};

View File

@ -66,7 +66,6 @@ struct bpf_reg_state {
struct { /* for PTR_TO_MEM | PTR_TO_MEM_OR_NULL */
u32 mem_size;
u32 dynptr_id; /* for dynptr slices */
};
/* For dynptr stack slots */
@ -148,46 +147,14 @@ struct bpf_reg_state {
#define BPF_ADD_CONST32 (1U << 30)
#define BPF_ADD_CONST (BPF_ADD_CONST64 | BPF_ADD_CONST32)
u32 id;
/* PTR_TO_SOCKET and PTR_TO_TCP_SOCK could be a ptr returned
* from a pointer-cast helper, bpf_sk_fullsock() and
* bpf_tcp_sock().
*
* Consider the following where "sk" is a reference counted
* pointer returned from "sk = bpf_sk_lookup_tcp();":
*
* 1: sk = bpf_sk_lookup_tcp();
* 2: if (!sk) { return 0; }
* 3: fullsock = bpf_sk_fullsock(sk);
* 4: if (!fullsock) { bpf_sk_release(sk); return 0; }
* 5: tp = bpf_tcp_sock(fullsock);
* 6: if (!tp) { bpf_sk_release(sk); return 0; }
* 7: bpf_sk_release(sk);
* 8: snd_cwnd = tp->snd_cwnd; // verifier will complain
*
* After bpf_sk_release(sk) at line 7, both "fullsock" ptr and
* "tp" ptr should be invalidated also. In order to do that,
* the reg holding "fullsock" and "sk" need to remember
* the original refcounted ptr id (i.e. sk_reg->id) in ref_obj_id
* such that the verifier can reset all regs which have
* ref_obj_id matching the sk_reg->id.
*
* sk_reg->ref_obj_id is set to sk_reg->id at line 1.
* sk_reg->id will stay as NULL-marking purpose only.
* After NULL-marking is done, sk_reg->id can be reset to 0.
*
* After "fullsock = bpf_sk_fullsock(sk);" at line 3,
* fullsock_reg->ref_obj_id is set to sk_reg->ref_obj_id.
*
* After "tp = bpf_tcp_sock(fullsock);" at line 5,
* tp_reg->ref_obj_id is set to fullsock_reg->ref_obj_id
* which is the same as sk_reg->ref_obj_id.
*
* From the verifier perspective, if sk, fullsock and tp
* are not NULL, they are the same ptr with different
* reg->type. In particular, bpf_sk_release(tp) is also
* allowed and has the same effect as bpf_sk_release(sk).
/*
* Tracks the parent object this register was derived from.
* Used for cascading invalidation: when the parent object is
* released or invalidated, all registers with matching parent_id
* are also invalidated. For example, a slice from bpf_dynptr_data()
* gets parent_id set to the dynptr's id.
*/
u32 ref_obj_id;
u32 parent_id;
/* Inside the callee two registers can be both PTR_TO_STACK like
* R1=fp-8 and R2=fp-8, but one of them points to this function stack
* while another to the caller's stack. To differentiate them 'frameno'
@ -364,10 +331,14 @@ struct bpf_reference_state {
* is used purely to inform the user of a reference leak.
*/
int insn_idx;
/* Use to keep track of the source object of a lock, to ensure
* it matches on unlock.
*/
void *ptr;
union {
/* For REF_TYPE_PTR */
int parent_id;
/* Use to keep track of the source object of a lock, to ensure
* it matches on unlock.
*/
void *ptr;
};
};
struct bpf_retval_range {
@ -585,7 +556,7 @@ bpf_get_spilled_stack_arg(int slot, struct bpf_func_state *frame)
iter < frame->out_stack_arg_cnt; \
iter++, reg = bpf_get_spilled_stack_arg(iter, frame))
#define bpf_for_each_reg_in_vstate_mask(__vst, __state, __reg, __mask, __expr) \
#define bpf_for_each_reg_in_vstate_mask(__vst, __state, __reg, __stack, __mask, __expr) \
({ \
struct bpf_verifier_state *___vstate = __vst; \
int ___i, ___j; \
@ -593,6 +564,7 @@ bpf_get_spilled_stack_arg(int slot, struct bpf_func_state *frame)
struct bpf_reg_state *___regs; \
__state = ___vstate->frame[___i]; \
___regs = __state->regs; \
__stack = NULL; \
for (___j = 0; ___j < MAX_BPF_REG; ___j++) { \
__reg = &___regs[___j]; \
(void)(__expr); \
@ -600,8 +572,10 @@ bpf_get_spilled_stack_arg(int slot, struct bpf_func_state *frame)
bpf_for_each_spilled_reg(___j, __state, __reg, __mask) { \
if (!__reg) \
continue; \
__stack = &__state->stack[___j]; \
(void)(__expr); \
} \
__stack = NULL; \
bpf_for_each_spilled_stack_arg(___j, __state, __reg) { \
if (!__reg) \
continue; \
@ -611,8 +585,13 @@ bpf_get_spilled_stack_arg(int slot, struct bpf_func_state *frame)
})
/* Invoke __expr over regsiters in __vst, setting __state and __reg */
#define bpf_for_each_reg_in_vstate(__vst, __state, __reg, __expr) \
bpf_for_each_reg_in_vstate_mask(__vst, __state, __reg, 1 << STACK_SPILL, __expr)
#define bpf_for_each_reg_in_vstate(__vst, __state, __reg, __expr) \
({ \
struct bpf_stack_state * ___stack; \
(void)___stack; \
bpf_for_each_reg_in_vstate_mask(__vst, __state, __reg, ___stack,\
1 << STACK_SPILL, __expr); \
})
/* linked list of verifier states used to prune search */
struct bpf_verifier_state_list {
@ -1438,6 +1417,25 @@ struct bpf_map_desc {
int uid;
};
/* The last initialized dynptr; Populated by process_dynptr_func() */
struct bpf_dynptr_desc {
enum bpf_dynptr_type type;
u32 id;
u32 parent_id;
};
/*
* The last seen rereferenced object; Updated by update_ref_obj() when a register refers to a
* referenced object. Used when the helper or kfunc is casting a referenced object, returning
* allocated memory derived from referenced object or creating a dynptr with a referenced
* object as parent.
*/
struct ref_obj_desc {
u32 id;
u32 parent_id;
u8 cnt;
};
struct bpf_kfunc_call_arg_meta {
/* In parameters */
struct btf *btf;
@ -1446,7 +1444,6 @@ struct bpf_kfunc_call_arg_meta {
const struct btf_type *func_proto;
const char *func_name;
/* Out parameters */
u32 ref_obj_id;
u8 release_regno;
bool r0_rdonly;
u32 ret_btf_id;
@ -1478,16 +1475,13 @@ struct bpf_kfunc_call_arg_meta {
struct {
struct btf_field *field;
} arg_rbtree_root;
struct {
enum bpf_dynptr_type type;
u32 id;
u32 ref_obj_id;
} initialized_dynptr;
struct {
u8 spi;
u8 frameno;
} iter;
struct bpf_map_desc map;
struct bpf_dynptr_desc dynptr;
struct ref_obj_desc ref_obj;
u64 mem_size;
};

View File

@ -6957,7 +6957,7 @@ bool btf_ctx_access(int off, int size, enum bpf_access_type type,
info->reg_type = ctx_arg_info->reg_type;
info->btf = ctx_arg_info->btf ? : btf_vmlinux;
info->btf_id = ctx_arg_info->btf_id;
info->ref_obj_id = ctx_arg_info->ref_obj_id;
info->ref_id = ctx_arg_info->ref_id;
return true;
}
}

View File

@ -870,7 +870,7 @@ int bpf_convert_ctx_accesses(struct bpf_verifier_env *env)
case PTR_TO_BTF_ID:
case PTR_TO_BTF_ID | PTR_UNTRUSTED:
/* PTR_TO_BTF_ID | MEM_ALLOC always has a valid lifetime, unlike
* PTR_TO_BTF_ID, and an active ref_obj_id, but the same cannot
* PTR_TO_BTF_ID, and an active referenced id, but the same cannot
* be said once it is marked PTR_UNTRUSTED, hence we must handle
* any faults for loads into such types. BPF_WRITE is disallowed
* for this case.

View File

@ -4957,7 +4957,7 @@ BTF_ID_FLAGS(func, bpf_stream_print_stack, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, bpf_task_work_schedule_signal, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, bpf_task_work_schedule_resume, KF_IMPLICIT_ARGS)
BTF_ID_FLAGS(func, bpf_dynptr_from_file)
BTF_ID_FLAGS(func, bpf_dynptr_file_discard)
BTF_ID_FLAGS(func, bpf_dynptr_file_discard, KF_RELEASE)
BTF_ID_FLAGS(func, bpf_timer_cancel_async)
BTF_KFUNCS_END(common_btf_ids)

View File

@ -665,8 +665,8 @@ static void print_reg_state(struct bpf_verifier_env *env,
verbose_a("id=%d", reg->id & ~BPF_ADD_CONST);
if (reg->id & BPF_ADD_CONST)
verbose(env, "%+d", reg->delta);
if (reg->ref_obj_id)
verbose_a("ref_obj_id=%d", reg->ref_obj_id);
if (reg->parent_id)
verbose_a("parent_id=%d", reg->parent_id);
if (type_is_non_owning_ref(reg->type))
verbose_a("%s", "non_own_ref");
if (type_is_map_ptr(t)) {
@ -768,21 +768,19 @@ void print_verifier_state(struct bpf_verifier_env *env, const struct bpf_verifie
verbose(env, "=dynptr_%s(", dynptr_type_str(reg->dynptr.type));
if (reg->id)
verbose_a("id=%d", reg->id);
if (reg->ref_obj_id)
verbose_a("ref_id=%d", reg->ref_obj_id);
if (reg->dynptr_id)
verbose_a("dynptr_id=%d", reg->dynptr_id);
if (reg->parent_id)
verbose_a("parent_id=%d", reg->parent_id);
verbose(env, ")");
break;
case STACK_ITER:
/* only main slot has ref_obj_id set; skip others */
if (!reg->ref_obj_id)
/* only main slot has id set; skip others */
if (!reg->id)
continue;
verbose(env, " fp%d=iter_%s(ref_id=%d,state=%s,depth=%u)",
verbose(env, " fp%d=iter_%s(id=%d,state=%s,depth=%u)",
(-i - 1) * BPF_REG_SIZE,
iter_type_str(reg->iter.btf, reg->iter.btf_id),
reg->ref_obj_id, iter_state_str(reg->iter.state),
reg->id, iter_state_str(reg->iter.state),
reg->iter.depth);
break;
case STACK_MISC:

View File

@ -489,7 +489,7 @@ static bool regs_exact(const struct bpf_reg_state *rold,
{
return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0 &&
check_ids(rold->id, rcur->id, idmap) &&
check_ids(rold->ref_obj_id, rcur->ref_obj_id, idmap);
check_ids(rold->parent_id, rcur->parent_id, idmap);
}
enum exact_level {
@ -614,7 +614,7 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold,
range_within(rold, rcur) &&
tnum_in(rold->var_off, rcur->var_off) &&
check_ids(rold->id, rcur->id, idmap) &&
check_ids(rold->ref_obj_id, rcur->ref_obj_id, idmap);
check_ids(rold->parent_id, rcur->parent_id, idmap);
case PTR_TO_PACKET_META:
case PTR_TO_PACKET:
/* We must have at least as much range as the old ptr
@ -794,7 +794,8 @@ static bool stacksafe(struct bpf_verifier_env *env, struct bpf_func_state *old,
cur_reg = &cur->stack[spi].spilled_ptr;
if (old_reg->dynptr.type != cur_reg->dynptr.type ||
old_reg->dynptr.first_slot != cur_reg->dynptr.first_slot ||
!check_ids(old_reg->ref_obj_id, cur_reg->ref_obj_id, idmap))
!check_ids(old_reg->id, cur_reg->id, idmap) ||
!check_ids(old_reg->parent_id, cur_reg->parent_id, idmap))
return false;
break;
case STACK_ITER:
@ -810,13 +811,13 @@ static bool stacksafe(struct bpf_verifier_env *env, struct bpf_func_state *old,
old_reg->iter.btf_id != cur_reg->iter.btf_id ||
old_reg->iter.state != cur_reg->iter.state ||
/* ignore {old_reg,cur_reg}->iter.depth, see above */
!check_ids(old_reg->ref_obj_id, cur_reg->ref_obj_id, idmap))
!check_ids(old_reg->id, cur_reg->id, idmap))
return false;
break;
case STACK_IRQ_FLAG:
old_reg = &old->stack[spi].spilled_ptr;
cur_reg = &cur->stack[spi].spilled_ptr;
if (!check_ids(old_reg->ref_obj_id, cur_reg->ref_obj_id, idmap) ||
if (!check_ids(old_reg->id, cur_reg->id, idmap) ||
old_reg->irq.kfunc_class != cur_reg->irq.kfunc_class)
return false;
break;

File diff suppressed because it is too large Load Diff

View File

@ -8,6 +8,10 @@
#include "bpf_qdisc_fifo.skel.h"
#include "bpf_qdisc_fq.skel.h"
#include "bpf_qdisc_fail__incompl_ops.skel.h"
#include "bpf_qdisc_fail__invalid_dynptr.skel.h"
#include "bpf_qdisc_fail__invalid_dynptr_slice.skel.h"
#include "bpf_qdisc_fail__invalid_dynptr_cross_frame.skel.h"
#include "bpf_qdisc_dynptr_use_after_invalidate_clone.skel.h"
#define LO_IFINDEX 1
@ -223,6 +227,10 @@ void test_ns_bpf_qdisc(void)
test_qdisc_attach_to_non_root();
if (test__start_subtest("incompl_ops"))
test_incompl_ops();
RUN_TESTS(bpf_qdisc_fail__invalid_dynptr);
RUN_TESTS(bpf_qdisc_fail__invalid_dynptr_cross_frame);
RUN_TESTS(bpf_qdisc_fail__invalid_dynptr_slice);
RUN_TESTS(bpf_qdisc_dynptr_use_after_invalidate_clone);
}
void serial_test_bpf_qdisc_default(void)

View File

@ -11,7 +11,7 @@ struct {
const char *prog_name;
const char *err_msg;
} cb_refs_tests[] = {
{ "underflow_prog", "must point to scalar, or struct with scalar" },
{ "underflow_prog", "release kfunc bpf_kfunc_call_test_release expects referenced PTR_TO_BTF_ID passed to R1" },
{ "leak_prog", "Possibly NULL pointer passed to helper R2" },
{ "nested_cb", "Unreleased reference id=4 alloc_insn=2" }, /* alloc_insn=2{4,5} */
{ "non_cb_transfer_ref", "Unreleased reference id=4 alloc_insn=1" }, /* alloc_insn=1{1,2} */

View File

@ -13,8 +13,8 @@ static struct {
const char *err_msg;
} spin_lock_fail_tests[] = {
{ "lock_id_kptr_preserve",
"[0-9]\\+: (bf) r1 = r0 ; R0=ptr_foo(id=2,ref_obj_id=2)"
" R1=ptr_foo(id=2,ref_obj_id=2) refs=2\n"
"[0-9]\\+: (bf) r1 = r0 ; R0=ptr_foo(id=2)"
" R1=ptr_foo(id=2) refs=2\n"
"[0-9]\\+: (85) call bpf_this_cpu_ptr#154\n"
"R1 type=ptr_ expected=percpu_ptr_" },
{ "lock_id_global_zero",

View File

@ -0,0 +1,74 @@
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include "bpf_experimental.h"
#include "bpf_qdisc_common.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
int proto;
SEC("struct_ops")
__success
int BPF_PROG(dynptr_use_after_invalidate_clone, struct sk_buff *skb, struct Qdisc *sch,
struct bpf_sk_buff_ptr *to_free)
{
struct bpf_dynptr ptr, ptr_clone;
struct ethhdr *hdr;
bpf_dynptr_from_skb((struct __sk_buff *)skb, 0, &ptr);
bpf_dynptr_clone(&ptr, &ptr_clone);
hdr = bpf_dynptr_slice(&ptr_clone, 0, NULL, sizeof(*hdr));
if (!hdr) {
bpf_qdisc_skb_drop(skb, to_free);
return NET_XMIT_DROP;
}
*(int *)&ptr = 0;
proto = hdr->h_proto;
bpf_qdisc_skb_drop(skb, to_free);
return NET_XMIT_DROP;
}
SEC("struct_ops")
__auxiliary
struct sk_buff *BPF_PROG(bpf_qdisc_test_dequeue, struct Qdisc *sch)
{
return NULL;
}
SEC("struct_ops")
__auxiliary
int BPF_PROG(bpf_qdisc_test_init, struct Qdisc *sch, struct nlattr *opt,
struct netlink_ext_ack *extack)
{
return 0;
}
SEC("struct_ops")
__auxiliary
void BPF_PROG(bpf_qdisc_test_reset, struct Qdisc *sch)
{
}
SEC("struct_ops")
__auxiliary
void BPF_PROG(bpf_qdisc_test_destroy, struct Qdisc *sch)
{
}
SEC(".struct_ops")
struct Qdisc_ops test = {
.enqueue = (void *)dynptr_use_after_invalidate_clone,
.dequeue = (void *)bpf_qdisc_test_dequeue,
.init = (void *)bpf_qdisc_test_init,
.reset = (void *)bpf_qdisc_test_reset,
.destroy = (void *)bpf_qdisc_test_destroy,
.id = "bpf_qdisc_test",
};

View File

@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include "bpf_experimental.h"
#include "bpf_qdisc_common.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
int proto;
SEC("struct_ops")
__failure __msg("Expected an initialized dynptr as R1")
int BPF_PROG(invalid_dynptr, struct sk_buff *skb, struct Qdisc *sch,
struct bpf_sk_buff_ptr *to_free)
{
struct bpf_dynptr ptr;
struct ethhdr *hdr;
bpf_dynptr_from_skb((struct __sk_buff *)skb, 0, &ptr);
bpf_qdisc_skb_drop(skb, to_free);
hdr = bpf_dynptr_slice(&ptr, 0, NULL, sizeof(*hdr));
if (!hdr)
return NET_XMIT_DROP;
proto = hdr->h_proto;
return NET_XMIT_DROP;
}
SEC("struct_ops")
__auxiliary
struct sk_buff *BPF_PROG(bpf_qdisc_test_dequeue, struct Qdisc *sch)
{
return NULL;
}
SEC("struct_ops")
__auxiliary
int BPF_PROG(bpf_qdisc_test_init, struct Qdisc *sch, struct nlattr *opt,
struct netlink_ext_ack *extack)
{
return 0;
}
SEC("struct_ops")
__auxiliary
void BPF_PROG(bpf_qdisc_test_reset, struct Qdisc *sch)
{
}
SEC("struct_ops")
__auxiliary
void BPF_PROG(bpf_qdisc_test_destroy, struct Qdisc *sch)
{
}
SEC(".struct_ops")
struct Qdisc_ops test = {
.enqueue = (void *)invalid_dynptr,
.dequeue = (void *)bpf_qdisc_test_dequeue,
.init = (void *)bpf_qdisc_test_init,
.reset = (void *)bpf_qdisc_test_reset,
.destroy = (void *)bpf_qdisc_test_destroy,
.id = "bpf_qdisc_test",
};

View File

@ -0,0 +1,74 @@
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include "bpf_experimental.h"
#include "bpf_qdisc_common.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
int proto;
static __noinline int free_skb(struct sk_buff *skb)
{
bpf_kfree_skb(skb);
return 0;
}
SEC("struct_ops")
__failure __msg("invalid mem access 'scalar'")
int BPF_PROG(invalid_dynptr_cross_frame, struct sk_buff *skb, struct Qdisc *sch,
struct bpf_sk_buff_ptr *to_free)
{
struct bpf_dynptr ptr;
struct ethhdr *hdr;
bpf_dynptr_from_skb((struct __sk_buff *)skb, 0, &ptr);
hdr = bpf_dynptr_slice(&ptr, 0, NULL, sizeof(*hdr));
if (!hdr)
return NET_XMIT_DROP;
free_skb(skb);
proto = hdr->h_proto;
return NET_XMIT_DROP;
}
SEC("struct_ops")
__auxiliary
struct sk_buff *BPF_PROG(bpf_qdisc_test_dequeue, struct Qdisc *sch)
{
return NULL;
}
SEC("struct_ops")
__auxiliary
int BPF_PROG(bpf_qdisc_test_init, struct Qdisc *sch, struct nlattr *opt,
struct netlink_ext_ack *extack)
{
return 0;
}
SEC("struct_ops")
__auxiliary
void BPF_PROG(bpf_qdisc_test_reset, struct Qdisc *sch)
{
}
SEC("struct_ops")
__auxiliary
void BPF_PROG(bpf_qdisc_test_destroy, struct Qdisc *sch)
{
}
SEC(".struct_ops")
struct Qdisc_ops test = {
.enqueue = (void *)invalid_dynptr_cross_frame,
.dequeue = (void *)bpf_qdisc_test_dequeue,
.init = (void *)bpf_qdisc_test_init,
.reset = (void *)bpf_qdisc_test_reset,
.destroy = (void *)bpf_qdisc_test_destroy,
.id = "bpf_qdisc_test",
};

View File

@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include "bpf_experimental.h"
#include "bpf_qdisc_common.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
int proto;
SEC("struct_ops")
__failure __msg("invalid mem access 'scalar'")
int BPF_PROG(invalid_dynptr_slice, struct sk_buff *skb, struct Qdisc *sch,
struct bpf_sk_buff_ptr *to_free)
{
struct bpf_dynptr ptr;
struct ethhdr *hdr;
bpf_dynptr_from_skb((struct __sk_buff *)skb, 0, &ptr);
hdr = bpf_dynptr_slice(&ptr, 0, NULL, sizeof(*hdr));
if (!hdr) {
bpf_qdisc_skb_drop(skb, to_free);
return NET_XMIT_DROP;
}
bpf_qdisc_skb_drop(skb, to_free);
proto = hdr->h_proto;
return NET_XMIT_DROP;
}
SEC("struct_ops")
__auxiliary
struct sk_buff *BPF_PROG(bpf_qdisc_test_dequeue, struct Qdisc *sch)
{
return NULL;
}
SEC("struct_ops")
__auxiliary
int BPF_PROG(bpf_qdisc_test_init, struct Qdisc *sch, struct nlattr *opt,
struct netlink_ext_ack *extack)
{
return 0;
}
SEC("struct_ops")
__auxiliary
void BPF_PROG(bpf_qdisc_test_reset, struct Qdisc *sch)
{
}
SEC("struct_ops")
__auxiliary
void BPF_PROG(bpf_qdisc_test_destroy, struct Qdisc *sch)
{
}
SEC(".struct_ops")
struct Qdisc_ops test = {
.enqueue = (void *)invalid_dynptr_slice,
.dequeue = (void *)bpf_qdisc_test_dequeue,
.init = (void *)bpf_qdisc_test_init,
.reset = (void *)bpf_qdisc_test_reset,
.destroy = (void *)bpf_qdisc_test_destroy,
.id = "bpf_qdisc_test",
};

View File

@ -154,7 +154,7 @@ int BPF_PROG(cgrp_kfunc_xchg_unreleased, struct cgroup *cgrp, const char *path)
}
SEC("tp_btf/cgroup_mkdir")
__failure __msg("must be referenced or trusted")
__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(cgrp_kfunc_rcu_get_release, struct cgroup *cgrp, const char *path)
{
struct cgroup *kptr;
@ -191,7 +191,7 @@ int BPF_PROG(cgrp_kfunc_release_untrusted, struct cgroup *cgrp, const char *path
}
SEC("tp_btf/cgroup_mkdir")
__failure __msg("R1 pointer type STRUCT cgroup must point")
__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(cgrp_kfunc_release_fp, struct cgroup *cgrp, const char *path)
{
struct cgroup *acquired = (struct cgroup *)&path;
@ -237,7 +237,7 @@ int BPF_PROG(cgrp_kfunc_release_null, struct cgroup *cgrp, const char *path)
}
SEC("tp_btf/cgroup_mkdir")
__failure __msg("release kernel function bpf_cgroup_release expects")
__failure __msg("release kfunc bpf_cgroup_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(cgrp_kfunc_release_unacquired, struct cgroup *cgrp, const char *path)
{
/* Cannot release trusted cgroup pointer which was not acquired. */

View File

@ -78,7 +78,7 @@ static int get_map_val_dynptr(struct bpf_dynptr *ptr)
* bpf_ringbuf_submit/discard_dynptr call
*/
SEC("?raw_tp")
__failure __msg("Unreleased reference id=2")
__failure __msg("Unreleased reference id=1")
int ringbuf_missing_release1(void *ctx)
{
struct bpf_dynptr ptr = {};
@ -91,7 +91,7 @@ int ringbuf_missing_release1(void *ctx)
}
SEC("?raw_tp")
__failure __msg("Unreleased reference id=4")
__failure __msg("Unreleased reference id=3")
int ringbuf_missing_release2(void *ctx)
{
struct bpf_dynptr ptr1, ptr2;
@ -136,7 +136,7 @@ int ringbuf_missing_release_callback(void *ctx)
/* Can't call bpf_ringbuf_submit/discard_dynptr on a non-initialized dynptr */
SEC("?raw_tp")
__failure __msg("arg 1 is an unacquired reference")
__failure __msg("Expected an initialized dynptr as R1")
int ringbuf_release_uninit_dynptr(void *ctx)
{
struct bpf_dynptr ptr;
@ -650,7 +650,7 @@ int invalid_offset(void *ctx)
/* Can't release a dynptr twice */
SEC("?raw_tp")
__failure __msg("arg 1 is an unacquired reference")
__failure __msg("Expected an initialized dynptr as R1")
int release_twice(void *ctx)
{
struct bpf_dynptr ptr;
@ -677,7 +677,7 @@ static int release_twice_callback_fn(__u32 index, void *data)
* within a callback function, fails
*/
SEC("?raw_tp")
__failure __msg("arg 1 is an unacquired reference")
__failure __msg("Expected an initialized dynptr as R1")
int release_twice_callback(void *ctx)
{
struct bpf_dynptr ptr;
@ -705,6 +705,48 @@ int dynptr_from_mem_invalid_api(void *ctx)
return 0;
}
/* Cannot create dynptr from dynptr data */
SEC("?raw_tp")
__failure __msg("Unsupported reg type mem for bpf_dynptr_from_mem data")
int dynptr_from_dynptr_data(void *ctx)
{
struct bpf_dynptr ptr, ptr2;
__u8 *data;
if (get_map_val_dynptr(&ptr))
return 0;
data = bpf_dynptr_data(&ptr, 0, sizeof(__u32));
if (!data)
return 0;
/* this should fail */
bpf_dynptr_from_mem(data, sizeof(__u32), 0, &ptr2);
return 0;
}
/* Cannot create dynptr from dynptr slice */
SEC("?tc")
__failure __msg("Unsupported reg type mem for bpf_dynptr_from_mem data")
int dynptr_from_dynptr_slice(struct __sk_buff *skb)
{
struct bpf_dynptr ptr, ptr2;
struct ethhdr *hdr;
char buffer[sizeof(*hdr)] = {};
bpf_dynptr_from_skb(skb, 0, &ptr);
hdr = bpf_dynptr_slice_rdwr(&ptr, 0, buffer, sizeof(buffer));
if (!hdr)
return SK_DROP;
/* this should fail */
bpf_dynptr_from_mem(hdr, sizeof(*hdr), 0, &ptr2);
return SK_PASS;
}
SEC("?tc")
__failure __msg("cannot overwrite referenced dynptr") __log_level(2)
int dynptr_pruning_overwrite(struct __sk_buff *ctx)

View File

@ -50,3 +50,63 @@ int xdp_no_dynptr_type(struct xdp_md *xdp)
bpf_dynptr_file_discard(&dynptr);
return 0;
}
SEC("lsm/file_open")
__failure
__msg("Leaking reference id={{[0-9]+}} alloc_insn={{[0-9]+}}. Release it first.")
int use_file_dynptr_after_put_file(void *ctx)
{
struct task_struct *task = bpf_get_current_task_btf();
struct file *file = bpf_get_task_exe_file(task);
struct bpf_dynptr dynptr;
char buf[64];
if (!file)
return 0;
if (bpf_dynptr_from_file(file, 0, &dynptr))
goto out;
/* this should fail - file dynptr should be discarded first to prevent resource leak */
bpf_put_file(file);
bpf_dynptr_read(buf, sizeof(buf), &dynptr, 0, 0);
return 0;
out:
bpf_dynptr_file_discard(&dynptr);
bpf_put_file(file);
return 0;
}
SEC("lsm/file_open")
__failure
__msg("Leaking reference id={{[0-9]+}} alloc_insn={{[0-9]+}}. Release it first.")
int use_file_dynptr_slice_after_put_file(void *ctx)
{
struct task_struct *task = bpf_get_current_task_btf();
struct file *file = bpf_get_task_exe_file(task);
struct bpf_dynptr dynptr;
char *data;
if (!file)
return 0;
if (bpf_dynptr_from_file(file, 0, &dynptr))
goto out;
data = bpf_dynptr_data(&dynptr, 0, 1);
if (!data)
goto out;
/* this should fail - file dynptr should be discarded first to prevent resource leak */
bpf_put_file(file);
*data = 'x';
return 0;
out:
bpf_dynptr_file_discard(&dynptr);
bpf_put_file(file);
return 0;
}

View File

@ -30,7 +30,7 @@ int force_clang_to_emit_btf_for_externs(void *ctx)
SEC("?raw_tp")
__success __log_level(2)
__msg("fp-8=iter_num(ref_id=1,state=active,depth=0)")
__msg("fp-8=iter_num(id=1,state=active,depth=0)")
int create_and_destroy(void *ctx)
{
struct bpf_iter_num iter;
@ -196,7 +196,7 @@ int leak_iter_from_subprog_fail(void *ctx)
SEC("?raw_tp")
__success __log_level(2)
__msg("fp-8=iter_num(ref_id=1,state=active,depth=0)")
__msg("fp-8=iter_num(id=1,state=active,depth=0)")
int valid_stack_reuse(void *ctx)
{
struct bpf_iter_num iter;

View File

@ -20,8 +20,8 @@ __s64 res_empty;
SEC("raw_tp/sys_enter")
__success __log_level(2)
__msg("fp-16=iter_testmod_seq(ref_id=1,state=active,depth=0)")
__msg("fp-16=iter_testmod_seq(ref_id=1,state=drained,depth=0)")
__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)
{
@ -38,8 +38,8 @@ __s64 res_full;
SEC("raw_tp/sys_enter")
__success __log_level(2)
__msg("fp-16=iter_testmod_seq(ref_id=1,state=active,depth=0)")
__msg("fp-16=iter_testmod_seq(ref_id=1,state=drained,depth=0)")
__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)
{
@ -58,8 +58,8 @@ static volatile int zero = 0;
SEC("raw_tp/sys_enter")
__success __log_level(2)
__msg("fp-16=iter_testmod_seq(ref_id=1,state=active,depth=0)")
__msg("fp-16=iter_testmod_seq(ref_id=1,state=drained,depth=0)")
__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)
{

View File

@ -252,7 +252,7 @@ int reject_untrusted_store_to_ref(struct __sk_buff *ctx)
}
SEC("?tc")
__failure __msg("R2 must be referenced")
__failure __msg("release helper bpf_kptr_xchg expects referenced PTR_TO_BTF_ID passed to R2")
int reject_untrusted_xchg(struct __sk_buff *ctx)
{
struct prog_test_ref_kfunc *p;

View File

@ -178,7 +178,7 @@ int BPF_PROG(task_kfunc_release_untrusted, struct task_struct *task, u64 clone_f
}
SEC("tp_btf/task_newtask")
__failure __msg("R1 pointer type STRUCT task_struct must point")
__failure __msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(task_kfunc_release_fp, struct task_struct *task, u64 clone_flags)
{
struct task_struct *acquired = (struct task_struct *)&clone_flags;
@ -224,7 +224,7 @@ int BPF_PROG(task_kfunc_release_null, struct task_struct *task, u64 clone_flags)
}
SEC("tp_btf/task_newtask")
__failure __msg("release kernel function bpf_task_release expects")
__failure __msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(task_kfunc_release_unacquired, struct task_struct *task, u64 clone_flags)
{
/* Cannot release trusted task pointer which was not acquired. */
@ -313,7 +313,7 @@ int BPF_PROG(task_access_comm4, struct task_struct *task, const char *buf, bool
}
SEC("tp_btf/task_newtask")
__failure __msg("R1 must be referenced or trusted")
__failure __msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(task_kfunc_release_in_map, struct task_struct *task, u64 clone_flags)
{
struct task_struct *local;

View File

@ -35,7 +35,7 @@ SEC("fentry/" SYS_PREFIX "sys_getpgid")
int test_ringbuf_mem_map_key(void *ctx)
{
int cur_pid = bpf_get_current_pid_tgid() >> 32;
struct sample *sample, sample_copy;
struct sample *sample;
int *lookup_val;
if (cur_pid != pid)
@ -55,16 +55,11 @@ int test_ringbuf_mem_map_key(void *ctx)
lookup_val = (int *)bpf_map_lookup_elem(&hash_map, sample);
__sink(lookup_val);
/* workaround - memcpy is necessary so that verifier doesn't
* complain with:
* verifier internal error: more than one arg with ref_obj_id R3
* when trying to do bpf_map_update_elem(&hash_map, sample, &sample->seq, BPF_ANY);
*
/*
* Since bpf_map_lookup_elem above uses 'sample' as key, test using
* sample field as value below
*/
__builtin_memcpy(&sample_copy, sample, sizeof(struct sample));
bpf_map_update_elem(&hash_map, &sample_copy, &sample->seq, BPF_ANY);
bpf_map_update_elem(&hash_map, sample, &sample->seq, BPF_ANY);
bpf_ringbuf_submit(sample, 0);
return 0;

View File

@ -146,7 +146,7 @@ try_discard_dynptr(struct bpf_dynptr *dynptr, void *context)
* not be able to read past the end of the pointer.
*/
SEC("?raw_tp")
__failure __msg("cannot release unowned const bpf_dynptr")
__failure __msg("CONST_PTR_TO_DYNPTR cannot be released")
int user_ringbuf_callback_discard_dynptr(void *ctx)
{
bpf_user_ringbuf_drain(&user_ringbuf, try_discard_dynptr, NULL, 0);
@ -166,7 +166,7 @@ try_submit_dynptr(struct bpf_dynptr *dynptr, void *context)
* not be able to read past the end of the pointer.
*/
SEC("?raw_tp")
__failure __msg("cannot release unowned const bpf_dynptr")
__failure __msg("CONST_PTR_TO_DYNPTR cannot be released")
int user_ringbuf_callback_submit_dynptr(void *ctx)
{
bpf_user_ringbuf_drain(&user_ringbuf, try_submit_dynptr, NULL, 0);

View File

@ -153,7 +153,7 @@ __weak int subprog_trusted_destroy(struct task_struct *task __arg_trusted)
SEC("?tp_btf/task_newtask")
__failure __log_level(2)
__msg("release kernel function bpf_task_release expects refcounted PTR_TO_BTF_ID")
__msg("release kfunc bpf_task_release expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(trusted_destroy_fail, struct task_struct *task, u64 clone_flags)
{
return subprog_trusted_destroy(task);

View File

@ -1288,7 +1288,7 @@ l1_%=: r1 = r6; \
SEC("tc")
__description("reference tracking: bpf_sk_release(listen_sk)")
__failure __msg("R1 must be referenced when passed to release function")
__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
__naked void bpf_sk_release_listen_sk(void)
{
asm volatile (

View File

@ -603,7 +603,7 @@ l2_%=: r0 = *(u32*)(r0 + %[bpf_tcp_sock_snd_cwnd]); \
SEC("tc")
__description("bpf_sk_release(skb->sk)")
__failure __msg("R1 must be referenced when passed to release function")
__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
__naked void bpf_sk_release_skb_sk(void)
{
asm volatile (" \
@ -620,7 +620,7 @@ l0_%=: r0 = 0; \
SEC("tc")
__description("bpf_sk_release(bpf_sk_fullsock(skb->sk))")
__failure __msg("R1 must be referenced when passed to release function")
__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
__naked void bpf_sk_fullsock_skb_sk(void)
{
asm volatile (" \
@ -644,7 +644,7 @@ l1_%=: r1 = r0; \
SEC("tc")
__description("bpf_sk_release(bpf_tcp_sock(skb->sk))")
__failure __msg("R1 must be referenced when passed to release function")
__failure __msg("release helper bpf_sk_release expects referenced PTR_TO_BTF_ID passed to R1")
__naked void bpf_tcp_sock_skb_sk(void)
{
asm volatile (" \

View File

@ -80,7 +80,7 @@ int BPF_PROG(get_task_exe_file_kfunc_unreleased)
}
SEC("lsm.s/file_open")
__failure __msg("release kernel function bpf_put_file expects")
__failure __msg("release kfunc bpf_put_file expects referenced PTR_TO_BTF_ID passed to R1")
int BPF_PROG(put_file_kfunc_unacquired, struct file *file)
{
/* Can't release an unacquired pointer. */

View File

@ -42,7 +42,7 @@ int wakeup_source_access_lock_fields(void *ctx)
}
SEC("syscall")
__failure __msg("type=scalar expected=fp")
__failure __msg("release kfunc bpf_wakeup_sources_read_unlock expects referenced PTR_TO_BTF_ID passed to R1")
int wakeup_source_unlock_no_lock(void *ctx)
{
struct bpf_ws_lock *lock = (void *)0x1;

View File

@ -2410,27 +2410,3 @@
.errstr_unpriv = "",
.prog_type = BPF_PROG_TYPE_CGROUP_SKB,
},
{
"calls: several args with ref_obj_id",
.insns = {
/* Reserve at least sizeof(struct iphdr) bytes in the ring buffer.
* With a smaller size, the verifier would reject the call to
* bpf_tcp_raw_gen_syncookie_ipv4 before we can reach the
* ref_obj_id error.
*/
BPF_MOV64_IMM(BPF_REG_2, 20),
BPF_MOV64_IMM(BPF_REG_3, 0),
BPF_LD_MAP_FD(BPF_REG_1, 0),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_ringbuf_reserve),
/* if r0 == 0 goto <exit> */
BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 3),
BPF_MOV64_REG(BPF_REG_1, BPF_REG_0),
BPF_MOV64_REG(BPF_REG_2, BPF_REG_0),
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_tcp_raw_gen_syncookie_ipv4),
BPF_EXIT_INSN(),
},
.fixup_map_ringbuf = { 2 },
.result = REJECT,
.errstr = "more than one arg with ref_obj_id",
.prog_type = BPF_PROG_TYPE_SCHED_CLS,
},