By John Viega, Matt Messier
Book Price: $69.99 USD
£35.50 GBP
PDF Price: $55.99
Cover | Table of Contents | Colophon
CreateProcessAsUser(
)
API to create a new process with a restricted
token that cannot be reverted to a more permissive token.socketpair( ), which will create two
endpoints of a connected unnamed socket. Fork the process using
fork( )
, drop the extra privileges in the child
process, and keep them in the parent process. Establish communication
between the parent and child processes.
Whenever the child process needs to perform an operation that
requires the extra privileges held by the parent process, defer the
operation to the parent.fork( ).stdin
,
stdout, and stderr file
descriptors are open, thus avoiding denial of service
attacks and avoiding having an attacker place untrusted files on
special hardcoded file descriptors.getdtablesize(
)
to obtain the size of the process's file descriptor
table. For each file descriptor in the process's
table, close the descriptors that are not stdin,
stdout, or stderr, which are
always 0, 1, and 2, respectively. Test stdin,
stdout, and stderr to ensure
that they're open using fstat(
)
for
each descriptor. If any one is not open, open
/dev/null and associate with the descriptor. If
the program is running setuid, stdin,
stdout, and stderr should also
be closed if they're not associated with a tty, and
reopened using /dev/null.stdin is not open, for example, the
first file opened is assigned a file descriptor of 0, which is
normally reserved for stdin. Similarly, if
stdout is not open, file descriptor 1 is assigned
next, followed by stderr's file
descriptor of 2 if it is not open.fork(
)
. When
fork( ) completes successfully, a nearly identical
copy of the calling process is created as a new process. Most
frequently, a new program is immediately executed using one of the
exec*( ) family of functions (see Recipe 1.7).
However, especially in the days before threading, it was common to
use fork( ) to create separate
"threads" of execution within a
program.
fork( )
is used to create a new process, the new
process is a nearly identical copy of the original process. The only
differences in the processes are the process ID, the parent process
ID, and the resource utilization counters, which are reset to zero in
the new process. Execution in both processes continues immediately
after the return from fork( ). Each process can
determine whether it is the parent or the child by checking the
return value from exec*( )
family of functions is used to
replace the current program within a process with another program.
Typically, when you're executing another program,
the original program continues to run while the new program is
executed, thus requiring two processes to achieve the desired effect.
The exec*( ) functions do not create a new
process. Instead, you must first use fork( ) to
create a new process, and then use one of the exec*(
) functions in the new process to run the new program. See
Recipe 1.6 for a discussion of using fork( )
securely.execve( )
is the system call used to load and begin
execution of a new program. The other functions in the
exec*( ) family are wrappers around the
execve( ) system call, and they are implemented in
user space in the standard C runtime library. When a new program is
loaded and executed with execve( ), the new
program replaces the old program within the same process. As part of
the process of loading the new program, the old
program's address space is replaced with a new
address space. File descriptors that are marked to close on execute
are closed; the new program inherits all others. All other
system-level properties are tied to the process, so the new program
inherits them from the old program. Such properties include the
process ID, user IDs, group IDs, working and root directories, and
signal mask.exec*(
) wrappers around the execve( ) system
call. Note that many of these wrappers should not be used in secure
code. In particular, never use the wrappers that are named with a
"p" suffix because they will search
the environment to locate the file to be executed. When executing
external programs, you should always specify the full path to the
file that you want to execute. If the PATH
environment variable is used to locate the file, the file that is
found to execute may not be the expected one.CreateProcess( )
API function to load and execute a new
program. Alternatively, use the CreateProcessAsUser(
)
API function to load and execute a new
program with a primary access token other than the one in use by the
current program.WinExec( )
. While this function still exists in the
Win32 API as a wrapper around CreateProcess( ) for
compatibility reasons, its use is deprecated, and new programs should
call CreateProcess( ) directly instead.ShellExecute(
)
. This function is implemented as a wrapper
around CreateProcess( ), and it does exactly what
we're about to advise against
doing with CreateProcess( )—but
we're getting a bit ahead of ourselves.ShellExecute( ) is so popular
is that virtually anything can be executed with the API. If the file
to execute as passed to ShellExecute( ) is not
actually executable, the API will search the registry looking for the
right application to launch the file. For example, if you pass it a
filename with a .TXT extension, the filename
will probably start Notepad with the specified file loaded. While
this can be an incredibly handy feature, it's also a
disaster waiting to happen. Users can configure their own file
associations, and there is no guarantee that you'll
get the expected behavior when you execute a program this way.
Another problem is that because users can configure their own file
associations, an attacker can do so as well, causing your program to
end up doing something completely unexpected and potentially
disastrous.setrlimit(
)
to
set the RLIMIT_CORE resource to zero, which will
prevent the operating system from leaving behind a core file. On
Windows, it is not possible to disable such behavior, but there is
equally no guarantee that a memory dump will be performed. A
system-wide setting that cannot be altered on a per-application basis
controls what action
Windows takes when an application
crashes.HKEY_LOCAL_MACHINE, and they require Administrator
access to change them. Even if you're reasonably
certain Dr. Watson will be the handler on systems on which your
program will be running, there is no way you can disable its
functionality on a per-application basis. On the other hand, any dump
that may be created by Dr. Watson is properly protected by ACLs that
prevent any other user from accessing them.access(
)
system
call to verify access to a file, and then to use open(
)
or
fopen( )
to open the file if the return from
access( ) indicates that access will be granted.access( ) completes and the time open(
) begins (both system calls are atomic within the operating
system kernel), there is a window of vulnerability where an attacker
can replace the file that is being operated upon.
Let's say that a program uses access(
) to check to see whether an attacker has write permissions
to a particular file, as shown in Figure 2-1. If
that file is a symbolic link, access( ) will
follow it, and report that the attacker does indeed have write
permissions for the underlying file. If the attacker can change the
symbolic link after the check occurs, but before the program starts
using the file, pointing it to a file he couldn't
otherwise access, the privileged program will end up opening a file
that it shouldn't, as shown in Figure 2-2. The problem is that the program can
manipulate either file, and it gets tricked into opening one on
behalf of the user that it shouldn't have.fstat(
)
function instead of the stat(
)
function. Both functions return the same information, but
fstat( ) uses an open file descriptor while
stat( ) uses a filename. Doing so removes the
possibility of a race condition, because the file to which the file
descriptor points can never change unless you reopen the file
descriptor. When operating on just the filename, there is no
guarantee that the underlying file pointed to by the filename remains
the same after the call to stat( ).GetFileInformationByHandle(
)
instead of functions like
FindFirstFile( ) or FindFirstFileEx(
). As with fstat( ) versus
stat( ) on Unix (which are also available on
Windows if you're using the C runtime API), the
primary difference between these functions is that one uses a file
handle while the others use filenames. If the only information you
need is the size of the file, you can use GetFileSize(
)
instead of GetFileInformationByHandle( ).chmod(
) and
fchmod( ) are not modified by umask
settings.requested_permissions contained the permissions
passed to the operating system to create a new file, the variable
actual_permissions would be the actual permissions
that the operating system would use to create the file.requested_permissions = 0666; actual_permissions = requested_permissions & ~umask( );
open( ) system call to create a new
file, you can force more restrictive permissions to be used than what
the user's umask might allow, but the only way to
create a file with less restrictive permissions is either to modify
the umask before creating the file or to use spc_lock_file(
)
, requires a single argument: the name of the
file to be used as the lock file. You must store the lock file in a
"safe" directory (see Recipe 2.4)
on a local filesystem. Network filesystems—versions of NFS
older than Version 3 in particular—may not necessarily support
the CreateMutex(
)
function. You will find it particularly useful in this recipe that
the mutex is created and a handle returned, or, if the mutex already
exists, a handle to the existing mutex is returned.mkstemp(
)
function in the standard C runtime library. This function generates a
random filename, attempts to create it, and
repeats the whole process until it is successful, thus guaranteeing
that a unique file is created. The file created by mkstemp(
) will be readable and writable by the owner, but not by
anyone else.chroot(
)
that
will restrict the process's access to the
filesystem. Specifically, chroot( ) alters a
process's perception of the filesystem by changing
its root directory, which effectively prevents the process from
accessing any part of the filesystem above the new root directory.chroot( )
system call, a process can alter its view of the filesystem by
changing its root directory to another directory within the
filesystem. Once the process's root directory has
been changed once, it can only be made more restrictive. It is not
possible to change the process's root directory to
another directory outside of its current view of the filesystem.chroot( ) is a simple way to increase
security for processes that do not require access to the filesystem
outside of a directory or hierarchy of directories containing its
data files. If an attacker is somehow able to compromise the program
and gain access to the filesystem, the potential for damage (whether
it is reading sensitive data or destroying data) is localized to the
restricted directory hierarchy imposed by altering the
process's root directory.chroot( ). The first time that chroot(
) is called, it does not necessarily alter the
process's current directory, which means that until
the current directory is forcibly changed, it may still be possible
to access areas of the filesystem outside the new root directory
structure. It is therefore imperative that the process calling
chroot( ) immediately change its current directory
to a directory within the new root directory structure. This is
easily accomplished as follows:chroot( ) can do. (See
Recipe 2.12.)jail(
)
, which
will "imprison" a process and its
descendants. It does all that chroot( ) does and
more.jail( ) system
call. (Discounting comments and blank lines, the code is a mere 35
lines.) However, it is possible to use the jail( )
system call in your own programs.chroot(
)
does, and then some. It restricts
much of the superuser's normal abilities, and it
restricts the IP address that programs running inside the jail may
use.jail( ). The
same caveats that apply to chroot( ) also apply to
jail( ) because jail( ) calls
chroot( ) internally. In particular, only the
superuser may create a jail successfully.version, path,
hostname, and ip_number. The
version field must be set to 0, and the
path field is treated the