From nobody Sat Sep 26 16:39:46 2026 Received: from mail-pf1-f182.google.com (mail-pf1-f182.google.com [209.85.210.182]) (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 1E41739A4DC for ; Tue, 1 Sep 2026 13:48:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.182 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788270495; cv=none; b=uLX6wmNNf8/zDQeTrzkJrJauQnwo4c7c3B3p3t6LycvTvXHI7FEnKhwtR/nWzaUFq8NKVVQJi3MiljGhSm7cO+VvbhuMpOydZX1AahJKyjsCq003nPuP6snvh/NEufEHMYwNk1L8uZ7FJazXoknqlV5iLUQ+Gc7C1I6w+lI2a6o= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1788270495; c=relaxed/simple; bh=zQ0+33HsIVp7SkKZyP8rBcC4/rxvlphvwq/cIYRfwts=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=nvuZgYoVMN/5mHu3LVL5FTs8Z/mfZb7hYiNUSSDPqOaaQfqb598axgAEjR8xyNb2LL/SerrIbArq7H+tGTi7agM2yVdWflFaOq79WVpClXeV9ZbsqYsdDCSh9XiVej9Ay/DCZEc1uKJAvK/7hxpuiLN0T8d99kITvwz8HiUwyP8= 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=aX4m7rPw; arc=none smtp.client-ip=209.85.210.182 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="aX4m7rPw" Received: by mail-pf1-f182.google.com with SMTP id d2e1a72fcca58-84e84a6c4bfso1116888b3a.1 for ; Tue, 01 Sep 2026 06:48:12 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nebusec.ai; s=google; t=1788270492; x=1788875292; 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=hGh/luDrC60UBrLdQENH1WKnA2lc40qpD3rWmmT6jZw=; b=aX4m7rPwHeens+/pweaexAMlLLllzqL9eIJEjzDx1w5J/n6oYMuRcxdGo9uKXQ8lWx D5ZKwBVSHuV2xtnx4PqMzDNfZWrq9jZYHx5JddYPYGMlWNfECnR/eAV7DilQ66/AHK5S g5OUX8X7Q1ZAD8emmKDrn0Nvc6NYHcSLDfi4jX8ToVw29btQMgqb3PBbzx0zEPxwqX8y vjD7oYsrYR9ttWV8Ml1n6oyLFNVtHo84KEjwhKRtceHDUjzU2MivaEJ/RlrhIDU5hQ9k WSjRt/SfITj9CbVXJPLyxSt+rlJ9msdDTgJziR6ig2MrjchRITc1hpqi1e5qzTRPgyxY XdMQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1788270492; x=1788875292; 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=hGh/luDrC60UBrLdQENH1WKnA2lc40qpD3rWmmT6jZw=; b=ewxGO43/DqKulvxSB+tBbErLYYziuXtosv7b5xqJXr2/8yGmfTYm2Vlp0KzjkbVKY1 NcABukKJDgrVlYGCKH9mJr+fsTTLCZKLrssaVTDQ6keznEodDEgNaiIRNY7EmkX1LeL8 kiyW4c9cYn/JtKaqfjxbG2NO3C1LQEnylQqKpxhWiM4yTt1rUAiHlS9VYox+vi6hrXDU H0venCjI5R+lLXOe7zuaMC2Xy+/mzw7BwO/xiCNjY/0tc+rcoL3XKiztB45u+5mPBIwF 4vy9KUXNltp6D0pnIq1STdfnQUQ+oaD3Pnz94FAszbFuZvFhyWNZNDZRRQPvF0L152Bv tWbw== X-Forwarded-Encrypted: i=1; AHgh+Rp8Qk2ZEHXNr/TQZBMRGAbUnLMp+3tpuWFkmSoIG7nwbIO45AjHV3meKDFTPb/wMe5XTQuEY7mzAGUP4UQ=@vger.kernel.org X-Gm-Message-State: AFuF++l9jKdHxrIx6piIwP8d9NUDGe1MJt2KCtN7gi+grZ3upIuh1o9t kv/TPz2EucSeRSMR3D7zbPX4IE2trL97NzVN6MeV3S63G16C0vrdxrOMaYIL5lijNpdq X-Gm-Gg: AR+sD11l8/gIQrL2VMbVN0X0NI/dKDxkerG9/XyKQYudTB3zc7SqA7HWLaLpsOuMHpw AbXKQ1Jsm8w8LfLVzQoXyZuP83oDpub80ZxEQPZOjpCfVVbnb7JE8hhWBbVpQ6cQpa4K3DloOYq rCAIENIFA6kq8PBHNGdcly+tdMtDpqfBuPx6Tlflbr24PVs4yu/u13CkXHGfqQPEGoIebMpKQ8z Kt3Gv03e3dhu4UwzYRXXC176G0j5gvqDV68cQsrBjS0tzBagcN45Z5kPKkf2cWFKbie2EpvmiHO hSkCKqjuOqLbPShfIzA+GMW3dHws0M8nvCHcoo4f9OkzZ9Kgu0mnsIPQPJZLqTZjeIGlnU22aMw pxa/MzxNVILFMdCSrwJTkqj59pYCj/vQxZqigM+t/QFliKBEJgmfAeAEQZe66yu4d2ZdZLeaWJX P2t3+I2mMk2sXn5pf75J3PerD9P/qvGjKfBH9MUnTryM+yp0FBbao7RbpjrMSSNjYj9tzuP30W3 HA0T6rso4sHB5SQZKU= X-Received: by 2002:a05:6a00:188e:b0:84e:c851:a058 with SMTP id d2e1a72fcca58-85bca701c7emr3830291b3a.10.1788270492052; Tue, 01 Sep 2026 06:48:12 -0700 (PDT) Received: from b6ad5085b32f.. ([122.51.212.64]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-85be5d9bcd7sm1166303b3a.11.2026.09.01.06.48.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 01 Sep 2026 06:48:10 -0700 (PDT) From: Zihan Xi To: Pablo Neira Ayuso , Florian Westphal Cc: Zihan Xi , Phil Sutter , "David S . Miller" , Eric Dumazet , Jakub Kicinski , Paolo Abeni , Simon Horman , netfilter-devel@vger.kernel.org, coreteam@netfilter.org, netdev@vger.kernel.org, linux-kernel@vger.kernel.org, stable@vger.kernel.org, Vega Subject: [PATCH nf 1/1] netfilter: x_tables: avoid holding mutex over faultable user copies Date: Tue, 1 Sep 2026 13:47:54 +0000 Message-ID: X-Mailer: git-send-email 2.47.3 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" The legacy IPv4, IPv6 and ARP table GET_INFO and GET_ENTRIES paths hold the per-family xtables mutexes while copying table data to userspace. A faultable destination can therefore sleep indefinitely with the mutex held, blocking unrelated table and registry operations. Disable page faults during the locked copy, release the lock, fault in the output range, and retry once. Move GET_INFO's fixed-size copy outside the table locks and apply the same retry handling to compat GET_ENTRIES paths. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: Codex:gpt-5.4 Signed-off-by: Zihan Xi --- net/ipv4/netfilter/arp_tables.c | 33 ++++++++++++++++++++++++++++----- net/ipv4/netfilter/ip_tables.c | 33 ++++++++++++++++++++++++++++----- net/ipv6/netfilter/ip6_tables.c | 33 ++++++++++++++++++++++++++++----- 3 files changed, 84 insertions(+), 15 deletions(-) diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_table= s.c index a87e07e80..d36e6770b 100644 --- a/net/ipv4/netfilter/arp_tables.c +++ b/net/ipv4/netfilter/arp_tables.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -696,6 +697,7 @@ static int copy_entries_to_user(unsigned int total_size, =20 loc_cpu_entry =3D private->entries; =20 + pagefault_disable(); /* FIXME: use iterator macros --RR */ /* ... then go back and fix counters and names */ for (off =3D 0, num =3D 0; off < total_size; off +=3D e->next_offset, num= ++){ @@ -720,6 +722,7 @@ static int copy_entries_to_user(unsigned int total_size, } =20 free_counters: + pagefault_enable(); vfree(counters); return ret; } @@ -800,6 +803,7 @@ static int compat_table_info(const struct xt_table_info= *info, =20 static int get_info(struct net *net, void __user *user, const int *len) { + struct arpt_getinfo info; char name[XT_TABLE_MAXNAMELEN]; struct xt_table *t; int ret; @@ -817,7 +821,6 @@ static int get_info(struct net *net, void __user *user,= const int *len) #endif t =3D xt_request_find_table_lock(net, NFPROTO_ARP, name); if (!IS_ERR(t)) { - struct arpt_getinfo info; const struct xt_table_info *private =3D t->private; #ifdef CONFIG_NETFILTER_XTABLES_COMPAT struct xt_table_info tmp; @@ -838,10 +841,7 @@ static int get_info(struct net *net, void __user *user= , const int *len) info.size =3D private->size; strscpy(info.name, name); =20 - if (copy_to_user(user, &info, *len) !=3D 0) - ret =3D -EFAULT; - else - ret =3D 0; + ret =3D 0; xt_table_unlock(t); module_put(t->me); } else @@ -850,6 +850,8 @@ static int get_info(struct net *net, void __user *user,= const int *len) if (in_compat_syscall()) xt_compat_unlock(NFPROTO_ARP); #endif + if (!ret && copy_to_user(user, &info, *len) !=3D 0) + ret =3D -EFAULT; return ret; } =20 @@ -859,6 +861,7 @@ static int get_entries(struct net *net, struct arpt_get= _entries __user *uptr, int ret; struct arpt_get_entries get; struct xt_table *t; + bool faulted =3D false; =20 if (*len < sizeof(get)) return -EINVAL; @@ -869,6 +872,7 @@ static int get_entries(struct net *net, struct arpt_get= _entries __user *uptr, =20 get.name[sizeof(get.name) - 1] =3D '\0'; =20 + retry: t =3D xt_find_table_lock(net, NFPROTO_ARP, get.name); if (!IS_ERR(t)) { const struct xt_table_info *private =3D t->private; @@ -884,6 +888,14 @@ static int get_entries(struct net *net, struct arpt_ge= t_entries __user *uptr, } else ret =3D PTR_ERR(t); =20 + if (ret =3D=3D -EFAULT && !faulted) { + faulted =3D true; + if (fault_in_writeable((char __user *)uptr->entrytable, + get.size)) + return -EFAULT; + goto retry; + } + return ret; } =20 @@ -1363,12 +1375,14 @@ static int compat_copy_entries_to_user(unsigned int= total_size, =20 pos =3D userptr; size =3D total_size; + pagefault_disable(); xt_entry_foreach(iter, private->entries, total_size) { ret =3D compat_copy_entry_to_user(iter, &pos, &size, counters, i++); if (ret !=3D 0) break; } + pagefault_enable(); vfree(counters); return ret; } @@ -1386,6 +1400,7 @@ static int compat_get_entries(struct net *net, int ret; struct compat_arpt_get_entries get; struct xt_table *t; + bool faulted =3D false; =20 if (*len < sizeof(get)) return -EINVAL; @@ -1396,6 +1411,7 @@ static int compat_get_entries(struct net *net, =20 get.name[sizeof(get.name) - 1] =3D '\0'; =20 + retry: xt_compat_lock(NFPROTO_ARP); t =3D xt_find_table_lock(net, NFPROTO_ARP, get.name); if (!IS_ERR(t)) { @@ -1416,6 +1432,13 @@ static int compat_get_entries(struct net *net, ret =3D PTR_ERR(t); =20 xt_compat_unlock(NFPROTO_ARP); + if (ret =3D=3D -EFAULT && !faulted) { + faulted =3D true; + if (fault_in_writeable((char __user *)uptr->entrytable, + get.size)) + return -EFAULT; + goto retry; + } return ret; } #endif diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c index 809441ced..e029072b0 100644 --- a/net/ipv4/netfilter/ip_tables.c +++ b/net/ipv4/netfilter/ip_tables.c @@ -21,6 +21,7 @@ #include #include #include +#include =20 #include #include @@ -824,6 +825,7 @@ copy_entries_to_user(unsigned int total_size, =20 loc_cpu_entry =3D private->entries; =20 + pagefault_disable(); /* FIXME: use iterator macros --RR */ /* ... then go back and fix counters and names */ for (off =3D 0, num =3D 0; off < total_size; off +=3D e->next_offset, num= ++){ @@ -861,6 +863,7 @@ copy_entries_to_user(unsigned int total_size, } =20 free_counters: + pagefault_enable(); vfree(counters); return ret; } @@ -943,6 +946,7 @@ static int compat_table_info(const struct xt_table_info= *info, =20 static int get_info(struct net *net, void __user *user, const int *len) { + struct ipt_getinfo info; char name[XT_TABLE_MAXNAMELEN]; struct xt_table *t; int ret; @@ -960,7 +964,6 @@ static int get_info(struct net *net, void __user *user,= const int *len) #endif t =3D xt_request_find_table_lock(net, AF_INET, name); if (!IS_ERR(t)) { - struct ipt_getinfo info; const struct xt_table_info *private =3D t->private; #ifdef CONFIG_NETFILTER_XTABLES_COMPAT struct xt_table_info tmp; @@ -981,10 +984,7 @@ static int get_info(struct net *net, void __user *user= , const int *len) info.size =3D private->size; strscpy(info.name, name); =20 - if (copy_to_user(user, &info, *len) !=3D 0) - ret =3D -EFAULT; - else - ret =3D 0; + ret =3D 0; =20 xt_table_unlock(t); module_put(t->me); @@ -994,6 +994,8 @@ static int get_info(struct net *net, void __user *user,= const int *len) if (in_compat_syscall()) xt_compat_unlock(AF_INET); #endif + if (!ret && copy_to_user(user, &info, *len) !=3D 0) + ret =3D -EFAULT; return ret; } =20 @@ -1004,6 +1006,7 @@ get_entries(struct net *net, struct ipt_get_entries _= _user *uptr, int ret; struct ipt_get_entries get; struct xt_table *t; + bool faulted =3D false; =20 if (*len < sizeof(get)) return -EINVAL; @@ -1013,6 +1016,7 @@ get_entries(struct net *net, struct ipt_get_entries _= _user *uptr, return -EINVAL; get.name[sizeof(get.name) - 1] =3D '\0'; =20 + retry: t =3D xt_find_table_lock(net, AF_INET, get.name); if (!IS_ERR(t)) { const struct xt_table_info *private =3D t->private; @@ -1027,6 +1031,14 @@ get_entries(struct net *net, struct ipt_get_entries = __user *uptr, } else ret =3D PTR_ERR(t); =20 + if (ret =3D=3D -EFAULT && !faulted) { + faulted =3D true; + if (fault_in_writeable((char __user *)uptr->entrytable, + get.size)) + return -EFAULT; + goto retry; + } + return ret; } =20 @@ -1561,12 +1573,14 @@ compat_copy_entries_to_user(unsigned int total_size= , struct xt_table *table, =20 pos =3D userptr; size =3D total_size; + pagefault_disable(); xt_entry_foreach(iter, private->entries, total_size) { ret =3D compat_copy_entry_to_user(iter, &pos, &size, counters, i++); if (ret !=3D 0) break; } + pagefault_enable(); =20 vfree(counters); return ret; @@ -1579,6 +1593,7 @@ compat_get_entries(struct net *net, struct compat_ipt= _get_entries __user *uptr, int ret; struct compat_ipt_get_entries get; struct xt_table *t; + bool faulted =3D false; =20 if (*len < sizeof(get)) return -EINVAL; @@ -1591,6 +1606,7 @@ compat_get_entries(struct net *net, struct compat_ipt= _get_entries __user *uptr, =20 get.name[sizeof(get.name) - 1] =3D '\0'; =20 + retry: xt_compat_lock(AF_INET); t =3D xt_find_table_lock(net, AF_INET, get.name); if (!IS_ERR(t)) { @@ -1610,6 +1626,13 @@ compat_get_entries(struct net *net, struct compat_ip= t_get_entries __user *uptr, ret =3D PTR_ERR(t); =20 xt_compat_unlock(AF_INET); + if (ret =3D=3D -EFAULT && !faulted) { + faulted =3D true; + if (fault_in_writeable((char __user *)uptr->entrytable, + get.size)) + return -EFAULT; + goto retry; + } return ret; } #endif diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_table= s.c index f42fb96ef..493e6fbc6 100644 --- a/net/ipv6/netfilter/ip6_tables.c +++ b/net/ipv6/netfilter/ip6_tables.c @@ -25,6 +25,7 @@ #include #include #include +#include =20 #include #include @@ -840,6 +841,7 @@ copy_entries_to_user(unsigned int total_size, =20 loc_cpu_entry =3D private->entries; =20 + pagefault_disable(); /* FIXME: use iterator macros --RR */ /* ... then go back and fix counters and names */ for (off =3D 0, num =3D 0; off < total_size; off +=3D e->next_offset, num= ++){ @@ -877,6 +879,7 @@ copy_entries_to_user(unsigned int total_size, } =20 free_counters: + pagefault_enable(); vfree(counters); return ret; } @@ -959,6 +962,7 @@ static int compat_table_info(const struct xt_table_info= *info, =20 static int get_info(struct net *net, void __user *user, const int *len) { + struct ip6t_getinfo info; char name[XT_TABLE_MAXNAMELEN]; struct xt_table *t; int ret; @@ -976,7 +980,6 @@ static int get_info(struct net *net, void __user *user,= const int *len) #endif t =3D xt_request_find_table_lock(net, AF_INET6, name); if (!IS_ERR(t)) { - struct ip6t_getinfo info; const struct xt_table_info *private =3D t->private; #ifdef CONFIG_NETFILTER_XTABLES_COMPAT struct xt_table_info tmp; @@ -997,10 +1000,7 @@ static int get_info(struct net *net, void __user *use= r, const int *len) info.size =3D private->size; strcpy(info.name, name); =20 - if (copy_to_user(user, &info, *len) !=3D 0) - ret =3D -EFAULT; - else - ret =3D 0; + ret =3D 0; =20 xt_table_unlock(t); module_put(t->me); @@ -1010,6 +1010,8 @@ static int get_info(struct net *net, void __user *use= r, const int *len) if (in_compat_syscall()) xt_compat_unlock(AF_INET6); #endif + if (!ret && copy_to_user(user, &info, *len) !=3D 0) + ret =3D -EFAULT; return ret; } =20 @@ -1020,6 +1022,7 @@ get_entries(struct net *net, struct ip6t_get_entries = __user *uptr, int ret; struct ip6t_get_entries get; struct xt_table *t; + bool faulted =3D false; =20 if (*len < sizeof(get)) return -EINVAL; @@ -1030,6 +1033,7 @@ get_entries(struct net *net, struct ip6t_get_entries = __user *uptr, =20 get.name[sizeof(get.name) - 1] =3D '\0'; =20 + retry: t =3D xt_find_table_lock(net, AF_INET6, get.name); if (!IS_ERR(t)) { struct xt_table_info *private =3D t->private; @@ -1044,6 +1048,14 @@ get_entries(struct net *net, struct ip6t_get_entries= __user *uptr, } else ret =3D PTR_ERR(t); =20 + if (ret =3D=3D -EFAULT && !faulted) { + faulted =3D true; + if (fault_in_writeable((char __user *)uptr->entrytable, + get.size)) + return -EFAULT; + goto retry; + } + return ret; } =20 @@ -1570,12 +1582,14 @@ compat_copy_entries_to_user(unsigned int total_size= , struct xt_table *table, =20 pos =3D userptr; size =3D total_size; + pagefault_disable(); xt_entry_foreach(iter, private->entries, total_size) { ret =3D compat_copy_entry_to_user(iter, &pos, &size, counters, i++); if (ret !=3D 0) break; } + pagefault_enable(); =20 vfree(counters); return ret; @@ -1588,6 +1602,7 @@ compat_get_entries(struct net *net, struct compat_ip6= t_get_entries __user *uptr, int ret; struct compat_ip6t_get_entries get; struct xt_table *t; + bool faulted =3D false; =20 if (*len < sizeof(get)) return -EINVAL; @@ -1600,6 +1615,7 @@ compat_get_entries(struct net *net, struct compat_ip6= t_get_entries __user *uptr, =20 get.name[sizeof(get.name) - 1] =3D '\0'; =20 + retry: xt_compat_lock(AF_INET6); t =3D xt_find_table_lock(net, AF_INET6, get.name); if (!IS_ERR(t)) { @@ -1619,6 +1635,13 @@ compat_get_entries(struct net *net, struct compat_ip= 6t_get_entries __user *uptr, ret =3D PTR_ERR(t); =20 xt_compat_unlock(AF_INET6); + if (ret =3D=3D -EFAULT && !faulted) { + faulted =3D true; + if (fault_in_writeable((char __user *)uptr->entrytable, + get.size)) + return -EFAULT; + goto retry; + } return ret; } #endif --=20 2.43.0