Tuesday, 27 January 2009

How can I allow users to run a script as root?

I am glad you asked me that question!

In this case I need a user to nuke a process related to themself but executing as 'nx'. So to kill it they would have to be root (or nx). Killing the process and the related cleanup requires a script to automate it so I need to make a script run as root.

I can do that by making the scripting language interpreter (e.g. perl, python, etc) always run as root ("setuid"). This means that all scripts executed by that interpreter run as root.... oops. Ok bad idea as anyone can run any script they write as root. I know, I shall make the kill command always run as root. Anyone can now kill any process .... oops. Ok bad idea, slightly less a security risk though.

The solution to the above class of problems is called a 'shim' after small bits of metal. Go look the reference up if you care. The solution is make a tiny C program that when executed by a user runs as root ("setuid") and then executes the script, sanitising and passing any necessary arguments.

So here is a trivial C program to execute a single script (nxclean.c):

#define PATH "/usr/local/sbin/nxclean"
#define BUF_SIZE 10000

#include <stdio.h>
#include <unistd.h>
#include <sys.h>

int main(void)
uid_t user;
char user_str[BUF_SIZE] = {'\0'};

// printf("My UID is: %d\n", getuid());
// printf("My EUID is: %d\n", geteuid());
user = getuid();
snprintf(user_str, BUF_SIZE, "%d", user);
// printf("My UID is: %s\n", user_str);
// printf("My UID now is: %d\n", getuid());

execl(PATH, PATH, user_str, (char *) 0);

So what is going on here? Firstly a couple of #DEFINE's to save typing. Note that the buffer sizing and the initialisation of the whole buffer to '\0'. Remember this is a program that is going to run as root. It takes no command line arguments, simplifying things, but it is taking things from the underlying OS so be sure to be safe. If you are taking command line arguments then check them every which way, run regex's, size checks, the works. Otherwise kiss your security goodbye.

In this case it stores the UID of the current user as it needs it later, makes itself root via the setuid(geteuid()); line and then executes the script (now as root) passing arguments created from the UID collected earlier.

Note this only works if you have set the little executable this compiles into to run as root. Google "setuid".