Skip to content

Commit afae708

Browse files
Florian Westphalgtpitch
authored andcommitted
netfilter: x_tables: do compat validation via translate_table
commit 09d9686 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 Grarak#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 <[email protected]> Signed-off-by: Pablo Neira Ayuso <[email protected]> Signed-off-by: Willy Tarreau <[email protected]>
1 parent eb27166 commit afae708

File tree

4 files changed

+80
-333
lines changed

4 files changed

+80
-333
lines changed

net/ipv4/netfilter/arp_tables.c

Lines changed: 22 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,19 +1211,17 @@ static inline void compat_release_entry(struct compat_arpt_entry *e)
12111211
module_put(t->u.kernel.target->me);
12121212
}
12131213

1214-
static inline int
1214+
static int
12151215
check_compat_entry_size_and_hooks(struct compat_arpt_entry *e,
12161216
struct xt_table_info *newinfo,
12171217
unsigned int *size,
12181218
const unsigned char *base,
1219-
const unsigned char *limit,
1220-
const unsigned int *hook_entries,
1221-
const unsigned int *underflows)
1219+
const unsigned char *limit)
12221220
{
12231221
struct xt_entry_target *t;
12241222
struct xt_target *target;
12251223
unsigned int entry_offset;
1226-
int ret, off, h;
1224+
int ret, off;
12271225

12281226
duprintf("check_compat_entry_size_and_hooks %p\n", e);
12291227
if ((unsigned long)e % __alignof__(struct compat_arpt_entry) != 0 ||
@@ -1268,17 +1266,6 @@ check_compat_entry_size_and_hooks(struct compat_arpt_entry *e,
12681266
if (ret)
12691267
goto release_target;
12701268

1271-
/* Check hooks & underflows */
1272-
for (h = 0; h < NF_ARP_NUMHOOKS; h++) {
1273-
if ((unsigned char *)e - base == hook_entries[h])
1274-
newinfo->hook_entry[h] = hook_entries[h];
1275-
if ((unsigned char *)e - base == underflows[h])
1276-
newinfo->underflow[h] = underflows[h];
1277-
}
1278-
1279-
/* Clear counters and comefrom */
1280-
memset(&e->counters, 0, sizeof(e->counters));
1281-
e->comefrom = 0;
12821269
return 0;
12831270

12841271
release_target:
@@ -1328,7 +1315,7 @@ static int translate_compat_table(struct xt_table_info **pinfo,
13281315
struct xt_table_info *newinfo, *info;
13291316
void *pos, *entry0, *entry1;
13301317
struct compat_arpt_entry *iter0;
1331-
struct arpt_entry *iter1;
1318+
struct arpt_replace repl;
13321319
unsigned int size;
13331320
int ret = 0;
13341321

@@ -1337,12 +1324,6 @@ static int translate_compat_table(struct xt_table_info **pinfo,
13371324
size = compatr->size;
13381325
info->number = compatr->num_entries;
13391326

1340-
/* Init all hooks to impossible value. */
1341-
for (i = 0; i < NF_ARP_NUMHOOKS; i++) {
1342-
info->hook_entry[i] = 0xFFFFFFFF;
1343-
info->underflow[i] = 0xFFFFFFFF;
1344-
}
1345-
13461327
duprintf("translate_compat_table: size %u\n", info->size);
13471328
j = 0;
13481329
xt_compat_lock(NFPROTO_ARP);
@@ -1351,9 +1332,7 @@ static int translate_compat_table(struct xt_table_info **pinfo,
13511332
xt_entry_foreach(iter0, entry0, compatr->size) {
13521333
ret = check_compat_entry_size_and_hooks(iter0, info, &size,
13531334
entry0,
1354-
entry0 + compatr->size,
1355-
compatr->hook_entry,
1356-
compatr->underflow);
1335+
entry0 + compatr->size);
13571336
if (ret != 0)
13581337
goto out_unlock;
13591338
++j;
@@ -1366,23 +1345,6 @@ static int translate_compat_table(struct xt_table_info **pinfo,
13661345
goto out_unlock;
13671346
}
13681347

1369-
/* Check hooks all assigned */
1370-
for (i = 0; i < NF_ARP_NUMHOOKS; i++) {
1371-
/* Only hooks which are valid */
1372-
if (!(compatr->valid_hooks & (1 << i)))
1373-
continue;
1374-
if (info->hook_entry[i] == 0xFFFFFFFF) {
1375-
duprintf("Invalid hook entry %u %u\n",
1376-
i, info->hook_entry[i]);
1377-
goto out_unlock;
1378-
}
1379-
if (info->underflow[i] == 0xFFFFFFFF) {
1380-
duprintf("Invalid underflow %u %u\n",
1381-
i, info->underflow[i]);
1382-
goto out_unlock;
1383-
}
1384-
}
1385-
13861348
ret = -ENOMEM;
13871349
newinfo = xt_alloc_table_info(size);
13881350
if (!newinfo)
@@ -1399,51 +1361,25 @@ static int translate_compat_table(struct xt_table_info **pinfo,
13991361
xt_entry_foreach(iter0, entry0, compatr->size)
14001362
compat_copy_entry_from_user(iter0, &pos, &size,
14011363
newinfo, entry1);
1364+
1365+
/* all module references in entry0 are now gone */
1366+
14021367
xt_compat_flush_offsets(NFPROTO_ARP);
14031368
xt_compat_unlock(NFPROTO_ARP);
14041369

1405-
ret = -ELOOP;
1406-
if (!mark_source_chains(newinfo, compatr->valid_hooks, entry1))
1407-
goto free_newinfo;
1370+
memcpy(&repl, compatr, sizeof(*compatr));
14081371

1409-
i = 0;
1410-
xt_entry_foreach(iter1, entry1, newinfo->size) {
1411-
ret = check_target(iter1, compatr->name);
1412-
if (ret != 0)
1413-
break;
1414-
++i;
1415-
if (strcmp(arpt_get_target(iter1)->u.user.name,
1416-
XT_ERROR_TARGET) == 0)
1417-
++newinfo->stacksize;
1418-
}
1419-
if (ret) {
1420-
/*
1421-
* The first i matches need cleanup_entry (calls ->destroy)
1422-
* because they had called ->check already. The other j-i
1423-
* entries need only release.
1424-
*/
1425-
int skip = i;
1426-
j -= i;
1427-
xt_entry_foreach(iter0, entry0, newinfo->size) {
1428-
if (skip-- > 0)
1429-
continue;
1430-
if (j-- == 0)
1431-
break;
1432-
compat_release_entry(iter0);
1433-
}
1434-
xt_entry_foreach(iter1, entry1, newinfo->size) {
1435-
if (i-- == 0)
1436-
break;
1437-
cleanup_entry(iter1);
1438-
}
1439-
xt_free_table_info(newinfo);
1440-
return ret;
1372+
for (i = 0; i < NF_ARP_NUMHOOKS; i++) {
1373+
repl.hook_entry[i] = newinfo->hook_entry[i];
1374+
repl.underflow[i] = newinfo->underflow[i];
14411375
}
14421376

1443-
/* And one copy for every other CPU */
1444-
for_each_possible_cpu(i)
1445-
if (newinfo->entries[i] && newinfo->entries[i] != entry1)
1446-
memcpy(newinfo->entries[i], entry1, newinfo->size);
1377+
repl.num_counters = 0;
1378+
repl.counters = NULL;
1379+
repl.size = newinfo->size;
1380+
ret = translate_table(newinfo, entry1, &repl);
1381+
if (ret)
1382+
goto free_newinfo;
14471383

14481384
*pinfo = newinfo;
14491385
*pentry0 = entry1;
@@ -1452,17 +1388,16 @@ static int translate_compat_table(struct xt_table_info **pinfo,
14521388

14531389
free_newinfo:
14541390
xt_free_table_info(newinfo);
1455-
out:
1391+
return ret;
1392+
out_unlock:
1393+
xt_compat_flush_offsets(NFPROTO_ARP);
1394+
xt_compat_unlock(NFPROTO_ARP);
14561395
xt_entry_foreach(iter0, entry0, compatr->size) {
14571396
if (j-- == 0)
14581397
break;
14591398
compat_release_entry(iter0);
14601399
}
14611400
return ret;
1462-
out_unlock:
1463-
xt_compat_flush_offsets(NFPROTO_ARP);
1464-
xt_compat_unlock(NFPROTO_ARP);
1465-
goto out;
14661401
}
14671402

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

0 commit comments

Comments
 (0)