xt_recent can try high order page allocations and this can fail. iptables: page allocation failure: order:9, mode:0xc0d0 It also wastes about half the allocated space because of kmalloc() power-of-two roundups and struct recent_table layout. Use vmalloc() instead to save space and be less prone to allocation errors when memory is fragmented.
Unified · Split
| @@ -29,6 +29,7 @@ · 28 unmodified lines | ||
| 29 | 29 | #include <linux/skbuff.h> |
| 30 | 30 | #include <linux/inet.h> |
| 31 | 31 | #include <linux/slab.h> |
| 32 | +#include <linux/vmalloc.h> | |
| 32 | 33 | #include <net/net_namespace.h> |
| 33 | 34 | #include <net/netns/generic.h> |
| 34 | 35 | |
| @@ -310,6 +311,14 @@ out: · 275 unmodified lines | ||
| 310 | 311 | return ret; |
| 311 | 312 | } |
| 312 | 313 | |
| 314 | +static void recent_table_free(void *addr) | |
| 315 | +{ | |
| 316 | + if (is_vmalloc_addr(addr)) | |
| 317 | + vfree(addr); | |
| 318 | + else | |
| 319 | + kfree(addr); | |
| 320 | +} | |
| 321 | + | |
| 313 | 322 | static int recent_mt_check(const struct xt_mtchk_param *par, |
| 314 | 323 | const struct xt_recent_mtinfo_v1 *info) |
| 315 | 324 | { |
| @@ -322,6 +331,7 @@ static int recent_mt_check(const struct xt_mtchk_param *par, · 6 unmodified lines | ||
| 322 | 331 | #endif |
| 323 | 332 | unsigned int i; |
| 324 | 333 | int ret = -EINVAL; |
| 334 | + size_t sz; | |
| 325 | 335 | |
| 326 | 336 | if (unlikely(!hash_rnd_inited)) { |
| 327 | 337 | get_random_bytes(&hash_rnd, sizeof(hash_rnd)); |
| @@ -360,8 +370,11 @@ static int recent_mt_check(const struct xt_mtchk_param *par, · 32 unmodified lines | ||
| 360 | 370 | goto out; |
| 361 | 371 | } |
| 362 | 372 | |
| 363 | − t = kzalloc(sizeof(*t) + sizeof(t->iphash[0]) * ip_list_hash_size, | |
| 364 | − GFP_KERNEL); | |
| 373 | + sz = sizeof(*t) + sizeof(t->iphash[0]) * ip_list_hash_size; | |
| 374 | + if (sz <= PAGE_SIZE) | |
| 375 | + t = kzalloc(sz, GFP_KERNEL); | |
| 376 | + else | |
| 377 | + t = vzalloc(sz); | |
| 365 | 378 | if (t == NULL) { |
| 366 | 379 | ret = -ENOMEM; |
| 367 | 380 | goto out; |
| @@ -377,14 +390,14 @@ static int recent_mt_check(const struct xt_mtchk_param *par, · 9 unmodified lines | ||
| 377 | 390 | uid = make_kuid(&init_user_ns, ip_list_uid); |
| 378 | 391 | gid = make_kgid(&init_user_ns, ip_list_gid); |
| 379 | 392 | if (!uid_valid(uid) || !gid_valid(gid)) { |
| 380 | − kfree(t); | |
| 393 | + recent_table_free(t); | |
| 381 | 394 | ret = -EINVAL; |
| 382 | 395 | goto out; |
| 383 | 396 | } |
| 384 | 397 | pde = proc_create_data(t->name, ip_list_perms, recent_net->xt_recent, |
| 385 | 398 | &recent_mt_fops, t); |
| 386 | 399 | if (pde == NULL) { |
| 387 | − kfree(t); | |
| 400 | + recent_table_free(t); | |
| 388 | 401 | ret = -ENOMEM; |
| 389 | 402 | goto out; |
| 390 | 403 | } |
| @@ -435,7 +448,7 @@ static void recent_mt_destroy(const struct xt_mtdtor_param *par) · 44 unmodified lines | ||
| 435 | 448 | remove_proc_entry(t->name, recent_net->xt_recent); |
| 436 | 449 | #endif |
| 437 | 450 | recent_table_flush(t); |
| 438 | − kfree(t); | |
| 451 | + recent_table_free(t); | |
| 439 | 452 | } |
| 440 | 453 | mutex_unlock(&recent_mutex); |
| 441 | 454 | } |