+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
@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
@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
#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.
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 (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__
- if (strcmp (filename, "/dev/null") == 0)
- filename = "NUL";
-#endif
+ if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
+ {
+ errno = ENOTDIR;
+ return NULL;
+ }
+
+ fp = fdopen (fd, mode);
+ if (fp == NULL)
+ {
+ int saved_errno = errno;
+ close (fd);
+ errno = saved_errno;
+ }
+ return fp;
+ }
+ }
+# endif
return fopen (filename, mode);
}
# undef open
{
mode_t mode;
+ int fd;
mode = 0;
if (flags & O_CREAT)
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.
}
# 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
Depends-on:
stdio
+unistd
configure.ac:
gl_FUNC_FOPEN
main ()
{
ASSERT (fopen ("nonexist.ent/", "w") == NULL);
+ ASSERT (fopen ("/dev/null/", "r") == NULL);
ASSERT (fopen ("/dev/null", "r") != NULL);
main ()
{
ASSERT (open ("nonexist.ent/", O_CREAT, 0600) < 0);
+ ASSERT (open ("/dev/null/", O_RDONLY) < 0);
ASSERT (open ("/dev/null", O_RDONLY) >= 0);