From nobody@FreeBSD.org  Thu Feb 19 15:18:18 2009
Return-Path: <nobody@FreeBSD.org>
Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34])
	by hub.freebsd.org (Postfix) with ESMTP id 3746710656C2
	for <freebsd-gnats-submit@FreeBSD.org>; Thu, 19 Feb 2009 15:18:18 +0000 (UTC)
	(envelope-from nobody@FreeBSD.org)
Received: from www.freebsd.org (www.freebsd.org [IPv6:2001:4f8:fff6::21])
	by mx1.freebsd.org (Postfix) with ESMTP id 23AD38FC15
	for <freebsd-gnats-submit@FreeBSD.org>; Thu, 19 Feb 2009 15:18:18 +0000 (UTC)
	(envelope-from nobody@FreeBSD.org)
Received: from www.freebsd.org (localhost [127.0.0.1])
	by www.freebsd.org (8.14.3/8.14.3) with ESMTP id n1JFIHNC006982
	for <freebsd-gnats-submit@FreeBSD.org>; Thu, 19 Feb 2009 15:18:17 GMT
	(envelope-from nobody@www.freebsd.org)
Received: (from nobody@localhost)
	by www.freebsd.org (8.14.3/8.14.3/Submit) id n1JFIHQm006981;
	Thu, 19 Feb 2009 15:18:17 GMT
	(envelope-from nobody)
Message-Id: <200902191518.n1JFIHQm006981@www.freebsd.org>
Date: Thu, 19 Feb 2009 15:18:17 GMT
From: Tanaka Akira <akr@fsij.org>
To: freebsd-gnats-submit@FreeBSD.org
Subject: FD leak by receiving SCM_RIGHTS by recvmsg with small control message buffer
X-Send-Pr-Version: www-3.1
X-GNATS-Notify:

>Number:         131876
>Category:       kern
>Synopsis:       [socket] FD leak by receiving SCM_RIGHTS by recvmsg with small control message buffer
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    rwatson
>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:  Mon Feb 23 09:01:16 UTC 2009
>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@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:
Responsible-Changed-From-To: freebsd-bugs->rwatson 
Responsible-Changed-By: rwatson 
Responsible-Changed-When: Sun Feb 22 18:19:06 UTC 2009 
Responsible-Changed-Why:  
Take ownership of this as I've been spending some time with UNIX domain 
socket sockets. 

http://www.freebsd.org/cgi/query-pr.cgi?pr=131876 
>Unformatted:
