From nobody Thu Sep 24 21:47:33 2026 Received: from mail-pj2-f12.google.com (mail-pj2-f12.google.com [74.125.227.140]) (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 CD5BD3FF8B6 for ; Sun, 20 Sep 2026 11:58:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=74.125.227.140 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789905532; cv=none; b=mV7rkQEwMJuSexierceOKt3pWWWuGtGsIWwwPD//D8UL/Uhv+H2DyjDtouftf/x3ffbAPyPVA/GY5ideYdxb0XwZKuk34WHf9auaGxnWIpqFi5fgNqyVvgYyRvRcfqv4xF/IbK62OGtfASIC4FzUcSyyBcUV+A4ARMdA58FR+YQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1789905532; c=relaxed/simple; bh=K2evip5lAYAUyG00/cvTpPdWp2OUU13HAtu556M56Nk=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=gv7o/Tu8GlhJS9poYyrXMbKegr/SJGEvdLawnibqkJXkfeVQnNtRZdVHyVC7oRZk05FC735pli2IurWmiEc5rTNTdz27BUupAe3atSVmvQwfqqH+z2BHO6Go8bCW95qzBdBib6W+GATgwZe3pPzDihJpt578EmGsRvdB94K5U3M= 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=qC9Tcye+; arc=none smtp.client-ip=74.125.227.140 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="qC9Tcye+" Received: by mail-pj2-f12.google.com with SMTP id d9443c01a7336-2d91ede8035so25521535ad.3 for ; Sun, 20 Sep 2026 04:58:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=nebusec.ai; s=google; t=1789905530; x=1790510330; 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=v1FW1MUX7B6vj5sqw5FVAUbuehSAGc/bp168fvHBMyA=; b=qC9Tcye+IhrYk81sMRqeQf+t8wBhZelkVfyorbK2tuy7xSEvMupNpNYJRCVGnPHvTo A8kQ9BqWKMxc3lBdQyDZZtUc5O7w++V40CfYKT1FrEqN5ELDj4f5dbl9pulfzIvfJdGx HYq7H4jzUcCZl4S7rTGEwMLYBfpN/t2CZ2bLl6nspMmGtmrkY5EVQR0COo/SR8fXOnQB jl9KbVHsgRRwK9HEg4jwLC6ew66tfhVEtv+fVjh6g3QloZTS7qZN5uGlmq5U4t/A/+Pj aCdSQiHZZ+S/QXZIxJUT0QKo4rwbJ10ojNYFjrkfwQ3fTHHW5dAHBvqs3oeTyoktEAVw j87Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789905530; x=1790510330; 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=v1FW1MUX7B6vj5sqw5FVAUbuehSAGc/bp168fvHBMyA=; b=Qd5HdaFc1GQuqlgj3G1UcVyLtJZTqPbcYwcFdX2ygWiasExCDtrdB3mBzMBIMuHC3f zccWgkIKfmrnzhtYUFySYiOJj38mVt5m1dbtXf9m0zQKP8DW4OFHih3VR9w+5UL5lhGh 8FMLePWnDpA2SfXy7/v+L8Z3UhyD1sTS+/JdR5g2NiIGc+3TTrdlHXCPj0xt2pBukZ4b eCM2V7s9PPuHNJINm66R2nQrISKilGnTjrlyZZNCH19noapazhisZ+MgdiIuu//eXOcb besHNqDG3/f3ul2AiLjMLOLQkJ8BQo2aZnbJo0pucrGlUSAiZ28tdSYpYQtoLxM2Q5hO uPIg== X-Forwarded-Encrypted: i=1; AKwUvByJjPoq5vr2Lb4p780AWf1hr/e8heF8PVkMaqyb9w+g9AwKGxUaD52+RnYyw8uBYCxWioTDyTigUj3VlH4=@vger.kernel.org X-Gm-Message-State: AFuF++nsLl33GFEDdpUsg+dDlAZZdebCMmMTqC8gPeQMh7CS6Qpw5thk EzOvsQaJbJOb3Hdq+7R2XwEwxn9ekX4b0H2P9RyR+XGHikWbs3zKi0dM0UCY50OJZrxp X-Gm-Gg: AYBFou2e0cR3CQQxtDYDQ6qLelXs60dPfXxlkkFVkHTTnHexsouOXA2lC/abSvVISE8 74I+MmgveCflEsjQk4ptfwsxD6EkOpPhPxCKgJbv5v+hp7u/ukTpBsmRlM0BO67NbgEP70nLOtZ /IauTuOOpHr8T7n10RL8m+MjF4vt7RshgcN2ujGsQKC4Rry2+07YadD6SOVPc9W8g2oDdi27xrB IZuoGxK5rZGhaa9yJYhhymJ1EM3oDm1g8dsEnsrwLZvKvdQ2kCC6EzvEMiujYpGx1D3OpOUTbHc ftUfhVDrByUt1Q4cNZa3yEDZjpKW9KtWAowufZvdDsbKkb6xE+Hw7kE/BuXKp9vYO9el1ymegnk pVTZP45jsCg7BpmBwvrJRw+j0FEUmSYyYE294m7/XhTJ2/EiyR1F+iuQSYPFTxDVTX5epKFf7vA CoI9TcqMsifOOaMwF0GDjHoAeiYvEIHz4/IOvyz3rFsOoMltlUG8BdVLECAf9WU+6xF7TUjLEPV QTgD4VKgQ7lNspNJDfC1ZWJ/fsVdQ== X-Received: by 2002:a17:90a:d64f:b0:39e:4c80:44b9 with SMTP id 98e67ed59e1d1-39e54dccbb8mr13643541a91.28.1789905530026; Sun, 20 Sep 2026 04:58:50 -0700 (PDT) Received: from b6ad5085b32f.. ([122.51.212.64]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-39e6c37c8e7sm8736746a91.9.2026.09.20.04.58.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 20 Sep 2026 04:58:49 -0700 (PDT) From: Zihan Xi To: netfilter-devel@vger.kernel.org Cc: Zihan Xi , coreteam@netfilter.org, pablo@netfilter.org, fw@strlen.de, phil@nwl.cc, netdev@vger.kernel.org, linux-kernel@vger.kernel.org, davem@davemloft.net, edumazet@google.com, kuba@kernel.org, pabeni@redhat.com, horms@kernel.org Subject: [PATCH nf v3 1/1] netfilter: x_tables: avoid holding mutex over faultable user copies Date: Sun, 20 Sep 2026 11:58:22 +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 may sleep indefinitely while the mutex is held, blocking unrelated table and registry operations. Disable page faults while each locked user copy runs. For GET_ENTRIES, validate the table and requested size while holding the table mutex, then release it before faulting in the output range. Reacquire the mutex and revalidate the table and size before the nofault copy. If a nofault copy fails, fault the range outside the lock and retry the operation up to three times after the initial attempt. A failed fault-in returns -EFAULT; repeated nofault failures are limited to four attempts in total and then return -EFAULT. This avoids discarding a locked counter snapshot on the common first fault. Move GET_INFO's fixed-size copy outside the table locks and apply the same fault-safe handling to the IPv4/IPv6 compat GET_ENTRIES paths. The ebtables GET paths use a separate ebt_mutex and are outside this IPv4/IPv6/ARP series. 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: - Validate table and requested size before fault-in, then revalidate the table and size after lock reacquisition in native and IPv4/IPv6 compat GET_ENTRIES paths. - Make retry semantics explicit: one initial nofault attempt followed by at most three retries. Keep fault-in and retry decisions outside family mutexes; failed fault-in and exhausted retries return -EFAULT. - Keep the fixed-size GET_INFO copy outside table locks and brace compat lookup error arms. - Clarify ARP-only runtime coverage, privilege scope, helper build, holder/waiter evidence, QEMU configuration, and the NOT RUN crash-log status. - Regenerate the numbered patch and cover with LF line endings; use the standard 0001-*.patch filename. - v2 Link: https://lore.kernel.org/all/cover.1788961415.git.zihanx@nebusec.ai/ changes in v2: - Rebase onto current nf.git after 0bd7ed1a3263c ("netfilter: arp_tables: remove the 32bit compat interface"). ARP GET_INFO and GET_ENTRIES use native paths only; IPv4 and IPv6 retain compat handling. - Drop hung_task_panic and the 10-second hung_task timeout from the reproducer, as pointed out by Pablo Neira Ayuso; observe holder/waiter wchan instead. - v1 Link: https://lore.kernel.org/all/cover.1788244146.git.zihanx@nebusec.ai/ net/ipv4/netfilter/arp_tables.c | 49 ++++++++++++++++--- net/ipv4/netfilter/ip_tables.c | 87 +++++++++++++++++++++++++++++---- net/ipv6/netfilter/ip6_tables.c | 87 +++++++++++++++++++++++++++++---- 3 files changed, 197 insertions(+), 26 deletions(-) diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_table= s.c index db307fa..a857e67 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 =20 @@ -695,6 +696,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= ++){ @@ -719,12 +721,14 @@ static int copy_entries_to_user(unsigned int total_si= ze, } =20 free_counters: + pagefault_enable(); vfree(counters); return ret; } =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; @@ -738,7 +742,6 @@ static int get_info(struct net *net, void __user *user,= const int *len) name[XT_TABLE_MAXNAMELEN-1] =3D '\0'; 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; =20 memset(&info, 0, sizeof(info)); @@ -751,15 +754,14 @@ static int get_info(struct net *net, void __user *use= r, 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 ret =3D PTR_ERR(t); =20 + if (!ret && copy_to_user(user, &info, *len) !=3D 0) + ret =3D -EFAULT; return ret; } =20 @@ -769,6 +771,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; + unsigned int retries =3D 0; =20 if (*len < sizeof(get)) return -EINVAL; @@ -779,21 +782,51 @@ static int get_entries(struct net *net, struct arpt_g= et_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)) { + ret =3D PTR_ERR(t); + goto out; + } + + if (get.size !=3D t->private->size) { + ret =3D -EAGAIN; + module_put(t->me); + xt_table_unlock(t); + goto out; + } + + module_put(t->me); + xt_table_unlock(t); + + /* Fault in only after validating the table and requested size. */ + if (fault_in_safe_writeable((char __user *)uptr->entrytable, + get.size)) + return -EFAULT; + t =3D xt_find_table_lock(net, NFPROTO_ARP, get.name); if (!IS_ERR(t)) { const struct xt_table_info *private =3D t->private; =20 - if (get.size =3D=3D private->size) + if (get.size !=3D private->size) { + ret =3D -EAGAIN; + } else { ret =3D copy_entries_to_user(private->size, t, uptr->entrytable); - else - ret =3D -EAGAIN; + } =20 module_put(t->me); xt_table_unlock(t); } else ret =3D PTR_ERR(t); =20 +out: + /* Allow three retries after the initial nofault copy. */ + if (ret =3D=3D -EFAULT && retries < 3) { + retries++; + goto retry; + } + return ret; } =20 diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c index 809441c..90f4a26 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; + unsigned int retries =3D 0; =20 if (*len < sizeof(get)) return -EINVAL; @@ -1013,20 +1016,50 @@ 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)) { + ret =3D PTR_ERR(t); + goto out; + } + + if (get.size !=3D t->private->size) { + ret =3D -EAGAIN; + module_put(t->me); + xt_table_unlock(t); + goto out; + } + + module_put(t->me); + xt_table_unlock(t); + + /* Fault in only after validating the table and requested size. */ + if (fault_in_safe_writeable((char __user *)uptr->entrytable, + get.size)) + return -EFAULT; + t =3D xt_find_table_lock(net, AF_INET, get.name); if (!IS_ERR(t)) { const struct xt_table_info *private =3D t->private; - if (get.size =3D=3D private->size) + if (get.size !=3D private->size) { + ret =3D -EAGAIN; + } else { ret =3D copy_entries_to_user(private->size, t, uptr->entrytable); - else - ret =3D -EAGAIN; + } =20 module_put(t->me); xt_table_unlock(t); } else ret =3D PTR_ERR(t); =20 +out: + /* Allow three retries after the initial nofault copy. */ + if (ret =3D=3D -EFAULT && retries < 3) { + retries++; + goto retry; + } + return ret; } =20 @@ -1561,12 +1594,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 +1614,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; + unsigned int retries =3D 0; =20 if (*len < sizeof(get)) return -EINVAL; @@ -1591,6 +1627,32 @@ compat_get_entries(struct net *net, struct compat_ip= t_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)) { + const struct xt_table_info *private =3D t->private; + struct xt_table_info info; + + ret =3D compat_table_info(private, &info); + if (!ret && get.size !=3D info.size) + ret =3D -EAGAIN; + + xt_compat_flush_offsets(AF_INET); + module_put(t->me); + xt_table_unlock(t); + } else { + ret =3D PTR_ERR(t); + } + xt_compat_unlock(AF_INET); + if (ret) + goto out; + + /* Fault in only after validating the table and requested size. */ + if (fault_in_safe_writeable((char __user *)uptr->entrytable, + get.size)) + return -EFAULT; + xt_compat_lock(AF_INET); t =3D xt_find_table_lock(net, AF_INET, get.name); if (!IS_ERR(t)) { @@ -1606,10 +1668,17 @@ compat_get_entries(struct net *net, struct compat_i= pt_get_entries __user *uptr, xt_compat_flush_offsets(AF_INET); module_put(t->me); xt_table_unlock(t); - } else + } else { ret =3D PTR_ERR(t); + } =20 xt_compat_unlock(AF_INET); +out: + /* Allow three retries after the initial nofault copy. */ + if (ret =3D=3D -EFAULT && retries < 3) { + retries++; + goto retry; + } return ret; } #endif diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_table= s.c index f42fb96..4952bfd 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; + unsigned int retries =3D 0; =20 if (*len < sizeof(get)) return -EINVAL; @@ -1030,20 +1033,50 @@ get_entries(struct net *net, struct ip6t_get_entrie= s __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)) { + ret =3D PTR_ERR(t); + goto out; + } + + if (get.size !=3D t->private->size) { + ret =3D -EAGAIN; + module_put(t->me); + xt_table_unlock(t); + goto out; + } + + module_put(t->me); + xt_table_unlock(t); + + /* Fault in only after validating the table and requested size. */ + if (fault_in_safe_writeable((char __user *)uptr->entrytable, + get.size)) + return -EFAULT; + t =3D xt_find_table_lock(net, AF_INET6, get.name); if (!IS_ERR(t)) { struct xt_table_info *private =3D t->private; - if (get.size =3D=3D private->size) + if (get.size !=3D private->size) { + ret =3D -EAGAIN; + } else { ret =3D copy_entries_to_user(private->size, t, uptr->entrytable); - else - ret =3D -EAGAIN; + } =20 module_put(t->me); xt_table_unlock(t); } else ret =3D PTR_ERR(t); =20 +out: + /* Allow three retries after the initial nofault copy. */ + if (ret =3D=3D -EFAULT && retries < 3) { + retries++; + goto retry; + } + return ret; } =20 @@ -1570,12 +1603,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 +1623,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; + unsigned int retries =3D 0; =20 if (*len < sizeof(get)) return -EINVAL; @@ -1600,6 +1636,32 @@ compat_get_entries(struct net *net, struct compat_ip= 6t_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)) { + const struct xt_table_info *private =3D t->private; + struct xt_table_info info; + + ret =3D compat_table_info(private, &info); + if (!ret && get.size !=3D info.size) + ret =3D -EAGAIN; + + xt_compat_flush_offsets(AF_INET6); + module_put(t->me); + xt_table_unlock(t); + } else { + ret =3D PTR_ERR(t); + } + xt_compat_unlock(AF_INET6); + if (ret) + goto out; + + /* Fault in only after validating the table and requested size. */ + if (fault_in_safe_writeable((char __user *)uptr->entrytable, + get.size)) + return -EFAULT; + xt_compat_lock(AF_INET6); t =3D xt_find_table_lock(net, AF_INET6, get.name); if (!IS_ERR(t)) { @@ -1615,10 +1677,17 @@ compat_get_entries(struct net *net, struct compat_i= p6t_get_entries __user *uptr, xt_compat_flush_offsets(AF_INET6); module_put(t->me); xt_table_unlock(t); - } else + } else { ret =3D PTR_ERR(t); + } =20 xt_compat_unlock(AF_INET6); +out: + /* Allow three retries after the initial nofault copy. */ + if (ret =3D=3D -EFAULT && retries < 3) { + retries++; + goto retry; + } return ret; } #endif --=20 2.55.0.windows.3