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

net/sched: act_ife: Fix metalist update behavior

Whenever an ife action replace changes the metalist, instead of
replacing the old data on the metalist, the current ife code is appending
the new metadata. Aside from being innapropriate behavior, this may lead
to an unbounded addition of metadata to the metalist which might cause an
out of bounds error when running the encode op:

[ 138.423369][ C1] ==================================================================
[ 138.424317][ C1] BUG: KASAN: slab-out-of-bounds in ife_tlv_meta_encode (net/ife/ife.c:168)
[ 138.424906][ C1] Write of size 4 at addr ffff8880077f4ffe by task ife_out_out_bou/255
[ 138.425778][ C1] CPU: 1 UID: 0 PID: 255 Comm: ife_out_out_bou Not tainted 7.0.0-rc1-00169-gfbdfa8da05b6 #624 PREEMPT(full)
[ 138.425795][ C1] Hardware name: Bochs Bochs, BIOS Bochs 01/01/2011
[ 138.425800][ C1] Call Trace:
[ 138.425804][ C1] <IRQ>
[ 138.425808][ C1] dump_stack_lvl (lib/dump_stack.c:122)
[ 138.425828][ C1] print_report (mm/kasan/report.c:379 mm/kasan/report.c:482)
[ 138.425839][ C1] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:221)
[ 138.425844][ C1] ? __virt_addr_valid (./arch/x86/include/asm/preempt.h:95 (discriminator 1) ./include/linux/rcupdate.h:975 (discriminator 1) ./include/linux/mmzone.h:2207 (discriminator 1) arch/x86/mm/physaddr.c:54 (discriminator 1))
[ 138.425853][ C1] ? ife_tlv_meta_encode (net/ife/ife.c:168)
[ 138.425859][ C1] kasan_report (mm/kasan/report.c:221 mm/kasan/report.c:597)
[ 138.425868][ C1] ? ife_tlv_meta_encode (net/ife/ife.c:168)
[ 138.425878][ C1] kasan_check_range (mm/kasan/generic.c:186 (discriminator 1) mm/kasan/generic.c:200 (discriminator 1))
[ 138.425884][ C1] __asan_memset (mm/kasan/shadow.c:84 (discriminator 2))
[ 138.425889][ C1] ife_tlv_meta_encode (net/ife/ife.c:168)
[ 138.425893][ C1] ? ife_tlv_meta_encode (net/ife/ife.c:171)
[ 138.425898][ C1] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:221)
[ 138.425903][ C1] ife_encode_meta_u16 (net/sched/act_ife.c:57)
[ 138.425910][ C1] ? __pfx_do_raw_spin_lock (kernel/locking/spinlock_debug.c:114)
[ 138.425916][ C1] ? __asan_memcpy (mm/kasan/shadow.c:105 (discriminator 3))
[ 138.425921][ C1] ? __pfx_ife_encode_meta_u16 (net/sched/act_ife.c:45)
[ 138.425927][ C1] ? srso_alias_return_thunk (arch/x86/lib/retpoline.S:221)
[ 138.425931][ C1] tcf_ife_act (net/sched/act_ife.c:847 net/sched/act_ife.c:879)

To solve this issue, fix the replace behavior by adding the metalist to
the ife rcu data structure.

Fixes: aa9fd9a325d51 ("sched: act: ife: update parameters via rcu handling")
Reported-by: Ruitong Liu <cnitlrt@gmail.com>
Tested-by: Ruitong Liu <cnitlrt@gmail.com>
Co-developed-by: Victor Nogueira <victor@mojatatu.com>
Signed-off-by: Victor Nogueira <victor@mojatatu.com>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
Link: https://patch.msgid.link/20260304140603.76500-1-jhs@mojatatu.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Jamal Hadi Salim and committed by
Jakub Kicinski
e2cedd40 4517b74c

