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