Re: userret: returning with the following locks held

From: Konstantin Belousov <kostikbel_at_gmail.com>
Date: Wed, 18 Mar 2026 18:01:45 UTC
On Wed, Mar 18, 2026 at 05:36:00PM +0800, Zhenlei Huang wrote:
> Hi,
> 
> While I'm working on the if_detach() / if_vmove() racing issue and I wrote
> a test and try to repeat the race condition and got this assert panic,
> 
> ```
> <6>epair1b: link state changed to UP
> userret: returning with the following locks held:
> exclusive sx ifnet_detach_sx (ifnet_detach_sx) r = 1 (0xffffffff83ab75e0) locked @ /home/zlei/freebsd-src/sys/net/if.c:1286
> panic: witness_warn
> cpuid = 2
> time = 1773824038
> KDB: stack backtrace:
> db_trace_self_wrapper() at db_trace_self_wrapper+0xa5/frame 0xfffffe00ec001710
> kdb_backtrace() at kdb_backtrace+0xc6/frame 0xfffffe00ec001870
> vpanic() at vpanic+0x214/frame 0xfffffe00ec001a10
> panic() at panic+0xb5/frame 0xfffffe00ec001ad0
> witness_warn() at witness_warn+0x7f7/frame 0xfffffe00ec001c30
> userret() at userret+0x11c/frame 0xfffffe00ec001d10
> amd64_syscall() at amd64_syscall+0x694/frame 0xfffffe00ec001f30
> fast_syscall_common() at fast_syscall_common+0xf8/frame 0xfffffe00ec001f30
> --- syscall (19, FreeBSD ELF64, compat.lseek), rip = 0xfd288de65ba, rsp = 0xfd284aad4b8, rbp = 0xfd284aad500 ---
> Uptime: 29m8s
> Dumping 1355 out of 16137 MB:..2%..11%..21%..31%..41%..51%..61%..71%..81%..91%
> ```
> 
> The line 1286 of sys/net/if.c is ( my WIP revision ),
> 
> ```
> 1250 static int
> 1251 if_vmove_reclaim(struct thread *td, char *ifname, int jid)
> 1252 {
> ...
> 1282         /* Get interface back from child jail/vnet. */
> 1283         found = if_unlink_ifnet(ifp, true);
> 1284         MPASS(found);
> 1285         sx_xlock(&ifnet_detach_sxlock);
> 1286         if_vmove(ifp, vnet_dst);
> 1287         sx_xunlock(&ifnet_detach_sxlock);
> 1288 
> 1289         /* Report the new if_xname back to the userland. */
> 1290         sprintf(ifname, "%s", ifp->if_xname);
> 1291 
> 1292         prison_free(pr);
> 1293         CURVNET_RESTORE();
> 1294         return (0);
> 1295 }
> ```
> 
> That puzzled me a lot. The current thread only reaches to `if_vmove()` , how does it happen to userret() ?
> 
> Is that witness warn is false report ?

I doubt that this is false report.

userret() is the common code that prepares thread for return from kernel
to usermode.  As part of the preparation, it checks that  no locks is
left owned by the thread.  What you see is the result of the failing
check.

In other words, you have some syscall that locked a lock, did not released
it, then returned.  The layer that manages return to userspace caught the
issue.