changeset 30163:6051c7c4c19f

Ensure that a filename ending in a slash cannot be used to access a non-directory.
author Bruno Haible <bruno@clisp.org>
date Wed, 24 Sep 2008 13:50:02 +0200
parents 6e48a0b21819
children c1343a90f0d5
files ChangeLog doc/posix-functions/fopen.texi doc/posix-functions/open.texi lib/fopen.c lib/open.c modules/fopen tests/test-fopen.c tests/test-open.c
diffstat 8 files changed, 99 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Sep 23 08:56:10 2008 -0600
+++ b/ChangeLog	Wed Sep 24 13:50:02 2008 +0200
@@ -1,3 +1,19 @@
+2008-09-24  Bruno Haible  <bruno@clisp.org>
+
+	Ensure that a filename ending in a slash cannot be used to access a
+	non-directory.
+	* lib/open.c (rpl_open): When the filename ends in a slash, use fstat()
+	to check whether it's really a directory.
+	* lib/fopen.c: Include fcntl.h, unistd.h.
+	(rpl_fopen): When the filename ends in a slash, use open(), fstat(),
+	and fdopen().
+	* modules/fopen (Depends-on): Add unistd.
+	* tests/test-open.c (main): Try to open "/dev/null/" as a directory.
+	* tests/test-fopen.c (main): Likewise.
+	* doc/posix-functions/open.texi: Mention the HP-UX, Solaris bug.
+	* doc/posix-functions/fopen.texi: Likewise.
+	Reported by Eric Blake.
+
 2008-09-23  Eric Blake  <ebb9@byu.net>
 
 	c-stack: avoid compiler optimizations when provoking overflow
--- a/doc/posix-functions/fopen.texi	Tue Sep 23 08:56:10 2008 -0600
+++ b/doc/posix-functions/fopen.texi	Wed Sep 24 13:50:02 2008 +0200
@@ -10,7 +10,8 @@
 @itemize
 @item
 This function does not fail when the file name argument ends in a slash
-and (without the slash) names a nonexistent file, on some platforms:
+and (without the slash) names a nonexistent file or a file that is not a
+directory, on some platforms:
 HP-UX 11.00, Solaris 9.
 @item
 On Windows platforms (excluding Cygwin), this function does usually not
--- a/doc/posix-functions/open.texi	Tue Sep 23 08:56:10 2008 -0600
+++ b/doc/posix-functions/open.texi	Wed Sep 24 13:50:02 2008 +0200
@@ -10,7 +10,8 @@
 @itemize
 @item
 This function does not fail when the file name argument ends in a slash
-and (without the slash) names a nonexistent file, on some platforms:
+and (without the slash) names a nonexistent file or a file that is not a
+directory, on some platforms:
 HP-UX 11.00, Solaris 9.
 @item
 On Windows platforms (excluding Cygwin), this function does usually not
--- a/lib/fopen.c	Tue Sep 23 08:56:10 2008 -0600
+++ b/lib/fopen.c	Wed Sep 24 13:50:02 2008 +0200
@@ -22,12 +22,19 @@
 #include <stdio.h>
 
 #include <errno.h>
+#include <fcntl.h>
 #include <string.h>
+#include <unistd.h>
 
 FILE *
 rpl_fopen (const char *filename, const char *mode)
 #undef fopen
 {
+#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+  if (strcmp (filename, "/dev/null") == 0)
+    filename = "NUL";
+#endif
+
 #if FOPEN_TRAILING_SLASH_BUG
   /* If the filename ends in a slash and a mode that requires write access is
      specified, then fail.
@@ -45,21 +52,41 @@
      fails with errno = EISDIR in this case.
      If the named file does not exist or does not name a directory, then
      fopen() must fail since the file does not contain a '.' directory.  */
-  if (mode[0] == 'w' || mode[0] == 'a')
-    {
-      size_t len = strlen (filename);
-      if (len > 0 && filename[len - 1] == '/')
-	{
-	  errno = EISDIR;
+  {
+    size_t len = strlen (filename);
+    if (len > 0 && filename[len - 1] == '/')
+      {
+	int fd;
+	struct stat statbuf;
+	FILE *fp;
+
+	if (mode[0] == 'w' || mode[0] == 'a')
+	  {
+	    errno = EISDIR;
+	    return NULL;
+	  }
+
+	fd = open (filename, O_RDONLY);
+	if (fd < 0)
 	  return NULL;
-	}
-    }
-#endif
+
+	if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
+	  {
+	    errno = ENOTDIR;
+	    return NULL;
+	  }
 
-#if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
-  if (strcmp (filename, "/dev/null") == 0)
-    filename = "NUL";
-#endif
+	fp = fdopen (fd, mode);
+	if (fp == NULL)
+	  {
+	    int saved_errno = errno;
+	    close (fd);
+	    errno = saved_errno;
+	  }
+	return fp;
+      }
+  }
+# endif
 
   return fopen (filename, mode);
 }
