Skip to content

SUID, GUID & Sticky bits

Created on Dec 4, ’22 ・ Updated on Mar 5, ’23

Description

SUID & SGID on binaries

SUID (Set User ID) and SGID (Set Group ID) are special types of UNIX file permissions. When a binary is executed, it is so with the privileges of its owner (if SUID is set), of its group (if SGID is set) or of both (if both are set). These file permissions only works for binaries and are ignored for interpreted scripts.

A typical example is the /usr/bin/passwd which allows a user to change its password. Any user should be able to change its password, however, this requires /etc/shadow to be modified, which can only be done by root. By setting the SUID permission for the binary, which is owned by root, we allow this binary to be run as root (the effective UID is changed) when another user executes it.

SUID and SGID are different from sudo with which we can allow a user or group of users to execute a command as another user or group irrespective of who owns that binary. sudo is also used to determine who can execute a command, whilst with SUID and SGID we do not change who can execute a binary.

SGID on directories

When the SGID bit is set on a directory, any file or directory created within that directory will inherit the group of the directory with the SGID bit, instead of the primary group of the user who created the directory.

Sticky bit

Using the sticky bit on a directory prevents any user having write access to the directory from removing any files from that directory. Typically used on /tmp. On Linux it is ignored when set on files.

Example

We have two users, user1 (UID 1001) and user2 (UID 1002). We also have a binary (suid) which reads a file (protected_file) and outputs its content. The binary can be executed by everybody but the file can be read only by user1. The binary is owned by user1.

For user2 to be able to see file's content when executing the binary, the SUID bit must be set, changing the effective UID from user2 (who can't read the file) to user1 (who can).

/* suid.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main() {
    printf("EUID=%d\n", geteuid());
    execlp("cat", "cat", "protected_file", 0);
    return 0;
}

User1 executes suid and the file's content is displayed:

# from user1
[user1@localhost tmp]$ cat protected_file
Hello from protected file!
[user1@localhost tmp]$ ls -l
total 20
-rw-rw----. 1 user1 user1   27 Mar 30 23:12 protected_file
-rwxrwxr-x. 1 user1 user1 8544 Mar 30 23:23 suid
-rw-rw-r--. 1 user1 user1  196 Mar 30 23:19 suid.c
[user1@localhost tmp]$ ./suid
EUID=1001
Hello from protected file!
[user1@localhost tmp]$

User2 tries the same thing but fails:

# from user2
[user2@localhost tmp]$ ls -l
total 20
-rw-rw----. 1 user1 user1   27 Mar 30 23:12 protected_file
-rwxrwxr-x. 1 user1 user1 8544 Mar 30 23:23 suid
-rw-rw-r--. 1 user1 user1  196 Mar 30 23:19 suid.c
[user2@localhost tmp]$ ./suid
EUID=1002
cat: protected_file: Permission denied
[user2@localhost tmp]$

User1 sets the SUID bit on the binary:

# from user1
[user1@localhost tmp]$ chmod u+s suid
[user1@localhost tmp]$ ls -l
total 20
-rw-rw----. 1 user1 user1   27 Mar 30 23:12 protected_file
-rwsrwxr-x. 1 user1 user1 8544 Mar 30 23:23 suid
-rw-rw-r--. 1 user1 user1  196 Mar 30 23:19 suid.c
[user1@localhost tmp]$

User2 can now see the file's content using the binary. We can see that the effective UID has changed and is now that of user1:

# from user2
[user2@localhost tmp]$ ls -l
total 20
-rw-rw----. 1 user1 user1   27 Mar 30 23:12 protected_file
-rwsrwxr-x. 1 user1 user1 8544 Mar 30 23:23 suid
-rw-rw-r--. 1 user1 user1  196 Mar 30 23:19 suid.c
[user2@localhost tmp]$ ./suid
EUID=1001
Hello from protected file!
[user2@localhost tmp]$