Merge tag 'bpf-next-6.15' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next

Pull bpf updates from Alexei Starovoitov:
 "For this merge window we're splitting BPF pull request into three for
  higher visibility: main changes, res_spin_lock, try_alloc_pages.

  These are the main BPF changes:

   - Add DFA-based live registers analysis to improve verification of
     programs with loops (Eduard Zingerman)

   - Introduce load_acquire and store_release BPF instructions and add
     x86, arm64 JIT support (Peilin Ye)

   - Fix loop detection logic in the verifier (Eduard Zingerman)

   - Drop unnecesary lock in bpf_map_inc_not_zero() (Eric Dumazet)

   - Add kfunc for populating cpumask bits (Emil Tsalapatis)

   - Convert various shell based tests to selftests/bpf/test_progs
     format (Bastien Curutchet)

   - Allow passing referenced kptrs into struct_ops callbacks (Amery
     Hung)

   - Add a flag to LSM bpf hook to facilitate bpf program signing
     (Blaise Boscaccy)

   - Track arena arguments in kfuncs (Ihor Solodrai)

   - Add copy_remote_vm_str() helper for reading strings from remote VM
     and bpf_copy_from_user_task_str() kfunc (Jordan Rome)

   - Add support for timed may_goto instruction (Kumar Kartikeya
     Dwivedi)

   - Allow bpf_get_netns_cookie() int cgroup_skb programs (Mahe Tardy)

   - Reduce bpf_cgrp_storage_busy false positives when accessing cgroup
     local storage (Martin KaFai Lau)

   - Introduce bpf_dynptr_copy() kfunc (Mykyta Yatsenko)

   - Allow retrieving BTF data with BTF token (Mykyta Yatsenko)

   - Add BPF kfuncs to set and get xattrs with 'security.bpf.' prefix
     (Song Liu)

   - Reject attaching programs to noreturn functions (Yafang Shao)

   - Introduce pre-order traversal of cgroup bpf programs (Yonghong
     Song)"

* tag 'bpf-next-6.15' of git://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next: (186 commits)
  selftests/bpf: Add selftests for load-acquire/store-release when register number is invalid
  bpf: Fix out-of-bounds read in check_atomic_load/store()
  libbpf: Add namespace for errstr making it libbpf_errstr
  bpf: Add struct_ops context information to struct bpf_prog_aux
  selftests/bpf: Sanitize pointer prior fclose()
  selftests/bpf: Migrate test_xdp_vlan.sh into test_progs
  selftests/bpf: test_xdp_vlan: Rename BPF sections
  bpf: clarify a misleading verifier error message
  selftests/bpf: Add selftest for attaching fexit to __noreturn functions
  bpf: Reject attaching fexit/fmod_ret to __noreturn functions
  bpf: Only fails the busy counter check in bpf_cgrp_storage_get if it creates storage
  bpf: Make perf_event_read_output accessible in all program types.
  bpftool: Using the right format specifiers
  bpftool: Add -Wformat-signedness flag to detect format errors
  selftests/bpf: Test freplace from user namespace
  libbpf: Pass BPF token from find_prog_btf_id to BPF_BTF_GET_FD_BY_ID
  bpf: Return prog btf_id without capable check
  bpf: BPF token support for BPF_BTF_GET_FD_BY_ID
  bpf, x86: Fix objtool warning for timed may_goto
  bpf: Check map->record at the beginning of check_and_free_fields()
  ...
This commit is contained in:
Linus Torvalds
2025-03-30 12:43:03 -07:00
213 changed files with 10654 additions and 3508 deletions

View File

@@ -1,12 +1,3 @@
bpf_cookie/multi_kprobe_attach_api # kprobe_multi_link_api_subtest:FAIL:fentry_raw_skel_load unexpected error: -3
bpf_cookie/multi_kprobe_link_api # kprobe_multi_link_api_subtest:FAIL:fentry_raw_skel_load unexpected error: -3
kprobe_multi_bench_attach # needs CONFIG_FPROBE
kprobe_multi_test # needs CONFIG_FPROBE
module_attach # prog 'kprobe_multi': failed to auto-attach: -95
fentry_test/fentry_many_args # fentry_many_args:FAIL:fentry_many_args_attach unexpected error: -524
fexit_test/fexit_many_args # fexit_many_args:FAIL:fexit_many_args_attach unexpected error: -524
tracing_struct/struct_many_args # struct_many_args:FAIL:tracing_struct_many_args__attach unexpected error: -524
fill_link_info/kprobe_multi_link_info # bpf_program__attach_kprobe_multi_opts unexpected error: -95
fill_link_info/kretprobe_multi_link_info # bpf_program__attach_kprobe_multi_opts unexpected error: -95
fill_link_info/kprobe_multi_invalid_ubuff # bpf_program__attach_kprobe_multi_opts unexpected error: -95
missed/kprobe_recursion # missed_kprobe_recursion__attach unexpected error: -95 (errno 95)

View File

@@ -95,18 +95,12 @@ TEST_GEN_PROGS += test_progs-cpuv4
TEST_INST_SUBDIRS += cpuv4
endif
TEST_GEN_FILES = test_lwt_ip_encap.bpf.o test_tc_edt.bpf.o
TEST_GEN_FILES = test_tc_edt.bpf.o
TEST_FILES = xsk_prereqs.sh $(wildcard progs/btf_dump_test_case_*.c)
# Order correspond to 'make run_tests' order
TEST_PROGS := test_kmod.sh \
test_xdp_redirect_multi.sh \
test_tunnel.sh \
test_lwt_seg6local.sh \
test_lirc_mode2.sh \
test_xdp_vlan_mode_generic.sh \
test_xdp_vlan_mode_native.sh \
test_lwt_ip_encap.sh \
test_tc_tunnel.sh \
test_tc_edt.sh \
test_xdping.sh \
@@ -117,9 +111,9 @@ TEST_PROGS := test_kmod.sh \
test_xsk.sh \
test_xdp_features.sh
TEST_PROGS_EXTENDED := with_addr.sh \
with_tunnels.sh ima_setup.sh verify_sig_setup.sh \
test_xdp_vlan.sh test_bpftool.py
TEST_PROGS_EXTENDED := \
ima_setup.sh verify_sig_setup.sh \
test_bpftool.py
TEST_KMODS := bpf_testmod.ko bpf_test_no_cfi.ko bpf_test_modorder_x.ko \
bpf_test_modorder_y.ko
@@ -135,7 +129,6 @@ TEST_GEN_PROGS_EXTENDED = \
veristat \
xdp_features \
xdp_hw_metadata \
xdp_redirect_multi \
xdp_synproxy \
xdping \
xskxceiver
@@ -184,9 +177,14 @@ ifeq ($(feature-llvm),1)
LLVM_CONFIG_LIB_COMPONENTS := mcdisassembler all-targets
# both llvm-config and lib.mk add -D_GNU_SOURCE, which ends up as conflict
LLVM_CFLAGS += $(filter-out -D_GNU_SOURCE,$(shell $(LLVM_CONFIG) --cflags))
LLVM_LDLIBS += $(shell $(LLVM_CONFIG) --link-static --libs $(LLVM_CONFIG_LIB_COMPONENTS))
LLVM_LDLIBS += $(shell $(LLVM_CONFIG) --link-static --system-libs $(LLVM_CONFIG_LIB_COMPONENTS))
LLVM_LDLIBS += -lstdc++
# Prefer linking statically if it's available, otherwise fallback to shared
ifeq ($(shell $(LLVM_CONFIG) --link-static --libs >/dev/null 2>&1 && echo static),static)
LLVM_LDLIBS += $(shell $(LLVM_CONFIG) --link-static --libs $(LLVM_CONFIG_LIB_COMPONENTS))
LLVM_LDLIBS += $(shell $(LLVM_CONFIG) --link-static --system-libs $(LLVM_CONFIG_LIB_COMPONENTS))
LLVM_LDLIBS += -lstdc++
else
LLVM_LDLIBS += $(shell $(LLVM_CONFIG) --link-shared --libs $(LLVM_CONFIG_LIB_COMPONENTS))
endif
LLVM_LDFLAGS += $(shell $(LLVM_CONFIG) --ldflags)
endif
@@ -306,6 +304,7 @@ $(OUTPUT)/runqslower: $(BPFOBJ) | $(DEFAULT_BPFTOOL) $(RUNQSLOWER_OUTPUT)
BPFTOOL_OUTPUT=$(HOST_BUILD_DIR)/bpftool/ \
BPFOBJ_OUTPUT=$(BUILD_DIR)/libbpf/ \
BPFOBJ=$(BPFOBJ) BPF_INCLUDE=$(INCLUDE_DIR) \
BPF_TARGET_ENDIAN=$(BPF_TARGET_ENDIAN) \
EXTRA_CFLAGS='-g $(OPT_FLAGS) $(SAN_CFLAGS) $(EXTRA_CFLAGS)' \
EXTRA_LDFLAGS='$(SAN_LDFLAGS) $(EXTRA_LDFLAGS)' && \
cp $(RUNQSLOWER_OUTPUT)runqslower $@
@@ -684,6 +683,7 @@ $(OUTPUT)/$(TRUNNER_BINARY): $(TRUNNER_TEST_OBJS) \
$(TRUNNER_EXTRA_OBJS) $$(BPFOBJ) \
$(RESOLVE_BTFIDS) \
$(TRUNNER_BPFTOOL) \
$(OUTPUT)/veristat \
| $(TRUNNER_BINARY)-extras
$$(call msg,BINARY,,$$@)
$(Q)$$(CC) $$(CFLAGS) $$(filter %.a %.o,$$^) $$(LDLIBS) $$(LDFLAGS) -o $$@

View File

@@ -0,0 +1,533 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
#ifndef BPF_ARENA_SPIN_LOCK_H
#define BPF_ARENA_SPIN_LOCK_H
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include "bpf_atomic.h"
#define arch_mcs_spin_lock_contended_label(l, label) smp_cond_load_acquire_label(l, VAL, label)
#define arch_mcs_spin_unlock_contended(l) smp_store_release((l), 1)
#if defined(ENABLE_ATOMICS_TESTS) && defined(__BPF_FEATURE_ADDR_SPACE_CAST)
#define EBUSY 16
#define EOPNOTSUPP 95
#define ETIMEDOUT 110
#ifndef __arena
#define __arena __attribute__((address_space(1)))
#endif
extern unsigned long CONFIG_NR_CPUS __kconfig;
/*
* Typically, we'd just rely on the definition in vmlinux.h for qspinlock, but
* PowerPC overrides the definition to define lock->val as u32 instead of
* atomic_t, leading to compilation errors. Import a local definition below so
* that we don't depend on the vmlinux.h version.
*/
struct __qspinlock {
union {
atomic_t val;
struct {
u8 locked;
u8 pending;
};
struct {
u16 locked_pending;
u16 tail;
};
};
};
#define arena_spinlock_t struct __qspinlock
/* FIXME: Using typedef causes CO-RE relocation error */
/* typedef struct qspinlock arena_spinlock_t; */
struct arena_mcs_spinlock {
struct arena_mcs_spinlock __arena *next;
int locked;
int count;
};
struct arena_qnode {
struct arena_mcs_spinlock mcs;
};
#define _Q_MAX_NODES 4
#define _Q_PENDING_LOOPS 1
/*
* Bitfields in the atomic value:
*
* 0- 7: locked byte
* 8: pending
* 9-15: not used
* 16-17: tail index
* 18-31: tail cpu (+1)
*/
#define _Q_MAX_CPUS 1024
#define _Q_SET_MASK(type) (((1U << _Q_ ## type ## _BITS) - 1)\
<< _Q_ ## type ## _OFFSET)
#define _Q_LOCKED_OFFSET 0
#define _Q_LOCKED_BITS 8
#define _Q_LOCKED_MASK _Q_SET_MASK(LOCKED)
#define _Q_PENDING_OFFSET (_Q_LOCKED_OFFSET + _Q_LOCKED_BITS)
#define _Q_PENDING_BITS 8
#define _Q_PENDING_MASK _Q_SET_MASK(PENDING)
#define _Q_TAIL_IDX_OFFSET (_Q_PENDING_OFFSET + _Q_PENDING_BITS)
#define _Q_TAIL_IDX_BITS 2
#define _Q_TAIL_IDX_MASK _Q_SET_MASK(TAIL_IDX)
#define _Q_TAIL_CPU_OFFSET (_Q_TAIL_IDX_OFFSET + _Q_TAIL_IDX_BITS)
#define _Q_TAIL_CPU_BITS (32 - _Q_TAIL_CPU_OFFSET)
#define _Q_TAIL_CPU_MASK _Q_SET_MASK(TAIL_CPU)
#define _Q_TAIL_OFFSET _Q_TAIL_IDX_OFFSET
#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK)
#define _Q_LOCKED_VAL (1U << _Q_LOCKED_OFFSET)
#define _Q_PENDING_VAL (1U << _Q_PENDING_OFFSET)
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
struct arena_qnode __arena qnodes[_Q_MAX_CPUS][_Q_MAX_NODES];
static inline u32 encode_tail(int cpu, int idx)
{
u32 tail;
tail = (cpu + 1) << _Q_TAIL_CPU_OFFSET;
tail |= idx << _Q_TAIL_IDX_OFFSET; /* assume < 4 */
return tail;
}
static inline struct arena_mcs_spinlock __arena *decode_tail(u32 tail)
{
u32 cpu = (tail >> _Q_TAIL_CPU_OFFSET) - 1;
u32 idx = (tail & _Q_TAIL_IDX_MASK) >> _Q_TAIL_IDX_OFFSET;
return &qnodes[cpu][idx].mcs;
}
static inline
struct arena_mcs_spinlock __arena *grab_mcs_node(struct arena_mcs_spinlock __arena *base, int idx)
{
return &((struct arena_qnode __arena *)base + idx)->mcs;
}
#define _Q_LOCKED_PENDING_MASK (_Q_LOCKED_MASK | _Q_PENDING_MASK)
/**
* xchg_tail - Put in the new queue tail code word & retrieve previous one
* @lock : Pointer to queued spinlock structure
* @tail : The new queue tail code word
* Return: The previous queue tail code word
*
* xchg(lock, tail)
*
* p,*,* -> n,*,* ; prev = xchg(lock, node)
*/
static __always_inline u32 xchg_tail(arena_spinlock_t __arena *lock, u32 tail)
{
u32 old, new;
old = atomic_read(&lock->val);
do {
new = (old & _Q_LOCKED_PENDING_MASK) | tail;
/*
* We can use relaxed semantics since the caller ensures that
* the MCS node is properly initialized before updating the
* tail.
*/
/* These loops are not expected to stall, but we still need to
* prove to the verifier they will terminate eventually.
*/
cond_break_label(out);
} while (!atomic_try_cmpxchg_relaxed(&lock->val, &old, new));
return old;
out:
bpf_printk("RUNTIME ERROR: %s unexpected cond_break exit!!!", __func__);
return old;
}
/**
* clear_pending - clear the pending bit.
* @lock: Pointer to queued spinlock structure
*
* *,1,* -> *,0,*
*/
static __always_inline void clear_pending(arena_spinlock_t __arena *lock)
{
WRITE_ONCE(lock->pending, 0);
}
/**
* clear_pending_set_locked - take ownership and clear the pending bit.
* @lock: Pointer to queued spinlock structure
*
* *,1,0 -> *,0,1
*
* Lock stealing is not allowed if this function is used.
*/
static __always_inline void clear_pending_set_locked(arena_spinlock_t __arena *lock)
{
WRITE_ONCE(lock->locked_pending, _Q_LOCKED_VAL);
}
/**
* set_locked - Set the lock bit and own the lock
* @lock: Pointer to queued spinlock structure
*
* *,*,0 -> *,0,1
*/
static __always_inline void set_locked(arena_spinlock_t __arena *lock)
{
WRITE_ONCE(lock->locked, _Q_LOCKED_VAL);
}
static __always_inline
u32 arena_fetch_set_pending_acquire(arena_spinlock_t __arena *lock)
{
u32 old, new;
old = atomic_read(&lock->val);
do {
new = old | _Q_PENDING_VAL;
/*
* These loops are not expected to stall, but we still need to
* prove to the verifier they will terminate eventually.
*/
cond_break_label(out);
} while (!atomic_try_cmpxchg_acquire(&lock->val, &old, new));
return old;
out:
bpf_printk("RUNTIME ERROR: %s unexpected cond_break exit!!!", __func__);
return old;
}
/**
* arena_spin_trylock - try to acquire the queued spinlock
* @lock : Pointer to queued spinlock structure
* Return: 1 if lock acquired, 0 if failed
*/
static __always_inline int arena_spin_trylock(arena_spinlock_t __arena *lock)
{
int val = atomic_read(&lock->val);
if (unlikely(val))
return 0;
return likely(atomic_try_cmpxchg_acquire(&lock->val, &val, _Q_LOCKED_VAL));
}
__noinline
int arena_spin_lock_slowpath(arena_spinlock_t __arena __arg_arena *lock, u32 val)
{
struct arena_mcs_spinlock __arena *prev, *next, *node0, *node;
int ret = -ETIMEDOUT;
u32 old, tail;
int idx;
/*
* Wait for in-progress pending->locked hand-overs with a bounded
* number of spins so that we guarantee forward progress.
*
* 0,1,0 -> 0,0,1
*/
if (val == _Q_PENDING_VAL) {
int cnt = _Q_PENDING_LOOPS;
val = atomic_cond_read_relaxed_label(&lock->val,
(VAL != _Q_PENDING_VAL) || !cnt--,
release_err);
}
/*
* If we observe any contention; queue.
*/
if (val & ~_Q_LOCKED_MASK)
goto queue;
/*
* trylock || pending
*
* 0,0,* -> 0,1,* -> 0,0,1 pending, trylock
*/
val = arena_fetch_set_pending_acquire(lock);
/*
* If we observe contention, there is a concurrent locker.
*
* Undo and queue; our setting of PENDING might have made the
* n,0,0 -> 0,0,0 transition fail and it will now be waiting
* on @next to become !NULL.
*/
if (unlikely(val & ~_Q_LOCKED_MASK)) {
/* Undo PENDING if we set it. */
if (!(val & _Q_PENDING_MASK))
clear_pending(lock);
goto queue;
}
/*
* We're pending, wait for the owner to go away.
*
* 0,1,1 -> *,1,0
*
* this wait loop must be a load-acquire such that we match the
* store-release that clears the locked bit and create lock
* sequentiality; this is because not all
* clear_pending_set_locked() implementations imply full
* barriers.
*/
if (val & _Q_LOCKED_MASK)
smp_cond_load_acquire_label(&lock->locked, !VAL, release_err);
/*
* take ownership and clear the pending bit.
*
* 0,1,0 -> 0,0,1
*/
clear_pending_set_locked(lock);
return 0;
/*
* End of pending bit optimistic spinning and beginning of MCS
* queuing.
*/
queue:
node0 = &(qnodes[bpf_get_smp_processor_id()])[0].mcs;
idx = node0->count++;
tail = encode_tail(bpf_get_smp_processor_id(), idx);
/*
* 4 nodes are allocated based on the assumption that there will not be
* nested NMIs taking spinlocks. That may not be true in some
* architectures even though the chance of needing more than 4 nodes
* will still be extremely unlikely. When that happens, we simply return
* an error. Original qspinlock has a trylock fallback in this case.
*/
if (unlikely(idx >= _Q_MAX_NODES)) {
ret = -EBUSY;
goto release_node_err;
}
node = grab_mcs_node(node0, idx);
/*
* Ensure that we increment the head node->count before initialising
* the actual node. If the compiler is kind enough to reorder these
* stores, then an IRQ could overwrite our assignments.
*/
barrier();
node->locked = 0;
node->next = NULL;
/*
* We touched a (possibly) cold cacheline in the per-cpu queue node;
* attempt the trylock once more in the hope someone let go while we
* weren't watching.
*/
if (arena_spin_trylock(lock))
goto release;
/*
* Ensure that the initialisation of @node is complete before we
* publish the updated tail via xchg_tail() and potentially link
* @node into the waitqueue via WRITE_ONCE(prev->next, node) below.
*/
smp_wmb();
/*
* Publish the updated tail.
* We have already touched the queueing cacheline; don't bother with
* pending stuff.
*
* p,*,* -> n,*,*
*/
old = xchg_tail(lock, tail);
next = NULL;
/*
* if there was a previous node; link it and wait until reaching the
* head of the waitqueue.
*/
if (old & _Q_TAIL_MASK) {
prev = decode_tail(old);
/* Link @node into the waitqueue. */
WRITE_ONCE(prev->next, node);
arch_mcs_spin_lock_contended_label(&node->locked, release_node_err);
/*
* While waiting for the MCS lock, the next pointer may have
* been set by another lock waiter. We cannot prefetch here
* due to lack of equivalent instruction in BPF ISA.
*/
next = READ_ONCE(node->next);
}
/*
* we're at the head of the waitqueue, wait for the owner & pending to
* go away.
*
* *,x,y -> *,0,0
*
* this wait loop must use a load-acquire such that we match the
* store-release that clears the locked bit and create lock
* sequentiality; this is because the set_locked() function below
* does not imply a full barrier.
*/
val = atomic_cond_read_acquire_label(&lock->val, !(VAL & _Q_LOCKED_PENDING_MASK),
release_node_err);
/*
* claim the lock:
*
* n,0,0 -> 0,0,1 : lock, uncontended
* *,*,0 -> *,*,1 : lock, contended
*
* If the queue head is the only one in the queue (lock value == tail)
* and nobody is pending, clear the tail code and grab the lock.
* Otherwise, we only need to grab the lock.
*/
/*
* In the PV case we might already have _Q_LOCKED_VAL set, because
* of lock stealing; therefore we must also allow:
*
* n,0,1 -> 0,0,1
*
* Note: at this point: (val & _Q_PENDING_MASK) == 0, because of the
* above wait condition, therefore any concurrent setting of
* PENDING will make the uncontended transition fail.
*/
if ((val & _Q_TAIL_MASK) == tail) {
if (atomic_try_cmpxchg_relaxed(&lock->val, &val, _Q_LOCKED_VAL))
goto release; /* No contention */
}
/*
* Either somebody is queued behind us or _Q_PENDING_VAL got set
* which will then detect the remaining tail and queue behind us
* ensuring we'll see a @next.
*/
set_locked(lock);
/*
* contended path; wait for next if not observed yet, release.
*/
if (!next)
next = smp_cond_load_relaxed_label(&node->next, (VAL), release_node_err);
arch_mcs_spin_unlock_contended(&next->locked);
release:;
/*
* release the node
*
* Doing a normal dec vs this_cpu_dec is fine. An upper context always
* decrements count it incremented before returning, thus we're fine.
* For contexts interrupting us, they either observe our dec or not.
* Just ensure the compiler doesn't reorder this statement, as a
* this_cpu_dec implicitly implied that.
*/
barrier();
node0->count--;
return 0;
release_node_err:
barrier();
node0->count--;
goto release_err;
release_err:
return ret;
}
/**
* arena_spin_lock - acquire a queued spinlock
* @lock: Pointer to queued spinlock structure
*
* On error, returned value will be negative.
* On success, zero is returned.
*
* The return value _must_ be tested against zero for success,
* instead of checking it against negative, for passing the
* BPF verifier.
*
* The user should do:
* if (arena_spin_lock(...) != 0) // failure
* or
* if (arena_spin_lock(...) == 0) // success
* or
* if (arena_spin_lock(...)) // failure
* or
* if (!arena_spin_lock(...)) // success
* instead of:
* if (arena_spin_lock(...) < 0) // failure
*
* The return value can still be inspected later.
*/
static __always_inline int arena_spin_lock(arena_spinlock_t __arena *lock)
{
int val = 0;
if (CONFIG_NR_CPUS > 1024)
return -EOPNOTSUPP;
bpf_preempt_disable();
if (likely(atomic_try_cmpxchg_acquire(&lock->val, &val, _Q_LOCKED_VAL)))
return 0;
val = arena_spin_lock_slowpath(lock, val);
/* FIXME: bpf_assert_range(-MAX_ERRNO, 0) once we have it working for all cases. */
if (val)
bpf_preempt_enable();
return val;
}
/**
* arena_spin_unlock - release a queued spinlock
* @lock : Pointer to queued spinlock structure
*/
static __always_inline void arena_spin_unlock(arena_spinlock_t __arena *lock)
{
/*
* unlock() needs release semantics:
*/
smp_store_release(&lock->locked, 0);
bpf_preempt_enable();
}
#define arena_spin_lock_irqsave(lock, flags) \
({ \
int __ret; \
bpf_local_irq_save(&(flags)); \
__ret = arena_spin_lock((lock)); \
if (__ret) \
bpf_local_irq_restore(&(flags)); \
(__ret); \
})
#define arena_spin_unlock_irqrestore(lock, flags) \
({ \
arena_spin_unlock((lock)); \
bpf_local_irq_restore(&(flags)); \
})
#endif
#endif /* BPF_ARENA_SPIN_LOCK_H */

View File

@@ -0,0 +1,140 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
#ifndef BPF_ATOMIC_H
#define BPF_ATOMIC_H
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include "bpf_experimental.h"
extern bool CONFIG_X86_64 __kconfig __weak;
/*
* __unqual_typeof(x) - Declare an unqualified scalar type, leaving
* non-scalar types unchanged,
*
* Prefer C11 _Generic for better compile-times and simpler code. Note: 'char'
* is not type-compatible with 'signed char', and we define a separate case.
*
* This is copied verbatim from kernel's include/linux/compiler_types.h, but
* with default expression (for pointers) changed from (x) to (typeof(x)0).
*
* This is because LLVM has a bug where for lvalue (x), it does not get rid of
* an extra address_space qualifier, but does in case of rvalue (typeof(x)0).
* Hence, for pointers, we need to create an rvalue expression to get the
* desired type. See https://github.com/llvm/llvm-project/issues/53400.
*/
#define __scalar_type_to_expr_cases(type) \
unsigned type : (unsigned type)0, signed type : (signed type)0
#define __unqual_typeof(x) \
typeof(_Generic((x), \
char: (char)0, \
__scalar_type_to_expr_cases(char), \
__scalar_type_to_expr_cases(short), \
__scalar_type_to_expr_cases(int), \
__scalar_type_to_expr_cases(long), \
__scalar_type_to_expr_cases(long long), \
default: (typeof(x))0))
/* No-op for BPF */
#define cpu_relax() ({})
#define READ_ONCE(x) (*(volatile typeof(x) *)&(x))
#define WRITE_ONCE(x, val) ((*(volatile typeof(x) *)&(x)) = (val))
#define cmpxchg(p, old, new) __sync_val_compare_and_swap((p), old, new)
#define try_cmpxchg(p, pold, new) \
({ \
__unqual_typeof(*(pold)) __o = *(pold); \
__unqual_typeof(*(p)) __r = cmpxchg(p, __o, new); \
if (__r != __o) \
*(pold) = __r; \
__r == __o; \
})
#define try_cmpxchg_relaxed(p, pold, new) try_cmpxchg(p, pold, new)
#define try_cmpxchg_acquire(p, pold, new) try_cmpxchg(p, pold, new)
#define smp_mb() \
({ \
unsigned long __val; \
__sync_fetch_and_add(&__val, 0); \
})
#define smp_rmb() \
({ \
if (!CONFIG_X86_64) \
smp_mb(); \
else \
barrier(); \
})
#define smp_wmb() \
({ \
if (!CONFIG_X86_64) \
smp_mb(); \
else \
barrier(); \
})
/* Control dependency provides LOAD->STORE, provide LOAD->LOAD */
#define smp_acquire__after_ctrl_dep() ({ smp_rmb(); })
#define smp_load_acquire(p) \
({ \
__unqual_typeof(*(p)) __v = READ_ONCE(*(p)); \
if (!CONFIG_X86_64) \
smp_mb(); \
barrier(); \
__v; \
})
#define smp_store_release(p, val) \
({ \
if (!CONFIG_X86_64) \
smp_mb(); \
barrier(); \
WRITE_ONCE(*(p), val); \
})
#define smp_cond_load_relaxed_label(p, cond_expr, label) \
({ \
typeof(p) __ptr = (p); \
__unqual_typeof(*(p)) VAL; \
for (;;) { \
VAL = (__unqual_typeof(*(p)))READ_ONCE(*__ptr); \
if (cond_expr) \
break; \
cond_break_label(label); \
cpu_relax(); \
} \
(typeof(*(p)))VAL; \
})
#define smp_cond_load_acquire_label(p, cond_expr, label) \
({ \
__unqual_typeof(*p) __val = \
smp_cond_load_relaxed_label(p, cond_expr, label); \
smp_acquire__after_ctrl_dep(); \
(typeof(*(p)))__val; \
})
#define atomic_read(p) READ_ONCE((p)->counter)
#define atomic_cond_read_relaxed_label(p, cond_expr, label) \
smp_cond_load_relaxed_label(&(p)->counter, cond_expr, label)
#define atomic_cond_read_acquire_label(p, cond_expr, label) \
smp_cond_load_acquire_label(&(p)->counter, cond_expr, label)
#define atomic_try_cmpxchg_relaxed(p, pold, new) \
try_cmpxchg_relaxed(&(p)->counter, pold, new)
#define atomic_try_cmpxchg_acquire(p, pold, new) \
try_cmpxchg_acquire(&(p)->counter, pold, new)
#endif /* BPF_ATOMIC_H */

View File

@@ -368,12 +368,12 @@ l_true: \
ret; \
})
#define cond_break \
#define __cond_break(expr) \
({ __label__ l_break, l_continue; \
asm volatile goto("may_goto %l[l_break]" \
:::: l_break); \
goto l_continue; \
l_break: break; \
l_break: expr; \
l_continue:; \
})
#else
@@ -392,7 +392,7 @@ l_true: \
ret; \
})
#define cond_break \
#define __cond_break(expr) \
({ __label__ l_break, l_continue; \
asm volatile goto("1:.byte 0xe5; \
.byte 0; \
@@ -400,7 +400,7 @@ l_true: \
.short 0" \
:::: l_break); \
goto l_continue; \
l_break: break; \
l_break: expr; \
l_continue:; \
})
#else
@@ -418,7 +418,7 @@ l_true: \
ret; \
})
#define cond_break \
#define __cond_break(expr) \
({ __label__ l_break, l_continue; \
asm volatile goto("1:.byte 0xe5; \
.byte 0; \
@@ -426,12 +426,15 @@ l_true: \
.short 0" \
:::: l_break); \
goto l_continue; \
l_break: break; \
l_break: expr; \
l_continue:; \
})
#endif
#endif
#define cond_break __cond_break(break)
#define cond_break_label(label) __cond_break(goto label)
#ifndef bpf_nop_mov
#define bpf_nop_mov(var) \
asm volatile("%[reg]=%[reg]"::[reg]"r"((short)var))

View File

@@ -87,4 +87,9 @@ struct dentry;
*/
extern int bpf_get_dentry_xattr(struct dentry *dentry, const char *name,
struct bpf_dynptr *value_ptr) __ksym __weak;
extern int bpf_set_dentry_xattr(struct dentry *dentry, const char *name__str,
const struct bpf_dynptr *value_p, int flags) __ksym __weak;
extern int bpf_remove_dentry_xattr(struct dentry *dentry, const char *name__str) __ksym __weak;
#endif

View File

@@ -19,7 +19,7 @@ int cap_enable_effective(__u64 caps, __u64 *old_caps)
err = capget(&hdr, data);
if (err)
return err;
return -errno;
if (old_caps)
*old_caps = (__u64)(data[1].effective) << 32 | data[0].effective;
@@ -32,7 +32,7 @@ int cap_enable_effective(__u64 caps, __u64 *old_caps)
data[1].effective |= cap1;
err = capset(&hdr, data);
if (err)
return err;
return -errno;
return 0;
}
@@ -49,7 +49,7 @@ int cap_disable_effective(__u64 caps, __u64 *old_caps)
err = capget(&hdr, data);
if (err)
return err;
return -errno;
if (old_caps)
*old_caps = (__u64)(data[1].effective) << 32 | data[0].effective;
@@ -61,7 +61,7 @@ int cap_disable_effective(__u64 caps, __u64 *old_caps)
data[1].effective &= ~cap1;
err = capset(&hdr, data);
if (err)
return err;
return -errno;
return 0;
}

View File

@@ -4,6 +4,7 @@
#include <linux/types.h>
#include <linux/capability.h>
#include <errno.h>
#ifndef CAP_PERFMON
#define CAP_PERFMON 38

View File

@@ -446,6 +446,23 @@ char *ping_command(int family)
return "ping";
}
int append_tid(char *str, size_t sz)
{
size_t end;
if (!str)
return -1;
end = strlen(str);
if (end + 8 > sz)
return -1;
sprintf(&str[end], "%07d", gettid());
str[end + 7] = '\0';
return 0;
}
int remove_netns(const char *name)
{
char *cmd;
@@ -761,6 +778,36 @@ struct tmonitor_ctx {
int pcap_fd;
};
static int __base_pr(const char *format, va_list args)
{
return vfprintf(stdout, format, args);
}
static tm_print_fn_t __tm_pr = __base_pr;
tm_print_fn_t traffic_monitor_set_print(tm_print_fn_t fn)
{
tm_print_fn_t old_print_fn;
old_print_fn = __atomic_exchange_n(&__tm_pr, fn, __ATOMIC_RELAXED);
return old_print_fn;
}
void tm_print(const char *format, ...)
{
tm_print_fn_t print_fn;
va_list args;
print_fn = __atomic_load_n(&__tm_pr, __ATOMIC_RELAXED);
if (!print_fn)
return;
va_start(args, format);
print_fn(format, args);
va_end(args);
}
/* Is this packet captured with a Ethernet protocol type? */
static bool is_ethernet(const u_char *packet)
{
@@ -778,7 +825,7 @@ static bool is_ethernet(const u_char *packet)
case 770: /* ARPHRD_FRAD */
case 778: /* ARPHDR_IPGRE */
case 803: /* ARPHRD_IEEE80211_RADIOTAP */
printf("Packet captured: arphdr_type=%d\n", arphdr_type);
tm_print("Packet captured: arphdr_type=%d\n", arphdr_type);
return false;
}
return true;
@@ -799,12 +846,13 @@ static const char *pkt_type_str(u16 pkt_type)
return "Unknown";
}
#define MAX_FLAGS_STRLEN 21
/* Show the information of the transport layer in the packet */
static void show_transport(const u_char *packet, u16 len, u32 ifindex,
const char *src_addr, const char *dst_addr,
u16 proto, bool ipv6, u8 pkt_type)
{
char *ifname, _ifname[IF_NAMESIZE];
char *ifname, _ifname[IF_NAMESIZE], flags[MAX_FLAGS_STRLEN] = "";
const char *transport_str;
u16 src_port, dst_port;
struct udphdr *udp;
@@ -827,47 +875,39 @@ static void show_transport(const u_char *packet, u16 len, u32 ifindex,
dst_port = ntohs(tcp->dest);
transport_str = "TCP";
} else if (proto == IPPROTO_ICMP) {
printf("%-7s %-3s IPv4 %s > %s: ICMP, length %d, type %d, code %d\n",
ifname, pkt_type_str(pkt_type), src_addr, dst_addr, len,
packet[0], packet[1]);
tm_print("%-7s %-3s IPv4 %s > %s: ICMP, length %d, type %d, code %d\n",
ifname, pkt_type_str(pkt_type), src_addr, dst_addr, len,
packet[0], packet[1]);
return;
} else if (proto == IPPROTO_ICMPV6) {
printf("%-7s %-3s IPv6 %s > %s: ICMPv6, length %d, type %d, code %d\n",
ifname, pkt_type_str(pkt_type), src_addr, dst_addr, len,
packet[0], packet[1]);
tm_print("%-7s %-3s IPv6 %s > %s: ICMPv6, length %d, type %d, code %d\n",
ifname, pkt_type_str(pkt_type), src_addr, dst_addr, len,
packet[0], packet[1]);
return;
} else {
printf("%-7s %-3s %s %s > %s: protocol %d\n",
ifname, pkt_type_str(pkt_type), ipv6 ? "IPv6" : "IPv4",
src_addr, dst_addr, proto);
tm_print("%-7s %-3s %s %s > %s: protocol %d\n",
ifname, pkt_type_str(pkt_type), ipv6 ? "IPv6" : "IPv4",
src_addr, dst_addr, proto);
return;
}
/* TCP or UDP*/
flockfile(stdout);
if (proto == IPPROTO_TCP)
snprintf(flags, MAX_FLAGS_STRLEN, "%s%s%s%s",
tcp->fin ? ", FIN" : "",
tcp->syn ? ", SYN" : "",
tcp->rst ? ", RST" : "",
tcp->ack ? ", ACK" : "");
if (ipv6)
printf("%-7s %-3s IPv6 %s.%d > %s.%d: %s, length %d",
ifname, pkt_type_str(pkt_type), src_addr, src_port,
dst_addr, dst_port, transport_str, len);
tm_print("%-7s %-3s IPv6 %s.%d > %s.%d: %s, length %d%s\n",
ifname, pkt_type_str(pkt_type), src_addr, src_port,
dst_addr, dst_port, transport_str, len, flags);
else
printf("%-7s %-3s IPv4 %s:%d > %s:%d: %s, length %d",
ifname, pkt_type_str(pkt_type), src_addr, src_port,
dst_addr, dst_port, transport_str, len);
if (proto == IPPROTO_TCP) {
if (tcp->fin)
printf(", FIN");
if (tcp->syn)
printf(", SYN");
if (tcp->rst)
printf(", RST");
if (tcp->ack)
printf(", ACK");
}
printf("\n");
funlockfile(stdout);
tm_print("%-7s %-3s IPv4 %s:%d > %s:%d: %s, length %d%s\n",
ifname, pkt_type_str(pkt_type), src_addr, src_port,
dst_addr, dst_port, transport_str, len, flags);
}
static void show_ipv6_packet(const u_char *packet, u32 ifindex, u8 pkt_type)
@@ -982,8 +1022,8 @@ static void *traffic_monitor_thread(void *arg)
ifname = _ifname;
}
printf("%-7s %-3s Unknown network protocol type 0x%x\n",
ifname, pkt_type_str(ptype), proto);
tm_print("%-7s %-3s Unknown network protocol type 0x%x\n",
ifname, pkt_type_str(ptype), proto);
}
}
@@ -1183,8 +1223,9 @@ void traffic_monitor_stop(struct tmonitor_ctx *ctx)
write(ctx->wake_fd, &w, sizeof(w));
pthread_join(ctx->thread, NULL);
printf("Packet file: %s\n", strrchr(ctx->pkt_fname, '/') + 1);
tm_print("Packet file: %s\n", strrchr(ctx->pkt_fname, '/') + 1);
traffic_monitor_release(ctx);
}
#endif /* TRAFFIC_MONITOR */

