Multi-User Ownership and Permissions on Unix/Linux

File security and ownership

Up to 216 (which is 65,534) userids can be created for a Unix system. Each user has a unique UID number in the range 0 - 65,533 and a unique name or userid in the passwd(5) database file, which is used to control login security. For systems requiring more than a mere 64K users, extensions exist which allow for up to 232 users - which should be enough for quite a while to come. The same one to one correspondence exists between group names and GIDs, see group(5). Files and processes also belong to a specific userid and group; this determines who is allowed to do what to a file (or directory which is a type of file), and which files a particular process is allowed to read, write or execute.

The system uses the UID and GID as 16 bit numbers internally, and resolves references to the userid and group names using the /etc/passwd and the /etc/group files (or network wide passwd and group maps which have the same format). These names are used when displaying this information to the user, for example when you use the ls -l or the ls -lg commands. Normally UID 0 is reserved for root, who has system administrator privileges.

When directories are displayed using the ls -l command, the first 10 character field describes the permissions attached to the file. The first character describes what kind of file it is, a - (hyphen) indicates a plain or ordinary file, d indicates a directory and l indicates a soft link. The remaining 9 characters are split into 3 components of 3 characters each, to describe the user's, group's and others' privileges respectively. Within each 3 character triplet, the first indicates presence or denial of read permission, the second is for write permission and the third is for execute permission. If the r, w or x character is present, the permission is allowed, if the position is occupied by a - (hyphen) it is denied.

Directory Rights

On a directory, the x permission means something different from the ability to execute a file. Obviously it doesn't make any sense to be able to execute a directory. Here the x permission means the ability to search through, or traverse a directory to access subdirectories, whether or not you are allowed to read the directory being traversed in this manner. Being allowed to write to a directory enables files to be renamed there, or deleted within it.

File Permission Examples

-rwxr-xr-- indicates user read, write and execute rights, group read and execute rights, and read rights only for others.
drwxr-xr-- indicates a directory which can be displayed and searched by user and group. User can also delete, create and rename files in the directory. Others may list the directory but not search through it or access files in it.


It is dangerous to allow anyone else write access to any of your directories, and you should never give write access to your home directory to anyone, because this gives them the ability to do anything (accidently or deliberately) to any of the contents of the directory. If you want to share your files, allocate read and execute permission to the directory and read permission to your files. Others can then read them or copy them into their own directories. If others want to let you use copies of their files they can do likewise. If you want to protect confidential information from prying eyes, you should not regard something intended as an open system in an academic environment as being very secure, but some protection can be given by putting files into a directory to which only you have access. This does not of course stop anyone with access to the root userid from looking at them.

The chmod(1) command

File permissions are assigned using the chmod command. This can be used in one of two ways. Some users prefer to give the octal 3 digit mode, others prefer to use the character equivalents.

Examples using octal modes:

chmod 421 f1 assigns permissions r---w---x to f1
chmod 750 f1 f2 assigns permissions rwxr-x--- to f1 and f2

Each octal digit adds 4 for read, 2 for write and 1 for execute permission. The first digit is for user, second for group and the third for others.

Examples using character equivalents:

chmod u=rwx f1
chmod g=rx f1
chmod o= f1
these three commands assign permissions rwxr-x--- to f1
chmod -R go-w dir1
removes write permission from group and others from directory dir1 including all objects contained in it and all its subdirectories etc.
chmod u+x *.exe
adds user execute rights to all files in current directory not prefixed by period (.) ending in .exe

Process Ownership

A process is the activity caused by a program running on a system. You can inspect processes on a system using the ps command, e.g.

ps lax
will display a lot of information about all processes.

Security is maintained by processes normally inheriting the UID and GID ownership fields from their parents, so every process you create while logged in normally has your user and group privileges. The login(1) program is an obvious exception to this rule. Some programs also use the setuid(2) or setgid(2) privileges so that they run with different real and effective UIDs or GIDs. This allows system administrators to create privileged programs to enable other users to access files etc. in the manner controlled by the privileged program.

Setuid, Setgid and the Sticky bit

On some directory listings you may see s, S or t characters instead of the x - indicating use of setuid, setgid or sticky bit. Setuid and setgid applies to programs such as passwd(1) or procmail(1), which run with the privilege of the program owner, or group owner, and not the program user which would otherwise be the case. The sticky bit is used on directories such as /tmp where all users have write access, but not to each others' files.

