1

In python I can check whether my own user can access a file or directory using os.access. However, is it possible check whether another user can do so?

One possibility is to check the user's uid and groups against the permissions on the file:

import os, grp, pwd, stat
def user_access(uid, path, perms=os.R_OK | os.W_OK):
    stats = os.stat(path)
    # other users
    if stats.st_mode & perms == perms:
        return True
    # owner
    if stats.st_uid == uid and (stats.st_mode >> 6) & perms == perms:
        return True
    # group
    user = pwd.getpwuid(uid).pw_name
    if (stats.st_mode >> 3) & perms == perms and user in grp.getgrgid(stats.st_gid).gr_mem:
         return True
    return False
        

However, I guess this is specific to POSIX systems and I could imagine there would be edge cases it fails on. In fact, the libc manual on access specifically warns against this solution:

There is another way you could check this access, which is ... to examine the file mode bits and mimic the system’s own access computation. This method is undesirable because many systems have additional access control features; your program cannot portably mimic them, and you would not want to try to keep track of the diverse features that different systems have. Using access is simple and automatically does whatever is appropriate for the system you are using.

Is there a more concise and/or robust solution for checking access of other users?

Edit 2020-12-09: Fixed bugs with the bit twiddling.

Quantum7
  • 3,165
  • 3
  • 34
  • 45

2 Answers2

1

I came up with a bad answer. Basically, you use os.setuid to switch users, then use os.access on the file.

import multiprocessing as mp
def user_access_mp(uid, path, perms=os.R_OK | os.W_OK):
    def disguise():
        os.setuid(uid)
        allowed = os.access(path, perms)
        exit(int(allowed))

    p = mp.Process(target=disguise)
    p.start()
    p.join()
    assert p.exitcode in (0, 1)  # other values indicate unexpected signals
    return bool(p.exitcode)

Unfortunately this (1) has to be done as root, (2) requires forking, since there's no way to setuid back to the original user, and (3) is slow.

Furthermore, it doesn't even work consistently on the system I care about, a RHEL server using LDAP. In this case I get 'False' for some files that I belong to the group of. Most likely this is because the system uses LDAP for groups, so some groups don't appear in a user's group list. It might be possible to bypass this using the C API (as done to fix grp.getgrall()), but I haven't tested. Anyways the requirement to run as root severely limits this solution.

Quantum7
  • 3,165
  • 3
  • 34
  • 45
  • 1
    POSIX is very limited when it comes to permissions, because there's a huge variance in how different systems implement access controls. You can use `setresuid()` to avoid forking, and you can use `setresgid()` to change to the user's default group. If you're root, you can just login as the user (something like execve of `su -c 'test -r myfile.txt' - otheruser` – root Dec 02 '20 at 18:25
  • @root Nice. It looks like if I change the real uid while keeping the effective uid 0 (`setresuid(500, 0, 0)`) then os.access works correctly but I can change back to root. However, LDAP groups are still broken. – Quantum7 Dec 08 '20 at 23:13
-1

I've never really tried this before, but let me know if it works:

import os
import stat

def isgroupreadable(filepath):
  st = os.stat(filepath)
  return bool(st.st_mode & stat.S_IRGRP)

If you wanted to print this, you could add at the end:

print(isgroupreadable(filepath))

Dharman
  • 30,962
  • 25
  • 85
  • 135
The Pilot Dude
  • 2,091
  • 2
  • 6
  • 24
  • This is roughly equivalent to my line `stats.st_mode & (perms << 6) != 0`. It just extracts the group-r bit (040) from the mode line. You would still have to check that the other user belongs to that group, and do the same with stat.S_IRUSR, stat.IROTH, etc. – Quantum7 Nov 20 '20 at 19:50