|
|
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). 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. Let's look at a how a developer could modify a program to be privilege aware. In particular, for our example, let's take a set-id program and configure it such that it:
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
There is nothing special about
So, let's begin this tale back in February of 2003 before the
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 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
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
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 *
58 * flags: PU_LIMITPRIVS, PU_CLEARLIMITSET, PU_CLEARINHERITABLE
59 *
60 * Caches the required privileges for use by __priv_bracket().
61 *
62 */
So this function (as used in the
# ppriv -S `pgrep ping`
According to
$ ppriv -lv net_icmpaccess
So rather than having the potential to access all of
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
1196 /* now we need the net_icmpaccess privilege */
1197 (void) __priv_bracket(PRIV_ON);
1198
1199 recv_sock = socket(family, SOCK_RAW,
1200 (family == AF_INET) ? IPPROTO_ICMP : IPPROTO_ICMPV6);
1201
1202 if (recv_sock < 0) {
1203 Fprintf(stderr, "%s: socket %s\n", progname, strerror(errno));
1204 exit(EXIT_FAILURE);
1205 }
1206
1207 /* revert to non-privileged user after opening sockets */
1208 (void) __priv_bracket(PRIV_OFF);
As you can see, the Our final step is to relinquish the privileges when we are sure that we will no longer need them:
602 __priv_relinquish();
The So, now that we have described this process, it is time to talk about one gotcha.
During the development of this HOWTO, 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 What if you are developing programs for another consolidation or something that is entirely external to OpenSolaris? Can you still implement privilege bracketing?
Absolutely! In addition to the ON private header files and functions discussed above, there are
also a set of public header files and functions that can also be used. For the sake of comparison, let's now
discuss how we can adapt the privilege-aware version of
Without further ado, let's just dive back into the code. By way of convention, I will be showing only the
changes that need be implemented to convert the
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
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
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
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
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
Moving along to the next section of replacement code, we come to where the bracketing of the
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
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 hope that this helps to illustrate how you can privilege enable your code whether you choose to use either method described above. 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!
|