Skip site navigation (1)Skip section navigation (2)
Date:      Sun, 11 Apr 1999 23:06:40 +0100 (BST)
From:      Tony Finch <dot@dotat.at>
To:        FreeBSD-gnats-submit@freebsd.org
Subject:   kern/11988: recvmsg with a cmsghdr but no iovec is broken
Message-ID:  <199904112206.XAA02365@shirt.www.demon.net>

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

>Number:         11988
>Category:       kern
>Synopsis:       recvmsg with a cmsghdr but no iovec is broken
>Confidential:   no
>Severity:       critical
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Wed Jun  2 02:30:00 PDT 1999
>Closed-Date:
>Last-Modified:
>Originator:     Tony Finch
>Release:        FreeBSD 3.1-STABLE i386
>Organization:
Demon Internet Ltd
>Environment:

FreeBSD shirt.www.demon.net 3.1-STABLE FreeBSD 3.1-STABLE #15: Thu Apr  1 17:31:19 BST 1999     root@shirt.www.demon.net:/usr/src/sys/compile/SHIRT  i386

>Description:

I have a program (included below) which opens a TCP listen socket and forks
a child with which it communicates over a unix domain socketpair. When a TCP
connection is received it passes the new fd to the child which deals with
the connection while the parent returns to accept(). The fd is passed using
sendmsg() with an empty msg_iov.

If the child process does not provide a pointer to a iovec in the msghdr,
or if the sum of the iov_len fields is zero, then the child only receives
one file descriptor over the socket and subsequent recvmsg()s do not fill
in the msg_control block. Furthermore, if the program is killed with SIGINT
between the first (successful) connection and the second connection (which
would cause the child to exit owing to EBADF) the machine reboots. I don't
have a panic message to show because my test machine is a couple of miles
away and doesn't like the serial console server.

The program does work if the receive iovec has more than zero space.

>How-To-Repeat:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/uio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <time.h>

extern const char *__progname;

static void vwarn(const char *message, va_list ap) {
    int err = errno;

    fprintf(stderr, "%s (%d): ", __progname, (int)getpid());
    vfprintf(stderr, message, ap);
    if(message[strlen(message)-1] == ':')
	fprintf(stderr, " %s (%d)\n", strerror(err), err);
    else
	fprintf(stderr, "\n");
}

#define DO_VWARN \
	va_list ap; \
	va_start(ap, message); \
	vwarn(message, ap); \
	va_end(ap)

static void warn(const char *message, ...) {
    DO_VWARN;
}

static void die(const char *message, ...) {
    DO_VWARN;
    exit(1);
}

static void debug_msg(struct msghdr *msg) {
    warn("msg = %p", msg);
    warn("msg->msg_name = %p", msg->msg_name);
    warn("msg->msg_namelen = %d", msg->msg_namelen);
    warn("msg->msg_iov = %p", msg->msg_iov);
    warn("msg->msg_iovlen = %d", msg->msg_iovlen);
    if(msg->msg_iov) {
	warn("msg->msg_iov->iov_base = %p", msg->msg_iov->iov_base);
	warn("msg->msg_iov->iov_len = %d", msg->msg_iov->iov_len);
	if(msg->msg_iov->iov_base)
	    warn("*msg->msg_iov->iov_base = %d", *msg->msg_iov->iov_base);
    }
    warn("msg->msg_control = %p", msg->msg_control);
    warn("msg->msg_controllen = %d", msg->msg_controllen);
    warn("msg->msg_flags = %d", msg->msg_flags);
    warn("((struct cmsghdr *)msg->msg_control)->cmsg_level = %d", ((struct cmsghdr *)msg->msg_control)->cmsg_level);
    warn("((struct cmsghdr *)msg->msg_control)->cmsg_type = %d", ((struct cmsghdr *)msg->msg_control)->cmsg_type);
    warn("((struct cmsghdr *)msg->msg_control)->cmsg_len = %d", ((struct cmsghdr *)msg->msg_control)->cmsg_len);
    warn("*(int *)CMSG_DATA((struct cmsghdr *)msg->msg_control) = %d", *(int *)CMSG_DATA((struct cmsghdr *)msg->msg_control));
}

