CVE-2026-98797

Status: ReceivedUnscored—
Severity—Unscored
Exploitation probability0.16 %higher than 5% of CVEs
Active exploitationNonot in the CISA catalog
Public exploitNot knownno public code found

In the Linux kernel, the following vulnerability has been resolved:

net: cap skb->queue_mapping when the tx queue is picked

skbedit can set skb->queue_mapping and raise the per-CPU skip_txqueue flag so __dev_queue_xmit() honours the mapping. __dev_queue_xmit() cleared the flag before sch_handle_egress() and only read it afterwards, so the flag was not confined to the xmit that set it: a nested xmit (mirred redirect or mirror, or a drop after skbedit) could set the flag and the outer xmit would consume it for an skb that never went through skbedit.

A forwarded packet still carries the ingress NIC's rx_queue + 1 in skb->queue_mapping, so the outer device then indexes its tx queue state with that stale value.

Read full descriptionShow less

Taprio's child array q->qdiscs[] is sized to the device's queue count, so taprio_enqueue() indexes past its allocation and dereferences the result as a struct Qdisc *.

We (ab)use the skb->nf_skip_egress which means "skip netfilter egress for this packet" to tag to "am I in tc egress?". Despite the overload I dont see it as a conflict since the marker is set only around the single sch_handle_egress() call and ingress path is guarded by tc_at_ingress. I will send a followup(net-next) patch once this hits net-next to rename the skb->nf_skip_egress bit/flag to skb->skip_egress

Arm the flag only from the egress classifier that can use it: raise skip_txqueue from tcf_skbedit_act() only when it runs inside sch_handle_egress(), thanks to skb->nf_skip_egress. An egress qdisc classifier runs in q->enqueue(), after the tx queue has been picked, so a mapping it sets cannot affect the current packet; arming the flag there only pollutes it for a later xmit. Then own the flag for the xmit frame the egress hook runs in: save the incoming value and clear it just before sch_handle_egress(), and restore it after the hook - on the consumed (drop) path, or, in the same call that reads it, on the surviving path. The save and the restores stay inside the egress_needed_key static branch, so a packet pays for them only when egress hooks are active (2f1e85b1aee4).

Store the value netdev_cap_txqueue() selected back into skb->queue_mapping in netdev_tx_queue_mapping(), as netdev_core_pick_tx() already does, so the skip_txqueue path never hands a later reader on the xmit path a mapping the device cannot serve. A store made still later in the same frame, by a tc BPF program attached to a transmit qdisc, is outside this path and is not re-capped; a separate followup will resolve that path.

netdev_xmit_skip_txqueue() returns the previous flag value so the save-and-clear is one call, and a no-op stub is provided when CONFIG_NET_EGRESS is disabled. skb->nf_skip_egress is compiled under CONFIG_NET_EGRESS rather than CONFIG_NETFILTER_SKIP_EGRESS, so skb_at_tc_egress() is valid whenever the egress path is built.

A local user in a network namespace can redirect a packet from a device with more TX queues to one with fewer after setting a mapping valid only on the larger device. That reaches these reads and, under KASAN, faults with "slab-out-of-bounds in taprio_enqueue".

Conditions to recreate the bug: the report's own trigger is a local user with CAP_NET_ADMIN in a network namespace, so no eBPF program is needed. With CONFIG_NET_SCH_TAPRIO=y, CONFIG_NET_ACT_SKBEDIT=y, CONFIG_NET_ACT_MIRRED=y, CONFIG_NET_CLS_MATCHALL=y, CONFIG_NET_SCH_PRIO=y and KASAN enabled, create qa (3 queues), qb (2 queues) and qc (1 queue) as dummy devices; put a taprio root on qb (num_tc 1, queues 2@0) and clsact on all three; then add an egress matchall filter on every device. On qa: "action skbedit queue_mapping 2 pipe action mirred egress redirect dev qb". On qb: "action mirred egress mirror dev qc". On qc: "action skbedit queue_mapping 0 pipe". Send one packet out qa. qc's skbedit sets the flag while qb's outer xmit is in flight; without the fix qb consumes it and reads its two-entry taprio child array with the forwarded packet's stale mapping. A qc whose skb ---truncated---

CVSS

NVD hasn't assigned a CVSS score to this CVE (common since the April 2026 policy change).

Exploitation probability (EPSS)

Probability of exploitation in the next 30 days
0.16 %
Percentile among all scored CVEs
5
Score date
10/20/2026

