From: Star Chang <starchang@google.com>
To: netdev@vger.kernel.org
Cc: ja@ssi.bg, dsahern@kernel.org, idosch@nvidia.com,
davem@davemloft.net, edumazet@google.com, kuba@kernel.org,
pabeni@redhat.com, horms@kernel.org, johannes@sipsolutions.net,
johannes.berg@intel.com, linux-kernel@vger.kernel.org,
wangroger@google.com, starchang@google.com
Subject: [PATCH net v5] arp: Fix Hole-196 vulnerability by dropping unicast ARP replies in L2 BMC
Date: Fri, 30 Oct 2026 03:11:36 +0000 [thread overview]
Message-ID: <20261030031136.607720-1-starchang@google.com> (raw)
Wi-Fi 802.11 to 802.3 frame translation allows attackers to inject
frames inside GTK-encrypted L2 broadcast/multicast (BMC) frames (the
"Hole-196" vulnerability).
While the `drop_unicast_in_l2_multicast` sysctl drops unicast
IPv4/IPv6 packets received over L2 BMC, it did not check ARP replies in
`net/ipv4/arp.c`. In laboratory testing on shared-key Wi-Fi networks,
an attacker can inject an unsolicited unicast ARP reply (e.g. sip =
gateway IP, sha = attacker MAC, tip = victim IP) over L2 broadcast. If
the victim already has a neighbor entry for the gateway (such as after
normal network traffic), `neigh_update()` is invoked after the
1-second locktime window, overwriting the gateway's cached MAC address
with the attacker's MAC.
Under standard networking behavior, a non-GARP ARP reply is strictly
meant to be delivered as an L2 unicast frame (PACKET_HOST) to the
requester. The only legitimate reason an ARP reply is sent over L2
broadcast is for Gratuitous ARP (sip == tip) or address announcement.
Enhance `drop_unicast_in_l2_multicast` sysctl enforcement:
- Drop ARP replies received over L2 BMC frames unless they are
Gratuitous ARP (sip == tip), recording
SKB_DROP_REASON_UNICAST_IN_L2_MULTICAST.
- Preserve legitimate GARP announcements (sip == tip) so they continue
to defer to the `drop_gratuitous_arp` sysctl, preserving VRRP/HSRP
router failover.
- Leave ARP requests untouched per RFC 826.
- Update Documentation/networking/ip-sysctl.rst to describe the ARP
behavior.
Fixes: 12b74dfadb5a ("ipv4: add option to drop unicast encapsulated in L2 multicast")
Signed-off-by: Star Chang <starchang@google.com>
---
v4 -> v5:
- Drop all IP and IPv6 changes to avoid UAF risks and adhere to RFC 1122.
- Simplify ARP logic to strictly drop non-GARP ARP replies (ar_op == ARPOP_REPLY && sip != tip) over L2 BMC.
- Do not inspect or gate on target hardware address (tha), leaving ARP requests untouched per RFC 826.
- Update Documentation/networking/ip-sysctl.rst to accurately reflect ARP behavior.
- Record SKB_DROP_REASON_UNICAST_IN_L2_MULTICAST on drop.
- Retain only Fixes: 12b74dfadb5a.
v3 -> v4:
- Addressed review feedback on ratelimit logging and drop counters.
v2 -> v3:
- Added THA validation and gratuitous ARP handling.
v1 -> v2:
- Initial expansion to ARP.
Documentation/networking/ip-sysctl.rst | 4 ++--
net/ipv4/arp.c | 20 ++++++++++++++++++++
2 files changed, 22 insertions(+), 2 deletions(-)
diff --git a/Documentation/networking/ip-sysctl.rst b/Documentation/networking/ip-sysctl.rst
index 208f46967ee5..0e4f151fac48 100644
--- a/Documentation/networking/ip-sysctl.rst
+++ b/Documentation/networking/ip-sysctl.rst
@@ -2297,8 +2297,8 @@ promote_secondaries - BOOLEAN
Default: 0 (disabled)
drop_unicast_in_l2_multicast - BOOLEAN
- Drop any unicast IP packets that are received in link-layer
- multicast (or broadcast) frames.
+ Drop any unicast IP packets and non-GARP ARP replies that are
+ received in link-layer multicast (or broadcast) frames.
This behavior (for multicast) is actually a SHOULD in RFC
1122, but is disabled by default for compatibility reasons.
diff --git a/net/ipv4/arp.c b/net/ipv4/arp.c
index d409f606aec0..d6a01d05aa75 100644
--- a/net/ipv4/arp.c
+++ b/net/ipv4/arp.c
@@ -791,6 +791,26 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb)
(!IN_DEV_ROUTE_LOCALNET(in_dev) && ipv4_is_loopback(tip)))
goto out_free_skb;
+/*
+ * Hole-196 defense for ARP:
+ * If drop_unicast_in_l2_multicast is enabled, drop ARP Replies received
+ * in L2 BMC frames unless they are Gratuitous ARP (sip == tip).
+ *
+ * Standard ARP Replies are strictly unicast (PACKET_HOST). An ARP Reply
+ * in an L2 broadcast frame with (sip != tip) is an injected unicast packet.
+ * Legitimate GARP (sip == tip) is preserved and defers to DROP_GRATUITOUS_ARP.
+ * ARP Requests are left untouched per RFC 826.
+ */
+ if ((skb->pkt_type == PACKET_BROADCAST ||
+ skb->pkt_type == PACKET_MULTICAST) &&
+ IN_DEV_ORCONF(in_dev, DROP_UNICAST_IN_L2_MULTICAST)) {
+ if (arp->ar_op == htons(ARPOP_REPLY) && sip != tip) {
+ kfree_skb_reason(skb,
+ SKB_DROP_REASON_UNICAST_IN_L2_MULTICAST);
+ return NET_RX_DROP;
+ }
+ }
+
/*
* For some 802.11 wireless deployments (and possibly other networks),
* there will be an ARP proxy and gratuitous ARP frames are attacks
--
2.56.0.317.g454f8302e1-goog
reply other threads:[~2026-10-30 3:11 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261030031136.607720-1-starchang@google.com \
--to=starchang@google.com \
--cc=davem@davemloft.net \
--cc=dsahern@kernel.org \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=idosch@nvidia.com \
--cc=ja@ssi.bg \
--cc=johannes.berg@intel.com \
--cc=johannes@sipsolutions.net \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=wangroger@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox