Page MenuHomeFreeBSD

Recursive lock panic of UNP_LINK_WLOCK
ClosedPublic

Authored by raviprakash.darbha_gmail.com on Aug 2 2016, 8:07 PM.
Tags
None
Referenced Files
F107179091: D7398.id19020.diff
Sat, Jan 11, 7:54 AM
F107177045: D7398.id19136.diff
Sat, Jan 11, 7:07 AM
F107174788: D7398.diff
Sat, Jan 11, 6:22 AM
F107170873: D7398.diff
Sat, Jan 11, 5:03 AM
Unknown Object (File)
Wed, Jan 8, 8:00 AM
Unknown Object (File)
Tue, Jan 7, 10:47 AM
Unknown Object (File)
Mon, Dec 30, 12:46 AM
Unknown Object (File)
Mon, Dec 16, 7:50 PM

Details

Summary

I am hitting an race condition ( fairly hard to reproduce ) but has been reproduced in our test setup twice so far. After looking at the call trace , it seems to me that the UNP_LINK_WLOCK is being called in this particular call flow. This looks like an error condition . All of the sonewconn in the src don't have this locked so freeing it before sonewconn is called.

Created a bug on freebsd to explain the bug.

Test Plan

Unit test to recreate the situation.

Diff Detail

Repository
rS FreeBSD src repository - subversion
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

raviprakash.darbha_gmail.com retitled this revision from to Recursive lock panic of UNP_LINK_WLOCK.
raviprakash.darbha_gmail.com updated this object.
raviprakash.darbha_gmail.com edited the test plan for this revision. (Show Details)

Thanks! Please use arc or upload a diff with full context (generate with -U99999).

sys/kern/uipc_usrreq.c
1386 ↗(On Diff #18965)

I think at the very least, this needs a paired UNP_LINK_WLOCK(). Later code in this routine expects the lock to be held.

raviprakash.darbha_gmail.com removed rS FreeBSD src repository - subversion as the repository for this revision.
raviprakash.darbha_gmail.com marked an inline comment as done.

Adding more context to the diff with the whole file.

Recent editors of this file tagged for review. Feel free to remove yourselves.

sys/kern/uipc_usrreq.c
1386 ↗(On Diff #18972)

I think the problem with this is that we're relying on the link lock to protect against races with a concurrent detach by the listener: VOP_UNP_DETACH doesn't require the vnode lock.

It seems you need to redo the VOP_UNP_CONNECT after re-acquiring the linkage lock. That'll work given the current implementation of vop_stdunp_connect(), which has no side effects, but isn't really ideal. I think you'd also need to bump the refcnt of unp2 before dropping the lock.

adjusted the diff as per comments.

Thanks! I think this is still not quite right: unp_refcount is protected by the PCB lock, so the "naked" operations there are not correct. You might be able to move the decrement down to where unp2's PCB lock is acquired.

Sorry for not having more to say. I'll have more time to look at this bug over the weekend. In the meantime, I wrote a quick test program that reproduces the crash: https://people.freebsd.org/~markj/unix_socket_detach.c

It takes a minute or two to hit the panic in my case. It's not clear to me if you already have a test case for the issue.

Thanks for the comments. I will be adding the PCB_LOCK to the diff and it looks fine as the order between the LINK_LOCK and PCB lock can be maintained. I will compile a patch tom after some testing . Looks something like this .

    unp2 = sotounpcb(so2);
    UNP_PCB_LOCK(unp2);
    unp2->unp_refcount++;
    UNP_PCB_UNLOCK(unp2);
    UNP_LINK_WUNLOCK();
    CURVNET_SET(so2->so_vnet);
    so3 = sonewconn(so2, 0);
    CURVNET_RESTORE();
    UNP_LINK_WLOCK();
    /* make sure v_socket is stable as we dropped the lock earlier. */
    VOP_UNP_CONNECT(vp, &so2);
} else
    so3 = NULL;
if (so3 == NULL) {
    error = ECONNREFUSED;
    goto bad2;
}
unp = sotounpcb(so);
unp3 = sotounpcb(so3);
UNP_PCB_LOCK(unp);
UNP_PCB_LOCK(unp2);
UNP_PCB_LOCK(unp3);

unp2->unp_refcount--;
sys/kern/uipc_usrreq.c
1386 ↗(On Diff #18965)

oops yea .. fixed that.

Looks something like this .

Thanks. This looks better, but still has some problems:

  • Be careful not to leak the reference on unp2 if sonewconn() returns NULL.
  • We need to free unp2 ourselves if the refcount drops to zero.
  • Calling VOP_UNP_CONNECT() again doesn't really help with anything: it just sets so2 to vp->v_socket, which may have become NULL via VOP_UNP_DETACH (which does not require the vnode lock).

The issue is that we're potentially racing with a close of the listening socket. soclose() may be aborting incomplete sockets after sonewconn() returns a non-NULL pointer. We need to know if so2 was closed while the link lock was dropped. Otherwise so3 may have been freed by the time we execute "unp3 = sotounpcb(so3);" and we'll end up with a use-after-free. I don't see a good way to check for a close of the listening socket: uipc_close() is a no-op for listening sockets. I just added a flag in this hacky attempt at a solution: https://people.freebsd.org/~markj/patches/unp_close_race.diff

The check in my diff is pretty ugly. I also realized that dropping the link lock opens another race: now another thread could close "so" as we're trying to connect it. So we'd also need to bump unp's refcount before dropping the lock and check to see if it was closed after re-acquiring the lock. Furthermore, another consequence of dropping the lock is that we introduce the possibility of listen queue overflows for unix domain sockets. Previously, the link lock serialized the transition from SQ_INCOMP to SQ_COMP since we did:

UNP_LINK_WLOCK();
...
so3 = sonewconn(so2, 0);
...
unp_connect2(so, so3, PRU_CONNECT);
UNP_LINK_WUNLOCK();

and unp_connect2() calls soisconnected() on so3. So it was previously impossible for the listening socket's incomplete queue to overflow, because only one socket could be in it at a time.

All of these complications make me think that we should avoid dropping the lock and instead add some state to the PCB that lets us skip some of the teardown in unp_detach(), avoiding the need to (re-)acquire the link lock. Does this make sense?

Hm, ignore what I said about listen queue overflows above. I was misreading some code in sonewconn(); listen queue overflows can certainly occur with or without this change.

Here's a solution that instead adds a flag to mark children of a listening socket, and uses the flag to avoid acquiring the link lock in uipc_detach(): https://people.freebsd.org/~markj/patches/unp_close_race2.diff

Oh thanks. . I was trying to figure out the last part so that helps :) Will have a closer look at your new patch. For some reason I am unable to create a panic with the test program you provided even after leaving it to run for long enough.

raviprakash.darbha_gmail.com edited edge metadata.

This looks good. I have updated the review with the latest one. ( was having some issues with the base review being old so your patch was not applying clean to the already existing review. modified the patch a little to suit the review )

Thanks Pho .. your test program works for me. I can hit the issue consistently .

This revision was automatically updated to reflect the committed changes.