OpenSolaris' security potpourri

Todays release of OpenSolaris will hopefully invite many of you out there to contribute your own code or have it available on OpenSolaris as well as on the other platforms you live on. To help you do so securely, I'll mention a potpourri of OpenSolaris features that we think help to make your code more secure.

Introduction of O_NOFOLLOW and O_NOLINKS

In Solaris 10, and thus now in OpenSolaris, we introduced two new flags to open(2) with the following description:

O_NOFOLLOW If the path names a symbolic link, open() fails and sets errno to ELOOP.
O_NOLINKS If the link count of the named file is greater than 1, open() fails and sets errno to EMLINK.

While O_NOFOLLOW certainly isn't new to UNIX, the addition of O_NOLINKS is. With these two flags, constructs like the open/stat-dance we used previously should become something of the past. In pseudo-code we did something like

	if (lstat(fname, &lsb) == -1) {
		fd = open(fname, O_CREAT|O_EXCL|O_WRONLY, 0600);
		if (fd == -1 && errno == EEXIST) {
			/\* start afresh \*/
	} else if (S_ISLNK(lsb.st_mode)) {
		/\* fail, symbolic link \*/
	} else {
		if ((fd = open(fname, O_WRONLY)) != -1) {
			if (fstat(fd, &fsb) != -1) {
				if (fsb.st_nlink > 1)
					/\* fail, too many links \*/
				else if (fsb.st_dev != lsb.st_dev ||
			 	    fsb.st_ino != lsb.st_ino) {
					/\* fail, file changed after lstat \*/
		/\* success \*/

Now, we can do

	fd = open(fname, O_CREAT|O_NOFOLLOW|O_NOLINKS, 0600);

Of course, if you want to make sure that you don't open any special files (devices), you'll still have to lstat()/fstat() and check for S_ISREG(). I hoped to introduce O_REGULAR too, but that didn't make it (yet? :-).

Non-executable stacks

Not new to Solaris, but maybe new to you, and certainly worth pointing out: large parts of Solaris, at least the commands in ON (the Operating System and Networking part of Solaris) are built with the stack-segment mapped as non-executable memory. Of course, this will not stop people from exploiting stack-overflows, but it'll surely make it harder to do.

Check out the definition of NES_MAPFILE in usr/src/cmd/Makefile.cmd. It defines an option passed to the linker (through the C-compiler front-end) that tells it to use a mapfile (mapfile_noexstk) that contains

stack = STACK ?RW;

This line is a segment declaration that tells the runtime linker to create the applications stack segment with the Read and Write flags enabled, but the eXecute flag unset. You can check which flags for the stack segment of a particular binary are set by running elfdump(1) on the binary. Search for an ELF section of type PT_SUNWSTACK, as in

$ elfdump /bin/ls 

ELF Header
  ei_magic:   { 0x7f, E, L, F }
  ei_class:   ELFCLASS32          ei_data:      ELFDATA2MSB
  e_machine:  EM_SPARC            e_version:    EV_CURRENT
  e_type:     ET_EXEC
  e_flags:                     0
  e_entry:               0x10e48  e_ehsize:     52  e_shstrndx:   21
  e_shoff:                0x6718  e_shentsize:  40  e_shnum:      23
  e_phoff:                  0x34  e_phentsize:  32  e_phnum:       6

Program Header[0]:
    p_vaddr:      0x10034         p_flags:    [ PF_X  PF_R ]
    p_paddr:      0               p_type:     [ PT_PHDR ]
    p_filesz:     0xc0            p_memsz:    0xc0
    p_offset:     0x34            p_align:    0
Program Header[5]:
    p_vaddr:      0               p_flags:    [ PF_W  PF_R ]
    p_paddr:      0               p_type:     [ PT_SUNWSTACK ]
    p_filesz:     0               p_memsz:    0
    p_offset:     0               p_align:    0

As shown here, the stack segment of /bin/ls is mapped with the PF_W and PF_R, and not with PF_X. Some example mapfiles can be found in /usr/lib/ld, like /usr/lib/ld/map.noexdata, which shows how to map data-segments without the executable bit set (for use on x86 machines). More info on mapfiles (and the linker in general) can be found in the Software Developer Collection on

lint security checking

If you've looked at the Makefiles in OpenSolaris, you may have encountered the -errsecurity flag that is passed on to lint. Specifying this flag makes lint perform several static checks on your code that helps keeping you (me?) alert when coding. It won't of course fix any of the logic bugs that are present, but it'll alert you when you use one of the known insecure functions that are part of any POSIX-like UNIX.

There are three levels of complaining that lint will perform for you, core, standard and extended. By default, the Solaris Makefiles set this to "core" which include these checks

  • Use of variable format strings with the printf() and scanf() family of functions
  • Use of unbounded string (%s) formats in scanf() functions
  • Use of functions with no safe usage: gets(), cftime(), ascftime(), creat()
  • Incorrect use of open() with O_CREAT

New code to Solaris should not introduce any new lint-warnings on the core level, but I'd suggest you run it with standard or extended on any code you change or introduce. For more info on this option, see section 5.3.13 of the lint Source Code Checker.

Least privilege

Glenn Brunette already showed an example of how to convert an existing setuid application (ping) into a privilege-aware application. There is more information on how to develop privilege aware applications in the Solaris Security for Developers Guide, but I thought I'd just show a little bit of one of the privilege-aware daemons, the NFS daemon nfsd.

Right after starting up, the NFS daemon uses its initial privileges to create a lockfile in a root-owned directory (_create_daemon_lock). It needs all privileges to do so, since it modifies an object owned by "root".

All that it needs from now on, is the privilege to bind to a reserved port (the nfs-port); nothing more, nothing less. Thus the call to __init_daemon_priv which is a convenience routine around a number of calls to setppriv() and setreuid() and setgid().

Once this routine returns, the daemon runs with just the privilege of a normal process (>tt>basic) augmented with the privilege to bind to the NFS port (sys_nfs). No more special "root" like privileges from this point on. In this way, we can perform the argument parsing and other initialization code using minimized privileges that limit the impact of a possible bug in the start-up code.

After the daemon has registered itself for the various protocols it is supposed to support (calls to do_one and do_one), it relinquishes even more privileges. For example, It gives up its right to fork(2) and exec(2) other binaries. For this it uses another convenience rapper-function called __fini_daemon_priv() which removes the listed privileges from the set of permitted privileges (which also removes them from the effective set).

When this routine completes, the nfs daemon runs with the following set of privileges:

# ppriv -v `pgrep nfsd`
5239:   /export/ws/work/usr/src/cmd/fs.d/nfs/nfsd/nfsd
flags = PRIV_AWARE
        E: sys_nfs
        I: none
        P: sys_nfs
        L: none

So, IF someone would be able exploit, e.g., a stack overflow bug in nfsd, it the exploit code would not only run as (daemon, daemon) without any basic privilege, it would also need to run in the address space of the daemon itself since it can't fork() nor exec().

Makes for a pretty useless target to attack, not?

Technorati Tag:
Technorati Tag:

Post a Comment:
  • HTML Syntax: NOT allowed



« April 2014