From nobody@FreeBSD.org  Sat Jun 16 09:38:07 2012
Return-Path: <nobody@FreeBSD.org>
Received: from mx1.freebsd.org (mx1.freebsd.org [69.147.83.52])
	by hub.freebsd.org (Postfix) with ESMTP id 9698B106566B
	for <freebsd-gnats-submit@FreeBSD.org>; Sat, 16 Jun 2012 09:38:07 +0000 (UTC)
	(envelope-from nobody@FreeBSD.org)
Received: from red.freebsd.org (red.freebsd.org [IPv6:2001:4f8:fff6::22])
	by mx1.freebsd.org (Postfix) with ESMTP id 8244F8FC08
	for <freebsd-gnats-submit@FreeBSD.org>; Sat, 16 Jun 2012 09:38:07 +0000 (UTC)
Received: from red.freebsd.org (localhost [127.0.0.1])
	by red.freebsd.org (8.14.4/8.14.4) with ESMTP id q5G9c7tU071556
	for <freebsd-gnats-submit@FreeBSD.org>; Sat, 16 Jun 2012 09:38:07 GMT
	(envelope-from nobody@red.freebsd.org)
Received: (from nobody@localhost)
	by red.freebsd.org (8.14.4/8.14.4/Submit) id q5G9c7Pl071555;
	Sat, 16 Jun 2012 09:38:07 GMT
	(envelope-from nobody)
Message-Id: <201206160938.q5G9c7Pl071555@red.freebsd.org>
Date: Sat, 16 Jun 2012 09:38:07 GMT
From: Jukka Ukkonen <jau@iki.fi>
To: freebsd-gnats-submit@FreeBSD.org
Subject: opendir() does not use O_CLOEXEC ; fdopendir() closed the fd as a side-effect
X-Send-Pr-Version: www-3.1
X-GNATS-Notify:

>Number:         169150
>Category:       kern
>Synopsis:       opendir() does not use O_CLOEXEC ; fdopendir() closed the fd as a side-effect
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Sat Jun 16 09:40:15 UTC 2012
>Closed-Date:    Thu Feb 13 10:30:09 CET 2014
>Last-Modified:  Thu Feb 13 10:30:09 CET 2014
>Originator:     Jukka Ukkonen
>Release:        FreeBSD 9.0-STABLE
>Organization:
-----
>Environment:
FreeBSD sleipnir 9.0-STABLE FreeBSD 9.0-STABLE #0: Sat Jun 16 10:47:48 EEST 2012     root@sleipnir:/usr/obj/usr/src/sys/Sleipnir  amd64
>Description:
If opendir() does not use O_CLOEXEC to open the target file, it allows a window
for another thread to call exec() before FD_CLOEXEC gets set by fcntl().
This may cause the open file descriptor to leak to the child program.
---
For fdopendir() this race cannot be entirely fixed unless the caller handles
the issue.
As a side-effect of fdopendir() failing it closed the file descriptor passed
by the caller. This is an uncommon, unexpected, and undocumented side-effect.
It is better to plug it at the same time.


>How-To-Repeat:
No prepackaged code sample.
>Fix:
Find a draft patch attached.


Patch attached with submission follows:

--- lib/libc/gen/opendir.c.orig	2012-06-16 11:44:44.000000000 +0300
+++ lib/libc/gen/opendir.c	2012-06-16 12:25:36.000000000 +0300
@@ -66,6 +66,28 @@
 DIR *
 fdopendir(int fd)
 {
+	struct stat statb;
+
+	/*
+	 * NOTE!
+	 * The _fstat() + S_ISDIR() directory test and FD_CLOEXEC jugglery
+	 * below are only needed because our caller could actually pass us
+	 * a file descriptor which neither refers a directory nor gets
+	 * automatically closed at exec().
+	 *
+	 * For opendir() these are redundant.
+	 */
+
+	if (_fstat(fd, &statb) != 0)
+		return (NULL);
+
+	if (!S_ISDIR(statb.st_mode)) {
+		errno = ENOTDIR;
+		return (NULL);
+	}
+
+	if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1)
+		return (NULL);
 
 	return (__opendir_common(fd, NULL, DTF_HIDEW|DTF_NODUP));
 }
@@ -74,22 +96,21 @@
 __opendir2(const char *name, int flags)
 {
 	int fd;
-	struct stat statb;
+	DIR *dir;
 
-	/*
-	 * stat() before _open() because opening of special files may be
-	 * harmful.
-	 */
-	if (stat(name, &statb) != 0)
-		return (NULL);
-	if (!S_ISDIR(statb.st_mode)) {
-		errno = ENOTDIR;
+	if ((fd = _open(name,
+			O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC)) == -1)
 		return (NULL);
+
+	dir = __opendir_common(fd, name, flags);
+
+	if (dir == NULL) {
+		int saved_errno = errno;
+		(void) _close (fd);
+		errno = saved_errno;
 	}
-	if ((fd = _open(name, O_RDONLY | O_NONBLOCK | O_DIRECTORY)) == -1)
-		return (NULL);
 
-	return __opendir_common(fd, name, flags);
+	return (dir);
 }
 
 static int
@@ -112,15 +133,8 @@
 	int unionstack;
 
 	dirp = NULL;
-	/* _fstat() the open handler because the file may have changed.  */
-	if (_fstat(fd, &statb) != 0)
-		goto fail;
-	if (!S_ISDIR(statb.st_mode)) {
-		errno = ENOTDIR;
-		goto fail;
-	}
-	if (_fcntl(fd, F_SETFD, FD_CLOEXEC) == -1 ||
-	    (dirp = malloc(sizeof(DIR) + sizeof(struct _telldir))) == NULL)
+
+	if ((dirp = malloc(sizeof(DIR) + sizeof(struct _telldir))) == NULL)
 		goto fail;
 
 	dirp->dd_td = (struct _telldir *)((char *)dirp + sizeof(DIR));
@@ -199,7 +213,8 @@
 		 */
 		if (flags & DTF_REWIND) {
 			(void)_close(fd);
-			if ((fd = _open(name, O_RDONLY | O_DIRECTORY)) == -1) {
+			if ((fd = _open(name,
+					O_RDONLY | O_DIRECTORY | O_CLOEXEC)) == -1) {
 				saved_errno = errno;
 				free(buf);
 				free(dirp);
@@ -248,7 +263,7 @@
 				 * This sort must be stable.
 				 */
 				mergesort(dpv, n, sizeof(*dpv),
-				    opendir_compar);
+					  opendir_compar);
 
 				dpv[n] = NULL;
 				xp = NULL;
@@ -308,7 +323,6 @@
 fail:
 	saved_errno = errno;
 	free(dirp);
-	(void)_close(fd);
 	errno = saved_errno;
 	return (NULL);
 }


>Release-Note:
>Audit-Trail:
State-Changed-From-To: open->closed 
State-Changed-By: brueffer 
State-Changed-When: Thu Feb 13 10:27:33 CET 2014 
State-Changed-Why:  
opendir() was changed to use O_CLOEXEC in HEAD a while ago (r241046). The change was merged 
to 9-STABLE yesterday (r261813).  Thanks for the report! 

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