Bugs in Hurd's recvmsg (msg_name AF_LOCAL, MSG_DONTWAIT)
- To: svante.signell@gmail.com
- Cc: debian-hurd@lists.debian.org, Bug hurd mailing list <bug-hurd@gnu.org>
- Subject: Bugs in Hurd's recvmsg (msg_name AF_LOCAL, MSG_DONTWAIT)
- From: Christian Seiler <christian@iwakd.de>
- Date: Mon, 1 Aug 2016 12:08:01 +0200
- Message-id: <[🔎] 5aedd85f-312a-c917-bfe5-18a67f8ffd2d@iwakd.de>
- In-reply-to: <1469640326.20491.16.camel@gmail.com>
- References: <5794BFB4.6040800@iwakd.de> <20160726141519.GA29519@shattrath> <e9a46eba-3187-e2d6-688d-d51afa684d51@iwakd.de> <1469561900.20491.2.camel@gmail.com> <5797D46E.5010403@iwakd.de> <1469614917.20491.6.camel@gmail.com> <bfc46688-4197-96ad-32c7-7d4d3a378063@iwakd.de> <B7C1808B-DB01-4323-AD8B-AA46721027F4@iwakd.de> <1469640326.20491.16.camel@gmail.com>
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: