diff options
| author | Jan Engelhardt <jengelh@medozas.de> | 2010-04-19 10:05:10 -0400 |
|---|---|---|
| committer | Patrick McHardy <kaber@trash.net> | 2010-04-19 10:05:10 -0400 |
| commit | f3c5c1bfd430858d3a05436f82c51e53104feb6b (patch) | |
| tree | ada5b570b66e141e79fdb256f69e2541a3d30c04 | |
| parent | e281b19897dc21c1071802808d461627d747a877 (diff) | |
netfilter: xtables: make ip_tables reentrant
Currently, the table traverser stores return addresses in the ruleset
itself (struct ip6t_entry->comefrom). This has a well-known drawback:
the jumpstack is overwritten on reentry, making it necessary for
targets to return absolute verdicts. Also, the ruleset (which might
be heavy memory-wise) needs to be replicated for each CPU that can
possibly invoke ip6t_do_table.
This patch decouples the jumpstack from struct ip6t_entry and instead
puts it into xt_table_info. Not being restricted by 'comefrom'
anymore, we can set up a stack as needed. By default, there is room
allocated for two entries into the traverser.
arp_tables is not touched though, because there is just one/two
modules and further patches seek to collapse the table traverser
anyhow.
Signed-off-by: Jan Engelhardt <jengelh@medozas.de>
Signed-off-by: Patrick McHardy <kaber@trash.net>
| -rw-r--r-- | include/linux/netfilter/x_tables.h | 7 | ||||
| -rw-r--r-- | net/ipv4/netfilter/arp_tables.c | 6 | ||||
| -rw-r--r-- | net/ipv4/netfilter/ip_tables.c | 65 | ||||
| -rw-r--r-- | net/ipv6/netfilter/ip6_tables.c | 56 | ||||
| -rw-r--r-- | net/netfilter/x_tables.c | 77 |
5 files changed, 145 insertions, 66 deletions
diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h index 26ced0c323a5..50c867256ca3 100644 --- a/include/linux/netfilter/x_tables.h +++ b/include/linux/netfilter/x_tables.h | |||
| @@ -401,6 +401,13 @@ struct xt_table_info { | |||
| 401 | unsigned int hook_entry[NF_INET_NUMHOOKS]; | 401 | unsigned int hook_entry[NF_INET_NUMHOOKS]; |
| 402 | unsigned int underflow[NF_INET_NUMHOOKS]; | 402 | unsigned int underflow[NF_INET_NUMHOOKS]; |
| 403 | 403 | ||
| 404 | /* | ||
| 405 | * Number of user chains. Since tables cannot have loops, at most | ||
| 406 | * @stacksize jumps (number of user chains) can possibly be made. | ||
| 407 | */ | ||
| 408 | unsigned int stacksize; | ||
| 409 | unsigned int *stackptr; | ||
| 410 | void ***jumpstack; | ||
| 404 | /* ipt_entry tables: one per CPU */ | 411 | /* ipt_entry tables: one per CPU */ |
| 405 | /* Note : this field MUST be the last one, see XT_TABLE_INFO_SZ */ | 412 | /* Note : this field MUST be the last one, see XT_TABLE_INFO_SZ */ |
| 406 | void *entries[1]; | 413 | void *entries[1]; |
diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c index e8e363d90365..07a699059390 100644 --- a/net/ipv4/netfilter/arp_tables.c +++ b/net/ipv4/netfilter/arp_tables.c | |||
| @@ -649,6 +649,9 @@ static int translate_table(struct xt_table_info *newinfo, void *entry0, | |||
| 649 | if (ret != 0) | 649 | if (ret != 0) |
| 650 | break; | 650 | break; |
| 651 | ++i; | 651 | ++i; |
| 652 | if (strcmp(arpt_get_target(iter)->u.user.name, | ||
| 653 | XT_ERROR_TARGET) == 0) | ||
| 654 | ++newinfo->stacksize; | ||
| 652 | } | 655 | } |
| 653 | duprintf("translate_table: ARPT_ENTRY_ITERATE gives %d\n", ret); | 656 | duprintf("translate_table: ARPT_ENTRY_ITERATE gives %d\n", ret); |
| 654 | if (ret != 0) | 657 | if (ret != 0) |
| @@ -1774,8 +1777,7 @@ struct xt_table *arpt_register_table(struct net *net, | |||
| 1774 | { | 1777 | { |
| 1775 | int ret; | 1778 | int ret; |
| 1776 | struct xt_table_info *newinfo; | 1779 | struct xt_table_info *newinfo; |
| 1777 | struct xt_table_info bootstrap | 1780 | struct xt_table_info bootstrap = {0}; |
| 1778 | = { 0, 0, 0, { 0 }, { 0 }, { } }; | ||
| 1779 | void *loc_cpu_entry; | 1781 | void *loc_cpu_entry; |
| 1780 | struct xt_table *new_table; | 1782 | struct xt_table *new_table; |
| 1781 | 1783 | ||
diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c index 18c5b1573f3e..70900ecf88e2 100644 --- a/net/ipv4/netfilter/ip_tables.c +++ b/net/ipv4/netfilter/ip_tables.c | |||
| @@ -321,8 +321,6 @@ ipt_do_table(struct sk_buff *skb, | |||
| 321 | const struct net_device *out, | 321 | const struct net_device *out, |
| 322 | struct xt_table *table) | 322 | struct xt_table *table) |
| 323 | { | 323 | { |
| 324 | #define tb_comefrom ((struct ipt_entry *)table_base)->comefrom | ||
| 325 | |||
| 326 | static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long)))); | 324 | static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long)))); |
| 327 | const struct iphdr *ip; | 325 | const struct iphdr *ip; |
| 328 | bool hotdrop = false; | 326 | bool hotdrop = false; |
| @@ -330,7 +328,8 @@ ipt_do_table(struct sk_buff *skb, | |||
| 330 | unsigned int verdict = NF_DROP; | 328 | unsigned int verdict = NF_DROP; |
| 331 | const char *indev, *outdev; | 329 | const char *indev, *outdev; |
| 332 | const void *table_base; | 330 | const void *table_base; |
| 333 | struct ipt_entry *e, *back; | 331 | struct ipt_entry *e, **jumpstack; |
| 332 | unsigned int *stackptr, origptr, cpu; | ||
| 334 | const struct xt_table_info *private; | 333 | const struct xt_table_info *private; |
| 335 | struct xt_match_param mtpar; | 334 | struct xt_match_param mtpar; |
| 336 | struct xt_target_param tgpar; | 335 | struct xt_target_param tgpar; |
| @@ -356,19 +355,23 @@ ipt_do_table(struct sk_buff *skb, | |||
| 356 | IP_NF_ASSERT(table->valid_hooks & (1 << hook)); | 355 | IP_NF_ASSERT(table->valid_hooks & (1 << hook)); |
| 357 | xt_info_rdlock_bh(); | 356 | xt_info_rdlock_bh(); |
| 358 | private = table->private; | 357 | private = table->private; |
| 359 | table_base = private->entries[smp_processor_id()]; | 358 | cpu = smp_processor_id(); |
| 359 | table_base = private->entries[cpu]; | ||
| 360 | jumpstack = (struct ipt_entry **)private->jumpstack[cpu]; | ||
| 361 | stackptr = &private->stackptr[cpu]; | ||
| 362 | origptr = *stackptr; | ||
| 360 | 363 | ||
| 361 | e = get_entry(table_base, private->hook_entry[hook]); | 364 | e = get_entry(table_base, private->hook_entry[hook]); |
| 362 | 365 | ||
| 363 | /* For return from builtin chain */ | 366 | pr_devel("Entering %s(hook %u); sp at %u (UF %p)\n", |
| 364 | back = get_entry(table_base, private->underflow[hook]); | 367 | table->name, hook, origptr, |
| 368 | get_entry(table_base, private->underflow[hook])); | ||
| 365 | 369 | ||
| 366 | do { | 370 | do { |
| 367 | const struct ipt_entry_target *t; | 371 | const struct ipt_entry_target *t; |
| 368 | const struct xt_entry_match *ematch; | 372 | const struct xt_entry_match *ematch; |
| 369 | 373 | ||
| 370 | IP_NF_ASSERT(e); | 374 | IP_NF_ASSERT(e); |
| 371 | IP_NF_ASSERT(back); | ||
| 372 | if (!ip_packet_match(ip, indev, outdev, | 375 | if (!ip_packet_match(ip, indev, outdev, |
| 373 | &e->ip, mtpar.fragoff)) { | 376 | &e->ip, mtpar.fragoff)) { |
| 374 | no_match: | 377 | no_match: |
| @@ -403,17 +406,28 @@ ipt_do_table(struct sk_buff *skb, | |||
| 403 | verdict = (unsigned)(-v) - 1; | 406 | verdict = (unsigned)(-v) - 1; |
| 404 | break; | 407 | break; |
| 405 | } | 408 | } |
| 406 | e = back; | 409 | if (*stackptr == 0) { |
| 407 | back = get_entry(table_base, back->comefrom); | 410 | e = get_entry(table_base, |
| 411 | private->underflow[hook]); | ||
| 412 | pr_devel("Underflow (this is normal) " | ||
| 413 | "to %p\n", e); | ||
| 414 | } else { | ||
| 415 | e = jumpstack[--*stackptr]; | ||
| 416 | pr_devel("Pulled %p out from pos %u\n", | ||
| 417 | e, *stackptr); | ||
| 418 | e = ipt_next_entry(e); | ||
| 419 | } | ||
| 408 | continue; | 420 | continue; |
| 409 | } | 421 | } |
| 410 | if (table_base + v != ipt_next_entry(e) && | 422 | if (table_base + v != ipt_next_entry(e) && |
| 411 | !(e->ip.flags & IPT_F_GOTO)) { | 423 | !(e->ip.flags & IPT_F_GOTO)) { |
| 412 | /* Save old back ptr in next entry */ | 424 | if (*stackptr >= private->stacksize) { |
| 413 | struct ipt_entry *next = ipt_next_entry(e); | 425 | verdict = NF_DROP; |
| 414 | next->comefrom = (void *)back - table_base; | 426 | break; |
| 415 | /* set back pointer to next entry */ | 427 | } |
| 416 | back = next; | 428 | jumpstack[(*stackptr)++] = e; |
| 429 | pr_devel("Pushed %p into pos %u\n", | ||
| 430 | e, *stackptr - 1); | ||
| 417 | } | 431 | } |
| 418 | 432 | ||
| 419 | e = get_entry(table_base, v); | 433 | e = get_entry(table_base, v); |
| @@ -426,18 +440,7 @@ ipt_do_table(struct sk_buff *skb, | |||
| 426 | tgpar.targinfo = t->data; | 440 | tgpar.targinfo = t->data; |
| 427 | 441 | ||
| 428 | 442 | ||
| 429 | #ifdef CONFIG_NETFILTER_DEBUG | ||
| 430 | tb_comefrom = 0xeeeeeeec; | ||
| 431 | #endif | ||
| 432 | verdict = t->u.kernel.target->target(skb, &tgpar); | 443 | verdict = t->u.kernel.target->target(skb, &tgpar); |
| 433 | #ifdef CONFIG_NETFILTER_DEBUG | ||
| 434 | if (tb_comefrom != 0xeeeeeeec && verdict == IPT_CONTINUE) { | ||
| 435 | printk("Target %s reentered!\n", | ||
| 436 | t->u.kernel.target->name); | ||
| 437 | verdict = NF_DROP; | ||
| 438 | } | ||
| 439 | tb_comefrom = 0x57acc001; | ||
| 440 | #endif | ||
| 441 | /* Target might have changed stuff. */ | 444 | /* Target might have changed stuff. */ |
| 442 | ip = ip_hdr(skb); | 445 | ip = ip_hdr(skb); |
| 443 | if (verdict == IPT_CONTINUE) | 446 | if (verdict == IPT_CONTINUE) |
| @@ -447,7 +450,9 @@ ipt_do_table(struct sk_buff *skb, | |||
| 447 | break; | 450 | break; |
| 448 | } while (!hotdrop); | 451 | } while (!hotdrop); |
| 449 | xt_info_rdunlock_bh(); | 452 | xt_info_rdunlock_bh(); |
| 450 | 453 | pr_devel("Exiting %s; resetting sp from %u to %u\n", | |
| 454 | __func__, *stackptr, origptr); | ||
| 455 | *stackptr = origptr; | ||
| 451 | #ifdef DEBUG_ALLOW_ALL | 456 | #ifdef DEBUG_ALLOW_ALL |
| 452 | return NF_ACCEPT; | 457 | return NF_ACCEPT; |
| 453 | #else | 458 | #else |
| @@ -455,8 +460,6 @@ ipt_do_table(struct sk_buff *skb, | |||
| 455 | return NF_DROP; | 460 | return NF_DROP; |
| 456 | else return verdict; | 461 | else return verdict; |
| 457 | #endif | 462 | #endif |
| 458 | |||
| 459 | #undef tb_comefrom | ||
| 460 | } | 463 | } |
| 461 | 464 | ||
| 462 | /* Figures out from what hook each rule can be called: returns 0 if | 465 | /* Figures out from what hook each rule can be called: returns 0 if |
| @@ -838,6 +841,9 @@ translate_table(struct net *net, struct xt_table_info *newinfo, void *entry0, | |||
| 838 | if (ret != 0) | 841 | if (ret != 0) |
| 839 | return ret; | 842 | return ret; |
| 840 | ++i; | 843 | ++i; |
| 844 | if (strcmp(ipt_get_target(iter)->u.user.name, | ||
| 845 | XT_ERROR_TARGET) == 0) | ||
| 846 | ++newinfo->stacksize; | ||
| 841 | } | 847 | } |
| 842 | 848 | ||
| 843 | if (i != repl->num_entries) { | 849 | if (i != repl->num_entries) { |
| @@ -2086,8 +2092,7 @@ struct xt_table *ipt_register_table(struct net *net, | |||
| 2086 | { | 2092 | { |
| 2087 | int ret; | 2093 | int ret; |
| 2088 | struct xt_table_info *newinfo; | 2094 | struct xt_table_info *newinfo; |
| 2089 | struct xt_table_info bootstrap | 2095 | struct xt_table_info bootstrap = {0}; |
| 2090 | = { 0, 0, 0, { 0 }, { 0 }, { } }; | ||
| 2091 | void *loc_cpu_entry; | 2096 | void *loc_cpu_entry; |
| 2092 | struct xt_table *new_table; | 2097 | struct xt_table *new_table; |
| 2093 | 2098 | ||
diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c index f2b815e72329..2a2770bcd640 100644 --- a/net/ipv6/netfilter/ip6_tables.c +++ b/net/ipv6/netfilter/ip6_tables.c | |||
| @@ -351,15 +351,14 @@ ip6t_do_table(struct sk_buff *skb, | |||
| 351 | const struct net_device *out, | 351 | const struct net_device *out, |
| 352 | struct xt_table *table) | 352 | struct xt_table *table) |
| 353 | { | 353 | { |
| 354 | #define tb_comefrom ((struct ip6t_entry *)table_base)->comefrom | ||
| 355 | |||
| 356 | static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long)))); | 354 | static const char nulldevname[IFNAMSIZ] __attribute__((aligned(sizeof(long)))); |
| 357 | bool hotdrop = false; | 355 | bool hotdrop = false; |
| 358 | /* Initializing verdict to NF_DROP keeps gcc happy. */ | 356 | /* Initializing verdict to NF_DROP keeps gcc happy. */ |
| 359 | unsigned int verdict = NF_DROP; | 357 | unsigned int verdict = NF_DROP; |
| 360 | const char *indev, *outdev; | 358 | const char *indev, *outdev; |
| 361 | const void *table_base; | 359 | const void *table_base; |
| 362 | struct ip6t_entry *e, *back; | 360 | struct ip6t_entry *e, **jumpstack; |
| 361 | unsigned int *stackptr, origptr, cpu; | ||
| 363 | const struct xt_table_info *private; | 362 | const struct xt_table_info *private; |
| 364 | struct xt_match_param mtpar; | 363 | struct xt_match_param mtpar; |
| 365 | struct xt_target_param tgpar; | 364 | struct xt_target_param tgpar; |
| @@ -383,19 +382,19 @@ ip6t_do_table(struct sk_buff *skb, | |||
| 383 | 382 | ||
| 384 | xt_info_rdlock_bh(); | 383 | xt_info_rdlock_bh(); |
| 385 | private = table->private; | 384 | private = table->private; |
| 386 | table_base = private->entries[smp_processor_id()]; | 385 | cpu = smp_processor_id(); |
| 386 | table_base = private->entries[cpu]; | ||
| 387 | jumpstack = (struct ip6t_entry **)private->jumpstack[cpu]; | ||
| 388 | stackptr = &private->stackptr[cpu]; | ||
| 389 | origptr = *stackptr; | ||
| 387 | 390 | ||
| 388 | e = get_entry(table_base, private->hook_entry[hook]); | 391 | e = get_entry(table_base, private->hook_entry[hook]); |
| 389 | 392 | ||
| 390 | /* For return from builtin chain */ | ||
| 391 | back = get_entry(table_base, private->underflow[hook]); | ||
| 392 | |||
| 393 | do { | 393 | do { |
| 394 | const struct ip6t_entry_target *t; | 394 | const struct ip6t_entry_target *t; |
| 395 | const struct xt_entry_match *ematch; | 395 | const struct xt_entry_match *ematch; |
| 396 | 396 | ||
| 397 | IP_NF_ASSERT(e); | 397 | IP_NF_ASSERT(e); |
| 398 | IP_NF_ASSERT(back); | ||
| 399 | if (!ip6_packet_match(skb, indev, outdev, &e->ipv6, | 398 | if (!ip6_packet_match(skb, indev, outdev, &e->ipv6, |
| 400 | &mtpar.thoff, &mtpar.fragoff, &hotdrop)) { | 399 | &mtpar.thoff, &mtpar.fragoff, &hotdrop)) { |
| 401 | no_match: | 400 | no_match: |
| @@ -432,17 +431,20 @@ ip6t_do_table(struct sk_buff *skb, | |||
| 432 | verdict = (unsigned)(-v) - 1; | 431 | verdict = (unsigned)(-v) - 1; |
| 433 | break; | 432 | break; |
| 434 | } | 433 | } |
| 435 | e = back; | 434 | if (*stackptr == 0) |
| 436 | back = get_entry(table_base, back->comefrom); | 435 | e = get_entry(table_base, |
| 436 | private->underflow[hook]); | ||
| 437 | else | ||
| 438 | e = ip6t_next_entry(jumpstack[--*stackptr]); | ||
| 437 | continue; | 439 | continue; |
| 438 | } | 440 | } |
| 439 | if (table_base + v != ip6t_next_entry(e) && | 441 | if (table_base + v != ip6t_next_entry(e) && |
| 440 | !(e->ipv6.flags & IP6T_F_GOTO)) { | 442 | !(e->ipv6.flags & IP6T_F_GOTO)) { |
| 441 | /* Save old back ptr in next entry */ | 443 | if (*stackptr >= private->stacksize) { |
| 442 | struct ip6t_entry *next = ip6t_next_entry(e); | 444 | verdict = NF_DROP; |
| 443 | next->comefrom = (void *)back - table_base; | 445 | break; |
| 444 | /* set back pointer to next entry */ | 446 | } |
| 445 | back = next; | 447 | jumpstack[(*stackptr)++] = e; |
| 446 | } | 448 | } |
| 447 | 449 | ||
| 448 | e = get_entry(table_base, v); | 450 | e = get_entry(table_base, v); |
| @@ -454,19 +456,7 @@ ip6t_do_table(struct sk_buff *skb, | |||
| 454 | tgpar.target = t->u.kernel.target; | 456 | tgpar.target = t->u.kernel.target; |
| 455 | tgpar.targinfo = t->data; | 457 | tgpar.targinfo = t->data; |
| 456 | 458 | ||
| 457 | #ifdef CONFIG_NETFILTER_DEBUG | ||
| 458 | tb_comefrom = 0xeeeeeeec; | ||
| 459 | #endif | ||
| 460 | verdict = t->u.kernel.target->target(skb, &tgpar); | 459 | verdict = t->u.kernel.target->target(skb, &tgpar); |
| 461 | |||
| 462 | #ifdef CONFIG_NETFILTER_DEBUG | ||
| 463 | if (tb_comefrom != 0xeeeeeeec && verdict == IP6T_CONTINUE) { | ||
| 464 | printk("Target %s reentered!\n", | ||
| 465 | t->u.kernel.target->name); | ||
| 466 | verdict = NF_DROP; | ||
| 467 | } | ||
| 468 | tb_comefrom = 0x57acc001; | ||
| 469 | #endif | ||
| 470 | if (verdict == IP6T_CONTINUE) | 460 | if (verdict == IP6T_CONTINUE) |
| 471 | e = ip6t_next_entry(e); | 461 | e = ip6t_next_entry(e); |
| 472 | else | 462 | else |
| @@ -474,10 +464,8 @@ ip6t_do_table(struct sk_buff *skb, | |||
| 474 | break; | 464 | break; |
| 475 | } while (!hotdrop); | 465 | } while (!hotdrop); |
| 476 | 466 | ||
| 477 | #ifdef CONFIG_NETFILTER_DEBUG | ||
| 478 | tb_comefrom = NETFILTER_LINK_POISON; | ||
| 479 | #endif | ||
| 480 | xt_info_rdunlock_bh(); | 467 | xt_info_rdunlock_bh(); |
| 468 | *stackptr = origptr; | ||
| 481 | 469 | ||
| 482 | #ifdef DEBUG_ALLOW_ALL | 470 | #ifdef DEBUG_ALLOW_ALL |
| 483 | return NF_ACCEPT; | 471 | return NF_ACCEPT; |
| @@ -486,8 +474,6 @@ ip6t_do_table(struct sk_buff *skb, | |||
| 486 | return NF_DROP; | 474 | return NF_DROP; |
| 487 | else return verdict; | 475 | else return verdict; |
| 488 | #endif | 476 | #endif |
| 489 | |||
| 490 | #undef tb_comefrom | ||
| 491 | } | 477 | } |
| 492 | 478 | ||
| 493 | /* Figures out from what hook each rule can be called: returns 0 if | 479 | /* Figures out from what hook each rule can be called: returns 0 if |
| @@ -869,6 +855,9 @@ translate_table(struct net *net, struct xt_table_info *newinfo, void *entry0, | |||
| 869 | if (ret != 0) | 855 | if (ret != 0) |
| 870 | return ret; | 856 | return ret; |
| 871 | ++i; | 857 | ++i; |
| 858 | if (strcmp(ip6t_get_target(iter)->u.user.name, | ||
| 859 | XT_ERROR_TARGET) == 0) | ||
| 860 | ++newinfo->stacksize; | ||
| 872 | } | 861 | } |
| 873 | 862 | ||
| 874 | if (i != repl->num_entries) { | 863 | if (i != repl->num_entries) { |
| @@ -2120,8 +2109,7 @@ struct xt_table *ip6t_register_table(struct net *net, | |||
| 2120 | { | 2109 | { |
| 2121 | int ret; | 2110 | int ret; |
| 2122 | struct xt_table_info *newinfo; | 2111 | struct xt_table_info *newinfo; |
| 2123 | struct xt_table_info bootstrap | 2112 | struct xt_table_info bootstrap = {0}; |
| 2124 | = { 0, 0, 0, { 0 }, { 0 }, { } }; | ||
| 2125 | void *loc_cpu_entry; | 2113 | void *loc_cpu_entry; |
| 2126 | struct xt_table *new_table; | 2114 | struct xt_table *new_table; |
| 2127 | 2115 | ||
diff --git a/net/netfilter/x_tables.c b/net/netfilter/x_tables.c index 8e23d8f68459..edde5c602890 100644 --- a/net/netfilter/x_tables.c +++ b/net/netfilter/x_tables.c | |||
| @@ -62,6 +62,9 @@ static const char *const xt_prefix[NFPROTO_NUMPROTO] = { | |||
| 62 | [NFPROTO_IPV6] = "ip6", | 62 | [NFPROTO_IPV6] = "ip6", |
| 63 | }; | 63 | }; |
| 64 | 64 | ||
| 65 | /* Allow this many total (re)entries. */ | ||
| 66 | static const unsigned int xt_jumpstack_multiplier = 2; | ||
| 67 | |||
| 65 | /* Registration hooks for targets. */ | 68 | /* Registration hooks for targets. */ |
| 66 | int | 69 | int |
| 67 | xt_register_target(struct xt_target *target) | 70 | xt_register_target(struct xt_target *target) |
| @@ -680,6 +683,26 @@ void xt_free_table_info(struct xt_table_info *info) | |||
| 680 | else | 683 | else |
| 681 | vfree(info->entries[cpu]); | 684 | vfree(info->entries[cpu]); |
| 682 | } | 685 | } |
| 686 | |||
| 687 | if (info->jumpstack != NULL) { | ||
| 688 | if (sizeof(void *) * info->stacksize > PAGE_SIZE) { | ||
| 689 | for_each_possible_cpu(cpu) | ||
| 690 | vfree(info->jumpstack[cpu]); | ||
| 691 | } else { | ||
| 692 | for_each_possible_cpu(cpu) | ||
| 693 | kfree(info->jumpstack[cpu]); | ||
| 694 | } | ||
| 695 | } | ||
| 696 | |||
| 697 | if (sizeof(void **) * nr_cpu_ids > PAGE_SIZE) | ||
| 698 | vfree(info->jumpstack); | ||
| 699 | else | ||
| 700 | kfree(info->jumpstack); | ||
| 701 | if (sizeof(unsigned int) * nr_cpu_ids > PAGE_SIZE) | ||
| 702 | vfree(info->stackptr); | ||
| 703 | else | ||
| 704 | kfree(info->stackptr); | ||
| 705 | |||
| 683 | kfree(info); | 706 | kfree(info); |
| 684 | } | 707 | } |
| 685 | EXPORT_SYMBOL(xt_free_table_info); | 708 | EXPORT_SYMBOL(xt_free_table_info); |
| @@ -724,6 +747,49 @@ EXPORT_SYMBOL_GPL(xt_compat_unlock); | |||
| 724 | DEFINE_PER_CPU(struct xt_info_lock, xt_info_locks); | 747 | DEFINE_PER_CPU(struct xt_info_lock, xt_info_locks); |
| 725 | EXPORT_PER_CPU_SYMBOL_GPL(xt_info_locks); | 748 | EXPORT_PER_CPU_SYMBOL_GPL(xt_info_locks); |
| 726 | 749 | ||
| 750 | static int xt_jumpstack_alloc(struct xt_table_info *i) | ||
| 751 | { | ||
| 752 | unsigned int size; | ||
| 753 | int cpu; | ||
| 754 | |||
| 755 | size = sizeof(unsigned int) * nr_cpu_ids; | ||
| 756 | if (size > PAGE_SIZE) | ||
| 757 | i->stackptr = vmalloc(size); | ||
| 758 | else | ||
| 759 | i->stackptr = kmalloc(size, GFP_KERNEL); | ||
| 760 | if (i->stackptr == NULL) | ||
| 761 | return -ENOMEM; | ||
| 762 | memset(i->stackptr, 0, size); | ||
| 763 | |||
| 764 | size = sizeof(void **) * nr_cpu_ids; | ||
| 765 | if (size > PAGE_SIZE) | ||
| 766 | i->jumpstack = vmalloc(size); | ||
| 767 | else | ||
| 768 | i->jumpstack = kmalloc(size, GFP_KERNEL); | ||
| 769 | if (i->jumpstack == NULL) | ||
| 770 | return -ENOMEM; | ||
| 771 | memset(i->jumpstack, 0, size); | ||
| 772 | |||
| 773 | i->stacksize *= xt_jumpstack_multiplier; | ||
| 774 | size = sizeof(void *) * i->stacksize; | ||
| 775 | for_each_possible_cpu(cpu) { | ||
| 776 | if (size > PAGE_SIZE) | ||
| 777 | i->jumpstack[cpu] = vmalloc_node(size, | ||
| 778 | cpu_to_node(cpu)); | ||
| 779 | else | ||
| 780 | i->jumpstack[cpu] = kmalloc_node(size, | ||
| 781 | GFP_KERNEL, cpu_to_node(cpu)); | ||
| 782 | if (i->jumpstack[cpu] == NULL) | ||
| 783 | /* | ||
| 784 | * Freeing will be done later on by the callers. The | ||
| 785 | * chain is: xt_replace_table -> __do_replace -> | ||
| 786 | * do_replace -> xt_free_table_info. | ||
| 787 | */ | ||
| 788 | return -ENOMEM; | ||
| 789 | } | ||
| 790 | |||
| 791 | return 0; | ||
| 792 | } | ||
| 727 | 793 | ||
| 728 | struct xt_table_info * | 794 | struct xt_table_info * |
| 729 | xt_replace_table(struct xt_table *table, | 795 | xt_replace_table(struct xt_table *table, |
| @@ -732,6 +798,7 @@ xt_replace_table(struct xt_table *table, | |||
| 732 | int *error) | 798 | int *error) |
| 733 | { | 799 | { |
| 734 | struct xt_table_info *private; | 800 | struct xt_table_info *private; |
| 801 | int ret; | ||
| 735 | 802 | ||
| 736 | /* Do the substitution. */ | 803 | /* Do the substitution. */ |
| 737 | local_bh_disable(); | 804 | local_bh_disable(); |
| @@ -746,6 +813,12 @@ xt_replace_table(struct xt_table *table, | |||
| 746 | return NULL; | 813 | return NULL; |
| 747 | } | 814 | } |
| 748 | 815 | ||
| 816 | ret = xt_jumpstack_alloc(newinfo); | ||
| 817 | if (ret < 0) { | ||
| 818 | *error = ret; | ||
| 819 | return NULL; | ||
| 820 | } | ||
| 821 | |||
| 749 | table->private = newinfo; | 822 | table->private = newinfo; |
| 750 | newinfo->initial_entries = private->initial_entries; | 823 | newinfo->initial_entries = private->initial_entries; |
| 751 | 824 | ||
| @@ -770,6 +843,10 @@ struct xt_table *xt_register_table(struct net *net, | |||
| 770 | struct xt_table_info *private; | 843 | struct xt_table_info *private; |
| 771 | struct xt_table *t, *table; | 844 | struct xt_table *t, *table; |
| 772 | 845 | ||
| 846 | ret = xt_jumpstack_alloc(newinfo); | ||
| 847 | if (ret < 0) | ||
| 848 | return ERR_PTR(ret); | ||
| 849 | |||
| 773 | /* Don't add one object to multiple lists. */ | 850 | /* Don't add one object to multiple lists. */ |
| 774 | table = kmemdup(input_table, sizeof(struct xt_table), GFP_KERNEL); | 851 | table = kmemdup(input_table, sizeof(struct xt_table), GFP_KERNEL); |
| 775 | if (!table) { | 852 | if (!table) { |
