discard ARP/NDP b/mcast/null announce (poison)
Subsystems (4)
v4 cover discard ARP/NDP b/mcast/null announce (poison)
The current ARP and NDP implementations accept announcements with multicast (broadcast incl.) and null MAC addresses as Sender HW Address (SHA) in ARP or src/target lladdr in NDP, and updates the cache for that neighbour.
Multicast (incl. broadcast) and null MAC addresses shall never be associated with a unicast or a multicast IPv4/6 address (see RFC1812, section 3.3.2).
ARP/NDP poisioning with a broadcast and certain multicast MAC addresses, especially when poisoning a Gateway IP, have some undesired implications compared to an ARP/NDP poisioning with a regular MAC (see commit message in patch 1 for more information).
Worth mentioning that if an attacker is able to ARP/NDP poison in a L2 segment, that in itself is probably a bigger security threat (Man-in-middle etc., see Note2 in patch 1)
Since these MACs should never be announced, this patch series discards/drops these packets, which prevents broadcast and multicast ARP/NDP poisoning vectors.
This patchset only modifies the behaviour of the neighbouring subsystem when processing network packets. Static entries can still be added with mcast/bcast/null MACs.
v3: https://lore.kernel.org/netdev/[email protected]/
v2: https://lore.kernel.org/netdev/[email protected]/
v1: https://lore.kernel.org/netdev/[email protected]/
Changes since v3
================
- Respin rebase on top net-next
Changes since v2
================
- Target net-next instead of net
- Use mausezahn for patch2 and remove arp_send.c
- Kept ndisc_send.c for patch 4, as ndisc6 and mausezahn are not valid options (see comment)
- Fixed comment llsrc->lltgt (AI review)
- Misc fixes: shellcheck, alphabetical order in Makefile
Changes since RFC v1
====================
- Discard announcements with multicast MAC addresses
- Check for dev->type == ARPHRD_ETHER instead of HW addrlen in ARP
- Use !is_valid_ether_addr()
- Added multicast test coverage and renamed tests accordingly
- Dropped patch 5 (scapy utils)
Comments
========
On ndisc_send.c alternatives, ndisc6 and extending mausezahn were not viable options. Submitted with ndisc_send.c, as preferred by maintainers.
Having said that, I still think Scapy is the best tool for these sort of packet generation, and can simplify - perhaps at a cost of some test execution time - selftest creation and maintenance. As also mentioned in ../bpf/generate_udp_fragments.py, it's sort of industry standard, and is widely used for dataplane functional testing. I've personally used it in several organizations to functionally test ASICs, NPUs and SW datapaths.
If maintainers are willing to reconsider that, I'd be happy to work on transitioning the existing selftests (using ndisc6, mausezahn...) towards Scapy.
Marc Suñé (4):
arp: discard invalid sha addr (b/mcast ARP poison) selftests/net: add no ARP b/mcast,null poison test
neigh: discard invalid lladdr (b/mcast poison) selftests/net: add no NDP b/mcast,null poison test
net/ipv4/arp.c | 8 + net/ipv6/ndisc.c | 16 + tools/testing/selftests/net/.gitignore | 1 + tools/testing/selftests/net/Makefile | 3 + .../net/arp_ndisc_no_invalid_sha_poison.sh | 368 ++++++++++++++++++ tools/testing/selftests/net/ndisc_send.c | 198 ++++++++++ 6 files changed, 594 insertions(+) create mode 100755 tools/testing/selftests/net/arp_ndisc_no_invalid_sha_poison.sh create mode 100644 tools/testing/selftests/net/ndisc_send.c
Signature · 1 line
2.47.3
PATCH v4 1/4 arp: discard invalid sha addr (b/mcast ARP poison)
Prior to this commit, the ARP implementation accepted ARP req/replies with multicast (including broadcast) and null MAC addresses as Sender HW Address (SHA), and updated the ARP cache for that neighbour. Broadcast, multicast and null MAC addresses shall never be associated with a unicast or a multicast IPv4 address (see RFC1812, section 3.3.2).
ARP poisioning with a broadcast MAC address and certain multicast addresses, especially when poisoning a Gateway IP, have some undesired implications compared to an ARP poisioning with a regular MAC (see Note1).
Worth mentioning that if an attacker is able to ARP poison in a L2 segment, that in itself is probably a bigger security threat (Man-in-middle etc., see Note2).
However, since these MACs should never be announced as SHA, discard/drop ARPs with SHA={b/mcast, null}, which prevents the broadcast/multicast ARP poisoning vector.
Note1:
After a successful broadcast/multicast ARP poisioning attack:
1. Unicast packets and refresh ("targeted") ARPs sent to or via the poisioned IP (e.g. the default GW) are flooded by bridges/switches. That is in absence of other security controls.
Hardware swiches generally have rate-limits to prevent/mitigate broadcast storms, since ARPs are usually answered by the CPU. Legit unicast packets could be dropped (perf. degradation).
Most modern NICs implement some form of L2 MAC filtering to early discard irrelevant packets. In contrast to an ARP poisoning attack with any other MAC, both unicast and ARP ("targeted") refresh packets are passed up to the Kernel networking stack (for all hosts in the L2 segment).
2. A single forged ARP packet (e.g. for the Gateway IP) can produce up to N "targeted" (to broadcast) ARPs, where N is the number of hosts in the L2 segment that have an ARP entry for that IP (e.g. GW), and some more traffic, since the real host will answer to targeted refresh ARPs with their (real) reply.
This is a relatively low amount of traffic compared to 1).
3. An attacker could use this form of ARP poisoning to discover all hosts in a L2 segment in a very short period of time with one or few packets.
By poisoning e.g. the default GW (likely multiple times, to avoid races with real gARPs from the GW), all hosts will eventually issue refresh "targeted" ARPs for the GW IP with the broadcast MAC address as destination. These packets will be flooded in the L2 segment, revealing the presence of hosts to the attacker.
For comparison:
* Passive ARP monitoring: also stealthy, but can take a long time or not be possible at all in switches, as most refresh ARPs are targeted.
* ARP req flooding: requires swiping the entire subnet. Noisy and easy to detect.
* ICMP/L4 port scans: similar to the above.
4. In the unlikely case that hosts were to run with /proc/sys/net/ipv4/conf/*/arp_accept=1 (unsafe, and disabled by default), poisoning with the broadcast MAC could be used to create significantly more broadcast traffic (low-volume amplification attack).
An attacker could send M fake gARP with a number of IP addresses, where M is /proc/sys/net/ipv4/neigh/*/gc_thresh3 (1024 by default). This would result in M x R ARPs, where R is the number of hosts in L2 segment with arp_accept=1, and likely other (real) ARP replies coming from the attacked host. This starts to get really relevant when R > 512, which is possible in large LANs but not very common.
Note2:
However, broadcast ARP poisoning might be subtle and difficult to spot. These ARP packets appear on the surface as regular broadcast ARP requests (unless ARP hdr is inspected), traffic continues to flow uninterrupted (unless broadcast rate-limit in switches kick-in) and, the next refresh ARP reply (from the GW) or any (valid) gARP from the GW, will restore the original MAC in the ARP table, making the traffic flow normally again.
--- net/ipv4/arp.c | 8 ++++++++ 1 file changed, 8 insertions(+)
net/ipv4/arp.c +8 -0
@@ -800,6 +800,14 @@ static int arp_process(struct net *net, struct sock *sk, struct sk_buff *skb) goto out_free_skb;/* +* For Ethernet devices, Multicast/Broadcast and zero MAC addresses should +* never be announced and accepted as sender HW address (RFC1812, 3.3.2). +* Prevents Broadcast/Mcast ARP poisoning attack. +*/ +if (dev->type ==ARPHRD_ETHER && !is_valid_ether_addr (sha)) + goto out_free_skb; + +/* * Special case: We must set Frame Relay source Q.922 address */ if (dev_type ==ARPHRD_DLCI )
Signature · 1 line
2.47.3
PATCH v4 2/4 selftests/net: add no ARP b/mcast,null poison test
In reply to Marc SuñéAdd a selftest to test that ARP bcast/mcast/null poisioning checks are never bypassed.
--- tools/testing/selftests/net/Makefile | 1 + .../net/arp_no_invalid_sha_poision.sh | 173 ++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100755 tools/testing/selftests/net/arp_no_invalid_sha_poision.sh
tools/testing/selftests/net/Makefile +1 -0
@@ -11,6 +11,7 @@ TEST_PROGS := \ amt.sh \ arp_ndisc_evict_nocarrier.sh \ arp_ndisc_untracked_subnets.sh \ + arp_no_invalid_sha_poision.sh \ bareudp.sh \ big_tcp.sh \ bind_bhash.sh \
tools/testing/selftests/net/arp_no_invalid_sha_poision.sh new file +173 -0
173 lines not on this page · raw message
Signature · 1 line
2.47.3
Jakub Kicinski
maintainerRe: [PATCH net-next v4 2/4] selftests/net: add no ARP b/mcast,null poison test
tools/testing/selftests/net/Makefile TEST_PROGS := \ · line 17
Quoted 12 lines · On Sat, 7 Feb 2026 21:40:34 +0100 Marc Suñé wrote:
tools/testing/selftests/net/Makefile +1
-0@@ -11,6 +11,7 @@ TEST_PROGS := \ amt.sh \ arp_ndisc_evict_nocarrier.sh \ arp_ndisc_untracked_subnets.sh \ + arp_no_invalid_sha_poision.sh \ bareudp.sh \ big_tcp.sh \ bind_bhash.sh \
Not sure why but we get:
# selftests: net: arp_no_invalid_sha_poision.sh # Warning: file arp_no_invalid_sha_poision.sh is missing! not ok 1 selftests: net: arp_no_invalid_sha_poision.sh
When we run
make TARGETS=net run_tests
The word "poison" is misspelled but both in the file name and makefile. I guess there's another typo somewhere.
Re: [PATCH net-next v4 2/4] selftests/net: add no ARP b/mcast,null poison test
Missatge de Jakub Kicinski <[email protected]> del dia dc., 11 de febr. 2026 a les 3:11:
Quoted 17 lines · On Sat, 7 Feb 2026 21:40:34 +0100 Marc Suñé wrote:
tools/testing/selftests/net/Makefile TEST_PROGS := \ · line 17
Quoted 12 lines
tools/testing/selftests/net/Makefile +1
-0@@ -11,6 +11,7 @@ TEST_PROGS := \ amt.sh \ arp_ndisc_evict_nocarrier.sh \ arp_ndisc_untracked_subnets.sh \ + arp_no_invalid_sha_poision.sh \ bareudp.sh \ big_tcp.sh \ bind_bhash.sh \Not sure why but we get:
Quoted 7 lines
# selftests: net:
arp_no_invalid_sha_poision.sh# Warning: filearp_no_invalid_sha_poision.shis missing! not ok 1 selftests: net:arp_no_invalid_sha_poision.shWhen we run
make TARGETS=netrun_tests
It seems to work for me (typo already fixed):
sudo make TARGETS=net run_tests
[...]
# timeout set to 3600 # selftests: net: arp_no_invalid_sha_poison.sh # TEST: 1.1 ARP no poisoning dmac=bcast reply sha=bcast [ OK ] # TEST: 1.2 ARP no poisoning dmac=bcast reply sha=null [ OK ] # TEST: 1.3 ARP no poisoning dmac=bcast req sha=bcast [ OK ] # TEST: 1.4 ARP no poisoning dmac=bcast req sha=null [ OK ] # TEST: 1.5 ARP no poisoning dmac=bcast req sha=mcast [ OK ] # TEST: 1.6 ARP no poisoning dmac=bcast reply sha=mcast [ OK ] # TEST: 1.7 ARP no poisoning dmac=veth0 reply sha=bcast [ OK ] # TEST: 1.8 ARP no poisoning dmac=veth0 reply sha=null [ OK ] # TEST: 1.9 ARP no poisoning dmac=veth0 req sha=bcast [ OK ] # TEST: 1.10 ARP no poisoning dmac=veth0 req sha=null [ OK ] # TEST: 1.11 ARP no poisoning dmac=veth0 req sha=mcast [ OK ] # TEST: 1.12 ARP no poisoning dmac=veth0 reply sha=mcast [ OK ] ok 25 selftests: net: arp_no_invalid_sha_poison.sh
Not sure why you are getting this error, and why selftests didn't fail in any of the 4 versions in patchwork. I assume tests run there too.
Quoted 2 lines
The word "poison" is misspelled but both in the file name and makefile. I guess there's another typo somewhere.
Sigh. Let me fix that, along with your comment on patch 4 (TEST_GEN_FILES vs. _PROGS) on the other thread. I will also fix some AI review warnings (error traces) on patch 4 that AI reviews _now_ highlighted. It seems AI reviews are non deterministic, as ndisc_send.c didn't change since v2, and the patch didn't even change v3->v4.
I guess you'd like me to wait till net-next is open again, correct?
Thank you for the guidance Jakub.
Jakub Kicinski
maintainerRe: [PATCH net-next v4 2/4] selftests/net: add no ARP b/mcast,null poison test
Quoted 11 lines · On Wed, 11 Feb 2026 19:29:01 +0100 Marc Sune wrote:
Quoted 2 lines
The word "poison" is misspelled but both in the file name and makefile. I guess there's another typo somewhere.
Sigh. Let me fix that, along with your comment on patch 4 (
TEST_GEN_FILESvs._PROGS) on the other thread. I will also fix some AI review warnings (error traces) on patch 4 that AI reviews _now_ highlighted. It seems AI reviews are non deterministic, asndisc_send.cdidn't change since v2, and the patch didn't even changev3->v4.I guess you'd like me to wait till net-next is open again, correct?
Yes, let's wait for net-next.
PATCH v4 3/4 neigh: discard invalid lladdr (b/mcast poison)
In reply to Marc SuñéPrior to this commit, the NDP implementation accepted NDP NS/NA with the broadcast, multicast and null addresses as src/dst lladdr, and updated the neighbour cache for that host.
Broadcast, multicast and null MAC addresses shall never be associated with a unicast or a multicast IPv6 address (see RFC1812, section 3.3.2).
NDP poisioning with a broadcast MAC and certain multicast MAC addresses, especially when poisoning a Gateway IP, have some undesired implications compared to an NDP poisioning with a regular MAC (see ARP bcast poison commit for more details).
Since these MACs should never be announced, discard/drop NDP with lladdr={bcast, null}, which prevents the broadcast/multicast NDP poisoning vector.
--- net/ipv6/ndisc.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
net/ipv6/ndisc.c +16 -0
@@ -830,6 +830,14 @@ static enum skb_drop_reason ndisc_recv_ns(struct sk_buff *skb)return reason; } +/* +* Broadcast/Multicast and zero MAC addresses should +* never be announced and accepted as llsrc address (prevent +* NDP B/MCAST MAC poisoning attack). +*/ +if (dev->type ==ARPHRD_ETHER && !is_valid_ether_addr (lladdr)) +return reason; +/* RFC2461 7.1.1: * If the IP source address is the unspecified address, * there MUST NOT be source link-layer address option
@@ -1033,6 +1041,14 @@ static enum skb_drop_reason ndisc_recv_na(struct sk_buff *skb)net_dbg_ratelimited ("NA: invalid link-layer address length\n" );return reason; } + +/* +* Broadcast/Multicast and zero MAC addresses should +* never be announced and accepted as lltgt address (prevent +* NDP B/MCAST MAC poisoning attack). +*/ +if (dev->type ==ARPHRD_ETHER && !is_valid_ether_addr (lladdr)) +return reason; } ifp =ipv6_get_ifaddr (dev_net (dev), &msg->target, dev,1 );if (ifp) {
Signature · 1 line
2.47.3
PATCH v4 4/4 selftests/net: add no NDP b/mcast,null poison test
In reply to Marc SuñéAdd a selftest to test that NDP bcast/mcast/null poisioning checks are never bypassed.
--- tools/testing/selftests/net/.gitignore | 1 + tools/testing/selftests/net/Makefile | 2 + .../net/arp_ndisc_no_invalid_sha_poison.sh | 368 ++++++++++++++++++ .../net/arp_no_invalid_sha_poision.sh | 173 -------- tools/testing/selftests/net/ndisc_send.c | 198 ++++++++++ 5 files changed, 569 insertions(+), 173 deletions(-) create mode 100755 tools/testing/selftests/net/arp_ndisc_no_invalid_sha_poison.sh delete mode 100755 tools/testing/selftests/net/arp_no_invalid_sha_poision.sh create mode 100644 tools/testing/selftests/net/ndisc_send.c
tools/testing/selftests/net/.gitignore +1 -0
@@ -18,6 +18,7 @@ ipv6_flowlabel_mgr ipv6_fragmentation log.txt msg_zerocopy +ndisc_send netlink-dumps nettest proc_net_pktgen
tools/testing/selftests/net/Makefile +2 -0
@@ -10,6 +10,7 @@ TEST_PROGS := \ altnames.sh \ amt.sh \ arp_ndisc_evict_nocarrier.sh \ + arp_ndisc_no_invalid_sha_poison.sh \ arp_ndisc_untracked_subnets.sh \ arp_no_invalid_sha_poision.sh \ bareudp.sh \
@@ -171,6 +172,7 @@ TEST_GEN_PROGS := \ epoll_busy_poll \ icmp_rfc4884 \ ipv6_fragmentation \ + ndisc_send \ proc_net_pktgen \ reuseaddr_conflict \ reuseport_bpf \
tools/testing/selftests/net/arp_ndisc_no_invalid_sha_poison.sh new file +368 -0
368 lines not on this page · raw message
tools/testing/selftests/net/arp_no_invalid_sha_poision.sh deleted +0 -173
173 lines not on this page · raw message
tools/testing/selftests/net/ndisc_send.c new file +198 -0
198 lines not on this page · raw message
Signature · 1 line
2.47.3
Jakub Kicinski
maintainerRe: [PATCH net-next v4 4/4] selftests/net: add no NDP b/mcast,null poison test
tools/testing/selftests/net/Makefile TEST_GEN_PROGS := \ · line 175
Quoted 5 lines · On Sat, 7 Feb 2026 21:40:36 +0100 Marc Suñé wrote:
tools/testing/selftests/net/Makefile +1
-0@@ -171,6 +172,7 @@ TEST_GEN_PROGS := \ epoll_busy_poll \ icmp_rfc4884 \ ipv6_fragmentation \ + ndisc_send \
This needs to go to TEST_GEN_FILES, _PROGS are tests in the kselftest makefile framework (somewhat confusingly). If we add it here ksft tries to run it which fails:
# selftests: net: ndisc_send
# 0.01 [+0.01] Usage: ./ndisc_send <iface> <mac_dst> <mac_src> <dst_ip> <src_ip> <target_ip> <op> <lladr> not ok 1 selftests: net: ndisc_send # exit=255