Privilege Vulnerabilities
Now that you are familiar with the basic privilege management API, you can explore the types of mistakes developers are likely to make when attempting to perform privilege management.
Reckless Use of Privileges
The most straightforward type of privilege vulnerability happens when a program running with elevated privileges performs a potentially dangerous action on behalf of an unprivileged user without first imposing any limitations on itself with privilege management functions. Although it is possible for programs to safely access resources without needing to temporarily or permanently drop privileges, it is very easy to make mistakes when doing so.
Here is a simple real-world example of a setuid root program named XF86_SVGA that used to ship with the XFree86 windowing package. Nicolas Dubee, a notorious and gifted researcher, discovered this vulnerability in 1997. Listing 9-1 is an excerpt from his advisory (available at http://packetstormsecurity.org/advisories/plaguez/plaguez.advisory.010.xfree86).
Listing 9-1. Privilege Misuse in XFree86 SVGA Server
[plaguez@plaguez plaguez]$ ls -al /etc/shadow -rw---- 1 root bin 1039 Aug 21 20:12 /etc/shadow [plaguez@plaguez bin]$ ID uid=502(plaguez) gid=500(users) groups=500(users) [plaguez@plaguez plaguez]$ cd /usr/X11R6/bin [plaguez@plaguez bin]$ ./XF86_SVGA -config /etc/shadow Unrecognized option: root:qEXaUxSeQ45ls:10171:-1:-1:-1:-1:-1:-1 use: X [:<display>] [option] -a # mouse acceleration (pixels) -ac disable access control restrictions -audit int set audit trail level -auth file select authorization file bc enable bug compatibility -bs disable any backing store support -c turns off key-click
|
The XF86_SVGA server, which was a setuid root program, happily read the configuration file /etc/shadow, and then proceeded to complain about the unrecognized option of root's password hash! The problem is that the X server would read in any configuration file the user requested as root, without regard for the actual user's permissions. Its configuration file parser happened to display a verbose error message, which printed the first line of the suspect configuration file.
Considering the effects of any elevated group privileges is important, too. Many programs are installed as setgid so that they run as a member of a particular group. If the program performs a privileged action without relinquishing group privileges, it can still be vulnerable to a privilege escalation attack by allowing the user to access resources designated to the group in question.
For example, the /sbin/dump program in NetBSD was installed as setgid tty so that it could notify system administrators if backup media needed to be changed. The dump program never dropped this group privilege, and local users could have the dump program start a program of their choice by setting the libc environment variable RCMD_CMD. This program would then run with an effective group ID of tty. Attackers could seize group tty privileges, which could allow them to interact with other user's terminals.
Dropping Privileges Permanently
Occasionally, application developers will make mistakes when writing the code for a program that permanently relinquishes its privileges. The following sample code represents part of a setuid root program:
/* set up special socket */ setup_socket();
/* drop root privs */ setuid(getuid());
/* main processing loop */ start_procloop();
This code is similar in spirit to what you find in several common network programs. The program needs to be root to obtain a socket bound to a port below 1024 or to obtain a special socket for sniffing. The author wants the program to be safe and follow a least-privilege design, so after obtaining this socket, the program drops its root privileges by performing a setuid(getuid()), which sets the saved set-user-ID, the real user ID, and the effective user ID to the value of the real user ID.
setuid(getuid()) is a common idiom for permanently relinquishing privileges, and it usually works without too many complications. However, in some situations, it's not enough, as explained in the following sections.
Dropping Group Privileges
Some programs are installed as both setuid and setgid, meaning they run with an elevated user ID and elevated group ID. The code in the previous section would be fine if the program is only setuid root, but if the program is setuid root and setgid wheel, the elevated group privileges aren't relinquished correctly. In the processing loop, the effective group ID of the process is still set to the privileged wheel group, so if attackers found a way to exploit the program in the main processing loop, they could gain access to resources available to that privileged group. The correct way to address this problem is to relinquish group privileges like this:
/* set up special socket */ setup_socket();
/* drop root privs - correct order */ setgid(getgid()); setuid(getuid()); /* main processing loop */ start_procloop();
This code drops the group permissions and then the user permissions. It seems fairly straightforward, but it can actually be done incorrectly, as shown in the following example:
/* set up special socket */ setup_socket();
/* drop root privs incorrect order */ setuid(getuid()); setgid(getgid());
/* main processing loop */ start_procloop();
This code doesn't fully work because the function calls are ordered incorrectly. The setuid(getuid()) function relinquishes root privileges. Remember that having an effective group ID of 0 doesn't mean you are a superuser, as superuser status is based solely on your effective user ID. The setgid(getgid()) call is performed with privileges of the nonprivileged user, so the result of the setgid(getgid()) call depends on the OS. In Linux, Solaris, and OpenBSD, only the effective group ID is modified, and the saved set-group-ID still contains the group ID of the privileged group. If attackers find a flaw in the program they could leverage to run arbitrary code, they could perform a setegid(0) or setregid(-1, 0) and recover the elevated group privileges.
Dropping Supplemental Group Privileges
Programs running as daemons can run into security issues related to dropping privileges that are a little different from setuid programs. This is because they are typically started as a privileged user and then assume the role of an unprivileged user based on user input. In this situation, you have to be cognizant of supplemental group IDs because if they aren't updated when privileges are dropped, they could leave the process with access to privileged resources.
Certain implementations of the rsync application contained a vulnerability of this nature, which is detailed at http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2002-0080. If rsync runs as a daemon, it starts off with the user ID and groups of the user running the daemon (typically root). If the rsync daemon needs to operate as an unprivileged user, it runs the following code:
if (am_root) { if (setgid(gid)) { rsyserr(FERROR, errno, "setgid %d failed", (int) gid); io_printf(fd,"@ERROR: setgid failed\n"); return -1; }
if (setuid(uid)) { rsyserr(FERROR, errno, "setuid %d failed", (int) uid); io_printf(fd,"@ERROR: setuid failed\n"); return -1; }
am_root = (getuid() == 0); }
This code releases the effective group ID before the effective user ID, so it should drop those privileges in the correct order. However, this code doesn't drop the supplementary group privileges! The developers solved this problem by inserting the following code:
#ifdef HAVE_SETGROUPS /* Get rid of any supplementary groups this process * might have inherited. */ if (setgroups(0, NULL)) { rsyserr(FERROR, errno, "setgroups failed"); io_printf(fd, "@ERROR: setgroups failed\n"); return -1; } #endif ... if (setgid(gid)) {
Note that setgroups() works only if you are the superuser and have an effective user ID of 0. This is another reason it's important to relinquish privileges in the correct order.
Dropping Nonsuperuser Elevated Privileges
As discussed earlier, the behavior of the setuid() and setgid() functions are different if the program isn't running as the superuser. setuid(getuid()) is a reasonable idiom for a program running as root that wants to drop privileges permanently, but if the effective user ID isn't 0, the same tactic yields system-dependant, and sometimes inadequate results.
Say that the simple network program was changed so that instead of being setuid root and setgid wheel, it's setuid to another nonprivileged user, such as daemon. This might happen if you installed a kernel-hardening patch that let programs with a particular user ID or group ID allocate special sockets to avoid the root privilege requirement. The code would look the same:
/* set up special socket */ setup_socket();
/* drop root privs */ setgid(getgid()); setuid(getuid());
/* main processing loop */ start_procloop();
However, the semantics of this code would be quite different when not running with an effective user ID of 0. Both setgid() and setuid() would be called as nonprivileged users, and they would change only the effective IDs, not the saved IDs. (In FreeBSD and NetBSD, this code would change all three IDs, so it wouldn't be vulnerable.) Attackers who exploited a problem in the program could therefore regain any relinquished privileges. The solution for nonsetuid root applications that need to fully drop their privileges is to use the setresgid() and setresuid() functions or the setregid() and setreuid() functions if necessary. OpenBSD versions before 2.3 require two calls to setuid().
A noted researcher named Michael Zalewski found a bug in Sendmail 8.12.0 (documented at www.sendmail.org/releases/8.12.1.html) that's a good real-world example of this situation. Sendmail used to install a set-user-ID root binary, but in version 8.12.0, it moved to a new configuration, with a set-group-ID smssp binary. Here's the code that is intended to drop the elevated group privileges:
int drop_privileges(to_real_uid) bool to_real_uid; { int rval = EX_OK; GIDSET_T emptygidset[1]; ... if (to_real_uid) { RunAsUserName = RealUserName; RunAsUid = RealUid; RunAsGid = RealGid; }
/* make sure no one can grab open descriptors for secret files */ endpwent(); sm_mbdb_terminate();
/* reset group permissions; these can be set later */ emptygidset[0] = (to_real_uid || RunAsGid != 0) ? RunAsGid : getegid();
if (setgroups(1, emptygidset) == -1 && geteuid() == 0) { syserr("drop_privileges: setgroups(1, %d) failed", (int) emptygidset[0]); rval = EX_OSERR; } /* reset primary group and user ID */ if ((to_real_uid || RunAsGid != 0) && EffGid != RunAsGid && setgid(RunAsGid) < 0) { syserr("drop_privileges: setgid(%d) failed", (int) RunAsGid); rval = EX_OSERR; } }
First, setgroups() fails, but that's fine because the supplemental groups are ones for the real user, which is a nonprivileged account. setgid() successfully changes the effective group ID from the saved set-group-ID to the real group ID but doesn't fully drop the privileges (except in FreeBSD and NetBSD). The saved set-group-ID still has the privileged smssp group ID. The Sendmail developers fixed the issue by replacing the call to setgid() with conditionally compiled calls to setresgid() or setregid(), depending on which function is available.
Mixing Temporary and Permanent Privilege Relinquishment
Many applications designed to run in an elevated context are programmed by security-conscious developers who adopt a model of least privilegesrunning an application with the minimal set of privileges it requires at a certain time to achieve its objectives. This model often means running as the invoking user for the bulk of the program and temporarily switching to a more powerful user when a privileged operation is required. If no more privileged operations are required, often the application permanently relinquishes its elevated user-ID by using setuid(). Although this model is preferred for developing a privileged application, subtle errors can result in using setuid() when the effective user-ID has been changed previously, as shown in this example:
#define STARTPRIV seteuid(0); #define ENDPRIV seteuid(realuid);
void main_loop(void) { uid_t realuid=getuid();
/* don't need privileges */ seteuid(realuid); /* process data */ ... STARTPRIV do_privileged_action(); ENDPRIV /* process more data */ ... /* done with root privs - drop permanently */ setuid(realuid); /* process yet more data */ ... }
This code starts out by relinquishing its privileges temporarily with seteuid(realuid). When the program needs its root privileges, it uses the STARTPRIV macro to obtain them and the ENDPRIV macro to release them. Those macros work by calling seteuid(0) and seteuid(realuid), respectively. After a bit of processing, the program decides it wants to fully drop its privileges, and it does that with the common idiom setuid(realuid). The problem is that at this point, the effective user ID is the real user ID of the program, not 0. Therefore, setuid(realuid) doesn't affect the saved set-user-ID in most UNIX implementations, with FreeBSD and NetBSD being the major exceptions. If attackers find a way to co-opt the program after the final privilege drop and run a seteuid(0), they could recover root privileges from the saved set-user-ID.
Here's another example:
void temp_drop(void) { seteuid(getuid()); }
void temp_gain(void) { seteuid(0); }
void main_loop(void) { ... while (options) { ... if (unsafe_option) { temp_drop();
if (process_option()==END_OF_OPTIONS) goto step2;
temp_gain(); } ... } ... step2: /* drop root privs */ setuid(getuid()); ... }
This code represents a simple set-user-ID root application. The main loop contains two steps: option processing and main processing. The option-processing code needs root privileges, but it temporarily drops them to process a potentially unsafe option. After the option-processing code is completed, the program enters step2, the main processing section. The rest of the code is complex and potentially prone to security issues, so it fully drops privileges with a setuid(getuid()) before continuing.
The problem is that if an unsafe option signals that the option processing is prematurely complete, the jump to step2 happens while privileges are temporarily dropped. The setuid(getuid()) call succeeds, but it doesn't correctly clear the saved set-user-ID in the process, except in FreeBSD and NetBSD. Therefore, if there's an exploitable problem in the main processing code, users can reclaim root privileges by performing a seteuid(0), which succeeds because the saved set-user-ID is still 0.
Dropping Privileges Temporarily
Temporary dropping of privileges can also be difficult to implement correctly. Many of the pitfalls in permanent privilege relinquishment can be applied to temporary privilege changes as well. Furthermore, dropping group privileges (and supplemental group privileges) is an easy step to overlook. Finally, the order in which privileges are relinquished can cause some privileges to be retained mistakenly.
Using the Wrong Idiom
If you drop privileges temporarily, your program is still vulnerable to a low-level attack, such as a buffer overflow. If attackers can run arbitrary code within the context of your process, they can issue the necessary system calls to propagate a saved set-user-ID to the effective and real user ID fields and regain privileges. To avoid this possibility, dropping privileges permanently as soon as possible is the safest option for a setuid application.
Tcptraceroute had a simple permission-related problem that a security specialist from Debian Linux named Matt Zimmerman discovered. The program intended to drop privileges permanently, but the author used the idiom for dropping privileges temporarily. Here's the vulnerable code:
defaults(); initcapture(); seteuid(getuid()); return trace(); }
This mistake was a simple one: The authors used the wrong function. They should have used setuid() rather than seteuid() to prevent privileges from being reclaimed later. Any memory corruption vulnerability that occurred in the application's trace() function could allow privileges to be regained simply by using seteuid(0). The full advisory is archived at http://freshmeat.net/articles/view/893/.
Dropping Group Privileges
Now take a look at a real-world example of a vulnerability related to dropping group privileges in the wrong order. (This vulnerability is documented in the FreeBSD security advisory FreeBSD-SA-01:11.inetd, which can be found at http://security.freebsd.org/advisories/FreeBSD-SA-01:11.inetd.asc.) The inetd server in FreeBSD contains code to handle the IDENT service, which remote users query to learn the user associated with any TCP connection on the machine. The service has an option thatallows users to place a .fakeid file in their home directory, which can contain a name the ident server provides instead of the real username. Because the ident server runs as root, the code in Listing 9-2 was used to drop privileges temporarily.
Listing 9-2. Incorrect Temporary Privilege Relinquishment in FreeBSD Inetd
/* * Here, if enabled, we read a user's ".fakeid" file in * their home directory. It consists of a line * containing the name they want. */ if (fflag) { FILE *fakeid = NULL; int fakeid_fd;
if (asprintf(&p, "%s/.fakeid", pw->pw_dir) == -1) iderror(lport, fport, s, errno); /* * Here we set ourself to effectively be the user, * so we don't open any files we have no permission * to open, especially symbolic links to sensitive * root-owned files or devices. */ seteuid(pw->pw_uid); setegid(pw->pw_gid); ...
|
This code first calls seteuid() to take on the user's privileges. It then calls setegid() to take on the caller's effective group ID, but this call fails because the program has relinquished its superuser privileges.
Using More Than One Account
To understand this problem, consider a daemon that needs to use more than one user account. (This example is based on one provided by Jan Wolter, a software designer that wrote an interesting paper entitled "Unix Incompatibility Notes: UID Function Setting," available at www.unixpapa.com/incnote/setuid.html.) Here's an example of how it might be implemented:
/* become user1 */ seteuid(user1); process_log1();
/* become user2 */ seteuid(user2); process_log2();
/* become root again */ seteuid(0);
The intent of this code is to do some processing as user1, and then assume the identity of user2 and do further processing. This implementation is flawed, however, because the call to seteuid(user2) fails because the program's effective user ID is no longer 0; it's user1. Correct code would have a seteuid(0) before the seteuid(user2) call.
Auditing Privilege-Management Code
Now that you have seen a variety of vulnerabilities in code running with special privileges, you can focus on a method for auditing how those privileges are managed throughout the application's lifespan. You can use the steps in the following sections to help you decide whether privilege management has been implemented correctly and adequately inhibits users' ability to exploit the application. You consider two main cases: an application that intends to drop privileges permanently and an application that intends to drop privileges temporarily.
Permanent Dropping of Privileges
Some programs run with root privileges and want to discard these root privileges permanently. When auditing an application that runs in a privileged context and you encounter this scenario, you need to address the following points:
Make sure the code that's going to drop privileges permanently is running with an effective user ID of 0. If it's not, it probably won't be able to drop privileges effectively. Look for possible unexpected code paths where the program might temporarily drop privileges and then permanently drop privileges without restoring temporary privileges first. If supplemental groups are potentially unsafe, make sure they are cleared with setgroups(). Again, setgroups() works only when running with an effective user ID of 0. Make sure the real group ID, the saved set-group-ID, and the effective group ID are set to an unprivileged group, usually done with setgid(getgid()). Look for code that mistakenly uses setegid() to try to drop privileges. Make sure the real user ID, the saved set-user-ID, and the effective user ID are set to an unprivileged user, usually done with setuid(getuid()). Keep an eye outfor code that mistakenly uses seteuid() to try to drop privileges. Make sure the privileged groups and supplemental groups are dropped before the process gives up its effective user ID of root. Otherwise, the program is likely to expose privileged group credentials.
There are also programs that run without root privileges but want to discard one set of privileges permanently; for those programs, check the following points:
The programmer can't modify groups with setgroups(), as this function works only for superusers. If the program requires this functionality but doesn't have root privileges, it has a design flaw. Programmers run into difficulty when using the setgid(getgid()) idiom because it probably leaves the saved set-group-ID set to a privileged group. You can suggest the use of setregid(getgid(), getgid()) or setresgid(getgid(), getgid(), getgid()), which sets all three group IDs to the real group ID. This method doesn't work in older versions of OpenBSD, however. You can instead suggest using setgid(getgid()) twice in a row to clear the saved set-group-ID. Similarly, developers run into difficulty using the setuid(getuid()) idiom because it probably leaves the saved set-user-ID set to a privileged user. setreuid(getuid(), getuid()) or setresuid(getuid(), getuid(), getuid()) should work to set all three user IDs to the real user ID. This method doesn't work in older versions of OpenBSD, but you can instead suggest using setuid(getuid()) twice in a row.
Temporary Dropping of Privileges
If programs need to drop their privileges temporarily, check for the following:
Make sure the code drops any relevant group permissions as well as supplemental group permissions. Make sure the code drops group permissions before user permissions. Make sure the code restores privileges before attempting to drop privileges again, either temporarily or permanently. Think about the consequences of changing the effective user ID for signals, debugging APIs, and special device files. These issues are discussed in more depth in this chapter and Chapter 10, "UNIX II: Processes." Signals are dealt with separately in Chapter 13, "Synchronization and State."
Function Audit Logs for Privileged Applications
As a useful auditing aid, you might find it advantageous to note in your function audit logs (described in Chapter 7, "Program Building Blocks") the privileges that each function runs with when auditing applications that switch privilege contexts. This is as simple as adding in an additional two entries for a function (See Table 9-5).
Table 9-5. Function Audit Log AdditionUser Privileges | RUID=user, EUID=root, SUID=root | Group Privileges | RGID=users, EGID=users, SGID=users, SUPP=users |
The example indicates both the user and group privileges in effect when the program is run. RUID, EUID, and SUID stand for "Real UID", "Effective UID", and "Saved set UID" respectively. The next row uses RGID, EGID, SGID, and SUPP to stand for "Real GID", "Effective GID", "Saved set GID", and "Supplemental Groups" respectively. You also need to add to your notes for the function if it changes privileges throughout the course of the function, and in which cases it will change privileges. This little addition to a standard audit log allows you to quickly and accurately assess whether resource accesses within the function are potentially dangerous or not.
Note You saw that the privilege management API can behave differently on different UNIX systems, and, as such, you might not be able to correctly assess what the user and group privileges will be for a particular function. In this case, you also should make a note in the function audit log if non-portable privilege API usage might cause the application to behave differently on other OSs.
Privilege Extensions
The UNIX privilege model often comes under criticism because of its all-or-nothing design. If you're the root user, you have the unrestricted ability to wreak havoc on the system because you're granted access to any resource you want. To understand why this is a problem, return to one of the examples used in the discussion of user IDs. The ping program requires root privileges to run because it needs to create a raw socket. If a vulnerability is discovered in ping that is exploitable before it drops privileges, not only can users create a raw socket, but they can also modify any file on the system, potentially load kernel modules to hijack system functionality, delete log files, and steal sensitive data. So any program that needs to perform an operation requiring special privileges essentially puts the entire system's security at stake. Several technologies, discussed in the following sections, have been developed to combat this age-old problem.
Linux File System IDs
One set of IDs not mentioned previously is relevant to code running on a Linux system. In Linux, each process also maintains a file system user ID (fsuid) and a file system group ID (fsgid). These IDs were created to address a potential security problem with signals. If you recall, when a daemon running as root temporarily drops privileges to assume a user's role, it sets its effective user ID to the ID of the less privileged user.
This behavior can lead to security issues because a process's effective user ID is used in security checks throughout the kernel. Specifically, it's used to determine whether certain signals can be sent to a process from another process. Because of this checking, when the daemon assumes the effective user ID of a local user on the machine, that user might be able to send signals and perhaps even attach a debugger to the daemon.
To address this issue, the Linux kernel programmers created the fsuid and fsgid to be used for all file system accesses. These IDs are usually kept 100% synced with the effective user ID, so their presence doesn't affect use of the normal privilege-management APIs. However, a program that wants to temporarily use a normal user's file system privileges without exposure to attacks caused by security checks based on effective IDs can simply change its file system user and group IDs with the API calls setfsuid() and setfsgid().
BSD securelevels
The BSD securelevels kernel protection (now supported by Linux to some extent) is intended to protect the system kernel from the root user. The primary focus of securelevels is to enforce some restrictions on every user on the system, including the superuser, so that a root compromise doesn't render a machine completely vulnerable. It uses a systemwide kernel value, the "securelevel," to help decide what actions system users are allowed to perform. The different branches and versions of BSD vary in the number of levels they provide and the protection each level offers, but the idea is essentially the same in each version. The following excerpt from the init(8) man page describes the available levels:
The kernel runs with four different levels of security. Any superuser process can raise the security level, but only init can lower it. The security levels are:
-1 Permanently insecure modealways run the system in level 0 mode.
0 Insecure modeimmutable and append-only flags may be turned off. All devices may be read or written subject to their permissions.
1 Secure modethe system immutable and system append-only flags may not be turned off; disks for mounted filesystems, /dev/mem, and /dev/kmem may not be opened for writing.
2 Highly secure modesame as secure mode, plus disks may not be opened for writing (except by mount(2)) whether mounted or not. This level precludes tampering with filesystems by unmounting them, but also inhibits running newfs(8) while the system is multi-user.
If the security level is initially -1, then init leaves it unchanged. Otherwise, init arranges to run the system in level 0 mode while single user and in level 1 mode while multiuser. If level 2 mode is desired while running multiuser, it can be set while single user, e.g., in the startup script /etc/rc, using sysctl(8).
As you can see, this systemwide setting can inhibit actions for even superusers. Although it offers a level of protection, it doesn't allow fine-tuning for specific processes and can be susceptible to bypasses by users modifying certain files and restarting the machine if they gain root access.
Capabilities
Linux has also undertaken the challenge of addressing the basic UNIX privilege shortcomings by implementing a technology known as capabilities. This model defines a set of administrative tasks (capabilities) that can be granted to or restricted from a process running with elevated privileges. Some of the defined capabilities include the following:
CAP_CHOWN Provides the capability to change the ownership of a file. CAP_SETUID/CAP_SETGID Provides the capability to manipulate a user and group privileges of a process by using the set*id() functions discussed previously. CAP_NET_RAW Provides the capability to use raw sockets. CAP_NET_BIND_SERVICE Provides the capability to bind to a "privileged" UDP or TCP port (that is, one lower than 1024). CAP_SYS_MODULE Provides the capability to load and unload kernel modules.
Being able to grant and omit certain capabilities from applications makes it possible to create processes that have one special system capability without putting the entire system at risk if it's compromised. The ping program is a perfect example. Instead of having it run with full permissions to create a raw socket, it could be granted the CAP_NET_RAW privilege. If the program is compromised, attackers can create raw sockets (which is still a breach), but can't automatically load kernel modules or mount new file systems, for example.
Capabilities are applied to running processes but can also be applied to files on disk to enforce restrictions or grant special privileges when a certain binary is run (much like the setuid/setgid bits associated with a file).
A process has three bitmasks of capabilities:
Permitted set The set of capabilities the process can enable. Effective set The set of capabilities that has been enabled already (the set that's consulted when performing a privileged operation). Inheritable set The set of capabilities that a new process can inherit when the current process creates one.
Although the effective set ultimately governs what a process can do, the other two sets are equally important. To see why, imagine that the ping program has only the CAP_NET_RAW capability in its effective set, but its permitted set includes a series of other random capabilities, such as CAP_SYS_MODULE. In this case, if users did compromise the ping program, they could enable the CAP_SYS_MODULE capability (thus adding it to the effective set) by using the sys_capset() system call and load kernel modules as a result.
File capabilities are similar, except they're associated with a file. A file has three capability sets also, but these sets differ slightly:
Allowed set The set of capabilities that are allowed to be added to the process capability sets when the executable runs. (Capabilities apply only to executables.) Forced set A set of capabilities that are granted in addition to those users might already have. This set allows a certain application to be given special privileges whenever it runs (like setuid/setgid bits on a file, but more refined). Effective set This set isn't really a set, but a bit indicating whether capabilities in the permitted set should automatically be transferred to the effective set when a new process image is loaded.
In early incarnations of the Linux capabilities solution (Linux kernel 2.2.15), Wojciech Purczynski discovered an interesting logic error. Specifically, users were able to restrict their privileges to their eventual advantage. By removing the CAP_SETUID privilege from the inheritable and permitted privilege sets and then running a setuid root application, the application would run with root privileges but wasn't permitted to drop privileges if necessary. Therefore, a call to setuid(getuid()) would fail, and the application would continue to run in an elevated privilege context. An exploit was constructed that targeted Sendmail 8.10.1. You can read more details about this vulnerability at www.securityfocus.com/bid/1322/discuss.
|
|