[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bugs in Hurd's recvmsg (msg_name AF_LOCAL, MSG_DONTWAIT)



Hi,

I've now isolated the problems and have a reproducer (attached)
and it has _nothing_ to do with SCM_CREDS, but with much more
basic functionality.

Problem 1 (causing SIGLOST):

When msg_name and msg_namelen are filled for a SOCK_STREAM AF_LOCAL
socket (maybe also other AF_LOCAL, didn't check) upon calling
recvmsg, SIGLOST is generated. After reading up on this a bit, it
appears to be that Hurd doesn't have peer names for anoymous
AF_LOCAL sockets (in contrast to Linux and kFreeBSD) - but even in
that case, I would've expected errno = EINVAL and not SIGLOST, so
this is definitely a bug.

Problem 2:

MSG_DONTWAIT is ignored for recvmsg, it always blocks (at least
when using AF_LOCK sockets). I've even tried to pre-fill the
msg_flags member with it (which you shouldn't need to do
according to POSIX, the flags argument should be enough), but
that also doesn't help.

I've attached a simple reproducer:

 gcc -Wall -o hurd_recvmsg hurd_recvmsg.c
 [shell 1] ./hurd_recvmsg server
 [shell 2] ./hurd_recvmsg
    => works on Hurd

 gcc -Wall -o hurd_recvmsg hurd_recvmsg.c -DMSG_NAME
 [shell 1] ./hurd_recvmsg server
 [shell 2] ./hurd_recvmsg
    => SIGLOST on server
       (even if you don't want to support peer names in
       AF_LOCAL, I'd expect recvmsg to either ignore msg_name
       or at the very least return -EINVAL - but not cause
       the program to crash with a signal that's Hurd-specific
       and hence most programs don't catch

 gcc -Wall -o hurd_recvmsg hurd_recvmsg.c -DSECOND_RECV
 [shell 1] ./hurd_recvmsg server
 [shell 2] ./hurd_recvmsg
    => hangs on recv in server (even though MSG_DONTWAIT
       is passed to the second call)

All three variants (+ the combined variant with both defines) work
on kFreeBSD, so this is a Hurd issue.

Tricky thing is: I can easily work around Problem 1, but I don't
see any way of working around problem 2 in the code, because having
working non-blocking I/O is absolutely essential. (I also wonder
why nobody ever found this bug before? This appears to be really
easy to trigger...)

Anyway, would be great if someone could look at Bug #2 here, because
I don't see any way to work around it.

Thank you!

Regards,
Christian
#include <sys/socket.h>
#include <sys/poll.h>
#include <sys/time.h>
#include <sys/un.h>
#include <sys/uio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
#include <fcntl.h>
#include <stddef.h>

#define SOCKET "test-socket"

static int do_recv(int sock, char *buffer, size_t count, struct cmsgcred **cred, int dontwait)
{
	struct cmsgcred *cred_buf;
	unsigned char control[CMSG_SPACE(sizeof(*cred_buf)) + 1024];
	struct cmsghdr  *cmsg;
	struct msghdr   msg;
	struct iovec    iov;
	int             len;
	char name_buf[256];

	if (dontwait)
		dontwait = MSG_DONTWAIT;

	iov.iov_base = buffer;
	iov.iov_len = count;

	memset(&msg, 0, sizeof(msg));
#ifdef MSG_NAME
	msg.msg_name = name_buf;
	msg.msg_namelen = sizeof(name_buf);
#endif
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	msg.msg_control = control;
	msg.msg_controllen = sizeof(control);
	msg.msg_flags = dontwait;

	len = recvmsg(sock, &msg, dontwait);

	if (len < 0)
		return len;

	cmsg = CMSG_FIRSTHDR(&msg);
	while (cmsg) {
		if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_CREDS) {
			cred_buf = malloc(sizeof(*cred_buf));
			memcpy(cred_buf, CMSG_DATA(cmsg), sizeof(*cred_buf));
			*cred = cred_buf;
			break;
		}
		cmsg = CMSG_NXTHDR(&msg, cmsg);
	}

	return len;
}

static int do_send(int sock, const char *buffer, size_t count)
{
	unsigned char   control[CMSG_SPACE(sizeof(struct cmsgcred))];
	struct cmsghdr  *cmsg;
	struct msghdr   msg;
	struct iovec    iov;

	iov.iov_base = (void *)buffer;
	iov.iov_len = count;

	memset(&msg, 0, sizeof(msg));
	msg.msg_iov = &iov;
	msg.msg_iovlen = 1;
	
	memset(&control, 0, sizeof(control));
	msg.msg_control = control;
	msg.msg_controllen = sizeof(control);

	cmsg = CMSG_FIRSTHDR(&msg);
	cmsg->cmsg_len = CMSG_LEN(sizeof(struct cmsgcred));
	cmsg->cmsg_level = SOL_SOCKET;
	cmsg->cmsg_type = SCM_CREDS;

	return sendmsg(sock, &msg, 0);
}

#define DO(_a) \
	do { \
		int r = (_a); \
		if (r < 0) { \
			perror(#_a); \
			exit(1); \
		} \
	} while(0)

int main(int argc, char **argv)
{
	int sock;
	int server = 0;
	struct sockaddr_storage addr_buf;
	struct sockaddr_un *addr_un = (struct sockaddr_un *)&addr_buf;
	struct sockaddr *addr = (struct sockaddr *)&addr_buf;
	socklen_t addr_len;
	int v;
	struct pollfd f;
	
	if (argc > 1 && !strcmp(argv[1], "server"))
		server = 1;
	
	addr_un->sun_family = AF_UNIX;
	strcpy(addr_un->sun_path, SOCKET); /* kids, don't do this in real code, this is a buffer overflow in waiting */
	addr_len = offsetof(struct sockaddr_un, sun_path) + strlen(SOCKET);
	
	DO(sock = socket(AF_LOCAL, SOCK_STREAM, 0));
	
	if (server) {
		int csock;
		struct cmsgcred *creds, *creds2;
		int v2, r;

		unlink(SOCKET);
		DO(bind(sock, addr, addr_len));
		DO(listen(sock, 1));
		DO(csock = accept(sock, NULL, NULL));
		close(sock);
		DO(do_recv(csock, (char *)&v, sizeof(int), &creds, 0));
#ifdef SECOND_RECV
		r = do_recv(csock, (char *)&v2, sizeof(int), &creds2, 1); /* set MSG_DONTWAIT on second message */
		if (r == 0 || errno != EAGAIN) {
			printf("Warning: second receive didn't give us -EAGAIN, got rv=%d, errno=%d (%m).\n", r, errno);
		}
#endif
		if (!creds) {
			printf("no creds, v = %d\n", v);
			return 0;
		}
		printf("creds->uid = %d, v = %d\n", (int)creds->cmcred_uid, v);
		return 0;
	}
	
	DO(connect(sock, addr, addr_len));
	
	v = 42;
	DO(do_send(sock, (const char *)&v, sizeof(int)));
	f.fd = sock;
	f.events = POLLIN;
	f.revents = 0;
	/* wait for server to exit and close socket */
	poll(&f, 1, -1);
	printf("Send successful.\n");
	return 0;
}

Reply to: