Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

netfilter: ebtables: reject blobs that don't provide all entry points

Harshit Mogalapalli says:
In ebt_do_table() function dereferencing 'private->hook_entry[hook]'
can lead to NULL pointer dereference. [..] Kernel panic:

general protection fault, probably for non-canonical address 0xdffffc0000000005: 0000 [#1] PREEMPT SMP KASAN
KASAN: null-ptr-deref in range [0x0000000000000028-0x000000000000002f]
[..]
RIP: 0010:ebt_do_table+0x1dc/0x1ce0
Code: 89 fa 48 c1 ea 03 80 3c 02 00 0f 85 5c 16 00 00 48 b8 00 00 00 00 00 fc ff df 49 8b 6c df 08 48 8d 7d 2c 48 89 fa 48 c1 ea 03 <0f> b6 14 02 48 89 f8 83 e0 07 83 c0 03 38 d0 7c 08 84 d2 0f 85 88
[..]
Call Trace:
nf_hook_slow+0xb1/0x170
__br_forward+0x289/0x730
maybe_deliver+0x24b/0x380
br_flood+0xc6/0x390
br_dev_xmit+0xa2e/0x12c0

For some reason ebtables rejects blobs that provide entry points that are
not supported by the table, but what it should instead reject is the
opposite: blobs that DO NOT provide an entry point supported by the table.

t->valid_hooks is the bitmask of hooks (input, forward ...) that will see
packets. Providing an entry point that is not support is harmless
(never called/used), but the inverse isn't: it results in a crash
because the ebtables traverser doesn't expect a NULL blob for a location
its receiving packets for.

Instead of fixing all the individual checks, do what iptables is doing and
reject all blobs that differ from the expected hooks.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Reported-by: Harshit Mogalapalli <harshit.m.mogalapalli@oracle.com>
Reported-by: syzkaller <syzkaller@googlegroups.com>
Signed-off-by: Florian Westphal <fw@strlen.de>

+1 -35
-4
include/linux/netfilter_bridge/ebtables.h
··· 94 94 struct ebt_replace_kernel *table; 95 95 unsigned int valid_hooks; 96 96 rwlock_t lock; 97 - /* e.g. could be the table explicitly only allows certain 98 - * matches, targets, ... 0 == let it in */ 99 - int (*check)(const struct ebt_table_info *info, 100 - unsigned int valid_hooks); 101 97 /* the data used by the kernel */ 102 98 struct ebt_table_info *private; 103 99 struct nf_hook_ops *ops;
-8
net/bridge/netfilter/ebtable_broute.c
··· 36 36 .entries = (char *)&initial_chain, 37 37 }; 38 38 39 - static int check(const struct ebt_table_info *info, unsigned int valid_hooks) 40 - { 41 - if (valid_hooks & ~(1 << NF_BR_BROUTING)) 42 - return -EINVAL; 43 - return 0; 44 - } 45 - 46 39 static const struct ebt_table broute_table = { 47 40 .name = "broute", 48 41 .table = &initial_table, 49 42 .valid_hooks = 1 << NF_BR_BROUTING, 50 - .check = check, 51 43 .me = THIS_MODULE, 52 44 }; 53 45
-8
net/bridge/netfilter/ebtable_filter.c
··· 43 43 .entries = (char *)initial_chains, 44 44 }; 45 45 46 - static int check(const struct ebt_table_info *info, unsigned int valid_hooks) 47 - { 48 - if (valid_hooks & ~FILTER_VALID_HOOKS) 49 - return -EINVAL; 50 - return 0; 51 - } 52 - 53 46 static const struct ebt_table frame_filter = { 54 47 .name = "filter", 55 48 .table = &initial_table, 56 49 .valid_hooks = FILTER_VALID_HOOKS, 57 - .check = check, 58 50 .me = THIS_MODULE, 59 51 }; 60 52
-8
net/bridge/netfilter/ebtable_nat.c
··· 43 43 .entries = (char *)initial_chains, 44 44 }; 45 45 46 - static int check(const struct ebt_table_info *info, unsigned int valid_hooks) 47 - { 48 - if (valid_hooks & ~NAT_VALID_HOOKS) 49 - return -EINVAL; 50 - return 0; 51 - } 52 - 53 46 static const struct ebt_table frame_nat = { 54 47 .name = "nat", 55 48 .table = &initial_table, 56 49 .valid_hooks = NAT_VALID_HOOKS, 57 - .check = check, 58 50 .me = THIS_MODULE, 59 51 }; 60 52
+1 -7
net/bridge/netfilter/ebtables.c
··· 1040 1040 goto free_iterate; 1041 1041 } 1042 1042 1043 - /* the table doesn't like it */ 1044 - if (t->check && (ret = t->check(newinfo, repl->valid_hooks))) 1043 + if (repl->valid_hooks != t->valid_hooks) 1045 1044 goto free_unlock; 1046 1045 1047 1046 if (repl->num_counters && repl->num_counters != t->private->nentries) { ··· 1229 1230 ret = translate_table(net, repl->name, newinfo); 1230 1231 if (ret != 0) 1231 1232 goto free_chainstack; 1232 - 1233 - if (table->check && table->check(newinfo, table->valid_hooks)) { 1234 - ret = -EINVAL; 1235 - goto free_chainstack; 1236 - } 1237 1233 1238 1234 table->private = newinfo; 1239 1235 rwlock_init(&table->lock);