Skip to contentSkip to conversation

netfilter : struct xt_table_info diet

PATCH In review Eric Dumazet 4 participants 8 messages Nov 14, 2007 – Nov 20, 2007

Subsystems (3)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 netfilter : struct xt_table_info diet

Hi David & Patrick

Please find an updated version of this patch, against net-2.6.25

(relax the check against sizes of loaded tables, using a divide by num_possible_cpus() in place of NR_CPUS)

Thank you

[PATCH] netfilter : struct xt_table_info diet

Instead of using a big array of NR_CPUS entries, we can compute the size needed at runtime, using nr_cpu_ids

This should save some ram (especially on David's machines where NR_CPUS=4096 :
32 KB can be saved per table, and 64KB for dynamically allocated ones (because of slab/slub alignements) )

In particular, the 'bootstrap' tables are not any more static (in data section) but on stack as their size is now very small.

This also should reduce the size used on stack in compat functions (get_info() declares an automatic variable, that could be bigger than kernel stack size for big NR_CPUS)

include/linux/netfilter/x_tables.h +4 -1
index 9657c4e..e305f2d 100644
@@ -269,9 +269,12 @@ struct xt_table_info
 	unsigned int underflow[NF_INET_NUMHOOKS];
 
 	/* ipt_entry tables: one per CPU */
-	char *entries[NR_CPUS];
+	/* Note : this field MUST be the last one, see XT_TABLE_INFO_SZ */
+	char *entries[1];
 };
 
+#define XT_TABLE_INFO_SZ (offsetof(struct xt_table_info, entries) \
+			  + nr_cpu_ids * sizeof(char *))
 extern int xt_register_target(struct xt_target *target);
 extern void xt_unregister_target(struct xt_target *target);
 extern int xt_register_targets(struct xt_target *target, unsigned int n);
net/ipv4/netfilter/arp_tables.c +2 -3
index 2909c92..a21722d 100644
@@ -811,8 +811,7 @@ static int do_replace(void __user *user, unsigned int len)
 		return -ENOPROTOOPT;
 
 	/* overflow check */
-	if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS -
-			SMP_CACHE_BYTES)
+	if (tmp.size >= INT_MAX / num_possible_cpus())
 		return -ENOMEM;
 	if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters))
 		return -ENOMEM;
@@ -1090,7 +1089,7 @@ int arpt_register_table(struct arpt_table *table,
 {
 	int ret;
 	struct xt_table_info *newinfo;
-	static struct xt_table_info bootstrap
+	struct xt_table_info bootstrap
 		= { 0, 0, 0, { 0 }, { 0 }, { } };
 	void *loc_cpu_entry;
 
net/ipv4/netfilter/ip_tables.c +10 -15
index a99fe89..0afef0f 100644
@@ -1090,7 +1090,8 @@ compat_calc_match(struct ipt_entry_match *m, int * size)
 	return 0;
 }
 
-static int compat_calc_entry(struct ipt_entry *e, struct xt_table_info *info,
+static int compat_calc_entry(struct ipt_entry *e,
+		const struct xt_table_info *info,
 		void *base, struct xt_table_info *newinfo)
 {
 	struct ipt_entry_target *t;
@@ -1118,7 +1119,7 @@ static int compat_calc_entry(struct ipt_entry *e, struct xt_table_info *info,
 	return 0;
 }
 
-static int compat_table_info(struct xt_table_info *info,
+static int compat_table_info(const struct xt_table_info *info,
 		struct xt_table_info *newinfo)
 {
 	void *loc_cpu_entry;
@@ -1127,13 +1128,9 @@ static int compat_table_info(struct xt_table_info *info,
 	if (!newinfo || !info)
 		return -EINVAL;
 
-	memset(newinfo, 0, sizeof(struct xt_table_info));
-	newinfo->size = info->size;
-	newinfo->number = info->number;
-	for (i = 0; i < NF_INET_NUMHOOKS; i++) {
-		newinfo->hook_entry[i] = info->hook_entry[i];
-		newinfo->underflow[i] = info->underflow[i];
-	}
+	/* we dont care about newinfo->entries[] */
+	memcpy(newinfo, info, offsetof(struct xt_table_info, entries));
+	newinfo->initial_entries = 0;
 	loc_cpu_entry = info->entries[raw_smp_processor_id()];
 	return IPT_ENTRY_ITERATE(loc_cpu_entry, info->size,
 			compat_calc_entry, info, loc_cpu_entry, newinfo);
@@ -1327,8 +1324,7 @@ do_replace(void __user *user, unsigned int len)
 		return -ENOPROTOOPT;
 
 	/* overflow check */
-	if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS -
-			SMP_CACHE_BYTES)
+	if (tmp.size >= INT_MAX / num_possible_cpus())
 		return -ENOMEM;
 	if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters))
 		return -ENOMEM;
@@ -1861,8 +1857,7 @@ compat_do_replace(void __user *user, unsigned int len)
 		return -ENOPROTOOPT;
 
 	/* overflow check */
-	if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS -
-			SMP_CACHE_BYTES)
+	if (tmp.size >= INT_MAX / num_possible_cpus())
 		return -ENOMEM;
 	if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters))
 		return -ENOMEM;
