0

I have a suid-to-root executable that users should run only with certain parameters, env var settings and context setup. I can't modify that exe.

I will wrap the exe in arg-less scripts, one for each allowed parameterization, env and context setup. The problem is how to allow the user to run these scripts but not run the exe other ways.

Are there any security issues with my proposed solutions below or better alternatives?

I don't know if polkit or sudo can accomplish this, but would be interested in how if anyone knows.

I have 3 solutions:

  1. use a mount namespace. Globally, mount the exe nosuid (it exits immediately if not run euid=0). Create a private mount namespace and remount the exe suid. Write a small suid exe with only arg = name of wrapper script, which enters the private namespace, drops privileges and runs the script. The wrapper scripts are all kept in a dedicated dir (/usr/local/bin/safescripts), hardcoded into the new small suid exe. The new exe always calls scripts only in that dir, which contains nothing else.

  2. use a directory "vault". Create parent-child directory pair /usr/local/lock and /usr/local/lock/vault. Both are root:root owned, /usr/local/lock is 750, /usr/local/lock/vault is 755. Users cannot get to vault unaided. But once in vault, users can get at what's there. Move the original suid exe into /usr/local/lock/vault. Write a small suid exe that takes a basename of one of the scripts, chdirs into /usr/local/lock/vault, drops privileges and runs the script. Same setup for the wrapper scripts as in 1, but they must call the original exe using "./" prefixed pathname while staying in the cwd (vault).

  3. handoff with elevated privileges (euid=0). Remove the suid bit on the original exe. Write a small suid exe that takes a basename of one of the scripts (as 1 and 2 above), and without dropping privileges, call the script (which must have #!/bin/bash -p) and allow the original exe to drop privileges itself.

I consider 3 to be too risky because so much is run with euid=0, including scripts.

Comparing the small exes of 1 and 2: 2 only does chdir (how bad can that be?) with euid=0, while 1 does an open of the bind-mounted suid namespace and setns to that fd - still small, but with more complex consequences. Also the setup of 1 requires a service to create the suid mount namespace and bind-mount it somewhere. Not much, but something. Alternatively, the new exe could create the mount namespace itself, but that makes it even more complex: it would have to call unshare and mount with elevated privs, both of which have many more options than open and setns.

I am leaning towards 2, which seems to be safest/easiest, although the vault setup seems kludgy. Unless someone can offer a better way or sees issues with 2.

3
  • "I will wrap the exe in arg-less scripts, one for each allowed parameterization, env and context setup." -- make those wrappers short C programs instead, so you can make them individually setuid, instead of the main program. Even better would be to not start privileged processes from untrusted sessions to begin with, and instead have some long-running program take instructions through e.g. a unix-domain socket and run that setuid executable on the requestors behalf with one of the sets of allowed parameters.
    – ilkkachu
    May 21, 2023 at 19:34
  • The scripts are a bit complex to make into C programs, and would not be short. The tricky part is the context that they have to set up - they do things like gunzip some files and take various action based on reading other files. Also, there are many of them, and will be more in the future. Using a socket-based daemon doesn't change this one issue. That's why I would like them to stay as scripts and not run with privileges.
    – schmeg
    May 21, 2023 at 20:53
  • the thing using a socket-based daemon changes is that then you don't inherit anything from the untrusted execution environment of the user, and there should be less issues with e.g. running the scripts with privileges, or having the scripts do the setup as a non-root user dedicated for that, and only run the final command as root. It wouldn't matter that the daemon user could run the actual command as root with any arguments, because you'd know that it wouldn't.
    – ilkkachu
    May 22, 2023 at 6:08

1 Answer 1

0

The mount namespace works, but is more complex than necessary.

The lock/vault directory trick is not secure because the /proc/PID/cwd magic symlink of the process within the vault allows other processes to cd into the vault (provided they have the same uid/gid).

Like solution 3, but with a sgid instead: Write a small sgid exe. Use a dedicated system group with no members: let's call it mygr. Change the original suid exe to be root:mygr 4750. Now the original suid exe can only be called by members of mygr, of which there are none other than achieved by calling the new small sgid-to-mygr. The new small sgid exe still controls the suid exe's usage exec'ing one of the few allowed target scripts. Because the original suid exe drops privileges in a fully general way (checked that), it will set the egid back to the rgid as well as the euid back to the ruid, so the egid=mygr will be temporary.

The scripts still need to be #!/bin/bash -p, so that egid=mygr isn't undone before the target suid exe gets exec'ed.

Because the new small exe is sgid to an otherwise useless group (mygr), there's much less chance of it being exploited for privilege escalation than a suid-to-root.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .