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