diff options
author | Dmitry Mishin <dim@openvz.org> | 2007-06-05 15:56:09 -0400 |
---|---|---|
committer | David S. Miller <davem@sunset.davemloft.net> | 2007-06-07 16:40:32 -0400 |
commit | 4c1b52bc7a2f5ee01ea3fc248a8748a1c6843f7c (patch) | |
tree | 4176d06988ff65fa3c5d060642523093a88c7a64 | |
parent | 3c158f7f57601bc27eab82f0dc4fd3fad314d845 (diff) |
[NETFILTER]: ip_tables: fix compat related crash
check_compat_entry_size_and_hooks iterates over the matches and calls
compat_check_calc_match, which loads the match and calculates the
compat offsets, but unlike the non-compat version, doesn't call
->checkentry yet. On error however it calls cleanup_matches, which in
turn calls ->destroy, which can result in crashes if the destroy
function (validly) expects to only get called after the checkentry
function.
Add a compat_release_match function that only drops the module reference
on error and rename compat_check_calc_match to compat_find_calc_match to
reflect the fact that it doesn't call the checkentry function.
Reported by Jan Engelhardt <jengelh@linux01.gwdg.de>
Signed-off-by: Dmitry Mishin <dim@openvz.org>
Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/linux/netfilter_ipv4/ip_tables.h | 20 | ||||
-rw-r--r-- | net/ipv4/netfilter/ip_tables.c | 81 |
2 files changed, 83 insertions, 18 deletions
diff --git a/include/linux/netfilter_ipv4/ip_tables.h b/include/linux/netfilter_ipv4/ip_tables.h index 2f46dd728ee1..e992cd6b28f5 100644 --- a/include/linux/netfilter_ipv4/ip_tables.h +++ b/include/linux/netfilter_ipv4/ip_tables.h | |||
@@ -264,6 +264,26 @@ ipt_get_target(struct ipt_entry *e) | |||
264 | __ret; \ | 264 | __ret; \ |
265 | }) | 265 | }) |
266 | 266 | ||
267 | /* fn returns 0 to continue iteration */ | ||
268 | #define IPT_ENTRY_ITERATE_CONTINUE(entries, size, n, fn, args...) \ | ||
269 | ({ \ | ||
270 | unsigned int __i, __n; \ | ||
271 | int __ret = 0; \ | ||
272 | struct ipt_entry *__entry; \ | ||
273 | \ | ||
274 | for (__i = 0, __n = 0; __i < (size); \ | ||
275 | __i += __entry->next_offset, __n++) { \ | ||
276 | __entry = (void *)(entries) + __i; \ | ||
277 | if (__n < n) \ | ||
278 | continue; \ | ||
279 | \ | ||
280 | __ret = fn(__entry , ## args); \ | ||
281 | if (__ret != 0) \ | ||
282 | break; \ | ||
283 | } \ | ||
284 | __ret; \ | ||
285 | }) | ||
286 | |||
267 | /* | 287 | /* |
268 | * Main firewall chains definitions and global var's definitions. | 288 | * Main firewall chains definitions and global var's definitions. |
269 | */ | 289 | */ |
diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c index e3f83bf160d9..9bacf1a03630 100644 --- a/net/ipv4/netfilter/ip_tables.c +++ b/net/ipv4/netfilter/ip_tables.c | |||
@@ -499,7 +499,8 @@ check_entry(struct ipt_entry *e, const char *name) | |||
499 | } | 499 | } |
500 | 500 | ||
501 | static inline int check_match(struct ipt_entry_match *m, const char *name, | 501 | static inline int check_match(struct ipt_entry_match *m, const char *name, |
502 | const struct ipt_ip *ip, unsigned int hookmask) | 502 | const struct ipt_ip *ip, unsigned int hookmask, |
503 | unsigned int *i) | ||
503 | { | 504 | { |
504 | struct xt_match *match; | 505 | struct xt_match *match; |
505 | int ret; | 506 | int ret; |
@@ -515,6 +516,8 @@ static inline int check_match(struct ipt_entry_match *m, const char *name, | |||
515 | m->u.kernel.match->name); | 516 | m->u.kernel.match->name); |
516 | ret = -EINVAL; | 517 | ret = -EINVAL; |
517 | } | 518 | } |
519 | if (!ret) | ||
520 | (*i)++; | ||
518 | return ret; | 521 | return ret; |
519 | } | 522 | } |
520 | 523 | ||
@@ -537,11 +540,10 @@ find_check_match(struct ipt_entry_match *m, | |||
537 | } | 540 | } |
538 | m->u.kernel.match = match; | 541 | m->u.kernel.match = match; |
539 | 542 | ||
540 | ret = check_match(m, name, ip, hookmask); | 543 | ret = check_match(m, name, ip, hookmask, i); |
541 | if (ret) | 544 | if (ret) |
542 | goto err; | 545 | goto err; |
543 | 546 | ||
544 | (*i)++; | ||
545 | return 0; | 547 | return 0; |
546 | err: | 548 | err: |
547 | module_put(m->u.kernel.match->me); | 549 | module_put(m->u.kernel.match->me); |
@@ -1425,7 +1427,7 @@ out: | |||
1425 | } | 1427 | } |
1426 | 1428 | ||
1427 | static inline int | 1429 | static inline int |
1428 | compat_check_calc_match(struct ipt_entry_match *m, | 1430 | compat_find_calc_match(struct ipt_entry_match *m, |
1429 | const char *name, | 1431 | const char *name, |
1430 | const struct ipt_ip *ip, | 1432 | const struct ipt_ip *ip, |
1431 | unsigned int hookmask, | 1433 | unsigned int hookmask, |
@@ -1449,6 +1451,31 @@ compat_check_calc_match(struct ipt_entry_match *m, | |||
1449 | } | 1451 | } |
1450 | 1452 | ||
1451 | static inline int | 1453 | static inline int |
1454 | compat_release_match(struct ipt_entry_match *m, unsigned int *i) | ||
1455 | { | ||
1456 | if (i && (*i)-- == 0) | ||
1457 | return 1; | ||
1458 | |||
1459 | module_put(m->u.kernel.match->me); | ||
1460 | return 0; | ||
1461 | } | ||
1462 | |||
1463 | static inline int | ||
1464 | compat_release_entry(struct ipt_entry *e, unsigned int *i) | ||
1465 | { | ||
1466 | struct ipt_entry_target *t; | ||
1467 | |||
1468 | if (i && (*i)-- == 0) | ||
1469 | return 1; | ||
1470 | |||
1471 | /* Cleanup all matches */ | ||
1472 | IPT_MATCH_ITERATE(e, compat_release_match, NULL); | ||
1473 | t = ipt_get_target(e); | ||
1474 | module_put(t->u.kernel.target->me); | ||
1475 | return 0; | ||
1476 | } | ||
1477 | |||
1478 | static inline int | ||
1452 | check_compat_entry_size_and_hooks(struct ipt_entry *e, | 1479 | check_compat_entry_size_and_hooks(struct ipt_entry *e, |
1453 | struct xt_table_info *newinfo, | 1480 | struct xt_table_info *newinfo, |
1454 | unsigned int *size, | 1481 | unsigned int *size, |
@@ -1485,10 +1512,10 @@ check_compat_entry_size_and_hooks(struct ipt_entry *e, | |||
1485 | off = 0; | 1512 | off = 0; |
1486 | entry_offset = (void *)e - (void *)base; | 1513 | entry_offset = (void *)e - (void *)base; |
1487 | j = 0; | 1514 | j = 0; |
1488 | ret = IPT_MATCH_ITERATE(e, compat_check_calc_match, name, &e->ip, | 1515 | ret = IPT_MATCH_ITERATE(e, compat_find_calc_match, name, &e->ip, |
1489 | e->comefrom, &off, &j); | 1516 | e->comefrom, &off, &j); |
1490 | if (ret != 0) | 1517 | if (ret != 0) |
1491 | goto cleanup_matches; | 1518 | goto release_matches; |
1492 | 1519 | ||
1493 | t = ipt_get_target(e); | 1520 | t = ipt_get_target(e); |
1494 | target = try_then_request_module(xt_find_target(AF_INET, | 1521 | target = try_then_request_module(xt_find_target(AF_INET, |
@@ -1499,7 +1526,7 @@ check_compat_entry_size_and_hooks(struct ipt_entry *e, | |||
1499 | duprintf("check_compat_entry_size_and_hooks: `%s' not found\n", | 1526 | duprintf("check_compat_entry_size_and_hooks: `%s' not found\n", |
1500 | t->u.user.name); | 1527 | t->u.user.name); |
1501 | ret = target ? PTR_ERR(target) : -ENOENT; | 1528 | ret = target ? PTR_ERR(target) : -ENOENT; |
1502 | goto cleanup_matches; | 1529 | goto release_matches; |
1503 | } | 1530 | } |
1504 | t->u.kernel.target = target; | 1531 | t->u.kernel.target = target; |
1505 | 1532 | ||
@@ -1526,8 +1553,8 @@ check_compat_entry_size_and_hooks(struct ipt_entry *e, | |||
1526 | 1553 | ||
1527 | out: | 1554 | out: |
1528 | module_put(t->u.kernel.target->me); | 1555 | module_put(t->u.kernel.target->me); |
1529 | cleanup_matches: | 1556 | release_matches: |
1530 | IPT_MATCH_ITERATE(e, cleanup_match, &j); | 1557 | IPT_MATCH_ITERATE(e, compat_release_match, &j); |
1531 | return ret; | 1558 | return ret; |
1532 | } | 1559 | } |
1533 | 1560 | ||
@@ -1574,15 +1601,26 @@ static int compat_copy_entry_from_user(struct ipt_entry *e, void **dstptr, | |||
1574 | return ret; | 1601 | return ret; |
1575 | } | 1602 | } |
1576 | 1603 | ||
1577 | static inline int compat_check_entry(struct ipt_entry *e, const char *name) | 1604 | static inline int compat_check_entry(struct ipt_entry *e, const char *name, |
1605 | unsigned int *i) | ||
1578 | { | 1606 | { |
1579 | int ret; | 1607 | int j, ret; |
1580 | 1608 | ||
1581 | ret = IPT_MATCH_ITERATE(e, check_match, name, &e->ip, e->comefrom); | 1609 | j = 0; |
1610 | ret = IPT_MATCH_ITERATE(e, check_match, name, &e->ip, e->comefrom, &j); | ||
1582 | if (ret) | 1611 | if (ret) |
1583 | return ret; | 1612 | goto cleanup_matches; |
1613 | |||
1614 | ret = check_target(e, name); | ||
1615 | if (ret) | ||
1616 | goto cleanup_matches; | ||
1584 | 1617 | ||
1585 | return check_target(e, name); | 1618 | (*i)++; |
1619 | return 0; | ||
1620 | |||
1621 | cleanup_matches: | ||
1622 | IPT_MATCH_ITERATE(e, cleanup_match, &j); | ||
1623 | return ret; | ||
1586 | } | 1624 | } |
1587 | 1625 | ||
1588 | static int | 1626 | static int |
@@ -1673,10 +1711,17 @@ translate_compat_table(const char *name, | |||
1673 | if (!mark_source_chains(newinfo, valid_hooks, entry1)) | 1711 | if (!mark_source_chains(newinfo, valid_hooks, entry1)) |
1674 | goto free_newinfo; | 1712 | goto free_newinfo; |
1675 | 1713 | ||
1714 | i = 0; | ||
1676 | ret = IPT_ENTRY_ITERATE(entry1, newinfo->size, compat_check_entry, | 1715 | ret = IPT_ENTRY_ITERATE(entry1, newinfo->size, compat_check_entry, |
1677 | name); | 1716 | name, &i); |
1678 | if (ret) | 1717 | if (ret) { |
1679 | goto free_newinfo; | 1718 | j -= i; |
1719 | IPT_ENTRY_ITERATE_CONTINUE(entry1, newinfo->size, i, | ||
1720 | compat_release_entry, &j); | ||
1721 | IPT_ENTRY_ITERATE(entry1, newinfo->size, cleanup_entry, &i); | ||
1722 | xt_free_table_info(newinfo); | ||
1723 | return ret; | ||
1724 | } | ||
1680 | 1725 | ||
1681 | /* And one copy for every other CPU */ | 1726 | /* And one copy for every other CPU */ |
1682 | for_each_possible_cpu(i) | 1727 | for_each_possible_cpu(i) |
@@ -1691,7 +1736,7 @@ translate_compat_table(const char *name, | |||
1691 | free_newinfo: | 1736 | free_newinfo: |
1692 | xt_free_table_info(newinfo); | 1737 | xt_free_table_info(newinfo); |
1693 | out: | 1738 | out: |
1694 | IPT_ENTRY_ITERATE(entry0, total_size, cleanup_entry, &j); | 1739 | IPT_ENTRY_ITERATE(entry0, total_size, compat_release_entry, &j); |
1695 | return ret; | 1740 | return ret; |
1696 | out_unlock: | 1741 | out_unlock: |
1697 | compat_flush_offsets(); | 1742 | compat_flush_offsets(); |