3. UNIX File Security

Part of the 22C:169 Lecture Notes for Sppring 2006
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

Introduction

To open a Unix file, a process calls the open( name, flags, mode ) system call. This creates an open file descriptor that is the handle on a file that may be read or written, depending on the access rights the process has to the descriptor.

The access rights a process gets depend on the rights requested in the flags and also on the rights the process has to the file, as determined by the access rights of the file and the process's effective user ID and effective group ID.

Unix Access Rights

The access rights of a file are a bit-vector with the following structure:
special owner group other
SUID SGID SVTX R W X R W X R W X

Each file has an owner ID and a group ID that are set when the file is created. Usually, these are set to the effective user ID of the creator of the file, and the effective group ID of the creator or to the group ID of the directory in which the file is created.

When an attempt is made to open or execute a file, first, the effective user ID of the process is compared with the user ID of the file. If these match, then the owner rights apply to the file, granting read, write and execute rights.

If the owner ID does not match, then the group ID is checked against the effective group ID of the process. If these match, then the file is opened (or executed) using the group rights. Finally, if no IDs match, the other rights are granted.

fchown( int fd, uid_t owner, gid_t group )
This changes the owner and group of the already opened file connected to the file descriptor fd to the user ID owner and the group ID group. It also clears the set-user-ID and set-group-ID bits in the access rights field of the file since changing the owner or group IDs is likely to require rethinking these bits.

Note that unless the current user is the superuser, the user may not change the owner ID of the file. The owner of a file may, however, change the group ID to any group that owner belongs to.

uid_t getuid( )
uid_t geteuid( )
gid_t getgid( )
gid_t getegid( )
These get the real and current user and group IDs of the current process. Note that the effective IDs are the ones used to open files, while the real IDs are usually associated with the actual human user who logged into the machine to launch the process. Initially, the effective and real IDs are the same, but the use of execve on a file with the set UID or set GID bits set will change the effective UID or GID to that associated with the file.

int setuid( uid_t uid )
int seteuid( uid_t uid )
int setgid( gid_t gid )
int setegid( gid_t gid )
These set the real and current user and group IDs of the current process, returning a boolean indicating success or failure.

setuid sets both the real and effective user ID, while seteuidsets only the effective user ID. Both are legal only if the current effective user ID is that of the superuser or if the effective user ID is the same as the new real user ID.

setgid sets both the real and effective user ID, while setegidsets only the effective user ID. This is legal when either the current effective user ID is that of the superuser or when the the new ID is the same as one of the effective or real IDs.

Path Issues

The right to open a file is also determined by the right of the process opening the file to traverse the path to that file from the root of the file system or from the current working directory of the process.

To open a file, the process attempting to open the file must have either read or execute rights to each directory on the path to that file from the root or from the current working directory. (Paths starting with / are rooted at the root of the file system, while those starting with anything else are rooted from the current working directory.)

Normally, all file names can be considered global, since all files are reachable from the root of the file system, but the root is not fixed.

int chdir( const char * path )
int fchdir( int fd )
These are equivalent to the cd shell command. fchdir() changes the current working directory to the open file referenced by the given file descriptor, while chdir() is equivalent to opening the named file, using fchdir() on the open file, and then closing that file. In all cases, the process must have execute access to the file that becomes the new working directory, and that file must in fact be a directory.

int chroot( const char * path )
This command is like chdir(), except that it changes the root of the file system. Note that chdir() has no effect on the root, and chroot() has no effect on the current working directory. It is straightforward to set things up so that the current working directory is unreachable from the root and visa versa. Only the super user can execute chroot!

Once the new root is established, and once the current working directory is moved inside this root, it is impossible to escape from the limited part of the file system bounded by the new root. Therefore, it is standard practice to build a miniature file system, containing directories like /bin that contain, in turn, all the applications that might be needed after chroot has been executed. Of course, files that were open prior to executing chroot stay open afterwards.

The primary use for chroot is to create a safe "sandbox" in which untrusted applications can be run.