Commit 5c398dc8f5a58b5417d8ae0d474704feb6e12a12
Committed by
David S. Miller
1 parent
7f8a688e1e
Exists in
master
and in
39 other branches
netlink: fix netlink_change_ngroups()
commit 6c04bb18ddd633 (netlink: use call_rcu for netlink_change_ngroups) used a somewhat convoluted and racy way to perform call_rcu(). The old block of memory is freed after a grace period, but the rcu_head used to track it is located in new block. This can clash if we call two times or more netlink_change_ngroups(), and a block is freed before another. call_rcu() called on different cpus makes no guarantee in order of callbacks. Fix this using a more standard way of handling this : Each block of memory contains its own rcu_head, so that no 'use after free' can happens. Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com> CC: Johannes Berg <johannes@sipsolutions.net> CC: Paul E. McKenney <paulmck@linux.vnet.ibm.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Showing 1 changed file with 24 additions and 41 deletions Side-by-side Diff
net/netlink/af_netlink.c
... | ... | @@ -83,9 +83,9 @@ |
83 | 83 | struct module *module; |
84 | 84 | }; |
85 | 85 | |
86 | -struct listeners_rcu_head { | |
87 | - struct rcu_head rcu_head; | |
88 | - void *ptr; | |
86 | +struct listeners { | |
87 | + struct rcu_head rcu; | |
88 | + unsigned long masks[0]; | |
89 | 89 | }; |
90 | 90 | |
91 | 91 | #define NETLINK_KERNEL_SOCKET 0x1 |
... | ... | @@ -119,7 +119,7 @@ |
119 | 119 | struct netlink_table { |
120 | 120 | struct nl_pid_hash hash; |
121 | 121 | struct hlist_head mc_list; |
122 | - unsigned long *listeners; | |
122 | + struct listeners __rcu *listeners; | |
123 | 123 | unsigned int nl_nonroot; |
124 | 124 | unsigned int groups; |
125 | 125 | struct mutex *cb_mutex; |
... | ... | @@ -338,7 +338,7 @@ |
338 | 338 | if (i < NLGRPLONGS(nlk_sk(sk)->ngroups)) |
339 | 339 | mask |= nlk_sk(sk)->groups[i]; |
340 | 340 | } |
341 | - tbl->listeners[i] = mask; | |
341 | + tbl->listeners->masks[i] = mask; | |
342 | 342 | } |
343 | 343 | /* this function is only called with the netlink table "grabbed", which |
344 | 344 | * makes sure updates are visible before bind or setsockopt return. */ |
... | ... | @@ -936,7 +936,7 @@ |
936 | 936 | int netlink_has_listeners(struct sock *sk, unsigned int group) |
937 | 937 | { |
938 | 938 | int res = 0; |
939 | - unsigned long *listeners; | |
939 | + struct listeners *listeners; | |
940 | 940 | |
941 | 941 | BUG_ON(!netlink_is_kernel(sk)); |
942 | 942 | |
... | ... | @@ -944,7 +944,7 @@ |
944 | 944 | listeners = rcu_dereference(nl_table[sk->sk_protocol].listeners); |
945 | 945 | |
946 | 946 | if (group - 1 < nl_table[sk->sk_protocol].groups) |
947 | - res = test_bit(group - 1, listeners); | |
947 | + res = test_bit(group - 1, listeners->masks); | |
948 | 948 | |
949 | 949 | rcu_read_unlock(); |
950 | 950 | |
... | ... | @@ -1498,7 +1498,7 @@ |
1498 | 1498 | struct socket *sock; |
1499 | 1499 | struct sock *sk; |
1500 | 1500 | struct netlink_sock *nlk; |
1501 | - unsigned long *listeners = NULL; | |
1501 | + struct listeners *listeners = NULL; | |
1502 | 1502 | |
1503 | 1503 | BUG_ON(!nl_table); |
1504 | 1504 | |
... | ... | @@ -1523,8 +1523,7 @@ |
1523 | 1523 | if (groups < 32) |
1524 | 1524 | groups = 32; |
1525 | 1525 | |
1526 | - listeners = kzalloc(NLGRPSZ(groups) + sizeof(struct listeners_rcu_head), | |
1527 | - GFP_KERNEL); | |
1526 | + listeners = kzalloc(sizeof(*listeners) + NLGRPSZ(groups), GFP_KERNEL); | |
1528 | 1527 | if (!listeners) |
1529 | 1528 | goto out_sock_release; |
1530 | 1529 | |
... | ... | @@ -1541,7 +1540,7 @@ |
1541 | 1540 | netlink_table_grab(); |
1542 | 1541 | if (!nl_table[unit].registered) { |
1543 | 1542 | nl_table[unit].groups = groups; |
1544 | - nl_table[unit].listeners = listeners; | |
1543 | + rcu_assign_pointer(nl_table[unit].listeners, listeners); | |
1545 | 1544 | nl_table[unit].cb_mutex = cb_mutex; |
1546 | 1545 | nl_table[unit].module = module; |
1547 | 1546 | nl_table[unit].registered = 1; |
1548 | 1547 | |
1549 | 1548 | |
1550 | 1549 | |
1551 | 1550 | |
... | ... | @@ -1572,43 +1571,28 @@ |
1572 | 1571 | EXPORT_SYMBOL(netlink_kernel_release); |
1573 | 1572 | |
1574 | 1573 | |
1575 | -static void netlink_free_old_listeners(struct rcu_head *rcu_head) | |
1574 | +static void listeners_free_rcu(struct rcu_head *head) | |
1576 | 1575 | { |
1577 | - struct listeners_rcu_head *lrh; | |
1578 | - | |
1579 | - lrh = container_of(rcu_head, struct listeners_rcu_head, rcu_head); | |
1580 | - kfree(lrh->ptr); | |
1576 | + kfree(container_of(head, struct listeners, rcu)); | |
1581 | 1577 | } |
1582 | 1578 | |
1583 | 1579 | int __netlink_change_ngroups(struct sock *sk, unsigned int groups) |
1584 | 1580 | { |
1585 | - unsigned long *listeners, *old = NULL; | |
1586 | - struct listeners_rcu_head *old_rcu_head; | |
1581 | + struct listeners *new, *old; | |
1587 | 1582 | struct netlink_table *tbl = &nl_table[sk->sk_protocol]; |
1588 | 1583 | |
1589 | 1584 | if (groups < 32) |
1590 | 1585 | groups = 32; |
1591 | 1586 | |
1592 | 1587 | if (NLGRPSZ(tbl->groups) < NLGRPSZ(groups)) { |
1593 | - listeners = kzalloc(NLGRPSZ(groups) + | |
1594 | - sizeof(struct listeners_rcu_head), | |
1595 | - GFP_ATOMIC); | |
1596 | - if (!listeners) | |
1588 | + new = kzalloc(sizeof(*new) + NLGRPSZ(groups), GFP_ATOMIC); | |
1589 | + if (!new) | |
1597 | 1590 | return -ENOMEM; |
1598 | - old = tbl->listeners; | |
1599 | - memcpy(listeners, old, NLGRPSZ(tbl->groups)); | |
1600 | - rcu_assign_pointer(tbl->listeners, listeners); | |
1601 | - /* | |
1602 | - * Free the old memory after an RCU grace period so we | |
1603 | - * don't leak it. We use call_rcu() here in order to be | |
1604 | - * able to call this function from atomic contexts. The | |
1605 | - * allocation of this memory will have reserved enough | |
1606 | - * space for struct listeners_rcu_head at the end. | |
1607 | - */ | |
1608 | - old_rcu_head = (void *)(tbl->listeners + | |
1609 | - NLGRPLONGS(tbl->groups)); | |
1610 | - old_rcu_head->ptr = old; | |
1611 | - call_rcu(&old_rcu_head->rcu_head, netlink_free_old_listeners); | |
1591 | + old = rcu_dereference_raw(tbl->listeners); | |
1592 | + memcpy(new->masks, old->masks, NLGRPSZ(tbl->groups)); | |
1593 | + rcu_assign_pointer(tbl->listeners, new); | |
1594 | + | |
1595 | + call_rcu(&old->rcu, listeners_free_rcu); | |
1612 | 1596 | } |
1613 | 1597 | tbl->groups = groups; |
1614 | 1598 | |
1615 | 1599 | |
1616 | 1600 | |
1617 | 1601 | |
... | ... | @@ -2104,18 +2088,17 @@ |
2104 | 2088 | |
2105 | 2089 | static void __init netlink_add_usersock_entry(void) |
2106 | 2090 | { |
2107 | - unsigned long *listeners; | |
2091 | + struct listeners *listeners; | |
2108 | 2092 | int groups = 32; |
2109 | 2093 | |
2110 | - listeners = kzalloc(NLGRPSZ(groups) + sizeof(struct listeners_rcu_head), | |
2111 | - GFP_KERNEL); | |
2094 | + listeners = kzalloc(sizeof(*listeners) + NLGRPSZ(groups), GFP_KERNEL); | |
2112 | 2095 | if (!listeners) |
2113 | - panic("netlink_add_usersock_entry: Cannot allocate listneres\n"); | |
2096 | + panic("netlink_add_usersock_entry: Cannot allocate listeners\n"); | |
2114 | 2097 | |
2115 | 2098 | netlink_table_grab(); |
2116 | 2099 | |
2117 | 2100 | nl_table[NETLINK_USERSOCK].groups = groups; |
2118 | - nl_table[NETLINK_USERSOCK].listeners = listeners; | |
2101 | + rcu_assign_pointer(nl_table[NETLINK_USERSOCK].listeners, listeners); | |
2119 | 2102 | nl_table[NETLINK_USERSOCK].module = THIS_MODULE; |
2120 | 2103 | nl_table[NETLINK_USERSOCK].registered = 1; |
2121 | 2104 |