From: Willem de Bruijn <will...@google.com>

Packet sockets in datagram mode take a link layer destination address
in sockaddr_ll.sll_addr. For some device types, address length exceeds
this field size, extending beyond the struct.

This addr argument is then passed to dev_hard_header without length
and assumed to be equal to dev->addr_len. Before commit 99137b7888f4
("packet: validate address length") it was possible to pass a shorter
msg_name and msg_namelen, causing an out of bound read.

After that and commit 6b8d95f1795c4 ("packet: validate address length
if non-zero") send() returns EINVAL if sockaddr_ll.sll_halen is
non-zero but less than dev->addr_len (zero length is allowed because
sockets in SOCK_RAW mode ignore sll_addr).

It turns out that legacy applications require more permissive input.
An otherwise legitimate Ethernet application that sets sll_halen > 0,
< ETH_ALEN sees EINVAL, even though ETH_ALEN fits in sockaddr_ll, so
cannot cause the out of bounds read.

Be more permissive. Ignore sll_halen, which is not used elsewhere.
Directly compare msg_namelen to dev->addr_len.

Fixes: 6b8d95f1795c4 ("packet: validate address length if non-zero")
Signed-off-by: Willem de Bruijn <will...@google.com>
---
 net/packet/af_packet.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c
index 9419c5cf4de5e..9ae30bbd00913 100644
--- a/net/packet/af_packet.c
+++ b/net/packet/af_packet.c
@@ -2616,6 +2616,9 @@ static int tpacket_snd(struct packet_sock *po, struct 
msghdr *msg)
                proto   = po->num;
                addr    = NULL;
        } else {
+               const int sa_len = offsetof(struct sockaddr_ll, sll_addr);
+               const short sk_type = po->sk.sk_socket->type;
+
                err = -EINVAL;
                if (msg->msg_namelen < sizeof(struct sockaddr_ll))
                        goto out;
@@ -2624,9 +2627,9 @@ static int tpacket_snd(struct packet_sock *po, struct 
msghdr *msg)
                                                sll_addr)))
                        goto out;
                proto   = saddr->sll_protocol;
-               addr    = saddr->sll_halen ? saddr->sll_addr : NULL;
+               addr    = sk_type == SOCK_DGRAM ? saddr->sll_addr : NULL;
                dev = dev_get_by_index(sock_net(&po->sk), saddr->sll_ifindex);
-               if (addr && dev && saddr->sll_halen < dev->addr_len)
+               if (addr && dev && msg->msg_namelen < (sa_len + dev->addr_len))
                        goto out_put;
        }
 
@@ -2818,15 +2821,17 @@ static int packet_snd(struct socket *sock, struct 
msghdr *msg, size_t len)
                proto   = po->num;
                addr    = NULL;
        } else {
+               const int sa_len = offsetof(struct sockaddr_ll, sll_addr);
+
                err = -EINVAL;
                if (msg->msg_namelen < sizeof(struct sockaddr_ll))
                        goto out;
                if (msg->msg_namelen < (saddr->sll_halen + offsetof(struct 
sockaddr_ll, sll_addr)))
                        goto out;
                proto   = saddr->sll_protocol;
-               addr    = saddr->sll_halen ? saddr->sll_addr : NULL;
+               addr    = sock->type == SOCK_DGRAM ? saddr->sll_addr : NULL;
                dev = dev_get_by_index(sock_net(sk), saddr->sll_ifindex);
-               if (addr && dev && saddr->sll_halen < dev->addr_len)
+               if (addr && dev && msg->msg_namelen < (sa_len + dev->addr_len))
                        goto out_unlock;
        }
 
-- 
2.21.0.593.g511ec345e18-goog

Reply via email to