]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
flow_dissector: check device type before reading ETH_ADDRS
authorYun Zhou <yun.zhou@windriver.com>
Tue, 16 Jun 2026 12:30:57 +0000 (20:30 +0800)
committerJakub Kicinski <kuba@kernel.org>
Fri, 19 Jun 2026 01:07:00 +0000 (18:07 -0700)
__skb_flow_dissect() unconditionally reads 12 bytes from eth_hdr(skb)
when FLOW_DISSECTOR_KEY_ETH_ADDRS is requested. This assumes the skb
has a valid Ethernet header at mac_header, which is not always the case.

The problem can be triggered by:
 1. Creating a TUN device in L3 mode (IFF_TUN, hard_header_len=0)
 2. Attaching a multiq qdisc with a flower filter matching on eth_src
 3. Sending a packet through AF_PACKET

Since TUN in L3 mode has no link-layer header, mac_header points to
the L3 data area. The flow dissector reads 12 bytes of uninitialized
skb memory, which then propagates through fl_set_masked_key() and is
used as a rhashtable lookup key in __fl_lookup(), as reported by KMSAN.

Rejecting the filter in the control path (at tc filter add time) is
not feasible because TC filter blocks can be shared between arbitrary
devices -- a filter installed on an Ethernet device may later classify
packets on a headerless device through a shared block. The device
association is not fixed at filter creation time.

Fix this by gating the memcpy on dev->type == ARPHRD_ETHER, which
ensures only true Ethernet-framed packets have their addresses read.
This is more precise than the previous hard_header_len >= 12 check,
which would incorrectly pass for non-Ethernet link types like IPoIB
(ARPHRD_INFINIBAND, hard_header_len=24) and FDDI (hard_header_len=21)
whose L2 headers are not in Ethernet format. Additionally check
skb_mac_header_was_set() to guard against the pathological case where
mac_header is the unset sentinel (~0U), which would cause eth_hdr() to
return a wild pointer.

For the act_mirred redirect case (Ethernet packet redirected to a
non-Ethernet device sharing a TC block), zeroing the key is the correct
behavior: the packet is now being classified on the target device, where
Ethernet address matching is not semantically meaningful.

Note: on non-Ethernet devices, the zeroed key will match a filter
configured with all-zero MAC addresses. This is an improvement over the
previous behavior where uninitialized memory could randomly match any
filter.

Reported-by: syzbot+fa2f5b1fb06147be5e16@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=fa2f5b1fb06147be5e16
Fixes: 67a900cc0436 ("flow_dissector: introduce support for Ethernet addresses")
Signed-off-by: Yun Zhou <yun.zhou@windriver.com>
Link: https://patch.msgid.link/20260616123057.482154-1-yun.zhou@windriver.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
net/core/flow_dissector.c

index 2a98f5fa74eb0933cfe41a6a9310a146e1033051..8aa4f9b4df81016a3d9a97e0d561f6dfc51aa88d 100644 (file)
@@ -1173,13 +1173,21 @@ bool __skb_flow_dissect(const struct net *net,
 
        if (dissector_uses_key(flow_dissector,
                               FLOW_DISSECTOR_KEY_ETH_ADDRS)) {
-               struct ethhdr *eth = eth_hdr(skb);
                struct flow_dissector_key_eth_addrs *key_eth_addrs;
 
                key_eth_addrs = skb_flow_dissector_target(flow_dissector,
                                                          FLOW_DISSECTOR_KEY_ETH_ADDRS,
                                                          target_container);
-               memcpy(key_eth_addrs, eth, sizeof(*key_eth_addrs));
+               /* TC filter blocks can be shared across devices with
+                * different link types, so we cannot validate this
+                * when the filter is installed -- check at dissect time.
+                */
+               if (skb && skb->dev &&
+                   skb->dev->type == ARPHRD_ETHER &&
+                   skb_mac_header_was_set(skb))
+                       memcpy(key_eth_addrs, eth_hdr(skb), sizeof(*key_eth_addrs));
+               else
+                       memset(key_eth_addrs, 0, sizeof(*key_eth_addrs));
        }
 
        if (dissector_uses_key(flow_dissector,