@@ -2036,7 +2031,7 @@ compat_get_entries(struct compat_ipt_get_entries __user *uptr, int *len)
 		duprintf("t->private->number = %u\n",
 			 private->number);
 		ret = compat_table_info(private, &info);
-		if (!ret && get.size == info.size) {
+		if (!ret && get.size == info->size) {
 			ret = compat_copy_entries_to_user(private->size,
 						   t, uptr->entrytable);
 		} else if (!ret) {
@@ -2159,7 +2154,7 @@ int ipt_register_table(struct xt_table *table, const struct ipt_replace *repl)
 {
 	int ret;
 	struct xt_table_info *newinfo;
-	static struct xt_table_info bootstrap
+	struct xt_table_info bootstrap
 		= { 0, 0, 0, { 0 }, { 0 }, { } };
 	void *loc_cpu_entry;
 
net/ipv6/netfilter/ip6_tables.c +2 -3
index e1e87ef..e60c1b4 100644
@@ -1042,8 +1042,7 @@ do_replace(void __user *user, unsigned int len)
 		return -EFAULT;
 
 	/* overflow check */
-	if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS -
-			SMP_CACHE_BYTES)
+	if (tmp.size >= INT_MAX / num_possible_cpus())
 		return -ENOMEM;
 	if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters))
 		return -ENOMEM;
@@ -1339,7 +1338,7 @@ int ip6t_register_table(struct xt_table *table,
 {
 	int ret;
 	struct xt_table_info *newinfo;
-	static struct xt_table_info bootstrap
+	struct xt_table_info bootstrap
 		= { 0, 0, 0, { 0 }, { 0 }, { } };
 	void *loc_cpu_entry;
 
net/netfilter/x_tables.c +1 -1
index d9a3bde..862b27d 100644
@@ -495,7 +495,7 @@ struct xt_table_info *xt_alloc_table_info(unsigned int size)
 	if ((SMP_ALIGN(size) >> PAGE_SHIFT) + 2 > num_physpages)
 		return NULL;
 
-	newinfo = kzalloc(sizeof(struct xt_table_info), GFP_KERNEL);
+	newinfo = kzalloc(XT_TABLE_INFO_SZ, GFP_KERNEL);
 	if (!newinfo)
 		return NULL;
 
#1 lore raw
Quoted 11 lines · Eric Dumazet

[PATCH] netfilter : struct xt_table_info diet

Instead of using a big array of NR_CPUS entries, we can compute the size needed at runtime, using nr_cpu_ids

This should save some ram (especially on David's machines where NR_CPUS=4096 :
32 KB can be saved per table, and 64KB for dynamically allocated ones (because
of slab/slub alignements) )

Quoted 7 lines

In particular, the 'bootstrap' tables are not any more static (in data section) but on stack as their size is now very small.

This also should reduce the size used on stack in compat functions (get_info() declares an automatic variable, that could be bigger than kernel
stack size for big NR_CPUS)

I fixed a compilation error with CONFIG_COMPAT and applied it, thanks Eric. One question though:

net/ipv4/netfilter/arp_tables.c static int do_replace(void __user *user, unsigned int len) · line 815

Quoted 9 lines
net/ipv4/netfilter/arp_tables.c +3 -2
+#define XT_TABLE_INFO_SZ (offsetof(struct xt_table_info, entries) \
+			  + nr_cpu_ids * sizeof(char *))
 
 
 	/* overflow check */
-	if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS -
-			SMP_CACHE_BYTES)
+	if (tmp.size >= INT_MAX / num_possible_cpus())
 		return -ENOMEM;

We need to make sure offsetof(struct xt_table_info, entries) + nr_cpu_ids * sizeof(char *) doesn't overflow, so why doesn't it use nr_cpu_ids here as well?

#2 lore raw
Quoted 28 lines · On Thu, 15 Nov 2007 13:41:54 +0100 Patrick McHardy <[email protected]> wrote:
Quoted 11 lines · Eric Dumazet

