10

I'm working on an indicator for Ubuntu, and one of the tasks it's supposed to perform is chmod -x and chmod +x a specific root-owned binary (/usr/lib/x86_64-linux-gnu/notify-osd to be precise). As far as I understand, my options are:

  • run command with pkexec each time ( which I kind of want to avoid for the sake of user experience );
  • since the indicator is in python, I could have a binary with setuid bit set written in C. However, I've been told this approach is frowned upon;
  • start a "proxy" which will run as root and only perform that one specific task , and the indicator will communicate with that "proxy"

My question really is , which approach would be best in terms of security AND user experience ? How should I approach implementing an app that needs one, very specific task to be done as root , and the rest - as regular user.

  • 2
    You're definitely asking the right questions. I wish more people stopped and considered what "least privilege" actually means. – Nathan Osman Oct 29 '16 at 6:14
  • 2
    For the C binary, you could consider adding the CAP_FOWNER capability to the binary instead of using setuid, which will limit its permissions. – user2313067 Oct 29 '16 at 10:07
2

There's not really one correct answer to this question. It's a trade off between security and usability.

run command with pkexec each time ( which I kind of want to avoid for the sake of user experience );

If the intended audience does not have "CLIphobia", sudo could be really great here if "each time" is within 15 minutes.

But from a security point of view, I'd recommend pkexec. I assume it can be trusted as it comes pre-installed with the distro, unlike some quickly hacked up setuid wrapper or daemon.

since the indicator is in python, I could have a binary with setuid bit set written in C. However, I've been told this approach is frowned upon;

The only problem I have heard of with setuid wrappers is that they can overwrite themselves with a trojan if they exploited, but this is not relevant for setuid-to-/root/ binaries as any exploit on a process running as root means you're screwed anyway.

The only problem I have experienced does not apply to setuid-to-/root/ binaries either, it was so that I did not understand all of unix user ids and forgot to set the real user ID, saved user ID and their group ID equivalents, this meant that a program that /drops/ privileges can still gain them back.

There could be other reasons why setuid wrappers are frowned upon, but I haven't heard of any. If no one else can find reasons why setuid wrappers are frowned upon, a setuid wrapper will be a really good choice.

start a "proxy" which will run as root and only perform that one specific task , and the indicator will communicate with that "proxy"

If you create a daemon to do the task, keep it as simple as possible and make sure that unauthorized users don't get to communicate with it.

I can think of two options: (1) use a password that an authorized client would know, the daemon does know and an unauthorized client does not know. The password would have to be stored in a file chmoded 400 and owned by root.

(2) use unix permissions on the socket to prevent any other local user to even try to (ab)use the daemon. https://stackoverflow.com/questions/20171747/how-to-create-unix-domain-socket-with-a-specific-permissions

Option two can be considered less secure and more inconsistent at UX. The user might not understand why it won't work when they're logged in as a different user which is really bad when it does happen.

2

First, I would like to address the question in the title:

How to approach creating a Linux app that requires root privilege?

Don't require or obtain all-out root privileges unless you absolutely, positively require them. Rather, as pointed out by user2313067 in a comment, use capabilities. From man 7 capabilities:

Starting with kernel 2.2, Linux divides the privileges traditionally associated with superuser into distinct units, known as capabilities, which can be independently enabled and disabled. Capabilities are a per-thread attribute.

Linux 2.2 was released in early 1999 and, as a consequence, is over 17 years old as of my writing this. Refusing to run on kernels that don't support capabilities is certainly not out of the realm of reasonable these days even for widely distributed software.

Specifically, in this case, your process would need the CAP_FOWNER capability, which among some other things tells the kernel to:

Bypass permission checks on operations that normally require the file system UID of the process to match the UID of the file (e.g., chmod(2), utime(2)), excluding those operations covered by CAP_DAC_OVERRIDE and CAP_DAC_READ_SEARCH

Second, generally speaking the best approach is to limit any extended permissions as much as possible in scope. In your case, that probably means a small program designed to only turn on or off the executable bit on the file in question, and nothing more. Create a small program, perhaps with the relevant file name hard coded (I suggest a #define macro if you use C, or some similar mechanism if you use another language), which takes as input a single, single-character parameter that says whether the executable bit should be on or off. Do loads of sanity checking and then call stat() and chmod() (not /usr/bin/stat and /bin/chmod) directly with the appropriate parameters. That way, even if there is a bug in your small program, it becomes very difficult to take advantage of it to do anything other than what was intended. Call this program with a full path, and make sure it cannot be used by any unauthorized user (and certainly not modified by any). Consider compiling it statically to reduce the risk of library preloader attacks.

Third, consider why you need to change the executable bit in the first place. Are you perhaps using global state to manage a local problem? There might be a way to accomplish what you are trying to do without needing to resort to file permission changes in the first place. Make sure you have exhausted all other reasonable options before you resort to changing persistent, global system state.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.