Hi Paul,
> > I expect I didn't observe the problem on the cfarm OpenBSD 7.7 hosts
> > because I'm in just one group there and a one-group user cannot exercise
> > the bug; whereas it looks like you're in multiple groups.
>
> Indeed, 'id' tells that I am in group 1000(bruno) and in group 0(wheel).
>
> > > The test-fchownat test got a bit further on OpenBSD 7.6, but it still
> > > fails:
> > >
> > > FAIL: test-fchownat
> > > ===================
> > >
> > > ../../gltests/test-lchown.h:185: assertion 'st1.st_gid == st2.st_gid'
> > > failed
> > > ../../gltests/test-lchown.h:192: assertion 'gids[1] == st2.st_gid' failed
> > > ../../gltests/test-lchown.h:185: assertion 'st1.st_gid == st2.st_gid'
> > > failed
> > > ../../gltests/test-lchown.h:192: assertion 'gids[1] == st2.st_gid' failed
> >
> > Thanks for reporting that. I think I see the problem, which is fixed in
> > the attached.
>
> Sorry, but it is not fixed. In a testdir of modules
> chown chownat fchownat lchown
> I still see the first assertion failure at the same place:
>
> ../../gltests/test-lchown.h:185: assertion 'st1.st_gid == st2.st_gid' failed
This piece of unit test calls the do_lchown() function, which exercises
fchownat() with AT_SYMLINK_NOFOLLOW, and then tests whether the target
of the symbolic link is unmodified. The assertion failure means that is
was modified. That is, that fchownat() is following the symlink despite
the AT_SYMLINK_NOFOLLOW flag. By adding to the unit test a
system ("ls -lrt " BASE " dir");
command before and after line 182, I get this output on Cygwin 2.9.0:
insgesamt 3
-rw------- 1 bruno None 0 22. Sep 13:08 file
drwx------+ 1 bruno None 0 22. Sep 13:08 sub
lrwxrwxrwx 1 bruno None 3 22. Sep 13:08 link3 -> sub
lrwxrwxrwx 1 bruno None 4 22. Sep 13:08 link2 -> link
lrwxrwxrwx 1 bruno None 4 22. Sep 13:08 link -> file
insgesamt 3
-rw------- 1 bruno Users 0 22. Sep 13:08 file
drwx------+ 1 bruno None 0 22. Sep 13:08 sub
lrwxrwxrwx 1 bruno None 3 22. Sep 13:08 link3 -> sub
lrwxrwxrwx 1 bruno None 4 22. Sep 13:08 link2 -> link
lrwxrwxrwx 1 bruno None 4 22. Sep 13:08 link -> file
and similarly on OpenBSD 7.6. Clearly, 'file' has been modified
although it should not have been.
Your fix in lib/fchownat.c does not fix the problem, because the
code *still* invokes fchownat() in this case.
The *only* possible fix to this problem is to not invoke fchownat()
in this case. Which is best arranged by making
gl_cv_func_fchownat_nofollow_works be "no" (or "guessing no")
instead of "yes" at configure time.
The patch below does it, and thereby fixes the test failure on
OpenBSD 7.6 and Cygwin 2.9.0. Committed, so that the coreutils release
can proceed.
2025-09-22 Bruno Haible <[email protected]>
fchownat: Fix test failure on OpenBSD and Cygwin 2.9 (regr. 2025-09-20).
* doc/posix-functions/fchownat.texi: Document that the dereferencing bug
also affects OpenBSD and Cygwin 2.9.
* m4/chown.m4 (gl_FUNC_CHOWN_CTIME): New macro, extracted from
gl_FUNC_CHOWN.
(gl_FUNC_CHOWN): Invoke it.
* m4/fchownat.m4 (gl_FUNC_FCHOWNAT_DEREF_BUG): Guess that a platform
that has the chown ctime bug also has the fchownat AT_SYMLINK_NOFOLLOW
bug.
* modules/fchownat (Files): Add m4/chown.m4.
diff --git a/doc/posix-functions/fchownat.texi
b/doc/posix-functions/fchownat.texi
index 34c1d724f1..c566949911 100644
--- a/doc/posix-functions/fchownat.texi
+++ b/doc/posix-functions/fchownat.texi
@@ -32,7 +32,7 @@
@item
Some platforms mistakenly dereference symlinks when using
@code{AT_SYMLINK_NOFOLLOW}:
-Linux kernel 2.6.17.
+Linux kernel 2.6.17, OpenBSD 7.6, Cygwin 2.9.0.
@item
This function does not fail for an empty filename on some platforms:
Linux with glibc < 2.11.
diff --git a/m4/chown.m4 b/m4/chown.m4
index f899f3b680..3b377f2989 100644
--- a/m4/chown.m4
+++ b/m4/chown.m4
@@ -1,5 +1,5 @@
# chown.m4
-# serial 39
+# serial 40
dnl Copyright (C) 1997-2001, 2003-2005, 2007, 2009-2025 Free Software
dnl Foundation, Inc.
dnl This file is free software; the Free Software Foundation
@@ -130,39 +130,7 @@ AC_DEFUN_ONCE([gl_FUNC_CHOWN]
;;
esac
- dnl OpenBSD fails to update ctime if ownership does not change.
- AC_CACHE_CHECK([whether chown updates ctime per POSIX],
- [gl_cv_func_chown_ctime_works],
- [dnl This test is tricky as it depends on timing and file timestamp
- dnl resolution, and there were false positives when configuring with
- dnl Linux fakeroot. Since the problem occurs only on OpenBSD and Cygwin,
- dnl test only on these platforms.
- AS_CASE([$host_os],
- [openbsd* | cygwin*],
- [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
-#include <unistd.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-]GL_MDA_DEFINES],
- [[struct stat st1, st2;
- if (close (creat ("conftest.file", 0600))) return 1;
- if (stat ("conftest.file", &st1)) return 2;
- sleep (1);
- if (chown ("conftest.file", st1.st_uid, st1.st_gid)) return 3;
- if (stat ("conftest.file", &st2)) return 4;
- if (st2.st_ctime <= st1.st_ctime) return 5;
- ]])],
- [gl_cv_func_chown_ctime_works=yes],
- [gl_cv_func_chown_ctime_works=no],
- [# Obey --enable-cross-guesses.
- gl_cv_func_chown_ctime_works="$gl_cross_guess_normal"
- ])
- rm -f conftest.file
- ],
- [gl_cv_func_chown_ctime_works=yes])
- ])
+ gl_FUNC_CHOWN_CTIME
case "$gl_cv_func_chown_ctime_works" in
*yes) ;;
*)
@@ -221,3 +189,47 @@ AC_DEFUN_ONCE([gl_FUNC_CHOWN_FOLLOWS_SYMLINK]
;;
esac
])
+
+AC_DEFUN_ONCE([gl_FUNC_CHOWN_CTIME],
+[
+ AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+ AC_CHECK_FUNCS_ONCE([chown])
+
+ dnl mingw lacks chown altogether.
+ if test $ac_cv_func_chown != no; then
+ dnl OpenBSD and Cygwin 2.9.0 fail to update ctime if ownership does not
+ dnl change.
+ AC_CACHE_CHECK([whether chown updates ctime per POSIX],
+ [gl_cv_func_chown_ctime_works],
+ [dnl This test is tricky as it depends on timing and file timestamp
+ dnl resolution, and there were false positives when configuring with
+ dnl Linux fakeroot. Since the problem occurs only on OpenBSD and Cygwin,
+ dnl test only on these platforms.
+ AS_CASE([$host_os],
+ [openbsd* | cygwin*],
+ [AC_RUN_IFELSE([AC_LANG_PROGRAM([[
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+]GL_MDA_DEFINES],
+ [[struct stat st1, st2;
+ if (close (creat ("conftest.file", 0600))) return 1;
+ if (stat ("conftest.file", &st1)) return 2;
+ sleep (1);
+ if (chown ("conftest.file", st1.st_uid, st1.st_gid)) return 3;
+ if (stat ("conftest.file", &st2)) return 4;
+ if (st2.st_ctime <= st1.st_ctime) return 5;
+ ]])],
+ [gl_cv_func_chown_ctime_works=yes],
+ [gl_cv_func_chown_ctime_works=no],
+ [# Obey --enable-cross-guesses.
+ gl_cv_func_chown_ctime_works="$gl_cross_guess_normal"
+ ])
+ rm -f conftest.file
+ ],
+ [gl_cv_func_chown_ctime_works=yes])
+ ])
+ fi
+])
diff --git a/m4/fchownat.m4 b/m4/fchownat.m4
index 300a254225..bad9b58360 100644
--- a/m4/fchownat.m4
+++ b/m4/fchownat.m4
@@ -1,5 +1,5 @@
# fchownat.m4
-# serial 8
+# serial 9
dnl Copyright (C) 2004-2025 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
@@ -46,16 +46,18 @@ AC_DEFUN([gl_FUNC_FCHOWNAT_DEREF_BUG]
AC_CACHE_CHECK([whether fchownat works with AT_SYMLINK_NOFOLLOW],
[gl_cv_func_fchownat_nofollow_works],
- [
- gl_dangle=conftest.dangle
- # Remove any remnants of a previous test.
- rm -f $gl_dangle
- # Arrange for deletion of the temporary file this test creates.
- ac_clean_files="$ac_clean_files $gl_dangle"
- ln -s conftest.no-such $gl_dangle
- AC_RUN_IFELSE(
- [AC_LANG_SOURCE(
- [[
+ [gl_FUNC_CHOWN_CTIME
+ case "$gl_cv_func_chown_ctime_works" in
+ *yes)
+ gl_dangle=conftest.dangle
+ # Remove any remnants of a previous test.
+ rm -f $gl_dangle
+ # Arrange for deletion of the temporary file this test creates.
+ ac_clean_files="$ac_clean_files $gl_dangle"
+ ln -s conftest.no-such $gl_dangle
+ AC_RUN_IFELSE(
+ [AC_LANG_SOURCE(
+ [[
#include <fcntl.h>
#include <unistd.h>
/* Android 4.3 declares fchownat() in <sys/stat.h> instead. */
@@ -70,12 +72,27 @@ AC_DEFUN([gl_FUNC_FCHOWNAT_DEREF_BUG]
AT_SYMLINK_NOFOLLOW) != 0
&& errno == ENOENT);
}
- ]])],
- [gl_cv_func_fchownat_nofollow_works=yes],
- [gl_cv_func_fchownat_nofollow_works=no],
- [gl_cv_func_fchownat_nofollow_works="$gl_cross_guess_normal"])
- ])
- AS_IF([test "$gl_cv_func_fchownat_nofollow_works" != yes], [$1], [$2])
+ ]])],
+ [gl_cv_func_fchownat_nofollow_works=yes],
+ [gl_cv_func_fchownat_nofollow_works=no],
+ [gl_cv_func_fchownat_nofollow_works="$gl_cross_guess_normal"])
+ ;;
+ *)
+ dnl On OpenBSD and Cygwin 2.9.0, the test above would produce
+ dnl gl_cv_func_fchownat_nofollow_works=yes, and this would then
+ dnl lead to an fchownat test failure:
+ dnl test-lchown.h:185: assertion 'st1.st_gid == st2.st_gid' failed
+ dnl Since testing for this bug directly is only possible on machines
+ dnl where the current user is in at least two groups, we use
+ dnl gl_FUNC_CHOWN_CTIME as a substitute test.
+ gl_cv_func_fchownat_nofollow_works="guessing no"
+ ;;
+ esac
+ ])
+ case "$gl_cv_func_fchownat_nofollow_works" in
+ *yes) $2 ;;
+ *) $1 ;;
+ esac
])
# gl_FUNC_FCHOWNAT_EMPTY_FILENAME_BUG([ACTION-IF-BUGGY[, ACTION-IF-NOT_BUGGY]])
diff --git a/modules/fchownat b/modules/fchownat
index 31e722c89c..61e1d215a1 100644
--- a/modules/fchownat
+++ b/modules/fchownat
@@ -5,6 +5,7 @@ Files:
lib/fchownat.c
lib/at-func.c
m4/fchownat.m4
+m4/chown.m4
Depends-on:
unistd-h