View File

@@ -18,6 +18,7 @@ typedef __u16 __sum16;
#include <netinet/udp.h>
#include <bpf/bpf_endian.h>
#include <net/if.h>
#include <stdio.h>
#define MAGIC_VAL 0x1234
#define NUM_ITER 100000
@@ -101,6 +102,18 @@ int send_recv_data(int lfd, int fd, uint32_t total_bytes);
int make_netns(const char *name);
int remove_netns(const char *name);
/**
* append_tid() - Append thread ID to the given string.
*
* @str: string to extend
* @sz: string's size
*
* 8 characters are used to append the thread ID (7 digits + '\0')
*
* Returns -1 on errors, 0 otherwise
*/
int append_tid(char *str, size_t sz);
static __u16 csum_fold(__u32 csum)
{
csum = (csum & 0xffff) + (csum >> 16);
@@ -240,10 +253,13 @@ static inline __sum16 build_udp_v6_csum(const struct ipv6hdr *ip6h,
struct tmonitor_ctx;
typedef int (*tm_print_fn_t)(const char *format, va_list args);
#ifdef TRAFFIC_MONITOR
struct tmonitor_ctx *traffic_monitor_start(const char *netns, const char *test_name,
const char *subtest_name);
void traffic_monitor_stop(struct tmonitor_ctx *ctx);
tm_print_fn_t traffic_monitor_set_print(tm_print_fn_t fn);
#else
static inline struct tmonitor_ctx *traffic_monitor_start(const char *netns, const char *test_name,
const char *subtest_name)
@@ -254,6 +270,11 @@ static inline struct tmonitor_ctx *traffic_monitor_start(const char *netns, cons
static inline void traffic_monitor_stop(struct tmonitor_ctx *ctx)
{
}
static inline tm_print_fn_t traffic_monitor_set_print(tm_print_fn_t fn)
{
return NULL;
}
#endif
#endif

View File

@@ -610,9 +610,11 @@ static int do_test_single(struct bpf_align_test *test)
.log_size = sizeof(bpf_vlog),
.log_level = 2,
);
const char *main_pass_start = "0: R1=ctx() R10=fp0";
const char *line_ptr;
int cur_line = -1;
int prog_len, i;
char *start;
int fd_prog;
int ret;
@@ -632,7 +634,13 @@ static int do_test_single(struct bpf_align_test *test)
ret = 0;
/* We make a local copy so that we can strtok() it */
strncpy(bpf_vlog_copy, bpf_vlog, sizeof(bpf_vlog_copy));
line_ptr = strtok(bpf_vlog_copy, "\n");
start = strstr(bpf_vlog_copy, main_pass_start);
if (!start) {
ret = 1;
printf("Can't find initial line '%s'\n", main_pass_start);
goto out;
}
line_ptr = strtok(start, "\n");
for (i = 0; i < MAX_MATCHES; i++) {
struct bpf_reg_match m = test->matches[i];
const char *p;
@@ -682,6 +690,7 @@ static int do_test_single(struct bpf_align_test *test)
break;
}
}
out:
if (fd_prog >= 0)
close(fd_prog);
}

View File

