From nobody Thu Sep 24 20:31:16 2026 Received: from mail-pj2-f25.google.com (mail-pj2-f25.google.com [74.125.227.153]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 342CB3ED13F for ; Sun, 20 Sep 2026 09:31:47 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.153 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789896714; cv=none; b=YcCNoyokYffKzP7iOAko583VPCyAJ5OENrogCZSC3j3l+Al/M98TlyAfx6eD8IaIlhbtiMNODTYfKLsBflY3+Su2aOjjZlAd5+KRo23awujiCqBCKZKSVwPgP0mVy6E8aWiVMBE/NuYTammOsdPHpYd/pxqEnhCsN+Mo9RztLA8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789896714; c=relaxed/simple; bh=HHMylUUoV2ErCt+GQxiWiYuY3/hIn1WlIHK1Vv/s7aE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nLzI7/PDPUCPtJeOTqBXLWyD9MdT0saQoZ9gQaBtnCOUPU6NiatI1FFnWUQ+DeqWkeahFGYBGqVq6P0fgAmmfWuCHxggh90q+mHhgDu0FVV945lvFOef1gd1ZzW26FixgA1P4mrBHAkFxweTnMpKk1bTmWFe8faOImYbMpZxHUE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=nebusec.ai; spf=pass smtp.mailfrom=nebusec.ai; dkim=pass (2048-bit key) header.d=nebusec.ai header.i=@nebusec.ai header.b=iokKdjdC; arc=none smtp.client-ip=74.125.227.153 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=nebusec.ai Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nebusec.ai Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=nebusec.ai header.i=@nebusec.ai header.b="iokKdjdC" Received: by mail-pj2-f25.google.com with SMTP id d9443c01a7336-2dd4b43b20bso13842915ad.1 for ; Sun, 20 Sep 2026 02:31:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nebusec.ai; s=google; t=1789896703; x=1790501503; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=6xnQtR49M9eGkH01aGrwefUnADH+0zwfkGfqg1+mMB8=; b=iokKdjdC0damnqdLIIDZov8xOw6uDdYi1cA8jVB8stIXO9MVGUIj2iOKAPr3GeSFUq nkRVPwJduqwGGekprLOexfQvmw99pr22GIxp7vmmioyKPkSZoB+sIPSbhDi0IQK8ZIP4 20BNSiHy9CRE+0zrS/NinUPcenQDA0rRmEP+L3KJiJyY3wLg3BHpLjowdhCXBznBvvLQ T4PW6sXjLOOTeYCFbjDp930O+6ubrYH9sY/d+vCE7O6+IjaL4hQS0BqiLN+eehwoycEZ RN6nE53OE5C/y81STZdFJESnjOznqQbGp3LtwVyKkSWN5RFhYycPFr5VF7WuD3bAdTT6 xp9g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789896703; x=1790501503; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=6xnQtR49M9eGkH01aGrwefUnADH+0zwfkGfqg1+mMB8=; b=i9n677cYmX/IOifNcJaRVJGzJMHI9n/shx8moInWEo8sHJGf1o7jOCBl/LG/THT+aA AZQgqMmGpfvCeznnEfSj2oqKonUDPX3PZtFhvSsG0kVUMYTdW7cpDmL/opk/9sFjPZe3 XMBZirtMJp5ZhSl2yimxfHNYF+3c7tZMPAo4eXLhBO5aRRAAXONmt6uObpsElgSy8XIZ UwMJVb11I4Q/sRFNI7RC7IxseM8kKRdCaXIIv9iAbGxmlfeVhTnqyCelA/QfbEqKgTsK alxeoVr3FtjIhhW0jlOKNUrXn3FoUCKK94njbGoAYG6ry4HpavmtGntBlgVvuX68Fvkf XB7Q== X-Forwarded-Encrypted: i=1; AKwUvBy5XNmIu6TrYD3rfrw227h4PKW0iE4EGMvFVer5unJWOYD0NX/84i0fKIitfHTgPKNIosKT7KeKv+2Eyp8=@vger.kernel.org X-Gm-Message-State: AFuF++kuJ67h2hiUKydQU5EkNS02Y6ZcTMJwpf+rGuHUvRvoNAb+C/W+ 0XrzarCPPvjBCz6c0SCbYWSHCYkR7WMjp8vLILqWzSVw1BI4UKBQdT7ZrMfCQCYkRNMu X-Gm-Gg: AYBFou3tAWgCL7bDtEO5WUs6bbr1D3DjFk4zN6Vn6ScLBUujy23EOm465gOEkDrRP57 HCTg7J4meaHCxhb+FUtkE7T5dudG4enBLKv8n6WHHjE983d5LYiZyhsVK/Is9esVtu5er2PvMj9 G3XH92uvl3cvWwRY2J16VrqXmSym3ETHeWpRbxSOvD6AQMnFgkoeyvPXiLRgEdPIEblWjGVQeih 1jdz280i3xu0ZJpuOvwypjk6cvf2EERn6wE0JNSMA+SKkXnJ9QnYFNMCBKVgP0Qjp4kkXqVx95M Vazvx/bkBurjVpgbHEjJ2nJKvTu+0Vl9gp2rnyv1UKBhc3u0wTcd9GKR+4zF7jXcr3XPoISiWH0 TbLdDeo6OBGfN3+9QExUCvG7S1hHY5n4HUcXw/ZQRjKQl5VDSiG3+KBLbeqMpQ/PBTC8aOfpJMF sMrfNgSLSiaRMFiUmcKPWidemxvNvw3DBeYJ2ts88DyyWH32/b0+I3/2bU5tyisuLOyaSvQAoi2 mfHW0pr9aJug35O0bjJhtUScc8jmhs= X-Received: by 2002:a17:903:1446:b0:2da:eb8f:b4e4 with SMTP id d9443c01a7336-2ddb1add397mr121862955ad.7.1789896703153; Sun, 20 Sep 2026 02:31:43 -0700 (PDT) Received: from b6ad5085b32f.. ([122.51.212.64]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2ddc17e17e0sm18435235ad.70.2026.09.20.02.31.38 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 20 Sep 2026 02:31:41 -0700 (PDT) From: Zihan Xi To: Simon Horman , Julian Anastasov Cc: Zihan Xi , Pablo Neira Ayuso , Florian Westphal , Phil Sutter , netdev@vger.kernel.org, lvs-devel@vger.kernel.org, netfilter-devel@vger.kernel.org, coreteam@netfilter.org, linux-kernel@vger.kernel.org Subject: [PATCH nf v3 1/2] ipvs: avoid stack overflow from recursive connection expiration Date: Sun, 20 Sep 2026 09:31:19 +0000 Message-ID: <650f5ad9c4eea0cf1d31b04b1d12d4a600e90337.1789877273.git.zihanx@nebusec.ai> In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" When a controlled IPVS connection expires, its controller may be expired synchronously if it has no remaining controlled connections. A chain of controlled connections can then recurse through ip_vs_conn_expire() and exhaust the kernel stack during namespace cleanup. Use an iterative cleanup path so the controller chain is expired with one stack frame. Keep the reference obtained for the controller until its expiration step, and drop it while checking the hash table. This prevents a concurrent lookup from rearming the timer after the connection has lost its last reference. Distinguish a connection already unlinked by a concurrent timer callback and avoid rearming its timer. Run the expiration path under RCU so a callback cannot outlive an RCU-delayed connection free. Fixes: f9200a52eedf ("ipvs: avoid expiring many connections from timer") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi --- changes in v3: - Handle a concurrent timer callback while keeping controller cleanup iterative and synchronous. - Keep expiration under RCU and avoid rearming an already unlinked connection. - v2 Link: https://lore.kernel.org/all/cover.1789435989.git.zihanx@nebusec.ai/ changes in v2: - Replace recursive controller expiration with an iterative repeat path. - v1 Link: https://lore.kernel.org/all/cover.1789110326.git.zihanx@nebusec.ai/ net/netfilter/ipvs/ip_vs_conn.c | 78 +++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_con= n.c index 6fa3e1dc534c..d111010469f8 100644 --- a/net/netfilter/ipvs/ip_vs_conn.c +++ b/net/netfilter/ipvs/ip_vs_conn.c @@ -311,19 +311,30 @@ static inline int ip_vs_conn_hash(struct ip_vs_conn *= cp) } =20 /* Try to unlink ip_vs_conn from conn_tab. - * returns bool success. + * returns the unlink state. */ -static inline bool ip_vs_conn_unlink(struct ip_vs_conn *cp) +enum ip_vs_conn_unlink_state { + IP_VS_CONN_UNLINK_BUSY, + IP_VS_CONN_UNLINKED, + IP_VS_CONN_UNLINK_GONE, +}; + +static inline enum ip_vs_conn_unlink_state +ip_vs_conn_unlink(struct ip_vs_conn *cp, bool has_ref) { struct netns_ipvs *ipvs =3D cp->ipvs; struct hlist_bl_head *head, *head2; u32 hash_key, hash_key2; struct ip_vs_rht *t; - bool ret =3D false; + enum ip_vs_conn_unlink_state state; bool use2; =20 - if (cp->flags & IP_VS_CONN_F_ONE_PACKET) - return refcount_dec_if_one(&cp->refcnt); + if (cp->flags & IP_VS_CONN_F_ONE_PACKET) { + if (has_ref) + __ip_vs_conn_put(cp); + return refcount_dec_if_one(&cp->refcnt) ? + IP_VS_CONN_UNLINKED : IP_VS_CONN_UNLINK_BUSY; + } =20 rcu_read_lock(); local_bh_disable(); @@ -337,6 +348,9 @@ static inline bool ip_vs_conn_unlink(struct ip_vs_conn = *cp) false /* new_hash2 */, &head, &head2); =20 if (cp->flags & IP_VS_CONN_F_HASHED) { + if (has_ref) + __ip_vs_conn_put(cp); + /* Decrease refcnt and unlink conn only if we are last user */ if (use2 =3D=3D ip_vs_conn_use_hash2(cp) && refcount_dec_if_one(&cp->refcnt)) { @@ -344,8 +358,14 @@ static inline bool ip_vs_conn_unlink(struct ip_vs_conn= *cp) if (use2) hlist_bl_del_rcu(&cp->hn1.node); cp->flags &=3D ~IP_VS_CONN_F_HASHED; - ret =3D true; + state =3D IP_VS_CONN_UNLINKED; + } else { + state =3D IP_VS_CONN_UNLINK_BUSY; } + } else { + if (has_ref) + __ip_vs_conn_put(cp); + state =3D IP_VS_CONN_UNLINK_GONE; } =20 conn_tab_unlock(head, head2); @@ -353,7 +373,7 @@ static inline bool ip_vs_conn_unlink(struct ip_vs_conn = *cp) local_bh_enable(); rcu_read_unlock(); =20 - return ret; + return state; } =20 =20 @@ -1331,24 +1351,29 @@ static void ip_vs_conn_del(struct ip_vs_conn *cp) } =20 /* Try to delete connection while holding reference */ -static void ip_vs_conn_del_put(struct ip_vs_conn *cp) +static bool ip_vs_conn_del_put(struct ip_vs_conn *cp) { if (timer_delete(&cp->timer)) { /* Drop cp->control chain too */ if (cp->control) cp->timeout =3D 0; - __ip_vs_conn_put(cp); - ip_vs_conn_expire(&cp->timer); - } else { - __ip_vs_conn_put(cp); + return true; } + + __ip_vs_conn_put(cp); + return false; } =20 static void ip_vs_conn_expire(struct timer_list *t) { struct ip_vs_conn *cp =3D timer_container_of(cp, t, timer); struct netns_ipvs *ipvs =3D cp->ipvs; + enum ip_vs_conn_unlink_state unlink_state; + bool has_ref =3D false; =20 + rcu_read_lock(); + +repeat: /* * do I control anybody? */ @@ -1356,24 +1381,27 @@ static void ip_vs_conn_expire(struct timer_list *t) goto expire_later; =20 /* Unlink conn if not referenced anymore */ - if (likely(ip_vs_conn_unlink(cp))) { + unlink_state =3D ip_vs_conn_unlink(cp, has_ref); + has_ref =3D false; + if (unlink_state =3D=3D IP_VS_CONN_UNLINKED) { struct ip_vs_conn *ct =3D cp->control; + bool next =3D false; =20 /* delete the timer if it is activated by other users */ timer_delete(&cp->timer); =20 /* does anybody control me? */ if (ct) { - bool has_ref =3D !cp->timeout && __ip_vs_conn_get(ct); + bool ct_ref =3D !cp->timeout && __ip_vs_conn_get(ct); =20 ip_vs_control_del(cp); /* Drop CTL or non-assured TPL if not used anymore */ - if (has_ref && !atomic_read(&ct->n_control) && + if (ct_ref && !atomic_read(&ct->n_control) && (!(ct->flags & IP_VS_CONN_F_TEMPLATE) || !(ct->state & IP_VS_CTPL_S_ASSURED))) { IP_VS_DBG(4, "drop controlling connection\n"); - ip_vs_conn_del_put(ct); - } else if (has_ref) { + next =3D ip_vs_conn_del_put(ct); + } else if (ct_ref) { __ip_vs_conn_put(ct); } } @@ -1402,21 +1430,33 @@ static void ip_vs_conn_expire(struct timer_list *t) else call_rcu(&cp->rcu_head, ip_vs_conn_rcu_free); atomic_dec(&ipvs->conn_count); - return; + if (next) { + cp =3D ct; + has_ref =3D true; + goto repeat; + } + goto out; } =20 + if (unlink_state =3D=3D IP_VS_CONN_UNLINK_GONE) + goto out; + expire_later: IP_VS_DBG(7, "delayed: conn->refcnt=3D%d conn->n_control=3D%d\n", refcount_read(&cp->refcnt), atomic_read(&cp->n_control)); =20 - refcount_inc(&cp->refcnt); + if (!has_ref && !__ip_vs_conn_get(cp)) + goto out; cp->timeout =3D 60*HZ; =20 if (ipvs->sync_state & IP_VS_STATE_MASTER) ip_vs_sync_conn(ipvs, cp, sysctl_sync_threshold(ipvs)); =20 __ip_vs_conn_put_timer(cp); + +out: + rcu_read_unlock(); } =20 /* Modify timer, so that it expires as soon as possible. --=20 2.43.0 From nobody Thu Sep 24 20:31:16 2026 Received: from mail-pj2-f34.google.com (mail-pj2-f34.google.com [74.125.227.162]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 872E03E5EEB for ; Sun, 20 Sep 2026 09:31:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.162 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789896720; cv=none; b=es8BWGSNgywUVHq/2dh2zlPav4wvYn7pUKRzh0H5G2v9cklMfyOxGI2WL9UqBN25bdAvGpuFrsqsYHbeCTDwoBrBxa6nlwxlztI7TysvXYKHbcQM+NG/BDW3fDVrIFja5tp6KHQW7YnrHPOcA96et0LiGot0oJ66LS54QW1L940= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789896720; c=relaxed/simple; bh=DFCad/hdE8IpyPcSDWClzGqWb3Q5eeOIEZMZ4lSwfH8=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=O81qUxiS1VZ+DSZOBDnG/NFki01WepeI3ADv7YEaBMBfbKuAYUOS9BaMXDTRH999x8CTmhGHRDfpjsSydUYYEL77YjsjL7lqg00IR3Wo9NNQeNjjBwNdSibwz2ocYyJ/ROGQNh3LQLF3jdruZUj3iOFeJbjbDE+u9DpbElS3lx0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=nebusec.ai; spf=pass smtp.mailfrom=nebusec.ai; dkim=pass (2048-bit key) header.d=nebusec.ai header.i=@nebusec.ai header.b=KJ0WEgBC; arc=none smtp.client-ip=74.125.227.162 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=nebusec.ai Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=nebusec.ai Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=nebusec.ai header.i=@nebusec.ai header.b="KJ0WEgBC" Received: by mail-pj2-f34.google.com with SMTP id d9443c01a7336-2d747eb79f7so14015655ad.1 for ; Sun, 20 Sep 2026 02:31:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nebusec.ai; s=google; t=1789896711; x=1790501511; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=AC0BwwxocEHWdEgXzu191R0Fu2SszgpaG8S1TYD6ghM=; b=KJ0WEgBC7grVskxFMGrsIAnpCAfK43lh//UevXwE2M69FSD77hYrP934ZjNVXW2gEX M6DPtn/M9G/g4NobkBMQ5sEmDAS0wV7OTfCaFwCLBmvseOxw+qDw1Y9df1GxWFH7d1R6 chKnrLkJYY0ICKhXcPtbQLYF22aSnjS2lFzXN9m1Y8okL1V27uf5WEf4msbOqIyPA3rF ZCt7upk+lqbhl2D5M6W92pwPaY8pLP3IUr5LM1oxMvgD4kNyWr8ihwoycfDuAMPxbqZj kxOVQkugBdLXtoiDj8q73d97s3t1DzhiAZl5UMVCEGcGPhA0b1Hex6N+1TFXxGduM6xE eUFQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789896711; x=1790501511; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=AC0BwwxocEHWdEgXzu191R0Fu2SszgpaG8S1TYD6ghM=; b=pr9Pxtk+sY/TlHbQya5v/6YYk8Y36aa+WQMHPWCj9M8Bu6c8X17sJ/WSAQj66A+o1f Nh8abvaOZmRv8cwDyUZY2Qpr7VWeNvVfmmOlNtWm6YbD9THGCaLu3W1mnKtcVrUXBb8I q7XWKwRh7TAZ7suk6z2p9Lgrtn4DKrySQUaCldIr2Vwjr4s+g8G73pG15yKegWVMFoO6 j5i0udQhB1tYP9WC4IzKpk6tOXJkbRrsF2Mh2dUIAzSt1bY37CU9VL8c4yZNKEJdvvxT Dw+L+a47tt4/mQA5IpOwzcDK+C7BTfonkNi8pOmhOPfkRjTS6RUoM/1jvcvkYq7eR3En 0frQ== X-Forwarded-Encrypted: i=1; AKwUvBzgMdxZMeZ7UQAMXYplGB4CS9uvwFtkJ+MyI3Ve6v9X0XF+83VbhPLIQeGsruRogC//uAalOH1BLRGjD0c=@vger.kernel.org X-Gm-Message-State: AFuF++m6sdNrbOu82tcCktmaxIw1YmUBb3k8VD+yk96/NWB8vlDi5rN1 d2yUWu/GkR4ELQp2WyVDdfV/jRaNZPVimy1KJ/0JZn934dQiIdmSYeQCbD0T93nhQZAo X-Gm-Gg: AYBFou0azw7j5xI07Up9myI8kNetDC8GY8FWyBH/Ed42JTyFM10CQr7EKzdSPhzcz+u Z4ahLhfKobILEG2926nXl8Ta3BX60QJK9Ry9vO35YBhDwfn1AX0Jm4OJzYes2WmZ4PxCG0LgrLD m1u9zC3YyvpD1u1g9lIm70DqtOsQ6wdJkFGK0o1tGjvxl99ui1Fq07mmYLdk1EGAR6E9Kq24s2s 1Jgm5KA4YDRFfi2UO0KWUR4bzs4F1+oC5x+h//zfieXlAGn0ENRnABp+RXI6JLeiTY3AgAONIbW n8bi4G/K9igp+HtRk5CBhUTRtB52nhnlFSlZuRAvCg3w65tIgGmcjLWd3SHrXMReMTVBCfWwdfB hMLHY2EkDHUDaXKTJN6W9pLRQDUBVtRLwbF4cHAsvy/qIHYIU6ntGrzlU/0fnzOVGwT55bcOrkb lvgKgxWLhM020Np+/m0mf2UsLWc8QeV80UaryjQFSk3BxWk1vgVwOgaBDAwaJtAoJ3ns1CYuGIZ 0K/DEoBlqYZx3iYIKrnv78snLBWNg== X-Received: by 2002:a17:902:f705:b0:2dd:c053:82f0 with SMTP id d9443c01a7336-2ddc05383e5mr62215595ad.39.1789896711133; Sun, 20 Sep 2026 02:31:51 -0700 (PDT) Received: from b6ad5085b32f.. ([122.51.212.64]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2ddc17e17e0sm18435235ad.70.2026.09.20.02.31.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 20 Sep 2026 02:31:49 -0700 (PDT) From: Zihan Xi To: Simon Horman , Julian Anastasov Cc: Zihan Xi , Pablo Neira Ayuso , Florian Westphal , Phil Sutter , netdev@vger.kernel.org, lvs-devel@vger.kernel.org, netfilter-devel@vger.kernel.org, coreteam@netfilter.org, linux-kernel@vger.kernel.org Subject: [PATCH nf v3 2/2] ipvs: reject FTP control ports as data ports Date: Sun, 20 Sep 2026 09:31:20 +0000 Message-ID: <4f45973a22e67d5ad1c6f2e5455d8b057be60ae6.1789877273.git.zihanx@nebusec.ai> In-Reply-To: References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" ip_vs_ftp_out() creates a wildcard data connection from the server-advertised passive port. If that port is one of the configured FTP control ports, ip_vs_conn_new() binds the FTP helper to the new connection again. A subsequent wildcard lookup can then extend a controlled-connection chain. Reject zero and configured control ports before creating passive connections. For active mode, reject a zero client port and a data port derived from a configured control port. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi --- changes in v3: - Resend this FTP helper fix with the generic cleanup fix as one nf series, as requested by the maintainer. - Keep the active-mode guard for configured FTP control ports. - v2 Link: https://lore.kernel.org/all/cover.1789435989.git.zihanx@nebusec.ai/ changes in v2: - Add the active-mode check for a data port derived from a configured control port. - v1 Link: https://lore.kernel.org/all/cover.1789110326.git.zihanx@nebusec.ai/ net/netfilter/ipvs/ip_vs_ftp.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/net/netfilter/ipvs/ip_vs_ftp.c b/net/netfilter/ipvs/ip_vs_ftp.c index 9e3e005a8263..4822a1a75212 100644 --- a/net/netfilter/ipvs/ip_vs_ftp.c +++ b/net/netfilter/ipvs/ip_vs_ftp.c @@ -62,6 +62,17 @@ static unsigned short ports[IP_VS_APP_MAX_PORTS] =3D {21= , 0}; module_param_array(ports, ushort, &ports_count, 0444); MODULE_PARM_DESC(ports, "Ports to monitor for FTP control commands"); =20 +static bool is_control_port(u16 port) +{ + unsigned int i; + + for (i =3D 0; i < ports_count; i++) { + if (ports[i] =3D=3D port) + return true; + } + return false; +} + =20 static char *ip_vs_ftp_data_ptr(struct sk_buff *skb, struct ip_vs_iphdr *i= pvsh) { @@ -319,6 +330,10 @@ static int ip_vs_ftp_out(struct ip_vs_app *app, struct= ip_vs_conn *cp, return 1; } =20 + /* Do not redirect data to control ports */ + if (!port || is_control_port(ntohs(port))) + return 0; + /* Now update or create a connection entry for it */ { struct ip_vs_conn_param p; @@ -529,6 +544,9 @@ static int ip_vs_ftp_in(struct ip_vs_app *app, struct i= p_vs_conn *cp, return 1; } =20 + if (!port || is_control_port(ntohs(cp->vport) - 1)) + return 0; + /* Passive mode off */ cp->app_data =3D (void *) IP_VS_FTP_ACTIVE; =20 --=20 2.43.0