NetBSD-Bugs archive

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

kern/40696: FD leak by recvmsg when receiving too much file descriptors with SCM_RIGHTS.



>Number:         40696
>Category:       kern
>Synopsis:       FD leak by recvmsg when receiving too much file descriptors 
>with SCM_RIGHTS.
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Fri Feb 20 04:45:00 +0000 2009
>Originator:     Tanaka Akira
>Release:        NetBSD 4.0.1/amd64
>Organization:
AIST
>Environment:
NetBSD netbsd.tky.aist.go.jp 4.0.1 NetBSD 4.0.1 (GENERIC) #0: Tue Oct  7 
21:00:39 PDT 2008  
builds@wb28:/home/builds/ab/netbsd-4-0-1-RELEASE/amd64/200810080053Z-obj/home/builds/ab/netbsd-4-0-1-RELEASE/src/sys/arch/amd64/compile/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 alocates a FD but doesn't record them.

This means the application cannot close the FD which is not reported.


>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        170   wd /        3869056 drwxr-xr-x     512 r 
akr      a.out        170    0 /        3568894 crw--w----   ttyp0 rw
akr      a.out        170    1 /        3568894 crw--w----   ttyp0 rw
akr      a.out        170    2 /        3568894 crw--w----   ttyp0 rw
akr      a.out        170    3* unix stream ffff800004d20700 <-> 
ffff800004d20680
akr      a.out        170    4* unix stream ffff800004d20680 <-> 
ffff800004d20700
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        170   wd /        3869056 drwxr-xr-x     512 r 
akr      a.out        170    0 /        3568894 crw--w----   ttyp0 rw
akr      a.out        170    1 /        3568894 crw--w----   ttyp0 rw
akr      a.out        170    2 /        3568894 crw--w----   ttyp0 rw
akr      a.out        170    3* unix stream ffff800004d20700 <-> 
ffff800004d20680
akr      a.out        170    4* unix stream ffff800004d20680 <-> 
ffff800004d20700
akr      a.out        170    5 /        3568894 crw--w----   ttyp0 rw
akr      a.out        170    6 /        3568894 crw--w----   ttyp0 rw
akr      a.out        170    7 /        3568894 crw--w----   ttyp0 rw
akr      a.out        170    8 /        3568894 crw--w----   ttyp0 rw
akr      a.out        170   14 /        3568894 crw--w----   ttyp0 rw

This program sends 10 FDs via UNIX domain socket pair and
receives some of them using recvmsg with small control message buffer.

The result shows:
* kernel allocates 5 FDs (5, 6, 7, 8 and 14)
* 4 of them are reported to the application (5, 6, 7, 8)
* The last FD, 14, is not reported to the application.

The application cannot close 14.
>Fix:



Home | Main Index | Thread Index | Old Index