Skip to content

Commit d69f93d

Browse files
Florian Westphalgregkh
authored andcommitted
netfilter: x_tables: do compat validation via translate_table
commit 09d9686047dbbe1cf4faa558d3ecc4aae2046054 upstream. This looks like refactoring, but its also a bug fix. Problem is that the compat path (32bit iptables, 64bit kernel) lacks a few sanity tests that are done in the normal path. For example, we do not check for underflows and the base chain policies. While its possible to also add such checks to the compat path, its more copy&pastry, for instance we cannot reuse check_underflow() helper as e->target_offset differs in the compat case. Other problem is that it makes auditing for validation errors harder; two places need to be checked and kept in sync. At a high level 32 bit compat works like this: 1- initial pass over blob: validate match/entry offsets, bounds checking lookup all matches and targets do bookkeeping wrt. size delta of 32/64bit structures assign match/target.u.kernel pointer (points at kernel implementation, needed to access ->compatsize etc.) 2- allocate memory according to the total bookkeeping size to contain the translated ruleset 3- second pass over original blob: for each entry, copy the 32bit representation to the newly allocated memory. This also does any special match translations (e.g. adjust 32bit to 64bit longs, etc). 4- check if ruleset is free of loops (chase all jumps) 5-first pass over translated blob: call the checkentry function of all matches and targets. The alternative implemented by this patch is to drop steps 3&4 from the compat process, the translation is changed into an intermediate step rather than a full 1:1 translate_table replacement. In the 2nd pass (step #3), change the 64bit ruleset back to a kernel representation, i.e. put() the kernel pointer and restore ->u.user.name . This gets us a 64bit ruleset that is in the format generated by a 64bit iptables userspace -- we can then use translate_table() to get the 'native' sanity checks. This has two drawbacks: 1. we re-validate all the match and target entry structure sizes even though compat translation is supposed to never generate bogus offsets. 2. we put and then re-lookup each match and target. THe upside is that we get all sanity tests and ruleset validations provided by the normal path and can remove some duplicated compat code. iptables-restore time of autogenerated ruleset with 300k chains of form -A CHAIN0001 -m limit --limit 1/s -j CHAIN0002 -A CHAIN0002 -m limit --limit 1/s -j CHAIN0003 shows no noticeable differences in restore times: old: 0m30.796s new: 0m31.521s 64bit: 0m25.674s Signed-off-by: Florian Westphal <fw@strlen.de> Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 3a69c0f commit d69f93d

4 files changed

Lines changed: 83 additions & 342 deletions

File tree

net/ipv4/netfilter/arp_tables.c

Lines changed: 23 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,19 +1233,17 @@ static inline void compat_release_entry(struct compat_arpt_entry *e)
12331233
module_put(t->u.kernel.target->me);
12341234
}
12351235