@@ -162,6 +162,66 @@ static void test_uaf(struct arena_atomics *skel)
ASSERT_EQ(skel->arena->uaf_recovery_fails, 0, "uaf_recovery_fails");
}
static void test_load_acquire(struct arena_atomics *skel)
{
LIBBPF_OPTS(bpf_test_run_opts, topts);
int err, prog_fd;
if (skel->data->skip_lacq_srel_tests) {
printf("%s:SKIP: ENABLE_ATOMICS_TESTS not defined, Clang doesn't support addr_space_cast, and/or JIT doesn't support load-acquire\n",
__func__);
test__skip();
return;
}
/* No need to attach it, just run it directly */
prog_fd = bpf_program__fd(skel->progs.load_acquire);
err = bpf_prog_test_run_opts(prog_fd, &topts);
if (!ASSERT_OK(err, "test_run_opts err"))
return;
if (!ASSERT_OK(topts.retval, "test_run_opts retval"))
return;
ASSERT_EQ(skel->arena->load_acquire8_result, 0x12,
"load_acquire8_result");
ASSERT_EQ(skel->arena->load_acquire16_result, 0x1234,
"load_acquire16_result");
ASSERT_EQ(skel->arena->load_acquire32_result, 0x12345678,
"load_acquire32_result");
ASSERT_EQ(skel->arena->load_acquire64_result, 0x1234567890abcdef,
"load_acquire64_result");
}
static void test_store_release(struct arena_atomics *skel)
{
LIBBPF_OPTS(bpf_test_run_opts, topts);
int err, prog_fd;
if (skel->data->skip_lacq_srel_tests) {
printf("%s:SKIP: ENABLE_ATOMICS_TESTS not defined, Clang doesn't support addr_space_cast, and/or JIT doesn't support store-release\n",
__func__);
test__skip();
return;
}
/* No need to attach it, just run it directly */
prog_fd = bpf_program__fd(skel->progs.store_release);
err = bpf_prog_test_run_opts(prog_fd, &topts);
if (!ASSERT_OK(err, "test_run_opts err"))
return;
if (!ASSERT_OK(topts.retval, "test_run_opts retval"))
return;
ASSERT_EQ(skel->arena->store_release8_result, 0x12,
"store_release8_result");
ASSERT_EQ(skel->arena->store_release16_result, 0x1234,
"store_release16_result");
ASSERT_EQ(skel->arena->store_release32_result, 0x12345678,
"store_release32_result");
ASSERT_EQ(skel->arena->store_release64_result, 0x1234567890abcdef,
"store_release64_result");
}
void test_arena_atomics(void)
{
struct arena_atomics *skel;
@@ -171,7 +231,7 @@ void test_arena_atomics(void)
if (!ASSERT_OK_PTR(skel, "arena atomics skeleton open"))
return;
if (skel->data->skip_tests) {
if (skel->data->skip_all_tests) {
printf("%s:SKIP:no ENABLE_ATOMICS_TESTS or no addr_space_cast support in clang",
__func__);
test__skip();
@@ -198,6 +258,10 @@ void test_arena_atomics(void)
test_xchg(skel);
if (test__start_subtest("uaf"))
test_uaf(skel);
if (test__start_subtest("load_acquire"))
test_load_acquire(skel);
if (test__start_subtest("store_release"))
test_store_release(skel);
cleanup:
arena_atomics__destroy(skel);

View File

@@ -0,0 +1,108 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
#include <test_progs.h>
#include <network_helpers.h>
#include <sys/sysinfo.h>
struct __qspinlock { int val; };
typedef struct __qspinlock arena_spinlock_t;
struct arena_qnode {
unsigned long next;
int count;
int locked;
};
#include "arena_spin_lock.skel.h"
static long cpu;
static int repeat;
pthread_barrier_t barrier;
static void *spin_lock_thread(void *arg)
{
int err, prog_fd = *(u32 *)arg;
LIBBPF_OPTS(bpf_test_run_opts, topts,
.data_in = &pkt_v4,
.data_size_in = sizeof(pkt_v4),
.repeat = repeat,
);
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(__sync_fetch_and_add(&cpu, 1), &cpuset);
ASSERT_OK(pthread_setaffinity_np(pthread_self(), sizeof(cpuset), &cpuset), "cpu affinity");
err = pthread_barrier_wait(&barrier);
if (err != PTHREAD_BARRIER_SERIAL_THREAD && err != 0)
ASSERT_FALSE(true, "pthread_barrier");
err = bpf_prog_test_run_opts(prog_fd, &topts);
ASSERT_OK(err, "test_run err");
ASSERT_EQ((int)topts.retval, 0, "test_run retval");
pthread_exit(arg);
}
static void test_arena_spin_lock_size(int size)
{
LIBBPF_OPTS(bpf_test_run_opts, topts);
struct arena_spin_lock *skel;
pthread_t thread_id[16];
int prog_fd, i, err;
void *ret;
if (get_nprocs() < 2) {
test__skip();
return;
}
skel = arena_spin_lock__open_and_load();
if (!ASSERT_OK_PTR(skel, "arena_spin_lock__open_and_load"))
return;
if (skel->data->test_skip == 2) {
test__skip();
goto end;
}
skel->bss->cs_count = size;
skel->bss->limit = repeat * 16;
ASSERT_OK(pthread_barrier_init(&barrier, NULL, 16), "barrier init");
prog_fd = bpf_program__fd(skel->progs.prog);
for (i = 0; i < 16; i++) {
err = pthread_create(&thread_id[i], NULL, &spin_lock_thread, &prog_fd);
if (!ASSERT_OK(err, "pthread_create"))
goto end_barrier;
}
for (i = 0; i < 16; i++) {
if (!ASSERT_OK(pthread_join(thread_id[i], &ret), "pthread_join"))
goto end_barrier;
if (!ASSERT_EQ(ret, &prog_fd, "ret == prog_fd"))
goto end_barrier;
}
ASSERT_EQ(skel->bss->counter, repeat * 16, "check counter value");
end_barrier:
pthread_barrier_destroy(&barrier);
end:
arena_spin_lock__destroy(skel);
return;
}
void test_arena_spin_lock(void)
{
repeat = 1000;
if (test__start_subtest("arena_spin_lock_1"))
test_arena_spin_lock_size(1);
cpu = 0;
if (test__start_subtest("arena_spin_lock_1000"))
test_arena_spin_lock_size(1000);
cpu = 0;
repeat = 100;
if (test__start_subtest("arena_spin_lock_50000"))
test_arena_spin_lock_size(50000);
}

View File

@@ -6,6 +6,10 @@
#include <test_progs.h>
#include "bloom_filter_map.skel.h"
#ifndef NUMA_NO_NODE
#define NUMA_NO_NODE (-1)
#endif
static void test_fail_cases(void)
{
LIBBPF_OPTS(bpf_map_create_opts, opts);
@@ -69,6 +73,7 @@ static void test_success_cases(void)
/* Create a map */
opts.map_flags = BPF_F_ZERO_SEED | BPF_F_NUMA_NODE;
opts.numa_node = NUMA_NO_NODE;
fd = bpf_map_create(BPF_MAP_TYPE_BLOOM_FILTER, NULL, 0, sizeof(value), 100, &opts);
if (!ASSERT_GE(fd, 0, "bpf_map_create bloom filter success case"))
return;

View File

@@ -323,19 +323,87 @@ static void test_task_pidfd(void)
static void test_task_sleepable(void)
{
struct bpf_iter_tasks *skel;
int pid, status, err, data_pipe[2], finish_pipe[2], c;
char *test_data = NULL;
char *test_data_long = NULL;
char *data[2];
if (!ASSERT_OK(pipe(data_pipe), "data_pipe") ||
!ASSERT_OK(pipe(finish_pipe), "finish_pipe"))
return;
skel = bpf_iter_tasks__open_and_load();
if (!ASSERT_OK_PTR(skel, "bpf_iter_tasks__open_and_load"))
return;
pid = fork();
if (!ASSERT_GE(pid, 0, "fork"))
return;
if (pid == 0) {
/* child */
close(data_pipe[0]);
close(finish_pipe[1]);
test_data = malloc(sizeof(char) * 10);
strncpy(test_data, "test_data", 10);
test_data[9] = '\0';
test_data_long = malloc(sizeof(char) * 5000);
for (int i = 0; i < 5000; ++i) {
if (i % 2 == 0)
test_data_long[i] = 'b';
else
test_data_long[i] = 'a';
}
test_data_long[4999] = '\0';
data[0] = test_data;
data[1] = test_data_long;
write(data_pipe[1], &data, sizeof(data));
/* keep child alive until after the test */
err = read(finish_pipe[0], &c, 1);
if (err != 1)
exit(-1);
close(data_pipe[1]);
close(finish_pipe[0]);
_exit(0);
}
/* parent */
close(data_pipe[1]);
close(finish_pipe[0]);
err = read(data_pipe[0], &data, sizeof(data));
ASSERT_EQ(err, sizeof(data), "read_check");
skel->bss->user_ptr = data[0];
skel->bss->user_ptr_long = data[1];
skel->bss->pid = pid;
do_dummy_read(skel->progs.dump_task_sleepable);
ASSERT_GT(skel->bss->num_expected_failure_copy_from_user_task, 0,
"num_expected_failure_copy_from_user_task");
ASSERT_GT(skel->bss->num_success_copy_from_user_task, 0,
"num_success_copy_from_user_task");
ASSERT_GT(skel->bss->num_expected_failure_copy_from_user_task_str, 0,
"num_expected_failure_copy_from_user_task_str");
ASSERT_GT(skel->bss->num_success_copy_from_user_task_str, 0,
"num_success_copy_from_user_task_str");
bpf_iter_tasks__destroy(skel);
write(finish_pipe[1], &c, 1);
err = waitpid(pid, &status, 0);
ASSERT_EQ(err, pid, "waitpid");
ASSERT_EQ(status, 0, "zero_child_exit");
close(data_pipe[0]);
close(finish_pipe[1]);
}
static void test_task_stack(void)

View File

@@ -72,11 +72,14 @@ static void test_bpf_nf_ct(int mode)
if (!ASSERT_OK(system(cmd), cmd))
goto end;
srv_port = (mode == TEST_XDP) ? 5005 : 5006;
srv_fd = start_server(AF_INET, SOCK_STREAM, "127.0.0.1", srv_port, TIMEOUT_MS);
srv_fd = start_server(AF_INET, SOCK_STREAM, "127.0.0.1", 0, TIMEOUT_MS);
if (!ASSERT_GE(srv_fd, 0, "start_server"))
goto end;
srv_port = get_socket_local_port(srv_fd);
if (!ASSERT_GE(srv_port, 0, "get_sock_local_port"))
goto end;
client_fd = connect_to_server(srv_fd);
if (!ASSERT_GE(client_fd, 0, "connect_to_server"))
goto end;
@@ -91,7 +94,7 @@ static void test_bpf_nf_ct(int mode)
skel->bss->saddr = peer_addr.sin_addr.s_addr;
skel->bss->sport = peer_addr.sin_port;
skel->bss->daddr = peer_addr.sin_addr.s_addr;
skel->bss->dport = htons(srv_port);
skel->bss->dport = srv_port;
if (mode == TEST_XDP)
prog_fd = bpf_program__fd(skel->progs.nf_xdp_ct_test);

View File

@@ -3866,11 +3866,11 @@ static struct btf_raw_test raw_tests[] = {
.err_str = "vlen != 0",
},
{
.descr = "decl_tag test #8, invalid kflag",
.descr = "decl_tag test #8, tag with kflag",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_VAR_ENC(NAME_TBD, 1, 0), /* [2] */
BTF_TYPE_ENC(NAME_TBD, BTF_INFO_ENC(BTF_KIND_DECL_TAG, 1, 0), 2), (-1),
BTF_DECL_ATTR_ENC(NAME_TBD, 2, -1),
BTF_END_RAW,
},
BTF_STR_SEC("\0local\0tag1"),
@@ -3881,8 +3881,6 @@ static struct btf_raw_test raw_tests[] = {
.key_type_id = 1,
.value_type_id = 1,
.max_entries = 1,
.btf_load_err = true,
.err_str = "Invalid btf_info kind_flag",
},
{
.descr = "decl_tag test #9, var, invalid component_idx",
@@ -4206,6 +4204,23 @@ static struct btf_raw_test raw_tests[] = {
.btf_load_err = true,
.err_str = "Type tags don't precede modifiers",
},
{
.descr = "type_tag test #7, tag with kflag",
.raw_types = {
BTF_TYPE_INT_ENC(0, BTF_INT_SIGNED, 0, 32, 4), /* [1] */
BTF_TYPE_ATTR_ENC(NAME_TBD, 1), /* [2] */
BTF_PTR_ENC(2), /* [3] */
BTF_END_RAW,
},
BTF_STR_SEC("\0tag"),
.map_type = BPF_MAP_TYPE_ARRAY,
.map_name = "tag_type_check_btf",
.key_size = sizeof(int),
.value_size = 4,
.key_type_id = 1,
.value_type_id = 1,
.max_entries = 1,
},
{
.descr = "enum64 test #1, unsigned, size 8",
.raw_types = {

View File

@@ -126,25 +126,68 @@ done:
return err;
}
static char *dump_buf;
static size_t dump_buf_sz;
static FILE *dump_buf_file;
struct test_ctx {
struct btf *btf;
struct btf_dump *d;
char *dump_buf;
size_t dump_buf_sz;
FILE *dump_buf_file;
};
static void test_ctx__free(struct test_ctx *t)
{
fclose(t->dump_buf_file);
free(t->dump_buf);
btf_dump__free(t->d);
btf__free(t->btf);
}
static int test_ctx__init(struct test_ctx *t)
{
t->dump_buf_file = open_memstream(&t->dump_buf, &t->dump_buf_sz);
if (!ASSERT_OK_PTR(t->dump_buf_file, "dump_memstream"))
return -1;
t->btf = btf__new_empty();
if (!ASSERT_OK_PTR(t->btf, "new_empty"))
goto err_out;
t->d = btf_dump__new(t->btf, btf_dump_printf, t->dump_buf_file, NULL);
if (!ASSERT_OK(libbpf_get_error(t->d), "btf_dump__new"))
goto err_out;
return 0;
err_out:
test_ctx__free(t);
return -1;
}
static void test_ctx__dump_and_compare(struct test_ctx *t,
const char *expected_output,
const char *message)
{
int i, err;
for (i = 1; i < btf__type_cnt(t->btf); i++) {
err = btf_dump__dump_type(t->d, i);
ASSERT_OK(err, "dump_type_ok");
}
fflush(t->dump_buf_file);
t->dump_buf[t->dump_buf_sz] = 0; /* some libc implementations don't do this */
ASSERT_STREQ(t->dump_buf, expected_output, message);
}
static void test_btf_dump_incremental(void)
{
struct btf *btf = NULL;
struct btf_dump *d = NULL;
int id, err, i;
struct test_ctx t = {};
struct btf *btf;
int id, err;
dump_buf_file = open_memstream(&dump_buf, &dump_buf_sz);
if (!ASSERT_OK_PTR(dump_buf_file, "dump_memstream"))
if (test_ctx__init(&t))
return;
btf = btf__new_empty();
if (!ASSERT_OK_PTR(btf, "new_empty"))
goto err_out;
d = btf_dump__new(btf, btf_dump_printf, dump_buf_file, NULL);
if (!ASSERT_OK(libbpf_get_error(d), "btf_dump__new"))
goto err_out;
btf = t.btf;
/* First, generate BTF corresponding to the following C code:
*
@@ -182,15 +225,7 @@ static void test_btf_dump_incremental(void)
err = btf__add_field(btf, "x", 4, 0, 0);
ASSERT_OK(err, "field_ok");
for (i = 1; i < btf__type_cnt(btf); i++) {
err = btf_dump__dump_type(d, i);
ASSERT_OK(err, "dump_type_ok");
}
fflush(dump_buf_file);
dump_buf[dump_buf_sz] = 0; /* some libc implementations don't do this */
ASSERT_STREQ(dump_buf,
test_ctx__dump_and_compare(&t,
"enum x;\n"
"\n"
"enum x {\n"
@@ -221,7 +256,7 @@ static void test_btf_dump_incremental(void)
* enum values don't conflict;
*
*/
fseek(dump_buf_file, 0, SEEK_SET);
fseek(t.dump_buf_file, 0, SEEK_SET);
id = btf__add_struct(btf, "s", 4);
ASSERT_EQ(id, 7, "struct_id");
@@ -232,14 +267,7 @@ static void test_btf_dump_incremental(void)
err = btf__add_field(btf, "s", 6, 64, 0);
ASSERT_OK(err, "field_ok");
for (i = 1; i < btf__type_cnt(btf); i++) {
err = btf_dump__dump_type(d, i);
ASSERT_OK(err, "dump_type_ok");
}
fflush(dump_buf_file);
dump_buf[dump_buf_sz] = 0; /* some libc implementations don't do this */
ASSERT_STREQ(dump_buf,
test_ctx__dump_and_compare(&t,
"struct s___2 {\n"
" enum x x;\n"
" enum {\n"
@@ -248,11 +276,53 @@ static void test_btf_dump_incremental(void)
" struct s s;\n"
"};\n\n" , "c_dump1");
err_out:
fclose(dump_buf_file);
free(dump_buf);
btf_dump__free(d);
btf__free(btf);
test_ctx__free(&t);
}
static void test_btf_dump_type_tags(void)
{
struct test_ctx t = {};
struct btf *btf;
int id, err;
if (test_ctx__init(&t))
return;
btf = t.btf;
/* Generate BTF corresponding to the following C code:
*
* struct s {
* void __attribute__((btf_type_tag(\"void_tag\"))) *p1;
* void __attribute__((void_attr)) *p2;
* };
*
*/
id = btf__add_type_tag(btf, "void_tag", 0);
ASSERT_EQ(id, 1, "type_tag_id");
id = btf__add_ptr(btf, id);
ASSERT_EQ(id, 2, "void_ptr_id1");
id = btf__add_type_attr(btf, "void_attr", 0);
ASSERT_EQ(id, 3, "type_attr_id");
id = btf__add_ptr(btf, id);
ASSERT_EQ(id, 4, "void_ptr_id2");
id = btf__add_struct(btf, "s", 8);
ASSERT_EQ(id, 5, "struct_id");
err = btf__add_field(btf, "p1", 2, 0, 0);
ASSERT_OK(err, "field_ok1");
err = btf__add_field(btf, "p2", 4, 0, 0);
ASSERT_OK(err, "field_ok2");
test_ctx__dump_and_compare(&t,
"struct s {\n"
" void __attribute__((btf_type_tag(\"void_tag\"))) *p1;\n"
" void __attribute__((void_attr)) *p2;\n"
"};\n\n", "dump_and_compare");
test_ctx__free(&t);
}
#define STRSIZE 4096
@@ -874,6 +944,9 @@ void test_btf_dump() {
if (test__start_subtest("btf_dump: incremental"))
test_btf_dump_incremental();
if (test__start_subtest("btf_dump: type_tags"))
test_btf_dump_type_tags();
btf = libbpf_find_kernel_btf();
if (!ASSERT_OK_PTR(btf, "no kernel BTF found"))
return;

View File

@@ -0,0 +1,128 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
#include <test_progs.h>
#include "cgroup_helpers.h"
#include "cgroup_preorder.skel.h"
static int run_getsockopt_test(int cg_parent, int cg_child, int sock_fd, bool all_preorder)
{
LIBBPF_OPTS(bpf_prog_attach_opts, opts);
enum bpf_attach_type prog_c_atype, prog_c2_atype, prog_p_atype, prog_p2_atype;
int prog_c_fd, prog_c2_fd, prog_p_fd, prog_p2_fd;
struct cgroup_preorder *skel = NULL;
struct bpf_program *prog;
__u8 *result, buf;
socklen_t optlen;
int err = 0;
skel = cgroup_preorder__open_and_load();
if (!ASSERT_OK_PTR(skel, "cgroup_preorder__open_and_load"))
return 0;
buf = 0x00;
err = setsockopt(sock_fd, SOL_IP, IP_TOS, &buf, 1);
if (!ASSERT_OK(err, "setsockopt"))
goto close_skel;
opts.flags = BPF_F_ALLOW_MULTI;
if (all_preorder)
opts.flags |= BPF_F_PREORDER;
prog = skel->progs.child;
prog_c_fd = bpf_program__fd(prog);
prog_c_atype = bpf_program__expected_attach_type(prog);
err = bpf_prog_attach_opts(prog_c_fd, cg_child, prog_c_atype, &opts);
if (!ASSERT_OK(err, "bpf_prog_attach_opts-child"))
goto close_skel;
opts.flags = BPF_F_ALLOW_MULTI | BPF_F_PREORDER;
prog = skel->progs.child_2;
prog_c2_fd = bpf_program__fd(prog);
prog_c2_atype = bpf_program__expected_attach_type(prog);
err = bpf_prog_attach_opts(prog_c2_fd, cg_child, prog_c2_atype, &opts);
if (!ASSERT_OK(err, "bpf_prog_attach_opts-child_2"))
goto detach_child;
optlen = 1;
err = getsockopt(sock_fd, SOL_IP, IP_TOS, &buf, &optlen);
if (!ASSERT_OK(err, "getsockopt"))
goto detach_child_2;
result = skel->bss->result;
if (all_preorder)
ASSERT_TRUE(result[0] == 1 && result[1] == 2, "child only");
else
ASSERT_TRUE(result[0] == 2 && result[1] == 1, "child only");
skel->bss->idx = 0;
memset(result, 0, 4);
opts.flags = BPF_F_ALLOW_MULTI;
if (all_preorder)
opts.flags |= BPF_F_PREORDER;
prog = skel->progs.parent;
prog_p_fd = bpf_program__fd(prog);
prog_p_atype = bpf_program__expected_attach_type(prog);
err = bpf_prog_attach_opts(prog_p_fd, cg_parent, prog_p_atype, &opts);
if (!ASSERT_OK(err, "bpf_prog_attach_opts-parent"))
goto detach_child_2;
opts.flags = BPF_F_ALLOW_MULTI | BPF_F_PREORDER;
prog = skel->progs.parent_2;
prog_p2_fd = bpf_program__fd(prog);
prog_p2_atype = bpf_program__expected_attach_type(prog);
err = bpf_prog_attach_opts(prog_p2_fd, cg_parent, prog_p2_atype, &opts);
if (!ASSERT_OK(err, "bpf_prog_attach_opts-parent_2"))
goto detach_parent;
err = getsockopt(sock_fd, SOL_IP, IP_TOS, &buf, &optlen);
if (!ASSERT_OK(err, "getsockopt"))
goto detach_parent_2;
if (all_preorder)
ASSERT_TRUE(result[0] == 3 && result[1] == 4 && result[2] == 1 && result[3] == 2,
"parent and child");
else
ASSERT_TRUE(result[0] == 4 && result[1] == 2 && result[2] == 1 && result[3] == 3,
"parent and child");
detach_parent_2:
ASSERT_OK(bpf_prog_detach2(prog_p2_fd, cg_parent, prog_p2_atype),
"bpf_prog_detach2-parent_2");
detach_parent:
ASSERT_OK(bpf_prog_detach2(prog_p_fd, cg_parent, prog_p_atype),
"bpf_prog_detach2-parent");
detach_child_2:
ASSERT_OK(bpf_prog_detach2(prog_c2_fd, cg_child, prog_c2_atype),
"bpf_prog_detach2-child_2");
detach_child:
ASSERT_OK(bpf_prog_detach2(prog_c_fd, cg_child, prog_c_atype),
"bpf_prog_detach2-child");
close_skel:
cgroup_preorder__destroy(skel);
return err;
}
void test_cgroup_preorder(void)
{
int cg_parent = -1, cg_child = -1, sock_fd = -1;
cg_parent = test__join_cgroup("/parent");
if (!ASSERT_GE(cg_parent, 0, "join_cgroup /parent"))
goto out;
cg_child = test__join_cgroup("/parent/child");
if (!ASSERT_GE(cg_child, 0, "join_cgroup /parent/child"))
goto out;
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (!ASSERT_GE(sock_fd, 0, "socket"))
goto out;
ASSERT_OK(run_getsockopt_test(cg_parent, cg_child, sock_fd, false), "getsockopt_test_1");
ASSERT_OK(run_getsockopt_test(cg_parent, cg_child, sock_fd, true), "getsockopt_test_2");
out:
close(sock_fd);
close(cg_child);
close(cg_parent);
}

View File

@@ -10,12 +10,18 @@
static int run_test(int cgroup_fd, int server_fd, bool classid)
{
struct connect4_dropper *skel;
int fd, err = 0;
int fd, err = 0, port;
skel = connect4_dropper__open_and_load();
if (!ASSERT_OK_PTR(skel, "skel_open"))
return -1;
port = get_socket_local_port(server_fd);
if (!ASSERT_GE(port, 0, "get_socket_local_port"))
return -1;
skel->bss->port = ntohs(port);
skel->links.connect_v4_dropper =
bpf_program__attach_cgroup(skel->progs.connect_v4_dropper,
cgroup_fd);
@@ -48,10 +54,9 @@ void test_cgroup_v1v2(void)
{
struct network_helper_opts opts = {};
int server_fd, client_fd, cgroup_fd;
static const int port = 60120;
/* Step 1: Check base connectivity works without any BPF. */
server_fd = start_server(AF_INET, SOCK_STREAM, NULL, port, 0);
server_fd = start_server(AF_INET, SOCK_STREAM, NULL, 0, 0);
if (!ASSERT_GE(server_fd, 0, "server_fd"))
return;
client_fd = connect_to_fd_opts(server_fd, &opts);
@@ -66,7 +71,7 @@ void test_cgroup_v1v2(void)
cgroup_fd = test__join_cgroup("/connect_dropper");
if (!ASSERT_GE(cgroup_fd, 0, "cgroup_fd"))
return;
server_fd = start_server(AF_INET, SOCK_STREAM, NULL, port, 0);
server_fd = start_server(AF_INET, SOCK_STREAM, NULL, 0, 0);
if (!ASSERT_GE(server_fd, 0, "server_fd")) {
close(cgroup_fd);
return;

View File

@@ -1,107 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
#include "bpf/libbpf.h"
#include "changes_pkt_data_freplace.skel.h"
#include "changes_pkt_data.skel.h"
#include <test_progs.h>
static void print_verifier_log(const char *log)
{
if (env.verbosity >= VERBOSE_VERY)
fprintf(stdout, "VERIFIER LOG:\n=============\n%s=============\n", log);
}
static void test_aux(const char *main_prog_name,
const char *to_be_replaced,
const char *replacement,
bool expect_load)
{
struct changes_pkt_data_freplace *freplace = NULL;
struct bpf_program *freplace_prog = NULL;
struct bpf_program *main_prog = NULL;
LIBBPF_OPTS(bpf_object_open_opts, opts);
struct changes_pkt_data *main = NULL;
char log[16*1024];
int err;
opts.kernel_log_buf = log;
opts.kernel_log_size = sizeof(log);
if (env.verbosity >= VERBOSE_SUPER)
opts.kernel_log_level = 1 | 2 | 4;
main = changes_pkt_data__open_opts(&opts);
if (!ASSERT_OK_PTR(main, "changes_pkt_data__open"))
goto out;
main_prog = bpf_object__find_program_by_name(main->obj, main_prog_name);
if (!ASSERT_OK_PTR(main_prog, "main_prog"))
goto out;
bpf_program__set_autoload(main_prog, true);
err = changes_pkt_data__load(main);
print_verifier_log(log);
if (!ASSERT_OK(err, "changes_pkt_data__load"))
goto out;
freplace = changes_pkt_data_freplace__open_opts(&opts);
if (!ASSERT_OK_PTR(freplace, "changes_pkt_data_freplace__open"))
goto out;
freplace_prog = bpf_object__find_program_by_name(freplace->obj, replacement);
if (!ASSERT_OK_PTR(freplace_prog, "freplace_prog"))
goto out;
bpf_program__set_autoload(freplace_prog, true);
bpf_program__set_autoattach(freplace_prog, true);
bpf_program__set_attach_target(freplace_prog,
bpf_program__fd(main_prog),
to_be_replaced);
err = changes_pkt_data_freplace__load(freplace);
print_verifier_log(log);
if (expect_load) {
ASSERT_OK(err, "changes_pkt_data_freplace__load");
} else {
ASSERT_ERR(err, "changes_pkt_data_freplace__load");
ASSERT_HAS_SUBSTR(log, "Extension program changes packet data", "error log");
}
out:
changes_pkt_data_freplace__destroy(freplace);
changes_pkt_data__destroy(main);
}
/* There are two global subprograms in both changes_pkt_data.skel.h:
* - one changes packet data;
* - another does not.
* It is ok to freplace subprograms that change packet data with those
* that either do or do not. It is only ok to freplace subprograms
* that do not change packet data with those that do not as well.
* The below tests check outcomes for each combination of such freplace.
* Also test a case when main subprogram itself is replaced and is a single
* subprogram in a program.
*/
void test_changes_pkt_data_freplace(void)
{
struct {
const char *main;
const char *to_be_replaced;
bool changes;
} mains[] = {
{ "main_with_subprogs", "changes_pkt_data", true },
{ "main_with_subprogs", "does_not_change_pkt_data", false },
{ "main_changes", "main_changes", true },
{ "main_does_not_change", "main_does_not_change", false },
};
struct {
const char *func;
bool changes;
} replacements[] = {
{ "changes_pkt_data", true },
{ "does_not_change_pkt_data", false }
};
char buf[64];
for (int i = 0; i < ARRAY_SIZE(mains); ++i) {
for (int j = 0; j < ARRAY_SIZE(replacements); ++j) {
snprintf(buf, sizeof(buf), "%s_with_%s",
mains[i].to_be_replaced, replacements[j].func);
if (!test__start_subtest(buf))
continue;
test_aux(mains[i].main, mains[i].to_be_replaced, replacements[j].func,
mains[i].changes || !replacements[j].changes);
}
}
}

View File

@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
#include "compute_live_registers.skel.h"
#include "test_progs.h"
void test_compute_live_registers(void)
{
RUN_TESTS(compute_live_registers);
}

View File

@@ -85,11 +85,11 @@ static int duration = 0;
#define NESTING_ERR_CASE(name) { \
NESTING_CASE_COMMON(name), \
.fails = true, \
.run_btfgen_fails = true, \
.run_btfgen_fails = true, \
}
#define ARRAYS_DATA(struct_name) STRUCT_TO_CHAR_PTR(struct_name) { \
.a = { [2] = 1 }, \
.a = { [2] = 1, [3] = 11 }, \
.b = { [1] = { [2] = { [3] = 2 } } }, \
.c = { [1] = { .c = 3 } }, \
.d = { [0] = { [0] = { .d = 4 } } }, \
@@ -108,6 +108,7 @@ static int duration = 0;
.input_len = sizeof(struct core_reloc_##name), \
.output = STRUCT_TO_CHAR_PTR(core_reloc_arrays_output) { \
.a2 = 1, \
.a3 = 12, \
.b123 = 2, \
.c1c = 3, \
.d00d = 4, \
@@ -602,6 +603,7 @@ static const struct core_reloc_test_case test_cases[] = {
ARRAYS_ERR_CASE(arrays___err_non_array),
ARRAYS_ERR_CASE(arrays___err_wrong_val_type),
ARRAYS_ERR_CASE(arrays___err_bad_zero_sz_arr),
ARRAYS_ERR_CASE(arrays___err_bad_signed_arr_elem_sz),
/* enum/ptr/int handling scenarios */
PRIMITIVES_CASE(primitives),

View File

@@ -25,6 +25,10 @@ static const char * const cpumask_success_testcases[] = {
"test_global_mask_nested_deep_rcu",
"test_global_mask_nested_deep_array_rcu",
"test_cpumask_weight",
"test_refcount_null_tracking",
"test_populate_reject_small_mask",
"test_populate_reject_unaligned",
"test_populate",
};
static void verify_success(const char *prog_name)
@@ -78,6 +82,5 @@ void test_cpumask(void)
verify_success(cpumask_success_testcases[i]);
}
RUN_TESTS(cpumask_success);
RUN_TESTS(cpumask_failure);
}

View File

@@ -10,6 +10,7 @@ enum test_setup_type {
SETUP_SYSCALL_SLEEP,
SETUP_SKB_PROG,
SETUP_SKB_PROG_TP,
SETUP_XDP_PROG,
};
static struct {
@@ -18,6 +19,8 @@ static struct {
} success_tests[] = {
{"test_read_write", SETUP_SYSCALL_SLEEP},
{"test_dynptr_data", SETUP_SYSCALL_SLEEP},
{"test_dynptr_copy", SETUP_SYSCALL_SLEEP},
{"test_dynptr_copy_xdp", SETUP_XDP_PROG},
{"test_ringbuf", SETUP_SYSCALL_SLEEP},
{"test_skb_readonly", SETUP_SKB_PROG},
{"test_dynptr_skb_data", SETUP_SKB_PROG},
@@ -120,6 +123,24 @@ static void verify_success(const char *prog_name, enum test_setup_type setup_typ
break;
}
case SETUP_XDP_PROG:
{
char data[5000];
int err, prog_fd;
LIBBPF_OPTS(bpf_test_run_opts, opts,
.data_in = &data,
.data_size_in = sizeof(data),
.repeat = 1,
);
prog_fd = bpf_program__fd(prog);
err = bpf_prog_test_run_opts(prog_fd, &opts);
if (!ASSERT_OK(err, "test_run"))
goto cleanup;
break;
}
}
ASSERT_EQ(skel->bss->err, 0, "err");

View File

@@ -83,8 +83,8 @@ static inline int bpf_prog_get_map_ids(int prog_fd, __u32 *nr_map_ids, __u32 *ma
int err;
memset(&info, 0, len);
info.nr_map_ids = *nr_map_ids,
info.map_ids = ptr_to_u64(map_ids),
info.nr_map_ids = *nr_map_ids;
info.map_ids = ptr_to_u64(map_ids);
err = bpf_prog_get_info_by_fd(prog_fd, &info, &len);
if (!ASSERT_OK(err, "bpf_prog_get_info_by_fd"))

View File

@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
#include <test_progs.h>
#include "fexit_noreturns.skel.h"
void test_fexit_noreturns(void)
{
RUN_TESTS(fexit_noreturns);
}

View File

@@ -8,11 +8,12 @@
#include <unistd.h>
#include <test_progs.h>
#include "test_get_xattr.skel.h"
#include "test_set_remove_xattr.skel.h"
#include "test_fsverity.skel.h"
static const char testfile[] = "/tmp/test_progs_fs_kfuncs";
static void test_xattr(void)
static void test_get_xattr(const char *name, const char *value, bool allow_access)
{
struct test_get_xattr *skel = NULL;
int fd = -1, err;
@@ -25,7 +26,7 @@ static void test_xattr(void)
close(fd);
fd = -1;
err = setxattr(testfile, "user.kfuncs", "hello", sizeof("hello"), 0);
err = setxattr(testfile, name, value, strlen(value) + 1, 0);
if (err && errno == EOPNOTSUPP) {
printf("%s:SKIP:local fs doesn't support xattr (%d)\n"
"To run this test, make sure /tmp filesystem supports xattr.\n",
@@ -48,16 +49,23 @@ static void test_xattr(void)
goto out;
fd = open(testfile, O_RDONLY, 0644);
if (!ASSERT_GE(fd, 0, "open_file"))
goto out;
ASSERT_EQ(skel->bss->found_xattr_from_file, 1, "found_xattr_from_file");
/* Trigger security_inode_getxattr */
err = getxattr(testfile, "user.kfuncs", v, sizeof(v));
ASSERT_EQ(err, -1, "getxattr_return");
ASSERT_EQ(errno, EINVAL, "getxattr_errno");
ASSERT_EQ(skel->bss->found_xattr_from_dentry, 1, "found_xattr_from_dentry");
err = getxattr(testfile, name, v, sizeof(v));
if (allow_access) {
ASSERT_EQ(err, -1, "getxattr_return");
ASSERT_EQ(errno, EINVAL, "getxattr_errno");
ASSERT_EQ(skel->bss->found_xattr_from_file, 1, "found_xattr_from_file");
ASSERT_EQ(skel->bss->found_xattr_from_dentry, 1, "found_xattr_from_dentry");
} else {
ASSERT_EQ(err, strlen(value) + 1, "getxattr_return");
ASSERT_EQ(skel->bss->found_xattr_from_file, 0, "found_xattr_from_file");
ASSERT_EQ(skel->bss->found_xattr_from_dentry, 0, "found_xattr_from_dentry");
}
out:
close(fd);
@@ -65,6 +73,127 @@ out:
remove(testfile);
}
/* xattr value we will set to security.bpf.foo */
static const char value_foo[] = "hello";
static void read_and_validate_foo(struct test_set_remove_xattr *skel)
{
char value_out[32];
int err;
err = getxattr(testfile, skel->rodata->xattr_foo, value_out, sizeof(value_out));
ASSERT_EQ(err, sizeof(value_foo), "getxattr size foo");
ASSERT_EQ(strncmp(value_out, value_foo, sizeof(value_foo)), 0, "strncmp value_foo");
}
static void set_foo(struct test_set_remove_xattr *skel)
{
ASSERT_OK(setxattr(testfile, skel->rodata->xattr_foo, value_foo, strlen(value_foo) + 1, 0),
"setxattr foo");
}
static void validate_bar_match(struct test_set_remove_xattr *skel)
{
char value_out[32];
int err;
err = getxattr(testfile, skel->rodata->xattr_bar, value_out, sizeof(value_out));
ASSERT_EQ(err, sizeof(skel->data->value_bar), "getxattr size bar");
ASSERT_EQ(strncmp(value_out, skel->data->value_bar, sizeof(skel->data->value_bar)), 0,
"strncmp value_bar");
}
static void validate_bar_removed(struct test_set_remove_xattr *skel)
{
char value_out[32];
int err;
err = getxattr(testfile, skel->rodata->xattr_bar, value_out, sizeof(value_out));
ASSERT_LT(err, 0, "getxattr size bar should fail");
}
static void test_set_remove_xattr(void)
{
struct test_set_remove_xattr *skel = NULL;
int fd = -1, err;
fd = open(testfile, O_CREAT | O_RDONLY, 0644);
if (!ASSERT_GE(fd, 0, "create_file"))
return;
close(fd);
fd = -1;
skel = test_set_remove_xattr__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_set_remove_xattr__open_and_load"))
return;
/* Set security.bpf.foo to "hello" */
err = setxattr(testfile, skel->rodata->xattr_foo, value_foo, strlen(value_foo) + 1, 0);
if (err && errno == EOPNOTSUPP) {
printf("%s:SKIP:local fs doesn't support xattr (%d)\n"
"To run this test, make sure /tmp filesystem supports xattr.\n",
__func__, errno);
test__skip();
goto out;
}
if (!ASSERT_OK(err, "setxattr"))
goto out;
skel->bss->monitored_pid = getpid();
err = test_set_remove_xattr__attach(skel);
if (!ASSERT_OK(err, "test_set_remove_xattr__attach"))
goto out;
/* First, test not _locked version of the kfuncs with getxattr. */
/* Read security.bpf.foo and trigger test_inode_getxattr. This
* bpf program will set security.bpf.bar to "world".
*/
read_and_validate_foo(skel);
validate_bar_match(skel);
/* Read security.bpf.foo and trigger test_inode_getxattr again.
* This will remove xattr security.bpf.bar.
*/
read_and_validate_foo(skel);
validate_bar_removed(skel);
ASSERT_TRUE(skel->bss->set_security_bpf_bar_success, "set_security_bpf_bar_success");
ASSERT_TRUE(skel->bss->remove_security_bpf_bar_success, "remove_security_bpf_bar_success");
ASSERT_TRUE(skel->bss->set_security_selinux_fail, "set_security_selinux_fail");
ASSERT_TRUE(skel->bss->remove_security_selinux_fail, "remove_security_selinux_fail");
/* Second, test _locked version of the kfuncs, with setxattr */
/* Set security.bpf.foo and trigger test_inode_setxattr. This
* bpf program will set security.bpf.bar to "world".
*/
set_foo(skel);
validate_bar_match(skel);
/* Set security.bpf.foo and trigger test_inode_setxattr again.
* This will remove xattr security.bpf.bar.
*/
set_foo(skel);
validate_bar_removed(skel);
ASSERT_TRUE(skel->bss->locked_set_security_bpf_bar_success,
"locked_set_security_bpf_bar_success");
ASSERT_TRUE(skel->bss->locked_remove_security_bpf_bar_success,
"locked_remove_security_bpf_bar_success");
ASSERT_TRUE(skel->bss->locked_set_security_selinux_fail,
"locked_set_security_selinux_fail");
ASSERT_TRUE(skel->bss->locked_remove_security_selinux_fail,
"locked_remove_security_selinux_fail");
out:
close(fd);
test_set_remove_xattr__destroy(skel);
remove(testfile);
}
#ifndef SHA256_DIGEST_SIZE
#define SHA256_DIGEST_SIZE 32
#endif
@@ -141,8 +270,21 @@ out:
void test_fs_kfuncs(void)
{
if (test__start_subtest("xattr"))
test_xattr();
/* Matches xattr_names in progs/test_get_xattr.c */
if (test__start_subtest("user_xattr"))
test_get_xattr("user.kfuncs", "hello", true);
if (test__start_subtest("security_bpf_xattr"))
test_get_xattr("security.bpf.xxx", "hello", true);
if (test__start_subtest("security_bpf_xattr_error"))
test_get_xattr("security.bpf", "hello", false);
if (test__start_subtest("security_selinux_xattr_error"))
test_get_xattr("security.selinux", "hello", false);
if (test__start_subtest("set_remove_xattr"))
test_set_remove_xattr();
if (test__start_subtest("fsverity"))
test_fsverity();

View File

@@ -0,0 +1,43 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Microsoft */
#include <test_progs.h>
#include "kfunc_call_test.skel.h"
#include "kfunc_call_test.lskel.h"
#include "test_kernel_flag.skel.h"
void test_kernel_flag(void)
{
struct test_kernel_flag *lsm_skel;
struct kfunc_call_test *skel = NULL;
struct kfunc_call_test_lskel *lskel = NULL;
int ret;
lsm_skel = test_kernel_flag__open_and_load();
if (!ASSERT_OK_PTR(lsm_skel, "lsm_skel"))
return;
lsm_skel->bss->monitored_tid = gettid();
ret = test_kernel_flag__attach(lsm_skel);
if (!ASSERT_OK(ret, "test_kernel_flag__attach"))
goto close_prog;
/* Test with skel. This should pass the gatekeeper */
skel = kfunc_call_test__open_and_load();
if (!ASSERT_OK_PTR(skel, "skel"))
goto close_prog;
/* Test with lskel. This should fail due to blocking kernel-based bpf() invocations */
lskel = kfunc_call_test_lskel__open_and_load();
if (!ASSERT_ERR_PTR(lskel, "lskel"))
goto close_prog;
close_prog:
if (skel)
kfunc_call_test__destroy(skel);
if (lskel)
kfunc_call_test_lskel__destroy(lskel);
lsm_skel->bss->monitored_tid = 0;
test_kernel_flag__destroy(lsm_skel);
}

View File

@@ -0,0 +1,540 @@
// SPDX-License-Identifier: GPL-2.0-only
#include <netinet/in.h>
#include "network_helpers.h"
#include "test_progs.h"
#define BPF_FILE "test_lwt_ip_encap.bpf.o"
#define NETNS_NAME_SIZE 32
#define NETNS_BASE "ns-lwt-ip-encap"
#define IP4_ADDR_1 "172.16.1.100"
#define IP4_ADDR_2 "172.16.2.100"
#define IP4_ADDR_3 "172.16.3.100"
#define IP4_ADDR_4 "172.16.4.100"
#define IP4_ADDR_5 "172.16.5.100"
#define IP4_ADDR_6 "172.16.6.100"
#define IP4_ADDR_7 "172.16.7.100"
#define IP4_ADDR_8 "172.16.8.100"
#define IP4_ADDR_GRE "172.16.16.100"
#define IP4_ADDR_SRC IP4_ADDR_1
#define IP4_ADDR_DST IP4_ADDR_4
#define IP6_ADDR_1 "fb01::1"
#define IP6_ADDR_2 "fb02::1"
#define IP6_ADDR_3 "fb03::1"
#define IP6_ADDR_4 "fb04::1"
#define IP6_ADDR_5 "fb05::1"
#define IP6_ADDR_6 "fb06::1"
#define IP6_ADDR_7 "fb07::1"
#define IP6_ADDR_8 "fb08::1"
#define IP6_ADDR_GRE "fb10::1"
#define IP6_ADDR_SRC IP6_ADDR_1
#define IP6_ADDR_DST IP6_ADDR_4
/* Setup/topology:
*
* NS1 NS2 NS3
* veth1 <---> veth2 veth3 <---> veth4 (the top route)
* veth5 <---> veth6 veth7 <---> veth8 (the bottom route)
*
* Each vethN gets IP[4|6]_ADDR_N address.
*
* IP*_ADDR_SRC = IP*_ADDR_1
* IP*_ADDR_DST = IP*_ADDR_4
*
* All tests test pings from IP*_ADDR__SRC to IP*_ADDR_DST.
*
* By default, routes are configured to allow packets to go
* IP*_ADDR_1 <=> IP*_ADDR_2 <=> IP*_ADDR_3 <=> IP*_ADDR_4 (the top route).
*
* A GRE device is installed in NS3 with IP*_ADDR_GRE, and
* NS1/NS2 are configured to route packets to IP*_ADDR_GRE via IP*_ADDR_8
* (the bottom route).
*
* Tests:
*
* 1. Routes NS2->IP*_ADDR_DST are brought down, so the only way a ping
* from IP*_ADDR_SRC to IP*_ADDR_DST can work is via IP*_ADDR_GRE.
*
* 2a. In an egress test, a bpf LWT_XMIT program is installed on veth1
* that encaps the packets with an IP/GRE header to route to IP*_ADDR_GRE.
*
* ping: SRC->[encap at veth1:egress]->GRE:decap->DST
* ping replies go DST->SRC directly
*
* 2b. In an ingress test, a bpf LWT_IN program is installed on veth2
* that encaps the packets with an IP/GRE header to route to IP*_ADDR_GRE.
*
* ping: SRC->[encap at veth2:ingress]->GRE:decap->DST
* ping replies go DST->SRC directly
*/
static int create_ns(char *name, size_t name_sz)
{
if (!name)
goto fail;
if (!ASSERT_OK(append_tid(name, name_sz), "append TID"))
goto fail;
SYS(fail, "ip netns add %s", name);
/* rp_filter gets confused by what these tests are doing, so disable it */
SYS(fail, "ip netns exec %s sysctl -wq net.ipv4.conf.all.rp_filter=0", name);
SYS(fail, "ip netns exec %s sysctl -wq net.ipv4.conf.default.rp_filter=0", name);
/* Disable IPv6 DAD because it sometimes takes too long and fails tests */
SYS(fail, "ip netns exec %s sysctl -wq net.ipv6.conf.all.accept_dad=0", name);
SYS(fail, "ip netns exec %s sysctl -wq net.ipv6.conf.default.accept_dad=0", name);
return 0;
fail:
return -1;
}
static int set_top_addr(const char *ns1, const char *ns2, const char *ns3)
{
SYS(fail, "ip -n %s a add %s/24 dev veth1", ns1, IP4_ADDR_1);
SYS(fail, "ip -n %s a add %s/24 dev veth2", ns2, IP4_ADDR_2);
SYS(fail, "ip -n %s a add %s/24 dev veth3", ns2, IP4_ADDR_3);
SYS(fail, "ip -n %s a add %s/24 dev veth4", ns3, IP4_ADDR_4);
SYS(fail, "ip -n %s -6 a add %s/128 dev veth1", ns1, IP6_ADDR_1);
SYS(fail, "ip -n %s -6 a add %s/128 dev veth2", ns2, IP6_ADDR_2);
SYS(fail, "ip -n %s -6 a add %s/128 dev veth3", ns2, IP6_ADDR_3);
SYS(fail, "ip -n %s -6 a add %s/128 dev veth4", ns3, IP6_ADDR_4);
SYS(fail, "ip -n %s link set dev veth1 up", ns1);
SYS(fail, "ip -n %s link set dev veth2 up", ns2);
SYS(fail, "ip -n %s link set dev veth3 up", ns2);
SYS(fail, "ip -n %s link set dev veth4 up", ns3);
return 0;
fail:
return 1;
}
static int set_bottom_addr(const char *ns1, const char *ns2, const char *ns3)
{
SYS(fail, "ip -n %s a add %s/24 dev veth5", ns1, IP4_ADDR_5);
SYS(fail, "ip -n %s a add %s/24 dev veth6", ns2, IP4_ADDR_6);
SYS(fail, "ip -n %s a add %s/24 dev veth7", ns2, IP4_ADDR_7);
SYS(fail, "ip -n %s a add %s/24 dev veth8", ns3, IP4_ADDR_8);
SYS(fail, "ip -n %s -6 a add %s/128 dev veth5", ns1, IP6_ADDR_5);
SYS(fail, "ip -n %s -6 a add %s/128 dev veth6", ns2, IP6_ADDR_6);
SYS(fail, "ip -n %s -6 a add %s/128 dev veth7", ns2, IP6_ADDR_7);
SYS(fail, "ip -n %s -6 a add %s/128 dev veth8", ns3, IP6_ADDR_8);
SYS(fail, "ip -n %s link set dev veth5 up", ns1);
SYS(fail, "ip -n %s link set dev veth6 up", ns2);
SYS(fail, "ip -n %s link set dev veth7 up", ns2);
SYS(fail, "ip -n %s link set dev veth8 up", ns3);
return 0;
fail:
return 1;
}
static int configure_vrf(const char *ns1, const char *ns2)
{
if (!ns1 || !ns2)
goto fail;
SYS(fail, "ip -n %s link add red type vrf table 1001", ns1);
SYS(fail, "ip -n %s link set red up", ns1);
SYS(fail, "ip -n %s route add table 1001 unreachable default metric 8192", ns1);
SYS(fail, "ip -n %s -6 route add table 1001 unreachable default metric 8192", ns1);
SYS(fail, "ip -n %s link set veth1 vrf red", ns1);
SYS(fail, "ip -n %s link set veth5 vrf red", ns1);
SYS(fail, "ip -n %s link add red type vrf table 1001", ns2);
SYS(fail, "ip -n %s link set red up", ns2);
SYS(fail, "ip -n %s route add table 1001 unreachable default metric 8192", ns2);
SYS(fail, "ip -n %s -6 route add table 1001 unreachable default metric 8192", ns2);
SYS(fail, "ip -n %s link set veth2 vrf red", ns2);
SYS(fail, "ip -n %s link set veth3 vrf red", ns2);
SYS(fail, "ip -n %s link set veth6 vrf red", ns2);
SYS(fail, "ip -n %s link set veth7 vrf red", ns2);
return 0;
fail:
return -1;
}
static int configure_ns1(const char *ns1, const char *vrf)
{
struct nstoken *nstoken = NULL;
if (!ns1 || !vrf)
goto fail;
nstoken = open_netns(ns1);
if (!ASSERT_OK_PTR(nstoken, "open ns1"))
goto fail;
/* Top route */
SYS(fail, "ip route add %s/32 dev veth1 %s", IP4_ADDR_2, vrf);
SYS(fail, "ip route add default dev veth1 via %s %s", IP4_ADDR_2, vrf);
SYS(fail, "ip -6 route add %s/128 dev veth1 %s", IP6_ADDR_2, vrf);
SYS(fail, "ip -6 route add default dev veth1 via %s %s", IP6_ADDR_2, vrf);
/* Bottom route */
SYS(fail, "ip route add %s/32 dev veth5 %s", IP4_ADDR_6, vrf);
SYS(fail, "ip route add %s/32 dev veth5 via %s %s", IP4_ADDR_7, IP4_ADDR_6, vrf);
SYS(fail, "ip route add %s/32 dev veth5 via %s %s", IP4_ADDR_8, IP4_ADDR_6, vrf);
SYS(fail, "ip -6 route add %s/128 dev veth5 %s", IP6_ADDR_6, vrf);
SYS(fail, "ip -6 route add %s/128 dev veth5 via %s %s", IP6_ADDR_7, IP6_ADDR_6, vrf);
SYS(fail, "ip -6 route add %s/128 dev veth5 via %s %s", IP6_ADDR_8, IP6_ADDR_6, vrf);
close_netns(nstoken);
return 0;
fail:
close_netns(nstoken);
return -1;
}
static int configure_ns2(const char *ns2, const char *vrf)
{
struct nstoken *nstoken = NULL;
if (!ns2 || !vrf)
goto fail;
nstoken = open_netns(ns2);
if (!ASSERT_OK_PTR(nstoken, "open ns2"))
goto fail;
SYS(fail, "ip netns exec %s sysctl -wq net.ipv4.ip_forward=1", ns2);
SYS(fail, "ip netns exec %s sysctl -wq net.ipv6.conf.all.forwarding=1", ns2);
/* Top route */
SYS(fail, "ip route add %s/32 dev veth2 %s", IP4_ADDR_1, vrf);
SYS(fail, "ip route add %s/32 dev veth3 %s", IP4_ADDR_4, vrf);
SYS(fail, "ip -6 route add %s/128 dev veth2 %s", IP6_ADDR_1, vrf);
SYS(fail, "ip -6 route add %s/128 dev veth3 %s", IP6_ADDR_4, vrf);
/* Bottom route */
SYS(fail, "ip route add %s/32 dev veth6 %s", IP4_ADDR_5, vrf);
SYS(fail, "ip route add %s/32 dev veth7 %s", IP4_ADDR_8, vrf);
SYS(fail, "ip -6 route add %s/128 dev veth6 %s", IP6_ADDR_5, vrf);
SYS(fail, "ip -6 route add %s/128 dev veth7 %s", IP6_ADDR_8, vrf);
close_netns(nstoken);
return 0;
fail:
close_netns(nstoken);
return -1;
}
static int configure_ns3(const char *ns3)
{
struct nstoken *nstoken = NULL;
if (!ns3)
goto fail;
nstoken = open_netns(ns3);
if (!ASSERT_OK_PTR(nstoken, "open ns3"))
goto fail;
/* Top route */
SYS(fail, "ip route add %s/32 dev veth4", IP4_ADDR_3);
SYS(fail, "ip route add %s/32 dev veth4 via %s", IP4_ADDR_1, IP4_ADDR_3);
SYS(fail, "ip route add %s/32 dev veth4 via %s", IP4_ADDR_2, IP4_ADDR_3);
SYS(fail, "ip -6 route add %s/128 dev veth4", IP6_ADDR_3);
SYS(fail, "ip -6 route add %s/128 dev veth4 via %s", IP6_ADDR_1, IP6_ADDR_3);
SYS(fail, "ip -6 route add %s/128 dev veth4 via %s", IP6_ADDR_2, IP6_ADDR_3);
/* Bottom route */
SYS(fail, "ip route add %s/32 dev veth8", IP4_ADDR_7);
SYS(fail, "ip route add %s/32 dev veth8 via %s", IP4_ADDR_5, IP4_ADDR_7);
SYS(fail, "ip route add %s/32 dev veth8 via %s", IP4_ADDR_6, IP4_ADDR_7);
SYS(fail, "ip -6 route add %s/128 dev veth8", IP6_ADDR_7);
SYS(fail, "ip -6 route add %s/128 dev veth8 via %s", IP6_ADDR_5, IP6_ADDR_7);
SYS(fail, "ip -6 route add %s/128 dev veth8 via %s", IP6_ADDR_6, IP6_ADDR_7);
/* Configure IPv4 GRE device */
SYS(fail, "ip tunnel add gre_dev mode gre remote %s local %s ttl 255",
IP4_ADDR_1, IP4_ADDR_GRE);
SYS(fail, "ip link set gre_dev up");
SYS(fail, "ip a add %s dev gre_dev", IP4_ADDR_GRE);
/* Configure IPv6 GRE device */
SYS(fail, "ip tunnel add gre6_dev mode ip6gre remote %s local %s ttl 255",
IP6_ADDR_1, IP6_ADDR_GRE);
SYS(fail, "ip link set gre6_dev up");
SYS(fail, "ip a add %s dev gre6_dev", IP6_ADDR_GRE);
close_netns(nstoken);
return 0;
fail:
close_netns(nstoken);
return -1;
}
static int setup_network(char *ns1, char *ns2, char *ns3, const char *vrf)
{
if (!ns1 || !ns2 || !ns3 || !vrf)
goto fail;
SYS(fail, "ip -n %s link add veth1 type veth peer name veth2 netns %s", ns1, ns2);
SYS(fail, "ip -n %s link add veth3 type veth peer name veth4 netns %s", ns2, ns3);
SYS(fail, "ip -n %s link add veth5 type veth peer name veth6 netns %s", ns1, ns2);
SYS(fail, "ip -n %s link add veth7 type veth peer name veth8 netns %s", ns2, ns3);
if (vrf[0]) {
if (!ASSERT_OK(configure_vrf(ns1, ns2), "configure vrf"))
goto fail;
}
if (!ASSERT_OK(set_top_addr(ns1, ns2, ns3), "set top addresses"))
goto fail;
if (!ASSERT_OK(set_bottom_addr(ns1, ns2, ns3), "set bottom addresses"))
goto fail;
if (!ASSERT_OK(configure_ns1(ns1, vrf), "configure ns1 routes"))
goto fail;
if (!ASSERT_OK(configure_ns2(ns2, vrf), "configure ns2 routes"))
goto fail;
if (!ASSERT_OK(configure_ns3(ns3), "configure ns3 routes"))
goto fail;
/* Link bottom route to the GRE tunnels */
SYS(fail, "ip -n %s route add %s/32 dev veth5 via %s %s",
ns1, IP4_ADDR_GRE, IP4_ADDR_6, vrf);
SYS(fail, "ip -n %s route add %s/32 dev veth7 via %s %s",
ns2, IP4_ADDR_GRE, IP4_ADDR_8, vrf);
SYS(fail, "ip -n %s -6 route add %s/128 dev veth5 via %s %s",
ns1, IP6_ADDR_GRE, IP6_ADDR_6, vrf);
SYS(fail, "ip -n %s -6 route add %s/128 dev veth7 via %s %s",
ns2, IP6_ADDR_GRE, IP6_ADDR_8, vrf);
return 0;
fail:
return -1;
}
static int remove_routes_to_gredev(const char *ns1, const char *ns2, const char *vrf)
{
SYS(fail, "ip -n %s route del %s dev veth5 %s", ns1, IP4_ADDR_GRE, vrf);
SYS(fail, "ip -n %s route del %s dev veth7 %s", ns2, IP4_ADDR_GRE, vrf);
SYS(fail, "ip -n %s -6 route del %s/128 dev veth5 %s", ns1, IP6_ADDR_GRE, vrf);
SYS(fail, "ip -n %s -6 route del %s/128 dev veth7 %s", ns2, IP6_ADDR_GRE, vrf);
return 0;
fail:
return -1;
}
static int add_unreachable_routes_to_gredev(const char *ns1, const char *ns2, const char *vrf)
{
SYS(fail, "ip -n %s route add unreachable %s/32 %s", ns1, IP4_ADDR_GRE, vrf);
SYS(fail, "ip -n %s route add unreachable %s/32 %s", ns2, IP4_ADDR_GRE, vrf);
SYS(fail, "ip -n %s -6 route add unreachable %s/128 %s", ns1, IP6_ADDR_GRE, vrf);
SYS(fail, "ip -n %s -6 route add unreachable %s/128 %s", ns2, IP6_ADDR_GRE, vrf);
return 0;
fail:
return -1;
}
#define GSO_SIZE 5000
#define GSO_TCP_PORT 9000
/* This tests the fix from commit ea0371f78799 ("net: fix GSO in bpf_lwt_push_ip_encap") */
static int test_gso_fix(const char *ns1, const char *ns3, int family)
{
const char *ip_addr = family == AF_INET ? IP4_ADDR_DST : IP6_ADDR_DST;
char gso_packet[GSO_SIZE] = {};
struct nstoken *nstoken = NULL;
int sfd, cfd, afd;
ssize_t bytes;
int ret = -1;
if (!ns1 || !ns3)
return ret;
nstoken = open_netns(ns3);
if (!ASSERT_OK_PTR(nstoken, "open ns3"))
return ret;
sfd = start_server_str(family, SOCK_STREAM, ip_addr, GSO_TCP_PORT, NULL);
if (!ASSERT_OK_FD(sfd, "start server"))
goto close_netns;
close_netns(nstoken);
nstoken = open_netns(ns1);
if (!ASSERT_OK_PTR(nstoken, "open ns1"))
goto close_server;
cfd = connect_to_addr_str(family, SOCK_STREAM, ip_addr, GSO_TCP_PORT, NULL);
if (!ASSERT_OK_FD(cfd, "connect to server"))
goto close_server;
close_netns(nstoken);
nstoken = NULL;
afd = accept(sfd, NULL, NULL);
if (!ASSERT_OK_FD(afd, "accept"))
goto close_client;
/* Send a packet larger than MTU */
bytes = send(cfd, gso_packet, GSO_SIZE, 0);
if (!ASSERT_EQ(bytes, GSO_SIZE, "send packet"))
goto close_accept;
/* Verify we received all expected bytes */
bytes = read(afd, gso_packet, GSO_SIZE);
if (!ASSERT_EQ(bytes, GSO_SIZE, "receive packet"))
goto close_accept;
ret = 0;
close_accept:
close(afd);
close_client:
close(cfd);
close_server:
close(sfd);
close_netns:
close_netns(nstoken);
return ret;
}
static int check_ping_ok(const char *ns1)
{
SYS(fail, "ip netns exec %s ping -c 1 -W1 -I veth1 %s > /dev/null", ns1, IP4_ADDR_DST);
SYS(fail, "ip netns exec %s ping6 -c 1 -W1 -I veth1 %s > /dev/null", ns1, IP6_ADDR_DST);
return 0;
fail:
return -1;
}
static int check_ping_fails(const char *ns1)
{
int ret;
ret = SYS_NOFAIL("ip netns exec %s ping -c 1 -W1 -I veth1 %s", ns1, IP4_ADDR_DST);
if (!ret)
return -1;
ret = SYS_NOFAIL("ip netns exec %s ping6 -c 1 -W1 -I veth1 %s", ns1, IP6_ADDR_DST);
if (!ret)
return -1;
return 0;
}
#define EGRESS true
#define INGRESS false
#define IPV4_ENCAP true
#define IPV6_ENCAP false
static void lwt_ip_encap(bool ipv4_encap, bool egress, const char *vrf)
{
char ns1[NETNS_NAME_SIZE] = NETNS_BASE "-1-";
char ns2[NETNS_NAME_SIZE] = NETNS_BASE "-2-";
char ns3[NETNS_NAME_SIZE] = NETNS_BASE "-3-";
char *sec = ipv4_encap ? "encap_gre" : "encap_gre6";
if (!vrf)
return;
if (!ASSERT_OK(create_ns(ns1, NETNS_NAME_SIZE), "create ns1"))
goto out;
if (!ASSERT_OK(create_ns(ns2, NETNS_NAME_SIZE), "create ns2"))
goto out;
if (!ASSERT_OK(create_ns(ns3, NETNS_NAME_SIZE), "create ns3"))
goto out;
if (!ASSERT_OK(setup_network(ns1, ns2, ns3, vrf), "setup network"))
goto out;
/* By default, pings work */
if (!ASSERT_OK(check_ping_ok(ns1), "ping OK"))
goto out;
/* Remove NS2->DST routes, ping fails */
SYS(out, "ip -n %s route del %s/32 dev veth3 %s", ns2, IP4_ADDR_DST, vrf);
SYS(out, "ip -n %s -6 route del %s/128 dev veth3 %s", ns2, IP6_ADDR_DST, vrf);
if (!ASSERT_OK(check_ping_fails(ns1), "ping expected fail"))
goto out;
/* Install replacement routes (LWT/eBPF), pings succeed */
if (egress) {
SYS(out, "ip -n %s route add %s encap bpf xmit obj %s sec %s dev veth1 %s",
ns1, IP4_ADDR_DST, BPF_FILE, sec, vrf);
SYS(out, "ip -n %s -6 route add %s encap bpf xmit obj %s sec %s dev veth1 %s",
ns1, IP6_ADDR_DST, BPF_FILE, sec, vrf);
} else {
SYS(out, "ip -n %s route add %s encap bpf in obj %s sec %s dev veth2 %s",
ns2, IP4_ADDR_DST, BPF_FILE, sec, vrf);
SYS(out, "ip -n %s -6 route add %s encap bpf in obj %s sec %s dev veth2 %s",
ns2, IP6_ADDR_DST, BPF_FILE, sec, vrf);
}
if (!ASSERT_OK(check_ping_ok(ns1), "ping OK"))
goto out;
/* Skip GSO tests with VRF: VRF routing needs properly assigned
* source IP/device, which is easy to do with ping but hard with TCP.
*/
if (egress && !vrf[0]) {
if (!ASSERT_OK(test_gso_fix(ns1, ns3, AF_INET), "test GSO"))
goto out;
}
/* Negative test: remove routes to GRE devices: ping fails */
if (!ASSERT_OK(remove_routes_to_gredev(ns1, ns2, vrf), "remove routes to gredev"))
goto out;
if (!ASSERT_OK(check_ping_fails(ns1), "ping expected fail"))
goto out;
/* Another negative test */
if (!ASSERT_OK(add_unreachable_routes_to_gredev(ns1, ns2, vrf),
"add unreachable routes"))
goto out;
ASSERT_OK(check_ping_fails(ns1), "ping expected fail");
out:
SYS_NOFAIL("ip netns del %s", ns1);
SYS_NOFAIL("ip netns del %s", ns2);
SYS_NOFAIL("ip netns del %s", ns3);
}
void test_lwt_ip_encap_vrf_ipv6(void)
{
if (test__start_subtest("egress"))
lwt_ip_encap(IPV6_ENCAP, EGRESS, "vrf red");
if (test__start_subtest("ingress"))
lwt_ip_encap(IPV6_ENCAP, INGRESS, "vrf red");
}
void test_lwt_ip_encap_vrf_ipv4(void)
{
if (test__start_subtest("egress"))
lwt_ip_encap(IPV4_ENCAP, EGRESS, "vrf red");
if (test__start_subtest("ingress"))
lwt_ip_encap(IPV4_ENCAP, INGRESS, "vrf red");
}
void test_lwt_ip_encap_ipv6(void)
{
if (test__start_subtest("egress"))
lwt_ip_encap(IPV6_ENCAP, EGRESS, "");
if (test__start_subtest("ingress"))
lwt_ip_encap(IPV6_ENCAP, INGRESS, "");
}
void test_lwt_ip_encap_ipv4(void)
{
if (test__start_subtest("egress"))
lwt_ip_encap(IPV4_ENCAP, EGRESS, "");
if (test__start_subtest("ingress"))
lwt_ip_encap(IPV4_ENCAP, INGRESS, "");
}

View File

@@ -0,0 +1,176 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Connects 6 network namespaces through veths.
* Each NS may have different IPv6 global scope addresses :
*
* NS1 NS2 NS3 NS4 NS5 NS6
* lo veth1 <-> veth2 veth3 <-> veth4 veth5 <-> veth6 lo veth7 <-> veth8 veth9 <-> veth10 lo
* fb00 ::1 ::12 ::21 ::34 ::43 ::56 ::65 ::78 ::87 ::910 ::109 ::6
* fd00 ::4
* fc42 ::1
*
* All IPv6 packets going to fb00::/16 through NS2 will be encapsulated in a
* IPv6 header with a Segment Routing Header, with segments :
* fd00::1 -> fd00::2 -> fd00::3 -> fd00::4
*
* 3 fd00::/16 IPv6 addresses are binded to seg6local End.BPF actions :
* - fd00::1 : add a TLV, change the flags and apply a End.X action to fc42::1
* - fd00::2 : remove the TLV, change the flags, add a tag
* - fd00::3 : apply an End.T action to fd00::4, through routing table 117
*
* fd00::4 is a simple Segment Routing node decapsulating the inner IPv6 packet.
* Each End.BPF action will validate the operations applied on the SRH by the
* previous BPF program in the chain, otherwise the packet is dropped.
*
* An UDP datagram is sent from fb00::1 to fb00::6. The test succeeds if this
* datagram can be read on NS6 when binding to fb00::6.
*/
#include "network_helpers.h"
#include "test_progs.h"
#define NETNS_BASE "lwt-seg6local-"
#define BPF_FILE "test_lwt_seg6local.bpf.o"
static void cleanup(void)
{
int ns;
for (ns = 1; ns < 7; ns++)
SYS_NOFAIL("ip netns del %s%d", NETNS_BASE, ns);
}
static int setup(void)
{
int ns;
for (ns = 1; ns < 7; ns++)
SYS(fail, "ip netns add %s%d", NETNS_BASE, ns);
SYS(fail, "ip -n %s6 link set dev lo up", NETNS_BASE);
for (ns = 1; ns < 6; ns++) {
int local_id = ns * 2 - 1;
int peer_id = ns * 2;
int next_ns = ns + 1;
SYS(fail, "ip -n %s%d link add veth%d type veth peer name veth%d netns %s%d",
NETNS_BASE, ns, local_id, peer_id, NETNS_BASE, next_ns);
SYS(fail, "ip -n %s%d link set dev veth%d up", NETNS_BASE, ns, local_id);
SYS(fail, "ip -n %s%d link set dev veth%d up", NETNS_BASE, next_ns, peer_id);
/* All link scope addresses to veths */
SYS(fail, "ip -n %s%d -6 addr add fb00::%d%d/16 dev veth%d scope link",
NETNS_BASE, ns, local_id, peer_id, local_id);
SYS(fail, "ip -n %s%d -6 addr add fb00::%d%d/16 dev veth%d scope link",
NETNS_BASE, next_ns, peer_id, local_id, peer_id);
}
SYS(fail, "ip -n %s5 -6 route add fb00::109 table 117 dev veth9 scope link", NETNS_BASE);
SYS(fail, "ip -n %s1 -6 addr add fb00::1/16 dev lo", NETNS_BASE);
SYS(fail, "ip -n %s1 -6 route add fb00::6 dev veth1 via fb00::21", NETNS_BASE);
SYS(fail, "ip -n %s2 -6 route add fb00::6 encap bpf in obj %s sec encap_srh dev veth2",
NETNS_BASE, BPF_FILE);
SYS(fail, "ip -n %s2 -6 route add fd00::1 dev veth3 via fb00::43 scope link", NETNS_BASE);
SYS(fail, "ip -n %s3 -6 route add fc42::1 dev veth5 via fb00::65", NETNS_BASE);
SYS(fail,
"ip -n %s3 -6 route add fd00::1 encap seg6local action End.BPF endpoint obj %s sec add_egr_x dev veth4",
NETNS_BASE, BPF_FILE);
SYS(fail,
"ip -n %s4 -6 route add fd00::2 encap seg6local action End.BPF endpoint obj %s sec pop_egr dev veth6",
NETNS_BASE, BPF_FILE);
SYS(fail, "ip -n %s4 -6 addr add fc42::1 dev lo", NETNS_BASE);
SYS(fail, "ip -n %s4 -6 route add fd00::3 dev veth7 via fb00::87", NETNS_BASE);
SYS(fail, "ip -n %s5 -6 route add fd00::4 table 117 dev veth9 via fb00::109", NETNS_BASE);
SYS(fail,
"ip -n %s5 -6 route add fd00::3 encap seg6local action End.BPF endpoint obj %s sec inspect_t dev veth8",
NETNS_BASE, BPF_FILE);
SYS(fail, "ip -n %s6 -6 addr add fb00::6/16 dev lo", NETNS_BASE);
SYS(fail, "ip -n %s6 -6 addr add fd00::4/16 dev lo", NETNS_BASE);
for (ns = 1; ns < 6; ns++)
SYS(fail, "ip netns exec %s%d sysctl -wq net.ipv6.conf.all.forwarding=1",
NETNS_BASE, ns);
SYS(fail, "ip netns exec %s6 sysctl -wq net.ipv6.conf.all.seg6_enabled=1", NETNS_BASE);
SYS(fail, "ip netns exec %s6 sysctl -wq net.ipv6.conf.lo.seg6_enabled=1", NETNS_BASE);
SYS(fail, "ip netns exec %s6 sysctl -wq net.ipv6.conf.veth10.seg6_enabled=1", NETNS_BASE);
return 0;
fail:
return -1;
}
#define SERVER_PORT 7330
#define CLIENT_PORT 2121
void test_lwt_seg6local(void)
{
struct sockaddr_in6 server_addr = {};
const char *ns1 = NETNS_BASE "1";
const char *ns6 = NETNS_BASE "6";
struct nstoken *nstoken = NULL;
const char *foobar = "foobar";
ssize_t bytes;
int sfd, cfd;
char buf[7];
if (!ASSERT_OK(setup(), "setup"))
goto out;
nstoken = open_netns(ns6);
if (!ASSERT_OK_PTR(nstoken, "open ns6"))
goto out;
sfd = start_server_str(AF_INET6, SOCK_DGRAM, "fb00::6", SERVER_PORT, NULL);
if (!ASSERT_OK_FD(sfd, "start server"))
goto close_netns;
close_netns(nstoken);
nstoken = open_netns(ns1);
if (!ASSERT_OK_PTR(nstoken, "open ns1"))
goto close_server;
cfd = start_server_str(AF_INET6, SOCK_DGRAM, "fb00::1", CLIENT_PORT, NULL);
if (!ASSERT_OK_FD(cfd, "start client"))
goto close_server;
close_netns(nstoken);
nstoken = NULL;
/* Send a packet larger than MTU */
server_addr.sin6_family = AF_INET6;
server_addr.sin6_port = htons(SERVER_PORT);
if (!ASSERT_EQ(inet_pton(AF_INET6, "fb00::6", &server_addr.sin6_addr), 1,
"build target addr"))
goto close_client;
bytes = sendto(cfd, foobar, sizeof(foobar), 0,
(struct sockaddr *)&server_addr, sizeof(server_addr));
if (!ASSERT_EQ(bytes, sizeof(foobar), "send packet"))
goto close_client;
/* Verify we received all expected bytes */
bytes = read(sfd, buf, sizeof(buf));
if (!ASSERT_EQ(bytes, sizeof(buf), "receive packet"))
goto close_client;
ASSERT_STREQ(buf, foobar, "check udp packet");
close_client:
close(cfd);
close_server:
close(sfd);
close_netns:
close_netns(nstoken);
out:
cleanup();
}

View File

@@ -33,20 +33,25 @@ void test_netns_cookie(void)
skel->links.get_netns_cookie_sockops = bpf_program__attach_cgroup(
skel->progs.get_netns_cookie_sockops, cgroup_fd);
if (!ASSERT_OK_PTR(skel->links.get_netns_cookie_sockops, "prog_attach"))
if (!ASSERT_OK_PTR(skel->links.get_netns_cookie_sockops, "prog_attach_sockops"))
goto done;
verdict = bpf_program__fd(skel->progs.get_netns_cookie_sk_msg);
map = bpf_map__fd(skel->maps.sock_map);
err = bpf_prog_attach(verdict, map, BPF_SK_MSG_VERDICT, 0);
if (!ASSERT_OK(err, "prog_attach"))
if (!ASSERT_OK(err, "prog_attach_sk_msg"))
goto done;
tc_fd = bpf_program__fd(skel->progs.get_netns_cookie_tcx);
err = bpf_prog_attach_opts(tc_fd, loopback, BPF_TCX_INGRESS, &opta);
if (!ASSERT_OK(err, "prog_attach"))
if (!ASSERT_OK(err, "prog_attach_tcx"))
goto done;
skel->links.get_netns_cookie_cgroup_skb = bpf_program__attach_cgroup(
skel->progs.get_netns_cookie_cgroup_skb, cgroup_fd);
if (!ASSERT_OK_PTR(skel->links.get_netns_cookie_cgroup_skb, "prog_attach_cgroup_skb"))
goto cleanup_tc;
server_fd = start_server(AF_INET6, SOCK_STREAM, "::1", 0, 0);
if (CHECK(server_fd < 0, "start_server", "errno %d\n", errno))
goto cleanup_tc;
@@ -69,16 +74,18 @@ void test_netns_cookie(void)
if (!ASSERT_OK(err, "getsockopt"))
goto cleanup_tc;
ASSERT_EQ(val, cookie_expected_value, "cookie_value");
ASSERT_EQ(val, cookie_expected_value, "cookie_value_sockops");
err = bpf_map_lookup_elem(bpf_map__fd(skel->maps.sk_msg_netns_cookies),
&client_fd, &val);
if (!ASSERT_OK(err, "map_lookup(sk_msg_netns_cookies)"))
goto cleanup_tc;
ASSERT_EQ(val, cookie_expected_value, "cookie_value");
ASSERT_EQ(skel->bss->tcx_init_netns_cookie, cookie_expected_value, "cookie_value");
ASSERT_EQ(skel->bss->tcx_netns_cookie, cookie_expected_value, "cookie_value");
ASSERT_EQ(val, cookie_expected_value, "cookie_value_sk_msg");
ASSERT_EQ(skel->bss->tcx_init_netns_cookie, cookie_expected_value, "cookie_value_init_tcx");
ASSERT_EQ(skel->bss->tcx_netns_cookie, cookie_expected_value, "cookie_value_tcx");
ASSERT_EQ(skel->bss->cgroup_skb_init_netns_cookie, cookie_expected_value, "cookie_value_init_cgroup_skb");
ASSERT_EQ(skel->bss->cgroup_skb_netns_cookie, cookie_expected_value, "cookie_value_cgroup_skb");
cleanup_tc:
err = bpf_prog_detach_opts(tc_fd, loopback, BPF_TCX_INGRESS, &optd);

View File

@@ -200,41 +200,28 @@ static void test_ns_current_pid_tgid_new_ns(int (*fn)(void *), void *arg)
return;
}
static void test_in_netns(int (*fn)(void *), void *arg)
{
struct nstoken *nstoken = NULL;
SYS(cleanup, "ip netns add ns_current_pid_tgid");
SYS(cleanup, "ip -net ns_current_pid_tgid link set dev lo up");
nstoken = open_netns("ns_current_pid_tgid");
if (!ASSERT_OK_PTR(nstoken, "open_netns"))
goto cleanup;
test_ns_current_pid_tgid_new_ns(fn, arg);
cleanup:
if (nstoken)
close_netns(nstoken);
SYS_NOFAIL("ip netns del ns_current_pid_tgid");
}
/* TODO: use a different tracepoint */
void serial_test_ns_current_pid_tgid(void)
void serial_test_current_pid_tgid(void)
{
if (test__start_subtest("root_ns_tp"))
test_current_pid_tgid_tp(NULL);
if (test__start_subtest("new_ns_tp"))
test_ns_current_pid_tgid_new_ns(test_current_pid_tgid_tp, NULL);
if (test__start_subtest("new_ns_cgrp")) {
int cgroup_fd = -1;
cgroup_fd = test__join_cgroup("/sock_addr");
if (ASSERT_GE(cgroup_fd, 0, "join_cgroup")) {
test_in_netns(test_current_pid_tgid_cgrp, &cgroup_fd);
close(cgroup_fd);
}
}
if (test__start_subtest("new_ns_sk_msg"))
test_in_netns(test_current_pid_tgid_sk_msg, NULL);
}
void test_ns_current_pid_tgid_cgrp(void)
{
int cgroup_fd = test__join_cgroup("/sock_addr");
if (ASSERT_OK_FD(cgroup_fd, "join_cgroup")) {
test_ns_current_pid_tgid_new_ns(test_current_pid_tgid_cgrp, &cgroup_fd);
close(cgroup_fd);
}
}
void test_ns_current_pid_tgid_sk_msg(void)
{
test_ns_current_pid_tgid_new_ns(test_current_pid_tgid_sk_msg, NULL);
}

View File

@@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta */
#include <test_progs.h>
#include <network_helpers.h>
#include "prepare.skel.h"
static bool check_prepared(struct bpf_object *obj)
{
bool is_prepared = true;
const struct bpf_map *map;
bpf_object__for_each_map(map, obj) {
if (bpf_map__fd(map) < 0)
is_prepared = false;
}
return is_prepared;
}
static void test_prepare_no_load(void)
{
struct prepare *skel;
int err;
LIBBPF_OPTS(bpf_test_run_opts, topts,
.data_in = &pkt_v4,
.data_size_in = sizeof(pkt_v4),
);
skel = prepare__open();
if (!ASSERT_OK_PTR(skel, "prepare__open"))
return;
if (!ASSERT_FALSE(check_prepared(skel->obj), "not check_prepared"))
goto cleanup;
err = bpf_object__prepare(skel->obj);
if (!ASSERT_TRUE(check_prepared(skel->obj), "check_prepared"))
goto cleanup;
if (!ASSERT_OK(err, "bpf_object__prepare"))
goto cleanup;
cleanup:
prepare__destroy(skel);
}
static void test_prepare_load(void)
{
struct prepare *skel;
int err, prog_fd;
LIBBPF_OPTS(bpf_test_run_opts, topts,
.data_in = &pkt_v4,
.data_size_in = sizeof(pkt_v4),
);
skel = prepare__open();
if (!ASSERT_OK_PTR(skel, "prepare__open"))
return;
if (!ASSERT_FALSE(check_prepared(skel->obj), "not check_prepared"))
goto cleanup;
err = bpf_object__prepare(skel->obj);
if (!ASSERT_OK(err, "bpf_object__prepare"))
goto cleanup;
err = prepare__load(skel);
if (!ASSERT_OK(err, "prepare__load"))
goto cleanup;
if (!ASSERT_TRUE(check_prepared(skel->obj), "check_prepared"))
goto cleanup;
prog_fd = bpf_program__fd(skel->progs.program);
if (!ASSERT_GE(prog_fd, 0, "prog_fd"))
goto cleanup;
err = bpf_prog_test_run_opts(prog_fd, &topts);
if (!ASSERT_OK(err, "test_run_opts err"))
goto cleanup;
if (!ASSERT_OK(topts.retval, "test_run_opts retval"))
goto cleanup;
ASSERT_EQ(skel->bss->err, 0, "err");
cleanup:
prepare__destroy(skel);
}
void test_prepare(void)
{
if (test__start_subtest("prepare_load"))
test_prepare_load();
if (test__start_subtest("prepare_no_load"))
test_prepare_no_load();
}

View File

@@ -6,6 +6,7 @@
#include "epilogue_tailcall.skel.h"
#include "pro_epilogue_goto_start.skel.h"
#include "epilogue_exit.skel.h"
#include "pro_epilogue_with_kfunc.skel.h"
struct st_ops_args {
__u64 a;
@@ -55,6 +56,7 @@ void test_pro_epilogue(void)
RUN_TESTS(pro_epilogue);
RUN_TESTS(pro_epilogue_goto_start);
RUN_TESTS(epilogue_exit);
RUN_TESTS(pro_epilogue_with_kfunc);
if (test__start_subtest("tailcall"))
test_tailcall();
}

View File

@@ -81,6 +81,9 @@ static const char * const inproper_region_tests[] = {
"nested_rcu_region",
"rcu_read_lock_global_subprog_lock",
"rcu_read_lock_global_subprog_unlock",
"rcu_read_lock_sleepable_helper_global_subprog",
"rcu_read_lock_sleepable_kfunc_global_subprog",
"rcu_read_lock_sleepable_global_subprog_indirect",
};
static void test_inproper_region(void)

View File

@@ -24,6 +24,7 @@ struct read_ret_desc {
{ .name = "copy_from_user", .ret = -EFAULT },
{ .name = "copy_from_user_task", .ret = -EFAULT },
{ .name = "copy_from_user_str", .ret = -EFAULT },
{ .name = "copy_from_user_task_str", .ret = -EFAULT },
};
void test_read_vsyscall(void)

View File

@@ -202,7 +202,7 @@ err_out:
void test_setget_sockopt(void)
{
cg_fd = test__join_cgroup(CG_NAME);
if (cg_fd < 0)
if (!ASSERT_OK_FD(cg_fd, "join cgroup"))
return;
if (create_netns())

View File

@@ -50,6 +50,9 @@ static struct {
{ "lock_id_mismatch_innermapval_mapval", "bpf_spin_unlock of different lock" },
{ "lock_global_subprog_call1", "global function calls are not allowed while holding a lock" },
{ "lock_global_subprog_call2", "global function calls are not allowed while holding a lock" },
{ "lock_global_sleepable_helper_subprog", "global function calls are not allowed while holding a lock" },
{ "lock_global_sleepable_kfunc_subprog", "global function calls are not allowed while holding a lock" },
{ "lock_global_sleepable_subprog_indirect", "global function calls are not allowed while holding a lock" },
};
static int match_regex(const char *pattern, const char *string)

View File

@@ -0,0 +1,144 @@
// SPDX-License-Identifier: GPL-2.0
#include "bpf/libbpf.h"
#include "summarization_freplace.skel.h"
#include "summarization.skel.h"
#include <test_progs.h>
static void print_verifier_log(const char *log)
{
if (env.verbosity >= VERBOSE_VERY)
fprintf(stdout, "VERIFIER LOG:\n=============\n%s=============\n", log);
}
static void test_aux(const char *main_prog_name,
const char *to_be_replaced,
const char *replacement,
bool expect_load,
const char *err_msg)
{
struct summarization_freplace *freplace = NULL;
struct bpf_program *freplace_prog = NULL;
struct bpf_program *main_prog = NULL;
LIBBPF_OPTS(bpf_object_open_opts, opts);
struct summarization *main = NULL;
char log[16*1024];
int err;
opts.kernel_log_buf = log;
opts.kernel_log_size = sizeof(log);
if (env.verbosity >= VERBOSE_SUPER)
opts.kernel_log_level = 1 | 2 | 4;
main = summarization__open_opts(&opts);
if (!ASSERT_OK_PTR(main, "summarization__open"))
goto out;
main_prog = bpf_object__find_program_by_name(main->obj, main_prog_name);
if (!ASSERT_OK_PTR(main_prog, "main_prog"))
goto out;
bpf_program__set_autoload(main_prog, true);
err = summarization__load(main);
print_verifier_log(log);
if (!ASSERT_OK(err, "summarization__load"))
goto out;
freplace = summarization_freplace__open_opts(&opts);
if (!ASSERT_OK_PTR(freplace, "summarization_freplace__open"))
goto out;
freplace_prog = bpf_object__find_program_by_name(freplace->obj, replacement);
if (!ASSERT_OK_PTR(freplace_prog, "freplace_prog"))
goto out;
bpf_program__set_autoload(freplace_prog, true);
bpf_program__set_autoattach(freplace_prog, true);
bpf_program__set_attach_target(freplace_prog,
bpf_program__fd(main_prog),
to_be_replaced);
err = summarization_freplace__load(freplace);
print_verifier_log(log);
/* The might_sleep extension doesn't work yet as sleepable calls are not
* allowed, but preserve the check in case it's supported later and then
* this particular combination can be enabled.
*/
if (!strcmp("might_sleep", replacement) && err) {
ASSERT_HAS_SUBSTR(log, "helper call might sleep in a non-sleepable prog", "error log");
ASSERT_EQ(err, -EINVAL, "err");
test__skip();
goto out;
}
if (expect_load) {
ASSERT_OK(err, "summarization_freplace__load");
} else {
ASSERT_ERR(err, "summarization_freplace__load");
ASSERT_HAS_SUBSTR(log, err_msg, "error log");
}
out:
summarization_freplace__destroy(freplace);
summarization__destroy(main);
}
/* There are two global subprograms in both summarization.skel.h:
* - one changes packet data;
* - another does not.
* It is ok to freplace subprograms that change packet data with those
* that either do or do not. It is only ok to freplace subprograms
* that do not change packet data with those that do not as well.
* The below tests check outcomes for each combination of such freplace.
* Also test a case when main subprogram itself is replaced and is a single
* subprogram in a program.
*
* This holds for might_sleep programs. It is ok to replace might_sleep with
* might_sleep and with does_not_sleep, but does_not_sleep cannot be replaced
* with might_sleep.
*/
void test_summarization_freplace(void)
{
struct {
const char *main;
const char *to_be_replaced;
bool has_side_effect;
} mains[2][4] = {
{
{ "main_changes_with_subprogs", "changes_pkt_data", true },
{ "main_changes_with_subprogs", "does_not_change_pkt_data", false },
{ "main_changes", "main_changes", true },
{ "main_does_not_change", "main_does_not_change", false },
},
{
{ "main_might_sleep_with_subprogs", "might_sleep", true },
{ "main_might_sleep_with_subprogs", "does_not_sleep", false },
{ "main_might_sleep", "main_might_sleep", true },
{ "main_does_not_sleep", "main_does_not_sleep", false },
},
};
const char *pkt_err = "Extension program changes packet data";
const char *slp_err = "Extension program may sleep";
struct {
const char *func;
bool has_side_effect;
const char *err_msg;
} replacements[2][2] = {
{
{ "changes_pkt_data", true, pkt_err },
{ "does_not_change_pkt_data", false, pkt_err },
},
{
{ "might_sleep", true, slp_err },
{ "does_not_sleep", false, slp_err },
},
};
char buf[64];
for (int t = 0; t < 2; t++) {
for (int i = 0; i < ARRAY_SIZE(mains); ++i) {
for (int j = 0; j < ARRAY_SIZE(replacements); ++j) {
snprintf(buf, sizeof(buf), "%s_with_%s",
mains[t][i].to_be_replaced, replacements[t][j].func);
if (!test__start_subtest(buf))
continue;
test_aux(mains[t][i].main, mains[t][i].to_be_replaced, replacements[t][j].func,
mains[t][i].has_side_effect || !replacements[t][j].has_side_effect,
replacements[t][j].err_msg);
}
}
}
}

View File

@@ -1600,6 +1600,7 @@ static void test_tailcall_bpf2bpf_freplace(void)
goto out;
err = bpf_link__destroy(freplace_link);
freplace_link = NULL;
if (!ASSERT_OK(err, "destroy link"))
goto out;

View File

@@ -13,7 +13,7 @@
#include "netlink_helpers.h"
#include "tc_helpers.h"
void serial_test_tc_links_basic(void)
void test_ns_tc_links_basic(void)
{
LIBBPF_OPTS(bpf_prog_query_opts, optq);
LIBBPF_OPTS(bpf_tcx_opts, optl);
@@ -260,7 +260,7 @@ cleanup:
assert_mprog_count(target, 0);
}
void serial_test_tc_links_before(void)
void test_ns_tc_links_before(void)
{
test_tc_links_before_target(BPF_TCX_INGRESS);
test_tc_links_before_target(BPF_TCX_EGRESS);
@@ -414,7 +414,7 @@ cleanup:
assert_mprog_count(target, 0);
}
void serial_test_tc_links_after(void)
void test_ns_tc_links_after(void)
{
test_tc_links_after_target(BPF_TCX_INGRESS);
test_tc_links_after_target(BPF_TCX_EGRESS);
@@ -514,7 +514,7 @@ cleanup:
assert_mprog_count(target, 0);
}
void serial_test_tc_links_revision(void)
void test_ns_tc_links_revision(void)
{
test_tc_links_revision_target(BPF_TCX_INGRESS);
test_tc_links_revision_target(BPF_TCX_EGRESS);
@@ -618,7 +618,7 @@ cleanup:
assert_mprog_count(target, 0);
}
void serial_test_tc_links_chain_classic(void)
void test_ns_tc_links_chain_classic(void)
{
test_tc_chain_classic(BPF_TCX_INGRESS, false);
test_tc_chain_classic(BPF_TCX_EGRESS, false);
@@ -846,7 +846,7 @@ cleanup:
assert_mprog_count(target, 0);
}
void serial_test_tc_links_replace(void)
void test_ns_tc_links_replace(void)
{
test_tc_links_replace_target(BPF_TCX_INGRESS);
test_tc_links_replace_target(BPF_TCX_EGRESS);
@@ -1158,7 +1158,7 @@ cleanup:
assert_mprog_count(target, 0);
}
void serial_test_tc_links_invalid(void)
void test_ns_tc_links_invalid(void)
{
test_tc_links_invalid_target(BPF_TCX_INGRESS);
test_tc_links_invalid_target(BPF_TCX_EGRESS);
@@ -1314,7 +1314,7 @@ cleanup:
assert_mprog_count(target, 0);
}
void serial_test_tc_links_prepend(void)
void test_ns_tc_links_prepend(void)
{
test_tc_links_prepend_target(BPF_TCX_INGRESS);
test_tc_links_prepend_target(BPF_TCX_EGRESS);
@@ -1470,7 +1470,7 @@ cleanup:
assert_mprog_count(target, 0);
}
void serial_test_tc_links_append(void)
void test_ns_tc_links_append(void)
{
test_tc_links_append_target(BPF_TCX_INGRESS);
test_tc_links_append_target(BPF_TCX_EGRESS);
@@ -1568,7 +1568,7 @@ cleanup:
ASSERT_EQ(if_nametoindex("tcx_opts2"), 0, "dev2_removed");
}
void serial_test_tc_links_dev_cleanup(void)
void test_ns_tc_links_dev_cleanup(void)
{
test_tc_links_dev_cleanup_target(BPF_TCX_INGRESS);
test_tc_links_dev_cleanup_target(BPF_TCX_EGRESS);
@@ -1672,7 +1672,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_links_chain_mixed(void)
void test_ns_tc_links_chain_mixed(void)
{
test_tc_chain_mixed(BPF_TCX_INGRESS);
test_tc_chain_mixed(BPF_TCX_EGRESS);
@@ -1782,7 +1782,7 @@ cleanup:
assert_mprog_count(target, 0);
}
void serial_test_tc_links_ingress(void)
void test_ns_tc_links_ingress(void)
{
test_tc_links_ingress(BPF_TCX_INGRESS, true, true);
test_tc_links_ingress(BPF_TCX_INGRESS, true, false);
@@ -1823,7 +1823,7 @@ static int qdisc_replace(int ifindex, const char *kind, bool block)
return err;
}
void serial_test_tc_links_dev_chain0(void)
void test_ns_tc_links_dev_chain0(void)
{
int err, ifindex;
@@ -1955,7 +1955,7 @@ cleanup:
ASSERT_EQ(if_nametoindex("tcx_opts2"), 0, "dev2_removed");
}
void serial_test_tc_links_dev_mixed(void)
void test_ns_tc_links_dev_mixed(void)
{
test_tc_links_dev_mixed(BPF_TCX_INGRESS);
test_tc_links_dev_mixed(BPF_TCX_EGRESS);

View File

@@ -10,7 +10,7 @@
#include "test_tc_link.skel.h"
#include "tc_helpers.h"
void serial_test_tc_opts_basic(void)
void test_ns_tc_opts_basic(void)
{
LIBBPF_OPTS(bpf_prog_attach_opts, opta);
LIBBPF_OPTS(bpf_prog_detach_opts, optd);
@@ -254,7 +254,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_opts_before(void)
void test_ns_tc_opts_before(void)
{
test_tc_opts_before_target(BPF_TCX_INGRESS);
test_tc_opts_before_target(BPF_TCX_EGRESS);
@@ -445,7 +445,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_opts_after(void)
void test_ns_tc_opts_after(void)
{
test_tc_opts_after_target(BPF_TCX_INGRESS);
test_tc_opts_after_target(BPF_TCX_EGRESS);
@@ -554,7 +554,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_opts_revision(void)
void test_ns_tc_opts_revision(void)
{
test_tc_opts_revision_target(BPF_TCX_INGRESS);
test_tc_opts_revision_target(BPF_TCX_EGRESS);
@@ -655,7 +655,7 @@ cleanup:
assert_mprog_count(target, 0);
}
void serial_test_tc_opts_chain_classic(void)
void test_ns_tc_opts_chain_classic(void)
{
test_tc_chain_classic(BPF_TCX_INGRESS, false);
test_tc_chain_classic(BPF_TCX_EGRESS, false);
@@ -864,7 +864,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_opts_replace(void)
void test_ns_tc_opts_replace(void)
{
test_tc_opts_replace_target(BPF_TCX_INGRESS);
test_tc_opts_replace_target(BPF_TCX_EGRESS);
@@ -1017,7 +1017,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_opts_invalid(void)
void test_ns_tc_opts_invalid(void)
{
test_tc_opts_invalid_target(BPF_TCX_INGRESS);
test_tc_opts_invalid_target(BPF_TCX_EGRESS);
@@ -1157,7 +1157,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_opts_prepend(void)
void test_ns_tc_opts_prepend(void)
{
test_tc_opts_prepend_target(BPF_TCX_INGRESS);
test_tc_opts_prepend_target(BPF_TCX_EGRESS);
@@ -1297,7 +1297,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_opts_append(void)
void test_ns_tc_opts_append(void)
{
test_tc_opts_append_target(BPF_TCX_INGRESS);
test_tc_opts_append_target(BPF_TCX_EGRESS);
@@ -1387,7 +1387,7 @@ cleanup:
ASSERT_EQ(if_nametoindex("tcx_opts2"), 0, "dev2_removed");
}
void serial_test_tc_opts_dev_cleanup(void)
void test_ns_tc_opts_dev_cleanup(void)
{
test_tc_opts_dev_cleanup_target(BPF_TCX_INGRESS);
test_tc_opts_dev_cleanup_target(BPF_TCX_EGRESS);
@@ -1563,7 +1563,7 @@ cleanup:
assert_mprog_count(target, 0);
}
void serial_test_tc_opts_mixed(void)
void test_ns_tc_opts_mixed(void)
{
test_tc_opts_mixed_target(BPF_TCX_INGRESS);
test_tc_opts_mixed_target(BPF_TCX_EGRESS);
@@ -1642,7 +1642,7 @@ cleanup:
assert_mprog_count(target, 0);
}
void serial_test_tc_opts_demixed(void)
void test_ns_tc_opts_demixed(void)
{
test_tc_opts_demixed_target(BPF_TCX_INGRESS);
test_tc_opts_demixed_target(BPF_TCX_EGRESS);
@@ -1813,7 +1813,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_opts_detach(void)
void test_ns_tc_opts_detach(void)
{
test_tc_opts_detach_target(BPF_TCX_INGRESS);
test_tc_opts_detach_target(BPF_TCX_EGRESS);
@@ -2020,7 +2020,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_opts_detach_before(void)
void test_ns_tc_opts_detach_before(void)
{
test_tc_opts_detach_before_target(BPF_TCX_INGRESS);
test_tc_opts_detach_before_target(BPF_TCX_EGRESS);
@@ -2236,7 +2236,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_opts_detach_after(void)
void test_ns_tc_opts_detach_after(void)
{
test_tc_opts_detach_after_target(BPF_TCX_INGRESS);
test_tc_opts_detach_after_target(BPF_TCX_EGRESS);
@@ -2265,7 +2265,7 @@ static void test_tc_opts_delete_empty(int target, bool chain_tc_old)
assert_mprog_count(target, 0);
}
void serial_test_tc_opts_delete_empty(void)
void test_ns_tc_opts_delete_empty(void)
{
test_tc_opts_delete_empty(BPF_TCX_INGRESS, false);
test_tc_opts_delete_empty(BPF_TCX_EGRESS, false);
@@ -2372,7 +2372,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_opts_chain_mixed(void)
void test_ns_tc_opts_chain_mixed(void)
{
test_tc_chain_mixed(BPF_TCX_INGRESS);
test_tc_chain_mixed(BPF_TCX_EGRESS);
@@ -2446,7 +2446,7 @@ cleanup:
ASSERT_EQ(if_nametoindex("tcx_opts2"), 0, "dev2_removed");
}
void serial_test_tc_opts_max(void)
void test_ns_tc_opts_max(void)
{
test_tc_opts_max_target(BPF_TCX_INGRESS, 0, false);
test_tc_opts_max_target(BPF_TCX_EGRESS, 0, false);
@@ -2748,7 +2748,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_opts_query(void)
void test_ns_tc_opts_query(void)
{
test_tc_opts_query_target(BPF_TCX_INGRESS);
test_tc_opts_query_target(BPF_TCX_EGRESS);
@@ -2807,7 +2807,7 @@ cleanup:
test_tc_link__destroy(skel);
}
void serial_test_tc_opts_query_attach(void)
void test_ns_tc_opts_query_attach(void)
{
test_tc_opts_query_attach_target(BPF_TCX_INGRESS);
test_tc_opts_query_attach_target(BPF_TCX_EGRESS);

View File

@@ -0,0 +1,16 @@
#include <test_progs.h>
#include "struct_ops_kptr_return.skel.h"
#include "struct_ops_kptr_return_fail__wrong_type.skel.h"
#include "struct_ops_kptr_return_fail__invalid_scalar.skel.h"
#include "struct_ops_kptr_return_fail__nonzero_offset.skel.h"
#include "struct_ops_kptr_return_fail__local_kptr.skel.h"
void test_struct_ops_kptr_return(void)
{
RUN_TESTS(struct_ops_kptr_return);
RUN_TESTS(struct_ops_kptr_return_fail__wrong_type);
RUN_TESTS(struct_ops_kptr_return_fail__invalid_scalar);
RUN_TESTS(struct_ops_kptr_return_fail__nonzero_offset);
RUN_TESTS(struct_ops_kptr_return_fail__local_kptr);
}

View File

@@ -0,0 +1,14 @@
#include <test_progs.h>
#include "struct_ops_refcounted.skel.h"
#include "struct_ops_refcounted_fail__ref_leak.skel.h"
#include "struct_ops_refcounted_fail__global_subprog.skel.h"
#include "struct_ops_refcounted_fail__tail_call.skel.h"
void test_struct_ops_refcounted(void)
{
RUN_TESTS(struct_ops_refcounted);
RUN_TESTS(struct_ops_refcounted_fail__ref_leak);
RUN_TESTS(struct_ops_refcounted_fail__global_subprog);
RUN_TESTS(struct_ops_refcounted_fail__tail_call);
}

View File

@@ -71,6 +71,8 @@
#define IP4_ADDR2_VETH1 "172.16.1.20"
#define IP4_ADDR_TUNL_DEV0 "10.1.1.100"
#define IP4_ADDR_TUNL_DEV1 "10.1.1.200"
#define IP6_ADDR_TUNL_DEV0 "fc80::100"
#define IP6_ADDR_TUNL_DEV1 "fc80::200"
#define IP6_ADDR_VETH0 "::11"
#define IP6_ADDR1_VETH1 "::22"
@@ -98,6 +100,27 @@
#define XFRM_SPI_IN_TO_OUT 0x1
#define XFRM_SPI_OUT_TO_IN 0x2
#define GRE_TUNL_DEV0 "gre00"
#define GRE_TUNL_DEV1 "gre11"
#define IP6GRE_TUNL_DEV0 "ip6gre00"
#define IP6GRE_TUNL_DEV1 "ip6gre11"
#define ERSPAN_TUNL_DEV0 "erspan00"
#define ERSPAN_TUNL_DEV1 "erspan11"
#define IP6ERSPAN_TUNL_DEV0 "ip6erspan00"
#define IP6ERSPAN_TUNL_DEV1 "ip6erspan11"
#define GENEVE_TUNL_DEV0 "geneve00"
#define GENEVE_TUNL_DEV1 "geneve11"
#define IP6GENEVE_TUNL_DEV0 "ip6geneve00"
#define IP6GENEVE_TUNL_DEV1 "ip6geneve11"
#define IP6TNL_TUNL_DEV0 "ip6tnl00"
#define IP6TNL_TUNL_DEV1 "ip6tnl11"
#define PING_ARGS "-i 0.01 -c 3 -w 10 -q"
static int config_device(void)
@@ -216,6 +239,18 @@ fail:
return -1;
}
static int set_ipv4_addr(const char *dev0, const char *dev1)
{
SYS(fail, "ip -n at_ns0 link set dev %s up", dev0);
SYS(fail, "ip -n at_ns0 addr add dev %s %s/24", dev0, IP4_ADDR_TUNL_DEV0);
SYS(fail, "ip link set dev %s up", dev1);
SYS(fail, "ip addr add dev %s %s/24", dev1, IP4_ADDR_TUNL_DEV1);
return 0;
fail:
return 1;
}
static int add_ipip_tunnel(enum ipip_encap encap)
{
int err;
@@ -356,6 +391,99 @@ static void delete_xfrm_tunnel(void)
IP4_ADDR1_VETH1, IP4_ADDR_VETH0, XFRM_SPI_OUT_TO_IN);
}
static int add_ipv4_tunnel(const char *dev0, const char *dev1,
const char *type, const char *opt)
{
if (!type || !opt || !dev0 || !dev1)
return -1;
SYS(fail, "ip -n at_ns0 link add dev %s type %s %s local %s remote %s",
dev0, type, opt, IP4_ADDR_VETH0, IP4_ADDR1_VETH1);
SYS(fail, "ip link add dev %s type %s external", dev1, type);
return set_ipv4_addr(dev0, dev1);
fail:
return -1;
}
static void delete_tunnel(const char *dev0, const char *dev1)
{
if (!dev0 || !dev1)
return;
SYS_NOFAIL("ip netns exec at_ns0 ip link delete dev %s", dev0);
SYS_NOFAIL("ip link delete dev %s", dev1);
}
static int set_ipv6_addr(const char *dev0, const char *dev1)
{
/* disable IPv6 DAD because it might take too long and fail tests */
SYS(fail, "ip -n at_ns0 addr add %s/96 dev veth0 nodad", IP6_ADDR_VETH0);
SYS(fail, "ip -n at_ns0 link set dev veth0 up");
SYS(fail, "ip addr add %s/96 dev veth1 nodad", IP6_ADDR1_VETH1);
SYS(fail, "ip link set dev veth1 up");
SYS(fail, "ip -n at_ns0 addr add dev %s %s/24", dev0, IP4_ADDR_TUNL_DEV0);
SYS(fail, "ip -n at_ns0 addr add dev %s %s/96 nodad", dev0, IP6_ADDR_TUNL_DEV0);
SYS(fail, "ip -n at_ns0 link set dev %s up", dev0);
SYS(fail, "ip addr add dev %s %s/24", dev1, IP4_ADDR_TUNL_DEV1);
SYS(fail, "ip addr add dev %s %s/96 nodad", dev1, IP6_ADDR_TUNL_DEV1);
SYS(fail, "ip link set dev %s up", dev1);
return 0;
fail:
return 1;
}
static int add_ipv6_tunnel(const char *dev0, const char *dev1,
const char *type, const char *opt)
{
if (!type || !opt || !dev0 || !dev1)
return -1;
SYS(fail, "ip -n at_ns0 link add dev %s type %s %s local %s remote %s",
dev0, type, opt, IP6_ADDR_VETH0, IP6_ADDR1_VETH1);
SYS(fail, "ip link add dev %s type %s external", dev1, type);
return set_ipv6_addr(dev0, dev1);
fail:
return -1;
}
static int add_geneve_tunnel(const char *dev0, const char *dev1,
const char *type, const char *opt)
{
if (!type || !opt || !dev0 || !dev1)
return -1;
SYS(fail, "ip -n at_ns0 link add dev %s type %s id 2 %s remote %s",
dev0, type, opt, IP4_ADDR1_VETH1);
SYS(fail, "ip link add dev %s type %s %s external", dev1, type, opt);
return set_ipv4_addr(dev0, dev1);
fail:
return -1;
}
static int add_ip6geneve_tunnel(const char *dev0, const char *dev1,
const char *type, const char *opt)
{
if (!type || !opt || !dev0 || !dev1)
return -1;
SYS(fail, "ip -n at_ns0 link add dev %s type %s id 22 %s remote %s",
dev0, type, opt, IP6_ADDR1_VETH1);
SYS(fail, "ip link add dev %s type %s %s external", dev1, type, opt);
return set_ipv6_addr(dev0, dev1);
fail:
return -1;
}
static int test_ping(int family, const char *addr)
{
SYS(fail, "%s %s %s > /dev/null", ping_command(family), PING_ARGS, addr);
@@ -364,32 +492,76 @@ fail:
return -1;
}
static int attach_tc_prog(struct bpf_tc_hook *hook, int igr_fd, int egr_fd)
static void ping_dev0(void)
{
/* ping from root namespace test */
test_ping(AF_INET, IP4_ADDR_TUNL_DEV0);
}
static void ping_dev1(void)
{
struct nstoken *nstoken;
/* ping from at_ns0 namespace test */
nstoken = open_netns("at_ns0");
if (!ASSERT_OK_PTR(nstoken, "setns"))
return;
test_ping(AF_INET, IP4_ADDR_TUNL_DEV1);
close_netns(nstoken);
}
static void ping6_veth0(void)
{
test_ping(AF_INET6, IP6_ADDR_VETH0);
}
static void ping6_dev0(void)
{
test_ping(AF_INET6, IP6_ADDR_TUNL_DEV0);
}
static void ping6_dev1(void)
{
struct nstoken *nstoken;
/* ping from at_ns0 namespace test */
nstoken = open_netns("at_ns0");
if (!ASSERT_OK_PTR(nstoken, "setns"))
return;
test_ping(AF_INET, IP6_ADDR_TUNL_DEV1);
close_netns(nstoken);
}
static int attach_tc_prog(int ifindex, int igr_fd, int egr_fd)
{
DECLARE_LIBBPF_OPTS(bpf_tc_hook, hook, .ifindex = ifindex,
.attach_point = BPF_TC_INGRESS | BPF_TC_EGRESS);
DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts1, .handle = 1,
.priority = 1, .prog_fd = igr_fd);
DECLARE_LIBBPF_OPTS(bpf_tc_opts, opts2, .handle = 1,
.priority = 1, .prog_fd = egr_fd);
int ret;
ret = bpf_tc_hook_create(hook);
ret = bpf_tc_hook_create(&hook);
if (!ASSERT_OK(ret, "create tc hook"))
return ret;
if (igr_fd >= 0) {
hook->attach_point = BPF_TC_INGRESS;
ret = bpf_tc_attach(hook, &opts1);
hook.attach_point = BPF_TC_INGRESS;
ret = bpf_tc_attach(&hook, &opts1);
if (!ASSERT_OK(ret, "bpf_tc_attach")) {
bpf_tc_hook_destroy(hook);
bpf_tc_hook_destroy(&hook);
return ret;
}
}
if (egr_fd >= 0) {
hook->attach_point = BPF_TC_EGRESS;
ret = bpf_tc_attach(hook, &opts2);
hook.attach_point = BPF_TC_EGRESS;
ret = bpf_tc_attach(&hook, &opts2);
if (!ASSERT_OK(ret, "bpf_tc_attach")) {
bpf_tc_hook_destroy(hook);
bpf_tc_hook_destroy(&hook);
return ret;
}
}
@@ -397,6 +569,50 @@ static int attach_tc_prog(struct bpf_tc_hook *hook, int igr_fd, int egr_fd)
return 0;
}
static int generic_attach(const char *dev, int igr_fd, int egr_fd)
{
int ifindex;
if (!ASSERT_OK_FD(igr_fd, "check ingress fd"))
return -1;
if (!ASSERT_OK_FD(egr_fd, "check egress fd"))
return -1;
ifindex = if_nametoindex(dev);
if (!ASSERT_NEQ(ifindex, 0, "get ifindex"))
return -1;
return attach_tc_prog(ifindex, igr_fd, egr_fd);
}
static int generic_attach_igr(const char *dev, int igr_fd)
{
int ifindex;
if (!ASSERT_OK_FD(igr_fd, "check ingress fd"))
return -1;
ifindex = if_nametoindex(dev);
if (!ASSERT_NEQ(ifindex, 0, "get ifindex"))
return -1;
return attach_tc_prog(ifindex, igr_fd, -1);
}
static int generic_attach_egr(const char *dev, int egr_fd)
{
int ifindex;
if (!ASSERT_OK_FD(egr_fd, "check egress fd"))
return -1;
ifindex = if_nametoindex(dev);
if (!ASSERT_NEQ(ifindex, 0, "get ifindex"))
return -1;
return attach_tc_prog(ifindex, -1, egr_fd);
}
static void test_vxlan_tunnel(void)
{
struct test_tunnel_kern *skel = NULL;
@@ -404,11 +620,9 @@ static void test_vxlan_tunnel(void)
int local_ip_map_fd = -1;
int set_src_prog_fd, get_src_prog_fd;
int set_dst_prog_fd;
int key = 0, ifindex = -1;
int key = 0;
uint local_ip;
int err;
DECLARE_LIBBPF_OPTS(bpf_tc_hook, tc_hook,
.attach_point = BPF_TC_INGRESS);
/* add vxlan tunnel */
err = add_vxlan_tunnel();
@@ -419,42 +633,22 @@ static void test_vxlan_tunnel(void)
skel = test_tunnel_kern__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_tunnel_kern__open_and_load"))
goto done;
ifindex = if_nametoindex(VXLAN_TUNL_DEV1);
if (!ASSERT_NEQ(ifindex, 0, "vxlan11 ifindex"))
goto done;
tc_hook.ifindex = ifindex;
get_src_prog_fd = bpf_program__fd(skel->progs.vxlan_get_tunnel_src);
set_src_prog_fd = bpf_program__fd(skel->progs.vxlan_set_tunnel_src);
if (!ASSERT_GE(get_src_prog_fd, 0, "bpf_program__fd"))
goto done;
if (!ASSERT_GE(set_src_prog_fd, 0, "bpf_program__fd"))
goto done;
if (attach_tc_prog(&tc_hook, get_src_prog_fd, set_src_prog_fd))
if (generic_attach(VXLAN_TUNL_DEV1, get_src_prog_fd, set_src_prog_fd))
goto done;
/* load and attach bpf prog to veth dev tc hook point */
ifindex = if_nametoindex("veth1");
if (!ASSERT_NEQ(ifindex, 0, "veth1 ifindex"))
goto done;
tc_hook.ifindex = ifindex;
set_dst_prog_fd = bpf_program__fd(skel->progs.veth_set_outer_dst);
if (!ASSERT_GE(set_dst_prog_fd, 0, "bpf_program__fd"))
goto done;
if (attach_tc_prog(&tc_hook, set_dst_prog_fd, -1))
if (generic_attach_igr("veth1", set_dst_prog_fd))
goto done;
/* load and attach prog set_md to tunnel dev tc hook point at_ns0 */
nstoken = open_netns("at_ns0");
if (!ASSERT_OK_PTR(nstoken, "setns src"))
goto done;
ifindex = if_nametoindex(VXLAN_TUNL_DEV0);
if (!ASSERT_NEQ(ifindex, 0, "vxlan00 ifindex"))
goto done;
tc_hook.ifindex = ifindex;
set_dst_prog_fd = bpf_program__fd(skel->progs.vxlan_set_tunnel_dst);
if (!ASSERT_GE(set_dst_prog_fd, 0, "bpf_program__fd"))
goto done;
if (attach_tc_prog(&tc_hook, -1, set_dst_prog_fd))
if (generic_attach_egr(VXLAN_TUNL_DEV0, set_dst_prog_fd))
goto done;
close_netns(nstoken);
@@ -468,9 +662,7 @@ static void test_vxlan_tunnel(void)
goto done;
/* ping test */
err = test_ping(AF_INET, IP4_ADDR_TUNL_DEV0);
if (!ASSERT_OK(err, "test_ping"))
goto done;
ping_dev0();
done:
/* delete vxlan tunnel */
@@ -488,11 +680,9 @@ static void test_ip6vxlan_tunnel(void)
int local_ip_map_fd = -1;
int set_src_prog_fd, get_src_prog_fd;
int set_dst_prog_fd;
int key = 0, ifindex = -1;
int key = 0;
uint local_ip;
int err;
DECLARE_LIBBPF_OPTS(bpf_tc_hook, tc_hook,
.attach_point = BPF_TC_INGRESS);
/* add vxlan tunnel */
err = add_ip6vxlan_tunnel();
@@ -503,31 +693,17 @@ static void test_ip6vxlan_tunnel(void)
skel = test_tunnel_kern__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_tunnel_kern__open_and_load"))
goto done;
ifindex = if_nametoindex(IP6VXLAN_TUNL_DEV1);
if (!ASSERT_NEQ(ifindex, 0, "ip6vxlan11 ifindex"))
goto done;
tc_hook.ifindex = ifindex;
get_src_prog_fd = bpf_program__fd(skel->progs.ip6vxlan_get_tunnel_src);
set_src_prog_fd = bpf_program__fd(skel->progs.ip6vxlan_set_tunnel_src);
if (!ASSERT_GE(set_src_prog_fd, 0, "bpf_program__fd"))
goto done;
if (!ASSERT_GE(get_src_prog_fd, 0, "bpf_program__fd"))
goto done;
if (attach_tc_prog(&tc_hook, get_src_prog_fd, set_src_prog_fd))
if (generic_attach(IP6VXLAN_TUNL_DEV1, get_src_prog_fd, set_src_prog_fd))
goto done;
/* load and attach prog set_md to tunnel dev tc hook point at_ns0 */
nstoken = open_netns("at_ns0");
if (!ASSERT_OK_PTR(nstoken, "setns src"))
goto done;
ifindex = if_nametoindex(IP6VXLAN_TUNL_DEV0);
if (!ASSERT_NEQ(ifindex, 0, "ip6vxlan00 ifindex"))
goto done;
tc_hook.ifindex = ifindex;
set_dst_prog_fd = bpf_program__fd(skel->progs.ip6vxlan_set_tunnel_dst);
if (!ASSERT_GE(set_dst_prog_fd, 0, "bpf_program__fd"))
goto done;
if (attach_tc_prog(&tc_hook, -1, set_dst_prog_fd))
if (generic_attach_egr(IP6VXLAN_TUNL_DEV0, set_dst_prog_fd))
goto done;
close_netns(nstoken);
@@ -541,9 +717,7 @@ static void test_ip6vxlan_tunnel(void)
goto done;
/* ping test */
err = test_ping(AF_INET, IP4_ADDR_TUNL_DEV0);
if (!ASSERT_OK(err, "test_ping"))
goto done;
ping_dev0();
done:
/* delete ipv6 vxlan tunnel */
@@ -557,12 +731,8 @@ done:
static void test_ipip_tunnel(enum ipip_encap encap)
{
struct test_tunnel_kern *skel = NULL;
struct nstoken *nstoken;
int set_src_prog_fd, get_src_prog_fd;
int ifindex = -1;
int err;
DECLARE_LIBBPF_OPTS(bpf_tc_hook, tc_hook,
.attach_point = BPF_TC_INGRESS);
/* add ipip tunnel */
err = add_ipip_tunnel(encap);
@@ -573,10 +743,6 @@ static void test_ipip_tunnel(enum ipip_encap encap)
skel = test_tunnel_kern__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_tunnel_kern__open_and_load"))
goto done;
ifindex = if_nametoindex(IPIP_TUNL_DEV1);
if (!ASSERT_NEQ(ifindex, 0, "ipip11 ifindex"))
goto done;
tc_hook.ifindex = ifindex;
switch (encap) {
case FOU:
@@ -598,26 +764,11 @@ static void test_ipip_tunnel(enum ipip_encap encap)
skel->progs.ipip_set_tunnel);
}
if (!ASSERT_GE(set_src_prog_fd, 0, "bpf_program__fd"))
goto done;
if (!ASSERT_GE(get_src_prog_fd, 0, "bpf_program__fd"))
goto done;
if (attach_tc_prog(&tc_hook, get_src_prog_fd, set_src_prog_fd))
if (generic_attach(IPIP_TUNL_DEV1, get_src_prog_fd, set_src_prog_fd))
goto done;
/* ping from root namespace test */
err = test_ping(AF_INET, IP4_ADDR_TUNL_DEV0);
if (!ASSERT_OK(err, "test_ping"))
goto done;
/* ping from at_ns0 namespace test */
nstoken = open_netns("at_ns0");
if (!ASSERT_OK_PTR(nstoken, "setns"))
goto done;
err = test_ping(AF_INET, IP4_ADDR_TUNL_DEV1);
if (!ASSERT_OK(err, "test_ping"))
goto done;
close_netns(nstoken);
ping_dev0();
ping_dev1();
done:
/* delete ipip tunnel */
@@ -628,11 +779,8 @@ done:
static void test_xfrm_tunnel(void)
{
DECLARE_LIBBPF_OPTS(bpf_tc_hook, tc_hook,
.attach_point = BPF_TC_INGRESS);
LIBBPF_OPTS(bpf_xdp_attach_opts, opts);
struct test_tunnel_kern *skel = NULL;
struct nstoken *nstoken;
int xdp_prog_fd;
int tc_prog_fd;
int ifindex;
@@ -646,19 +794,16 @@ static void test_xfrm_tunnel(void)
if (!ASSERT_OK_PTR(skel, "test_tunnel_kern__open_and_load"))
goto done;
ifindex = if_nametoindex("veth1");
if (!ASSERT_NEQ(ifindex, 0, "veth1 ifindex"))
goto done;
/* attach tc prog to tunnel dev */
tc_hook.ifindex = ifindex;
tc_prog_fd = bpf_program__fd(skel->progs.xfrm_get_state);
if (!ASSERT_GE(tc_prog_fd, 0, "bpf_program__fd"))
goto done;
if (attach_tc_prog(&tc_hook, tc_prog_fd, -1))
if (generic_attach_igr("veth1", tc_prog_fd))
goto done;
/* attach xdp prog to tunnel dev */
ifindex = if_nametoindex("veth1");
if (!ASSERT_NEQ(ifindex, 0, "veth1 ifindex"))
goto done;
xdp_prog_fd = bpf_program__fd(skel->progs.xfrm_get_state_xdp);
if (!ASSERT_GE(xdp_prog_fd, 0, "bpf_program__fd"))
goto done;
@@ -666,14 +811,7 @@ static void test_xfrm_tunnel(void)
if (!ASSERT_OK(err, "bpf_xdp_attach"))
goto done;
/* ping from at_ns0 namespace test */
nstoken = open_netns("at_ns0");
if (!ASSERT_OK_PTR(nstoken, "setns"))
goto done;
err = test_ping(AF_INET, IP4_ADDR_TUNL_DEV1);
close_netns(nstoken);
if (!ASSERT_OK(err, "test_ping"))
goto done;
ping_dev1();
if (!ASSERT_EQ(skel->bss->xfrm_reqid, 1, "req_id"))
goto done;
@@ -690,6 +828,281 @@ done:
test_tunnel_kern__destroy(skel);
}
enum gre_test {
GRE,
GRE_NOKEY,
GRETAP,
GRETAP_NOKEY,
};
static void test_gre_tunnel(enum gre_test test)
{
struct test_tunnel_kern *skel;
int set_fd, get_fd;
int err;
skel = test_tunnel_kern__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_tunnel_kern__open_and_load"))
return;
switch (test) {
case GRE:
err = add_ipv4_tunnel(GRE_TUNL_DEV0, GRE_TUNL_DEV1, "gre", "seq");
set_fd = bpf_program__fd(skel->progs.gre_set_tunnel_no_key);
get_fd = bpf_program__fd(skel->progs.gre_get_tunnel);
break;
case GRE_NOKEY:
err = add_ipv4_tunnel(GRE_TUNL_DEV0, GRE_TUNL_DEV1, "gre", "seq key 2");
set_fd = bpf_program__fd(skel->progs.gre_set_tunnel);
get_fd = bpf_program__fd(skel->progs.gre_get_tunnel);
break;
case GRETAP:
err = add_ipv4_tunnel(GRE_TUNL_DEV0, GRE_TUNL_DEV1, "gretap", "seq");
set_fd = bpf_program__fd(skel->progs.gre_set_tunnel_no_key);
get_fd = bpf_program__fd(skel->progs.gre_get_tunnel);
break;
case GRETAP_NOKEY:
err = add_ipv4_tunnel(GRE_TUNL_DEV0, GRE_TUNL_DEV1, "gretap", "seq key 2");
set_fd = bpf_program__fd(skel->progs.gre_set_tunnel);
get_fd = bpf_program__fd(skel->progs.gre_get_tunnel);
break;
}
if (!ASSERT_OK(err, "add tunnel"))
goto done;
if (generic_attach(GRE_TUNL_DEV1, get_fd, set_fd))
goto done;
ping_dev0();
ping_dev1();
done:
delete_tunnel(GRE_TUNL_DEV0, GRE_TUNL_DEV1);
test_tunnel_kern__destroy(skel);
}
enum ip6gre_test {
IP6GRE,
IP6GRETAP
};
static void test_ip6gre_tunnel(enum ip6gre_test test)
{
struct test_tunnel_kern *skel;
int set_fd, get_fd;
int err;
skel = test_tunnel_kern__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_tunnel_kern__open_and_load"))
return;
switch (test) {
case IP6GRE:
err = add_ipv6_tunnel(IP6GRE_TUNL_DEV0, IP6GRE_TUNL_DEV1,
"ip6gre", "flowlabel 0xbcdef key 2");
break;
case IP6GRETAP:
err = add_ipv6_tunnel(IP6GRE_TUNL_DEV0, IP6GRE_TUNL_DEV1,
"ip6gretap", "flowlabel 0xbcdef key 2");
break;
}
if (!ASSERT_OK(err, "add tunnel"))
goto done;
set_fd = bpf_program__fd(skel->progs.ip6gretap_set_tunnel);
get_fd = bpf_program__fd(skel->progs.ip6gretap_get_tunnel);
if (generic_attach(IP6GRE_TUNL_DEV1, get_fd, set_fd))
goto done;
ping6_veth0();
ping6_dev1();
ping_dev0();
ping_dev1();
done:
delete_tunnel(IP6GRE_TUNL_DEV0, IP6GRE_TUNL_DEV1);
test_tunnel_kern__destroy(skel);
}
enum erspan_test {
V1,
V2
};
static void test_erspan_tunnel(enum erspan_test test)
{
struct test_tunnel_kern *skel;
int set_fd, get_fd;
int err;
skel = test_tunnel_kern__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_tunnel_kern__open_and_load"))
return;
switch (test) {
case V1:
err = add_ipv4_tunnel(ERSPAN_TUNL_DEV0, ERSPAN_TUNL_DEV1,
"erspan", "seq key 2 erspan_ver 1 erspan 123");
break;
case V2:
err = add_ipv4_tunnel(ERSPAN_TUNL_DEV0, ERSPAN_TUNL_DEV1,
"erspan",
"seq key 2 erspan_ver 2 erspan_dir egress erspan_hwid 3");
break;
}
if (!ASSERT_OK(err, "add tunnel"))
goto done;
set_fd = bpf_program__fd(skel->progs.erspan_set_tunnel);
get_fd = bpf_program__fd(skel->progs.erspan_get_tunnel);
if (generic_attach(ERSPAN_TUNL_DEV1, get_fd, set_fd))
goto done;
ping_dev0();
ping_dev1();
done:
delete_tunnel(ERSPAN_TUNL_DEV0, ERSPAN_TUNL_DEV1);
test_tunnel_kern__destroy(skel);
}
static void test_ip6erspan_tunnel(enum erspan_test test)
{
struct test_tunnel_kern *skel;
int set_fd, get_fd;
int err;
skel = test_tunnel_kern__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_tunnel_kern__open_and_load"))
return;
switch (test) {
case V1:
err = add_ipv6_tunnel(IP6ERSPAN_TUNL_DEV0, IP6ERSPAN_TUNL_DEV1,
"ip6erspan", "seq key 2 erspan_ver 1 erspan 123");
break;
case V2:
err = add_ipv6_tunnel(IP6ERSPAN_TUNL_DEV0, IP6ERSPAN_TUNL_DEV1,
"ip6erspan",
"seq key 2 erspan_ver 2 erspan_dir egress erspan_hwid 7");
break;
}
if (!ASSERT_OK(err, "add tunnel"))
goto done;
set_fd = bpf_program__fd(skel->progs.ip4ip6erspan_set_tunnel);
get_fd = bpf_program__fd(skel->progs.ip4ip6erspan_get_tunnel);
if (generic_attach(IP6ERSPAN_TUNL_DEV1, get_fd, set_fd))
goto done;
ping6_veth0();
ping_dev1();
done:
delete_tunnel(IP6ERSPAN_TUNL_DEV0, IP6ERSPAN_TUNL_DEV1);
test_tunnel_kern__destroy(skel);
}
static void test_geneve_tunnel(void)
{
struct test_tunnel_kern *skel;
int set_fd, get_fd;
int err;
skel = test_tunnel_kern__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_tunnel_kern__open_and_load"))
return;
err = add_geneve_tunnel(GENEVE_TUNL_DEV0, GENEVE_TUNL_DEV1,
"geneve", "dstport 6081");
if (!ASSERT_OK(err, "add tunnel"))
goto done;
set_fd = bpf_program__fd(skel->progs.geneve_set_tunnel);
get_fd = bpf_program__fd(skel->progs.geneve_get_tunnel);
if (generic_attach(GENEVE_TUNL_DEV1, get_fd, set_fd))
goto done;
ping_dev0();
ping_dev1();
done:
delete_tunnel(GENEVE_TUNL_DEV0, GENEVE_TUNL_DEV1);
test_tunnel_kern__destroy(skel);
}
static void test_ip6geneve_tunnel(void)
{
struct test_tunnel_kern *skel;
int set_fd, get_fd;
int err;
skel = test_tunnel_kern__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_tunnel_kern__open_and_load"))
return;
err = add_ip6geneve_tunnel(IP6GENEVE_TUNL_DEV0, IP6GENEVE_TUNL_DEV1,
"geneve", "");
if (!ASSERT_OK(err, "add tunnel"))
goto done;
set_fd = bpf_program__fd(skel->progs.ip6geneve_set_tunnel);
get_fd = bpf_program__fd(skel->progs.ip6geneve_get_tunnel);
if (generic_attach(IP6GENEVE_TUNL_DEV1, get_fd, set_fd))
goto done;
ping_dev0();
ping_dev1();
done:
delete_tunnel(IP6GENEVE_TUNL_DEV0, IP6GENEVE_TUNL_DEV1);
test_tunnel_kern__destroy(skel);
}
enum ip6tnl_test {
IPIP6,
IP6IP6
};
static void test_ip6tnl_tunnel(enum ip6tnl_test test)
{
struct test_tunnel_kern *skel;
int set_fd, get_fd;
int err;
skel = test_tunnel_kern__open_and_load();
if (!ASSERT_OK_PTR(skel, "test_tunnel_kern__open_and_load"))
return;
err = add_ipv6_tunnel(IP6TNL_TUNL_DEV0, IP6TNL_TUNL_DEV1, "ip6tnl", "");
if (!ASSERT_OK(err, "add tunnel"))
goto done;
switch (test) {
case IPIP6:
set_fd = bpf_program__fd(skel->progs.ipip6_set_tunnel);
get_fd = bpf_program__fd(skel->progs.ipip6_get_tunnel);
break;
case IP6IP6:
set_fd = bpf_program__fd(skel->progs.ip6ip6_set_tunnel);
get_fd = bpf_program__fd(skel->progs.ip6ip6_get_tunnel);
break;
}
if (generic_attach(IP6TNL_TUNL_DEV1, get_fd, set_fd))
goto done;
ping6_veth0();
switch (test) {
case IPIP6:
ping_dev0();
ping_dev1();
break;
case IP6IP6:
ping6_dev0();
ping6_dev1();
break;
}
done:
delete_tunnel(IP6TNL_TUNL_DEV0, IP6TNL_TUNL_DEV1);
test_tunnel_kern__destroy(skel);
}
#define RUN_TEST(name, ...) \
({ \
if (test__start_subtest(#name)) { \
@@ -707,6 +1120,20 @@ static void *test_tunnel_run_tests(void *arg)
RUN_TEST(ipip_tunnel, FOU);
RUN_TEST(ipip_tunnel, GUE);
RUN_TEST(xfrm_tunnel);
RUN_TEST(gre_tunnel, GRE);
RUN_TEST(gre_tunnel, GRE_NOKEY);
RUN_TEST(gre_tunnel, GRETAP);
RUN_TEST(gre_tunnel, GRETAP_NOKEY);
RUN_TEST(ip6gre_tunnel, IP6GRE);
RUN_TEST(ip6gre_tunnel, IP6GRETAP);
RUN_TEST(erspan_tunnel, V1);
RUN_TEST(erspan_tunnel, V2);
RUN_TEST(ip6erspan_tunnel, V1);
RUN_TEST(ip6erspan_tunnel, V2);
RUN_TEST(geneve_tunnel);
RUN_TEST(ip6geneve_tunnel);
RUN_TEST(ip6tnl_tunnel, IPIP6);
RUN_TEST(ip6tnl_tunnel, IP6IP6);
return NULL;
}

View File

@@ -0,0 +1,139 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
#include <test_progs.h>
#include <string.h>
#include <stdio.h>
#define __CHECK_STR(str, name) \
do { \
if (!ASSERT_HAS_SUBSTR(fix->output, (str), (name))) \
goto out; \
} while (0)
struct fixture {
char tmpfile[80];
int fd;
char *output;
size_t sz;
char veristat[80];
};
static struct fixture *init_fixture(void)
{
struct fixture *fix = malloc(sizeof(struct fixture));
/* for no_alu32 and cpuv4 veristat is in parent folder */
if (access("./veristat", F_OK) == 0)
strcpy(fix->veristat, "./veristat");
else if (access("../veristat", F_OK) == 0)
strcpy(fix->veristat, "../veristat");
else
PRINT_FAIL("Can't find veristat binary");
snprintf(fix->tmpfile, sizeof(fix->tmpfile), "/tmp/test_veristat.XXXXXX");
fix->fd = mkstemp(fix->tmpfile);
fix->sz = 1000000;
fix->output = malloc(fix->sz);
return fix;
}
static void teardown_fixture(struct fixture *fix)
{
free(fix->output);
close(fix->fd);
remove(fix->tmpfile);
free(fix);
}
static void test_set_global_vars_succeeds(void)
{
struct fixture *fix = init_fixture();
SYS(out,
"%s set_global_vars.bpf.o"\
" -G \"var_s64 = 0xf000000000000001\" "\
" -G \"var_u64 = 0xfedcba9876543210\" "\
" -G \"var_s32 = -0x80000000\" "\
" -G \"var_u32 = 0x76543210\" "\
" -G \"var_s16 = -32768\" "\
" -G \"var_u16 = 60652\" "\
" -G \"var_s8 = -128\" "\
" -G \"var_u8 = 255\" "\
" -G \"var_ea = EA2\" "\
" -G \"var_eb = EB2\" "\
" -G \"var_ec = EC2\" "\
" -G \"var_b = 1\" "\
"-vl2 > %s", fix->veristat, fix->tmpfile);
read(fix->fd, fix->output, fix->sz);
__CHECK_STR("_w=0xf000000000000001 ", "var_s64 = 0xf000000000000001");
__CHECK_STR("_w=0xfedcba9876543210 ", "var_u64 = 0xfedcba9876543210");
__CHECK_STR("_w=0x80000000 ", "var_s32 = -0x80000000");
__CHECK_STR("_w=0x76543210 ", "var_u32 = 0x76543210");
__CHECK_STR("_w=0x8000 ", "var_s16 = -32768");
__CHECK_STR("_w=0xecec ", "var_u16 = 60652");
__CHECK_STR("_w=128 ", "var_s8 = -128");
__CHECK_STR("_w=255 ", "var_u8 = 255");
__CHECK_STR("_w=11 ", "var_ea = EA2");
__CHECK_STR("_w=12 ", "var_eb = EB2");
__CHECK_STR("_w=13 ", "var_ec = EC2");
__CHECK_STR("_w=1 ", "var_b = 1");
out:
teardown_fixture(fix);
}
static void test_set_global_vars_from_file_succeeds(void)
{
struct fixture *fix = init_fixture();
char input_file[80];
const char *vars = "var_s16 = -32768\nvar_u16 = 60652";
int fd;
snprintf(input_file, sizeof(input_file), "/tmp/veristat_input.XXXXXX");
fd = mkstemp(input_file);
if (!ASSERT_GE(fd, 0, "valid fd"))
goto out;
write(fd, vars, strlen(vars));
syncfs(fd);
SYS(out, "%s set_global_vars.bpf.o -G \"@%s\" -vl2 > %s",
fix->veristat, input_file, fix->tmpfile);
read(fix->fd, fix->output, fix->sz);
__CHECK_STR("_w=0x8000 ", "var_s16 = -32768");
__CHECK_STR("_w=0xecec ", "var_u16 = 60652");
out:
close(fd);
remove(input_file);
teardown_fixture(fix);
}
static void test_set_global_vars_out_of_range(void)
{
struct fixture *fix = init_fixture();
SYS_FAIL(out,
"%s set_global_vars.bpf.o -G \"var_s32 = 2147483648\" -vl2 2> %s",
fix->veristat, fix->tmpfile);
read(fix->fd, fix->output, fix->sz);
__CHECK_STR("is out of range [-2147483648; 2147483647]", "out of range");
out:
teardown_fixture(fix);
}
void test_veristat(void)
{
if (test__start_subtest("set_global_vars_succeeds"))
test_set_global_vars_succeeds();
if (test__start_subtest("set_global_vars_out_of_range"))
test_set_global_vars_out_of_range();
if (test__start_subtest("set_global_vars_from_file_succeeds"))
test_set_global_vars_from_file_succeeds();
}
#undef __CHECK_STR

View File

@@ -3,17 +3,50 @@
/* Create 3 namespaces with 3 veth peers, and forward packets in-between using
* native XDP
*
* XDP_TX
* NS1(veth11) NS2(veth22) NS3(veth33)
* | | |
* | | |
* (veth1, (veth2, (veth3,
* id:111) id:122) id:133)
* ^ | ^ | ^ |
* | | XDP_REDIRECT | | XDP_REDIRECT | |
* | ------------------ ------------------ |
* -----------------------------------------
* XDP_REDIRECT
* Network topology:
* ---------- ---------- ----------
* | NS1 | | NS2 | | NS3 |
* | veth11 | | veth22 | | veth33 |
* ----|----- -----|---- -----|----
* | | |
* ----|------------------|----------------|----
* | veth1 veth2 veth3 |
* | |
* | NSO |
* ---------------------------------------------
*
* Test cases:
* - [test_xdp_veth_redirect] : ping veth33 from veth11
*
* veth11 veth22 veth33
* (XDP_PASS) (XDP_TX) (XDP_PASS)
* | | |
* | | |
* veth1 veth2 veth3
* (XDP_REDIRECT) (XDP_REDIRECT) (XDP_REDIRECT)
* ^ | ^ | ^ |
* | | | | | |
* | ------------------ ------------------ |
* -----------------------------------------
*
* - [test_xdp_veth_broadcast_redirect]: broadcast from veth11
* - IPv4 ping : BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS
* -> echo request received by all except veth11
* - IPv4 ping : BPF_F_BROADCAST
* -> echo request received by all veth
* - [test_xdp_veth_egress]:
* - all src mac should be the magic mac
*
* veth11 veth22 veth33
* (XDP_PASS) (XDP_PASS) (XDP_PASS)
* | | |
* | | |
* veth1 veth2 veth3
* (XDP_REDIRECT) (XDP_REDIRECT) (XDP_REDIRECT)
* | ^ ^
* | | |
* ----------------------------------------
*
*/
#define _GNU_SOURCE
@@ -22,192 +55,545 @@
#include "network_helpers.h"
#include "xdp_dummy.skel.h"
#include "xdp_redirect_map.skel.h"
#include "xdp_redirect_multi_kern.skel.h"
#include "xdp_tx.skel.h"
#include <uapi/linux/if_link.h>
#define VETH_PAIRS_COUNT 3
#define NS_SUFFIX_LEN 6
#define VETH_NAME_MAX_LEN 16
#define VETH_NAME_MAX_LEN 32
#define IP_MAX_LEN 16
#define IP_SRC "10.1.1.11"
#define IP_DST "10.1.1.33"
#define IP_CMD_MAX_LEN 128
struct skeletons {
struct xdp_dummy *xdp_dummy;
struct xdp_tx *xdp_tx;
struct xdp_redirect_map *xdp_redirect_maps;
};
#define IP_NEIGH "10.1.1.253"
#define PROG_NAME_MAX_LEN 128
#define NS_NAME_MAX_LEN 32
struct veth_configuration {
char local_veth[VETH_NAME_MAX_LEN]; /* Interface in main namespace */
char remote_veth[VETH_NAME_MAX_LEN]; /* Peer interface in dedicated namespace*/
const char *namespace; /* Namespace for the remote veth */
char next_veth[VETH_NAME_MAX_LEN]; /* Local interface to redirect traffic to */
char *remote_addr; /* IP address of the remote veth */
char namespace[NS_NAME_MAX_LEN]; /* Namespace for the remote veth */
int next_veth; /* Local interface to redirect traffic to */
char remote_addr[IP_MAX_LEN]; /* IP address of the remote veth */
};
static struct veth_configuration config[VETH_PAIRS_COUNT] = {
struct net_configuration {
char ns0_name[NS_NAME_MAX_LEN];
struct veth_configuration veth_cfg[VETH_PAIRS_COUNT];
};
static const struct net_configuration default_config = {
.ns0_name = "ns0-",
{
.local_veth = "veth1",
.remote_veth = "veth11",
.next_veth = "veth2",
.remote_addr = IP_SRC,
.namespace = "ns-veth11"
},
{
.local_veth = "veth2",
.remote_veth = "veth22",
.next_veth = "veth3",
.remote_addr = NULL,
.namespace = "ns-veth22"
},
{
.local_veth = "veth3",
.remote_veth = "veth33",
.next_veth = "veth1",
.remote_addr = IP_DST,
.namespace = "ns-veth33"
{
.local_veth = "veth1-",
.remote_veth = "veth11",
.next_veth = 1,
.remote_addr = IP_SRC,
.namespace = "ns-veth11-"
},
{
.local_veth = "veth2-",
.remote_veth = "veth22",
.next_veth = 2,
.remote_addr = "",
.namespace = "ns-veth22-"
},
{
.local_veth = "veth3-",
.remote_veth = "veth33",
.next_veth = 0,
.remote_addr = IP_DST,
.namespace = "ns-veth33-"
}
}
};
static int attach_programs_to_veth_pair(struct skeletons *skeletons, int index)
struct prog_configuration {
char local_name[PROG_NAME_MAX_LEN]; /* BPF prog to attach to local_veth */
char remote_name[PROG_NAME_MAX_LEN]; /* BPF prog to attach to remote_veth */
u32 local_flags; /* XDP flags to use on local_veth */
u32 remote_flags; /* XDP flags to use on remote_veth */
};
static int attach_programs_to_veth_pair(struct bpf_object **objs, size_t nb_obj,
struct net_configuration *net_config,
struct prog_configuration *prog, int index)
{
struct bpf_program *local_prog, *remote_prog;
struct bpf_link **local_link, **remote_link;
struct nstoken *nstoken;
struct bpf_link *link;
int interface;
int interface, ret, i;
switch (index) {
case 0:
local_prog = skeletons->xdp_redirect_maps->progs.xdp_redirect_map_0;
local_link = &skeletons->xdp_redirect_maps->links.xdp_redirect_map_0;
remote_prog = skeletons->xdp_dummy->progs.xdp_dummy_prog;
remote_link = &skeletons->xdp_dummy->links.xdp_dummy_prog;
break;
case 1:
local_prog = skeletons->xdp_redirect_maps->progs.xdp_redirect_map_1;
local_link = &skeletons->xdp_redirect_maps->links.xdp_redirect_map_1;
remote_prog = skeletons->xdp_tx->progs.xdp_tx;
remote_link = &skeletons->xdp_tx->links.xdp_tx;
break;
case 2:
local_prog = skeletons->xdp_redirect_maps->progs.xdp_redirect_map_2;
local_link = &skeletons->xdp_redirect_maps->links.xdp_redirect_map_2;
remote_prog = skeletons->xdp_dummy->progs.xdp_dummy_prog;
remote_link = &skeletons->xdp_dummy->links.xdp_dummy_prog;
break;
for (i = 0; i < nb_obj; i++) {
local_prog = bpf_object__find_program_by_name(objs[i], prog[index].local_name);
if (local_prog)
break;
}
interface = if_nametoindex(config[index].local_veth);
if (!ASSERT_OK_PTR(local_prog, "find local program"))
return -1;
for (i = 0; i < nb_obj; i++) {
remote_prog = bpf_object__find_program_by_name(objs[i], prog[index].remote_name);
if (remote_prog)
break;
}
if (!ASSERT_OK_PTR(remote_prog, "find remote program"))
return -1;
interface = if_nametoindex(net_config->veth_cfg[index].local_veth);
if (!ASSERT_NEQ(interface, 0, "non zero interface index"))
return -1;
link = bpf_program__attach_xdp(local_prog, interface);
if (!ASSERT_OK_PTR(link, "attach xdp program to local veth"))
ret = bpf_xdp_attach(interface, bpf_program__fd(local_prog),
prog[index].local_flags, NULL);
if (!ASSERT_OK(ret, "attach xdp program to local veth"))
return -1;
*local_link = link;
nstoken = open_netns(config[index].namespace);
nstoken = open_netns(net_config->veth_cfg[index].namespace);
if (!ASSERT_OK_PTR(nstoken, "switch to remote veth namespace"))
return -1;
interface = if_nametoindex(config[index].remote_veth);
interface = if_nametoindex(net_config->veth_cfg[index].remote_veth);
if (!ASSERT_NEQ(interface, 0, "non zero interface index")) {
close_netns(nstoken);
return -1;
}
link = bpf_program__attach_xdp(remote_prog, interface);
*remote_link = link;
close_netns(nstoken);
if (!ASSERT_OK_PTR(link, "attach xdp program to remote veth"))
return -1;
ret = bpf_xdp_attach(interface, bpf_program__fd(remote_prog),
prog[index].remote_flags, NULL);
if (!ASSERT_OK(ret, "attach xdp program to remote veth")) {
close_netns(nstoken);
return -1;
}
close_netns(nstoken);
return 0;
}
static int configure_network(struct skeletons *skeletons)
static int create_network(struct net_configuration *net_config)
{
int interface_id;
int map_fd;
int err;
int i = 0;
struct nstoken *nstoken = NULL;
int i, err;
/* First create and configure all interfaces */
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
SYS(fail, "ip netns add %s", config[i].namespace);
SYS(fail, "ip link add %s type veth peer name %s netns %s",
config[i].local_veth, config[i].remote_veth, config[i].namespace);
SYS(fail, "ip link set dev %s up", config[i].local_veth);
if (config[i].remote_addr)
SYS(fail, "ip -n %s addr add %s/24 dev %s", config[i].namespace,
config[i].remote_addr, config[i].remote_veth);
SYS(fail, "ip -n %s link set dev %s up", config[i].namespace,
config[i].remote_veth);
}
memcpy(net_config, &default_config, sizeof(struct net_configuration));
/* Then configure the redirect map and attach programs to interfaces */
map_fd = bpf_map__fd(skeletons->xdp_redirect_maps->maps.tx_port);
if (!ASSERT_GE(map_fd, 0, "open redirect map"))
/* Create unique namespaces */
err = append_tid(net_config->ns0_name, NS_NAME_MAX_LEN);
if (!ASSERT_OK(err, "append TID to ns0 name"))
goto fail;
SYS(fail, "ip netns add %s", net_config->ns0_name);
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
interface_id = if_nametoindex(config[i].next_veth);
if (!ASSERT_NEQ(interface_id, 0, "non zero interface index"))
goto fail;
err = bpf_map_update_elem(map_fd, &i, &interface_id, BPF_ANY);
if (!ASSERT_OK(err, "configure interface redirection through map"))
goto fail;
if (attach_programs_to_veth_pair(skeletons, i))
err = append_tid(net_config->veth_cfg[i].namespace, NS_NAME_MAX_LEN);
if (!ASSERT_OK(err, "append TID to ns name"))
goto fail;
SYS(fail, "ip netns add %s", net_config->veth_cfg[i].namespace);
}
/* Create interfaces */
nstoken = open_netns(net_config->ns0_name);
if (!nstoken)
goto fail;
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
SYS(fail, "ip link add %s type veth peer name %s netns %s",
net_config->veth_cfg[i].local_veth, net_config->veth_cfg[i].remote_veth,
net_config->veth_cfg[i].namespace);
SYS(fail, "ip link set dev %s up", net_config->veth_cfg[i].local_veth);
if (net_config->veth_cfg[i].remote_addr[0])
SYS(fail, "ip -n %s addr add %s/24 dev %s",
net_config->veth_cfg[i].namespace,
net_config->veth_cfg[i].remote_addr,
net_config->veth_cfg[i].remote_veth);
SYS(fail, "ip -n %s link set dev %s up", net_config->veth_cfg[i].namespace,
net_config->veth_cfg[i].remote_veth);
}
close_netns(nstoken);
return 0;
fail:
close_netns(nstoken);
return -1;
}
static void cleanup_network(void)
static void cleanup_network(struct net_configuration *net_config)
{
int i;
/* Deleting namespaces is enough to automatically remove veth pairs as well
*/
SYS_NOFAIL("ip netns del %s", net_config->ns0_name);
for (i = 0; i < VETH_PAIRS_COUNT; i++)
SYS_NOFAIL("ip netns del %s", config[i].namespace);
SYS_NOFAIL("ip netns del %s", net_config->veth_cfg[i].namespace);
}
static int check_ping(struct skeletons *skeletons)
#define VETH_REDIRECT_SKEL_NB 3
static void xdp_veth_redirect(u32 flags)
{
struct prog_configuration ping_config[VETH_PAIRS_COUNT] = {
{
.local_name = "xdp_redirect_map_0",
.remote_name = "xdp_dummy_prog",
.local_flags = flags,
.remote_flags = flags,
},
{
.local_name = "xdp_redirect_map_1",
.remote_name = "xdp_tx",
.local_flags = flags,
.remote_flags = flags,
},
{
.local_name = "xdp_redirect_map_2",
.remote_name = "xdp_dummy_prog",
.local_flags = flags,
.remote_flags = flags,
}
};
struct bpf_object *bpf_objs[VETH_REDIRECT_SKEL_NB];
struct xdp_redirect_map *xdp_redirect_map;
struct net_configuration net_config;
struct nstoken *nstoken = NULL;
struct xdp_dummy *xdp_dummy;
struct xdp_tx *xdp_tx;
int map_fd;
int i;
xdp_dummy = xdp_dummy__open_and_load();
if (!ASSERT_OK_PTR(xdp_dummy, "xdp_dummy__open_and_load"))
return;
xdp_tx = xdp_tx__open_and_load();
if (!ASSERT_OK_PTR(xdp_tx, "xdp_tx__open_and_load"))
goto destroy_xdp_dummy;
xdp_redirect_map = xdp_redirect_map__open_and_load();
if (!ASSERT_OK_PTR(xdp_redirect_map, "xdp_redirect_map__open_and_load"))
goto destroy_xdp_tx;
if (!ASSERT_OK(create_network(&net_config), "create network"))
goto destroy_xdp_redirect_map;
/* Then configure the redirect map and attach programs to interfaces */
map_fd = bpf_map__fd(xdp_redirect_map->maps.tx_port);
if (!ASSERT_OK_FD(map_fd, "open redirect map"))
goto destroy_xdp_redirect_map;
bpf_objs[0] = xdp_dummy->obj;
bpf_objs[1] = xdp_tx->obj;
bpf_objs[2] = xdp_redirect_map->obj;
nstoken = open_netns(net_config.ns0_name);
if (!ASSERT_OK_PTR(nstoken, "open NS0"))
goto destroy_xdp_redirect_map;
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
int next_veth = net_config.veth_cfg[i].next_veth;
int interface_id;
int err;
interface_id = if_nametoindex(net_config.veth_cfg[next_veth].local_veth);
if (!ASSERT_NEQ(interface_id, 0, "non zero interface index"))
goto destroy_xdp_redirect_map;
err = bpf_map_update_elem(map_fd, &i, &interface_id, BPF_ANY);
if (!ASSERT_OK(err, "configure interface redirection through map"))
goto destroy_xdp_redirect_map;
if (attach_programs_to_veth_pair(bpf_objs, VETH_REDIRECT_SKEL_NB,
&net_config, ping_config, i))
goto destroy_xdp_redirect_map;
}
/* Test: if all interfaces are properly configured, we must be able to ping
* veth33 from veth11
*/
return SYS_NOFAIL("ip netns exec %s ping -c 1 -W 1 %s > /dev/null",
config[0].namespace, IP_DST);
ASSERT_OK(SYS_NOFAIL("ip netns exec %s ping -c 1 -W 1 %s > /dev/null",
net_config.veth_cfg[0].namespace, IP_DST), "ping");
destroy_xdp_redirect_map:
close_netns(nstoken);
xdp_redirect_map__destroy(xdp_redirect_map);
destroy_xdp_tx:
xdp_tx__destroy(xdp_tx);
destroy_xdp_dummy:
xdp_dummy__destroy(xdp_dummy);
cleanup_network(&net_config);
}
#define BROADCAST_REDIRECT_SKEL_NB 2
static void xdp_veth_broadcast_redirect(u32 attach_flags, u64 redirect_flags)
{
struct prog_configuration prog_cfg[VETH_PAIRS_COUNT] = {
{
.local_name = "xdp_redirect_map_multi_prog",
.remote_name = "xdp_count_0",
.local_flags = attach_flags,
.remote_flags = attach_flags,
},
{
.local_name = "xdp_redirect_map_multi_prog",
.remote_name = "xdp_count_1",
.local_flags = attach_flags,
.remote_flags = attach_flags,
},
{
.local_name = "xdp_redirect_map_multi_prog",
.remote_name = "xdp_count_2",
.local_flags = attach_flags,
.remote_flags = attach_flags,
}
};
struct bpf_object *bpf_objs[BROADCAST_REDIRECT_SKEL_NB];
struct xdp_redirect_multi_kern *xdp_redirect_multi_kern;
struct xdp_redirect_map *xdp_redirect_map;
struct bpf_devmap_val devmap_val = {};
struct net_configuration net_config;
struct nstoken *nstoken = NULL;
u16 protocol = ETH_P_IP;
int group_map;
int flags_map;
int cnt_map;
u64 cnt = 0;
int i, err;
xdp_redirect_multi_kern = xdp_redirect_multi_kern__open_and_load();
if (!ASSERT_OK_PTR(xdp_redirect_multi_kern, "xdp_redirect_multi_kern__open_and_load"))
return;
xdp_redirect_map = xdp_redirect_map__open_and_load();
if (!ASSERT_OK_PTR(xdp_redirect_map, "xdp_redirect_map__open_and_load"))
goto destroy_xdp_redirect_multi_kern;
if (!ASSERT_OK(create_network(&net_config), "create network"))
goto destroy_xdp_redirect_map;
group_map = bpf_map__fd(xdp_redirect_multi_kern->maps.map_all);
if (!ASSERT_OK_FD(group_map, "open map_all"))
goto destroy_xdp_redirect_map;
flags_map = bpf_map__fd(xdp_redirect_multi_kern->maps.redirect_flags);
if (!ASSERT_OK_FD(group_map, "open map_all"))
goto destroy_xdp_redirect_map;
err = bpf_map_update_elem(flags_map, &protocol, &redirect_flags, BPF_NOEXIST);
if (!ASSERT_OK(err, "init IP count"))
goto destroy_xdp_redirect_map;
cnt_map = bpf_map__fd(xdp_redirect_map->maps.rxcnt);
if (!ASSERT_OK_FD(cnt_map, "open rxcnt map"))
goto destroy_xdp_redirect_map;
bpf_objs[0] = xdp_redirect_multi_kern->obj;
bpf_objs[1] = xdp_redirect_map->obj;
nstoken = open_netns(net_config.ns0_name);
if (!ASSERT_OK_PTR(nstoken, "open NS0"))
goto destroy_xdp_redirect_map;
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
int ifindex = if_nametoindex(net_config.veth_cfg[i].local_veth);
if (attach_programs_to_veth_pair(bpf_objs, BROADCAST_REDIRECT_SKEL_NB,
&net_config, prog_cfg, i))
goto destroy_xdp_redirect_map;
SYS(destroy_xdp_redirect_map,
"ip -n %s neigh add %s lladdr 00:00:00:00:00:01 dev %s",
net_config.veth_cfg[i].namespace, IP_NEIGH, net_config.veth_cfg[i].remote_veth);
devmap_val.ifindex = ifindex;
err = bpf_map_update_elem(group_map, &ifindex, &devmap_val, 0);
if (!ASSERT_OK(err, "bpf_map_update_elem"))
goto destroy_xdp_redirect_map;
}
SYS_NOFAIL("ip netns exec %s ping %s -i 0.1 -c 4 -W1 > /dev/null ",
net_config.veth_cfg[0].namespace, IP_NEIGH);
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
err = bpf_map_lookup_elem(cnt_map, &i, &cnt);
if (!ASSERT_OK(err, "get IP cnt"))
goto destroy_xdp_redirect_map;
if (redirect_flags & BPF_F_EXCLUDE_INGRESS)
/* veth11 shouldn't receive the ICMP requests;
* others should
*/
ASSERT_EQ(cnt, i ? 4 : 0, "compare IP cnt");
else
/* All remote veth should receive the ICMP requests */
ASSERT_EQ(cnt, 4, "compare IP cnt");
}
destroy_xdp_redirect_map:
close_netns(nstoken);
xdp_redirect_map__destroy(xdp_redirect_map);
destroy_xdp_redirect_multi_kern:
xdp_redirect_multi_kern__destroy(xdp_redirect_multi_kern);
cleanup_network(&net_config);
}
#define VETH_EGRESS_SKEL_NB 3
static void xdp_veth_egress(u32 flags)
{
struct prog_configuration prog_cfg[VETH_PAIRS_COUNT] = {
{
.local_name = "xdp_redirect_map_all_prog",
.remote_name = "xdp_dummy_prog",
.local_flags = flags,
.remote_flags = flags,
},
{
.local_name = "xdp_redirect_map_all_prog",
.remote_name = "store_mac_1",
.local_flags = flags,
.remote_flags = flags,
},
{
.local_name = "xdp_redirect_map_all_prog",
.remote_name = "store_mac_2",
.local_flags = flags,
.remote_flags = flags,
}
};
const char magic_mac[6] = { 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
struct xdp_redirect_multi_kern *xdp_redirect_multi_kern;
struct bpf_object *bpf_objs[VETH_EGRESS_SKEL_NB];
struct xdp_redirect_map *xdp_redirect_map;
struct bpf_devmap_val devmap_val = {};
struct net_configuration net_config;
int mac_map, egress_map, res_map;
struct nstoken *nstoken = NULL;
struct xdp_dummy *xdp_dummy;
int err;
int i;
xdp_dummy = xdp_dummy__open_and_load();
if (!ASSERT_OK_PTR(xdp_dummy, "xdp_dummy__open_and_load"))
return;
xdp_redirect_multi_kern = xdp_redirect_multi_kern__open_and_load();
if (!ASSERT_OK_PTR(xdp_redirect_multi_kern, "xdp_redirect_multi_kern__open_and_load"))
goto destroy_xdp_dummy;
xdp_redirect_map = xdp_redirect_map__open_and_load();
if (!ASSERT_OK_PTR(xdp_redirect_map, "xdp_redirect_map__open_and_load"))
goto destroy_xdp_redirect_multi_kern;
if (!ASSERT_OK(create_network(&net_config), "create network"))
goto destroy_xdp_redirect_map;
mac_map = bpf_map__fd(xdp_redirect_multi_kern->maps.mac_map);
if (!ASSERT_OK_FD(mac_map, "open mac_map"))
goto destroy_xdp_redirect_map;
egress_map = bpf_map__fd(xdp_redirect_multi_kern->maps.map_egress);
if (!ASSERT_OK_FD(egress_map, "open map_egress"))
goto destroy_xdp_redirect_map;
devmap_val.bpf_prog.fd = bpf_program__fd(xdp_redirect_multi_kern->progs.xdp_devmap_prog);
bpf_objs[0] = xdp_dummy->obj;
bpf_objs[1] = xdp_redirect_multi_kern->obj;
bpf_objs[2] = xdp_redirect_map->obj;
nstoken = open_netns(net_config.ns0_name);
if (!ASSERT_OK_PTR(nstoken, "open NS0"))
goto destroy_xdp_redirect_map;
for (i = 0; i < VETH_PAIRS_COUNT; i++) {
int ifindex = if_nametoindex(net_config.veth_cfg[i].local_veth);
SYS(destroy_xdp_redirect_map,
"ip -n %s neigh add %s lladdr 00:00:00:00:00:01 dev %s",
net_config.veth_cfg[i].namespace, IP_NEIGH, net_config.veth_cfg[i].remote_veth);
if (attach_programs_to_veth_pair(bpf_objs, VETH_REDIRECT_SKEL_NB,
&net_config, prog_cfg, i))
goto destroy_xdp_redirect_map;
err = bpf_map_update_elem(mac_map, &ifindex, magic_mac, 0);
if (!ASSERT_OK(err, "bpf_map_update_elem"))
goto destroy_xdp_redirect_map;
devmap_val.ifindex = ifindex;
err = bpf_map_update_elem(egress_map, &ifindex, &devmap_val, 0);
if (!ASSERT_OK(err, "bpf_map_update_elem"))
goto destroy_xdp_redirect_map;
}
SYS_NOFAIL("ip netns exec %s ping %s -i 0.1 -c 4 -W1 > /dev/null ",
net_config.veth_cfg[0].namespace, IP_NEIGH);
res_map = bpf_map__fd(xdp_redirect_map->maps.rx_mac);
if (!ASSERT_OK_FD(res_map, "open rx_map"))
goto destroy_xdp_redirect_map;
for (i = 0; i < 2; i++) {
u32 key = i;
u64 res;
err = bpf_map_lookup_elem(res_map, &key, &res);
if (!ASSERT_OK(err, "get MAC res"))
goto destroy_xdp_redirect_map;
ASSERT_STRNEQ((const char *)&res, magic_mac, ETH_ALEN, "compare mac");
}
destroy_xdp_redirect_map:
close_netns(nstoken);
xdp_redirect_map__destroy(xdp_redirect_map);
destroy_xdp_redirect_multi_kern:
xdp_redirect_multi_kern__destroy(xdp_redirect_multi_kern);
destroy_xdp_dummy:
xdp_dummy__destroy(xdp_dummy);
cleanup_network(&net_config);
}
void test_xdp_veth_redirect(void)
{
struct skeletons skeletons = {};
if (test__start_subtest("0"))
xdp_veth_redirect(0);
skeletons.xdp_dummy = xdp_dummy__open_and_load();
if (!ASSERT_OK_PTR(skeletons.xdp_dummy, "xdp_dummy__open_and_load"))
return;
if (test__start_subtest("DRV_MODE"))
xdp_veth_redirect(XDP_FLAGS_DRV_MODE);
skeletons.xdp_tx = xdp_tx__open_and_load();
if (!ASSERT_OK_PTR(skeletons.xdp_tx, "xdp_tx__open_and_load"))
goto destroy_xdp_dummy;
skeletons.xdp_redirect_maps = xdp_redirect_map__open_and_load();
if (!ASSERT_OK_PTR(skeletons.xdp_redirect_maps, "xdp_redirect_map__open_and_load"))
goto destroy_xdp_tx;
if (configure_network(&skeletons))
goto destroy_xdp_redirect_map;
ASSERT_OK(check_ping(&skeletons), "ping");
destroy_xdp_redirect_map:
xdp_redirect_map__destroy(skeletons.xdp_redirect_maps);
destroy_xdp_tx:
xdp_tx__destroy(skeletons.xdp_tx);
destroy_xdp_dummy:
xdp_dummy__destroy(skeletons.xdp_dummy);
cleanup_network();
if (test__start_subtest("SKB_MODE"))
xdp_veth_redirect(XDP_FLAGS_SKB_MODE);
}
void test_xdp_veth_broadcast_redirect(void)
{
if (test__start_subtest("0/BROADCAST"))
xdp_veth_broadcast_redirect(0, BPF_F_BROADCAST);
if (test__start_subtest("0/(BROADCAST | EXCLUDE_INGRESS)"))
xdp_veth_broadcast_redirect(0, BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS);
if (test__start_subtest("DRV_MODE/BROADCAST"))
xdp_veth_broadcast_redirect(XDP_FLAGS_DRV_MODE, BPF_F_BROADCAST);
if (test__start_subtest("DRV_MODE/(BROADCAST | EXCLUDE_INGRESS)"))
xdp_veth_broadcast_redirect(XDP_FLAGS_DRV_MODE,
BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS);
if (test__start_subtest("SKB_MODE/BROADCAST"))
xdp_veth_broadcast_redirect(XDP_FLAGS_SKB_MODE, BPF_F_BROADCAST);
if (test__start_subtest("SKB_MODE/(BROADCAST | EXCLUDE_INGRESS)"))
xdp_veth_broadcast_redirect(XDP_FLAGS_SKB_MODE,
BPF_F_BROADCAST | BPF_F_EXCLUDE_INGRESS);
}
void test_xdp_veth_egress(void)
{
if (test__start_subtest("0/egress"))
xdp_veth_egress(0);
if (test__start_subtest("DRV_MODE/egress"))
xdp_veth_egress(XDP_FLAGS_DRV_MODE);
if (test__start_subtest("SKB_MODE/egress"))
xdp_veth_egress(XDP_FLAGS_SKB_MODE);
}

View File

@@ -19,6 +19,7 @@
#include "priv_prog.skel.h"
#include "dummy_st_ops_success.skel.h"
#include "token_lsm.skel.h"
#include "priv_freplace_prog.skel.h"
static inline int sys_mount(const char *dev_name, const char *dir_name,
const char *type, unsigned long flags,
@@ -788,6 +789,84 @@ static int userns_obj_priv_prog(int mnt_fd, struct token_lsm *lsm_skel)
return 0;
}
static int userns_obj_priv_freplace_setup(int mnt_fd, struct priv_freplace_prog **fr_skel,
struct priv_prog **skel, int *tgt_fd)
{
LIBBPF_OPTS(bpf_object_open_opts, opts);
int err;
char buf[256];
/* use bpf_token_path to provide BPF FS path */
snprintf(buf, sizeof(buf), "/proc/self/fd/%d", mnt_fd);
opts.bpf_token_path = buf;
*skel = priv_prog__open_opts(&opts);
if (!ASSERT_OK_PTR(*skel, "priv_prog__open_opts"))
return -EINVAL;
err = priv_prog__load(*skel);
if (!ASSERT_OK(err, "priv_prog__load"))
return -EINVAL;
*fr_skel = priv_freplace_prog__open_opts(&opts);
if (!ASSERT_OK_PTR(*skel, "priv_freplace_prog__open_opts"))
return -EINVAL;
*tgt_fd = bpf_program__fd((*skel)->progs.xdp_prog1);
return 0;
}
/* Verify that freplace works from user namespace, because bpf token is loaded
* in bpf_object__prepare
*/
static int userns_obj_priv_freplace_prog(int mnt_fd, struct token_lsm *lsm_skel)
{
struct priv_freplace_prog *fr_skel = NULL;
struct priv_prog *skel = NULL;
int err, tgt_fd;
err = userns_obj_priv_freplace_setup(mnt_fd, &fr_skel, &skel, &tgt_fd);
if (!ASSERT_OK(err, "setup"))
goto out;
err = bpf_object__prepare(fr_skel->obj);
if (!ASSERT_OK(err, "freplace__prepare"))
goto out;
err = bpf_program__set_attach_target(fr_skel->progs.new_xdp_prog2, tgt_fd, "xdp_prog1");
if (!ASSERT_OK(err, "set_attach_target"))
goto out;
err = priv_freplace_prog__load(fr_skel);
ASSERT_OK(err, "priv_freplace_prog__load");
out:
priv_freplace_prog__destroy(fr_skel);
priv_prog__destroy(skel);
return err;
}
/* Verify that replace fails to set attach target from user namespace without bpf token */
static int userns_obj_priv_freplace_prog_fail(int mnt_fd, struct token_lsm *lsm_skel)
{
struct priv_freplace_prog *fr_skel = NULL;
struct priv_prog *skel = NULL;
int err, tgt_fd;
err = userns_obj_priv_freplace_setup(mnt_fd, &fr_skel, &skel, &tgt_fd);
if (!ASSERT_OK(err, "setup"))
goto out;
err = bpf_program__set_attach_target(fr_skel->progs.new_xdp_prog2, tgt_fd, "xdp_prog1");
if (ASSERT_ERR(err, "attach fails"))
err = 0;
else
err = -EINVAL;
out:
priv_freplace_prog__destroy(fr_skel);
priv_prog__destroy(skel);
return err;
}
/* this test is called with BPF FS that doesn't delegate BPF_BTF_LOAD command,
* which should cause struct_ops application to fail, as BTF won't be uploaded
* into the kernel, even if STRUCT_OPS programs themselves are allowed
@@ -1004,12 +1083,28 @@ void test_token(void)
if (test__start_subtest("obj_priv_prog")) {
struct bpffs_opts opts = {
.cmds = bit(BPF_PROG_LOAD),
.progs = bit(BPF_PROG_TYPE_KPROBE),
.progs = bit(BPF_PROG_TYPE_XDP),
.attachs = ~0ULL,
};
subtest_userns(&opts, userns_obj_priv_prog);
}
if (test__start_subtest("obj_priv_freplace_prog")) {
struct bpffs_opts opts = {
.cmds = bit(BPF_BTF_LOAD) | bit(BPF_PROG_LOAD) | bit(BPF_BTF_GET_FD_BY_ID),
.progs = bit(BPF_PROG_TYPE_EXT) | bit(BPF_PROG_TYPE_XDP),
.attachs = ~0ULL,
};
subtest_userns(&opts, userns_obj_priv_freplace_prog);
}
if (test__start_subtest("obj_priv_freplace_prog_fail")) {
struct bpffs_opts opts = {
.cmds = bit(BPF_BTF_LOAD) | bit(BPF_PROG_LOAD) | bit(BPF_BTF_GET_FD_BY_ID),
.progs = bit(BPF_PROG_TYPE_EXT) | bit(BPF_PROG_TYPE_XDP),
.attachs = ~0ULL,
};
subtest_userns(&opts, userns_obj_priv_freplace_prog_fail);
}
if (test__start_subtest("obj_priv_btf_fail")) {
struct bpffs_opts opts = {
/* disallow BTF loading */

View File

@@ -45,7 +45,7 @@ static void subtest_basic_usdt(void)
LIBBPF_OPTS(bpf_usdt_opts, opts);
struct test_usdt *skel;
struct test_usdt__bss *bss;
int err;
int err, i;
skel = test_usdt__open_and_load();
if (!ASSERT_OK_PTR(skel, "skel_open"))
@@ -75,6 +75,7 @@ static void subtest_basic_usdt(void)
ASSERT_EQ(bss->usdt0_cookie, 0xcafedeadbeeffeed, "usdt0_cookie");
ASSERT_EQ(bss->usdt0_arg_cnt, 0, "usdt0_arg_cnt");
ASSERT_EQ(bss->usdt0_arg_ret, -ENOENT, "usdt0_arg_ret");
ASSERT_EQ(bss->usdt0_arg_size, -ENOENT, "usdt0_arg_size");
/* auto-attached usdt3 gets default zero cookie value */
ASSERT_EQ(bss->usdt3_cookie, 0, "usdt3_cookie");
@@ -86,6 +87,9 @@ static void subtest_basic_usdt(void)
ASSERT_EQ(bss->usdt3_args[0], 1, "usdt3_arg1");
ASSERT_EQ(bss->usdt3_args[1], 42, "usdt3_arg2");
ASSERT_EQ(bss->usdt3_args[2], (uintptr_t)&bla, "usdt3_arg3");
ASSERT_EQ(bss->usdt3_arg_sizes[0], 4, "usdt3_arg1_size");
ASSERT_EQ(bss->usdt3_arg_sizes[1], 8, "usdt3_arg2_size");
ASSERT_EQ(bss->usdt3_arg_sizes[2], 8, "usdt3_arg3_size");
/* auto-attached usdt12 gets default zero cookie value */
ASSERT_EQ(bss->usdt12_cookie, 0, "usdt12_cookie");
@@ -104,6 +108,11 @@ static void subtest_basic_usdt(void)
ASSERT_EQ(bss->usdt12_args[10], nums[idx], "usdt12_arg11");
ASSERT_EQ(bss->usdt12_args[11], t1.y, "usdt12_arg12");
int usdt12_expected_arg_sizes[12] = { 4, 4, 8, 8, 4, 8, 8, 8, 4, 2, 2, 1 };
for (i = 0; i < 12; i++)
ASSERT_EQ(bss->usdt12_arg_sizes[i], usdt12_expected_arg_sizes[i], "usdt12_arg_size");
/* trigger_func() is marked __always_inline, so USDT invocations will be
* inlined in two different places, meaning that each USDT will have
* at least 2 different places to be attached to. This verifies that

View File

@@ -45,6 +45,7 @@
#include "verifier_ldsx.skel.h"
#include "verifier_leak_ptr.skel.h"
#include "verifier_linked_scalars.skel.h"
#include "verifier_load_acquire.skel.h"
#include "verifier_loops1.skel.h"
#include "verifier_lwt.skel.h"
#include "verifier_map_in_map.skel.h"
@@ -80,6 +81,7 @@
#include "verifier_spill_fill.skel.h"
#include "verifier_spin_lock.skel.h"
#include "verifier_stack_ptr.skel.h"
#include "verifier_store_release.skel.h"
#include "verifier_subprog_precision.skel.h"
#include "verifier_subreg.skel.h"
#include "verifier_tailcall_jit.skel.h"
@@ -121,7 +123,7 @@ static void run_tests_aux(const char *skel_name,
/* test_verifier tests are executed w/o CAP_SYS_ADMIN, do the same here */
err = cap_disable_effective(1ULL << CAP_SYS_ADMIN, &old_caps);
if (err) {
PRINT_FAIL("failed to drop CAP_SYS_ADMIN: %i, %s\n", err, strerror(err));
PRINT_FAIL("failed to drop CAP_SYS_ADMIN: %i, %s\n", err, strerror(-err));
return;
}
@@ -131,7 +133,7 @@ static void run_tests_aux(const char *skel_name,
err = cap_enable_effective(old_caps, NULL);
if (err)
PRINT_FAIL("failed to restore CAP_SYS_ADMIN: %i, %s\n", err, strerror(err));
PRINT_FAIL("failed to restore CAP_SYS_ADMIN: %i, %s\n", err, strerror(-err));
}
#define RUN(skel) run_tests_aux(#skel, skel##__elf_bytes, NULL)
@@ -173,6 +175,7 @@ void test_verifier_int_ptr(void) { RUN(verifier_int_ptr); }
void test_verifier_iterating_callbacks(void) { RUN(verifier_iterating_callbacks); }
void test_verifier_jeq_infer_not_null(void) { RUN(verifier_jeq_infer_not_null); }
void test_verifier_jit_convergence(void) { RUN(verifier_jit_convergence); }
void test_verifier_load_acquire(void) { RUN(verifier_load_acquire); }
void test_verifier_ld_ind(void) { RUN(verifier_ld_ind); }
void test_verifier_ldsx(void) { RUN(verifier_ldsx); }
void test_verifier_leak_ptr(void) { RUN(verifier_leak_ptr); }
@@ -211,6 +214,7 @@ void test_verifier_sockmap_mutate(void) { RUN(verifier_sockmap_mutate); }
void test_verifier_spill_fill(void) { RUN(verifier_spill_fill); }
void test_verifier_spin_lock(void) { RUN(verifier_spin_lock); }
void test_verifier_stack_ptr(void) { RUN(verifier_stack_ptr); }
void test_verifier_store_release(void) { RUN(verifier_store_release); }
void test_verifier_subprog_precision(void) { RUN(verifier_subprog_precision); }
void test_verifier_subreg(void) { RUN(verifier_subreg); }
void test_verifier_tailcall_jit(void) { RUN(verifier_tailcall_jit); }

View File

@@ -0,0 +1,175 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Network topology:
* ----------- -----------
* | NS1 | | NS2 |
* | veth0 -|--------|- veth0 |
* ----------- -----------
*
*/
#define _GNU_SOURCE
#include <net/if.h>
#include <uapi/linux/if_link.h>
#include "network_helpers.h"
#include "test_progs.h"
#include "test_xdp_vlan.skel.h"
#define VETH_NAME "veth0"
#define NS_MAX_SIZE 32
#define NS1_NAME "ns-xdp-vlan-1-"
#define NS2_NAME "ns-xdp-vlan-2-"
#define NS1_IP_ADDR "100.64.10.1"
#define NS2_IP_ADDR "100.64.10.2"
#define VLAN_ID 4011
static int setup_network(char *ns1, char *ns2)
{
if (!ASSERT_OK(append_tid(ns1, NS_MAX_SIZE), "create ns1 name"))
goto fail;
if (!ASSERT_OK(append_tid(ns2, NS_MAX_SIZE), "create ns2 name"))
goto fail;
SYS(fail, "ip netns add %s", ns1);
SYS(fail, "ip netns add %s", ns2);
SYS(fail, "ip -n %s link add %s type veth peer name %s netns %s",
ns1, VETH_NAME, VETH_NAME, ns2);
/* NOTICE: XDP require VLAN header inside packet payload
* - Thus, disable VLAN offloading driver features
*/
SYS(fail, "ip netns exec %s ethtool -K %s rxvlan off txvlan off", ns1, VETH_NAME);
SYS(fail, "ip netns exec %s ethtool -K %s rxvlan off txvlan off", ns2, VETH_NAME);
/* NS1 configuration */
SYS(fail, "ip -n %s addr add %s/24 dev %s", ns1, NS1_IP_ADDR, VETH_NAME);
SYS(fail, "ip -n %s link set %s up", ns1, VETH_NAME);
/* NS2 configuration */
SYS(fail, "ip -n %s link add link %s name %s.%d type vlan id %d",
ns2, VETH_NAME, VETH_NAME, VLAN_ID, VLAN_ID);
SYS(fail, "ip -n %s addr add %s/24 dev %s.%d", ns2, NS2_IP_ADDR, VETH_NAME, VLAN_ID);
SYS(fail, "ip -n %s link set %s up", ns2, VETH_NAME);
SYS(fail, "ip -n %s link set %s.%d up", ns2, VETH_NAME, VLAN_ID);
/* At this point ping should fail because VLAN tags are only used by NS2 */
return !SYS_NOFAIL("ip netns exec %s ping -W 1 -c1 %s", ns2, NS1_IP_ADDR);
fail:
return -1;
}
static void cleanup_network(const char *ns1, const char *ns2)
{
SYS_NOFAIL("ip netns del %s", ns1);
SYS_NOFAIL("ip netns del %s", ns2);
}
static void xdp_vlan(struct bpf_program *xdp, struct bpf_program *tc, u32 flags)
{
LIBBPF_OPTS(bpf_tc_hook, tc_hook, .attach_point = BPF_TC_EGRESS);
LIBBPF_OPTS(bpf_tc_opts, tc_opts, .handle = 1, .priority = 1);
char ns1[NS_MAX_SIZE] = NS1_NAME;
char ns2[NS_MAX_SIZE] = NS2_NAME;
struct nstoken *nstoken = NULL;
int interface;
int ret;
if (!ASSERT_OK(setup_network(ns1, ns2), "setup network"))
goto cleanup;
nstoken = open_netns(ns1);
if (!ASSERT_OK_PTR(nstoken, "open NS1"))
goto cleanup;
interface = if_nametoindex(VETH_NAME);
if (!ASSERT_NEQ(interface, 0, "get interface index"))
goto cleanup;
ret = bpf_xdp_attach(interface, bpf_program__fd(xdp), flags, NULL);
if (!ASSERT_OK(ret, "attach xdp_vlan_change"))
goto cleanup;
tc_hook.ifindex = interface;
ret = bpf_tc_hook_create(&tc_hook);
if (!ASSERT_OK(ret, "bpf_tc_hook_create"))
goto detach_xdp;
/* Now we'll use BPF programs to pop/push the VLAN tags */
tc_opts.prog_fd = bpf_program__fd(tc);
ret = bpf_tc_attach(&tc_hook, &tc_opts);
if (!ASSERT_OK(ret, "bpf_tc_attach"))
goto detach_xdp;
close_netns(nstoken);
nstoken = NULL;
/* Now the namespaces can reach each-other, test with pings */
SYS(detach_tc, "ip netns exec %s ping -i 0.2 -W 2 -c 2 %s > /dev/null", ns1, NS2_IP_ADDR);
SYS(detach_tc, "ip netns exec %s ping -i 0.2 -W 2 -c 2 %s > /dev/null", ns2, NS1_IP_ADDR);
detach_tc:
bpf_tc_detach(&tc_hook, &tc_opts);
detach_xdp:
bpf_xdp_detach(interface, flags, NULL);
cleanup:
close_netns(nstoken);
cleanup_network(ns1, ns2);
}
/* First test: Remove VLAN by setting VLAN ID 0, using "xdp_vlan_change"
* egress use TC to add back VLAN tag 4011
*/
void test_xdp_vlan_change(void)
{
struct test_xdp_vlan *skel;
skel = test_xdp_vlan__open_and_load();
if (!ASSERT_OK_PTR(skel, "xdp_vlan__open_and_load"))
return;
if (test__start_subtest("0"))
xdp_vlan(skel->progs.xdp_vlan_change, skel->progs.tc_vlan_push, 0);
if (test__start_subtest("DRV_MODE"))
xdp_vlan(skel->progs.xdp_vlan_change, skel->progs.tc_vlan_push,
XDP_FLAGS_DRV_MODE);
if (test__start_subtest("SKB_MODE"))
xdp_vlan(skel->progs.xdp_vlan_change, skel->progs.tc_vlan_push,
XDP_FLAGS_SKB_MODE);
test_xdp_vlan__destroy(skel);
}
/* Second test: XDP prog fully remove vlan header
*
* Catch kernel bug for generic-XDP, that doesn't allow us to
* remove a VLAN header, because skb->protocol still contain VLAN
* ETH_P_8021Q indication, and this cause overwriting of our changes.
*/
void test_xdp_vlan_remove(void)
{
struct test_xdp_vlan *skel;
skel = test_xdp_vlan__open_and_load();
if (!ASSERT_OK_PTR(skel, "xdp_vlan__open_and_load"))
return;
if (test__start_subtest("0"))
xdp_vlan(skel->progs.xdp_vlan_remove_outer2, skel->progs.tc_vlan_push, 0);
if (test__start_subtest("DRV_MODE"))
xdp_vlan(skel->progs.xdp_vlan_remove_outer2, skel->progs.tc_vlan_push,
XDP_FLAGS_DRV_MODE);
if (test__start_subtest("SKB_MODE"))
xdp_vlan(skel->progs.xdp_vlan_remove_outer2, skel->progs.tc_vlan_push,
XDP_FLAGS_SKB_MODE);
test_xdp_vlan__destroy(skel);
}

View File

@@ -6,6 +6,8 @@
#include <stdbool.h>
#include <stdatomic.h>
#include "bpf_arena_common.h"
#include "../../../include/linux/filter.h"
#include "bpf_misc.h"
struct {
__uint(type, BPF_MAP_TYPE_ARENA);
@@ -19,9 +21,17 @@ struct {
} arena SEC(".maps");
#if defined(ENABLE_ATOMICS_TESTS) && defined(__BPF_FEATURE_ADDR_SPACE_CAST)
bool skip_tests __attribute((__section__(".data"))) = false;
bool skip_all_tests __attribute((__section__(".data"))) = false;
#else
bool skip_tests = true;
bool skip_all_tests = true;
#endif
#if defined(ENABLE_ATOMICS_TESTS) && \
defined(__BPF_FEATURE_ADDR_SPACE_CAST) && \
(defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86))
bool skip_lacq_srel_tests __attribute((__section__(".data"))) = false;
#else
bool skip_lacq_srel_tests = true;
#endif
__u32 pid = 0;
@@ -274,4 +284,111 @@ int uaf(const void *ctx)
return 0;
}
#if __clang_major__ >= 18
__u8 __arena_global load_acquire8_value = 0x12;
__u16 __arena_global load_acquire16_value = 0x1234;
__u32 __arena_global load_acquire32_value = 0x12345678;
__u64 __arena_global load_acquire64_value = 0x1234567890abcdef;
__u8 __arena_global load_acquire8_result = 0;
__u16 __arena_global load_acquire16_result = 0;
__u32 __arena_global load_acquire32_result = 0;
__u64 __arena_global load_acquire64_result = 0;
#else
/* clang-17 crashes if the .addr_space.1 ELF section has holes. Work around
* this issue by defining the below variables as 64-bit.
*/
__u64 __arena_global load_acquire8_value;
__u64 __arena_global load_acquire16_value;
__u64 __arena_global load_acquire32_value;
__u64 __arena_global load_acquire64_value;
__u64 __arena_global load_acquire8_result;
__u64 __arena_global load_acquire16_result;
__u64 __arena_global load_acquire32_result;
__u64 __arena_global load_acquire64_result;
#endif
SEC("raw_tp/sys_enter")
int load_acquire(const void *ctx)
{
#if defined(ENABLE_ATOMICS_TESTS) && \
defined(__BPF_FEATURE_ADDR_SPACE_CAST) && \
(defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86))
#define LOAD_ACQUIRE_ARENA(SIZEOP, SIZE, SRC, DST) \
{ asm volatile ( \
"r1 = %[" #SRC "] ll;" \
"r1 = addr_space_cast(r1, 0x0, 0x1);" \
".8byte %[load_acquire_insn];" \
"r3 = %[" #DST "] ll;" \
"r3 = addr_space_cast(r3, 0x0, 0x1);" \
"*(" #SIZE " *)(r3 + 0) = r2;" \
: \
: __imm_addr(SRC), \
__imm_insn(load_acquire_insn, \
BPF_ATOMIC_OP(BPF_##SIZEOP, BPF_LOAD_ACQ, \
BPF_REG_2, BPF_REG_1, 0)), \
__imm_addr(DST) \
: __clobber_all); } \
LOAD_ACQUIRE_ARENA(B, u8, load_acquire8_value, load_acquire8_result)
LOAD_ACQUIRE_ARENA(H, u16, load_acquire16_value,
load_acquire16_result)
LOAD_ACQUIRE_ARENA(W, u32, load_acquire32_value,
load_acquire32_result)
LOAD_ACQUIRE_ARENA(DW, u64, load_acquire64_value,
load_acquire64_result)
#undef LOAD_ACQUIRE_ARENA
#endif
return 0;
}
#if __clang_major__ >= 18
__u8 __arena_global store_release8_result = 0;
__u16 __arena_global store_release16_result = 0;
__u32 __arena_global store_release32_result = 0;
__u64 __arena_global store_release64_result = 0;
#else
/* clang-17 crashes if the .addr_space.1 ELF section has holes. Work around
* this issue by defining the below variables as 64-bit.
*/
__u64 __arena_global store_release8_result;
__u64 __arena_global store_release16_result;
__u64 __arena_global store_release32_result;
__u64 __arena_global store_release64_result;
#endif
SEC("raw_tp/sys_enter")
int store_release(const void *ctx)
{
#if defined(ENABLE_ATOMICS_TESTS) && \
defined(__BPF_FEATURE_ADDR_SPACE_CAST) && \
(defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86))
#define STORE_RELEASE_ARENA(SIZEOP, DST, VAL) \
{ asm volatile ( \
"r1 = " VAL ";" \
"r2 = %[" #DST "] ll;" \
"r2 = addr_space_cast(r2, 0x0, 0x1);" \
".8byte %[store_release_insn];" \
: \
: __imm_addr(DST), \
__imm_insn(store_release_insn, \
BPF_ATOMIC_OP(BPF_##SIZEOP, BPF_STORE_REL, \
BPF_REG_2, BPF_REG_1, 0)) \
: __clobber_all); } \
STORE_RELEASE_ARENA(B, store_release8_result, "0x12")
STORE_RELEASE_ARENA(H, store_release16_result, "0x1234")
STORE_RELEASE_ARENA(W, store_release32_result, "0x12345678")
STORE_RELEASE_ARENA(DW, store_release64_result,
"0x1234567890abcdef ll")
#undef STORE_RELEASE_ARENA
#endif
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@@ -0,0 +1,51 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
#include "bpf_arena_spin_lock.h"
struct {
__uint(type, BPF_MAP_TYPE_ARENA);
__uint(map_flags, BPF_F_MMAPABLE);
__uint(max_entries, 100); /* number of pages */
#ifdef __TARGET_ARCH_arm64
__ulong(map_extra, 0x1ull << 32); /* start of mmap() region */
#else
__ulong(map_extra, 0x1ull << 44); /* start of mmap() region */
#endif
} arena SEC(".maps");
int cs_count;
#if defined(ENABLE_ATOMICS_TESTS) && defined(__BPF_FEATURE_ADDR_SPACE_CAST)
arena_spinlock_t __arena lock;
int test_skip = 1;
#else
int test_skip = 2;
#endif
int counter;
int limit;
SEC("tc")
int prog(void *ctx)
{
int ret = -2;
#if defined(ENABLE_ATOMICS_TESTS) && defined(__BPF_FEATURE_ADDR_SPACE_CAST)
unsigned long flags;
if ((ret = arena_spin_lock_irqsave(&lock, flags)))
return ret;
if (counter != limit)
counter++;
bpf_repeat(cs_count);
ret = 0;
arena_spin_unlock_irqrestore(&lock, flags);
#endif
return ret;
}
char _license[] SEC("license") = "GPL";

View File

@@ -9,6 +9,13 @@ char _license[] SEC("license") = "GPL";
uint32_t tid = 0;
int num_unknown_tid = 0;
int num_known_tid = 0;
void *user_ptr = 0;
void *user_ptr_long = 0;
uint32_t pid = 0;
static char big_str1[5000];
static char big_str2[5005];
static char big_str3[4996];
SEC("iter/task")
int dump_task(struct bpf_iter__task *ctx)
@@ -35,7 +42,9 @@ int dump_task(struct bpf_iter__task *ctx)
}
int num_expected_failure_copy_from_user_task = 0;
int num_expected_failure_copy_from_user_task_str = 0;
int num_success_copy_from_user_task = 0;
int num_success_copy_from_user_task_str = 0;
SEC("iter.s/task")
int dump_task_sleepable(struct bpf_iter__task *ctx)
@@ -44,6 +53,9 @@ int dump_task_sleepable(struct bpf_iter__task *ctx)
struct task_struct *task = ctx->task;
static const char info[] = " === END ===";
struct pt_regs *regs;
char task_str1[10] = "aaaaaaaaaa";
char task_str2[10], task_str3[10];
char task_str4[20] = "aaaaaaaaaaaaaaaaaaaa";
void *ptr;
uint32_t user_data = 0;
int ret;
@@ -78,8 +90,106 @@ int dump_task_sleepable(struct bpf_iter__task *ctx)
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}
++num_success_copy_from_user_task;
/* Read an invalid pointer and ensure we get an error */
ptr = NULL;
ret = bpf_copy_from_user_task_str((char *)task_str1, sizeof(task_str1), ptr, task, 0);
if (ret >= 0 || task_str1[9] != 'a' || task_str1[0] != '\0') {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}
/* Read an invalid pointer and ensure we get error with pad zeros flag */
ptr = NULL;
ret = bpf_copy_from_user_task_str((char *)task_str1, sizeof(task_str1),
ptr, task, BPF_F_PAD_ZEROS);
if (ret >= 0 || task_str1[9] != '\0' || task_str1[0] != '\0') {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}
++num_expected_failure_copy_from_user_task_str;
/* Same length as the string */
ret = bpf_copy_from_user_task_str((char *)task_str2, 10, user_ptr, task, 0);
/* only need to do the task pid check once */
if (bpf_strncmp(task_str2, 10, "test_data\0") != 0 || ret != 10 || task->tgid != pid) {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}
/* Shorter length than the string */
ret = bpf_copy_from_user_task_str((char *)task_str3, 2, user_ptr, task, 0);
if (bpf_strncmp(task_str3, 2, "t\0") != 0 || ret != 2) {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}
/* Longer length than the string */
ret = bpf_copy_from_user_task_str((char *)task_str4, 20, user_ptr, task, 0);
if (bpf_strncmp(task_str4, 10, "test_data\0") != 0 || ret != 10
|| task_str4[sizeof(task_str4) - 1] != 'a') {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}
/* Longer length than the string with pad zeros flag */
ret = bpf_copy_from_user_task_str((char *)task_str4, 20, user_ptr, task, BPF_F_PAD_ZEROS);
if (bpf_strncmp(task_str4, 10, "test_data\0") != 0 || ret != 10
|| task_str4[sizeof(task_str4) - 1] != '\0') {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}
/* Longer length than the string past a page boundary */
ret = bpf_copy_from_user_task_str(big_str1, 5000, user_ptr, task, 0);
if (bpf_strncmp(big_str1, 10, "test_data\0") != 0 || ret != 10) {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}
/* String that crosses a page boundary */
ret = bpf_copy_from_user_task_str(big_str1, 5000, user_ptr_long, task, BPF_F_PAD_ZEROS);
if (bpf_strncmp(big_str1, 4, "baba") != 0 || ret != 5000
|| bpf_strncmp(big_str1 + 4996, 4, "bab\0") != 0) {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}
for (int i = 0; i < 4999; ++i) {
if (i % 2 == 0) {
if (big_str1[i] != 'b') {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}
} else {
if (big_str1[i] != 'a') {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}
}
}
/* Longer length than the string that crosses a page boundary */
ret = bpf_copy_from_user_task_str(big_str2, 5005, user_ptr_long, task, BPF_F_PAD_ZEROS);
if (bpf_strncmp(big_str2, 4, "baba") != 0 || ret != 5000
|| bpf_strncmp(big_str2 + 4996, 5, "bab\0\0") != 0) {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}
/* Shorter length than the string that crosses a page boundary */
ret = bpf_copy_from_user_task_str(big_str3, 4996, user_ptr_long, task, 0);
if (bpf_strncmp(big_str3, 4, "baba") != 0 || ret != 4996
|| bpf_strncmp(big_str3 + 4992, 4, "bab\0") != 0) {
BPF_SEQ_PRINTF(seq, "%s\n", info);
return 0;
}
++num_success_copy_from_user_task_str;
if (ctx->meta->seq_num == 0)
BPF_SEQ_PRINTF(seq, " tgid gid data\n");

View File

@@ -135,6 +135,8 @@
#define __arch_arm64 __arch("ARM64")
#define __arch_riscv64 __arch("RISCV64")
#define __caps_unpriv(caps) __attribute__((btf_decl_tag("comment:test_caps_unpriv=" EXPAND_QUOTE(caps))))
#define __load_if_JITed() __attribute__((btf_decl_tag("comment:load_mode=jited")))
#define __load_if_no_JITed() __attribute__((btf_decl_tag("comment:load_mode=no_jited")))
/* Define common capabilities tested using __caps_unpriv */
#define CAP_NET_ADMIN 12
@@ -172,6 +174,9 @@
#elif defined(__TARGET_ARCH_riscv)
#define SYSCALL_WRAPPER 1
#define SYS_PREFIX "__riscv_"
#elif defined(__TARGET_ARCH_powerpc)
#define SYSCALL_WRAPPER 1
#define SYS_PREFIX ""
#else
#define SYSCALL_WRAPPER 0
#define SYS_PREFIX "__se_"
@@ -208,4 +213,21 @@
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
#if (defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86) || \
(defined(__TARGET_ARCH_riscv) && __riscv_xlen == 64) || \
defined(__TARGET_ARCH_arm) || defined(__TARGET_ARCH_s390) || \
defined(__TARGET_ARCH_loongarch)) && \
__clang_major__ >= 18
#define CAN_USE_GOTOL
#endif
#if _clang_major__ >= 18
#define CAN_USE_BPF_ST
#endif
#if __clang_major__ >= 18 && defined(ENABLE_ATOMICS_TESTS) && \
(defined(__TARGET_ARCH_arm64) || defined(__TARGET_ARCH_x86))
#define CAN_USE_LOAD_ACQ_STORE_REL
#endif
#endif

View File

@@ -15,7 +15,11 @@
#define SO_KEEPALIVE 9
#define SO_PRIORITY 12
#define SO_REUSEPORT 15
#if defined(__TARGET_ARCH_powerpc)
#define SO_RCVLOWAT 16
#else
#define SO_RCVLOWAT 18
#endif
#define SO_BINDTODEVICE 25
#define SO_MARK 36
#define SO_MAX_PACING_RATE 47

View File

@@ -0,0 +1,3 @@
#include "core_reloc_types.h"
void f(struct core_reloc_arrays___err_bad_signed_arr_elem_sz x) {}

View File

@@ -0,0 +1,41 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
char _license[] SEC("license") = "GPL";
unsigned int idx;
__u8 result[4];
SEC("cgroup/getsockopt")
int child(struct bpf_sockopt *ctx)
{
if (idx < 4)
result[idx++] = 1;
return 1;
}
SEC("cgroup/getsockopt")
int child_2(struct bpf_sockopt *ctx)
{
if (idx < 4)
result[idx++] = 2;
return 1;
}
SEC("cgroup/getsockopt")
int parent(struct bpf_sockopt *ctx)
{
if (idx < 4)
result[idx++] = 3;
return 1;
}
SEC("cgroup/getsockopt")
int parent_2(struct bpf_sockopt *ctx)
{
if (idx < 4)
result[idx++] = 4;
return 1;
}

View File

@@ -1,39 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
__noinline
long changes_pkt_data(struct __sk_buff *sk)
{
return bpf_skb_pull_data(sk, 0);
}
__noinline __weak
long does_not_change_pkt_data(struct __sk_buff *sk)
{
return 0;
}
SEC("?tc")
int main_with_subprogs(struct __sk_buff *sk)
{
changes_pkt_data(sk);
does_not_change_pkt_data(sk);
return 0;
}
SEC("?tc")
int main_changes(struct __sk_buff *sk)
{
bpf_skb_pull_data(sk, 0);
return 0;
}
SEC("?tc")
int main_does_not_change(struct __sk_buff *sk)
{
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@@ -0,0 +1,424 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include "../../../include/linux/filter.h"
#include "bpf_arena_common.h"
#include "bpf_misc.h"
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, __u64);
} test_map SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARENA);
__uint(map_flags, BPF_F_MMAPABLE);
__uint(max_entries, 1);
} arena SEC(".maps");
SEC("socket")
__log_level(2)
__msg(" 0: .......... (b7) r0 = 42")
__msg(" 1: 0......... (bf) r1 = r0")
__msg(" 2: .1........ (bf) r2 = r1")
__msg(" 3: ..2....... (bf) r3 = r2")
__msg(" 4: ...3...... (bf) r4 = r3")
__msg(" 5: ....4..... (bf) r5 = r4")
__msg(" 6: .....5.... (bf) r6 = r5")
__msg(" 7: ......6... (bf) r7 = r6")
__msg(" 8: .......7.. (bf) r8 = r7")
__msg(" 9: ........8. (bf) r9 = r8")
__msg("10: .........9 (bf) r0 = r9")
__msg("11: 0......... (95) exit")
__naked void assign_chain(void)
{
asm volatile (
"r0 = 42;"
"r1 = r0;"
"r2 = r1;"
"r3 = r2;"
"r4 = r3;"
"r5 = r4;"
"r6 = r5;"
"r7 = r6;"
"r8 = r7;"
"r9 = r8;"
"r0 = r9;"
"exit;"
::: __clobber_all);
}
SEC("socket")
__log_level(2)
__msg("0: .......... (b7) r1 = 7")
__msg("1: .1........ (07) r1 += 7")
__msg("2: .......... (b7) r2 = 7")
__msg("3: ..2....... (b7) r3 = 42")
__msg("4: ..23...... (0f) r2 += r3")
__msg("5: .......... (b7) r0 = 0")
__msg("6: 0......... (95) exit")
__naked void arithmetics(void)
{
asm volatile (
"r1 = 7;"
"r1 += 7;"
"r2 = 7;"
"r3 = 42;"
"r2 += r3;"
"r0 = 0;"
"exit;"
::: __clobber_all);
}
#ifdef CAN_USE_BPF_ST
SEC("socket")
__log_level(2)
__msg(" 1: .1........ (07) r1 += -8")
__msg(" 2: .1........ (7a) *(u64 *)(r1 +0) = 7")
__msg(" 3: .1........ (b7) r2 = 42")
__msg(" 4: .12....... (7b) *(u64 *)(r1 +0) = r2")
__msg(" 5: .12....... (7b) *(u64 *)(r1 +0) = r2")
__msg(" 6: .......... (b7) r0 = 0")
__naked void store(void)
{
asm volatile (
"r1 = r10;"
"r1 += -8;"
"*(u64 *)(r1 +0) = 7;"
"r2 = 42;"
"*(u64 *)(r1 +0) = r2;"
"*(u64 *)(r1 +0) = r2;"
"r0 = 0;"
"exit;"
::: __clobber_all);
}
#endif
SEC("socket")
__log_level(2)
__msg("1: ....4..... (07) r4 += -8")
__msg("2: ....4..... (79) r5 = *(u64 *)(r4 +0)")
__msg("3: ....45.... (07) r4 += -8")
__naked void load(void)
{
asm volatile (
"r4 = r10;"
"r4 += -8;"
"r5 = *(u64 *)(r4 +0);"
"r4 += -8;"
"r0 = r5;"
"exit;"
::: __clobber_all);
}
SEC("socket")
__log_level(2)
__msg("0: .1........ (61) r2 = *(u32 *)(r1 +0)")
__msg("1: ..2....... (d4) r2 = le64 r2")
__msg("2: ..2....... (bf) r0 = r2")
__naked void endian(void)
{
asm volatile (
"r2 = *(u32 *)(r1 +0);"
"r2 = le64 r2;"
"r0 = r2;"
"exit;"
::: __clobber_all);
}
SEC("socket")
__log_level(2)
__msg(" 8: 0......... (b7) r1 = 1")
__msg(" 9: 01........ (db) r1 = atomic64_fetch_add((u64 *)(r0 +0), r1)")
__msg("10: 01........ (c3) lock *(u32 *)(r0 +0) += r1")
__msg("11: 01........ (db) r1 = atomic64_xchg((u64 *)(r0 +0), r1)")
__msg("12: 01........ (bf) r2 = r0")
__msg("13: .12....... (bf) r0 = r1")
__msg("14: 012....... (db) r0 = atomic64_cmpxchg((u64 *)(r2 +0), r0, r1)")
__naked void atomic(void)
{
asm volatile (
"r2 = r10;"
"r2 += -8;"
"r1 = 0;"
"*(u64 *)(r2 +0) = r1;"
"r1 = %[test_map] ll;"
"call %[bpf_map_lookup_elem];"
"if r0 == 0 goto 1f;"
"r1 = 1;"
"r1 = atomic_fetch_add((u64 *)(r0 +0), r1);"
".8byte %[add_nofetch];" /* same as "lock *(u32 *)(r0 +0) += r1;" */
"r1 = xchg_64(r0 + 0, r1);"
"r2 = r0;"
"r0 = r1;"
"r0 = cmpxchg_64(r2 + 0, r0, r1);"
"1: exit;"
:
: __imm(bpf_map_lookup_elem),
__imm_addr(test_map),
__imm_insn(add_nofetch, BPF_ATOMIC_OP(BPF_W, BPF_ADD, BPF_REG_0, BPF_REG_1, 0))
: __clobber_all);
}
#ifdef CAN_USE_LOAD_ACQ_STORE_REL
SEC("socket")
__log_level(2)
__msg("2: .12....... (db) store_release((u64 *)(r2 -8), r1)")
__msg("3: .......... (bf) r3 = r10")
__msg("4: ...3...... (db) r4 = load_acquire((u64 *)(r3 -8))")
__naked void atomic_load_acq_store_rel(void)
{
asm volatile (
"r1 = 42;"
"r2 = r10;"
".8byte %[store_release_insn];" /* store_release((u64 *)(r2 - 8), r1); */
"r3 = r10;"
".8byte %[load_acquire_insn];" /* r4 = load_acquire((u64 *)(r3 + 0)); */
"r0 = r4;"
"exit;"
:
: __imm_insn(store_release_insn,
BPF_ATOMIC_OP(BPF_DW, BPF_STORE_REL, BPF_REG_2, BPF_REG_1, -8)),
__imm_insn(load_acquire_insn,
BPF_ATOMIC_OP(BPF_DW, BPF_LOAD_ACQ, BPF_REG_4, BPF_REG_3, -8))
: __clobber_all);
}
#endif /* CAN_USE_LOAD_ACQ_STORE_REL */
SEC("socket")
__log_level(2)
__msg("4: .12....7.. (85) call bpf_trace_printk#6")
__msg("5: 0......7.. (0f) r0 += r7")
__naked void regular_call(void)
{
asm volatile (
"r7 = 1;"
"r1 = r10;"
"r1 += -8;"
"r2 = 1;"
"call %[bpf_trace_printk];"
"r0 += r7;"
"exit;"
:
: __imm(bpf_trace_printk)
: __clobber_all);
}
SEC("socket")
__log_level(2)
__msg("2: 012....... (25) if r1 > 0x7 goto pc+1")
__msg("3: ..2....... (bf) r0 = r2")
__naked void if1(void)
{
asm volatile (
"r0 = 1;"
"r2 = 2;"
"if r1 > 0x7 goto +1;"
"r0 = r2;"
"exit;"
::: __clobber_all);
}
SEC("socket")
__log_level(2)
__msg("3: 0123...... (2d) if r1 > r3 goto pc+1")
__msg("4: ..2....... (bf) r0 = r2")
__naked void if2(void)
{
asm volatile (
"r0 = 1;"
"r2 = 2;"
"r3 = 7;"
"if r1 > r3 goto +1;"
"r0 = r2;"
"exit;"
::: __clobber_all);
}
SEC("socket")
__log_level(2)
__msg("0: .......... (b7) r1 = 0")
__msg("1: .1........ (b7) r2 = 7")
__msg("2: .12....... (25) if r1 > 0x7 goto pc+4")
__msg("3: .12....... (07) r1 += 1")
__msg("4: .12....... (27) r2 *= 2")
__msg("5: .12....... (05) goto pc+0")
__msg("6: .12....... (05) goto pc-5")
__msg("7: .......... (b7) r0 = 0")
__msg("8: 0......... (95) exit")
__naked void loop(void)
{
asm volatile (
"r1 = 0;"
"r2 = 7;"
"if r1 > 0x7 goto +4;"
"r1 += 1;"
"r2 *= 2;"
"goto +0;"
"goto -5;"
"r0 = 0;"
"exit;"
:
: __imm(bpf_trace_printk)
: __clobber_all);
}
#ifdef CAN_USE_GOTOL
SEC("socket")
__log_level(2)
__msg("2: .123...... (25) if r1 > 0x7 goto pc+2")
__msg("3: ..2....... (bf) r0 = r2")
__msg("4: 0......... (06) gotol pc+1")
__msg("5: ...3...... (bf) r0 = r3")
__msg("6: 0......... (95) exit")
__naked void gotol(void)
{
asm volatile (
"r2 = 42;"
"r3 = 24;"
"if r1 > 0x7 goto +2;"
"r0 = r2;"
"gotol +1;"
"r0 = r3;"
"exit;"
:
: __imm(bpf_trace_printk)
: __clobber_all);
}
#endif
SEC("socket")
__log_level(2)
__msg("0: .......... (b7) r1 = 1")
__msg("1: .1........ (e5) may_goto pc+1")
__msg("2: .......... (05) goto pc-3")
__msg("3: .1........ (bf) r0 = r1")
__msg("4: 0......... (95) exit")
__naked void may_goto(void)
{
asm volatile (
"1: r1 = 1;"
".8byte %[may_goto];"
"goto 1b;"
"r0 = r1;"
"exit;"
:
: __imm(bpf_get_smp_processor_id),
__imm_insn(may_goto, BPF_RAW_INSN(BPF_JMP | BPF_JCOND, 0, 0, +1 /* offset */, 0))
: __clobber_all);
}
SEC("socket")
__log_level(2)
__msg("1: 0......... (18) r2 = 0x7")
__msg("3: 0.2....... (0f) r0 += r2")
__naked void ldimm64(void)
{
asm volatile (
"r0 = 0;"
"r2 = 0x7 ll;"
"r0 += r2;"
"exit;"
:
:: __clobber_all);
}
/* No rules specific for LD_ABS/LD_IND, default behaviour kicks in */
SEC("socket")
__log_level(2)
__msg("2: 0123456789 (30) r0 = *(u8 *)skb[42]")
__msg("3: 012.456789 (0f) r7 += r0")
__msg("4: 012.456789 (b7) r3 = 42")
__msg("5: 0123456789 (50) r0 = *(u8 *)skb[r3 + 0]")
__msg("6: 0......7.. (0f) r7 += r0")
__naked void ldabs(void)
{
asm volatile (
"r6 = r1;"
"r7 = 0;"
"r0 = *(u8 *)skb[42];"
"r7 += r0;"
"r3 = 42;"
".8byte %[ld_ind];" /* same as "r0 = *(u8 *)skb[r3];" */
"r7 += r0;"
"r0 = r7;"
"exit;"
:
: __imm_insn(ld_ind, BPF_LD_IND(BPF_B, BPF_REG_3, 0))
: __clobber_all);
}
#ifdef __BPF_FEATURE_ADDR_SPACE_CAST
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
__log_level(2)
__msg(" 6: .12345.... (85) call bpf_arena_alloc_pages")
__msg(" 7: 0......... (bf) r1 = addr_space_cast(r0, 0, 1)")
__msg(" 8: .1........ (b7) r2 = 42")
__naked void addr_space_cast(void)
{
asm volatile (
"r1 = %[arena] ll;"
"r2 = 0;"
"r3 = 1;"
"r4 = 0;"
"r5 = 0;"
"call %[bpf_arena_alloc_pages];"
"r1 = addr_space_cast(r0, 0, 1);"
"r2 = 42;"
"*(u64 *)(r1 +0) = r2;"
"r0 = 0;"
"exit;"
:
: __imm(bpf_arena_alloc_pages),
__imm_addr(arena)
: __clobber_all);
}
#endif
static __used __naked int aux1(void)
{
asm volatile (
"r0 = r1;"
"r0 += r2;"
"exit;"
::: __clobber_all);
}
SEC("socket")
__log_level(2)
__msg("0: ....45.... (b7) r1 = 1")
__msg("1: .1..45.... (b7) r2 = 2")
__msg("2: .12.45.... (b7) r3 = 3")
/* Conservative liveness for subprog parameters. */
__msg("3: .12345.... (85) call pc+2")
__msg("4: .......... (b7) r0 = 0")
__msg("5: 0......... (95) exit")
__msg("6: .12....... (bf) r0 = r1")
__msg("7: 0.2....... (0f) r0 += r2")
/* Conservative liveness for subprog return value. */
__msg("8: 0......... (95) exit")
__naked void subprog1(void)
{
asm volatile (
"r1 = 1;"
"r2 = 2;"
"r3 = 3;"
"call aux1;"
"r0 = 0;"
"exit;"
::: __clobber_all);
}
/* to retain debug info for BTF generation */
void kfunc_root(void)
{
bpf_arena_alloc_pages(0, 0, 0, 0, 0);
}
char _license[] SEC("license") = "GPL";

View File

@@ -13,12 +13,14 @@
#define VERDICT_REJECT 0
#define VERDICT_PROCEED 1
int port;
SEC("cgroup/connect4")
int connect_v4_dropper(struct bpf_sock_addr *ctx)
{
if (ctx->type != SOCK_STREAM)
return VERDICT_PROCEED;
if (ctx->user_port == bpf_htons(60120))
if (ctx->user_port == bpf_htons(port))
return VERDICT_REJECT;
return VERDICT_PROCEED;
}

View File

@@ -347,6 +347,7 @@ struct core_reloc_nesting___err_too_deep {
*/
struct core_reloc_arrays_output {
int a2;
int a3;
char b123;
int c1c;
int d00d;
@@ -455,6 +456,15 @@ struct core_reloc_arrays___err_bad_zero_sz_arr {
struct core_reloc_arrays_substruct d[1][2];
};
struct core_reloc_arrays___err_bad_signed_arr_elem_sz {
/* int -> short (signed!): not supported case */
short a[5];
char b[2][3][4];
struct core_reloc_arrays_substruct c[3];
struct core_reloc_arrays_substruct d[1][2];
struct core_reloc_arrays_substruct f[][2];
};
/*
* PRIMITIVES
*/

View File

@@ -61,6 +61,7 @@ u32 bpf_cpumask_any_distribute(const struct cpumask *src) __ksym __weak;
u32 bpf_cpumask_any_and_distribute(const struct cpumask *src1,
const struct cpumask *src2) __ksym __weak;
u32 bpf_cpumask_weight(const struct cpumask *cpumask) __ksym __weak;
int bpf_cpumask_populate(struct cpumask *cpumask, void *src, size_t src__sz) __ksym __weak;
void bpf_rcu_read_lock(void) __ksym __weak;
void bpf_rcu_read_unlock(void) __ksym __weak;

View File

@@ -222,3 +222,41 @@ int BPF_PROG(test_invalid_nested_array, struct task_struct *task, u64 clone_flag
return 0;
}
SEC("tp_btf/task_newtask")
__failure __msg("type=scalar expected=fp")
int BPF_PROG(test_populate_invalid_destination, struct task_struct *task, u64 clone_flags)
{
struct bpf_cpumask *invalid = (struct bpf_cpumask *)0x123456;
u64 bits;
int ret;
ret = bpf_cpumask_populate((struct cpumask *)invalid, &bits, sizeof(bits));
if (!ret)
err = 2;
return 0;
}
SEC("tp_btf/task_newtask")
__failure __msg("leads to invalid memory access")
int BPF_PROG(test_populate_invalid_source, struct task_struct *task, u64 clone_flags)
{
void *garbage = (void *)0x123456;
struct bpf_cpumask *local;
int ret;
local = create_cpumask();
if (!local) {
err = 1;
return 0;
}
ret = bpf_cpumask_populate((struct cpumask *)local, garbage, 8);
if (!ret)
err = 2;
bpf_cpumask_release(local);
return 0;
}

View File

@@ -749,7 +749,6 @@ out:
}
SEC("tp_btf/task_newtask")
__success
int BPF_PROG(test_refcount_null_tracking, struct task_struct *task, u64 clone_flags)
{
struct bpf_cpumask *mask1, *mask2;
@@ -770,3 +769,122 @@ free_masks_return:
bpf_cpumask_release(mask2);
return 0;
}
SEC("tp_btf/task_newtask")
int BPF_PROG(test_populate_reject_small_mask, struct task_struct *task, u64 clone_flags)
{
struct bpf_cpumask *local;
u8 toofewbits;
int ret;
if (!is_test_task())
return 0;
local = create_cpumask();
if (!local)
return 0;
/* The kfunc should prevent this operation */
ret = bpf_cpumask_populate((struct cpumask *)local, &toofewbits, sizeof(toofewbits));
if (ret != -EACCES)
err = 2;
bpf_cpumask_release(local);
return 0;
}
/* Mask is guaranteed to be large enough for bpf_cpumask_t. */
#define CPUMASK_TEST_MASKLEN (sizeof(cpumask_t))
/* Add an extra word for the test_populate_reject_unaligned test. */
u64 bits[CPUMASK_TEST_MASKLEN / 8 + 1];
extern bool CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS __kconfig __weak;
SEC("tp_btf/task_newtask")
int BPF_PROG(test_populate_reject_unaligned, struct task_struct *task, u64 clone_flags)
{
struct bpf_cpumask *mask;
char *src;
int ret;
if (!is_test_task())
return 0;
/* Skip if unaligned accesses are fine for this arch. */
if (CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS)
return 0;
mask = bpf_cpumask_create();
if (!mask) {
err = 1;
return 0;
}
/* Misalign the source array by a byte. */
src = &((char *)bits)[1];
ret = bpf_cpumask_populate((struct cpumask *)mask, src, CPUMASK_TEST_MASKLEN);
if (ret != -EINVAL)
err = 2;
bpf_cpumask_release(mask);
return 0;
}
SEC("tp_btf/task_newtask")
int BPF_PROG(test_populate, struct task_struct *task, u64 clone_flags)
{
struct bpf_cpumask *mask;
bool bit;
int ret;
int i;
if (!is_test_task())
return 0;
/* Set only odd bits. */
__builtin_memset(bits, 0xaa, CPUMASK_TEST_MASKLEN);
mask = bpf_cpumask_create();
if (!mask) {
err = 1;
return 0;
}
/* Pass the entire bits array, the kfunc will only copy the valid bits. */
ret = bpf_cpumask_populate((struct cpumask *)mask, bits, CPUMASK_TEST_MASKLEN);
if (ret) {
err = 2;
goto out;
}
/*
* Test is there to appease the verifier. We cannot directly
* access NR_CPUS, the upper bound for nr_cpus, so we infer
* it from the size of cpumask_t.
*/
if (nr_cpus < 0 || nr_cpus >= CPUMASK_TEST_MASKLEN * 8) {
err = 3;
goto out;
}
bpf_for(i, 0, nr_cpus) {
/* Odd-numbered bits should be set, even ones unset. */
bit = bpf_cpumask_test_cpu(i, (const struct cpumask *)mask);
if (bit == (i % 2 != 0))
continue;
err = 4;
break;
}
out:
bpf_cpumask_release(mask);
return 0;
}
#undef CPUMASK_TEST_MASKLEN

View File

@@ -1,20 +1,19 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2022 Facebook */
#include <vmlinux.h>
#include <string.h>
#include <stdbool.h>
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
#include "bpf_kfuncs.h"
#include "errno.h"
char _license[] SEC("license") = "GPL";
int pid, err, val;
struct sample {
struct ringbuf_sample {
int pid;
int seq;
long value;
@@ -121,7 +120,7 @@ int test_dynptr_data(void *ctx)
static int ringbuf_callback(__u32 index, void *data)
{
struct sample *sample;
struct ringbuf_sample *sample;
struct bpf_dynptr *ptr = (struct bpf_dynptr *)data;
@@ -138,7 +137,7 @@ SEC("?tp/syscalls/sys_enter_nanosleep")
int test_ringbuf(void *ctx)
{
struct bpf_dynptr ptr;
struct sample *sample;
struct ringbuf_sample *sample;
if (bpf_get_current_pid_tgid() >> 32 != pid)
return 0;
@@ -567,3 +566,117 @@ int BPF_PROG(test_dynptr_skb_tp_btf, void *skb, void *location)
return 1;
}
static inline int bpf_memcmp(const char *a, const char *b, u32 size)
{
int i;
bpf_for(i, 0, size) {
if (a[i] != b[i])
return a[i] < b[i] ? -1 : 1;
}
return 0;
}
SEC("?tp/syscalls/sys_enter_nanosleep")
int test_dynptr_copy(void *ctx)
{
char data[] = "hello there, world!!";
char buf[32] = {'\0'};
__u32 sz = sizeof(data);
struct bpf_dynptr src, dst;
bpf_ringbuf_reserve_dynptr(&ringbuf, sz, 0, &src);
bpf_ringbuf_reserve_dynptr(&ringbuf, sz, 0, &dst);
/* Test basic case of copying contiguous memory backed dynptrs */
err = bpf_dynptr_write(&src, 0, data, sz, 0);
err = err ?: bpf_dynptr_copy(&dst, 0, &src, 0, sz);
err = err ?: bpf_dynptr_read(buf, sz, &dst, 0, 0);
err = err ?: bpf_memcmp(data, buf, sz);
/* Test that offsets are handled correctly */
err = err ?: bpf_dynptr_copy(&dst, 3, &src, 5, sz - 5);
err = err ?: bpf_dynptr_read(buf, sz - 5, &dst, 3, 0);
err = err ?: bpf_memcmp(data + 5, buf, sz - 5);
bpf_ringbuf_discard_dynptr(&src, 0);
bpf_ringbuf_discard_dynptr(&dst, 0);
return 0;
}
SEC("xdp")
int test_dynptr_copy_xdp(struct xdp_md *xdp)
{
struct bpf_dynptr ptr_buf, ptr_xdp;
char data[] = "qwertyuiopasdfghjkl";
char buf[32] = {'\0'};
__u32 len = sizeof(data);
int i, chunks = 200;
/* ptr_xdp is backed by non-contiguous memory */
bpf_dynptr_from_xdp(xdp, 0, &ptr_xdp);
bpf_ringbuf_reserve_dynptr(&ringbuf, len * chunks, 0, &ptr_buf);
/* Destination dynptr is backed by non-contiguous memory */
bpf_for(i, 0, chunks) {
err = bpf_dynptr_write(&ptr_buf, i * len, data, len, 0);
if (err)
goto out;
}
err = bpf_dynptr_copy(&ptr_xdp, 0, &ptr_buf, 0, len * chunks);
if (err)
goto out;
bpf_for(i, 0, chunks) {
__builtin_memset(buf, 0, sizeof(buf));
err = bpf_dynptr_read(&buf, len, &ptr_xdp, i * len, 0);
if (err)
goto out;
if (bpf_memcmp(data, buf, len) != 0)
goto out;
}
/* Source dynptr is backed by non-contiguous memory */
__builtin_memset(buf, 0, sizeof(buf));
bpf_for(i, 0, chunks) {
err = bpf_dynptr_write(&ptr_buf, i * len, buf, len, 0);
if (err)
goto out;
}
err = bpf_dynptr_copy(&ptr_buf, 0, &ptr_xdp, 0, len * chunks);
if (err)
goto out;
bpf_for(i, 0, chunks) {
__builtin_memset(buf, 0, sizeof(buf));
err = bpf_dynptr_read(&buf, len, &ptr_buf, i * len, 0);
if (err)
goto out;
if (bpf_memcmp(data, buf, len) != 0)
goto out;
}
/* Both source and destination dynptrs are backed by non-contiguous memory */
err = bpf_dynptr_copy(&ptr_xdp, 2, &ptr_xdp, len, len * (chunks - 1));
if (err)
goto out;
bpf_for(i, 0, chunks - 1) {
__builtin_memset(buf, 0, sizeof(buf));
err = bpf_dynptr_read(&buf, len, &ptr_xdp, 2 + i * len, 0);
if (err)
goto out;
if (bpf_memcmp(data, buf, len) != 0)
goto out;
}
if (bpf_dynptr_copy(&ptr_xdp, 2000, &ptr_xdp, 0, len * chunks) != -E2BIG)
err = 1;
out:
bpf_ringbuf_discard_dynptr(&ptr_buf, 0);
return XDP_DROP;
}

View File

@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
SEC("fexit/do_exit")
__failure __msg("Attaching fexit/fmod_ret to __noreturn functions is rejected.")
int BPF_PROG(noreturns)
{
return 0;
}

View File

@@ -222,7 +222,7 @@ int __noinline global_local_irq_balance(void)
}
SEC("?tc")
__failure __msg("global function calls are not allowed with IRQs disabled")
__success
int irq_global_subprog(struct __sk_buff *ctx)
{
unsigned long flags;
@@ -441,4 +441,73 @@ int irq_ooo_refs_array(struct __sk_buff *ctx)
return 0;
}
int __noinline
global_subprog(int i)
{
if (i)
bpf_printk("%p", &i);
return i;
}
int __noinline
global_sleepable_helper_subprog(int i)
{
if (i)
bpf_copy_from_user(&i, sizeof(i), NULL);
return i;
}
int __noinline
global_sleepable_kfunc_subprog(int i)
{
if (i)
bpf_copy_from_user_str(&i, sizeof(i), NULL, 0);
global_subprog(i);
return i;
}
int __noinline
global_subprog_calling_sleepable_global(int i)
{
if (!i)
global_sleepable_kfunc_subprog(i);
return i;
}
SEC("?syscall")
__success
int irq_non_sleepable_global_subprog(void *ctx)
{
unsigned long flags;
bpf_local_irq_save(&flags);
global_subprog(0);
bpf_local_irq_restore(&flags);
return 0;
}
SEC("?syscall")
__failure __msg("global functions that may sleep are not allowed in non-sleepable context")
int irq_sleepable_helper_global_subprog(void *ctx)
{
unsigned long flags;
bpf_local_irq_save(&flags);
global_sleepable_helper_subprog(0);
bpf_local_irq_restore(&flags);
return 0;
}
SEC("?syscall")
__failure __msg("global functions that may sleep are not allowed in non-sleepable context")
int irq_sleepable_global_subprog_indirect(void *ctx)
{
unsigned long flags;
bpf_local_irq_save(&flags);
global_subprog_calling_sleepable_global(0);
bpf_local_irq_restore(&flags);
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@@ -7,6 +7,8 @@
#include "bpf_misc.h"
#include "bpf_compiler.h"
#define unlikely(x) __builtin_expect(!!(x), 0)
static volatile int zero = 0;
int my_pid;
@@ -1174,6 +1176,122 @@ __naked int loop_state_deps2(void)
);
}
SEC("?raw_tp")
__failure
__msg("math between fp pointer and register with unbounded")
__flag(BPF_F_TEST_STATE_FREQ)
__naked int loop_state_deps3(void)
{
/* This is equivalent to a C program below.
*
* if (random() != 24) { // assume false branch is placed first
* i = iter_new(); // fp[-8]
* while (iter_next(i));
* iter_destroy(i);
* return;
* }
*
* for (i = 10; i > 0; i--); // increase dfs_depth for child states
*
* i = iter_new(); // fp[-8]
* b = -24; // r8
* for (;;) { // checkpoint (L)
* if (iter_next(i)) // checkpoint (N)
* break;
* if (random() == 77) { // assume false branch is placed first
* *(u64 *)(r10 + b) = 7; // this is not safe when b == -25
* iter_destroy(i);
* return;
* }
* if (random() == 42) { // assume false branch is placed first
* b = -25;
* }
* }
* iter_destroy(i);
*
* In case of a buggy verifier first loop might poison
* env->cur_state->loop_entry with a state having 0 branches
* and small dfs_depth. This would trigger NOT_EXACT states
* comparison for some states within second loop.
* Specifically, checkpoint (L) might be problematic if:
* - branch with '*(u64 *)(r10 + b) = 7' is not explored yet;
* - checkpoint (L) is first reached in state {b=-24};
* - traversal is pruned at checkpoint (N) setting checkpoint's (L)
* branch count to 0, thus making it eligible for use in pruning;
* - checkpoint (L) is next reached in state {b=-25},
* this would cause NOT_EXACT comparison with a state {b=-24}
* while 'b' is not marked precise yet.
*/
asm volatile (
"call %[bpf_get_prandom_u32];"
"if r0 == 24 goto 2f;"
"r1 = r10;"
"r1 += -8;"
"r2 = 0;"
"r3 = 5;"
"call %[bpf_iter_num_new];"
"1:"
"r1 = r10;"
"r1 += -8;"
"call %[bpf_iter_num_next];"
"if r0 != 0 goto 1b;"
"r1 = r10;"
"r1 += -8;"
"call %[bpf_iter_num_destroy];"
"r0 = 0;"
"exit;"
"2:"
/* loop to increase dfs_depth */
"r0 = 10;"
"3:"
"r0 -= 1;"
"if r0 != 0 goto 3b;"
/* end of loop */
"r1 = r10;"
"r1 += -8;"
"r2 = 0;"
"r3 = 10;"
"call %[bpf_iter_num_new];"
"r8 = -24;"
"main_loop_%=:"
"r1 = r10;"
"r1 += -8;"
"call %[bpf_iter_num_next];"
"if r0 == 0 goto main_loop_end_%=;"
/* first if */
"call %[bpf_get_prandom_u32];"
"if r0 == 77 goto unsafe_write_%=;"
/* second if */
"call %[bpf_get_prandom_u32];"
"if r0 == 42 goto poison_r8_%=;"
/* iterate */
"goto main_loop_%=;"
"main_loop_end_%=:"
"r1 = r10;"
"r1 += -8;"
"call %[bpf_iter_num_destroy];"
"r0 = 0;"
"exit;"
"unsafe_write_%=:"
"r0 = r10;"
"r0 += r8;"
"r1 = 7;"
"*(u64 *)(r0 + 0) = r1;"
"goto main_loop_end_%=;"
"poison_r8_%=:"
"r8 = -25;"
"goto main_loop_%=;"
:
: __imm(bpf_get_prandom_u32),
__imm(bpf_iter_num_new),
__imm(bpf_iter_num_next),
__imm(bpf_iter_num_destroy)
: __clobber_all
);
}
SEC("?raw_tp")
__success
__naked int triple_continue(void)
@@ -1512,4 +1630,25 @@ int iter_destroy_bad_arg(const void *ctx)
return 0;
}
SEC("raw_tp")
__success
int clean_live_states(const void *ctx)
{
char buf[1];
int i, j, k, l, m, n, o;
bpf_for(i, 0, 10)
bpf_for(j, 0, 10)
bpf_for(k, 0, 10)
bpf_for(l, 0, 10)
bpf_for(m, 0, 10)
bpf_for(n, 0, 10)
bpf_for(o, 0, 10) {
if (unlikely(bpf_get_prandom_u32()))
buf[0] = 42;
bpf_printk("%s", buf);
}
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@@ -28,6 +28,7 @@ struct {
} sock_map SEC(".maps");
int tcx_init_netns_cookie, tcx_netns_cookie;
int cgroup_skb_init_netns_cookie, cgroup_skb_netns_cookie;
SEC("sockops")
int get_netns_cookie_sockops(struct bpf_sock_ops *ctx)
@@ -91,4 +92,12 @@ int get_netns_cookie_tcx(struct __sk_buff *skb)
return TCX_PASS;
}
SEC("cgroup_skb/ingress")
int get_netns_cookie_cgroup_skb(struct __sk_buff *skb)
{
cgroup_skb_init_netns_cookie = bpf_get_netns_cookie(NULL);
cgroup_skb_netns_cookie = bpf_get_netns_cookie(skb);
return SK_PASS;
}
char _license[] SEC("license") = "GPL";

View File

@@ -134,7 +134,7 @@ int __noinline preempt_global_subprog(void)
}
SEC("?tc")
__failure __msg("global function calls are not allowed with preemption disabled")
__success
int preempt_global_subprog_test(struct __sk_buff *ctx)
{
preempt_disable();
@@ -143,4 +143,70 @@ int preempt_global_subprog_test(struct __sk_buff *ctx)
return 0;
}
int __noinline
global_subprog(int i)
{
if (i)
bpf_printk("%p", &i);
return i;
}
int __noinline
global_sleepable_helper_subprog(int i)
{
if (i)
bpf_copy_from_user(&i, sizeof(i), NULL);
return i;
}
int __noinline
global_sleepable_kfunc_subprog(int i)
{
if (i)
bpf_copy_from_user_str(&i, sizeof(i), NULL, 0);
global_subprog(i);
return i;
}
int __noinline
global_subprog_calling_sleepable_global(int i)
{
if (!i)
global_sleepable_kfunc_subprog(i);
return i;
}
SEC("?syscall")
__failure __msg("global functions that may sleep are not allowed in non-sleepable context")
int preempt_global_sleepable_helper_subprog(struct __sk_buff *ctx)
{
preempt_disable();
if (ctx->mark)
global_sleepable_helper_subprog(ctx->mark);
preempt_enable();
return 0;
}
SEC("?syscall")
__failure __msg("global functions that may sleep are not allowed in non-sleepable context")
int preempt_global_sleepable_kfunc_subprog(struct __sk_buff *ctx)
{
preempt_disable();
if (ctx->mark)
global_sleepable_kfunc_subprog(ctx->mark);
preempt_enable();
return 0;
}
SEC("?syscall")
__failure __msg("global functions that may sleep are not allowed in non-sleepable context")
int preempt_global_sleepable_subprog_indirect(struct __sk_buff *ctx)
{
preempt_disable();
if (ctx->mark)
global_subprog_calling_sleepable_global(ctx->mark);
preempt_enable();
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta */
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
//#include <bpf/bpf_tracing.h>
char _license[] SEC("license") = "GPL";
int err;
struct {
__uint(type, BPF_MAP_TYPE_RINGBUF);
__uint(max_entries, 4096);
} ringbuf SEC(".maps");
struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, __u32);
__type(value, __u32);
} array_map SEC(".maps");
SEC("cgroup_skb/egress")
int program(struct __sk_buff *skb)
{
err = 0;
return 0;
}

View File

@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
#include "vmlinux.h"
#include <bpf/bpf_helpers.h>
char _license[] SEC("license") = "GPL";
SEC("freplace/xdp_prog1")
int new_xdp_prog2(struct xdp_md *xd)
{
return XDP_DROP;
}

View File

@@ -6,8 +6,8 @@
char _license[] SEC("license") = "GPL";
SEC("kprobe")
int kprobe_prog(void *ctx)
SEC("xdp")
int xdp_prog1(struct xdp_md *xdp)
{
return 1;
return XDP_DROP;
}

View File

@@ -0,0 +1,88 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "bpf_misc.h"
#include "../test_kmods/bpf_testmod.h"
#include "../test_kmods/bpf_testmod_kfunc.h"
char _license[] SEC("license") = "GPL";
void __kfunc_btf_root(void)
{
bpf_kfunc_st_ops_inc10(NULL);
}
static __noinline __used int subprog(struct st_ops_args *args)
{
args->a += 1;
return args->a;
}
__success
/* prologue */
__xlated("0: r8 = r1")
__xlated("1: r1 = 0")
__xlated("2: call kernel-function")
__xlated("3: if r0 != 0x0 goto pc+5")
__xlated("4: r6 = *(u64 *)(r8 +0)")
__xlated("5: r7 = *(u64 *)(r6 +0)")
__xlated("6: r7 += 1000")
__xlated("7: *(u64 *)(r6 +0) = r7")
__xlated("8: goto pc+2")
__xlated("9: r1 = r0")
__xlated("10: call kernel-function")
__xlated("11: r1 = r8")
/* save __u64 *ctx to stack */
__xlated("12: *(u64 *)(r10 -8) = r1")
/* main prog */
__xlated("13: r1 = *(u64 *)(r1 +0)")
__xlated("14: r6 = r1")
__xlated("15: call kernel-function")
__xlated("16: r1 = r6")
__xlated("17: call pc+")
/* epilogue */
__xlated("18: r1 = 0")
__xlated("19: r6 = 0")
__xlated("20: call kernel-function")
__xlated("21: if r0 != 0x0 goto pc+6")
__xlated("22: r1 = *(u64 *)(r10 -8)")
__xlated("23: r1 = *(u64 *)(r1 +0)")
__xlated("24: r6 = *(u64 *)(r1 +0)")
__xlated("25: r6 += 10000")
__xlated("26: *(u64 *)(r1 +0) = r6")
__xlated("27: goto pc+2")
__xlated("28: r1 = r0")
__xlated("29: call kernel-function")
__xlated("30: r0 = r6")
__xlated("31: r0 *= 2")
__xlated("32: exit")
SEC("struct_ops/test_pro_epilogue")
__naked int test_kfunc_pro_epilogue(void)
{
asm volatile (
"r1 = *(u64 *)(r1 +0);"
"r6 = r1;"
"call %[bpf_kfunc_st_ops_inc10];"
"r1 = r6;"
"call subprog;"
"exit;"
:
: __imm(bpf_kfunc_st_ops_inc10)
: __clobber_all);
}
SEC("syscall")
__retval(22022) /* (PROLOGUE_A [1000] + KFUNC_INC10 + SUBPROG_A [1] + EPILOGUE_A [10000]) * 2 */
int syscall_pro_epilogue(void *ctx)
{
struct st_ops_args args = {};
return bpf_kfunc_st_ops_test_pro_epilogue(&args);
}
SEC(".struct_ops.link")
struct bpf_testmod_st_ops pro_epilogue_with_kfunc = {
.test_pro_epilogue = (void *)test_kfunc_pro_epilogue,
};

View File

@@ -242,7 +242,8 @@ out:
}
SEC("?lsm.s/bpf")
int BPF_PROG(inproper_sleepable_kfunc, int cmd, union bpf_attr *attr, unsigned int size)
int BPF_PROG(inproper_sleepable_kfunc, int cmd, union bpf_attr *attr, unsigned int size,
bool kernel)
{
struct bpf_key *bkey;
@@ -439,3 +440,61 @@ int rcu_read_lock_global_subprog_unlock(void *ctx)
ret += global_subprog_unlock(ret);
return 0;
}
int __noinline
global_sleepable_helper_subprog(int i)
{
if (i)
bpf_copy_from_user(&i, sizeof(i), NULL);
return i;
}
int __noinline
global_sleepable_kfunc_subprog(int i)
{
if (i)
bpf_copy_from_user_str(&i, sizeof(i), NULL, 0);
global_subprog(i);
return i;
}
int __noinline
global_subprog_calling_sleepable_global(int i)
{
if (!i)
global_sleepable_kfunc_subprog(i);
return i;
}
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
int rcu_read_lock_sleepable_helper_global_subprog(void *ctx)
{
volatile int ret = 0;
bpf_rcu_read_lock();
ret += global_sleepable_helper_subprog(ret);
bpf_rcu_read_unlock();
return 0;
}
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
int rcu_read_lock_sleepable_kfunc_global_subprog(void *ctx)
{
volatile int ret = 0;
bpf_rcu_read_lock();
ret += global_sleepable_kfunc_subprog(ret);
bpf_rcu_read_unlock();
return 0;
}
SEC("?fentry.s/" SYS_PREFIX "sys_getpgid")
int rcu_read_lock_sleepable_global_subprog_indirect(void *ctx)
{
volatile int ret = 0;
bpf_rcu_read_lock();
ret += global_subprog_calling_sleepable_global(ret);
bpf_rcu_read_unlock();
return 0;
}

View File

@@ -8,14 +8,16 @@
int target_pid = 0;
void *user_ptr = 0;
int read_ret[9];
int read_ret[10];
char _license[] SEC("license") = "GPL";
/*
* This is the only kfunc, the others are helpers
* These are the kfuncs, the others are helpers
*/
int bpf_copy_from_user_str(void *dst, u32, const void *, u64) __weak __ksym;
int bpf_copy_from_user_task_str(void *dst, u32, const void *,
struct task_struct *, u64) __weak __ksym;
SEC("fentry/" SYS_PREFIX "sys_nanosleep")
int do_probe_read(void *ctx)
@@ -47,6 +49,11 @@ int do_copy_from_user(void *ctx)
read_ret[7] = bpf_copy_from_user_task(buf, sizeof(buf), user_ptr,
bpf_get_current_task_btf(), 0);
read_ret[8] = bpf_copy_from_user_str((char *)buf, sizeof(buf), user_ptr, 0);
read_ret[9] = bpf_copy_from_user_task_str((char *)buf,
sizeof(buf),
user_ptr,
bpf_get_current_task_btf(),
0);
return 0;
}

View File

@@ -0,0 +1,47 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
#include "bpf_experimental.h"
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
#include <stdbool.h>
char _license[] SEC("license") = "GPL";
enum Enum { EA1 = 0, EA2 = 11 };
enum Enumu64 {EB1 = 0llu, EB2 = 12llu };
enum Enums64 { EC1 = 0ll, EC2 = 13ll };
const volatile __s64 var_s64 = -1;
const volatile __u64 var_u64 = 0;
const volatile __s32 var_s32 = -1;
const volatile __u32 var_u32 = 0;
const volatile __s16 var_s16 = -1;
const volatile __u16 var_u16 = 0;
const volatile __s8 var_s8 = -1;
const volatile __u8 var_u8 = 0;
const volatile enum Enum var_ea = EA1;
const volatile enum Enumu64 var_eb = EB1;
const volatile enum Enums64 var_ec = EC1;
const volatile bool var_b = false;
char arr[4] = {0};
SEC("socket")
int test_set_globals(void *ctx)
{
volatile __s8 a;
a = var_s64;
a = var_u64;
a = var_s32;
a = var_u32;
a = var_s16;
a = var_u16;
a = var_s8;
a = var_u8;
a = var_ea;
a = var_eb;
a = var_ec;
a = var_b;
return a;
}

View File

@@ -35,7 +35,10 @@ static __always_inline int local_strncmp(const char *s1, unsigned int sz,
SEC("tp/syscalls/sys_enter_getpgid")
int strncmp_no_helper(void *ctx)
{
if (local_strncmp(str, cmp_str_len + 1, target) < 0)
const char *target_str = target;
barrier_var(target_str);
if (local_strncmp(str, cmp_str_len + 1, target_str) < 0)
__sync_add_and_fetch(&hits, 1);
return 0;
}

View File

@@ -0,0 +1,30 @@
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "../test_kmods/bpf_testmod.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
void bpf_task_release(struct task_struct *p) __ksym;
/* This test struct_ops BPF programs returning referenced kptr. The verifier should
* allow a referenced kptr or a NULL pointer to be returned. A referenced kptr to task
* here is acquried automatically as the task argument is tagged with "__ref".
*/
SEC("struct_ops/test_return_ref_kptr")
struct task_struct *BPF_PROG(kptr_return, int dummy,
struct task_struct *task, struct cgroup *cgrp)
{
if (dummy % 2) {
bpf_task_release(task);
return NULL;
}
return task;
}
SEC(".struct_ops.link")
struct bpf_testmod_ops testmod_kptr_return = {
.test_return_ref_kptr = (void *)kptr_return,
};

View File

@@ -0,0 +1,26 @@
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "../test_kmods/bpf_testmod.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
struct cgroup *bpf_cgroup_acquire(struct cgroup *p) __ksym;
void bpf_task_release(struct task_struct *p) __ksym;
/* This test struct_ops BPF programs returning referenced kptr. The verifier should
* reject programs returning a non-zero scalar value.
*/
SEC("struct_ops/test_return_ref_kptr")
__failure __msg("At program exit the register R0 has smin=1 smax=1 should have been in [0, 0]")
struct task_struct *BPF_PROG(kptr_return_fail__invalid_scalar, int dummy,
struct task_struct *task, struct cgroup *cgrp)
{
bpf_task_release(task);
return (struct task_struct *)1;
}
SEC(".struct_ops.link")
struct bpf_testmod_ops testmod_kptr_return = {
.test_return_ref_kptr = (void *)kptr_return_fail__invalid_scalar,
};

View File

@@ -0,0 +1,34 @@
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "../test_kmods/bpf_testmod.h"
#include "bpf_experimental.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
struct cgroup *bpf_cgroup_acquire(struct cgroup *p) __ksym;
void bpf_task_release(struct task_struct *p) __ksym;
/* This test struct_ops BPF programs returning referenced kptr. The verifier should
* reject programs returning a local kptr.
*/
SEC("struct_ops/test_return_ref_kptr")
__failure __msg("At program exit the register R0 is not a known value (ptr_or_null_)")
struct task_struct *BPF_PROG(kptr_return_fail__local_kptr, int dummy,
struct task_struct *task, struct cgroup *cgrp)
{
struct task_struct *t;
bpf_task_release(task);
t = bpf_obj_new(typeof(*task));
if (!t)
return NULL;
return t;
}
SEC(".struct_ops.link")
struct bpf_testmod_ops testmod_kptr_return = {
.test_return_ref_kptr = (void *)kptr_return_fail__local_kptr,
};

View File

@@ -0,0 +1,25 @@
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "../test_kmods/bpf_testmod.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
struct cgroup *bpf_cgroup_acquire(struct cgroup *p) __ksym;
void bpf_task_release(struct task_struct *p) __ksym;
/* This test struct_ops BPF programs returning referenced kptr. The verifier should
* reject programs returning a modified referenced kptr.
*/
SEC("struct_ops/test_return_ref_kptr")
__failure __msg("dereference of modified trusted_ptr_ ptr R0 off={{[0-9]+}} disallowed")
struct task_struct *BPF_PROG(kptr_return_fail__nonzero_offset, int dummy,
struct task_struct *task, struct cgroup *cgrp)
{
return (struct task_struct *)&task->jobctl;
}
SEC(".struct_ops.link")
struct bpf_testmod_ops testmod_kptr_return = {
.test_return_ref_kptr = (void *)kptr_return_fail__nonzero_offset,
};

View File

@@ -0,0 +1,30 @@
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "../test_kmods/bpf_testmod.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
struct cgroup *bpf_cgroup_acquire(struct cgroup *p) __ksym;
void bpf_task_release(struct task_struct *p) __ksym;
/* This test struct_ops BPF programs returning referenced kptr. The verifier should
* reject programs returning a referenced kptr of the wrong type.
*/
SEC("struct_ops/test_return_ref_kptr")
__failure __msg("At program exit the register R0 is not a known value (ptr_or_null_)")
struct task_struct *BPF_PROG(kptr_return_fail__wrong_type, int dummy,
struct task_struct *task, struct cgroup *cgrp)
{
struct task_struct *ret;
ret = (struct task_struct *)bpf_cgroup_acquire(cgrp);
bpf_task_release(task);
return ret;
}
SEC(".struct_ops.link")
struct bpf_testmod_ops testmod_kptr_return = {
.test_return_ref_kptr = (void *)kptr_return_fail__wrong_type,
};

View File

@@ -0,0 +1,31 @@
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "../test_kmods/bpf_testmod.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
__attribute__((nomerge)) extern void bpf_task_release(struct task_struct *p) __ksym;
/* This is a test BPF program that uses struct_ops to access a referenced
* kptr argument. This is a test for the verifier to ensure that it
* 1) recongnizes the task as a referenced object (i.e., ref_obj_id > 0), and
* 2) the same reference can be acquired from multiple paths as long as it
* has not been released.
*/
SEC("struct_ops/test_refcounted")
int BPF_PROG(refcounted, int dummy, struct task_struct *task)
{
if (dummy == 1)
bpf_task_release(task);
else
bpf_task_release(task);
return 0;
}
SEC(".struct_ops.link")
struct bpf_testmod_ops testmod_refcounted = {
.test_refcounted = (void *)refcounted,
};

View File

@@ -0,0 +1,39 @@
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "../test_kmods/bpf_testmod.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
extern void bpf_task_release(struct task_struct *p) __ksym;
__noinline int subprog_release(__u64 *ctx __arg_ctx)
{
struct task_struct *task = (struct task_struct *)ctx[1];
int dummy = (int)ctx[0];
bpf_task_release(task);
return dummy + 1;
}
/* Test that the verifier rejects a program that contains a global
* subprogram with referenced kptr arguments
*/
SEC("struct_ops/test_refcounted")
__failure __log_level(2)
__msg("Validating subprog_release() func#1...")
__msg("invalid bpf_context access off=8. Reference may already be released")
int refcounted_fail__global_subprog(unsigned long long *ctx)
{
struct task_struct *task = (struct task_struct *)ctx[1];
bpf_task_release(task);
return subprog_release(ctx);
}
SEC(".struct_ops.link")
struct bpf_testmod_ops testmod_ref_acquire = {
.test_refcounted = (void *)refcounted_fail__global_subprog,
};

View File

@@ -0,0 +1,22 @@
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "../test_kmods/bpf_testmod.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
/* Test that the verifier rejects a program that acquires a referenced
* kptr through context without releasing the reference
*/
SEC("struct_ops/test_refcounted")
__failure __msg("Unreleased reference id=1 alloc_insn=0")
int BPF_PROG(refcounted_fail__ref_leak, int dummy,
struct task_struct *task)
{
return 0;
}
SEC(".struct_ops.link")
struct bpf_testmod_ops testmod_ref_acquire = {
.test_refcounted = (void *)refcounted_fail__ref_leak,
};

View File

@@ -0,0 +1,36 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
#include <bpf/bpf_tracing.h>
#include "../test_kmods/bpf_testmod.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
struct {
__uint(type, BPF_MAP_TYPE_PROG_ARRAY);
__uint(max_entries, 1);
__uint(key_size, sizeof(__u32));
__uint(value_size, sizeof(__u32));
} prog_array SEC(".maps");
/* Test that the verifier rejects a program with referenced kptr arguments
* that tail call
*/
SEC("struct_ops/test_refcounted")
__failure __msg("program with __ref argument cannot tail call")
int refcounted_fail__tail_call(unsigned long long *ctx)
{
struct task_struct *task = (struct task_struct *)ctx[1];
bpf_task_release(task);
bpf_tail_call(ctx, &prog_array, 0);
return 0;
}
SEC(".struct_ops.link")
struct bpf_testmod_ops testmod_ref_acquire = {
.test_refcounted = (void *)refcounted_fail__tail_call,
};

View File

@@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-2.0
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include "bpf_misc.h"
__noinline
long changes_pkt_data(struct __sk_buff *sk)
{
return bpf_skb_pull_data(sk, 0);
}
__noinline __weak
long does_not_change_pkt_data(struct __sk_buff *sk)
{
return 0;
}
SEC("?tc")
int main_changes_with_subprogs(struct __sk_buff *sk)
{
changes_pkt_data(sk);
does_not_change_pkt_data(sk);
return 0;
}
SEC("?tc")
int main_changes(struct __sk_buff *sk)
{
bpf_skb_pull_data(sk, 0);
return 0;
}
SEC("?tc")
int main_does_not_change(struct __sk_buff *sk)
{
return 0;
}
__noinline
long might_sleep(struct pt_regs *ctx __arg_ctx)
{
int i;
bpf_copy_from_user(&i, sizeof(i), NULL);
return i;
}
__noinline __weak
long does_not_sleep(struct pt_regs *ctx __arg_ctx)
{
return 0;
}
SEC("?uprobe.s")
int main_might_sleep_with_subprogs(struct pt_regs *ctx)
{
might_sleep(ctx);
does_not_sleep(ctx);
return 0;
}
SEC("?uprobe.s")
int main_might_sleep(struct pt_regs *ctx)
{
int i;
bpf_copy_from_user(&i, sizeof(i), NULL);
return i;
}
SEC("?uprobe.s")
int main_does_not_sleep(struct pt_regs *ctx)
{
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bpf.h>
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
SEC("?freplace")
@@ -15,4 +15,19 @@ long does_not_change_pkt_data(struct __sk_buff *sk)
return 0;
}
SEC("?freplace")
long might_sleep(struct pt_regs *ctx)
{
int i;
bpf_copy_from_user(&i, sizeof(i), NULL);
return i;
}
SEC("?freplace")
long does_not_sleep(struct pt_regs *ctx)
{
return 0;
}
char _license[] SEC("license") = "GPL";

View File

@@ -51,13 +51,13 @@ out:
}
SEC("lsm/bpf")
int BPF_PROG(lsm_run, int cmd, union bpf_attr *attr, unsigned int size)
int BPF_PROG(lsm_run, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
{
return bpf_link_create_verify(cmd);
}
SEC("lsm.s/bpf")
int BPF_PROG(lsm_s_run, int cmd, union bpf_attr *attr, unsigned int size)
int BPF_PROG(lsm_s_run, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
{
return bpf_link_create_verify(cmd);
}

View File

@@ -15,6 +15,7 @@ struct {
struct core_reloc_arrays_output {
int a2;
int a3;
char b123;
int c1c;
int d00d;
@@ -41,6 +42,7 @@ int test_core_arrays(void *ctx)
{
struct core_reloc_arrays *in = (void *)&data.in;
struct core_reloc_arrays_output *out = (void *)&data.out;
int *a;
if (CORE_READ(&out->a2, &in->a[2]))
return 1;
@@ -53,6 +55,9 @@ int test_core_arrays(void *ctx)
if (CORE_READ(&out->f01c, &in->f[0][1].c))
return 1;
a = __builtin_preserve_access_index(({ in->a; }));
out->a3 = a[0] + a[1] + a[2] + a[3];
return 0;
}

View File

@@ -6,6 +6,7 @@
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
#include "bpf_kfuncs.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
@@ -17,12 +18,23 @@ static const char expected_value[] = "hello";
char value1[32];
char value2[32];
/* Matches caller of test_get_xattr() in prog_tests/fs_kfuncs.c */
static const char xattr_names[][64] = {
/* The following work. */
"user.kfuncs",
"security.bpf.xxx",
/* The following do not work. */
"security.bpf",
"security.selinux"
};
SEC("lsm.s/file_open")
int BPF_PROG(test_file_open, struct file *f)
{
struct bpf_dynptr value_ptr;
__u32 pid;
int ret;
int ret, i;
pid = bpf_get_current_pid_tgid() >> 32;
if (pid != monitored_pid)
@@ -30,7 +42,11 @@ int BPF_PROG(test_file_open, struct file *f)
bpf_dynptr_from_mem(value1, sizeof(value1), 0, &value_ptr);
ret = bpf_get_file_xattr(f, "user.kfuncs", &value_ptr);
for (i = 0; i < ARRAY_SIZE(xattr_names); i++) {
ret = bpf_get_file_xattr(f, xattr_names[i], &value_ptr);
if (ret == sizeof(expected_value))
break;
}
if (ret != sizeof(expected_value))
return 0;
if (bpf_strncmp(value1, ret, expected_value))
@@ -44,7 +60,7 @@ int BPF_PROG(test_inode_getxattr, struct dentry *dentry, char *name)
{
struct bpf_dynptr value_ptr;
__u32 pid;
int ret;
int ret, i;
pid = bpf_get_current_pid_tgid() >> 32;
if (pid != monitored_pid)
@@ -52,7 +68,11 @@ int BPF_PROG(test_inode_getxattr, struct dentry *dentry, char *name)
bpf_dynptr_from_mem(value2, sizeof(value2), 0, &value_ptr);
ret = bpf_get_dentry_xattr(dentry, "user.kfuncs", &value_ptr);
for (i = 0; i < ARRAY_SIZE(xattr_names); i++) {
ret = bpf_get_dentry_xattr(dentry, xattr_names[i], &value_ptr);
if (ret == sizeof(expected_value))
break;
}
if (ret != sizeof(expected_value))
return 0;
if (bpf_strncmp(value2, ret, expected_value))

View File

@@ -0,0 +1,28 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2025 Microsoft Corporation
*
* Author: Blaise Boscaccy <bboscaccy@linux.microsoft.com>
*/
#include "vmlinux.h"
#include <errno.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>
char _license[] SEC("license") = "GPL";
__u32 monitored_tid;
SEC("lsm.s/bpf")
int BPF_PROG(bpf, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
{
__u32 tid;
tid = bpf_get_current_pid_tgid() & 0xFFFFFFFF;
if (!kernel || tid != monitored_tid)
return 0;
else
return -EINVAL;
}

View File

@@ -36,7 +36,7 @@ char _license[] SEC("license") = "GPL";
SEC("?lsm.s/bpf")
__failure __msg("cannot pass in dynptr at an offset=-8")
int BPF_PROG(not_valid_dynptr, int cmd, union bpf_attr *attr, unsigned int size)
int BPF_PROG(not_valid_dynptr, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
{
unsigned long val;
@@ -46,7 +46,7 @@ int BPF_PROG(not_valid_dynptr, int cmd, union bpf_attr *attr, unsigned int size)
SEC("?lsm.s/bpf")
__failure __msg("arg#0 expected pointer to stack or const struct bpf_dynptr")
int BPF_PROG(not_ptr_to_stack, int cmd, union bpf_attr *attr, unsigned int size)
int BPF_PROG(not_ptr_to_stack, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
{
unsigned long val = 0;
@@ -55,7 +55,7 @@ int BPF_PROG(not_ptr_to_stack, int cmd, union bpf_attr *attr, unsigned int size)
}
SEC("lsm.s/bpf")
int BPF_PROG(dynptr_data_null, int cmd, union bpf_attr *attr, unsigned int size)
int BPF_PROG(dynptr_data_null, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
{
struct bpf_key *trusted_keyring;
struct bpf_dynptr ptr;

View File

@@ -23,7 +23,7 @@ extern struct bpf_key *bpf_lookup_system_key(__u64 id) __ksym;
extern void bpf_key_put(struct bpf_key *key) __ksym;
SEC("lsm.s/bpf")
int BPF_PROG(bpf, int cmd, union bpf_attr *attr, unsigned int size)
int BPF_PROG(bpf, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
{
struct bpf_key *bkey;
__u32 pid;

View File

@@ -7,7 +7,7 @@
char tp_name[128];
SEC("lsm.s/bpf")
int BPF_PROG(lsm_run, int cmd, union bpf_attr *attr, unsigned int size)
int BPF_PROG(lsm_run, int cmd, union bpf_attr *attr, unsigned int size, bool kernel)
{
switch (cmd) {
case BPF_RAW_TRACEPOINT_OPEN:

View File

@@ -1,7 +1,6 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2018 Facebook */
#include <stdlib.h>
#include <linux/in.h>
#include <linux/ip.h>
#include <linux/ipv6.h>

View File

@@ -0,0 +1,133 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2024 Meta Platforms, Inc. and affiliates. */
#include "vmlinux.h"
#include <errno.h>
#include <bpf/bpf_tracing.h>
#include "bpf_kfuncs.h"
#include "bpf_misc.h"
char _license[] SEC("license") = "GPL";
__u32 monitored_pid;
const char xattr_foo[] = "security.bpf.foo";
const char xattr_bar[] = "security.bpf.bar";
static const char xattr_selinux[] = "security.selinux";
char value_bar[] = "world";
char read_value[32];
bool set_security_bpf_bar_success;
bool remove_security_bpf_bar_success;
bool set_security_selinux_fail;
bool remove_security_selinux_fail;
char name_buf[32];
static inline bool name_match_foo(const char *name)
{
bpf_probe_read_kernel(name_buf, sizeof(name_buf), name);
return !bpf_strncmp(name_buf, sizeof(xattr_foo), xattr_foo);
}
/* Test bpf_set_dentry_xattr and bpf_remove_dentry_xattr */
SEC("lsm.s/inode_getxattr")
int BPF_PROG(test_inode_getxattr, struct dentry *dentry, char *name)
{
struct bpf_dynptr value_ptr;
__u32 pid;
int ret;
pid = bpf_get_current_pid_tgid() >> 32;
if (pid != monitored_pid)
return 0;
/* Only do the following for security.bpf.foo */
if (!name_match_foo(name))
return 0;
bpf_dynptr_from_mem(read_value, sizeof(read_value), 0, &value_ptr);
/* read security.bpf.bar */
ret = bpf_get_dentry_xattr(dentry, xattr_bar, &value_ptr);
if (ret < 0) {
/* If security.bpf.bar doesn't exist, set it */
bpf_dynptr_from_mem(value_bar, sizeof(value_bar), 0, &value_ptr);
ret = bpf_set_dentry_xattr(dentry, xattr_bar, &value_ptr, 0);
if (!ret)
set_security_bpf_bar_success = true;
ret = bpf_set_dentry_xattr(dentry, xattr_selinux, &value_ptr, 0);
if (ret)
set_security_selinux_fail = true;
} else {
/* If security.bpf.bar exists, remove it */
ret = bpf_remove_dentry_xattr(dentry, xattr_bar);
if (!ret)
remove_security_bpf_bar_success = true;
ret = bpf_remove_dentry_xattr(dentry, xattr_selinux);
if (ret)
remove_security_selinux_fail = true;
}
return 0;
}
bool locked_set_security_bpf_bar_success;
bool locked_remove_security_bpf_bar_success;
bool locked_set_security_selinux_fail;
bool locked_remove_security_selinux_fail;
/* Test bpf_set_dentry_xattr_locked and bpf_remove_dentry_xattr_locked.
* It not necessary to differentiate the _locked version and the
* not-_locked version in the BPF program. The verifier will fix them up
* properly.
*/
SEC("lsm.s/inode_setxattr")
int BPF_PROG(test_inode_setxattr, struct mnt_idmap *idmap,
struct dentry *dentry, const char *name,
const void *value, size_t size, int flags)
{
struct bpf_dynptr value_ptr;
__u32 pid;
int ret;
pid = bpf_get_current_pid_tgid() >> 32;
if (pid != monitored_pid)
return 0;
/* Only do the following for security.bpf.foo */
if (!name_match_foo(name))
return 0;
bpf_dynptr_from_mem(read_value, sizeof(read_value), 0, &value_ptr);
/* read security.bpf.bar */
ret = bpf_get_dentry_xattr(dentry, xattr_bar, &value_ptr);
if (ret < 0) {
/* If security.bpf.bar doesn't exist, set it */
bpf_dynptr_from_mem(value_bar, sizeof(value_bar), 0, &value_ptr);
ret = bpf_set_dentry_xattr(dentry, xattr_bar, &value_ptr, 0);
if (!ret)
locked_set_security_bpf_bar_success = true;
ret = bpf_set_dentry_xattr(dentry, xattr_selinux, &value_ptr, 0);
if (ret)
locked_set_security_selinux_fail = true;
} else {
/* If security.bpf.bar exists, remove it */
ret = bpf_remove_dentry_xattr(dentry, xattr_bar);
if (!ret)
locked_remove_security_bpf_bar_success = true;
ret = bpf_remove_dentry_xattr(dentry, xattr_selinux);
if (ret)
locked_remove_security_selinux_fail = true;
}
return 0;
}

Some files were not shown because too many files have changed in this diff Show More