[PATCH] netfilter : struct xt_table_info diet

Instead of using a big array of NR_CPUS entries, we can compute the size needed at runtime, using nr_cpu_ids

This should save some ram (especially on David's machines where NR_CPUS=4096 :
32 KB can be saved per table, and 64KB for dynamically allocated ones (because
of slab/slub alignements) )

Quoted 7 lines

In particular, the 'bootstrap' tables are not any more static (in data section) but on stack as their size is now very small.

This also should reduce the size used on stack in compat functions (get_info() declares an automatic variable, that could be bigger than kernel
stack size for big NR_CPUS)

I fixed a compilation error with CONFIG_COMPAT and applied it, thanks Eric. One question though:

Quoted 2 lines
include/linux/netfilter/x_tables.h +2 -0
+#define XT_TABLE_INFO_SZ (offsetof(struct xt_table_info, entries) \
+			  + nr_cpu_ids * sizeof(char *))
Quoted 10 lines

net/ipv4/netfilter/arp_tables.c static int do_replace(void __user *user, unsigned int len) · line 815

Quoted 5 lines
net/ipv4/netfilter/arp_tables.c +1 -2
 	/* overflow check */
-	if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS -
-			SMP_CACHE_BYTES)
+	if (tmp.size >= INT_MAX / num_possible_cpus())
 		return -ENOMEM;

We need to make sure offsetof(struct xt_table_info, entries) + nr_cpu_ids * sizeof(char *) doesn't overflow, so why doesn't it use nr_cpu_ids here as well?

nr_cpu_ids is <= NR_CPUS, so XT_TABLE_INFO_SZ cannot overflow

The 'overflow check' we do here is in fact not very usefull now that we dont need to multiply tmp.size by NR_CPUS and potentially overflow the result.

We can delete the test, because kmalloc()/vmalloc() will probably fail gracefully if we ask too much memory.

We could imagine a dual Opteron machine, with a total of 32GB of ram, and it could be possible to load a 3GB iptable (that would consume 2*3GB of ram), but the 'overflow check' test actually forbids such a scenario.

#3 lore raw
Quoted 16 lines · Eric Dumazet
Quoted 12 lines · On Thu, 15 Nov 2007 13:41:54 +0100 Patrick McHardy <[email protected]> wrote:

net/ipv4/netfilter/arp_tables.c static int do_replace(void __user *user, unsigned int len) · line 815

Quoted 8 lines
net/ipv4/netfilter/arp_tables.c +3 -2
+#define XT_TABLE_INFO_SZ (offsetof(struct xt_table_info, entries) \
+			  + nr_cpu_ids * sizeof(char *))
 
 	/* overflow check */
-	if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS -
-			SMP_CACHE_BYTES)
+	if (tmp.size >= INT_MAX / num_possible_cpus())
 		return -ENOMEM;

We need to make sure offsetof(struct xt_table_info, entries) + nr_cpu_ids * sizeof(char *) doesn't overflow, so why doesn't it use nr_cpu_ids here as well?

Quoted 1 line

nr_cpu_ids is <= NR_CPUS, so XT_TABLE_INFO_SZ cannot overflow

Yes, but nr_cpu_ids is >= num_possible_cpus, which is what we're using with your patch.

Quoted 6 lines

The 'overflow check' we do here is in fact not very usefull now that we dont need to multiply tmp.size by NR_CPUS and potentially overflow the result.

We can delete the test, because kmalloc()/vmalloc() will probably fail gracefully if we ask too much memory.

You're right, I'll remove it. Thanks.

#4 lore raw
In reply to Eric Dumazet

From: Eric Dumazet <[email protected]>

Instead of using a big array of NR_CPUS entries, we can compute the size needed at runtime, using nr_cpu_ids

This should save some ram (especially on David's machines where NR_CPUS=4096 :
32 KB can be saved per table, and 64KB for dynamically allocated ones (because of slab/slub alignements) )

In particular, the 'bootstrap' tables are not any more static (in data section) but on stack as their size is now very small.

This also should reduce the size used on stack in compat functions (get_info() declares an automatic variable, that could be bigger than kernel stack size for big NR_CPUS)

---
Tue, 20 Nov 2007 15:23:33 +0900, Simon Horman <[email protected]>

Up-port to net-2.6.25 6d20d53c3d54d80211247dbe5c9cf67fda083a88

include/linux/netfilter/x_tables.h +4 -1
index 9657c4e..e305f2d 100644
@@ -269,9 +269,12 @@ struct xt_table_info
 	unsigned int underflow[NF_INET_NUMHOOKS];
 
 	/* ipt_entry tables: one per CPU */
