)]}'
{
  "commit": "ce052b9402e461a9aded599f5b47e76bc727f7de",
  "tree": "f0129d301ed272ed73639551b02a7aff24a5fea2",
  "parents": [
    "188e0fa5a679570ea35474575e724d8211423d17"
  ],
  "author": {
    "name": "Jamal Hadi Salim",
    "email": "jhs@mojatatu.com",
    "time": "Fri Nov 28 10:19:19 2025 -0500"
  },
  "committer": {
    "name": "Paolo Abeni",
    "email": "pabeni@redhat.com",
    "time": "Thu Dec 04 11:43:45 2025 +0100"
  },
  "message": "net/sched: ets: Always remove class from active list before deleting in ets_qdisc_change\n\nzdi-disclosures@trendmicro.com says:\n\nThe vulnerability is a race condition between `ets_qdisc_dequeue` and\n`ets_qdisc_change`.  It leads to UAF on `struct Qdisc` object.\nAttacker requires the capability to create new user and network namespace\nin order to trigger the bug.\nSee my additional commentary at the end of the analysis.\n\nAnalysis:\n\nstatic int ets_qdisc_change(struct Qdisc *sch, struct nlattr *opt,\n                          struct netlink_ext_ack *extack)\n{\n...\n\n      // (1) this lock is preventing .change handler (`ets_qdisc_change`)\n      //to race with .dequeue handler (`ets_qdisc_dequeue`)\n      sch_tree_lock(sch);\n\n      for (i \u003d nbands; i \u003c oldbands; i++) {\n              if (i \u003e\u003d q-\u003enstrict \u0026\u0026 q-\u003eclasses[i].qdisc-\u003eq.qlen)\n                      list_del_init(\u0026q-\u003eclasses[i].alist);\n              qdisc_purge_queue(q-\u003eclasses[i].qdisc);\n      }\n\n      WRITE_ONCE(q-\u003enbands, nbands);\n      for (i \u003d nstrict; i \u003c q-\u003enstrict; i++) {\n              if (q-\u003eclasses[i].qdisc-\u003eq.qlen) {\n\t\t      // (2) the class is added to the q-\u003eactive\n                      list_add_tail(\u0026q-\u003eclasses[i].alist, \u0026q-\u003eactive);\n                      q-\u003eclasses[i].deficit \u003d quanta[i];\n              }\n      }\n      WRITE_ONCE(q-\u003enstrict, nstrict);\n      memcpy(q-\u003eprio2band, priomap, sizeof(priomap));\n\n      for (i \u003d 0; i \u003c q-\u003enbands; i++)\n              WRITE_ONCE(q-\u003eclasses[i].quantum, quanta[i]);\n\n      for (i \u003d oldbands; i \u003c q-\u003enbands; i++) {\n              q-\u003eclasses[i].qdisc \u003d queues[i];\n              if (q-\u003eclasses[i].qdisc !\u003d \u0026noop_qdisc)\n                      qdisc_hash_add(q-\u003eclasses[i].qdisc, true);\n      }\n\n      // (3) the qdisc is unlocked, now dequeue can be called in parallel\n      // to the rest of .change handler\n      sch_tree_unlock(sch);\n\n      ets_offload_change(sch);\n      for (i \u003d q-\u003enbands; i \u003c oldbands; i++) {\n\t      // (4) we\u0027re reducing the refcount for our class\u0027s qdisc and\n\t      //  freeing it\n              qdisc_put(q-\u003eclasses[i].qdisc);\n\t      // (5) If we call .dequeue between (4) and (5), we will have\n\t      // a strong UAF and we can control RIP\n              q-\u003eclasses[i].qdisc \u003d NULL;\n              WRITE_ONCE(q-\u003eclasses[i].quantum, 0);\n              q-\u003eclasses[i].deficit \u003d 0;\n              gnet_stats_basic_sync_init(\u0026q-\u003eclasses[i].bstats);\n              memset(\u0026q-\u003eclasses[i].qstats, 0, sizeof(q-\u003eclasses[i].qstats));\n      }\n      return 0;\n}\n\nComment:\nThis happens because some of the classes have their qdiscs assigned to\nNULL, but remain in the active list. This commit fixes this issue by always\nremoving the class from the active list before deleting and freeing its\nassociated qdisc\n\nReproducer Steps\n(trimmed version of what was sent by zdi-disclosures@trendmicro.com)\n\n```\nDEV\u003d\"${DEV:-lo}\"\nROOT_HANDLE\u003d\"${ROOT_HANDLE:-1:}\"\nBAND2_HANDLE\u003d\"${BAND2_HANDLE:-20:}\"   # child under 1:2\nPING_BYTES\u003d\"${PING_BYTES:-48}\"\nPING_COUNT\u003d\"${PING_COUNT:-200000}\"\nPING_DST\u003d\"${PING_DST:-127.0.0.1}\"\n\nSLOW_TBF_RATE\u003d\"${SLOW_TBF_RATE:-8bit}\"\nSLOW_TBF_BURST\u003d\"${SLOW_TBF_BURST:-100b}\"\nSLOW_TBF_LAT\u003d\"${SLOW_TBF_LAT:-1s}\"\n\ncleanup() {\n  tc qdisc del dev \"$DEV\" root 2\u003e/dev/null\n}\ntrap cleanup EXIT\n\nip link set \"$DEV\" up\n\ntc qdisc del dev \"$DEV\" root 2\u003e/dev/null || true\n\ntc qdisc add dev \"$DEV\" root handle \"$ROOT_HANDLE\" ets bands 2 strict 2\n\ntc qdisc add dev \"$DEV\" parent 1:2 handle \"$BAND2_HANDLE\" \\\n  tbf rate \"$SLOW_TBF_RATE\" burst \"$SLOW_TBF_BURST\" latency \"$SLOW_TBF_LAT\"\n\ntc filter add dev \"$DEV\" parent 1: protocol all prio 1 u32 match u32 0 0 flowid 1:2\ntc -s qdisc ls dev $DEV\n\nping -I \"$DEV\" -f -c \"$PING_COUNT\" -s \"$PING_BYTES\" -W 0.001 \"$PING_DST\" \\\n  \u003e/dev/null 2\u003e\u00261 \u0026\ntc qdisc change dev \"$DEV\" root handle \"$ROOT_HANDLE\" ets bands 2 strict 0\ntc qdisc change dev \"$DEV\" root handle \"$ROOT_HANDLE\" ets bands 2 strict 2\ntc -s qdisc ls dev $DEV\ntc qdisc del dev \"$DEV\" parent 1:2 || true\ntc -s qdisc ls dev $DEV\ntc qdisc change dev \"$DEV\" root handle \"$ROOT_HANDLE\" ets bands 1 strict 1\n```\n\nKASAN report\n```\n\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\u003d\nBUG: KASAN: slab-use-after-free in ets_qdisc_dequeue+0x1071/0x11b0 kernel/net/sched/sch_ets.c:481\nRead of size 8 at addr ffff8880502fc018 by task ping/12308\n\u003e\nCPU: 0 UID: 0 PID: 12308 Comm: ping Not tainted 6.18.0-rc4-dirty #1 PREEMPT(full)\nHardware name: QEMU Ubuntu 25.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014\nCall Trace:\n \u003cIRQ\u003e\n __dump_stack kernel/lib/dump_stack.c:94\n dump_stack_lvl+0x100/0x190 kernel/lib/dump_stack.c:120\n print_address_description kernel/mm/kasan/report.c:378\n print_report+0x156/0x4c9 kernel/mm/kasan/report.c:482\n kasan_report+0xdf/0x110 kernel/mm/kasan/report.c:595\n ets_qdisc_dequeue+0x1071/0x11b0 kernel/net/sched/sch_ets.c:481\n dequeue_skb kernel/net/sched/sch_generic.c:294\n qdisc_restart kernel/net/sched/sch_generic.c:399\n __qdisc_run+0x1c9/0x1b00 kernel/net/sched/sch_generic.c:417\n __dev_xmit_skb kernel/net/core/dev.c:4221\n __dev_queue_xmit+0x2848/0x4410 kernel/net/core/dev.c:4729\n dev_queue_xmit kernel/./include/linux/netdevice.h:3365\n[...]\n\nAllocated by task 17115:\n kasan_save_stack+0x30/0x50 kernel/mm/kasan/common.c:56\n kasan_save_track+0x14/0x30 kernel/mm/kasan/common.c:77\n poison_kmalloc_redzone kernel/mm/kasan/common.c:400\n __kasan_kmalloc+0xaa/0xb0 kernel/mm/kasan/common.c:417\n kasan_kmalloc kernel/./include/linux/kasan.h:262\n __do_kmalloc_node kernel/mm/slub.c:5642\n __kmalloc_node_noprof+0x34e/0x990 kernel/mm/slub.c:5648\n kmalloc_node_noprof kernel/./include/linux/slab.h:987\n qdisc_alloc+0xb8/0xc30 kernel/net/sched/sch_generic.c:950\n qdisc_create_dflt+0x93/0x490 kernel/net/sched/sch_generic.c:1012\n ets_class_graft+0x4fd/0x800 kernel/net/sched/sch_ets.c:261\n qdisc_graft+0x3e4/0x1780 kernel/net/sched/sch_api.c:1196\n[...]\n\nFreed by task 9905:\n kasan_save_stack+0x30/0x50 kernel/mm/kasan/common.c:56\n kasan_save_track+0x14/0x30 kernel/mm/kasan/common.c:77\n __kasan_save_free_info+0x3b/0x70 kernel/mm/kasan/generic.c:587\n kasan_save_free_info kernel/mm/kasan/kasan.h:406\n poison_slab_object kernel/mm/kasan/common.c:252\n __kasan_slab_free+0x5f/0x80 kernel/mm/kasan/common.c:284\n kasan_slab_free kernel/./include/linux/kasan.h:234\n slab_free_hook kernel/mm/slub.c:2539\n slab_free kernel/mm/slub.c:6630\n kfree+0x144/0x700 kernel/mm/slub.c:6837\n rcu_do_batch kernel/kernel/rcu/tree.c:2605\n rcu_core+0x7c0/0x1500 kernel/kernel/rcu/tree.c:2861\n handle_softirqs+0x1ea/0x8a0 kernel/kernel/softirq.c:622\n __do_softirq kernel/kernel/softirq.c:656\n[...]\n\nCommentary:\n\n1. Maher Azzouzi working with Trend Micro Zero Day Initiative was reported as\nthe person who found the issue. I requested to get a proper email to add to the\nreported-by tag but got no response. For this reason i will credit the person\ni exchanged emails with i.e zdi-disclosures@trendmicro.com\n\n2. Neither i nor Victor who did a much more thorough testing was able to\nreproduce a UAF with the PoC or other approaches we tried. We were both able to\nreproduce a null ptr deref. After exchange with zdi-disclosures@trendmicro.com\nthey sent a small change to be made to the code to add an extra delay which\nwas able to simulate the UAF. i.e, this:\n   qdisc_put(q-\u003eclasses[i].qdisc);\n   mdelay(90);\n   q-\u003eclasses[i].qdisc \u003d NULL;\n\nI was informed by Thomas Gleixner(tglx@linutronix.de) that adding delays was\nacceptable approach for demonstrating the bug, quote:\n\"Adding such delays is common exploit validation practice\"\nThe equivalent delay could happen \"by virt scheduling the vCPU out, SMIs,\nNMIs, PREEMPT_RT enabled kernel\"\n\n3. I asked the OP to test and report back but got no response and after a\nfew days gave up and proceeded to submit this fix.\n\nFixes: de6d25924c2a (\"net/sched: sch_ets: don\u0027t peek at classes beyond \u0027nbands\u0027\")\nReported-by: zdi-disclosures@trendmicro.com\nTested-by: Victor Nogueira \u003cvictor@mojatatu.com\u003e\nSigned-off-by: Jamal Hadi Salim \u003cjhs@mojatatu.com\u003e\nReviewed-by: Davide Caratti \u003cdcaratti@redhat.com\u003e\nLink: https://patch.msgid.link/20251128151919.576920-1-jhs@mojatatu.com\nSigned-off-by: Paolo Abeni \u003cpabeni@redhat.com\u003e\n\n",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "82635dd2cfa59f08d3670bfa8c82cdad540b41b6",
      "old_mode": 33188,
      "old_path": "net/sched/sch_ets.c",
      "new_id": "ae46643e596d30aebe8eca42d75d2dc716395fc1",
      "new_mode": 33188,
      "new_path": "net/sched/sch_ets.c"
    }
  ]
}
