From: Antonio Quartulli <[email protected]>
The TCP error paths in ovpn_tcp_rcv() and ovpn_tcp_send_sock() take a
peer reference and then schedule the deferred-delete work:
ovpn_peer_hold(peer);
schedule_work(&peer->tcp.defer_del_work);
ovpn_tcp_peer_del_work() drops exactly one reference per run, but
schedule_work() returns false and does not re-queue when the work is
already pending. The reference, however, was taken unconditionally, so
every hold+schedule that lands on an already-pending work leaks one peer
reference.
ovpn_tcp_rcv() is the strparser receive callback and has no guard against
this: a TCP segment packed with packets whose length header is valid for
the stream parser but whose payload is smaller than the opcode size
passes ovpn_tcp_parse() and hits the error path. strparser delivers all
complete messages in a loop, so many error invocations run before the
scheduled work executes, leaking one reference each. A remote peer can
exploit this to pin the peer (and the netdev reference it holds) forever,
preventing interface teardown - a denial of service.
Take the reference only when schedule_work() actually queues the work.
schedule_work() flips the work pending bit atomically, so exactly one
caller - even across the concurrent RX and TX paths - observes the
idle->pending transition and acquires the single reference that the lone
ovpn_peer_put() in the worker balances. ovpn_peer_del() is idempotent
(ovpn_peer_remove() bails on an already-unhashed peer), so a work item
re-queued while running stays refcount-balanced.
Fixes: 11851cbd60ea ("ovpn: implement TCP transport")
Signed-off-by: Antonio Quartulli <[email protected]>
---
drivers/net/ovpn/tcp.c | 29 +++++++++++++++++++----------
1 file changed, 19 insertions(+), 10 deletions(-)
diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
index 433bd07a4f1b..6cf684699ada 100644
--- a/drivers/net/ovpn/tcp.c
+++ b/drivers/net/ovpn/tcp.c
@@ -148,10 +148,14 @@ static void ovpn_tcp_rcv(struct strparser *strp, struct
sk_buff *skb)
ovpn_recv(peer, skb);
return;
err:
- /* take reference for deferred peer deletion. should never fail */
- if (WARN_ON(!ovpn_peer_hold(peer)))
- goto err_nopeer;
- schedule_work(&peer->tcp.defer_del_work);
+ /* schedule deferred peer deletion and take a reference only if the
+ * work was actually queued: the matching ovpn_peer_put() in
+ * ovpn_tcp_peer_del_work() runs once per queued work, so re-arming an
+ * already-pending work must not take another reference (it would be
+ * leaked, e.g. on a flood of invalid packets)
+ */
+ if (schedule_work(&peer->tcp.defer_del_work))
+ ovpn_peer_hold(peer);
ovpn_dev_dstats_rx_dropped(peer->ovpn->dev);
err_nopeer:
kfree_skb(skb);
@@ -280,15 +284,20 @@ static void ovpn_tcp_send_sock(struct ovpn_peer *peer,
struct sock *sk)
peer->id, ret);
/* in case of TCP error we can't recover the VPN
- * stream therefore we abort the connection
+ * stream therefore we abort the connection.
+ *
+ * Take a reference only if the work was actually
+ * queued: ovpn_tcp_peer_del_work() drops exactly one
+ * reference per run, so re-arming an already-pending
+ * work (e.g. already scheduled from the RX path) must
+ * not take another reference (it would be leaked).
*/
- ovpn_peer_hold(peer);
- schedule_work(&peer->tcp.defer_del_work);
+ if (schedule_work(&peer->tcp.defer_del_work))
+ ovpn_peer_hold(peer);
/* we bail out immediately and keep tx_in_progress set
- * to true. This way we prevent more TX attempts
- * which would lead to more invocations of
- * schedule_work()
+ * to true, so that no further TX is attempted on the
+ * aborted stream
*/
return;
}
--
2.53.0
_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel