History

netfilter: x_tables: Switch synchronization to RCU

When running concurrent iptables rules replacement with data, the per CPU sequence count is checked after the assignment of the new information. The sequence count is used to synchronize with the packet path without the use of any explicit locking. If there are any packets in the packet path using the table information, the sequence count is incremented to an odd value and is incremented to an even after the packet process completion. The new table value assignment is followed by a write memory barrier so every CPU should see the latest value. If the packet path has started with the old table information, the sequence counter will be odd and the iptables replacement will wait till the sequence count is even prior to freeing the old table info. However, this assumes that the new table information assignment and the memory barrier is actually executed prior to the counter check in the replacement thread. If CPU decides to execute the assignment later as there is no user of the table information prior to the sequence check, the packet path in another CPU may use the old table information. The replacement thread would then free the table information under it leading to a use after free in the packet processing context- Unable to handle kernel NULL pointer dereference at virtual address 000000000000008e pc : ip6t_do_table+0x5d0/0x89c lr : ip6t_do_table+0x5b8/0x89c ip6t_do_table+0x5d0/0x89c ip6table_filter_hook+0x24/0x30 nf_hook_slow+0x84/0x120 ip6_input+0x74/0xe0 ip6_rcv_finish+0x7c/0x128 ipv6_rcv+0xac/0xe4 __netif_receive_skb+0x84/0x17c process_backlog+0x15c/0x1b8 napi_poll+0x88/0x284 net_rx_action+0xbc/0x23c __do_softirq+0x20c/0x48c This could be fixed by forcing instruction order after the new table information assignment or by switching to RCU for the synchronization.

Subash Abhinov Kasiviswanathan committed 5 years ago

Commit cc00bca1 parent 819f56b5 files · +40 −56Browse files

Unified · Split

5 files
  1. include/linux/netfilter/x_tables.h +4 −1
  2. net/ipv4/netfilter/arp_tables.c +7 −7
  3. net/ipv4/netfilter/ip_tables.c +7 −7
  4. net/ipv6/netfilter/ip6_tables.c +7 −7
  5. net/netfilter/x_tables.c +15 −34