Setuid example

rich@saturn:~$ ls -l /usr/bin/passwd
-rwsr-xr-x 1 root root 27132 2006-07-11 13:51 /usr/bin/passwd

When a user changes their own password using the passwd command, this program is run by the user, but runs with the UID of its owner (root).

Setgid example

rich@saturn:~$ ls -l /usr/bin/procmail
-rwsr-sr-x 1 root mail 68152 2005-05-03 03:10 /usr/bin/procmail
rich@saturn:~$ ls -l /var/mail
total 15652
-rw-rw---- 1 rich mail 15984976 2006-10-18 20:03 rich
-rw-rw---- 1 test mail     1810 2006-10-15 08:20 test
-rw-rw---- 1 rich mail     4333 2006-09-26 07:00 trap

When user1 uses procmail to send an email to user2, procmail needs to be able to write to the mail spool (/var/mail/user2) of the other user, which has group ownership of mail and group write access. The write access by user1 to user2's mailbox is restricted to what the procmail program is programmed to do, i.e. deliver a message. Unless an exploitable bug is discovered in procmail, the limited write access use of procmail gives user1 to user2's mailbox could not be used to delete messages already there.

Sticky bit example

rich@saturn:~$ ls -ld /tmp
drwxrwxrwt 13 root root 4096 2006-10-18 10:43 /tmp

Everyone on the system can write files to the /tmp directory.

rich@saturn:~$ echo hello > /tmp/hellof
rich@saturn:~$ ls -l /tmp
total 3
-rw------- 1 pete pete    6 2006-10-18 10:55 scratch
-rw-r--r-- 1 rich rich    6 2006-10-18 10:52 hellof
drwx------ 3 root root 4096 2006-10-18 10:39 872388287423
rich@saturn:~$ cat /tmp/scratch
cat: /tmp/scratch: Permission denied
rich@saturn:~$ rm /tmp/scratch
rm: remove write-protected regular file `/tmp/scratch'? y
rm: cannot remove `/tmp/scratch': Operation not permitted
rich@saturn:~$ rm /tmp/hellof

In practice, programs which use /tmp for temporary storage tend to name files there in such a manner that other users of /tmp are very unlikely to want the same names. Typically, contents of /tmp are automatically removed, e.g. on reboot or after then have not been accessed for a certain number of days.

Writing Setuid programs

On occasion it will be neccessary to write a program to enable ordinary users to carry out specific work, e.g. reading or writing from a particular file or database, that requires privileges belonging to root or a particular user or group. In general, try to design the application to minimise the privilege required. E.G, in the above example, mail spools are writeable by the mail group. If the job can be done using Setgid permission using a specialised group this is safer than using Setuid.