-	char *entries[NR_CPUS];
+	/* Note : this field MUST be the last one, see XT_TABLE_INFO_SZ */
+	char *entries[1];
 };
 
+#define XT_TABLE_INFO_SZ (offsetof(struct xt_table_info, entries) \
+			  + nr_cpu_ids * sizeof(char *))
 extern int xt_register_target(struct xt_target *target);
 extern void xt_unregister_target(struct xt_target *target);
 extern int xt_register_targets(struct xt_target *target, unsigned int n);
net/ipv4/netfilter/arp_tables.c +2 -3
index 2909c92..a21722d 100644
@@ -811,8 +811,7 @@ static int do_replace(void __user *user, unsigned int len)
 		return -ENOPROTOOPT;
 
 	/* overflow check */
-	if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS -
-			SMP_CACHE_BYTES)
+	if (tmp.size >= INT_MAX / num_possible_cpus())
 		return -ENOMEM;
 	if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters))
 		return -ENOMEM;
@@ -1090,7 +1089,7 @@ int arpt_register_table(struct arpt_table *table,
 {
 	int ret;
 	struct xt_table_info *newinfo;
-	static struct xt_table_info bootstrap
+	struct xt_table_info bootstrap
 		= { 0, 0, 0, { 0 }, { 0 }, { } };
 	void *loc_cpu_entry;
 
net/ipv4/netfilter/ip_tables.c +10 -15
index a99fe89..0afef0f 100644
@@ -1090,7 +1090,8 @@ compat_calc_match(struct ipt_entry_match *m, int * size)
 	return 0;
 }
 
-static int compat_calc_entry(struct ipt_entry *e, struct xt_table_info *info,
+static int compat_calc_entry(struct ipt_entry *e,
+		const struct xt_table_info *info,
 		void *base, struct xt_table_info *newinfo)
 {
 	struct ipt_entry_target *t;
@@ -1118,7 +1119,7 @@ static int compat_calc_entry(struct ipt_entry *e, struct xt_table_info *info,
 	return 0;
 }
 
-static int compat_table_info(struct xt_table_info *info,
+static int compat_table_info(const struct xt_table_info *info,
 		struct xt_table_info *newinfo)
 {
 	void *loc_cpu_entry;
@@ -1127,13 +1128,9 @@ static int compat_table_info(struct xt_table_info *info,
 	if (!newinfo || !info)
 		return -EINVAL;
 
-	memset(newinfo, 0, sizeof(struct xt_table_info));
-	newinfo->size = info->size;
-	newinfo->number = info->number;
-	for (i = 0; i < NF_INET_NUMHOOKS; i++) {
-		newinfo->hook_entry[i] = info->hook_entry[i];
-		newinfo->underflow[i] = info->underflow[i];
-	}
+	/* we dont care about newinfo->entries[] */
+	memcpy(newinfo, info, offsetof(struct xt_table_info, entries));
+	newinfo->initial_entries = 0;
 	loc_cpu_entry = info->entries[raw_smp_processor_id()];
 	return IPT_ENTRY_ITERATE(loc_cpu_entry, info->size,
 			compat_calc_entry, info, loc_cpu_entry, newinfo);
@@ -1327,8 +1324,7 @@ do_replace(void __user *user, unsigned int len)
 		return -ENOPROTOOPT;
 
 	/* overflow check */
-	if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS -
-			SMP_CACHE_BYTES)
+	if (tmp.size >= INT_MAX / num_possible_cpus())
 		return -ENOMEM;
 	if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters))
 		return -ENOMEM;
@@ -1861,8 +1857,7 @@ compat_do_replace(void __user *user, unsigned int len)
 		return -ENOPROTOOPT;
 
 	/* overflow check */
-	if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS -
-			SMP_CACHE_BYTES)
+	if (tmp.size >= INT_MAX / num_possible_cpus())
 		return -ENOMEM;
 	if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters))
 		return -ENOMEM;
@@ -2036,7 +2031,7 @@ compat_get_entries(struct compat_ipt_get_entries __user *uptr, int *len)
 		duprintf("t->private->number = %u\n",
 			 private->number);
 		ret = compat_table_info(private, &info);
