From: Tom Smyth Subject: trunk(4): adding a bridge/veb/tpmr member to a trunk panics on teardown To: bugs@openbsd.org Cc: tech Date: Wed, 4 Nov 2026 00:13:27 +0000 >Synopsis: trunk_port_create() fails to reject an interface that is already a bridge(4)/veb(4)/tpmr(4) port; destroying the stack later triggers a NULL ether_port deref (panic on DIAGNOSTIC). >Category: kernel >Severity: serious >Priority: medium >Class: sw-bug >Release: OpenBSD 8.0-current (snapshot, GENERIC.MP #125, 2026-11-02) >Environment: System : OpenBSD 8.0 Details : OpenBSD 8.0-current (GENERIC.MP) #125: Mon Nov 2 04:56:12 CST 2026 deraadt@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/GENERIC.MP Architecture: OpenBSD.amd64 Machine : amd64 (QEMU/KVM guest, 4 vcpu) >Description: ifconfig trunkport accepts an interface that is already enslaved to a bridge(4), veb(4) or tpmr(4). trunk_port_create() (sys/net/if_trunk.c) guards against joining a second trunk/aggr by testing ac_trport, but never tests ether_brport_isset(), so the bridge-family membership is not detected. aggr_add_port() checks both and is not affected. Both the bridge-family driver and trunk(4) interpose the member's if_ioctl by saving the previous handler and installing their own: tpmr add: p_ioctl = ifp->if_ioctl; /* original */ ifp->if_ioctl = tpmr_p_ioctl; ether_brport_set() trunk add: tp_ioctl = ifp->if_ioctl; /* == tpmr_p_ioctl */ ifp->if_ioctl = trunk_port_ioctl; Only the tpmr-first / trunk-second order is reachable, because trunk_port_create() sets if_type = IFT_IEEE8023ADLAG, after which the bridge-family add would fail its IFT_ETHER check. If the bridge-family interface is then destroyed (or the member is removed from it) before the trunk is torn down, tpmr_p_dtor() clears ac_brport and restores if_ioctl, but trunk(4) still holds tp_ioctl pointing at the now-stale tpmr_p_ioctl. When the member is finally detached, trunk_port_destroy() restores that stale pointer and calls ifnewlladdr(), which re-enters tpmr_p_ioctl() with ac_brport == NULL: panic: kernel diagnostic assertion "ep != NULL" failed: file ".../sys/net/if_tpmr.c", line 684 vether101: tpmr_p_ioctl called without an ether_brport set db_enter() panic() tpmr_p_ioctl() tpmr_p_ioctl+0xe1 ifnewlladdr() ifnewlladdr+0x104 trunk_port_destroy() trunk_port_destroy+0x2e8 if_hooks_run() if_hooks_run+0xdb if_deactivate() if_deactivate+0x34 ether_ifdetach() ether_ifdetach+0x21 vether_clone_destroy() if_clone_destroy() ifioctl() sys_ioctl() syscall() On a non-DIAGNOSTIC (GENERIC) kernel the KASSERT is compiled out and this is an unchecked NULL pointer dereference in tpmr_p_ioctl(). The operations are all available to root via ioctl(SIOCSTRUNKPORT) / SIOCIFDESTROY; no special hardware is involved. bridge(4) and veb(4) exhibit the same acceptance bug (same missing check); the reverse order is refused in every case because of the if_type change above. >How-To-Repeat: As root: ifconfig tpmr0 create ifconfig trunk0 create ifconfig vether0 create ifconfig tpmr0 add vether0 ifconfig trunk0 trunkport vether0 # should fail EBUSY, but succeeds ifconfig tpmr0 destroy # clears ac_brport; trunk unaware ifconfig vether0 destroy # panic >Fix: Make trunk_port_create() reject an interface that is already a bridge-family port, mirroring aggr_add_port(). trunk_port_create() already has a local "int error = 0;" and ether_brport_isset() returns EBUSY when ac_brport is set. Index: sys/net/if_trunk.c =================================================================== --- sys/net/if_trunk.c +++ sys/net/if_trunk.c @@ -295,6 +295,10 @@ if (SMR_PTR_GET_LOCKED(&ac0->ac_trport) != NULL) return (EBUSY); + /* Reject a port already enslaved to a bridge(4)/veb(4)/tpmr(4) */ + if ((error = ether_brport_isset(ifp)) != 0) + return (error); + /* Take MTU from the first member port */ if (SLIST_EMPTY(&tr->tr_ports)) { if (tr->tr_ifflags & IFF_DEBUG) With the diff applied, the "trunkport" step above returns "device busy" and the panic no longer occurs. >Verification: Built GENERIC.MP from -current with the diff applied and booted it (OpenBSD 8.0-current GENERIC.MP #0, 2026-11-03). On the stock #125 kernel the How-To-Repeat sequence panics at the final destroy; on the patched kernel: # ifconfig trunk0 trunkport vether0 ifconfig: trunk0 vether0: SIOCSTRUNKPORT: Device busy i.e. the enslave is now refused with EBUSY and the subsequent teardown no longer panics. Tested on an isolated amd64 KVM guest. -- Kindest regards, Tom Smyth.