netfilter: ip6t_eui64: validate MAC header before using it
Subsystems (3)
A message that is not in the archive (sent off-list or not yet received): [email protected]
PATCH 1/1 netfilter: ip6t_eui64: validate MAC header before using it
From: Zhengchuan Liang <[email protected]>
eui64_mt6() derives a modified EUI-64 from the Ethernet source address and compares it with the low 64 bits of the IPv6 source address.
The match unconditionally reaches skb_mac_header() and eth_hdr(skb) after a guard that only rejects an invalid MAC header when par->fragoff != 0. As a result, non-fragment packets can still reach eth_hdr(skb) even when the skb has no MAC header set, or when the MAC header does not cover a full Ethernet header.
Fix this by first checking that the MAC header is set and spans a full Ethernet header before accessing it, then using that validated header directly for the EUI-64 comparison. Preserve the existing hotdrop behavior for non-first fragments with an invalid MAC header.
--- net/ipv6/netfilter/ip6t_eui64.c | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-)
net/ipv6/netfilter/ip6t_eui64.c +16 -7
@@ -19,21 +19,30 @@ MODULE_AUTHOR("Andras Kis-Szabo <[email protected]>");static bool eui64_mt6 (const struct sk_buff *skb,struct xt_action_param *par) { +const unsigned char *mac; +const struct ethhdr *eth;unsigned char eui64[8 ]; -if (!(skb_mac_header (skb) >= skb->head && -skb_mac_header (skb) +ETH_HLEN <= skb->data) && - par->fragoff !=0 ) { - par->hotdrop = true; +if (!skb_mac_header_was_set (skb)) { +if (par->fragoff !=0 ) + par->hotdrop = true;return false; } + mac =skb_mac_header (skb); +if (mac < skb->head || mac +ETH_HLEN > skb->data) { +if (par->fragoff !=0 ) + par->hotdrop = true; +return false; + } + eth = (const struct ethhdr *)mac; +memset (eui64,0 ,sizeof (eui64)); -if (eth_hdr (skb)->h_proto ==htons (ETH_P_IPV6 )) { +if (eth->h_proto ==htons (ETH_P_IPV6 )) {if (ipv6_hdr (skb)->version ==0x6 ) { -memcpy (eui64,eth_hdr (skb)->h_source,3 ); -memcpy (eui64 +5 ,eth_hdr (skb)->h_source +3 ,3 ); +memcpy (eui64, eth->h_source,3 ); +memcpy (eui64 +5 , eth->h_source +3 ,3 ); eui64[3 ] =0xff ; eui64[4 ] =0xfe ; eui64[0 ] ^=0x02 ;
Signature · 1 line
2.43.0
Florian Westphal
maintainerQuoted 12 lines · Ren Wei
From: Zhengchuan Liang <[email protected]>
eui64_mt6()derives a modified EUI-64 from the Ethernet source address and compares it with the low 64 bits of the IPv6 source address.The match unconditionally reaches
skb_mac_header()andeth_hdr(skb)after a guard that only rejects an invalid MAC header whenpar->fragoff != 0. As a result, non-fragment packets can still reacheth_hdr(skb)even when the skb has no MAC header set, or when the MAC header does not cover a full Ethernet header.
Quoted 4 lines
Fix this by first checking that the MAC header is set and spans a full Ethernet header before accessing it, then using that validated header directly for the EUI-64 comparison. Preserve the existing hotdrop behavior for non-first fragments with an invalid MAC header.
I find this rather confusing. E.g. why is net/netfilter/xt_mac.c safe? I get the feeling that this patch is not sufficient?
net/ipv6/netfilter/ip6t_eui64.c MODULE_AUTHOR("Andras Kis-Szabo <[email protected]>"); · line 26
Quoted 1 line
net/ipv6/netfilter/ip6t_eui64.c +1
-0+if (!skb_mac_header_was_set (skb)) {
Makes sense to me.
net/ipv6/netfilter/ip6t_eui64.c MODULE_AUTHOR("Andras Kis-Szabo <[email protected]>"); · line 36
Quoted 5 lines
net/ipv6/netfilter/ip6t_eui64.c +5
-0+ mac =skb_mac_header (skb); +if (mac < skb->head || mac +ETH_HLEN > skb->data) { +if (par->fragoff !=0 ) + par->hotdrop = true; +return false;
Why do we still need this stunt? Why not:
mac_len = skb_mac_header_len(skb);
if (mac_len < ETH_HLEN) {
par->hotdrop = true;
return false;
}?
I also feel there should be a check for ethernet, i.e.
if (skb->dev == NULL || skb->dev->type != ARPHRD_ETHER)
... like in xt_mac.c.
Florian Westphal
maintainerQuoted 39 lines · Florian Westphal
Quoted 12 lines · Ren Wei
From: Zhengchuan Liang <[email protected]>
eui64_mt6()derives a modified EUI-64 from the Ethernet source address and compares it with the low 64 bits of the IPv6 source address.The match unconditionally reaches
skb_mac_header()andeth_hdr(skb)after a guard that only rejects an invalid MAC header whenpar->fragoff != 0. As a result, non-fragment packets can still reacheth_hdr(skb)even when the skb has no MAC header set, or when the MAC header does not cover a full Ethernet header.Quoted 4 lines
Fix this by first checking that the MAC header is set and spans a full Ethernet header before accessing it, then using that validated header directly for the EUI-64 comparison. Preserve the existing hotdrop behavior for non-first fragments with an invalid MAC header.
I find this rather confusing. E.g. why is
net/netfilter/xt_mac.csafe? I get the feeling that this patch is not sufficient?net/ipv6/netfilter/ip6t_eui64.c MODULE_AUTHOR("Andras Kis-Szabo <[email protected]>"); · line 26
Quoted 1 line
net/ipv6/netfilter/ip6t_eui64.c +1
-0+if (!skb_mac_header_was_set (skb)) {Makes sense to me.
net/ipv6/netfilter/ip6t_eui64.c MODULE_AUTHOR("Andras Kis-Szabo <[email protected]>"); · line 36
Quoted 5 lines
net/ipv6/netfilter/ip6t_eui64.c +5
-0+ mac =skb_mac_header (skb); +if (mac < skb->head || mac +ETH_HLEN > skb->data) { +if (par->fragoff !=0 ) + par->hotdrop = true; +return false;Why do we still need this stunt? Why not:
mac_len=skb_mac_header_len(skb);if (mac_len < ETH_HLEN) { par->hotdrop = true; return false; }
Quoted 6 lines
?
I also feel there should be a check for ethernet, i.e.
if (skb->dev == NULL || skb->dev->type != ARPHRD_ETHER)... like in
xt_mac.c.
There are other suspicious spots, e.g. in nf_log_syslog.c and in ipset.
Will you make a patch to cover all of these?
Florian Westphal
maintainer[ Trimming Cc list ]
Quoted 42 lines · Florian Westphal
Quoted 41 lines · Florian Westphal
Quoted 12 lines · Ren Wei
From: Zhengchuan Liang <[email protected]>
eui64_mt6()derives a modified EUI-64 from the Ethernet source address and compares it with the low 64 bits of the IPv6 source address.The match unconditionally reaches
skb_mac_header()andeth_hdr(skb)after a guard that only rejects an invalid MAC header whenpar->fragoff != 0. As a result, non-fragment packets can still reacheth_hdr(skb)even when the skb has no MAC header set, or when the MAC header does not cover a full Ethernet header.Quoted 4 lines
Fix this by first checking that the MAC header is set and spans a full Ethernet header before accessing it, then using that validated header directly for the EUI-64 comparison. Preserve the existing hotdrop behavior for non-first fragments with an invalid MAC header.
I find this rather confusing. E.g. why is
net/netfilter/xt_mac.csafe? I get the feeling that this patch is not sufficient?net/ipv6/netfilter/ip6t_eui64.c MODULE_AUTHOR("Andras Kis-Szabo <[email protected]>"); · line 26
Quoted 1 line
net/ipv6/netfilter/ip6t_eui64.c +1
-0+if (!skb_mac_header_was_set (skb)) {Makes sense to me.
net/ipv6/netfilter/ip6t_eui64.c MODULE_AUTHOR("Andras Kis-Szabo <[email protected]>"); · line 36
Quoted 5 lines
net/ipv6/netfilter/ip6t_eui64.c +5
-0+ mac =skb_mac_header (skb); +if (mac < skb->head || mac +ETH_HLEN > skb->data) { +if (par->fragoff !=0 ) + par->hotdrop = true; +return false;Why do we still need this stunt? Why not:
mac_len=skb_mac_header_len(skb);if (mac_len < ETH_HLEN) { par->hotdrop = true; return false; }?
Quoted 8 lines
Quoted 4 lines
I also feel there should be a check for ethernet, i.e.
if (skb->dev == NULL || skb->dev->type != ARPHRD_ETHER)... like in
xt_mac.c.There are other suspicious spots, e.g. in
nf_log_syslog.cand in ipset.Will you make a patch to cover all of these?
Ping. Will you followup or do you expect us to take over?
Quoted 42 lines · Florian Westphal
[ Trimming Cc list ]
Quoted 39 lines · Florian Westphal
Quoted 38 lines · Florian Westphal
Quoted 12 lines · Ren Wei
From: Zhengchuan Liang <[email protected]>
eui64_mt6()derives a modified EUI-64 from the Ethernet source address and compares it with the low 64 bits of the IPv6 source address.The match unconditionally reaches
skb_mac_header()andeth_hdr(skb)after a guard that only rejects an invalid MAC header whenpar->fragoff != 0. As a result, non-fragment packets can still reacheth_hdr(skb)even when the skb has no MAC header set, or when the MAC header does not cover a full Ethernet header.Quoted 4 lines
Fix this by first checking that the MAC header is set and spans a full Ethernet header before accessing it, then using that validated header directly for the EUI-64 comparison. Preserve the existing hotdrop behavior for non-first fragments with an invalid MAC header.
I find this rather confusing. E.g. why is
net/netfilter/xt_mac.csafe? I get the feeling that this patch is not sufficient?net/ipv6/netfilter/ip6t_eui64.c MODULE_AUTHOR("Andras Kis-Szabo <[email protected]>"); · line 26
Quoted 1 line
net/ipv6/netfilter/ip6t_eui64.c +1
-0+if (!skb_mac_header_was_set (skb)) {Makes sense to me.
net/ipv6/netfilter/ip6t_eui64.c MODULE_AUTHOR("Andras Kis-Szabo <[email protected]>"); · line 36
Quoted 5 lines
net/ipv6/netfilter/ip6t_eui64.c +5
-0+ mac =skb_mac_header (skb); +if (mac < skb->head || mac +ETH_HLEN > skb->data) { +if (par->fragoff !=0 ) + par->hotdrop = true; +return false;Why do we still need this stunt? Why not:
mac_len=skb_mac_header_len(skb);if (mac_len < ETH_HLEN) { par->hotdrop = true; return false; }?
Quoted 8 lines
Quoted 7 lines
Quoted 4 lines
I also feel there should be a check for ethernet, i.e.
if (skb->dev == NULL || skb->dev->type != ARPHRD_ETHER)... like in
xt_mac.c.There are other suspicious spots, e.g. in
nf_log_syslog.cand in ipset.Will you make a patch to cover all of these?
Ping. Will you followup or do you expect us to take over?
Sorry for our late reply. We will try to send version 2 in 2 days.
PATCH v2 1/2 netfilter: ip6t_eui64: reject invalid MAC header for all packets
In reply to a message not in the archiveFrom: Zhengchuan Liang <[email protected]>
eui64_mt6() derives a modified EUI-64 from the Ethernet source address and compares it with the low 64 bits of the IPv6 source address.
The existing guard only rejects an invalid MAC header when par->fragoff != 0. For packets with par->fragoff == 0, eui64_mt6() can still reach eth_hdr(skb) even when the MAC header is not valid.
Fix this by removing the par->fragoff != 0 condition so that packets with an invalid MAC header are rejected before accessing eth_hdr(skb).
---
Changes in v2:
This patch specifically fixes the memory-safety issue in eui64_mt6():
the old guard only rejected invalid MAC headers for non-first fragments, while our PoC triggers the KASAN report with a non-fragment packet (par->fragoff == 0). This patch is sufficient enough to fix the memory-safety issue, and we do not find other eth_hdr(skb) users with similar use-after-free or out-of-bound issues.
net/ipv6/netfilter/ip6t_eui64.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
net/ipv6/netfilter/ip6t_eui64.c +1 -2
@@ -22,8 +22,7 @@ eui64_mt6(const struct sk_buff *skb, struct xt_action_param *par)unsigned char eui64[8 ];if (!(skb_mac_header (skb) >= skb->head && -skb_mac_header (skb) +ETH_HLEN <= skb->data) && - par->fragoff !=0 ) { +skb_mac_header (skb) +ETH_HLEN <= skb->data)) { par->hotdrop = true;return false; }
Signature · 1 line
2.43.0
PATCH v2 2/2 netfilter: require Ethernet MAC header before using eth_hdr()
In reply to a message not in the archiveFrom: Zhengchuan Liang <[email protected]>
ip6t_eui64, xt_mac, the bitmap:ip,mac, hash:ip,mac, and hash:mac ipset types, and nf_log_syslog access eth_hdr(skb) after either assuming that the skb is associated with an Ethernet device or checking only that the ETH_HLEN bytes at skb_mac_header(skb) lie between skb->head and skb->data.
Make these paths first verify that the skb is associated with an Ethernet device, that the MAC header was set, and that it spans at least a full Ethernet header before accessing eth_hdr(skb).
---
Changes in v2:
This patch addresses suspicious eth_hdr(skb) users by requiring an Ethernet device together with a valid MAC header before accessing it.
net/ipv6/netfilter/ip6t_eui64.c | 7 +++++-- net/netfilter/ipset/ip_set_bitmap_ipmac.c | 5 +++-- net/netfilter/ipset/ip_set_hash_ipmac.c | 9 +++++---- net/netfilter/ipset/ip_set_hash_mac.c | 5 +++-- net/netfilter/nf_log_syslog.c | 7 ++++++- net/netfilter/xt_mac.c | 4 +--- 6 files changed, 23 insertions(+), 14 deletions(-)
net/ipv6/netfilter/ip6t_eui64.c +5 -2
@@ -7,6 +7,7 @@#include <linux/module.h> #include <linux/skbuff.h> #include <linux/ipv6.h> +#include <linux/if_arp.h> #include <linux/if_ether.h> #include <linux/netfilter/x_tables.h>
@@ -21,8 +22,10 @@ eui64_mt6(const struct sk_buff *skb, struct xt_action_param *par) {unsigned char eui64[8 ]; -if (!(skb_mac_header (skb) >= skb->head && -skb_mac_header (skb) +ETH_HLEN <= skb->data)) { +if (!skb->dev || skb->dev->type !=ARPHRD_ETHER ) +return false; + +if (!skb_mac_header_was_set (skb) ||skb_mac_header_len (skb) <ETH_HLEN ) { par->hotdrop = true;return false; }
net/netfilter/ipset/ip_set_bitmap_ipmac.c +3 -2
@@ -11,6 +11,7 @@#include <linux/etherdevice.h> #include <linux/skbuff.h> #include <linux/errno.h> +#include <linux/if_arp.h> #include <linux/if_ether.h> #include <linux/netlink.h> #include <linux/jiffies.h>
@@ -220,8 +221,8 @@ bitmap_ipmac_kadt(struct ip_set *set, const struct sk_buff *skb,return -IPSET_ERR_BITMAP_RANGE ;/* Backward compatibility: we don't check the second flag */ -if (skb_mac_header (skb) < skb->head || - (skb_mac_header (skb) +ETH_HLEN ) > skb->data) +if (!skb->dev || skb->dev->type !=ARPHRD_ETHER || + !skb_mac_header_was_set (skb) ||skb_mac_header_len (skb) <ETH_HLEN )return -EINVAL ; e.id =ip_to_id (map, ip);
net/netfilter/ipset/ip_set_hash_ipmac.c +5 -4
@@ -11,6 +11,7 @@#include <linux/skbuff.h> #include <linux/errno.h> #include <linux/random.h> +#include <linux/if_arp.h> #include <linux/if_ether.h> #include <net/ip.h> #include <net/ipv6.h>
@@ -89,8 +90,8 @@ hash_ipmac4_kadt(struct ip_set *set, const struct sk_buff *skb,struct hash_ipmac4_elem e = { .ip =0 , { .foo[0 ] =0 , .foo[1 ] =0 } };struct ip_set_ext ext =IP_SET_INIT_KEXT (skb, opt, set); -if (skb_mac_header (skb) < skb->head || - (skb_mac_header (skb) +ETH_HLEN ) > skb->data) +if (!skb->dev || skb->dev->type !=ARPHRD_ETHER || + !skb_mac_header_was_set (skb) ||skb_mac_header_len (skb) <ETH_HLEN )return -EINVAL ;if (opt->flags &IPSET_DIM_TWO_SRC )
@@ -205,8 +206,8 @@ hash_ipmac6_kadt(struct ip_set *set, const struct sk_buff *skb, };struct ip_set_ext ext =IP_SET_INIT_KEXT (skb, opt, set); -if (skb_mac_header (skb) < skb->head || - (skb_mac_header (skb) +ETH_HLEN ) > skb->data) +if (!skb->dev || skb->dev->type !=ARPHRD_ETHER || + !skb_mac_header_was_set (skb) ||skb_mac_header_len (skb) <ETH_HLEN )return -EINVAL ;if (opt->flags &IPSET_DIM_TWO_SRC )
net/netfilter/ipset/ip_set_hash_mac.c +3 -2
@@ -8,6 +8,7 @@#include <linux/etherdevice.h> #include <linux/skbuff.h> #include <linux/errno.h> +#include <linux/if_arp.h> #include <linux/if_ether.h> #include <net/netlink.h>
@@ -77,8 +78,8 @@ hash_mac4_kadt(struct ip_set *set, const struct sk_buff *skb,struct hash_mac4_elem e = { { .foo[0 ] =0 , .foo[1 ] =0 } };struct ip_set_ext ext =IP_SET_INIT_KEXT (skb, opt, set); -if (skb_mac_header (skb) < skb->head || - (skb_mac_header (skb) +ETH_HLEN ) > skb->data) +if (!skb->dev || skb->dev->type !=ARPHRD_ETHER || + !skb_mac_header_was_set (skb) ||skb_mac_header_len (skb) <ETH_HLEN )return -EINVAL ;if (opt->flags &IPSET_DIM_ONE_SRC )
net/netfilter/nf_log_syslog.c +4 -1
@@ -78,7 +78,9 @@ dump_arp_packet(struct nf_log_buf *m,else logflags =NF_LOG_DEFAULT_MASK ; -if (logflags &NF_LOG_MACDECODE ) { +if ((logflags &NF_LOG_MACDECODE ) && + skb->dev && skb->dev->type ==ARPHRD_ETHER && +skb_mac_header_was_set (skb) && +skb_mac_header_len (skb) >=ETH_HLEN ) {nf_log_buf_add (m,"MACSRC=%pM MACDST=%pM " ,eth_hdr (skb)->h_source,eth_hdr (skb)->h_dest);
nf_log_dump_vlan(m, skb);
@@ -789,6 +791,9 @@ static void dump_mac_header(struct nf_log_buf *m,
switch (dev->type) {
case ARPHRD_ETHER:
+ if (!skb_mac_header_was_set(skb) || skb_mac_header_len(skb) < ETH_HLEN)
+ return;
+
nf_log_buf_add(m, "MACSRC=%pM MACDST=%pM ",
eth_hdr(skb)->h_source, eth_hdr(skb)->h_dest);
nf_log_dump_vlan(m, skb);
net/netfilter/xt_mac.c +1 -3
@@ -29,9 +29,7 @@ static bool mac_mt(const struct sk_buff *skb, struct xt_action_param *par)if (skb->dev ==NULL || skb->dev->type !=ARPHRD_ETHER )return false; -if (skb_mac_header (skb) < skb->head) -return false; -if (skb_mac_header (skb) +ETH_HLEN > skb->data) +if (!skb_mac_header_was_set (skb) ||skb_mac_header_len (skb) <ETH_HLEN )return false; ret =ether_addr_equal (eth_hdr (skb)->h_source, info->srcaddr); ret ^= info->invert;
Signature · 1 line
2.43.0