static void child_process(void)
{
    int n, fd;
    FILE *fp;
    char byte;
    struct iovec iov;
    struct msghdr msg;
    struct fdcmsg {
	struct cmsghdr cmsg;
	int fd;
    } cmsg;
    fd_set rfds;

    msg.msg_name = NULL;
    msg.msg_namelen = 0;
#if 0
    /* if this code is enabled the bug does not appear */
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    iov.iov_base = &byte;
    iov.iov_len = 1;
#elif 1
    /* this and the next two sections all exhibit the bug */
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    iov.iov_base = &byte;
    iov.iov_len = 0;
#elif 0
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    iov.iov_base = NULL;
    iov.iov_len = 0;
#else
    msg.msg_iov = NULL;
    msg.msg_iovlen = 0;
    iov.iov_base = NULL;
    iov.iov_len = 0;
#endif    
    msg.msg_control = (void *)&cmsg;
    msg.msg_controllen = sizeof(cmsg);
    msg.msg_flags = 0;

    for(;;) {
	FD_ZERO(&rfds);
	FD_SET(3, &rfds);
	if(select(4, &rfds, NULL, NULL, NULL) < 0)
	    die("select:");
	if(!FD_ISSET(3, &rfds))
	   exit(1);
	byte = 255;
	cmsg.cmsg.cmsg_level = 0;
	cmsg.cmsg.cmsg_type = 0;
	cmsg.cmsg.cmsg_len = sizeof(cmsg);
	*(int *)CMSG_DATA(&cmsg.cmsg) = -1;
	debug_msg(&msg);
	n = recvmsg(3, &msg, 0);
	if(n < 0)
	    die("recvmsg:");
	else
	    warn("recvmsg = %d", n);
	debug_msg(&msg);
	fd = *(int *)CMSG_DATA(&cmsg.cmsg);
	if(write(fd, "wibble\n", 7) < 7)
	    die("write:");
	fp = fdopen(fd, "w");
	if(!fp)
	    die("fdopen:");
	fprintf(fp, "connection to pid %d\n", getpid());
	fclose(fp);
    }
}

static int make_child(void) {
    int pid;
    int fd[2];

    if(socketpair(PF_UNIX, SOCK_STREAM, 0, fd) < 0)
	die("socketpair:");
    switch(pid = fork()) {
    case -1:
	die("fork:");
    case 0:
	close(fd[0]);
	dup2(fd[1], 3);
	close(fd[1]);
	child_process();
	exit(0);
    default:
	warn("started pid %d", pid);
	close(fd[1]);
	return(fd[0]);
    }
}

static int make_sock(int port, int backlog) {
    int s;
    int one = 1;
    struct sockaddr_in sa;

    s = socket(PF_INET, SOCK_STREAM, 0);
    if(s < 0)
	die("socket:");
    
    if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (void *)&one, sizeof(one)) < 0)
	die("setsockopt SO_REUSEADDR:");

    memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(port);
    sa.sin_addr.s_addr = INADDR_ANY;
    if(bind(s, (struct sockaddr *)&sa, sizeof(sa)) < 0)
	die("bind:");

    if(listen(s, backlog) < 0)
	die("listen:");

    return(s);
}

int main(int argc, char *argv[]) {
    int sock, child, conn, n;
    char byte;
    struct iovec iov;
    struct msghdr msg;
    struct fdcmsg {
	struct cmsghdr cmsg;
	int fd;
    } cmsg;

    setvbuf(stderr, NULL, _IOLBF, 0);

    sock = make_sock(5000, 10);
    child = make_child();

    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    /* these alternatives don't make any difference */
#if 0
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
#else
    msg.msg_iov = NULL;
    msg.msg_iovlen = 0;
#endif    
    msg.msg_control = (void *)&cmsg;
    msg.msg_controllen = sizeof(cmsg);
    msg.msg_flags = 0;

    iov.iov_base = &byte;
    iov.iov_len = 1;
    byte = 0;

    cmsg.cmsg.cmsg_level = SOL_SOCKET;
    cmsg.cmsg.cmsg_type = SCM_RIGHTS;
    cmsg.cmsg.cmsg_len = sizeof(cmsg);

    for(;;) {
	conn = accept(sock, NULL, 0);
	if(conn < 0) {
	    warn("accept:");
	    continue;
	} else
	    warn("accept = %d", conn);
	*(int *)CMSG_DATA(&cmsg.cmsg) = conn;
	debug_msg(&msg);
	n = sendmsg(child, &msg, 0);
	if(n < 0)
	    warn("sendmsg:");
	else
	    warn("sendmsg = %d", n);
	n = close(conn);
	warn("close = %d", n);
    }
}

>Fix:


>Release-Note:
>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?199904112206.XAA02365>