From c13ca15f1a9efad6c85e2debf5bcc54db425d017 Mon Sep 17 00:00:00 2001 From: Eric Blake Date: Sat, 14 Nov 2009 08:17:44 -0700 Subject: [PATCH] openat: detect Solaris fchownat bug Solaris 9 fchownat(dir,"name/",uid,gid,flag) has same bugs as chown and lchown. * lib/fchownat.c (rpl_fchownat): Work around Solaris bug. Avoid penalizing glibc chownat when only lchownat is broken. * m4/openat.m4 (gl_FUNC_FCHOWNAT): Replace fchownat if there are trailing slash bugs. * doc/posix-functions/fchownat.texi (fchownat): Document the bug. * modules/openat-tests (Files): Include more files. (Depends-on): Add mgetgroups, sleep, stat-time. (configure.ac): Add additional checks. (Makefile.am): Build new test. * tests/test-fchownat.c: New file. Signed-off-by: Eric Blake --- ChangeLog | 12 ++++++ doc/posix-functions/fchownat.texi | 13 +++++- lib/fchownat.c | 79 +++++++++++++++++++++++++++++++--- m4/openat.m4 | 10 ++++- modules/openat-tests | 13 +++++- tests/test-fchownat.c | 89 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 204 insertions(+), 12 deletions(-) create mode 100644 tests/test-fchownat.c diff --git a/ChangeLog b/ChangeLog index f0e2a18df..5c8f1b68d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,17 @@ 2009-11-14 Eric Blake + openat: detect Solaris fchownat bug + * lib/fchownat.c (rpl_fchownat): Work around Solaris bug. Avoid + penalizing glibc chownat when only lchownat is broken. + * m4/openat.m4 (gl_FUNC_FCHOWNAT): Replace fchownat if there are + trailing slash bugs. + * doc/posix-functions/fchownat.texi (fchownat): Document the bug. + * modules/openat-tests (Files): Include more files. + (Depends-on): Add mgetgroups, sleep, stat-time. + (configure.ac): Add additional checks. + (Makefile.am): Build new test. + * tests/test-fchownat.c: New file. + lchown: detect Solaris and FreeBSD bug * lib/lchown.c (rpl_lchown): Work around bug. * m4/lchown.m4 (gl_FUNC_LCHOWN): Check for trailing slash bugs. diff --git a/doc/posix-functions/fchownat.texi b/doc/posix-functions/fchownat.texi index 7ddd3f37a..6285750a4 100644 --- a/doc/posix-functions/fchownat.texi +++ b/doc/posix-functions/fchownat.texi @@ -9,10 +9,21 @@ Gnulib module: openat Portability problems fixed by Gnulib: @itemize @item +Some platforms fail to detect trailing slash on non-directories, as in +@code{fchown(dir,"link-to-file/",uid,gid,flag)}: +Solaris 9. +@item +Some platforms mistakenly dereference symlinks when using +@code{AT_SYMLINK_NOFOLLOW}: +Linux kernel 2.6.17. +@item This function is missing on some platforms: glibc 2.3.6, MacOS X 10.3, FreeBSD 6.0, NetBSD 3.0, OpenBSD 3.8, AIX 5.1, HP-UX 11, IRIX 6.5, OSF/1 5.1, Cygwin 1.5.x, mingw, Interix 3.5, BeOS. -But the replacement function is not safe to be used in libraries and is not multithread-safe. +But the replacement function is not safe to be used in libraries and +is not multithread-safe. Also, the replacement may fail to change +symlinks if @code{lchown} is unsupported, or fail altogether if +@code{chown} is unsupported. @end itemize Portability problems not fixed by Gnulib: diff --git a/lib/fchownat.c b/lib/fchownat.c index 09b4aa8af..0492dccf1 100644 --- a/lib/fchownat.c +++ b/lib/fchownat.c @@ -25,6 +25,13 @@ #include +#include +#include + +#include "openat.h" + +#if !HAVE_FCHOWNAT + /* Replacement for Solaris' function by the same name. Invoke chown or lchown on file, FILE, using OWNER and GROUP, in the directory open on descriptor FD. If FLAG is AT_SYMLINK_NOFOLLOW, then @@ -33,10 +40,68 @@ then (chown|lchown)/restore_cwd. If either the save_cwd or the restore_cwd fails, then give a diagnostic and exit nonzero. */ -#define AT_FUNC_NAME fchownat -#define AT_FUNC_F1 lchown -#define AT_FUNC_F2 chown -#define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW -#define AT_FUNC_POST_FILE_PARAM_DECLS , uid_t owner, gid_t group, int flag -#define AT_FUNC_POST_FILE_ARGS , owner, group -#include "at-func.c" +# define AT_FUNC_NAME fchownat +# define AT_FUNC_F1 lchown +# define AT_FUNC_F2 chown +# define AT_FUNC_USE_F1_COND AT_SYMLINK_NOFOLLOW +# define AT_FUNC_POST_FILE_PARAM_DECLS , uid_t owner, gid_t group, int flag +# define AT_FUNC_POST_FILE_ARGS , owner, group +# include "at-func.c" +# undef AT_FUNC_NAME +# undef AT_FUNC_F1 +# undef AT_FUNC_F2 +# undef AT_FUNC_USE_F1_COND +# undef AT_FUNC_POST_FILE_PARAM_DECLS +# undef AT_FUNC_POST_FILE_ARGS + +#else /* HAVE_FCHOWNAT */ + +# undef fchownat + +# if FCHOWNAT_NOFOLLOW_BUG + +/* Failure to handle AT_SYMLINK_NOFOLLOW requires the /proc/self/fd or + fchdir workaround to call lchown for lchownat, but there is no need + to penalize chownat. */ +static int +local_lchownat (int fd, char const *file, uid_t owner, gid_t group); + +# define AT_FUNC_NAME local_lchownat +# define AT_FUNC_F1 lchown +# define AT_FUNC_POST_FILE_PARAM_DECLS , uid_t owner, gid_t group +# define AT_FUNC_POST_FILE_ARGS , owner, group +# include "at-func.c" +# undef AT_FUNC_NAME +# undef AT_FUNC_F1 +# undef AT_FUNC_POST_FILE_PARAM_DECLS +# undef AT_FUNC_POST_FILE_ARGS + +# endif + +/* Work around bugs with trailing slash, using the same workarounds as + chown and lchown. */ + +int +rpl_fchownat (int fd, char const *file, uid_t owner, gid_t group, int flag) +{ +# if FCHOWNAT_NOFOLLOW_BUG + if (flag == AT_SYMLINK_NOFOLLOW) + return local_lchownat (fd, file, owner, group); +# endif +# if CHOWN_TRAILING_SLASH_BUG + { + size_t len = strlen (file); + struct stat st; + if (len && file[len - 1] == '/') + { + if (statat (fd, file, &st)) + return -1; + if (flag == AT_SYMLINK_NOFOLLOW) + return fchownat (fd, file, owner, group, 0); + } + } +# endif + return fchownat (fd, file, owner, group, flag); +} + +#endif /* HAVE_FCHOWNAT */ diff --git a/m4/openat.m4 b/m4/openat.m4 index 6b4f95c76..e6ea25ea6 100644 --- a/m4/openat.m4 +++ b/m4/openat.m4 @@ -1,4 +1,4 @@ -# serial 25 +# serial 26 # See if we need to use our replacement for Solaris' openat et al functions. dnl Copyright (C) 2004-2009 Free Software Foundation, Inc. @@ -102,9 +102,15 @@ main () # Also use the replacement function if fchownat is simply not available. AC_DEFUN([gl_FUNC_FCHOWNAT], [ + AC_REQUIRE([gl_FUNC_CHOWN]) AC_CHECK_FUNC([fchownat], - [gl_FUNC_FCHOWNAT_DEREF_BUG([REPLACE_FCHOWNAT=1])], + [gl_FUNC_FCHOWNAT_DEREF_BUG([REPLACE_FCHOWNAT=1 + AC_DEFINE([FCHOWNAT_NOFOLLOW_BUG], [1], [Define to 1 if your + platform has fchownat, but it cannot perform lchown tasks.])])], [HAVE_FCHOWNAT=0]) + if test $REPLACE_CHOWN = 1; then + REPLACE_FCHOWNAT=1 + fi if test $HAVE_FCHOWNAT = 0 || test $REPLACE_FCHOWNAT = 1; then AC_LIBOBJ([fchownat]) fi diff --git a/modules/openat-tests b/modules/openat-tests index 1440a9bf9..62cef8820 100644 --- a/modules/openat-tests +++ b/modules/openat-tests @@ -1,24 +1,33 @@ Files: +tests/test-chown.h +tests/test-lchown.h tests/test-lstat.h tests/test-mkdir.h tests/test-rmdir.h tests/test-stat.h tests/test-unlink.h +tests/test-fchownat.c tests/test-fstatat.c tests/test-mkdirat.c tests/test-openat.c tests/test-unlinkat.c Depends-on: +mgetgroups pathmax +sleep +stat-time symlink unlinkdir configure.ac: +AC_CHECK_FUNCS_ONCE([getegid usleep]) Makefile.am: -TESTS += test-fstatat test-mkdirat test-openat test-unlinkat -check_PROGRAMS += test-fstatat test-mkdirat test-openat test-unlinkat +TESTS += test-fchownat test-fstatat test-mkdirat test-openat test-unlinkat +check_PROGRAMS += test-fchownat test-fstatat test-mkdirat test-openat \ + test-unlinkat +test_fchownat_LDADD = $(LDADD) @LIBINTL@ test_fstatat_LDADD = $(LDADD) @LIBINTL@ test_mkdirat_LDADD = $(LDADD) @LIBINTL@ test_openat_LDADD = $(LDADD) @LIBINTL@ diff --git a/tests/test-fchownat.c b/tests/test-fchownat.c new file mode 100644 index 000000000..2dbc857ec --- /dev/null +++ b/tests/test-fchownat.c @@ -0,0 +1,89 @@ +/* Tests of fchownat. + Copyright (C) 2009 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +/* Written by Eric Blake , 2009. */ + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "mgetgroups.h" +#include "openat.h" +#include "stat-time.h" + +#define ASSERT(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \ + fflush (stderr); \ + abort (); \ + } \ + } \ + while (0) + +#define BASE "test-fchownat.t" + +#include "test-chown.h" +#include "test-lchown.h" + +static int dfd = AT_FDCWD; + +/* Wrapper around fchownat to test chown behavior. */ +static int +do_chown (char const *name, uid_t user, gid_t group) +{ + return chownat (dfd, name, user, group); +} + +/* Wrapper around fchownat to test lchown behavior. */ +static int +do_lchown (char const *name, uid_t user, gid_t group) +{ + return lchownat (dfd, name, user, group); +} + +int +main (void) +{ + int result1; /* Skip because of no chown/symlink support. */ + int result2; /* Skip because of no lchown support. */ + + /* Clean up any trash from prior testsuite runs. */ + ASSERT (system ("rm -rf " BASE "*") == 0); + + /* Basic tests. */ + result1 = test_chown (do_chown, true); + result2 = test_lchown (do_lchown, result1 == 0); + dfd = open (".", O_RDONLY); + ASSERT (0 <= dfd); + ASSERT (test_chown (do_chown, false) == result1); + ASSERT (test_lchown (do_lchown, false) == result2); + /* We expect 0/0, 0/77, or 77/77, but not 77/0. */ + ASSERT (result1 <= result2); + ASSERT (close (dfd) == 0); + + /* FIXME - add additional tests of dfd not at current directory. */ + return result1 | result2; +} -- 2.11.0