From: Bruno Haible Date: Wed, 24 Sep 2008 11:50:02 +0000 (+0200) Subject: Ensure that a filename ending in a slash cannot be used to access a non-directory. X-Git-Tag: v0.1~7010 X-Git-Url: http://erislabs.org.uk/gitweb/?a=commitdiff_plain;h=955345ebebdae2b894b5a3d8e21036335cb13e5d;p=gnulib.git Ensure that a filename ending in a slash cannot be used to access a non-directory. --- diff --git a/ChangeLog b/ChangeLog index 858614354..22bf905ac 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2008-09-24 Bruno Haible + + 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 c-stack: avoid compiler optimizations when provoking overflow diff --git a/doc/posix-functions/fopen.texi b/doc/posix-functions/fopen.texi index 442d1b665..5fdb8dac4 100644 --- a/doc/posix-functions/fopen.texi +++ b/doc/posix-functions/fopen.texi @@ -10,7 +10,8 @@ Portability problems fixed by Gnulib: @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 diff --git a/doc/posix-functions/open.texi b/doc/posix-functions/open.texi index bb982cfae..ed3155da8 100644 --- a/doc/posix-functions/open.texi +++ b/doc/posix-functions/open.texi @@ -10,7 +10,8 @@ Portability problems fixed by Gnulib: @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 diff --git a/lib/fopen.c b/lib/fopen.c index 514300183..f64122b9f 100644 --- a/lib/fopen.c +++ b/lib/fopen.c @@ -22,12 +22,19 @@ #include #include +#include #include +#include 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 @@ rpl_fopen (const char *filename, const char *mode) 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); } diff --git a/lib/open.c b/lib/open.c index dfac4919d..317fb8afa 100644 --- a/lib/open.c +++ b/lib/open.c @@ -35,6 +35,7 @@ open (const char *filename, int flags, ...) # undef open { mode_t mode; + int fd; mode = 0; if (flags & O_CREAT) @@ -52,6 +53,11 @@ open (const char *filename, int flags, ...) 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 @@ open (const char *filename, int flags, ...) } # 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 + 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 diff --git a/modules/fopen b/modules/fopen index 434cf1786..58727455e 100644 --- a/modules/fopen +++ b/modules/fopen @@ -7,6 +7,7 @@ m4/fopen.m4 Depends-on: stdio +unistd configure.ac: gl_FUNC_FOPEN diff --git a/tests/test-fopen.c b/tests/test-fopen.c index f1b7b6d32..337a38919 100644 --- a/tests/test-fopen.c +++ b/tests/test-fopen.c @@ -37,6 +37,7 @@ int main () { ASSERT (fopen ("nonexist.ent/", "w") == NULL); + ASSERT (fopen ("/dev/null/", "r") == NULL); ASSERT (fopen ("/dev/null", "r") != NULL); diff --git a/tests/test-open.c b/tests/test-open.c index aad36c4ad..0eb8a3309 100644 --- a/tests/test-open.c +++ b/tests/test-open.c @@ -39,6 +39,7 @@ int main () { ASSERT (open ("nonexist.ent/", O_CREAT, 0600) < 0); + ASSERT (open ("/dev/null/", O_RDONLY) < 0); ASSERT (open ("/dev/null", O_RDONLY) >= 0);