Privilege Enabling Set-ID Programs: Part 2

Previously, we talked about how the ping program was modified in Solaris 10 to be privilege aware.  During that discussion, it was noted that the functions used in the ping program were private to the OpenSolaris ON (OS and Networking) consolidation.  While this is fine for programs like ping that reside in that consolidation, it does not offer a solution to programs in other consolidations or for those developed external to OpenSolaris.  As a companion to that article, let's now discuss how we can adapt ping.c to use the public privilege manipulation functions yet still accomplish the same result - making the program privilege-aware and implementing bracketing around privileged operations.

Without further ado, let's just dive into the code.  By way of convention, I will be showing only the changes that need be implemented to covert the original ping.c program to use the new public functions.

The first thing that you will notice is that a different header file is used.  If you want to use the public interfaces, then you should be sure to include priv.h and not priv_utils.h:

72c72
< #include <priv_utils.h>
---
> #include <priv.h>

Next, we move to the section of the code that configured the privilege sets at the start of the program.  This was done in order to drop any privileges that were not needed and disable those that were left (and not needed right now).  This was originally accomplished using the __init_suid_priv function which provided a convenient wrapper for the functionality described below.  Rather than a single line, we need to do a little more work.  To make the code easier to follow, a new function, setup_privs, was created to do the initial privilege operations.  This handles the majority of the work originally done by __init_suid_priv.  This code is fairly well documented, so I will not go into too much detail as to what it does.

225a226
> static priv_set_t \*setup_privs(void);
227a229,298
>  \* setup_privs()
>  \*/
> priv_set_t \*
> setup_privs(void)
> {
>     priv_set_t \*pPrivSet = NULL;
>     priv_set_t \*lPrivSet = NULL;
>
>     /\*
>      \* Start with the 'basic' privilege set and then remove any
>      \* of the 'basic' privileges that will not be needed by this
>      \* process.  The 'net_icmpaccess' privilege will be added
>      \* since we know that we will need it for the permitted set.
>      \*/
>
>     if ((pPrivSet = priv_str_to_set("basic", ",", NULL)) == NULL) {
>             perror("priv_str_to_set");
>             return (NULL);
>     }
>
>     /\*
>      \* Let's clear all of the privileges we know we will not
>      \* need from the 'basic' set.
>      \*/
>
>     (void) priv_delset(pPrivSet, PRIV_FILE_LINK_ANY);
>     (void) priv_delset(pPrivSet, PRIV_PROC_EXEC);
>     (void) priv_delset(pPrivSet, PRIV_PROC_FORK);
>     (void) priv_delset(pPrivSet, PRIV_PROC_INFO);
>     (void) priv_delset(pPrivSet, PRIV_PROC_SESSION);
>
>     /\* Next add the known required privilege, 'net_icmpaccess' \*/
>
>     (void) priv_addset(pPrivSet, PRIV_NET_ICMPACCESS);
>
>     /\* Set the permitted privilege set. \*/
>
>     if (setppriv(PRIV_SET, PRIV_PERMITTED, pPrivSet) != 0) {
>             perror("setppriv(PRIV_SET, PRIV_PERMITTED)");
>             return (NULL);
>     }
>
>     /\* Ensure that the 'net_icmpaccess' privilege is off by default. \*/
>
>     if (priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_NET_ICMPACCESS,
>             NULL) != 0) {
>             perror("priv_set(PRIV_OFF, PRIV_EFFECTIVE)");
>             return (NULL);
>     }
>
>     /\* Clear the limit set. \*/
>
>     if ((lPrivSet = priv_allocset()) == NULL) {
>             perror("priv_allocset");
>             return (NULL);
>     }
>
>     priv_emptyset(lPrivSet);
>
>     if (setppriv(PRIV_SET, PRIV_LIMIT, lPrivSet) != 0) {
>             perror("setppriv(PRIV_SET, PRIV_LIMIT)");
>             return (NULL);
>     }
>
>     priv_freeset(lPrivSet);
>
>     return (pPrivSet);
> }
>
> /\*

So, why do we go through the process of starting with the basic set of privileges and removing them all?  Why not just start out with no privileges and simply the code?  The answer lies in the fact that the basic privilege set is not intended to be static.  Over time additional non-administrative privileges may be added.  If you started with simply no privileges, then you may find that your code will fail as it will need a privilege for an operation that previously did not require one.  In essence, this is a way of future-proofing your code so that it can better adapt to changes in Solaris down the road.

