// SPDX-License-Identifier: GPL-2.0 /* Copyright (c) 2024-2025 Meta Platforms, Inc. and affiliates. */ #include #include #include #include "bpf_misc.h" #define EDEADLK 35 #define ETIMEDOUT 110 struct arr_elem { struct bpf_res_spin_lock lock; }; struct { __uint(type, BPF_MAP_TYPE_ARRAY); __uint(max_entries, 64); __type(key, int); __type(value, struct arr_elem); } arrmap SEC(".maps"); struct bpf_res_spin_lock lockA __hidden SEC(".data.A"); struct bpf_res_spin_lock lockB __hidden SEC(".data.B"); SEC("tc") int res_spin_lock_test(struct __sk_buff *ctx) { struct arr_elem *elem1, *elem2; int r; elem1 = bpf_map_lookup_elem(&arrmap, &(int){0}); if (!elem1) return -1; elem2 = bpf_map_lookup_elem(&arrmap, &(int){0}); if (!elem2) return -1; r = bpf_res_spin_lock(&elem1->lock); if (r) return r; r = bpf_res_spin_lock(&elem2->lock); if (!r) { bpf_res_spin_unlock(&elem2->lock); bpf_res_spin_unlock(&elem1->lock); return -1; } bpf_res_spin_unlock(&elem1->lock); return r != -EDEADLK; } SEC("tc") int res_spin_lock_test_AB(struct __sk_buff *ctx) { int r; r = bpf_res_spin_lock(&lockA); if (r) return !r; /* Only unlock if we took the lock. */ if (!bpf_res_spin_lock(&lockB)) bpf_res_spin_unlock(&lockB); bpf_res_spin_unlock(&lockA); return 0; } int err; SEC("tc") int res_spin_lock_test_BA(struct __sk_buff *ctx) { int r; r = bpf_res_spin_lock(&lockB); if (r) return !r; if (!bpf_res_spin_lock(&lockA)) bpf_res_spin_unlock(&lockA); else err = -EDEADLK; bpf_res_spin_unlock(&lockB); return err ?: 0; } SEC("tc") int res_spin_lock_test_held_lock_max(struct __sk_buff *ctx) { struct bpf_res_spin_lock *locks[48] = {}; struct arr_elem *e; u64 time_beg, time; int ret = 0, i; _Static_assert(ARRAY_SIZE(((struct rqspinlock_held){}).locks) == 31, "RES_NR_HELD assumed to be 31"); for (i = 0; i < 34; i++) { int key = i; /* We cannot pass in i as it will get spilled/filled by the compiler and * loses bounds in verifier state. */ e = bpf_map_lookup_elem(&arrmap, &key); if (!e) return 1; locks[i] = &e->lock; } for (; i < 48; i++) { int key = i - 2; /* We cannot pass in i as it will get spilled/filled by the compiler and * loses bounds in verifier state. */ e = bpf_map_lookup_elem(&arrmap, &key); if (!e) return 1; locks[i] = &e->lock; } time_beg = bpf_ktime_get_ns(); for (i = 0; i < 34; i++) { if (bpf_res_spin_lock(locks[i])) goto end; } /* Trigger AA, after exhausting entries in the held lock table. This * time, only the timeout can save us, as AA detection won't succeed. */ ret = bpf_res_spin_lock(locks[34]); if (!ret) { bpf_res_spin_unlock(locks[34]); ret = 1; goto end; } ret = ret != -ETIMEDOUT ? 2 : 0; end: for (i = i - 1; i >= 0; i--) bpf_res_spin_unlock(locks[i]); time = bpf_ktime_get_ns() - time_beg; /* Time spent should be easily above our limit (1/4 s), since AA * detection won't be expedited due to lack of held lock entry. */ return ret ?: (time > 1000000000 / 4 ? 0 : 1); } char _license[] SEC("license") = "GPL";