If the job to be done requires an interpreted script, this should never be made setuid or setgid directly. One reason for this is because running a script involves running 2 programs, with a likely and unknown delay between the 2. Firstly when the kernel identifies the first 2 characters (#!) of a script it will take the rest of the first line as the path of the interpreter to execute. Secondly the kernel will load and execute the interpreter and pass the pathname of the script to the interpreter, and the interpreter will interpret and run the script.

Exploiting a race condition vulnerability

All an attacker needs to do to exploit the delay between the first and second stage is to change the script to one which will do what the attacker wants, e.g. set the root password to a known value. This kind of vulnerability is called a race condition - the security of a system depends upon which of 2 processes or activities completes first. The attacker needs to change the contents of a link after the first stage, and before the second.

This can be done by the attacker creating a symbolic or soft link in /tmp or elsewhere to the setuid script, executing the setuid shell and then changing the contents of the link before the script is executed. If the attacker can arrange for the system to become very heavily loaded between stages 1 and 2, e.g. by running some very demanding programs, he or she will have plenty of time to change the target of the link before the program is loaded and interpreted. The attacker has to write 2 exploit scripts. The one he wants to get run with higher privileges must use the same scripting language as the setuid program. The other script will try to create the symlink, execute the setuid script through this, load the system and then change the target of the symlink and then remove the evidence.

Designing a specially privileged script securely

Instead of making the script setuid or setgid directly, it is better to write a wrapper in 'C' (or another compiled and not interpreted language) to execute the script. Then the wrapper program can be setuid and the script need have no special permissions.

Other precautions to take in connection with the wrapper program and script are to make sure neither can execute arbitrary data supplied by any user as code. E.G. use of Perl or Python eval() functions on user input should be avoided, or any input that could result in a buffer or integer overflow.

Worked Example

A 'salt' is a random value used to seed the algorithm used in order to take a one way hash of a password. In order to login, the password itself is not stored on the system, but the hash and the salt are stored, so the one-way hash algorithm can be repeated, and if the stored and computed hashes are the same, the user is considered to have input the correct password.

I am going to design an example program to enable a user to view his or her own salt. This together with the hashed password, is stored in /etc/shadow, which is only readable by root. Here is a typical record stored in /etc/shadow :


columns are colon ':' delimited. The salt is part of the '$' delimited second column, in this case, the salt is tpVtMvLu, and the hashed password is: BGOuzsKMSD5MW2lqCN4O60 . Running as root, we can extract the salt for a user called 'example' with the following command line:

cat /etc/shadow | grep "^example:" | awk -F: '{ print $2 }' \
  |  awk -F$ '{ print $3 }'

The first awk command obtains the 2nd colon ":" delimited column, While the second awk command obtains the third dollar "$" delimited string from the second column. (The null string before the first dollar counting as the first.)

We now have a minor design problem to solve. The easiest way to obtain the userid string ('example' in this case) is from the $USER environment variable; e.g. the command

echo $USER

will display your current userid. Using environment variables which can be manipulated by a user as input to a setuid program is considered very bad security practice. Instead, we are going to use the getuid() system call within our setuid 'C' wrapper program to get the numeric real userid. This will be different from the effective userid obtainable using geteuid() . We pass this value as a parameter to the script which will use the /etc/passwd file to look up the userid string.

shell script

This script will run as root in order to be able to read /etc/shadow in a controlled manner to display selected contents to a non-root user. Precautions taken include avoidance of use of any writeable files. All data is stored within pipelines or shell variables. The only input allowed is through parameter 1 ( $1) which must exactly match a UID present in /etc/passwd . If the script is not run as root, /etc/shadow will not be readable, so no output will be given.

# Shell Script Program to demonstrate setuid script wrapping
# This Script prints out the users salt from /etc/shadow
# Parameter needed: $1 is the numeric UID of the
# user executing the setuid wrapper program for this
# script which executes with root privileges.
# uncomment lines beginning '# echo' to debug

# echo no parameters: $#
# echo parameter 0: $0
# echo parameter 1: $1

# Do some sanity checks
if [ $# -ne 1 ]; then
  # called incorrectly without UID parameter. Give no information.
# echo passed no. parms test
if [ ! -r /etc/shadow ]; then
  # shadow unreadable. Give no information.
# echo /etc/shadow readable

# Obtain position of record in /etc/passwd containing UID

# The first line obtains column 3 from the passwd file.
# The second outputs the first record number whose column
# exactly matches the script UID parameter.

recnum=`cat /etc/passwd | awk -F: '{ print $3 }' | \
  grep -n "^$1\$"  | awk -F: '{ print $1 }' | sed -n 1p `

# echo recnum: $recnum

# Obtain user login name (column 1 of /etc/passwd record)

# Check that the UID parameter exists in /etc/passwd
num_recs=`echo $recnum | wc -w`

# echo num_recs: $num_recs

# Exit script if UID does not exist
if [ ${num_recs:-0} -ne 1 ]; then
  echo no such UID: $1

user=`sed -n ${recnum}p /etc/passwd | awk -F: '{ print $1 }'`

# echo user: $user

# prepend ^ to user login name and append : delimiter

# echo carat_user: $carat_user

# extract and print out salt from /etc/shadow file
# this requires root privileges
cat /etc/shadow | grep "$carat_user" | awk -F: '{ print $2 }' \
  |  awk -F$ '{ print $3 }'


/* wrapper.c demonstrates wrapper around privileged script.
 * This C wrapper runs setuid and the script needs no special
 * permission, as this wrapper will execute the script as
 * the root user.  */

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(void){
  uid_t uid; /* integer type for storing numeric UID */
  char uidstring[BUFSIZ];
  snprintf(uidstring,BUFSIZ,"%d",uid); /* convert uid to string */
  /* replace current setuid program with non setuid script */
  printf("If you see this, the execlp() call has failed\n");
  return 0; /* so we compile without a warning */

This program needs to convert the numeric UID into a decimal format character string. Note use of snprintf() instead of sprintf() for this purpose to protect against a very improbable buffer overrun. Note also use of the absolute pathname for the script to be executed starting with / . Without this precaution, any user able to execute this wrapper would be able to use it to execute any script, simply by running it within a different directory from the one containing the script.

Installation and final test

root@saturn:~/setuid# gcc -o wrapper wrapper.c
root@saturn:~/setuid# chmod u+s wrapper
root@saturn:~/setuid# ls -l
total 16
-rwxr-xr-x 1 root root 1663 2006-10-20 14:33 script
-rwsr-xr-x 1 root root 7327 2006-10-20 14:38 wrapper
-rw-r--r-- 1 rich rich  707 2006-10-20 14:37 wrapper.c

The wrapper program must be given setuid permission ( chmod u+s wrapper ) after the executable has been compiled. The ls -l command shows the additional setuid permission granted.

root@saturn:~/setuid# whoami
root@saturn:~/setuid# su example
example@saturn:/home/rich/setuid$ whoami
example@saturn:/home/rich/setuid$ ./wrapper

Use of setuid() within 'C' Programs

In the example above, the

chmod u+s wrapper
command was used by root to enable wrapper to be run by ordinary users, but to run as root with root privileges. This is an example of privilege increase. In some other situations, a program e.g. login starts running as root, but wants to execute a program with ordinary user privileges, e.g. the user's login shell. This involves a privilege decrease. The setuid(2) and setgid(2) functions are used when a program run as root can run more securely with reduced privileges as another system user.

This facility enables many of the system services to run within a user identity created for the purpose of the service. A good example is the webserver program, apache typically runs as the user www-data. This is safer than requiring the services to do everything as root, as it can reduce the consequences of an exploit to the environment used by the service concerned.

setuid(2) system call documentation

SETUID(2)                  Linux Programmer’s Manual                 SETUID(2)

       setuid - set user identity

       #include <sys/types.h>
       #include <unistd.h>

       int setuid(uid_t uid);

       setuid()  sets  the  effective  user ID of the current process.  If the
       effective UID of the caller is root, the real UID and saved set-user-ID
       are also set.

       Under  Linux,  setuid()  is implemented like the POSIX version with the
       _POSIX_SAVED_IDS feature.  This allows a set-user-ID (other than  root)
       program to drop all of its user privileges, do some un-privileged work,
       and then re-engage the original effective user ID in a secure manner.

       If the user is root or the program is  set-user-ID-root,  special  care
       must  be  taken.  The setuid() function checks the effective user ID of
       the caller and if it is the superuser, all process  related  user  ID’s
       are set to uid.  After this has occurred, it is impossible for the pro‐
       gram to regain root privileges.

       Thus, a set-user-ID-root program wishing to temporarily drop root priv‐
       ileges,  assume  the  identity of a non-root user, and then regain root
       privileges afterwards cannot use setuid().   You  can  accomplish  this
       with the (non-POSIX, BSD) call seteuid().

       On  success,  zero is returned.  On error, -1 is returned, and errno is
       set appropriately.

       EAGAIN The uid does not match the current uid and  uid  brings  process
              over it’s NPROC rlimit.

       EPERM  The  user is not privileged (Linux: does not have the CAP_SETUID
              capability) and uid does not match the real UID  or  saved  set-
              user-ID of the calling process.

       SVr4,  SVID, POSIX.1.  Not quite compatible with the 4.4BSD call, which
       sets all of the real, saved, and effective user IDs.  SVr4 documents an
       additional EINVAL error condition.

       Linux  has  the  concept  of  filesystem user ID, normally equal to the
       effective user ID.  The setuid() call also sets the filesystem user  ID
       of the current process.  See setfsuid(2).

       If  uid  is  different  from the old effective uid, the process will be
       forbidden from leaving core dumps.

       getuid(2), seteuid(2), setfsuid(2), setreuid(2), capabilities(7)

Linux 2.6.6                       2004-05-27                         SETUID(2)

Useful Further Reading

Students wanting a broader understanding of the security design of Unix introduced above are recommended to read the manpages referenced below.

Some Unix Security Related Manpages
description reference
Capabilities system splitting what root can do into functional roles capabilities(7)
System call to set GID of process to reduced group privilege setgid(2)
/etc/passwd user database file format passwd(5)
/etc/shadow password hash file format shadow(5)
/etc/group system group database file format group(5)
login program used to login as user login(1)
passwd program used by user to change password passwd(1)