This is a small suite of C programs that implement their own access-control list
layer on top of normal Unix permissions, plus a minimal sudo clone. I
wrote it to actually understand the setuid() family of system calls
rather than just reading about them: how a process changes identity, how the setuid
bit hands you the file owner's privileges, and how easy it is to get that wrong.
Every binary is owned by a privileged user with its setuid bit set, so it starts
running as that owner regardless of who invokes it. Each program then reads the real
user ID with getuid(), decides whether that user is allowed to do what
they asked, and only then switches identity to carry out the operation.
setacl / getacl: write and read ACL entries on a file.fput / fget: write to and read from a file, mediated by the ACL.cd / mkdir: directory operations through the same checks.privx: a tiny sudo, runs a program as the binary's owner.
Rather than invent a side-file format, each file carries its own ACL in an extended
attribute named user.acl on its inode. setacl takes an
identity (-u for a user or -g for a group) and a permission
string in rwx form, and stores an entry; -d removes the
attribute entirely. The permission string is validated before anything is written, so
a malformed mode never lands on disk.
setacl -u larry -p rw- file.txt # grant larry read/write
setacl -g staff -p r-- file.txt # grant the staff group read
getacl file.txt # print the stored entries
setacl -d file.txt # drop the ACL
When fget or fput runs, it looks up the caller in the file's
ACL first. If there is an entry for that user or group, the ACL decides. If there is
no matching entry, it falls back to the file's normal DAC bits. A new file inherits an
ACL equal to its DAC permissions, so behaviour is predictable until you explicitly
change it.
privx is the part that makes the privilege model concrete. It stats the
target program, reads the owner UID, and runs the command as that owner. A program
owned by root runs as root; a program owned by an ordinary user runs as that user. The
child drops to the target identity with setuid() immediately after the
fork, before it ever calls execvp(), so the privileged parent never hands
a root environment to the executed command.
Most of the interesting work was in the failure cases, which is where setuid programs usually go wrong:
exec, so there is no window where attacker-controlled code
runs with elevated rights.It is a learning build, not a replacement for POSIX ACLs, and the trust model is deliberately simple. But writing it is the fastest way I have found to internalise why setuid binaries are such a sensitive part of a Unix system, and why every check has to happen in the right order.