include/linux/netfilter/x_tables.h+4−1
@@ -227,7 +227,7 @@ struct xt_table { · 226 unmodified lines
227227 unsigned int valid_hooks;
228228
229229 /* Man behind the curtain... */
230− struct xt_table_info *private;
230+ struct xt_table_info __rcu *private;
231231
232232 /* Set this to THIS_MODULE if you are a module, otherwise NULL */
233233 struct module *me;
@@ -448,6 +448,9 @@ xt_get_per_cpu_counter(struct xt_counters *cnt, unsigned int cpu) · 214 unmodified lines
448448
449449 struct nf_hook_ops *xt_hook_ops_alloc(const struct xt_table *, nf_hookfn *);
450450
451+struct xt_table_info
452+*xt_table_get_private_protected(const struct xt_table *table);
453+
451454 #ifdef CONFIG_COMPAT
452455 #include <net/compat.h>
453456
net/ipv4/netfilter/arp_tables.c+7−7
@@ -203,7 +203,7 @@ unsigned int arpt_do_table(struct sk_buff *skb, · 202 unmodified lines
203203
204204 local_bh_disable();
205205 addend = xt_write_recseq_begin();
206− private = READ_ONCE(table->private); /* Address dependency. */
206+ private = rcu_access_pointer(table->private);
207207 cpu = smp_processor_id();
208208 table_base = private->entries;
209209 jumpstack = (struct arpt_entry **)private->jumpstack[cpu];
@@ -649,7 +649,7 @@ static struct xt_counters *alloc_counters(const struct xt_table *table) · 439 unmodified lines
649649 {
650650 unsigned int countersize;
651651 struct xt_counters *counters;
652− const struct xt_table_info *private = table->private;
652+ const struct xt_table_info *private = xt_table_get_private_protected(table);
653653
654654 /* We need atomic snapshot of counters: rest doesn't change
655655 * (other than comefrom, which userspace doesn't care
@@ -673,7 +673,7 @@ static int copy_entries_to_user(unsigned int total_size, · 17 unmodified lines
673673 unsigned int off, num;
674674 const struct arpt_entry *e;
675675 struct xt_counters *counters;
676− struct xt_table_info *private = table->private;
676+ struct xt_table_info *private = xt_table_get_private_protected(table);
677677 int ret = 0;
678678 void *loc_cpu_entry;
679679
@@ -807,7 +807,7 @@ static int get_info(struct net *net, void __user *user, const int *len) · 127 unmodified lines
807807 t = xt_request_find_table_lock(net, NFPROTO_ARP, name);
808808 if (!IS_ERR(t)) {
809809 struct arpt_getinfo info;
810− const struct xt_table_info *private = t->private;
810+ const struct xt_table_info *private = xt_table_get_private_protected(t);
811811 #ifdef CONFIG_COMPAT
812812 struct xt_table_info tmp;
813813
@@ -860,7 +860,7 @@ static int get_entries(struct net *net, struct arpt_get_entries __user *uptr, · 46 unmodified lines
860860
861861 t = xt_find_table_lock(net, NFPROTO_ARP, get.name);
862862 if (!IS_ERR(t)) {
863− const struct xt_table_info *private = t->private;
863+ const struct xt_table_info *private = xt_table_get_private_protected(t);
864864
865865 if (get.size == private->size)
866866 ret = copy_entries_to_user(private->size,
@@ -1017,7 +1017,7 @@ static int do_add_counters(struct net *net, sockptr_t arg, unsigned int len) · 150 unmodified lines
10171017 }
10181018
10191019 local_bh_disable();
1020− private = t->private;
1020+ private = xt_table_get_private_protected(t);
10211021 if (private->number != tmp.num_counters) {
10221022 ret = -EINVAL;
10231023 goto unlock_up_free;
@@ -1330,7 +1330,7 @@ static int compat_copy_entries_to_user(unsigned int total_size, · 306 unmodified lines
13301330 void __user *userptr)
13311331 {
13321332 struct xt_counters *counters;
1333− const struct xt_table_info *private = table->private;
1333+ const struct xt_table_info *private = xt_table_get_private_protected(table);
13341334 void __user *pos;
13351335 unsigned int size;
13361336 int ret = 0;
net/ipv4/netfilter/ip_tables.c+7−7
@@ -258,7 +258,7 @@ ipt_do_table(struct sk_buff *skb, · 257 unmodified lines
258258 WARN_ON(!(table->valid_hooks & (1 << hook)));
259259 local_bh_disable();
260260 addend = xt_write_recseq_begin();
261− private = READ_ONCE(table->private); /* Address dependency. */
261+ private = rcu_access_pointer(table->private);
262262 cpu = smp_processor_id();
263263 table_base = private->entries;
264264 jumpstack = (struct ipt_entry **)private->jumpstack[cpu];
@@ -791,7 +791,7 @@ static struct xt_counters *alloc_counters(const struct xt_table *table) · 526 unmodified lines
791791 {
792792 unsigned int countersize;
793793 struct xt_counters *counters;
794− const struct xt_table_info *private = table->private;
794+ const struct xt_table_info *private = xt_table_get_private_protected(table);
795795
796796 /* We need atomic snapshot of counters: rest doesn't change
797797 (other than comefrom, which userspace doesn't care
@@ -815,7 +815,7 @@ copy_entries_to_user(unsigned int total_size, · 17 unmodified lines
815815 unsigned int off, num;
816816 const struct ipt_entry *e;
817817 struct xt_counters *counters;
818− const struct xt_table_info *private = table->private;
818+ const struct xt_table_info *private = xt_table_get_private_protected(table);
819819 int ret = 0;
820820 const void *loc_cpu_entry;
821821
@@ -964,7 +964,7 @@ static int get_info(struct net *net, void __user *user, const int *len) · 142 unmodified lines
964964 t = xt_request_find_table_lock(net, AF_INET, name);
965965 if (!IS_ERR(t)) {
966966 struct ipt_getinfo info;
967− const struct xt_table_info *private = t->private;
967+ const struct xt_table_info *private = xt_table_get_private_protected(t);
968968 #ifdef CONFIG_COMPAT
969969 struct xt_table_info tmp;
970970
@@ -1018,7 +1018,7 @@ get_entries(struct net *net, struct ipt_get_entries __user *uptr, · 47 unmodified lines
10181018
10191019 t = xt_find_table_lock(net, AF_INET, get.name);
10201020 if (!IS_ERR(t)) {
1021− const struct xt_table_info *private = t->private;
1021+ const struct xt_table_info *private = xt_table_get_private_protected(t);
10221022 if (get.size == private->size)
10231023 ret = copy_entries_to_user(private->size,
10241024 t, uptr->entrytable);
@@ -1173,7 +1173,7 @@ do_add_counters(struct net *net, sockptr_t arg, unsigned int len) · 148 unmodified lines
11731173 }
11741174
11751175 local_bh_disable();
1176− private = t->private;
1176+ private = xt_table_get_private_protected(t);
11771177 if (private->number != tmp.num_counters) {
11781178 ret = -EINVAL;
11791179 goto unlock_up_free;
@@ -1543,7 +1543,7 @@ compat_copy_entries_to_user(unsigned int total_size, struct xt_table *table, · 363 unmodified lines
15431543 void __user *userptr)
15441544 {
15451545 struct xt_counters *counters;
1546− const struct xt_table_info *private = table->private;
1546+ const struct xt_table_info *private = xt_table_get_private_protected(table);
15471547 void __user *pos;
15481548 unsigned int size;
15491549 int ret = 0;
net/ipv6/netfilter/ip6_tables.c+7−7
@@ -280,7 +280,7 @@ ip6t_do_table(struct sk_buff *skb, · 279 unmodified lines
280280
281281 local_bh_disable();
282282 addend = xt_write_recseq_begin();
283− private = READ_ONCE(table->private); /* Address dependency. */
283+ private = rcu_access_pointer(table->private);
284284 cpu = smp_processor_id();
285285 table_base = private->entries;
286286 jumpstack = (struct ip6t_entry **)private->jumpstack[cpu];
@@ -807,7 +807,7 @@ static struct xt_counters *alloc_counters(const struct xt_table *table) · 520 unmodified lines
807807 {
808808 unsigned int countersize;
809809 struct xt_counters *counters;
810− const struct xt_table_info *private = table->private;
810+ const struct xt_table_info *private = xt_table_get_private_protected(table);
811811
812812 /* We need atomic snapshot of counters: rest doesn't change
813813 (other than comefrom, which userspace doesn't care
@@ -831,7 +831,7 @@ copy_entries_to_user(unsigned int total_size, · 17 unmodified lines
831831 unsigned int off, num;
832832 const struct ip6t_entry *e;
833833 struct xt_counters *counters;
834− const struct xt_table_info *private = table->private;
834+ const struct xt_table_info *private = xt_table_get_private_protected(table);
835835 int ret = 0;
836836 const void *loc_cpu_entry;
837837
@@ -980,7 +980,7 @@ static int get_info(struct net *net, void __user *user, const int *len) · 142 unmodified lines
980980 t = xt_request_find_table_lock(net, AF_INET6, name);
981981 if (!IS_ERR(t)) {
982982 struct ip6t_getinfo info;
983− const struct xt_table_info *private = t->private;
983+ const struct xt_table_info *private = xt_table_get_private_protected(t);
984984 #ifdef CONFIG_COMPAT
985985 struct xt_table_info tmp;
986986
@@ -1035,7 +1035,7 @@ get_entries(struct net *net, struct ip6t_get_entries __user *uptr, · 48 unmodified lines
10351035
10361036 t = xt_find_table_lock(net, AF_INET6, get.name);
10371037 if (!IS_ERR(t)) {
1038− struct xt_table_info *private = t->private;
1038+ struct xt_table_info *private = xt_table_get_private_protected(t);
10391039 if (get.size == private->size)
10401040 ret = copy_entries_to_user(private->size,
10411041 t, uptr->entrytable);
@@ -1189,7 +1189,7 @@ do_add_counters(struct net *net, sockptr_t arg, unsigned int len) · 147 unmodified lines
11891189 }
11901190
11911191 local_bh_disable();
1192− private = t->private;
1192+ private = xt_table_get_private_protected(t);
11931193 if (private->number != tmp.num_counters) {
11941194 ret = -EINVAL;
11951195 goto unlock_up_free;
@@ -1552,7 +1552,7 @@ compat_copy_entries_to_user(unsigned int total_size, struct xt_table *table, · 356 unmodified lines
15521552 void __user *userptr)
15531553 {
15541554 struct xt_counters *counters;
1555− const struct xt_table_info *private = table->private;
1555+ const struct xt_table_info *private = xt_table_get_private_protected(table);
15561556 void __user *pos;
15571557 unsigned int size;
15581558 int ret = 0;
net/netfilter/x_tables.c+15−34
@@ -1349,6 +1349,14 @@ struct xt_counters *xt_counters_alloc(unsigned int counters) · 1348 unmodified lines
13491349 }
13501350 EXPORT_SYMBOL(xt_counters_alloc);
13511351
1352+struct xt_table_info
1353+*xt_table_get_private_protected(const struct xt_table *table)
1354+{
1355+ return rcu_dereference_protected(table->private,
1356+ mutex_is_locked(&xt[table->af].mutex));
1357+}
1358+EXPORT_SYMBOL(xt_table_get_private_protected);
1359+
13521360 struct xt_table_info *
13531361 xt_replace_table(struct xt_table *table,
13541362 unsigned int num_counters,
@@ -1356,7 +1364,6 @@ xt_replace_table(struct xt_table *table, · 1 unmodified line
13561364 int *error)
13571365 {
13581366 struct xt_table_info *private;
1359− unsigned int cpu;
13601367 int ret;
13611368
13621369 ret = xt_jumpstack_alloc(newinfo);
@@ -1366,47 +1373,20 @@ xt_replace_table(struct xt_table *table, · 3 unmodified lines
13661373 }
13671374
13681375 /* Do the substitution. */
1369− local_bh_disable();
1370− private = table->private;
1376+ private = xt_table_get_private_protected(table);
13711377
13721378 /* Check inside lock: is the old number correct? */
13731379 if (num_counters != private->number) {
13741380 pr_debug("num_counters != table->private->number (%u/%u)\n",
13751381 num_counters, private->number);
1376− local_bh_enable();
13771382 *error = -EAGAIN;
13781383 return NULL;
13791384 }
13801385
13811386 newinfo->initial_entries = private->initial_entries;
1382− /*
1383− * Ensure contents of newinfo are visible before assigning to
1384− * private.
1385− */
1386− smp_wmb();
1387− table->private = newinfo;
1388−
1389− /* make sure all cpus see new ->private value */
1390− smp_wmb();
13911387
1392− /*
1393− * Even though table entries have now been swapped, other CPU's
1394− * may still be using the old entries...
1395− */
1396− local_bh_enable();
1397−
1398− /* ... so wait for even xt_recseq on all cpus */
1399− for_each_possible_cpu(cpu) {
1400− seqcount_t *s = &per_cpu(xt_recseq, cpu);
1401− u32 seq = raw_read_seqcount(s);
1402−
1403− if (seq & 1) {
1404− do {
1405− cond_resched();
1406− cpu_relax();
1407− } while (seq == raw_read_seqcount(s));
1408− }
1409− }
1388+ rcu_assign_pointer(table->private, newinfo);
1389+ synchronize_rcu();
14101390
14111391 audit_log_nfcfg(table->name, table->af, private->number,
14121392 !private->number ? AUDIT_XT_OP_REGISTER :
@@ -1442,12 +1422,12 @@ struct xt_table *xt_register_table(struct net *net, · 29 unmodified lines
14421422 }
14431423
14441424 /* Simplifies replace_table code. */
1445− table->private = bootstrap;
1425+ rcu_assign_pointer(table->private, bootstrap);
14461426
14471427 if (!xt_replace_table(table, 0, newinfo, &ret))
14481428 goto unlock;
14491429
1450− private = table->private;
1430+ private = xt_table_get_private_protected(table);
14511431 pr_debug("table->private->number = %u\n", private->number);
14521432
14531433 /* save number of initial entries */
@@ -1470,7 +1450,8 @@ void *xt_unregister_table(struct xt_table *table) · 16 unmodified lines
14701450 struct xt_table_info *private;
14711451
14721452 mutex_lock(&xt[table->af].mutex);
1473− private = table->private;
1453+ private = xt_table_get_private_protected(table);
1454+ RCU_INIT_POINTER(table->private, NULL);
14741455 list_del(&table->list);
14751456 mutex_unlock(&xt[table->af].mutex);
14761457 audit_log_nfcfg(table->name, table->af, private->number,