[PATCH nf 1/1] netfilter: x_tables: avoid holding mutex over faultable user copies
Zihan Xi <[email protected]>
| Newsgroups | gmane.linux.kernel.stable,gmane.comp.security.firewalls.netfilter.devel,gmane.linux.network,gmane.linux.kernel |
|---|---|
| Message-ID | <ced14892f76660c0be9ea6136872ba3d56a07238.1788244146.git.zihanx@nebusec.ai> |
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: [email protected]
Reported-by: Vega <[email protected]>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Zihan Xi <[email protected]>
---
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_tables.c
index a87e07e80..d36e6770b 100644
--- a/net/ipv4/netfilter/arp_tables.c
+++ b/net/ipv4/netfilter/arp_tables.c
@@ -23,6 +23,7 @@
#include <linux/init.h>
#include <linux/mutex.h>
#include <linux/err.h>
+#include <linux/pagemap.h>
#include <net/compat.h>
#include <net/sock.h>
#include <linux/uaccess.h>
@@ -696,6 +697,7 @@ static int copy_entries_to_user(unsigned int total_size,
loc_cpu_entry = private->entries;
+ pagefault_disable();
/* FIXME: use iterator macros --RR */
/* ... then go back and fix counters and names */
for (off = 0, num = 0; off < total_size; off += e->next_offset, num++){
@@ -720,6 +722,7 @@ static int copy_entries_to_user(unsigned int total_size,
}
free_counters:
+ pagefault_enable();
vfree(counters);
return ret;
}
@@ -800,6 +803,7 @@ static int compat_table_info(const struct xt_table_info *info,
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 = xt_request_find_table_lock(net, NFPROTO_ARP, name);
if (!IS_ERR(t)) {
- struct arpt_getinfo info;
const struct xt_table_info *private = 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 = private->size;
strscpy(info.name, name);
- if (copy_to_user(user, &info, *len) != 0)
- ret = -EFAULT;
- else
- ret = 0;
+ ret = 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) != 0)
+ ret = -EFAULT;
return ret;
}
@@ -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 = false;
if (*len < sizeof(get))
return -EINVAL;
@@ -869,6 +872,7 @@ static int get_entries(struct net *net, struct arpt_get_entries __user *uptr,
get.name[sizeof(get.name) - 1] = '\0';
+ retry:
t = xt_find_table_lock(net, NFPROTO_ARP, get.name);
if (!IS_ERR(t)) {
const struct xt_table_info *private = t->private;
@@ -884,6 +888,14 @@ static int get_entries(struct net *net, struct arpt_get_entries __user *uptr,
} else
ret = PTR_ERR(t);
+ if (ret == -EFAULT && !faulted) {
+ faulted = true;
+ if (fault_in_writeable((char __user *)uptr->entrytable,
+ get.size))
+ return -EFAULT;
+ goto retry;
+ }
+
return ret;
}
@@ -1363,12 +1375,14 @@ static int compat_copy_entries_to_user(unsigned int total_size,
pos = userptr;
size = total_size;
+ pagefault_disable();
xt_entry_foreach(iter, private->entries, total_size) {
ret = compat_copy_entry_to_user(iter, &pos,
&size, counters, i++);
if (ret != 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 = false;
if (*len < sizeof(get))
return -EINVAL;
@@ -1396,6 +1411,7 @@ static int compat_get_entries(struct net *net,
get.name[sizeof(get.name) - 1] = '\0';
+ retry:
xt_compat_lock(NFPROTO_ARP);
t = 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 = PTR_ERR(t);
xt_compat_unlock(NFPROTO_ARP);
+ if (ret == -EFAULT && !faulted) {
+ faulted = 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 <linux/proc_fs.h>
#include <linux/err.h>
#include <linux/cpumask.h>
+#include <linux/pagemap.h>
#include <linux/netfilter/x_tables.h>
#include <linux/netfilter_ipv4/ip_tables.h>
@@ -824,6 +825,7 @@ copy_entries_to_user(unsigned int total_size,
loc_cpu_entry = private->entries;
+ pagefault_disable();
/* FIXME: use iterator macros --RR */
/* ... then go back and fix counters and names */
for (off = 0, num = 0; off < total_size; off += e->next_offset, num++){
@@ -861,6 +863,7 @@ copy_entries_to_user(unsigned int total_size,
}
free_counters:
+ pagefault_enable();
vfree(counters);
return ret;
}
@@ -943,6 +946,7 @@ static int compat_table_info(const struct xt_table_info *info,
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 = xt_request_find_table_lock(net, AF_INET, name);
if (!IS_ERR(t)) {
- struct ipt_getinfo info;
const struct xt_table_info *private = 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 = private->size;
strscpy(info.name, name);
- if (copy_to_user(user, &info, *len) != 0)
- ret = -EFAULT;
- else
- ret = 0;
+ ret = 0;
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) != 0)
+ ret = -EFAULT;
return ret;
}
@@ -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 = false;
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] = '\0';
+ retry:
t = xt_find_table_lock(net, AF_INET, get.name);
if (!IS_ERR(t)) {
const struct xt_table_info *private = t->private;
@@ -1027,6 +1031,14 @@ get_entries(struct net *net, struct ipt_get_entries __user *uptr,
} else
ret = PTR_ERR(t);
+ if (ret == -EFAULT && !faulted) {
+ faulted = true;
+ if (fault_in_writeable((char __user *)uptr->entrytable,
+ get.size))
+ return -EFAULT;
+ goto retry;
+ }
+
return ret;
}
@@ -1561,12 +1573,14 @@ compat_copy_entries_to_user(unsigned int total_size, struct xt_table *table,
pos = userptr;
size = total_size;
+ pagefault_disable();
xt_entry_foreach(iter, private->entries, total_size) {
ret = compat_copy_entry_to_user(iter, &pos,
&size, counters, i++);
if (ret != 0)
break;
}
+ pagefault_enable();
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 = false;
if (*len < sizeof(get))
return -EINVAL;
@@ -1591,6 +1606,7 @@ compat_get_entries(struct net *net, struct compat_ipt_get_entries __user *uptr,
get.name[sizeof(get.name) - 1] = '\0';
+ retry:
xt_compat_lock(AF_INET);
t = 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_ipt_get_entries __user *uptr,
ret = PTR_ERR(t);
xt_compat_unlock(AF_INET);
+ if (ret == -EFAULT && !faulted) {
+ faulted = 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_tables.c
index f42fb96ef..493e6fbc6 100644
--- a/net/ipv6/netfilter/ip6_tables.c
+++ b/net/ipv6/netfilter/ip6_tables.c
@@ -25,6 +25,7 @@
#include <linux/proc_fs.h>
#include <linux/err.h>
#include <linux/cpumask.h>
+#include <linux/pagemap.h>
#include <linux/netfilter_ipv6/ip6_tables.h>
#include <linux/netfilter/x_tables.h>
@@ -840,6 +841,7 @@ copy_entries_to_user(unsigned int total_size,
loc_cpu_entry = private->entries;
+ pagefault_disable();
/* FIXME: use iterator macros --RR */
/* ... then go back and fix counters and names */
for (off = 0, num = 0; off < total_size; off += e->next_offset, num++){
@@ -877,6 +879,7 @@ copy_entries_to_user(unsigned int total_size,
}
free_counters:
+ pagefault_enable();
vfree(counters);
return ret;
}
@@ -959,6 +962,7 @@ static int compat_table_info(const struct xt_table_info *info,
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 = xt_request_find_table_lock(net, AF_INET6, name);
if (!IS_ERR(t)) {
- struct ip6t_getinfo info;
const struct xt_table_info *private = 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 *user, const int *len)
info.size = private->size;
strcpy(info.name, name);
- if (copy_to_user(user, &info, *len) != 0)
- ret = -EFAULT;
- else
- ret = 0;
+ ret = 0;
xt_table_unlock(t);
module_put(t->me);
@@ -1010,6 +1010,8 @@ static int get_info(struct net *net, void __user *user, const int *len)
if (in_compat_syscall())
xt_compat_unlock(AF_INET6);
#endif
+ if (!ret && copy_to_user(user, &info, *len) != 0)
+ ret = -EFAULT;
return ret;
}
@@ -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 = false;
if (*len < sizeof(get))
return -EINVAL;
@@ -1030,6 +1033,7 @@ get_entries(struct net *net, struct ip6t_get_entries __user *uptr,
get.name[sizeof(get.name) - 1] = '\0';
+ retry:
t = xt_find_table_lock(net, AF_INET6, get.name);
if (!IS_ERR(t)) {
struct xt_table_info *private = t->private;
@@ -1044,6 +1048,14 @@ get_entries(struct net *net, struct ip6t_get_entries __user *uptr,
} else
ret = PTR_ERR(t);
+ if (ret == -EFAULT && !faulted) {
+ faulted = true;
+ if (fault_in_writeable((char __user *)uptr->entrytable,
+ get.size))
+ return -EFAULT;
+ goto retry;
+ }
+
return ret;
}
@@ -1570,12 +1582,14 @@ compat_copy_entries_to_user(unsigned int total_size, struct xt_table *table,
pos = userptr;
size = total_size;
+ pagefault_disable();
xt_entry_foreach(iter, private->entries, total_size) {
ret = compat_copy_entry_to_user(iter, &pos,
&size, counters, i++);
if (ret != 0)
break;
}
+ pagefault_enable();
vfree(counters);
return ret;
@@ -1588,6 +1602,7 @@ compat_get_entries(struct net *net, struct compat_ip6t_get_entries __user *uptr,
int ret;
struct compat_ip6t_get_entries get;
struct xt_table *t;
+ bool faulted = false;
if (*len < sizeof(get))
return -EINVAL;
@@ -1600,6 +1615,7 @@ compat_get_entries(struct net *net, struct compat_ip6t_get_entries __user *uptr,
get.name[sizeof(get.name) - 1] = '\0';
+ retry:
xt_compat_lock(AF_INET6);
t = 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_ip6t_get_entries __user *uptr,
ret = PTR_ERR(t);
xt_compat_unlock(AF_INET6);
+ if (ret == -EFAULT && !faulted) {
+ faulted = true;
+ if (fault_in_writeable((char __user *)uptr->entrytable,
+ get.size))
+ return -EFAULT;
+ goto retry;
+ }
return ret;
}
#endif
--
2.43.0