EPSS (Exploit Prediction Scoring System, FIRST) estimates how likely a vulnerability is to be exploited in the wild within 30 days. It complements CVSS (impact) and CISA KEV (confirmed exploitation).

Affected technologies (1)

⚠ AI-inferred from the description — NVD hasn't analyzed this CVE yet, these aren't verified CPEs.

References

Show 4 references

Raw JSON (NVD)

Show
{
  "id": "CVE-2026-98797",
  "cveTags": [],
  "metrics": {},
  "affected": [
    {
      "source": "416baaa9-dc9f-4396-8d5f-8c081fb06d67",
      "affectedData": [
        {
          "repo": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git",
          "vendor": "Linux",
          "product": "Linux",
          "versions": [
            {
              "status": "affected",
              "version": "2f1e85b1aee459b7d0fd981839042c6a38ffaf0c",
              "lessThan": "c6ec35c8267c84c2094cb485db20c06384fc2bcc",
              "versionType": "git"
            },
            {
              "status": "affected",
              "version": "2f1e85b1aee459b7d0fd981839042c6a38ffaf0c",
              "lessThan": "2f581814e4e3ea6a5e98f49813dc4c68444a0c4a",
              "versionType": "git"
            },
            {
              "status": "affected",
              "version": "2f1e85b1aee459b7d0fd981839042c6a38ffaf0c",
              "lessThan": "f99de42f5c6f8d088063817dad82c3334e87c570",
              "versionType": "git"
            },
            {
              "status": "affected",
              "version": "2f1e85b1aee459b7d0fd981839042c6a38ffaf0c",
              "lessThan": "ea4d4b5dddb5121e64f56fd8e0720bd8b447b63d",
              "versionType": "git"
            }
          ],
          "programFiles": [
            "include/linux/netfilter_netdev.h",
            "include/linux/rtnetlink.h",
            "include/linux/skbuff.h",
            "include/net/sch_generic.h",
            "net/core/dev.c",
            "net/sched/act_skbedit.c"
          ],
          "defaultStatus": "unaffected"
        },
        {
          "repo": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git",
          "vendor": "Linux",
          "product": "Linux",
          "versions": [
            {
              "status": "affected",
              "version": "5.19"
            },
            {
              "status": "unaffected",
              "version": "0",
              "lessThan": "5.19",
              "versionType": "semver"
            },
            {
              "status": "unaffected",
              "version": "6.12.113",
              "versionType": "semver",
              "lessThanOrEqual": "6.12.*"
            },
            {
              "status": "unaffected",
              "version": "6.18.56",
              "versionType": "semver",
              "lessThanOrEqual": "6.18.*"
            },
            {
              "status": "unaffected",
              "version": "7.2.10",
              "versionType": "semver",
              "lessThanOrEqual": "7.2.*"
            },
            {
              "status": "unaffected",
              "version": "7.3",
              "versionType": "original_commit_for_fix",
              "lessThanOrEqual": "*"
            }
          ],
          "programFiles": [
            "include/linux/netfilter_netdev.h",
            "include/linux/rtnetlink.h",
            "include/linux/skbuff.h",
            "include/net/sch_generic.h",
            "net/core/dev.c",
            "net/sched/act_skbedit.c"
          ],
          "defaultStatus": "affected"
        }
      ]
    }
  ],
  "published": "2026-10-18T16:17:33.673",
  "references": [
    {
      "url": "https://git.kernel.org/stable/c/2f581814e4e3ea6a5e98f49813dc4c68444a0c4a",
      "source": "416baaa9-dc9f-4396-8d5f-8c081fb06d67"
    },
    {
      "url": "https://git.kernel.org/stable/c/c6ec35c8267c84c2094cb485db20c06384fc2bcc",
      "source": "416baaa9-dc9f-4396-8d5f-8c081fb06d67"
    },
    {
      "url": "https://git.kernel.org/stable/c/ea4d4b5dddb5121e64f56fd8e0720bd8b447b63d",
      "source": "416baaa9-dc9f-4396-8d5f-8c081fb06d67"
    },
    {
      "url": "https://git.kernel.org/stable/c/f99de42f5c6f8d088063817dad82c3334e87c570",
      "source": "416baaa9-dc9f-4396-8d5f-8c081fb06d67"
    }
  ],
  "vulnStatus": "Received",
  "descriptions": [
    {
      "lang": "en",
      "value": "In the Linux kernel, the following vulnerability has been resolved:\n\nnet: cap skb->queue_mapping when the tx queue is picked\n\nskbedit can set skb->queue_mapping and raise the per-CPU skip_txqueue\nflag so __dev_queue_xmit() honours the mapping. __dev_queue_xmit()\ncleared the flag before sch_handle_egress() and only read it afterwards,\nso the flag was not confined to the xmit that set it: a nested xmit\n(mirred redirect or mirror, or a drop after skbedit) could set the flag\nand the outer xmit would consume it for an skb that never went through\nskbedit.\n\nA forwarded packet still carries the ingress NIC's rx_queue + 1 in\nskb->queue_mapping, so the outer device then indexes its tx queue state\nwith that stale value. Taprio's child array q->qdiscs[] is sized to the\ndevice's queue count, so taprio_enqueue() indexes past its allocation\nand dereferences the result as a struct Qdisc *.\n\nWe (ab)use the skb->nf_skip_egress which means \"skip netfilter egress\nfor this packet\" to tag to \"am I in tc egress?\". Despite the overload\nI dont see it as a conflict since the marker is set only around the\nsingle sch_handle_egress() call and ingress path is guarded by\ntc_at_ingress.\nI will send a followup(net-next) patch once this hits net-next to\nrename the skb->nf_skip_egress bit/flag to skb->skip_egress\n\nArm the flag only from the egress classifier that can use it: raise\nskip_txqueue from tcf_skbedit_act() only when it runs inside\nsch_handle_egress(), thanks to skb->nf_skip_egress. An egress qdisc\nclassifier runs in q->enqueue(), after the tx queue has been picked,\nso a mapping it sets cannot affect the current packet; arming the flag\nthere only pollutes it for a later xmit. Then own the flag for the xmit\nframe the egress hook runs in: save the incoming value and clear it just\nbefore sch_handle_egress(), and restore it after the hook - on the\nconsumed (drop) path, or, in the same call that reads it, on the\nsurviving path. The save and the restores stay inside the\negress_needed_key static branch, so a packet pays for them only when\negress hooks are active (2f1e85b1aee4).\n\nStore the value netdev_cap_txqueue() selected back into skb->queue_mapping\nin netdev_tx_queue_mapping(), as netdev_core_pick_tx() already does, so\nthe skip_txqueue path never hands a later reader on the xmit path a\nmapping the device cannot serve. A store made still later in the same\nframe, by a tc BPF program attached to a transmit qdisc, is outside this\npath and is not re-capped; a separate followup will resolve that path.\n\nnetdev_xmit_skip_txqueue() returns the previous flag value so the\nsave-and-clear is one call, and a no-op stub is provided when\nCONFIG_NET_EGRESS is disabled. skb->nf_skip_egress is compiled under\nCONFIG_NET_EGRESS rather than CONFIG_NETFILTER_SKIP_EGRESS, so\nskb_at_tc_egress() is valid whenever the egress path is built.\n\nA local user in a network namespace can redirect a packet from a device\nwith more TX queues to one with fewer after setting a mapping valid only\non the larger device. That reaches these reads and, under KASAN, faults\nwith \"slab-out-of-bounds in taprio_enqueue\".\n\nConditions to recreate the bug: the report's own trigger is a local user\nwith CAP_NET_ADMIN in a network namespace, so no eBPF program is needed.\nWith CONFIG_NET_SCH_TAPRIO=y, CONFIG_NET_ACT_SKBEDIT=y,\nCONFIG_NET_ACT_MIRRED=y, CONFIG_NET_CLS_MATCHALL=y,\nCONFIG_NET_SCH_PRIO=y and KASAN enabled, create qa (3 queues), qb\n(2 queues) and qc (1 queue) as dummy devices; put a taprio root on qb\n(num_tc 1, queues 2@0) and clsact on all three; then add an egress\nmatchall filter on every device. On qa: \"action skbedit queue_mapping 2\npipe action mirred egress redirect dev qb\". On qb: \"action mirred egress\nmirror dev qc\". On qc: \"action skbedit queue_mapping 0 pipe\". Send one\npacket out qa. qc's skbedit sets the flag while qb's outer xmit is in\nflight; without the fix qb consumes it and reads its two-entry taprio\nchild array with the forwarded packet's stale mapping. A qc whose\nskb\n---truncated---"
    }
  ],
  "lastModified": "2026-10-19T10:19:43.707",
  "sourceIdentifier": "416baaa9-dc9f-4396-8d5f-8c081fb06d67"
}