git: 3acf7e0da487 - main - if_ovpn: avoid LOR between ovpn and UDP locks
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 12 Sep 2024 08:37:10 UTC
The branch main has been updated by kp: URL: https://cgit.FreeBSD.org/src/commit/?id=3acf7e0da487c08de39d04ac069ae73fb8a488c0 commit 3acf7e0da487c08de39d04ac069ae73fb8a488c0 Author: Kristof Provost <kp@FreeBSD.org> AuthorDate: 2024-09-09 12:14:03 +0000 Commit: Kristof Provost <kp@FreeBSD.org> CommitDate: 2024-09-12 07:44:19 +0000 if_ovpn: avoid LOR between ovpn and UDP locks When we install the tunneling function we had the ovpn lock, and then took the UDP lock. During normal data flow we are called with the UDP lock held and then take the ovpn lock. This naturally produces a lock order reversal warning. Avoid this by releasing the ovpn lock before installing the tunnel function. This is safe, in that installing the tunnel function does not fail (other than with EBUSY, which would mean another thread has already installed the function). On cleanup the problem is more difficult, in that we cannot reasonably release the ovpn lock before we can remove the tunneling function callback. Solve this by delaying the removal of the tunnel callback until the ovpn_softc is cleaned up. It's still safe for ovpn_udp_input() to be caled when all peers are removed. That will only increment counters (which are still allocated), discover there are no peers and then pass the message on to userspace, if any userspace users of the socket remain. We ensure that the socket object remains valid by holding a reference, which we release when we remove the ovpn_softc. This removes the need for per-peer reference counting on the socket, so remove that. Reviewed by: zlei Sponsored by: Rubicon Communications, LLC ("Netgate") Differential Revision:: https://reviews.freebsd.org/D46616 --- sys/net/if_ovpn.c | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/sys/net/if_ovpn.c b/sys/net/if_ovpn.c index 349fa5b7cd13..e2c1dc7f7fc5 100644 --- a/sys/net/if_ovpn.c +++ b/sys/net/if_ovpn.c @@ -390,15 +390,11 @@ ovpn_rele_so(struct ovpn_softc *sc) has_peers = ovpn_has_peers(sc); - /* Only remove the tunnel function if we're releasing the socket for - * the last peer. */ - if (! has_peers) - (void)udp_set_kernel_tunneling(sc->so, NULL, NULL, NULL); - - sorele(sc->so); - - if (! has_peers) - sc->so = NULL; + if (! has_peers) { + MPASS(sc->peercount == 0); + } else { + MPASS(sc->peercount > 0); + } } static void @@ -628,26 +624,23 @@ ovpn_new_peer(struct ifnet *ifp, const nvlist_t *nvl) if (sc->so != NULL && so != sc->so) goto error_locked; - if (sc->so == NULL) + if (sc->so == NULL) { sc->so = so; + /* + * Maintain one extra ref so the socket doesn't go away until + * we're destroying the ifp. + */ + soref(sc->so); + } /* Insert the peer into the list. */ RB_INSERT(ovpn_kpeers, &sc->peers, peer); sc->peercount++; - soref(sc->so); - - ret = udp_set_kernel_tunneling(sc->so, ovpn_udp_input, NULL, sc); - if (ret == EBUSY) { - /* Fine, another peer already set the input function. */ - ret = 0; - } - if (ret != 0) { - RB_REMOVE(ovpn_kpeers, &sc->peers, peer); - sc->peercount--; - goto error_locked; - } OVPN_WUNLOCK(sc); + ret = udp_set_kernel_tunneling(sc->so, ovpn_udp_input, NULL, sc); + MPASS(ret == 0 || ret == EBUSY); + ret = 0; goto done; @@ -2493,12 +2486,21 @@ static void ovpn_clone_destroy_cb(struct epoch_context *ctx) { struct ovpn_softc *sc; + int ret __diagused; sc = __containerof(ctx, struct ovpn_softc, epoch_ctx); MPASS(sc->peercount == 0); MPASS(RB_EMPTY(&sc->peers)); + if (sc->so != NULL) { + CURVNET_SET(sc->ifp->if_vnet); + ret = udp_set_kernel_tunneling(sc->so, NULL, NULL, NULL); + MPASS(ret == 0); + sorele(sc->so); + CURVNET_RESTORE(); + } + COUNTER_ARRAY_FREE(sc->counters, OVPN_COUNTER_SIZE); if_free(sc->ifp);