--- a/lib/open.c	Tue Sep 23 08:56:10 2008 -0600
+++ b/lib/open.c	Wed Sep 24 13:50:02 2008 +0200
@@ -35,6 +35,7 @@
 # undef open
 {
   mode_t mode;
+  int fd;
 
   mode = 0;
   if (flags & O_CREAT)
@@ -52,6 +53,11 @@
       va_end (arg);
     }
 
+# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
+  if (strcmp (filename, "/dev/null") == 0)
+    filename = "NUL";
+# endif
+
 # if OPEN_TRAILING_SLASH_BUG
   /* If the filename ends in a slash and one of O_CREAT, O_WRONLY, O_RDWR
      is specified, then fail.
@@ -85,11 +91,37 @@
     }
 # endif
 
-# if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
-  if (strcmp (filename, "/dev/null") == 0)
-    filename = "NUL";
+  fd = open (filename, flags, mode);
+
+# if OPEN_TRAILING_SLASH_BUG
+  /* If the filename ends in a slash and fd does not refer to a directory,
+     then fail.
+     Rationale: POSIX <http://www.opengroup.org/susv3/basedefs/xbd_chap04.html>
+     says that
+       "A pathname that contains at least one non-slash character and that
+        ends with one or more trailing slashes shall be resolved as if a
+        single dot character ( '.' ) were appended to the pathname."
+     and
+       "The special filename dot shall refer to the directory specified by
+        its predecessor."
+     If the named file without the slash is not a directory, open() must fail
+     with ENOTDIR.  */
+  if (fd >= 0)
+    {
+      size_t len = strlen (filename);
+      if (len > 0 && filename[len - 1] == '/')
+	{
+	  struct stat statbuf;
+
+	  if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
+	    {
+	      errno = ENOTDIR;
+	      return -1;
+	    }
+	}
+    }
 # endif
 
-  return open (filename, flags, mode);
+  return fd;
 }
 #endif
--- a/modules/fopen	Tue Sep 23 08:56:10 2008 -0600
+++ b/modules/fopen	Wed Sep 24 13:50:02 2008 +0200
@@ -7,6 +7,7 @@
 
 Depends-on:
 stdio
+unistd
 
 configure.ac:
 gl_FUNC_FOPEN
--- a/tests/test-fopen.c	Tue Sep 23 08:56:10 2008 -0600
+++ b/tests/test-fopen.c	Wed Sep 24 13:50:02 2008 +0200
@@ -37,6 +37,7 @@
 main ()
 {
   ASSERT (fopen ("nonexist.ent/", "w") == NULL);
+  ASSERT (fopen ("/dev/null/", "r") == NULL);
 
   ASSERT (fopen ("/dev/null", "r") != NULL);
 
--- a/tests/test-open.c	Tue Sep 23 08:56:10 2008 -0600
+++ b/tests/test-open.c	Wed Sep 24 13:50:02 2008 +0200
@@ -39,6 +39,7 @@
 main ()
 {
   ASSERT (open ("nonexist.ent/", O_CREAT, 0600) < 0);
+  ASSERT (open ("/dev/null/", O_RDONLY) < 0);
 
   ASSERT (open ("/dev/null", O_RDONLY) >= 0);