So at this point, we have defined a function that will help minic most of the behavior that had been done by __init_suid_priv.  Let's take a look at how this function is used:

243a315
>     priv_set_t \*privSet = NULL;
252,253d323
<     (void) __init_suid_priv(PU_CLEARLIMITSET, PRIV_NET_ICMPACCESS,
<         (char \*)NULL);
254a325,337
>     if ((privSet = setup_privs()) == NULL) {
>             exit(EXIT_FAILURE);
>     }
>
>     /\*
>      \* Reset the real and effective UIDs for this process.
>      \*/
>
>     if (setreuid(getuid(), getuid()) != 0) {
>             perror("setreuid");
>             exit(EXIT_FAILURE);
>     }
>

As you can see, the __init_suid_priv call has been replaced by calls to both setup_privs and setreuid(2) functions.  This is a fairly simple replacement since all of the "hard work" was done in the setup_privs function.  We capture the privilege set parameter, privSet, as we will need it later in the code when it comes time to relinquish our privileges.  Let's continue sequentially down the code to see where other replacements are needed.  Frankly, the hard part is over.  The rest of the changes needed to complete the conversion from private to public privilege manipulation functions are trivial.  Let's take a look at the next replacement:

602c685,687
<     __priv_relinquish();
---
>     /\*
>      \* Clear the permitted set of the 'net_icmpaccess' privilege.
>      \*/
603a689,696
>     (void) priv_delset(privSet, PRIV_NET_ICMPACCESS);
>
>     if (setppriv(PRIV_SET, PRIV_PERMITTED, privSet) != 0) {
>             perror("setppriv(PRIV_PERMITTED)");
>             exit(EXIT_FAILURE);
>     }
>     priv_freeset(privSet);
>

In this case, we are replacing the __priv_relinquish function with a call to setppriv(2).  Before we can do this however, we need to remove the net_icmpaccess privilege from privSet using the priv_delset function.  Remember that privSet (returned to us from setup_privs earlier in the code) contains the privileges from the basic set that we had not already dropped as well as the net_icmpaccess privilege.  By removing the net_icmpaccess privilege, setppriv will set to the process' permitted privilege set to the non-dropped basic privileges (which in this particular case is none although as Solaris continues to evolve and new non-administrative privileges are added, this may change).

Moving along to the next section of replacement code, we come to where the bracketing of the net_icmpaccess privilege is enforced.  In thiscase, the call to __priv_bracket is replaced with a call to priv_set(3C) function.  priv_set is called with the PRIV_ON parameter which enables the net_icmpaccess privilege for the process' effective privilege set.

1197c1290,1293
<     (void) __priv_bracket(PRIV_ON);
---
>     if (priv_set(PRIV_ON, PRIV_EFFECTIVE, PRIV_NET_ICMPACCESS, NULL) != 0) {
>             perror("priv_set(PRIV_ON, PRIV_EFFECTIVE)");
>             exit(EXIT_FAILURE);
>     }

Similarly, the companion instance of __priv_bracket is replaced with another call to priv_set once the privileged operations are complete to remove the net_icmpaccess privilege (from the process' effective privilege set) - therefore completing the bracketing of privilege.

1208c1304,1308
<     (void) __priv_bracket(PRIV_OFF);
---
>     if (priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_NET_ICMPACCESS,
>             NULL) != 0) {
>             perror("priv_set(PRIV_OFF, PRIV_EFFECTIVE)");
>             exit(EXIT_FAILURE);
>     }

That is all there is to it.  As you can tell, there is a bit more work in the initial setup of a process' privilege sets, but once that is complete, the use of the public privilege manipulation functions is straightforward.  I know that this helps to illustrate how you can privilege enable your code whether you choose to use ON specific functions or the public ones defined in the Solaris product documentation.  For those who just can't get enough, check out Joep Vesseur's discussion of several developer-focused security enhancements in Solaris 10 (and OpenSolaris).

Last, but certainly not least, I would like to offer my sincere thanks to Casper, Joep and Darren for their help with this article.  You guys are the best!

Take care and have fun!

Technorati Tag:

Comments:

Post a Comment:
Comments are closed for this entry.
About

gbrunett

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today