classification
Title: os.access can return bogus values when run as superuser
Type: behavior Stage: needs patch
Components: Documentation Versions: Python 3.1, Python 3.2, Python 2.7, Python 2.6
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: docs@python Nosy List: docs@python, ngie, r.david.murray
Priority: normal Keywords:

Created on 2010-07-20 02:02 by ngie, last changed 2014-10-13 07:04 by georg.brandl. This issue is now closed.

Messages (6)
msg110850 - (view) Author: Enji Cooper (ngie) * Date: 2010-07-20 02:02
As seen in the nose bug [1], I stumbled upon an OS quirk with FreeBSD, where apparently (as superuser) due to the wording of the POSIX spec for access(2), it's considered free game to return 0 for the system call (True) for os.[RWX]_OK.

Only python was affected by this on the system I was using (both perl and sh did the right thing in detecting the executable bit(s) on the file).

An example should be provided to do the right thing with the stat module, and developers should be warned against using os.access because (as the patch and test log demonstrate), stat(2) does the right thing when access(2) does not as superuser...

Here's an example of the code I used to detect the executable bit:

import os
import stat

s = os.stat(afile)

executable = (s.st_mode & stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) == 0

[1] http://code.google.com/p/python-nose/issues/detail?id=351
[2] http://www.opengroup.org/onlinepubs/000095399/functions/access.html
msg110903 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2010-07-20 14:16
I'm curious, are the perl and sh functions you say do the right thing wrappers around 'access', or are they some other sort of function?  The Python code is just a thin wrapper around the system function, and as such will follow the system function's semantics.

I do agree that the FreeBSD interpretation of the semantics of access is...unexpected, as well as unfortunate :)  So, a doc note about the superuser X_OK quirk is appropriate.  Care to propose a doc patch?  Personally I wouldn't include the stat example, but rather simply refer to stat as the portable way to check if the execute bit is set (and then you still have to call os.access to see if the real uid has permission to execute).

On the other hand, proposing a portable version of 'access' for inclusion in shutils might be appropriate, if you want to open a new issue for that (preferably with a patch including docs and unit tests :)
msg110943 - (view) Author: Enji Cooper (ngie) * Date: 2010-07-20 18:26
Well, bash screws up in this dept:

$ ls -l typescript
-rw-r--r--  1 gcooper  gcooper  37875 Jul 12 22:19 typescript
$ sudo sh -c 'test -x typescript; echo $?'
1
$ sudo bash -c 'test -x typescript; echo $?'
0
$ csh
%if (-x typescript) then
if? echo "executable"
if? endif
%

test(1) is a built-in in bash; on FreeBSD/NetBSD(/OpenBSD?) it's a standalone app (which uses eaccess(2)). csh does some tricky code for testing file access in sh.exp.c (see sh_access).

perl does some tricky stuff in pp_sys.c (look for pp_ftrread), where it 
uses eaccess(2) if it's present, else falls back to access(2) to do its bidding. So maybe os.access should use eaccess(2) if it's present on the OS instead of access(2), and note that the item is OS implementation dependent (beware!)?
msg110986 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2010-07-21 01:27
Hmm.  The document you quoted said that eaccess doesn't necessarily get this right everywhere either, but it sounds like it would at least fix the problem on FreeBSD...but at the price of no longer being a simple wrapper around access.  I'm not sure what I think about that.  This may be a question for python-dev.
msg110987 - (view) Author: Enji Cooper (ngie) * Date: 2010-07-21 01:32
My initial analysis was incorrect after talking with the bash(1) folks. test(1) is doing things wrong too:

        case FILEX:
                /* XXX work around eaccess(2) false positives for superuser */
                if (eaccess(nm, X_OK) != 0)
                        return 0;
                if (S_ISDIR(s.st_mode) || geteuid() != 0)
                        return 1;
                return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;

So it looks like test(1) is broken as well (doesn't check for ACLs, or MAC info).

Interesting why it's only implemented for X_OK though...
msg110988 - (view) Author: Enji Cooper (ngie) * Date: 2010-07-21 01:32
My initial analysis was incorrect after talking with the bash(1) folks. test(1) is doing things wrong too:

        case FILEX:
                /* XXX work around eaccess(2) false positives for superuser */
                if (eaccess(nm, X_OK) != 0)
                        return 0;
                if (S_ISDIR(s.st_mode) || geteuid() != 0)
                        return 1;
                return (s.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) != 0;

So it looks like test(1) is broken as well (doesn't check for ACLs, or MAC info).

Interesting why it's only implemented for X_OK though...

Based on this analysis, I'd say that access(2) is broken on FreeBSD and needs to be fixed.
History
Date User Action Args
2014-10-13 07:04:31georg.brandlsetstatus: open -> closed
resolution: not a bug
2010-07-21 01:32:53ngiesetmessages: + msg110988
2010-07-21 01:32:28ngiesetmessages: + msg110987
2010-07-21 01:27:08r.david.murraysetmessages: + msg110986
2010-07-20 18:26:06ngiesetmessages: + msg110943
2010-07-20 14:16:42r.david.murraysetassignee: docs@python
components: + Documentation, - Library (Lib)
versions: + Python 3.1, Python 2.7, Python 3.2
nosy: + docs@python, r.david.murray

messages: + msg110903
stage: needs patch
2010-07-20 02:02:35ngiecreate