1236-
static inline int
1236+
static int
12371237
check_compat_entry_size_and_hooks(struct compat_arpt_entry *e,
12381238
struct xt_table_info *newinfo,
12391239
unsigned int *size,
12401240
const unsigned char *base,
1241-
const unsigned char *limit,
1242-
const unsigned int *hook_entries,
1243-
const unsigned int *underflows)
1241+
const unsigned char *limit)
12441242
{
12451243
struct xt_entry_target *t;
12461244
struct xt_target *target;
12471245
unsigned int entry_offset;
1248-
int ret, off, h;
1246+
int ret, off;
12491247

12501248
duprintf("check_compat_entry_size_and_hooks %p\n", e);
12511249
if ((unsigned long)e % __alignof__(struct compat_arpt_entry) != 0 ||
@@ -1290,17 +1288,6 @@ check_compat_entry_size_and_hooks(struct compat_arpt_entry *e,
12901288
if (ret)
12911289
goto release_target;
12921290

1293-
/* Check hooks & underflows */
1294-
for (h = 0; h < NF_ARP_NUMHOOKS; h++) {
1295-
if ((unsigned char *)e - base == hook_entries[h])
1296-
newinfo->hook_entry[h] = hook_entries[h];
1297-
if ((unsigned char *)e - base == underflows[h])
1298-
newinfo->underflow[h] = underflows[h];
1299-
}
1300-
1301-
/* Clear counters and comefrom */
1302-
memset(&e->counters, 0, sizeof(e->counters));
1303-
e->comefrom = 0;
13041291
return 0;
13051292

13061293
release_target:
@@ -1350,7 +1337,7 @@ static int translate_compat_table(struct xt_table_info **pinfo,
13501337
struct xt_table_info *newinfo, *info;
13511338
void *pos, *entry0, *entry1;
13521339
struct compat_arpt_entry *iter0;
1353-
struct arpt_entry *iter1;
1340+
struct arpt_replace repl;
13541341
unsigned int size;
13551342
int ret = 0;
13561343

@@ -1359,12 +1346,6 @@ static int translate_compat_table(struct xt_table_info **pinfo,
13591346
size = compatr->size;
13601347
info->number = compatr->num_entries;
13611348

1362-
/* Init all hooks to impossible value. */
1363-
for (i = 0; i < NF_ARP_NUMHOOKS; i++) {
1364-
info->hook_entry[i] = 0xFFFFFFFF;
1365-
info->underflow[i] = 0xFFFFFFFF;
1366-
}
1367-
13681349
duprintf("translate_compat_table: size %u\n", info->size);
13691350
j = 0;
13701351
xt_compat_lock(NFPROTO_ARP);
@@ -1373,9 +1354,7 @@ static int translate_compat_table(struct xt_table_info **pinfo,
13731354
xt_entry_foreach(iter0, entry0, compatr->size) {
13741355
ret = check_compat_entry_size_and_hooks(iter0, info, &size,
13751356
entry0,
1376-
entry0 + compatr->size,
1377-
compatr->hook_entry,
1378-
compatr->underflow);
1357+
entry0 + compatr->size);
13791358
if (ret != 0)
13801359
goto out_unlock;
13811360
++j;
@@ -1388,23 +1367,6 @@ static int translate_compat_table(struct xt_table_info **pinfo,
13881367
goto out_unlock;
13891368
}
13901369

1391-
/* Check hooks all assigned */
1392-
for (i = 0; i < NF_ARP_NUMHOOKS; i++) {
1393-
/* Only hooks which are valid */
1394-
if (!(compatr->valid_hooks & (1 << i)))
1395-
continue;
1396-
if (info->hook_entry[i] == 0xFFFFFFFF) {
1397-
duprintf("Invalid hook entry %u %u\n",
1398-
i, info->hook_entry[i]);
1399-
goto out_unlock;
1400-
}
1401-
if (info->underflow[i] == 0xFFFFFFFF) {
1402-
duprintf("Invalid underflow %u %u\n",
1403-
i, info->underflow[i]);
1404-
goto out_unlock;
1405-
}
1406-
}
1407-
14081370
ret = -ENOMEM;
14091371
newinfo = xt_alloc_table_info(size);
14101372
if (!newinfo)
@@ -1421,73 +1383,43 @@ static int translate_compat_table(struct xt_table_info **pinfo,
14211383
xt_entry_foreach(iter0, entry0, compatr->size)
14221384
compat_copy_entry_from_user(iter0, &pos, &size,
14231385
newinfo, entry1);
1386+
1387+
/* all module references in entry0 are now gone */
1388+
14241389
xt_compat_flush_offsets(NFPROTO_ARP);
14251390
xt_compat_unlock(NFPROTO_ARP);
14261391

1427-
ret = -ELOOP;
1428-
if (!mark_source_chains(newinfo, compatr->valid_hooks, entry1))
1429-
goto free_newinfo;
1430-
1431-
i = 0;
1432-
xt_entry_foreach(iter1, entry1, newinfo->size) {
1433-
iter1->counters.pcnt = xt_percpu_counter_alloc();
1434-
if (IS_ERR_VALUE(iter1->counters.pcnt)) {
1435-
ret = -ENOMEM;
1436-
break;
1437-
}
1392+
memcpy(&repl, compatr, sizeof(*compatr));
14381393

1439-
ret = check_target(iter1, compatr->name);
1440-
if (ret != 0) {
1441-
xt_percpu_counter_free(iter1->counters.pcnt);
1442-
break;
1443-
}
1444-
++i;
1445-
if (strcmp(arpt_get_target(iter1)->u.user.name,
1446-
XT_ERROR_TARGET) == 0)
1447-
++newinfo->stacksize;
1448-
}
1449-
if (ret) {
1450-
/*
1451-
* The first i matches need cleanup_entry (calls ->destroy)
1452-
* because they had called ->check already. The other j-i
1453-
* entries need only release.
1454-
*/
1455-
int skip = i;
1456-
j -= i;
1457-
xt_entry_foreach(iter0, entry0, newinfo->size) {
1458-
if (skip-- > 0)
1459-
continue;
1460-
if (j-- == 0)
1461-
break;
1462-
compat_release_entry(iter0);
1463-
}
1464-
xt_entry_foreach(iter1, entry1, newinfo->size) {
1465-
if (i-- == 0)
1466-
break;
1467-
cleanup_entry(iter1);
1468-
}
1469-
xt_free_table_info(newinfo);
1470-
return ret;
1394+
for (i = 0; i < NF_ARP_NUMHOOKS; i++) {
1395+
repl.hook_entry[i] = newinfo->hook_entry[i];
1396+
repl.underflow[i] = newinfo->underflow[i];
14711397
}
14721398

1399+
repl.num_counters = 0;
1400+
repl.counters = NULL;
1401+
repl.size = newinfo->size;
1402+
ret = translate_table(newinfo, entry1, &repl);
1403+
if (ret)
1404+
goto free_newinfo;
1405+
14731406
*pinfo = newinfo;
14741407
*pentry0 = entry1;
14751408
xt_free_table_info(info);
14761409
return 0;
14771410

14781411
free_newinfo:
14791412
xt_free_table_info(newinfo);
1480-
out:
1413+
return ret;
1414+
out_unlock:
1415+
xt_compat_flush_offsets(NFPROTO_ARP);
1416+
xt_compat_unlock(NFPROTO_ARP);
14811417
xt_entry_foreach(iter0, entry0, compatr->size) {
14821418
if (j-- == 0)
14831419
break;
14841420
compat_release_entry(iter0);
14851421
}
14861422
return ret;
1487-
out_unlock:
1488-
xt_compat_flush_offsets(NFPROTO_ARP);
1489-
xt_compat_unlock(NFPROTO_ARP);
1490-
goto out;
14911423
}
14921424

14931425
static int compat_do_replace(struct net *net, void __user *user,

0 commit comments

Comments
 (0)