Volkan Gülen
← blog
24.12.09 4 min
Unix ACL

drwxrwsr-t volkan devs folder 12345 Dec 9 01:23 | what is this s--t?

Understanding setuid, setgid and sticky bits in the chmod Command

Here’s an example output of the ls -l command:

text
-rwsr-xr-x 1 volkan developers 12345 Dec 9 01:23 myfile

You might think what the s is this? Don’t worry. Let’s dive into the details.

Introduction

Have you ever wondered how certain programs on your system gain the necessary permissions to perform specific tasks? Or how shared directories manage access among multiple users seamlessly? These functionalities often hinge on special permission flags in Unix-like operating systems.

Imagine a scenario where a user needs to perform tasks that require higher privileges than their current access level permits. How can the system securely grant the necessary permissions without exposing vulnerabilities? This is where setuid and setgid come into play. These special permission flags allow executable files to run with the permissions of the file owner or group, respectively, enabling controlled privilege escalation.

What are setuid, setgid and sticky bits?

Let’s start with the definitions:

  • setuid (Set User ID): When the setuid bit is set on a compiled executable, the program runs with the permissions of the file owner, rather than the user who executed it. This is particularly useful for programs that need to perform tasks requiring higher privileges, such as changing passwords. Note: the kernel ignores setuid on shell scripts — more on why in a moment.

  • setgid (Set Group ID): Similarly, the setgid bit allows an executable to run with the permissions of the file’s group. Additionally, when set on a directory, it ensures that new files and subdirectories inherit the group ID of the directory, providing shared access among group members.

  • sticky: The sticky bit is primarily used on directories to prevent users from deleting files they do not own. When set, users can only delete files they own.

Why do we use them?

Addressing Permission Challenges

  • Privilege Escalation: Some tasks require higher privileges than a regular user have. Instead of granting users full administrative rights, setuid allows specific programs to execute with elevated permissions safely.

  • Shared Directories: In shared directories, to maintaining consistent group ownership. setgid automates group assignment for new files, inheriting the group ID of the parent directory.

  • Security and Efficiency: By controlling which programs have elevated privileges, setuid and setgid help maintain system security while ensuring necessary functionalities are accessible. This approach minimizes the risk of unauthorized access or accidental system modifications.

  • Unwanted deletion prevention: The sticky bit is particularly useful in shared directories where multiple users have write access, ensuring that users can only delete their files.

Using chmod to set the special bits

The chmod command is used to change the file mode (permissions) of a file or directory. To set setuid and setgid bits, you can use symbolic or numeric modes.

Symbolic Mode

  • Set setuid:
bash
  chmod u+s filename
  • Set setgid:
bash
  chmod g+s filename
  • Set sticky:
bash
  chmod +t directory

Numeric Mode

  • Set setuid: Add 4000 to the existing permission value.
  • Set setgid: Add 2000 to the existing permission value.
  • Set sticky: Add 1000 to the existing permission value.

For example, to set both setuid and setgid on a file with existing permissions 755:

bash
chmod 6755 filename

A few Examples

Example 1: Using setuid on a Compiled Binary

A quick but important clarification before the example: setuid does not work on shell scripts. If you set the bit on a .sh file, the kernel will silently ignore it. The reason is a class of vulnerability called TOCTOU — Time-of-Check to Time-of-Use.

Here’s the problem with scripts: the kernel checks permissions and opens the script file (check), then passes control to the shell interpreter, which opens the file again to read it (use). In the window between those two events, an attacker with the right access could swap the script for a malicious one. The interpreter would then execute attacker-controlled code with elevated privileges.

Compiled binaries don’t have this problem. The kernel sets the effective UID before execution begins — there’s no interpreter step, no second open, no window.

So the correct approach is to write a small C wrapper:

  1. Write and compile the binary:

    c
    // network-reset.c
    #include <unistd.h>
    int main() {
        char *args[] = { "systemctl", "restart", "network", NULL };
        execv("/usr/bin/systemctl", args);
        return 1;
    }
    bash
    gcc -o /usr/local/bin/network-reset network-reset.c
  2. Change ownership to root:

    bash
    sudo chown root:root /usr/local/bin/network-reset
  3. Set the setuid bit and executable permissions:

    bash
    sudo chmod 4755 /usr/local/bin/network-reset

Result: Any user can run /usr/local/bin/network-reset, and it will execute with root privileges — without granting full root access. The binary is fixed at compile time, so there’s no TOCTOU window to exploit. That said, even compiled setuid binaries carry risk: keep them as small and focused as possible, and avoid anything that takes user-controlled input.

Example 2: Managing Shared Directories

In a collaborative environment, you might have a directory where multiple users need to create and modify files. Ensuring that all files inherit the same group ownership simplifies permission management.

  1. Create the Shared Directory:

    bash
    mkdir /srv/shared
  2. Change Group Ownership:

    bash
    sudo chown root:developers /srv/shared
  3. Set Appropriate Permissions with setgid and sticky bit:

    bash
    sudo chmod 3775 /srv/shared

Result: Any new files or subdirectories created within /srv/shared will automatically inherit the developers group, ensuring consistent group ownership. The sticky bit prevents users from deleting files they don’t own.

Careful Considerations

  • Compiled binaries only: Never set setuid or setgid on shell scripts — the kernel ignores it on interpreted files precisely because of TOCTOU risks. Use a compiled binary wrapper instead.
  • Use the least privilege principle: Grant the minimal permissions required. A binary that only needs to restart one service shouldn’t run as full root if a more targeted capability suffices.
  • Keep setuid binaries small and auditable: Every line of code in a setuid binary is a potential privilege escalation vector. The smaller and more focused, the better.

One Last Note

Always remember to use these powerful tools responsibly, having a secure system is better than creating a vulnerability while trying to be smart.