summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--configure.ac31
-rw-r--r--src/bltin/test.c20
2 files changed, 51 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac
index 60a2a26..8ae0dc6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -90,6 +90,37 @@ AC_CHECK_FUNCS(bsearch faccessat getpwnam getrlimit isalpha killpg \
 	       sigsetmask stpcpy strchrnul strsignal strtod strtoimax \
 	       strtoumax sysconf)
 
+dnl Check whether it's worth working around FreeBSD PR kern/125009.
+dnl The traditional behavior of access/faccessat is crazy, but
+dnl POSIX.1-2008 explicitly allows those functions to misbehave.
+dnl
+dnl Unaffected kernels:
+dnl
+dnl - all versions of Linux
+dnl - NetBSD sys/kern/vfs_subr.c 1.64, 1997-04-23
+dnl - FreeBSD 9 (r212002), 2010-09-10
+dnl - OpenBSD sys/kern/vfs_subr.c 1.166, 2008-06-09
+dnl
+dnl Also worked around in Debian's libc0.1 2.13-19 when using
+dnl kFreeBSD 8.
+
+AC_ARG_ENABLE(test-workaround, AS_HELP_STRING(--enable-test-workaround, \
+	[Guard against faccessat(2) that tells root all files are executable]),,
+	[enable_test_workaround=auto])
+
+if test "enable_test_workaround" = "auto" &&
+   test "$ac_cv_func_faccessat" = yes; then
+	case `uname -s 2>/dev/null` in
+	GNU/kFreeBSD | \
+	FreeBSD)
+		enable_test_workaround=yes
+	esac
+fi
+if test "$enable_test_workaround" = "yes"; then
+	AC_DEFINE([HAVE_TRADITIONAL_FACCESSAT], [1],
+		[Define if your faccessat tells root all files are executable])
+fi
+
 if test "$enable_fnmatch" = yes; then
 	use_fnmatch=
 	AC_CHECK_FUNCS(fnmatch, use_fnmatch=yes)
diff --git a/src/bltin/test.c b/src/bltin/test.c
index 458e9f5..bab9a1f 100644
--- a/src/bltin/test.c
+++ b/src/bltin/test.c
@@ -155,6 +155,14 @@ static int test_st_mode(const struct stat64 *, int);
 static int bash_group_member(gid_t);
 #endif
 
+#ifdef HAVE_FACCESSAT
+# ifdef HAVE_TRADITIONAL_FACCESSAT
+static inline int faccessat_confused_about_superuser(void) { return 1; }
+# else
+static inline int faccessat_confused_about_superuser(void) { return 0; }
+# endif
+#endif
+
 static inline intmax_t getn(const char *s)
 {
 	return atomax10(s);
@@ -493,8 +501,20 @@ equalf (const char *f1, const char *f2)
 }
 
 #ifdef HAVE_FACCESSAT
+static int has_exec_bit_set(const char *path)
+{
+	struct stat64 st;
+
+	if (stat64(path, &st))
+		return 0;
+	return st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH);
+}
+
 static int test_file_access(const char *path, int mode)
 {
+	if (faccessat_confused_about_superuser() &&
+	    mode == X_OK && geteuid() == 0 && !has_exec_bit_set(path))
+		return 0;
 	return !faccessat(AT_FDCWD, path, mode, AT_EACCESS);
 }
 #else	/* HAVE_FACCESSAT */