Safer set-uid programs in Ada with the suid-helper library

pinotattari profile image Riccardo Bernardini ・5 min read

Set-uid programs: the good, the bad and the risky

In *nix systems access control is traditionally often done by checking if the user requesting a given operation has read/write/execution privilege with respect to a given file/directory.

In some cases this policy is not fine-grained enough. An example is the common passwd that allows the user to change its password. Changing the password requires modifying the file /etc/passwd that stores the passwords (to be honest, this was true on older systems, nowadays things are a bit different, but we stick to this for the sake of the example). This causes a problem with the permission accesses of /etc/passwd: the file must be clearly not writable by the ordinary user, but... how could the user change its password?

The solution is allow the user to modify /etc/passwd in a controlled way, via an executable that will change only the user's password. In other words, the user must gain temporally limited root privileges (administrator privileges for you Window people 😄). This is done with the idea of set-user-ID executable. If an executable is marked as setuid, the user running the program gets the privileges of the owner of the executable (usually root). In this way the program can carry out privileged actions on behalf of the user.

The three identities

We need a bit of more detail. In current *nix systems a running executable has 3 identities

  • The real user ID. This is the user that is running the code.
  • The effective user ID. This is the identity that is used to check the accesses. In a normal program this is equal to the real user ID, but in a setuid program it is initialized to the identity of the owner of the executable.
  • The saved user ID. This is initialized with the effective user ID; therefore, in a normal program it is equal to the real UID, while in a setuid program is equal to the ID of the owner.

How do these IDs interact? It is really simple:

The program can change its effective ID to its real or saved ID.

This means that a normal program starts with the effective ID equal to the identity of the user and it cannot be changed; while a setuid program can set it to the real identity (we will say that it drops the privileges) or to the identity of the owner (we will say that it restores the privileges). It is possible for the program to definitively drop the privileges by setting the saved ID to the real ID.

Good practices in setuid codes

Of course a setuid program is a potential security risk because of this privilege escalation provided by the setuid mechanism. Sure, if the program was correctly written, without weakness nor bugs, it would be safe to have setuid code, but... You know... We are all human beings and a defensive approach to limit the potential damage it is not a bad idea. We will consider two simple good practices

  1. Drop the privileges as soon as possible, restoring them only when necessary.
  2. Consider the environment variables as tainted since their value is under the control of the user. For example, the user could set the PATH variable to force the code to execute programs under the control of the user. It would be better not using environment variables at all, but if you really need replace the variable values with trusted values (e.g. write in PATH a list of trusted directory) or, at least, sanitize the value given by the user.

What is setuid-helper and how can it help me?

setuid-helper is a small Ada library that provides a package Setuid.Helper designed to help following the above guidelines for setuid programs.

During the package initialization (elaboration in Ada jargon) the package does three things

  1. It saves the environment variables received from the user
  2. It deletes every environment variable
  3. It drops the privileges

This means that when the first instruction of the main is executed, the program runs with the privileges dropped and the environment empty. The only way to restore the privileges is by using the procedure With_Privileges_Do. This procedure exists in different flavors that differ in how the action to be done is specified.

In the simplest case

procedure With_Privileges_Do (Callback         : access procedure;
                              Drop_Permanently : Boolean := True);

the procedure With_Privileges_Do expects a Callback parameter represented by an access (a pointer for you C people 😄) to a parameter-less procedure. The semantic is really simple: the privileges are restored, Callback called and the privileged are dropped again. Unless the second parameter is False, the privileges are permanently dropped and it will not be possible to restore them again. Note that permanent dropping is the default.

This is a typical usage example where the callback is defined locally inside a declare block

      procedure Unmount_Callback is
         Unmount (Mountpoint_Name);
      end Unmount_Callback;
      With_Privileges_Do (Callback         => Unmount_Callback'Access,
                          Drop_Permanently => True);

There are two other versions of With_Privileges_Do: one expects a handler object (that can store a status), the other expects a function and it can return a value. See the .ads file and the README file for more details.

Managing the environment

As said before, it is maybe better not using environment variables at all. This is not always possible and setuid.helper provides a way to manage it as safely as possible, keeping two different environments: one normal and one privileged.

More precisely, the package mantains three "enviroments"

  1. The tainted environment. This is a copy of the original environment. It cannot be changed, but it can be read with the functions Tainted_Variable. This allows to import in a controlled way the values provided by the user in the environment.
  2. The user environment. This is used when the privileges are dropped. It is initially empty and it can be written with Set_User_Environment.
  3. The safe environment used when privileges are on. It is initially empty and it can be written with Set_Safe_Environment.

Procedure Set_Safe_Environment expects the value to be of type Safe_Value, not String. A string can be converted to a Safe_Value by calling the function Bless.

Just to be clear: it is expected that the value given to Bless is checked or sanitized, but nothing prevents the programmer to call Bless with any unsafe value. The usage of a different type is to avoid involuntary short-circuits where a value of the original environment (to be read with Tainted_Variable, nomen omen...) is given to Set_Safe_Environment

Portability, installation, ...

Just check the beginning of the README file. I work with Linux and in Linux it works; I guess it should work with any modern *nix (if it works for you under another *nix please let me know).

What about Windows? Honestly, I do not know much Windows, but I am afraid that the idea of the setuid bit is very *nix-ish. Is maybe possible to bring the same idea to Windows? Well, let me know, never say never...


setuid-helper is a pretty young library (currently is 0.1.0 since I am not sure about the stability of its API). Any feedback is welcome.


Editor guide
ferruck profile image
Philipp Trommler

Thanks for sharing, seems like a library that could come in handy sometimes. Just two questions/remarks:

  1. Why don't you initialize the user environment with the tainted environment? I get your point that you want to favour a controlled usage of the provided variables but it seems like this virtually provokes the creation of programs that don't honor user-set environment variables even where they're safe to use...
  2. Have you already published your library to Alire? ;-)
pinotattari profile image
Riccardo Bernardini Author
  1. Good question. Honestly I was unsure between the two versions, then I decided for this one that forces you to import what you really need. But it was quite a tie.

  2. No, not yet :-) Were you at FOSDEM, too?

ferruck profile image
Philipp Trommler
  1. Yes, I got that. Still I think that it'll lead to "misbehaving" (in the sense of unexpected) programs. No offense, just my two pence...
  2. Yes, indeed, and I saw your talk! 😉