@@ -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;
}