+45 -52
+1 -3
include/net/tc_act/tc_ife.h
··· 13 13 u8 eth_src[ETH_ALEN]; 14 14 u16 eth_type; 15 15 u16 flags; 16 - 16 + struct list_head metalist; 17 17 struct rcu_head rcu; 18 18 }; 19 19 20 20 struct tcf_ife_info { 21 21 struct tc_action common; 22 22 struct tcf_ife_params __rcu *params; 23 - /* list of metaids allowed */ 24 - struct list_head metalist; 25 23 }; 26 24 #define to_ife(a) ((struct tcf_ife_info *)a) 27 25
+44 -49
net/sched/act_ife.c
··· 293 293 /* called when adding new meta information 294 294 */ 295 295 static int __add_metainfo(const struct tcf_meta_ops *ops, 296 - struct tcf_ife_info *ife, u32 metaid, void *metaval, 297 - int len, bool atomic, bool exists) 296 + struct tcf_ife_params *p, u32 metaid, void *metaval, 297 + int len, bool atomic) 298 298 { 299 299 struct tcf_meta_info *mi = NULL; 300 300 int ret = 0; ··· 313 313 } 314 314 } 315 315 316 - if (exists) 317 - spin_lock_bh(&ife->tcf_lock); 318 - list_add_tail(&mi->metalist, &ife->metalist); 319 - if (exists) 320 - spin_unlock_bh(&ife->tcf_lock); 316 + list_add_tail(&mi->metalist, &p->metalist); 321 317 322 318 return ret; 323 319 } 324 320 325 321 static int add_metainfo_and_get_ops(const struct tcf_meta_ops *ops, 326 - struct tcf_ife_info *ife, u32 metaid, 327 - bool exists) 322 + struct tcf_ife_params *p, u32 metaid) 328 323 { 329 324 int ret; 330 325 331 326 if (!try_module_get(ops->owner)) 332 327 return -ENOENT; 333 - ret = __add_metainfo(ops, ife, metaid, NULL, 0, true, exists); 328 + ret = __add_metainfo(ops, p, metaid, NULL, 0, true); 334 329 if (ret) 335 330 module_put(ops->owner); 336 331 return ret; 337 332 } 338 333 339 - static int add_metainfo(struct tcf_ife_info *ife, u32 metaid, void *metaval, 340 - int len, bool exists) 334 + static int add_metainfo(struct tcf_ife_params *p, u32 metaid, void *metaval, 335 + int len) 341 336 { 342 337 const struct tcf_meta_ops *ops = find_ife_oplist(metaid); 343 338 int ret; 344 339 345 340 if (!ops) 346 341 return -ENOENT; 347 - ret = __add_metainfo(ops, ife, metaid, metaval, len, false, exists); 342 + ret = __add_metainfo(ops, p, metaid, metaval, len, false); 348 343 if (ret) 349 344 /*put back what find_ife_oplist took */ 350 345 module_put(ops->owner); 351 346 return ret; 352 347 } 353 348 354 - static int use_all_metadata(struct tcf_ife_info *ife, bool exists) 349 + static int use_all_metadata(struct tcf_ife_params *p) 355 350 { 356 351 struct tcf_meta_ops *o; 357 352 int rc = 0; ··· 354 359 355 360 read_lock(&ife_mod_lock); 356 361 list_for_each_entry(o, &ifeoplist, list) { 357 - rc = add_metainfo_and_get_ops(o, ife, o->metaid, exists); 362 + rc = add_metainfo_and_get_ops(o, p, o->metaid); 358 363 if (rc == 0) 359 364 installed += 1; 360 365 } ··· 366 371 return -EINVAL; 367 372 } 368 373 369 - static int dump_metalist(struct sk_buff *skb, struct tcf_ife_info *ife) 374 + static int dump_metalist(struct sk_buff *skb, struct tcf_ife_params *p) 370 375 { 371 376 struct tcf_meta_info *e; 372 377 struct nlattr *nest; ··· 374 379 int total_encoded = 0; 375 380 376 381 /*can only happen on decode */ 377 - if (list_empty(&ife->metalist)) 382 + if (list_empty(&p->metalist)) 378 383 return 0; 379 384 380 385 nest = nla_nest_start_noflag(skb, TCA_IFE_METALST); 381 386 if (!nest) 382 387 goto out_nlmsg_trim; 383 388 384 - list_for_each_entry(e, &ife->metalist, metalist) { 389 + list_for_each_entry(e, &p->metalist, metalist) { 385 390 if (!e->ops->get(skb, e)) 386 391 total_encoded += 1; 387 392 } ··· 398 403 return -1; 399 404 } 400 405 401 - /* under ife->tcf_lock */ 402 - static void _tcf_ife_cleanup(struct tc_action *a) 406 + static void __tcf_ife_cleanup(struct tcf_ife_params *p) 403 407 { 404 - struct tcf_ife_info *ife = to_ife(a); 405 408 struct tcf_meta_info *e, *n; 406 409 407 - list_for_each_entry_safe(e, n, &ife->metalist, metalist) { 410 + list_for_each_entry_safe(e, n, &p->metalist, metalist) { 408 411 list_del(&e->metalist); 409 412 if (e->metaval) { 410 413 if (e->ops->release) ··· 415 422 } 416 423 } 417 424 425 + static void tcf_ife_cleanup_params(struct rcu_head *head) 426 + { 427 + struct tcf_ife_params *p = container_of(head, struct tcf_ife_params, 428 + rcu); 429 + 430 + __tcf_ife_cleanup(p); 431 + kfree(p); 432 + } 433 + 418 434 static void tcf_ife_cleanup(struct tc_action *a) 419 435 { 420 436 struct tcf_ife_info *ife = to_ife(a); 421 437 struct tcf_ife_params *p; 422 438 423 - spin_lock_bh(&ife->tcf_lock); 424 - _tcf_ife_cleanup(a); 425 - spin_unlock_bh(&ife->tcf_lock); 426 - 427 439 p = rcu_dereference_protected(ife->params, 1); 428 440 if (p) 429 - kfree_rcu(p, rcu); 441 + call_rcu(&p->rcu, tcf_ife_cleanup_params); 430 442 } 431 443 432 444 static int load_metalist(struct nlattr **tb, bool rtnl_held) ··· 453 455 return 0; 454 456 } 455 457 456 - static int populate_metalist(struct tcf_ife_info *ife, struct nlattr **tb, 457 - bool exists, bool rtnl_held) 458 + static int populate_metalist(struct tcf_ife_params *p, struct nlattr **tb) 458 459 { 459 460 int len = 0; 460 461 int rc = 0; ··· 465 468 val = nla_data(tb[i]); 466 469 len = nla_len(tb[i]); 467 470 468 - rc = add_metainfo(ife, i, val, len, exists); 471 + rc = add_metainfo(p, i, val, len); 469 472 if (rc) 470 473 return rc; 471 474 } ··· 520 523 p = kzalloc_obj(*p); 521 524 if (!p) 522 525 return -ENOMEM; 526 + INIT_LIST_HEAD(&p->metalist); 523 527 524 528 if (tb[TCA_IFE_METALST]) { 525 529 err = nla_parse_nested_deprecated(tb2, IFE_META_MAX, ··· 565 567 } 566 568 567 569 ife = to_ife(*a); 568 - if (ret == ACT_P_CREATED) 569 - INIT_LIST_HEAD(&ife->metalist); 570 570 571 571 err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack); 572 572 if (err < 0) ··· 596 600 } 597 601 598 602 if (tb[TCA_IFE_METALST]) { 599 - err = populate_metalist(ife, tb2, exists, 600 - !(flags & TCA_ACT_FLAGS_NO_RTNL)); 603 + err = populate_metalist(p, tb2); 601 604 if (err) 602 605 goto metadata_parse_err; 603 606 } else { ··· 605 610 * as we can. You better have at least one else we are 606 611 * going to bail out 607 612 */ 608 - err = use_all_metadata(ife, exists); 613 + err = use_all_metadata(p); 609 614 if (err) 610 615 goto metadata_parse_err; 611 616 } ··· 621 626 if (goto_ch) 622 627 tcf_chain_put_by_act(goto_ch); 623 628 if (p) 624 - kfree_rcu(p, rcu); 629 + call_rcu(&p->rcu, tcf_ife_cleanup_params); 625 630 626 631 return ret; 627 632 metadata_parse_err: 628 633 if (goto_ch) 629 634 tcf_chain_put_by_act(goto_ch); 630 635 release_idr: 636 + __tcf_ife_cleanup(p); 631 637 kfree(p); 632 638 tcf_idr_release(*a, bind); 633 639 return err; ··· 675 679 if (nla_put(skb, TCA_IFE_TYPE, 2, &p->eth_type)) 676 680 goto nla_put_failure; 677 681 678 - if (dump_metalist(skb, ife)) { 682 + if (dump_metalist(skb, p)) { 679 683 /*ignore failure to dump metalist */ 680 684 pr_info("Failed to dump metalist\n"); 681 685 } ··· 689 693 return -1; 690 694 } 691 695 692 - static int find_decode_metaid(struct sk_buff *skb, struct tcf_ife_info *ife, 696 + static int find_decode_metaid(struct sk_buff *skb, struct tcf_ife_params *p, 693 697 u16 metaid, u16 mlen, void *mdata) 694 698 { 695 699 struct tcf_meta_info *e; 696 700 697 701 /* XXX: use hash to speed up */ 698 - list_for_each_entry(e, &ife->metalist, metalist) { 702 + list_for_each_entry_rcu(e, &p->metalist, metalist) { 699 703 if (metaid == e->metaid) { 700 704 if (e->ops) { 701 705 /* We check for decode presence already */ ··· 712 716 { 713 717 struct tcf_ife_info *ife = to_ife(a); 714 718 int action = ife->tcf_action; 719 + struct tcf_ife_params *p; 715 720 u8 *ifehdr_end; 716 721 u8 *tlv_data; 717 722 u16 metalen; 723 + 724 + p = rcu_dereference_bh(ife->params); 718 725 719 726 bstats_update(this_cpu_ptr(ife->common.cpu_bstats), skb); 720 727 tcf_lastuse_update(&ife->tcf_tm); ··· 744 745 return TC_ACT_SHOT; 745 746 } 746 747 747 - if (find_decode_metaid(skb, ife, mtype, dlen, curr_data)) { 748 + if (find_decode_metaid(skb, p, mtype, dlen, curr_data)) { 748 749 /* abuse overlimits to count when we receive metadata 749 750 * but dont have an ops for it 750 751 */ ··· 768 769 /*XXX: check if we can do this at install time instead of current 769 770 * send data path 770 771 **/ 771 - static int ife_get_sz(struct sk_buff *skb, struct tcf_ife_info *ife) 772 + static int ife_get_sz(struct sk_buff *skb, struct tcf_ife_params *p) 772 773 { 773 - struct tcf_meta_info *e, *n; 774 + struct tcf_meta_info *e; 774 775 int tot_run_sz = 0, run_sz = 0; 775 776 776 - list_for_each_entry_safe(e, n, &ife->metalist, metalist) { 777 + list_for_each_entry_rcu(e, &p->metalist, metalist) { 777 778 if (e->ops->check_presence) { 778 779 run_sz = e->ops->check_presence(skb, e); 779 780 tot_run_sz += run_sz; ··· 794 795 OUTERHDR:TOTMETALEN:{TLVHDR:Metadatum:TLVHDR..}:ORIGDATA 795 796 where ORIGDATA = original ethernet header ... 796 797 */ 797 - u16 metalen = ife_get_sz(skb, ife); 798 + u16 metalen = ife_get_sz(skb, p); 798 799 int hdrm = metalen + skb->dev->hard_header_len + IFE_METAHDRLEN; 799 800 unsigned int skboff = 0; 800 801 int new_len = skb->len + hdrm; ··· 832 833 if (!ife_meta) 833 834 goto drop; 834 835 835 - spin_lock(&ife->tcf_lock); 836 - 837 836 /* XXX: we dont have a clever way of telling encode to 838 837 * not repeat some of the computations that are done by 839 838 * ops->presence_check... 840 839 */ 841 - list_for_each_entry(e, &ife->metalist, metalist) { 840 + list_for_each_entry_rcu(e, &p->metalist, metalist) { 842 841 if (e->ops->encode) { 843 842 err = e->ops->encode(skb, (void *)(ife_meta + skboff), 844 843 e); 845 844 } 846 845 if (err < 0) { 847 846 /* too corrupt to keep around if overwritten */ 848 - spin_unlock(&ife->tcf_lock); 849 847 goto drop; 850 848 } 851 849 skboff += err; 852 850 } 853 - spin_unlock(&ife->tcf_lock); 854 851 oethh = (struct ethhdr *)skb->data; 855 852 856 853 if (!is_zero_ether_addr(p->eth_src))