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 docs.sun.com.
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:
OpenSolaris
Technorati Tag:
Solaris