kern/131876: FD leak by receiving SCM_RIGHTS by recvmsg with small
control message buffer
Tanaka Akira
akr at fsij.org
Thu Feb 19 07:20:01 PST 2009
>Number: 131876
>Category: kern
>Synopsis: FD leak by receiving SCM_RIGHTS by recvmsg with small control message buffer
>Confidential: no
>Severity: non-critical
>Priority: medium
>Responsible: freebsd-bugs
>State: open
>Quarter:
>Keywords:
>Date-Required:
>Class: sw-bug
>Submitter-Id: current-users
>Arrival-Date: Thu Feb 19 15:20:00 UTC 2009
>Closed-Date:
>Last-Modified:
>Originator: Tanaka Akira
>Release: FreeBSD 6.4-RELEASE amd64
>Organization:
AIST
>Environment:
FreeBSD freebsd.tky.aist.go.jp 6.4-RELEASE FreeBSD 6.4-RELEASE #0: Wed Nov 26 08:21:48 UTC 2008 root at palmer.cse.buffalo.edu:/usr/obj/usr/src/sys/GENERIC amd64
>Description:
When recvmsg receives SCM_RIGHTS control message,
it allocates file descriptors and report them in the control message buffer given by the application.
If the buffer is too small to record the FDs,
recvmsg allocates FDs but doesn't record them.
This means the application cannot close those FDs which is not reported.
i.e. FDs leaks.
>How-To-Repeat:
% cat tst.c
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <unistd.h>
#define MAX_FDS 10
#define SEND_FDS 10
#define RECV_FDS 3
int main(int argc, char **argv)
{
int ret;
int sv[2];
struct msghdr msg;
struct iovec iov;
union {
struct cmsghdr header;
char bytes[CMSG_SPACE(sizeof(int)*MAX_FDS)];
} cmsg;
struct cmsghdr *cmh = &cmsg.header, *c;
int *fds;
int i;
char buf[1024];
char cmdline[1024];
snprintf(cmdline, sizeof(cmdline), "fstat -p %u", (unsigned)getpid());
ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sv);
if (ret == -1) { perror("socketpair"); exit(1); }
iov.iov_base = "a";
iov.iov_len = 1;
cmh->cmsg_len = CMSG_LEN(sizeof(int)*SEND_FDS);
cmh->cmsg_level = SOL_SOCKET;
cmh->cmsg_type = SCM_RIGHTS;
fds = (int *)CMSG_DATA(cmh);
for (i = 0; i < SEND_FDS; i++) {
fds[i] = 0; /* stdin */
}
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmh;
msg.msg_controllen = CMSG_SPACE(sizeof(int)*SEND_FDS);
msg.msg_flags = 0;
ret = sendmsg(sv[0], &msg, 0);
if (ret == -1) { perror("sendmsg"); exit(1); }
system(cmdline); /* fstat -p $$ before recvmsg */
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmh;
msg.msg_controllen = CMSG_SPACE(sizeof(int)*RECV_FDS);
msg.msg_flags = 0;
printf("before recvmsg: msg_controllen=%d\n", msg.msg_controllen);
ret = recvmsg(sv[1], &msg, 0);
if (ret == -1) { perror("sendmsg"); exit(1); }
printf("after recvmsg: msg_controllen=%d\n", msg.msg_controllen);
for (c = CMSG_FIRSTHDR(&msg); c != NULL; c = CMSG_NXTHDR(&msg, c)) {
if (c->cmsg_len == 0) { printf("cmsg_len is zero\n"); exit(1); }
if (c->cmsg_level == SOL_SOCKET && c->cmsg_type == SCM_RIGHTS) {
int *fdp, *end;
printf("cmsg_len=%d\n", c->cmsg_len);
fdp = (int *)CMSG_DATA(c);
end = (int *)((char *)c + c->cmsg_len);
for (i = 0; fdp+i < end; i++) {
printf("fd[%d]=%d\n", i, fdp[i]);
}
}
}
system(cmdline); /* fstat -p $$ after recvmsg */
return 0;
}
% gcc -Wall tst.c
% ./a.out
USER CMD PID FD MOUNT INUM MODE SZ|DV R/W
akr a.out 9822 root / 2 drwxr-xr-x 512 r
akr a.out 9822 wd / 32142 drwxr-xr-x 512 r
akr a.out 9822 text / 32138 -rwxr-xr-x 9286 r
akr a.out 9822 0 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 1 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 2 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 3* local stream ffffff00613da320 <-> ffffff00613da258
akr a.out 9822 4* local stream ffffff00613da258 <-> ffffff00613da320
before recvmsg: msg_controllen=32
after recvmsg: msg_controllen=32
cmsg_len=56
fd[0]=5
fd[1]=6
fd[2]=7
fd[3]=8
fd[4]=0
fd[5]=0
fd[6]=0
fd[7]=0
fd[8]=0
fd[9]=0
USER CMD PID FD MOUNT INUM MODE SZ|DV R/W
akr a.out 9822 root / 2 drwxr-xr-x 512 r
akr a.out 9822 wd / 32142 drwxr-xr-x 512 r
akr a.out 9822 text / 32138 -rwxr-xr-x 9286 r
akr a.out 9822 0 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 1 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 2 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 3* local stream ffffff00613da320 <-> ffffff00613da258
akr a.out 9822 4* local stream ffffff00613da258 <-> ffffff00613da320
akr a.out 9822 5 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 6 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 7 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 8 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 9 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 10 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 11 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 12 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 13 /dev 97 crw--w---- ttyp0 rw
akr a.out 9822 14 /dev 97 crw--w---- ttyp0 rw
This program sends 10 file descriptors via UNIX domain socket pair and
receives some of them using a small buffer.
This result shows
* recvmsg allocates 10 FDs (5-14)
* 4 of them (5-8) are reported to the application
* 6 of them (9-14) are not reported to the application
So the application cannot close the 6 file descriptors.
>Fix:
>Release-Note:
>Audit-Trail:
>Unformatted:
More information about the freebsd-bugs
mailing list