I want to find files that a particular user will not be able to read.
Assume the username is “user123” and they are in a group called “user123”. I want to find files that, if they are owned by user123 have u+r on; failing that if the file is group user123 it should have g+r on; failing that it can have o+r on.
Since GNU find has “-readable”, I could do this:
sudo -u user123 find /start ! -readable -ls
However the process has to run by a user that does not have sudo access. Therefore I I tried this: (it doesn’t check o+r but that isn’t important at this point)
find /start ( -user user123 ! -perm -u=r ) -o ( -group user123 ! -perm -g=r ) -ls
but it lists this file:
272118 4 -rw------- 1 user123 user123 3243 Jul 3 19:50 /start/blah/blah/file.txt
This file is the only file under /start that is owned by user123 with g=r off. It is as if find is interpreting the -u=r as -g=r.
I decided to try reversing the logic and instead test not ( truth ) instead:
find /etc/puppet ! ( ( -user puppet -perm -u=r ) -o ( -group puppet -perm -g=r ) -o ( -perm -o=r ) ) -ls
That works!
Why did the original find fail? Is it a bug in find (unlikely) or is the logic wrong?
Update: I had the logic wrong. As pointed out below, since
! ( A || B || C ) == ( !A && !B && !C )
these are the two equivalent statements:
find /start ! ( ( -user user123 -perm -u=r ) -o ( -group user123 -perm -g=r ) -o ( ! ( -user user123 -o -group user123 ) -perm -o=r ) ) -ls find /start ! ( -user user123 -perm -u=r ) ! ( -group user123 -perm -g=r ) ! ( ! ( -user user123 -o -group user123 ) -perm -o=r ) -ls
My goal was not to have to test user/group twice. What I really need is a more complicated if-then-else structure, which would probably only be possible if there was an -xor operator. I could build an xor out of and/or/not but it would be more complex than the two solutions above.
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
There are far many more things to take into consideration to check whether a user has access to a file via a given path:
- The owner of the file
- the group of the file
- the ACLs in the file
- the uid, gid, and supplementary gids of the user
- search access to any path component leading to that file.
- whether the file is a symlink
- permissions apply differently for users of id 0.
- possibly more security features like SELinux…
Short of actually switching all the uids and gids to those of the user and check, it’s very difficult to implement the same logic as what the system does.
With zsh, you could do (as root):
readable() ( USERNAME=$u [ -r "$REPLY" ] ) u=some-user print -rl -- **/*(DoN^+readable)
Or with perl:
find . -print0 | sudo -u some-user perl -Mfiletest=access -l -0ne ' print unless -r'
That is in both cases, descend the directory tree as root but test for file access as the corresponding user.
Running find -readable as some-user won’t in cases as it won’t be able to go past the directories for which the user has no access or no read permission (but possibly access).
Even when only considering the permission and ownership of the file itself (and not ACLs or path components…), you need at least (here GNU syntax):
u=some-user; g=$(id -G "$u" | sed 's/ / -o -group /g'); IFS=" "
find . ! ( -user "$u" -perm -u=r -o
! -user "$u" ( -group $g ) -perm -g=r -o
! -user "$u" ! ( -group $g ) -perm -o=r )
The idea being that if the file is owned by the user, all other permissions are irrelevant. If not, then if the file is group-owned by any of the user’s groups, then the “other” permission is irrelevant.
Method 2
The logic is wrong. You’re thinking this file shouldn’t have been listed because it’s owned by user123 and has the user’s r bit set. However, it’s listed because it matches the second criterion (it’s owned by group user123 and has the group’s r bit unset).
Your second version works because of one of de Morgan’s laws: negating the logical ORing of a group of statements is logically equivalent to ANDing the negation of the individual statements. In other words:
! ( A || B || C ) == ( !A && !B && !C )
So the working find is looking for a file that
- Is not (owned by user
user123and readable by said user) AND - Is not (owned by group
user123and readable by said group) AND - Is not world-readable.
while the first find is looking for a file that
- Is owned by user
user123and not readable by said user OR - Is owned by group
user123and not readable by said group OR (if you had completed it) - Is not world-readable
So a file matching ANY of the above 3 criteria (and not necessarily all) would be listed as you have seen.
Edit
Incidentally (after viewing your profile), I’m a big fan of your O’Reilly book 🙂
Method 3
Test environment:
$ sudo tree -fp /tmp/del
/tmp/del
├── [drwxr-xr-x] /tmp/del/1
│ └── [-rw-r--r--] /tmp/del/1/f1
├── [d---------] /tmp/del/2
│ └── [-rw-------] /tmp/del/2/f1 <-- this filepath is not accessible for ! root
└── [drwxr-xr-x] /tmp/del/3
└── [-rw-r--r--] /tmp/del/3/f1
Full otput:
$ sudo find /tmp/del/ -type f -exec sudo -u user123 ls {} ;
/tmp/del/3/f1
/tmp/del/1/f1
ls: cannot access '/tmp/del/2/f1': Permission denied <-- required filepath
“Dirty” required output:
# Just redirect stdout
>/dev/null sudo find /tmp/del/ -type f -exec sudo -u user123 ls {} ;
ls: cannot access '/tmp/del/2/f1': Permission denied
Reusable code
Just hide stdout and show stderr instead it:
function xfind() { 2>&1 >/dev/null sudo find "${2}" -type f -exec sudo -u "${1}" ls {} ; | grep -o "cannot access .*$"; }
“Production” usage example: what I can’t see in root’s home:
$ xfind $USER /root cannot access '/root/.cache/dconf/user': Permission denied cannot access '/root/.cache/gstreamer-1.0/registry.x86_64.bin': Permission denied ...
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0