Subject: standards/19209: test(1)'s -r, -w, and -x don't match POSIX for root (or 4.4BSD, or even V7)
To: None <gnats-bugs@gnats.netbsd.org>
From: Greg A. Woods <woods@weird.com>
List: netbsd-bugs
Date: 11/29/2002 20:16:52
>Number: 19209
>Category: standards
>Synopsis: test(1)'s -r, -w, and -x don't match POSIX for root (or 4.4BSD, or even V7)
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: standards-manager
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Fri Nov 29 17:17:00 PST 2002
>Closed-Date:
>Last-Modified:
>Originator: Greg A. Woods
>Release: netbsd-1.6
>Organization:
Planix, Inc.; Toronto, Ontario; Canada
>Environment:
System: NetBSD 1.6
>Description:
I've wanted to fix this for a while, but finally got perturbed
into doing it due to frustrations with a small script I was
porting -- one that has to run as root.
For the nitty gritty details see the comment included in the
patch below....
>How-To-Repeat:
try the following as root:
# chmod a-w /bin/sh # just in case....
# if [ -w /bin/sh ] ; then echo "OOPS! /bin/sh is writable!" ; fi
>Fix:
Index: test.c
===================================================================
RCS file: /cvs/master/m-NetBSD/main/basesrc/bin/test/test.c,v
retrieving revision 1.24
diff -c -c -r1.24 test.c
*** test.c 16 Sep 2001 19:03:26 -0000 1.24
--- test.c 30 Nov 2002 01:02:09 -0000
***************
*** 353,371 ****
filstat(char *nm, enum token mode)
{
struct stat s;
if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
return 0;
switch (mode) {
case FILRD:
! return access(nm, R_OK) == 0;
case FILWR:
! return access(nm, W_OK) == 0;
case FILEX:
! return access(nm, X_OK) == 0;
case FILEXIST:
! return access(nm, F_OK) == 0;
case FILREG:
return S_ISREG(s.st_mode);
case FILDIR:
--- 353,433 ----
filstat(char *nm, enum token mode)
{
struct stat s;
+ u_int16_t i;
if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s))
return 0;
+ /*
+ * The manual, and IEEE POSIX 1003.2, suggests these should check the
+ * mode bits, not use access(). For example for '-w':
+ *
+ * True shall indicate only that the write flag is on. The file
+ * is not writable on a read-only file system even if this test
+ * indicates true.
+ *
+ * On the other hand IEEE POSIX 1003.1-2001, as quoted in SuSv3, says:
+ *
+ * True shall indicate that permission to write from[sic] file
+ * will be granted, as defined in File Read, Write, and Creation.
+ *
+ * However that section says only:
+ *
+ * When a file is to be read or written, the file shall be opened
+ * with an access mode corresponding to the operation to be
+ * performed. If file access permissions deny access, the
+ * requested operation shall fail.
+ *
+ * and when I first read this I though surely we can't go about using
+ * open(O_WRITE) to try this test! However the POSIX 1003.1-2001
+ * Rationale section for test does in fact say:
+ *
+ * On historical BSD systems, test -w directory always returned
+ * false because test tried to open the directory for writing,
+ * which always fails.
+ *
+ * and indeed this is in fact true for Seventh Edition UNIX, UNIX 32V,
+ * and UNIX System III, and thus presumably also for BSD up to and
+ * including 4.3.
+ *
+ * Oddly the SysV implementation (at least in the 'test' builtin in
+ * /bin/sh) also uses access(name, 2) even though it goes to much
+ * greater lengths to test for execute permissions (like pdksh does).
+ *
+ * Interestingly the 4.4BSD was correct and it was implemented
+ * "intelligently" with stat() instead of open().
+ *
+ * This was apparently broken in NetBSD around about 1994/06/30 when
+ * the old 4.4BSD implementation was replaced with a (arguably much
+ * better coded) implementation derived from pdksh.
+ *
+ * Note that modern pdksh is yet different again, but still not
+ * correct, at least not w.r.t. POSIX as I interpret it.
+ */
switch (mode) {
case FILRD:
! i = S_IROTH;
! if (s.st_uid == geteuid())
! i = S_IRUSR;
! else if (s.st_gid == getegid())
! i = S_IRGRP;
! return (s.st_mode & i);
case FILWR:
! i = S_IWOTH;
! if (s.st_uid == geteuid())
! i = S_IWUSR;
! else if (s.st_gid == getegid())
! i = S_IWGRP;
! return (s.st_mode & i);
case FILEX:
! i = S_IXOTH;
! if (s.st_uid == geteuid())
! i = S_IXUSR;
! else if (s.st_gid == getegid())
! i = S_IXGRP;
! return (s.st_mode & i);
case FILEXIST:
! return 1; /* the successful lstat()/stat() is good enough */
case FILREG:
return S_ISREG(s.st_mode);
case FILDIR:
>Release-Note:
>Audit-Trail:
>Unformatted: