Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 78 additions & 101 deletions pocs/linux/kernelctf/CVE-2025-38248_cos/docs/vulnerability.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
##bridge: mcast: Fix use-after-free during router port configuration
[ Upstream commit 7544f3f5b0b58c396f374d060898b5939da31709 ]
# Vulnerability Details

- **Requirements**:
- **Capabilities**: `CAP_NET_ADMIN`
- **Kernel configuration**: `CONFIG_BRIDGE_IGMP_SNOOPING`
- **User namespaces required**: Yes
- **Introduced by**:
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4b30ae9adb04 ("net: bridge: mcast: re-implement br_multicast_{enable, disable}_port functions")
- https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2796d846d74a ("net: bridge: vlan: convert mcast router global option to per-vlan entry")
- **Fixed by**: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=f05a4f9e959e0fc098046044c650acf897ea52d2
- **Affected Versions**: `5.15 - 6.15.5`
- **Affected Component**: `net/bridge` (multicast snooping)
- **Cause**: Use-after-free
- **Description**: When per-VLAN multicast snooping is toggled, `br_multicast_port_ctx_deinit()` only cancels multicast router timers but does not remove the port from the global (or per-VLAN) multicast router port lists. A port in permanent multicast router state (`mcast_router=2`) can be re-added to the router list after snooping mode changes, and when the port is subsequently deleted, a dangling pointer remains in the list. Traversing the list (e.g., when adding a new port) triggers a use-after-free on the freed `net_bridge_port` object.

# Vulnerability Analysis

The bridge maintains a global list of ports behind which a multicast
router resides. The list is consulted during forwarding to ensure
Expand All @@ -10,21 +24,25 @@ When per-VLAN multicast snooping is enabled, the per-port multicast
context is disabled on each port and the port is removed from the global
router port list:

# ip link add name br1 up type bridge vlan_filtering 1 mcast_snooping 1
# ip link add name dummy1 up master br1 type dummy
# ip link set dev dummy1 type bridge_slave mcast_router 2
$ bridge -d mdb show | grep router
router ports on br1: dummy1
# ip link set dev br1 type bridge mcast_vlan_snooping 1
$ bridge -d mdb show | grep router
```bash
# ip link add name br1 up type bridge vlan_filtering 1 mcast_snooping 1
# ip link add name dummy1 up master br1 type dummy
# ip link set dev dummy1 type bridge_slave mcast_router 2
$ bridge -d mdb show | grep router
router ports on br1: dummy1
# ip link set dev br1 type bridge mcast_vlan_snooping 1
$ bridge -d mdb show | grep router
```

However, the port can be re-added to the global list even when per-VLAN
multicast snooping is enabled:

# ip link set dev dummy1 type bridge_slave mcast_router 0
# ip link set dev dummy1 type bridge_slave mcast_router 2
$ bridge -d mdb show | grep router
router ports on br1: dummy1
```bash
# ip link set dev dummy1 type bridge_slave mcast_router 0
# ip link set dev dummy1 type bridge_slave mcast_router 2
$ bridge -d mdb show | grep router
router ports on br1: dummy1
```

