I need to send some ancillary data over a Unix domain socket, but I'm having problems creating a msghdr
. I can only seem to access the first cmsghdr
in msghdr.msg_control
. CMSG_NXTHDR()
returns NULL no matter how large I make msghdr.msg_control
.
I'm running 64-bit Linux 3.13.0 with eglibc
2.19, if that matters. Here is some sample code that demonstrates the problem. I compiled it with gcc -std=c99 -Wall -Werror -Wpedantic test.c
. I'm aware that I could send both file descriptors in the same message—this is just a test.
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
int main()
{
int data1 = STDIN_FILENO;
int data2 = STDOUT_FILENO;
char control[CMSG_SPACE(sizeof(data1)) + CMSG_SPACE(sizeof(data2))];
struct msghdr mh = {
.msg_namelen = 0,
.msg_iovlen = 0,
.msg_control = control,
.msg_controllen = sizeof(control),
.msg_flags = 0
};
struct cmsghdr *cmh = CMSG_FIRSTHDR(&mh);
if (cmh == NULL) {
puts("Can't get first cmsg");
return 1;
}
cmh->cmsg_len = CMSG_LEN(sizeof(data1));
cmh->cmsg_level = SOL_SOCKET;
cmh->cmsg_type = SCM_RIGHTS;
memcpy(CMSG_DATA(cmh), &data1, sizeof(data1));
cmh = CMSG_NXTHDR(&mh, cmh);
if (cmh == NULL) {
puts("Can't get second cmsg");
return 1;
}
cmh->cmsg_len = CMSG_LEN(sizeof(data2));
cmh->cmsg_level = SOL_SOCKET;
cmh->cmsg_type = SCM_RIGHTS;
memcpy(CMSG_DATA(cmh), &data2, sizeof(data2));
}
When I build and run this program, it prints "Can't get second cmsg". This is true even if I increase the size of control
by hundreds of bytes. Apparently CMSG_NXTHDR()
doesn't think there is a second cmsghdr
in .msg_control
. How can I convince it that there are two of them, not just one?
I had couple of hours wasted on this issue, so thought document what I found. It is a case of uninitialised control buffer. The man pages does not instruct about this specifically but if you look in the header file /usr/include/bits/socket.h, the code reads as
_EXTERN_INLINE struct cmsghdr *
__NTH (__cmsg_nxthdr (struct msghdr *__mhdr, struct cmsghdr *__cmsg))
{
if ((size_t) __cmsg->cmsg_len < sizeof (struct cmsghdr))
/* The kernel header does this so there may be a reason. */
return (struct cmsghdr *) 0;
__cmsg = (struct cmsghdr *) ((unsigned char *) __cmsg
+ CMSG_ALIGN (__cmsg->cmsg_len));
if ((unsigned char *) (__cmsg + 1) > ((unsigned char *) __mhdr->msg_control
+ __mhdr->msg_controllen)
|| ((unsigned char *) __cmsg + CMSG_ALIGN (__cmsg->cmsg_len)
> ((unsigned char *) __mhdr->msg_control + __mhdr->msg_controllen)))
/* No more entries. */
return (struct cmsghdr *) 0;
return __cmsg;
}
After the first check, the __cmsg is made to point to an address which is treated as the next header. Subsequently, the rhs expression on second if statements, uses the value of __cmsg->cmsg_len which could be a garbage if you have not initialised the control buffer first. Depending on the garbage value, you could have the possibility of this check failing and thus resulting in a null value no matter how much you pump up the size of control buffer.
Solution could be as simple as(from your code)
char control[CMSG_SPACE(sizeof(data1)) + CMSG_SPACE(sizeof(data2))] = {};
That did it for me.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With