This
chapter presents specific techniques for designing trusted programs.
16.1 Writing SUID and SGID Programs
SUID (set user ID) and SGID (set group ID) programs change the effective UID or GID of a process to the UID or GID of the program. They are a solution to the problem of providing controlled access to system-level files and directories, because they give a process the access rights of the files' owner.
The potential for security abuse is higher for programs in which the
user ID is set to
root
or the group ID is set to any group
that provides write access to system-level files.
Do not write a program that
sets the user ID to
root
unless there is no other way to
accomplish the task.
The
chown
system call
automatically removes any SUID or SGID bits on a file, unless the RUID of
the executing process is set to zero.
This prevents the accidental creation
of SUID or SGID programs owned by the
root
account.
For
more information, see
chown
(2).
The following list provides suggestions for creating more secure SUID and SGID programs:
Verify all user-provided pathnames with the
access
system call.
Trap all relevant signals to prevent core dumps.
Test for all error conditions, such as system call return values and buffer overflow.
When possible, create SGID programs rather than SUID programs. One reason is that file access is generally more restrictive for a group than for a user. If your SGID program is compromised, the restrictive file access reduces the range of actions available to the attacker.
Another reason is that it is easier to access files owned by the user
executing the SGID program.
When a user executes an SUID program, the original
effective UID is no longer available for use for file access.
However, when
a user executes an SGID program, the user's primary GID is still available
as part of the group access list.
Therefore, the SGID process still has group
access to the files that the user could access.
16.2 Handling Errors
Most system calls and library routines return an integer
return code, which indicates the success or failure of the call.
Always check
the return code to make sure that a routine succeeded.
If the call fails,
test the global variable
errno
to find out why it failed.
The
errno
variable is set when an error occurs in a system call.
You
can use this value to obtain a more detailed description of the error condition.
This information can help the program decide how to respond, or produce a
more helpful diagnostic message.
This error code corresponds to an error name
in
<errno.h>
.
For more information, see
errno
(2).
The following
errno
values indicate a possible security
breach:
Indicates an attempt by someone other than the owner to modify a file in a way reserved to the file owner or superuser. It can also mean that a user attempted to do something that is reserved for a superuser.
Indicates an attempt to access a file for which the user does not have permission.
Indicates an attempt to access a file on a mounted file system when that permission has been revoked.
If your program makes a privileged system call but the resulting executable program does not have superuser privilege, it will fail when it tries to execute the privileged system call. If the security administrator has set up the audit system to log failed attempts to execute privileged system calls, the failure will be audited.
If your program detects a possible security
breach, do not have it display a diagnostic message that could help an attacker
defeat the program.
For instance, do not display a message that indicates
the program is about to exit because the attacker's real user ID (UID) did
not match a UID in an access file, or even worse, provide the name of the
access file.
Restrict this information by using the
audgen()
routine for SUID root programs and using
syslog
for other
programs.
In addition, you could program a small delay before issuing a message
to prevent programmed attempts to penetrate your program by systematically
trying various inputs.
16.3 Protecting Permanent and Temporary Files
If your program uses any permanent files (for example, a database), make sure these files have restrictive permissions and that your program provides controlled access. These precautions also apply to shared memory segments, semaphores, and interprocess communication mechanisms; set restrictive permissions on all of these objects.
Programs sometimes create temporary files to store data while the program is running. Follow these precautions when you use temporary files:
Be sure your program deletes temporary files before it exits.
Avoid storing sensitive information in temporary files, unless the information has been encrypted.
Give only the owner of the temporary file read and write permission.
Set the file creation mask to 077 by using the
umask()
system call at the beginning of the program.
Create temporary files in private
directories that are writable only by the owner or in
/tmp
.
The
/tmp
, directory has the sticky bit set (mode 1777),
so that files in it can be deleted only by the file owner, the owner of the
directory, or the superuser.
A common practice is to create a temporary file, then unlink the file while it is still open. This limits access to any processes that had the file open before the unlink; when the processes exit, the inode is released.
Note that this use of
unlink
on an NFS-mounted file
system takes a slightly different action.
The client kernel renames the file
and the unlink is sent to NFS only when the process exits.
You cannot guarantee
that the file will be inaccessible to someone else, but you can be reasonably
sure that the file will be inaccessible when the process exits.
In any case,
always explicitly ensure that no temporary files remain after the process
exits.
16.4 Specifying a Secure Search Path
If you use the
popen
,
system
, or
exec*p
routines, which execute
/bin/sh
or
/sbin/sh
, be careful when specifying a pathname or defining the
shell
PATH
variable.
The
PATH
variable is a security-sensitive variable because it specifies
the search path for executing commands and scripts on your system.
For more
information, see
environ
(7),
popen
(3), and
system
(3).
The following list describes how to create a secure search path:
Specify absolute pathnames for the
PATH
variable.
Do not include public or temporary directories, other users' directories, or the current working directory in your search path. Including these directories increases the possibility of inadvertently executing the wrong program or of being trapped by a malicious program.
Be sure that system directories appear before user directories in the list. This prevents you from mistakenly executing a program that might have the same name as a system program.
Analyze your path-list syntax, especially your use of nulls, decimal points, and colons. A null entry or decimal point entry in a path list specifies the current working directory and a colon is used to separate entries in the path list. For this reason, the first entry following an equal sign should never begin with a colon.
If a path list ends with a colon, certain shells and
exec*p
routines search the current working directory last.
To avoid
having various shells interpret this trailing colon in different ways, use
the decimal point rather than a null entry to reference the current working
directory.
You might want to use the
execve
system call rather
than any of the
exec*p
routines because
execve
requires that you specify the pathname.
For more information, see
execve
(2).
16.5 Responding to Signals
The Tru64 UNIX operating system generates signals in response to certain events. The event could be initiated by a user at a terminal (such as quit, interrupt, or stop), by a program error (such as a bus error), or by another program (such as kill).
By default, most signals terminate the receiving process; however, some signals only stop the receiving process. Many signals, such as SIGQUIT or SIGTRAP, write the core image to a file for debugging purposes. A core image file might contain sensitive information, such as passwords.
To protect sensitive information in core image files and protect programs from being interrupted by input from the keyboard, write programs that capture signals such as SIGQUIT, SIGTRAP, or SIGTSTP.
Use the
signal
routine to cause your process to change
its response to a signal.
This routine enables a process to ignore a signal
or call a subroutine when the signal is delivered.
(The SIGKILL and SIGSTOP
signals cannot be caught, ignored, or blocked.
They are always passed to the
receiving process.) For more information, see
signal
(3)
and
sigvec
(2).
Also,
be aware that child processes inherit the signal mask that the parent process
sets before calling
fork
.
The
execve
system call resets all caught signals to the default action; ignored signals
remain ignored.
Therefore,
be sure that processes handle signals appropriately before you call
fork
or
execve
.
For more information, see the
fork
(2)
and
execve
(2)
reference pages.
16.6 Using Open File Descriptors with Child Processes
A child process can inherit all the open file descriptors of its parent process and therefore can have the same type of access to files. This relationship creates a security concern.
For example, suppose you write a set user ID (SUID) program that does the following:
Allows users to write data to a sensitive, privileged file
Creates a child process that runs in a nonprivileged state
Because the parent SUID process opens a file for writing, the child (or any user running the child process) can write to that sensitive file.
To protect sensitive, privileged files from users of a child process,
close all file descriptors that are not needed by the child process before
the child is created.
An
efficient way to close file descriptors before creating a child process is
to use the
fcntl
system call.
You can use
this call to set the
close-on-exec
flag on the file after
you open it.
File descriptors that have this flag set are automatically closed
when the process starts a new program with the
exec
system
call.
For more information, see the
fcntl
(2)
reference page.
16.7 Security Concerns in X Environment
The following sections discuss several ways to increase security in the X programming environment:
Restrict access control
Protect keyboard input
Block keyboard and mouse events
Protect device-related events
Users logged into hosts listed in the access control list can
call the
XGrabKeyboard
function to take control of the
keyboard.
When a client has called this function, the X server directs all
keyboard events only to that client.
Using this call, an attacker could grab
the input stream from a window and direct it to another window.
The attacker
could return simulated keystrokes to the window to fool the user running the
window.
Thus, the user might not realize that anything was wrong.
The ability of an attacker to capture a user's keystrokes threatens the confidentiality of the data stored on the workstation.
X windows provide a secure keyboard mode that directs everything a user types at the workstation keyboard to a single, secure window. Users can set this mode by selecting the Secure Keyboard item from the Commands menu in a X window.
Include a secure keyboard mode in programs that deal with sensitive data. This precaution is especially important if your program prompts a user for a password.
Some guidelines for implementing secure keyboard mode follow:
Use the
XGrabKeyboard
call to the
Xlib
library.
Use a visual cue to let the user know that secure keyboard mode has been set, for example, reverse video on the screen.
Use the
XUngrabKeyboard
function to release
the keyboard grab when the user reduces the window to an icon.
Releasing
the keyboard frees the user to direct keystrokes to another window.
16.7.2 Block Keyboard and Mouse Events
Hosts listed
in the access control list can send events to any window if they know its
ID.
The
XSendEvent
call enables the calling application
to send keyboard or mouse events to the specified window.
An attacker could
use this call to send potentially destructive data to a window.
For example,
this data could execute the
rm -rf *
command or use a
text editor to change the contents of a sensitive file.
If the terminal was
idle, a user might not notice these commands being executed.
The ability of an attacker to send potentially destructive data to a workstation window threatens the integrity of the data stored on the workstation.
X
windows block keyboard and mouse events sent from another client if the
allowSendEvents
resource is set to
False
in the
.Xdefaults
file.
You can write programs that block events sent from other clients.
The
XSendEvent
call sends an event to the specified window and sets
the
send_event
flag in the event structure to
True
.
Test this flag for each keyboard and mouse event that your
program accepts.
If the flag is set to
False,
the event
was initiated by the keyboard and is safe to accept.
16.7.3 Protect Device-Related Events
Device-related events, such as keyboard and mouse events, propagate upward from the source window to ancestor windows until one of the following conditions is met:
An X client selects the event for a window by setting its event mask
An X client rejects the event by including that event in the
do-not-propagate
mask
You can use the
XReparentWindow
function to change the parent of a window.
This call changes a
window's parent to another window on the same screen.
All you need to know
to change a window's parent is the window ID.
With the window ID of the child,
you can discover the window ID of its parent.
The misuse of the
XReparentWindow
call can threaten
security in a windowing system.
The new parent window can select any event
that the child window does not select.
Take these precautions to protect against this type of abuse:
Have the child window select the device events that it needs. This precaution prevents the new parent from intercepting events that propagated upward from the child. Parent windows that centralize event handling for child windows are at greater security risk. An attacker can change the parent and intercept the events intended for the children. Therefore, it is safer for each child window to handle its own device events. Events that the child explicitly selects never propagate.
Have the child window specify that device events will not
propagate further in the window hierarchy by setting the
do-not-propagate
mask.
This precaution prevents any device event from propagating
to the parent window, regardless of whether the child requested the event.
Have the child window ask to be notified when its parent window
is changed by setting the
StructureNotify
or
SubstructureNotify
bit in the child window's event mask.
For information
on setting these event masks, see the
X Window System: The Complete Reference to Xlib, X Protocol, ICCCM, XLFD.
When you write a shell script that handles sensitive
data, set and export the
PATH
variable before writing the
body of the script.
Do not make shell scripts SUID or SGID.