Since commit 4b30ae9adb04 ("net: bridge: mcast: re-implement
br_multicast_{enable, disable}_port functions"), when per-VLAN multicast
Expand All @@ -34,32 +52,38 @@ result, a port will remain in the global router port list even after it
is deleted. This will lead to a use-after-free [1] when the list is
traversed (when adding a new port to the list, for example):

# ip link del dev dummy1
# ip link add name dummy2 up master br1 type dummy
# ip link set dev dummy2 type bridge_slave mcast_router 2
```bash
# ip link del dev dummy1
# ip link add name dummy2 up master br1 type dummy
# ip link set dev dummy2 type bridge_slave mcast_router 2
```

Similarly, stale entries can also be found in the per-VLAN router port
list. When per-VLAN multicast snooping is disabled, the per-{port, VLAN}
contexts are disabled on each port and the port is removed from the
per-VLAN router port list:

# ip link add name br1 up type bridge vlan_filtering 1 mcast_snooping 1 mcast_vlan_snooping 1
# ip link add name dummy1 up master br1 type dummy
# bridge vlan add vid 2 dev dummy1
# bridge vlan global set vid 2 dev br1 mcast_snooping 1
# bridge vlan set vid 2 dev dummy1 mcast_router 2
$ bridge vlan global show dev br1 vid 2 | grep router
router ports: dummy1
# ip link set dev br1 type bridge mcast_vlan_snooping 0
$ bridge vlan global show dev br1 vid 2 | grep router
```bash
# ip link add name br1 up type bridge vlan_filtering 1 mcast_snooping 1 mcast_vlan_snooping 1
# ip link add name dummy1 up master br1 type dummy
# bridge vlan add vid 2 dev dummy1
# bridge vlan global set vid 2 dev br1 mcast_snooping 1
# bridge vlan set vid 2 dev dummy1 mcast_router 2
$ bridge vlan global show dev br1 vid 2 | grep router
router ports: dummy1
# ip link set dev br1 type bridge mcast_vlan_snooping 0
$ bridge vlan global show dev br1 vid 2 | grep router
```

However, the port can be re-added to the per-VLAN list even when
per-VLAN multicast snooping is disabled:

# bridge vlan set vid 2 dev dummy1 mcast_router 0
# bridge vlan set vid 2 dev dummy1 mcast_router 2
$ bridge vlan global show dev br1 vid 2 | grep router
router ports: dummy1
```bash
# bridge vlan set vid 2 dev dummy1 mcast_router 0
# bridge vlan set vid 2 dev dummy1 mcast_router 2
$ bridge vlan global show dev br1 vid 2 | grep router
router ports: dummy1
```

When the VLAN is deleted from the port, the per-{port, VLAN} multicast
context will not be disabled since multicast snooping is not enabled
Expand All @@ -68,80 +92,33 @@ port list even after it is no longer member in the VLAN. This will lead
to a use-after-free [2] when the list is traversed (when adding a new
port to the list, for example):

# ip link add name dummy2 up master br1 type dummy
# bridge vlan add vid 2 dev dummy2
# bridge vlan del vid 2 dev dummy1
# bridge vlan set vid 2 dev dummy2 mcast_router 2

Fix these issues by removing the port from the relevant (global or
per-VLAN) router port list in br_multicast_port_ctx_deinit(). The
function is invoked during port deletion with the per-port multicast
context and during VLAN deletion with the per-{port, VLAN} multicast
context.
```bash
# ip link add name dummy2 up master br1 type dummy
# bridge vlan add vid 2 dev dummy2
# bridge vlan del vid 2 dev dummy1
# bridge vlan set vid 2 dev dummy2 mcast_router 2
```

The root cause is that `br_multicast_port_ctx_deinit()` only cancels
multicast router timers but does not remove the port from the router
port lists:

```c
void br_multicast_port_ctx_deinit(struct net_bridge_mcast_port *pmctx)
{
#if IS_ENABLED(CONFIG_IPV6)
del_timer_sync(&pmctx->ip6_mc_router_timer);
#endif
del_timer_sync(&pmctx->ip4_mc_router_timer);
// Missing: br_ip4_multicast_rport_del(pmctx)
// Missing: br_ip6_multicast_rport_del(pmctx)
}
```

The fix adds the missing list removal calls so that ports are properly
unlinked from the router port lists during port/VLAN deletion.

Note that deleting the multicast router timer is not enough as it only
takes care of the temporary multicast router states (1 or 3) and not the
permanent one (2).

[1]
BUG: KASAN: slab-out-of-bounds in br_multicast_add_router.part.0+0x3f1/0x560
Write of size 8 at addr ffff888004a67328 by task ip/384
[...]
Call Trace:
<TASK>
dump_stack_lvl+0x6f/0xa0
print_address_description.constprop.0+0x6f/0x350
print_report+0x108/0x205
kasan_report+0xdf/0x110
br_multicast_add_router.part.0+0x3f1/0x560
br_multicast_set_port_router+0x74e/0xac0
br_setport+0xa55/0x1870
br_port_slave_changelink+0x95/0x120
__rtnl_newlink+0x5e8/0xa40
rtnl_newlink+0x627/0xb00
rtnetlink_rcv_msg+0x6fb/0xb70
netlink_rcv_skb+0x11f/0x350
netlink_unicast+0x426/0x710
netlink_sendmsg+0x75a/0xc20
__sock_sendmsg+0xc1/0x150
____sys_sendmsg+0x5aa/0x7b0
___sys_sendmsg+0xfc/0x180
__sys_sendmsg+0x124/0x1c0
do_syscall_64+0xbb/0x360
entry_SYSCALL_64_after_hwframe+0x4b/0x53

[2]
BUG: KASAN: slab-use-after-free in br_multicast_add_router.part.0+0x378/0x560
Read of size 8 at addr ffff888009f00840 by task bridge/391
[...]
Call Trace:
<TASK>
dump_stack_lvl+0x6f/0xa0
print_address_description.constprop.0+0x6f/0x350
print_report+0x108/0x205
kasan_report+0xdf/0x110
br_multicast_add_router.part.0+0x378/0x560
br_multicast_set_port_router+0x6f9/0xac0
br_vlan_process_options+0x8b6/0x1430
br_vlan_rtm_process_one+0x605/0xa30
br_vlan_rtm_process+0x396/0x4c0
rtnetlink_rcv_msg+0x2f7/0xb70
netlink_rcv_skb+0x11f/0x350
netlink_unicast+0x426/0x710
netlink_sendmsg+0x75a/0xc20
__sock_sendmsg+0xc1/0x150
____sys_sendmsg+0x5aa/0x7b0
___sys_sendmsg+0xfc/0x180
__sys_sendmsg+0x124/0x1c0
do_syscall_64+0xbb/0x360
entry_SYSCALL_64_after_hwframe+0x4b/0x53

Fixes: 2796d846d74a ("net: bridge: vlan: convert mcast router global option to per-vlan entry")
Fixes: 4b30ae9adb04 ("net: bridge: mcast: re-implement br_multicast_{enable, disable}_port functions")
Reported-by: syzbot+7bfa4b72c6a5da128d32@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/all/684c18bd.a00a0220.279073.000b.GAE@google.com/T/
Signed-off-by: Ido Schimmel <idosch@nvidia.com>
Link: https://patch.msgid.link/20250619182228.1656906-1-idosch@nvidia.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>

Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
CFLAGS = -static -pthread -s
KERNELXDK_INCLUDE_DIR ?= /usr/local/include
KERNELXDK_LIB_DIR ?= /usr/lib

CXX = g++
CXXFLAGS = -I. -I$(KERNELXDK_INCLUDE_DIR) -static -pthread -s
LDFLAGS = -L$(KERNELXDK_LIB_DIR) -lkernelXDK -lkeyutils

exploit: exploit.c
$(CC) $(CFLAGS) -o $@ $<
exploit: exploit.cpp target_db.kxdb
$(CXX) $(CXXFLAGS) -o $@ $< $(LDFLAGS)

exploit_debug: exploit.c
$(CC) $(CFLAGS) -o $@ $< -g
exploit_debug: exploit.cpp target_db.kxdb
$(CXX) $(CXXFLAGS) -g -o $@ $< $(LDFLAGS)

target_db.kxdb:
wget -O target_db.kxdb https://storage.googleapis.com/kernelxdk/db/kernelctf.kxdb

clean:
rm -f exploit
rm -f exploit exploit_debug target_db.kxdb

.PHONY: clean
Loading
Loading