-		if (!ret && get.size == info.size) {
+		if (!ret && get.size == info->size) {
 			ret = compat_copy_entries_to_user(private->size,
 						   t, uptr->entrytable);
 		} else if (!ret) {
@@ -2159,7 +2154,7 @@ int ipt_register_table(struct xt_table *table, const struct ipt_replace *repl)
 {
 	int ret;
 	struct xt_table_info *newinfo;
-	static struct xt_table_info bootstrap
+	struct xt_table_info bootstrap
 		= { 0, 0, 0, { 0 }, { 0 }, { } };
 	void *loc_cpu_entry;
 
net/ipv6/netfilter/ip6_tables.c +2 -3
index e1e87ef..e60c1b4 100644
@@ -1042,8 +1042,7 @@ do_replace(void __user *user, unsigned int len)
 		return -EFAULT;
 
 	/* overflow check */
-	if (tmp.size >= (INT_MAX - sizeof(struct xt_table_info)) / NR_CPUS -
-			SMP_CACHE_BYTES)
+	if (tmp.size >= INT_MAX / num_possible_cpus())
 		return -ENOMEM;
 	if (tmp.num_counters >= INT_MAX / sizeof(struct xt_counters))
 		return -ENOMEM;
@@ -1339,7 +1338,7 @@ int ip6t_register_table(struct xt_table *table,
 {
 	int ret;
 	struct xt_table_info *newinfo;
-	static struct xt_table_info bootstrap
+	struct xt_table_info bootstrap
 		= { 0, 0, 0, { 0 }, { 0 }, { } };
 	void *loc_cpu_entry;
 
net/netfilter/x_tables.c +1 -1
index d9a3bde..862b27d 100644
@@ -495,7 +495,7 @@ struct xt_table_info *xt_alloc_table_info(unsigned int size)
 	if ((SMP_ALIGN(size) >> PAGE_SHIFT) + 2 > num_physpages)
 		return NULL;
 
-	newinfo = kzalloc(sizeof(struct xt_table_info), GFP_KERNEL);
+	newinfo = kzalloc(XT_TABLE_INFO_SZ, GFP_KERNEL);
 	if (!newinfo)
 		return NULL;
 
#5 lore raw

include/linux/netfilter/x_tables.h struct xt_table_info · line 273

Quoted 8 lines · Simon Horman
include/linux/netfilter/x_tables.h +2 -1
@@ -269,9 +269,12 @@ struct xt_table_info
       unsigned int underflow[NF_INET_NUMHOOKS];
 
       /* ipt_entry tables: one per CPU */
-       char *entries[NR_CPUS];
+       /* Note : this field MUST be the last one, see XT_TABLE_INFO_SZ */
+       char *entries[1];

Just do

	char *entries[];

Not only is it ISO C99, but the compiler will die if it's not at the end.

Actually I haven't read the rest of the patch, so scratch this comment if you really need to have one entry there for some reason :)

Cheers,

Signature · 4 lines
Visit Openswan at http://www.openswan.org/
Email: Herbert Xu ~{PmV>HI~} <[email protected]>
Home Page: http://gondor.apana.org.au/~herbert/
PGP Key: http://gondor.apana.org.au/~herbert/pubkey.txt
#6 lore raw
Quoted 13 lines · Herbert Xu

include/linux/netfilter/x_tables.h struct xt_table_info · line 274

Quoted 8 lines · Simon Horman
include/linux/netfilter/x_tables.h +2 -1
@@ -269,9 +269,12 @@ struct xt_table_info
       unsigned int underflow[NF_INET_NUMHOOKS];
 
       /* ipt_entry tables: one per CPU */
-       char *entries[NR_CPUS];
+       /* Note : this field MUST be the last one, see XT_TABLE_INFO_SZ */
+       char *entries[1];
 

Just do

Quoted 8 lines
	char *entries[];

Not only is it ISO C99, but the compiler will die if it's not at the end.

Actually I haven't read the rest of the patch, so scratch this comment if you really need to have one entry there for some reason :)

I thought this too, but its needed for the bootstrap tables, which are declared on the stack :)

#7 lore raw
In reply to Simon Horman
Quoted 9 lines · Simon Horman

From: Eric Dumazet <[email protected]>

Instead of using a big array of NR_CPUS entries, we can compute the size needed at runtime, using nr_cpu_ids

This should save some ram (especially on David's machines where NR_CPUS=4096 :
32 KB can be saved per table, and 64KB for dynamically allocated ones (because of slab/slub alignements) )

Quoted 6 lines

In particular, the 'bootstrap' tables are not any more static (in data section) but on stack as their size is now very small.

This also should reduce the size used on stack in compat functions (get_info() declares an automatic variable, that could be bigger than kernel stack size for big NR_CPUS)

This seems to be identical to the patch I already got queued apart from reintroducing the NR_CPUS check. Am I missing something?