Privilege Enabling Set-ID Programs

There have been quite a few posts, articles,and resources made available discussing the new process privilege model introduced the Solaris 10 operating system. The majority of them have focused on describing what this new privilege model is, why it is useful and how it can be used through mechanisms such as Solaris RBAC, SMF, and ppriv(1). Today, with the launch of OpenSolaris, I feel the urge to discuss privileges from the view of a software developer (not that I am one anymore, but please indulge me a bit).  If you are interested in this kind of thing, you can find more information about this in the Solaris 10 product documentation.

Today, let's look at a how a developer could develop a program to be privilege aware. In particular, for our example, let's take a set-id program and configure it such that it:

  1. drops any privileges that it will never need;
  2. enables the remaining privileges exactly when it needs them;
  3. relinquishes the use of privileges when they are no longer needed

Sounds pretty straight forward, right? Essentially, we just want to ensure that a program is only running with those privileges that it needs, and it activates those privileges only when it needs them. To illustrate this concept, let's take a look at ping.c. This program was converted to use Solaris privileges by Casper Dik way back in 2003. I chose to use it for this example because:

  1. the ping(1M) source code is simple and straightforward to read and understand, and
  2. all of the changes needed to make it privilege aware were contained in one file

There is nothing special about ping with respect to privileges and the same techniques described below could be applied to other programs whether they are set-id or not.

So, let's begin this tale back in February of 2003 before the ping command was made privilege aware. In those days, ping was a set-uid root program that controlled its use of privilege using seteuid(2). When the program started as root, it quickly set its effective UID to the UID of the calling user to run with less privilege. When it came time to execute a privileged operation, the code issued another seteuid call that reset its EUID to root so that the privileged operation could succeed.

With the introduction of process privileges in Solaris 10, this model was no longer needed. Rather than executing code as EUID 0, specific privileges are used to define the types of privileged operations that are be permitted. This is a huge step forward as privilege aware programs will now run only with the privileges that they need (exactly when they need them). So, enough of the fluff, let's get to the code! Note that all of the following code examples were taken from ping.c.

The first thing that you will notice that if you want to configure a program to be privilege aware, you will need to include a new header that will declare functions and define constants used by the privilege functions described below:

     72    #include <priv_utils.h>

With the header out of the way, we move into main where the first thing that we do is drop all of the privileges that we do not need:

    247         /\*
    248          \* This program needs the net_icmpaccess privileges.  We'll fail
    249          \* on the socket call and report the error there when we have
    250          \* insufficient privileges.
    251          \*/
    252         (void) __init_suid_priv(PU_CLEARLIMITSET, PRIV_NET_ICMPACCESS,
    253             (char \*)NULL);

Remember that ping is still set-uid root. This means that when it is started, it will have the privileges that have been assigned to root which by default is all. The purpose of the __init_suid_priv function is to do the following (described in priv_utils.h):

     48 /\*
     49  \* Should be run at the start of a set-uid root program;
     50  \* if the effective uid == 0 and the real uid != 0,
     51  \* the specified privileges X are assigned as follows:
     52  \*
     53  \* P = I + X + B (B added insofar allowable from L)
     54  \* E = I
     55  \* (i.e., the requested privileges are dormant, not active)
     56  \* Then resets all uids to the invoking uid; no-op if euid == uid == 0.
     57  \*
     59  \*
     60  \* Caches the required privileges for use by __priv_bracket().
     61  \*
     62  \*/

So this function (as used in the ping.c code) will reset the real and effective UID of the process to that of the calling user so that it is no longer running as root.  Further, it will clear the limit set of the process which means that any children spawned by this process will themselves have no privileges.  Lastly, this function will also add the net_icmpaccess privilege to the process' permitted set so that it can be enabled and used when necessary.  The process' four privilege sets (effective, inheritable, permitted and limit) will look like this:

# ppriv -S `pgrep ping`
14527: ping localhost
flags = PRIV_AWARE
E: basic
I: basic
P: basic,net_icmpaccess
L: none

According to ppriv, the net_icmpaccess privilege is used to allow a process to send and receive ICMP packets:

$ ppriv -lv net_icmpaccess
Allows a process to send and receive ICMP packets.

So rather than having the potential to access all of root's power, a ping process will now run as the calling user's UID with a single (non-basic) privilege that allows the sending or receiving ICMP packets.  Sounds great, but we are not done yet!  In priv_utils.h, you will find more instruction as to how to proceed:

     65 /\*
     66  \* After calling __init_suid_priv we can __priv_bracket(PRIV_ON) and
     67  \* __priv_bracket(PRIV_OFF) and __priv_relinquish to get rid of the
     68  \* privileges forever.
     69  \*/

Recall that before ping was made privilege aware, it used seteuid to control when its privileges were in effect. This was necessary for the program to only run in a privileged capacity when it needs to execute privileged operations. In the new model, the process leverages a capability called privilege bracketing which controls when a privilege (defined in the process' permitted set) is made effective. To see this capability in action, take a look at the following code which appears in the setup_socket function:

   1196         /\* now we need the net_icmpaccess privilege \*/
   1197         (void) __priv_bracket(PRIV_ON);
   1199         recv_sock = socket(family, SOCK_RAW,
   1200             (family == AF_INET) ? IPPROTO_ICMP : IPPROTO_ICMPV6);
   1202         if (recv_sock < 0) {
   1203                 Fprintf(stderr, "%s: socket %s\\n", progname, strerror(errno));
   1204                 exit(EXIT_FAILURE);
   1205         }
   1207         /\* revert to non-privileged user after opening sockets \*/
   1208         (void) __priv_bracket(PRIV_OFF);

As you can see, the __priv_bracket function is used around the privileged operation (in this case the socket(2) call) to control whether or not the instructions are executed with privilege. This is one form of privilege bracketing that enables and disables all of the privileges cached by the __init_suid_priv function called earlier in the program (and described above). There are other privilege manipulation functions available to allow more fine grained control if needed.

Our final step is to relinquish the privileges when we are sure that we will no longer need them:

    602         __priv_relinquish();

The __priv_relinquish function is called after the setup_socket function has completed in main. Since the program will no longer need the net_raw_icmpaccess privilege (the only non-basic privilege available to the process), it can now be safely dropped. The __priv_relinquish function is used for just this purpose. Note that once a privilege is relinquished, it is gone. If you think that you may need the privilege later in the program, you should simply disable its use (removing it from the process' effective set) until you need it again.

That's all there is to it.  Pretty cool, eh?

One thing that I should mention, however.  In the development of this article, I was reminded by Darren Moffat that the privilege functions and header file described above are private to Solaris (and more specifically the ON [OS and Networking] consolidation).  So, the approach described above will work just fine if you are modifying set-id programs in ON such as atq, atrm, traceroute, lpstat, and the like.  If you want to make other programs privilege aware, you can do this using a set of privilege manipulation functions described in the Solaris 10 product documentation. Regardless of the method you use, however, there should be nothing to stop you from improving your code by making your programs privilege aware and bracketing privileged operations.

Take care and have fun!

Technorati Tag:


Post a Comment:
Comments are closed for this entry.



« April 2014