safe_open(3)safe_open(3)NAMEsafe_open - Open a file for I/O with comprehensive or customized secu‐
rity checks
SYNOPSIS
#include <safe_open.h>
int safe_open(
char const *pathname,
int oflags,
ulong_t sflags,
... );
LIBRARY
Standard C Library (libc)
PARAMETERS
Identifies the file or other file system object being opened. Includes
the flags (such as O_RDONLY, O_CREAT, or O_APPEND) that one would spec‐
ify to an open() call. The flags actually passed by safe_open() to
open() may vary from these; see DESCRIPTION for details. Includes
flags that can relax 20 of the restrictions that safe_open() enforces
by default. You can specify one or more of the following flags, each of
which tells safe_open() not to enforce a particular restriction: Accept
the risk of blocking for off-line modems or named pipes (fifos). This
flag is often appropriate when intending to write to a named pipe,
since a non-blocking open of a named pipe for writing fails when there
is no reader. Allow opening file system objects under /dev/fd (or any
other file system mounted as type fdfs). Allow opening file system
objects that are the targets of file-on-file mounts. Allow opening
file system objects under /proc (or any other file system mounted as
type procfs). Allow opening files on non-local file systems. Not set‐
ting this flag excludes (at least) NFS and DCE file systems. For sup‐
port of dataless client configurations, the /, /usr, /usr/var, and /var
file systems are always accepted, even when this flag is not specified.
Local file systems are currently defined as MFS, CFS, and any
others that set the M_LOCAL bit in the m_flags field of their
mount structures. (At the time this reference page was written,
the full list of local file systems included AdvFS, CDFS, CFS,
DVDFS, MFS, and UFS.) Allow relative paths in the pathname
parameter. When creating a new file, do not check for a possi‐
bly-inappropriate default access control list (ACL) on the new
file. If this flag is omitted, newly-created files are checked
to see whether they have inherited a default ACL from the con‐
taining directory. If so, and if the owner of that directory is
neither UID 0 nor the effective UID of the process, an attempt
is made to delete the ACL from the file. If any other readers or
writers of the file are detected after removing the ACL, the
file is closed and safe_open() fails, setting errno to [EMLINK].
When resolving the pathname parameter, accept symbolic links
that have the same UID as the directory in which they are found.
If this flag is omitted, only symbolic links owned by the effec‐
tive UID of the process or by UID 0 will be accepted. Do not
check for other links to the same file. If this flag is omitted,
named pipes and regular files with link counts greater than one
will be rejected, causing safe_open() to fail with errno set to
[EMLINK]. Limit “writable directory” checks to making sure that
directories do not have world write permission. If this flag is
omitted, safe_open() makes sure that directories have neither
world write nor group write permission. Limit “writable direc‐
tory” checking to the immediate parent directory of pathname
(and of any symbolic links traversed to the final symbolic link
target, when relevant). If this flag is omitted, safe_open()
will also traverse upward from the immediate parent directory,
checking each directory until reaching (and testing) the root
directory, as well as checking each directory traversed to
resolve any symbolic links. Skip “writable directory” checks
for the starting directory of the pathname parameter. See
RESTRICTIONS for a limited number of cases when this flag is
ignored. Skip “writable directory” checks for directories with
the “sticky bit” (S_ISVTX) set in their mode bits. Skip the
checks for symbolic link ownership. Allow block-special files
to be opened. Allow character-special files to be opened.
Allow directories to be opened for reading. Allow named pipes
(fifos) to be opened. Allow symbolic links to be followed if
all other constraints are met. Even with this flag set, the
object type of the symbolic link's target must be acceptable. On
systems where the open() call supports the O_NOFOLLOW flag,
including O_NOFOLLOW in oflags overrides the inclusion of
OPN_TYPE_SYMLINK in sflags. Allow files not owned by the effec‐
tive UID to be opened. Reserved for future expansion.
DESCRIPTION
The safe_open() function attempts to open the file system object iden‐
tified by its pathname parameter and makes validity checks, as con‐
trolled by the oflags and sflags parameters. The validity checking that
this function performs helps to protect against some common security
vulnerabilities, such as “time of check, time of use” (TOCTOU) race
conditions, that can be created when programs manipulate arbitrary file
system objects.
If successful, safe_open() returns a file descriptor. On failure, the
function returns -1 and sets errno to indicate the error.
For many applications that want to protect against security vulnerabil‐
ities when opening files, the safe_open() function will be a drop-in
replacement for an open() call, after selecting a suitable set of val‐
ues for the sflags parameter. However, the oflags parameter is not
necessarily passed to the open() call or passed exactly as specified in
order to protect against security vulnerabilities. The safe_open()
function differs from the open() function with respect to oflags values
in the following ways: The safe_open() function never implements
O_CREAT without an implicit O_EXCL.
When called with only O_CREAT in its oflags parameter,
safe_open() always passes 0600 to open() as a mode parameter.
Therefore, applications that need to use an oflags parameter
that specifies O_CREAT without O_EXCL will need to add some
retry logic in addition to the safe_open() call. (See EXAMPLES.)
If the pathname parameter is a symbolic link, safe_open() will
never create a file, even when O_CREAT is specified. The
safe_open() function never passes O_TRUNC to open().
When called with O_TRUNC in its oflags parameter, safe_open()
simulates the O_TRUNC operation by using a call to ftruncate().
The safe_open() function always adds both O_NONBLOCK and O_NDE‐
LAY to any open() calls (even if O_CREAT is specified in oflags)
unless OPN_BLOCKING is included in sflags.
This function makes the following checks before opening the specified
pathname parameter whether or not a new or existing file system object
is being opened. There is no guaranteed order in which these checks are
made. If a file system or the target object fails any of the checks,
the function will return failure (unless otherwise noted). Because of
the potential for race conditions, some of the checks may be made more
than once. Validate the type of the target file system, depending on
the OPN_FSTYPE_* flags that are provided for sflags. If no OPN_FSTYPE_*
flags are set, only local file systems (as defined in the description
of the OPN_FSTYPE_REMOTE flag) are permitted and they cannot be one of
the “pseudo” local file systems (FDFS, FFM, or PROCFS). Validate the
type of the file system object, depending on the OPN_TYPE_* flags that
are provided for sflags. If no OPN_TYPE_* flags are set, only a regular
file may be opened. Confirm that the target file does not exist if
O_CREAT and O_EXCL are included in oflags Validate that no component of
the resolved pathname parameter is in an untrustworthy (world- or
group-writable) directory, depending on the OPN_TRUST_* flags that are
provided for sflags. Regardless of whether OPN_TRUST_* flags are set,
safe_open() always subjects any symbolic link component found in path‐
name to the writable-directory check. Other directory components are
checked, depending on whether OPN_TRUST_PARENT_DIRS, OPN_TRUST_START‐
ING_DIRS, or both are set. Symbolic links are also subject to proper
ownership checking, as controlled by the OPN_TRUST_DIR_OWNERS and
OPN_TRUST_SYMLINK_OWNERS flags.
If no OPN_TRUST_* flags are present: All directories in the
absolute pathname of the file system object being opened will be
checked None of those directories may be group-writable or
world-writable All symbolic links found during pathname traver‐
sal must be owned by UID 0 or the effective UID of the calling
process If the pathname parameter identifies a symbolic link and
OPN_TYPE_SYMLINK is specified, the link's target must also pass
the object type validation, depending on any other OPN_TYPE_*
flags that are included.
To open the file system object, safe_open() may use the resolved path‐
name that was obtained during the per-component, access-checking tra‐
versal of pathname. (This is always done on file creation.) If
OPN_BLOCKING was not included in sflags, the open is always done as a
non-blocking open and fcntl() is called to clear the non-blocking bits.
If an existing file was opened, safe_open() makes additional checks as
follows: Verify that pathname was successfully opened and that the
opened file system object is what was expected based on the earlier
checks on the type or types of file system objects that are allowed to
be opened. Check to make sure that the call to fcntl() succeeded if
OPN_BLOCKING was not specified in sflags, and O_NONBLOCK and O_NDELAY
were not both specified in oflags. Verify that the call to ftruncate()
succeeded if O_TRUNC was specified in oflags. (This step is skipped
for named pipes and objects opened through file systems of type FDFS.)
Confirm that the object being opened does not have a link count greater
than one if OPN_TRUST_NLINKS was not specified in sflags, and the file
system object being opened is a named pipe or a regular file.
For a newly created file, assuming OPN_TRUST_DEFAULT_ACLS was not spec‐
ified in sflags, safe_open() makes sure that at least one of the fol‐
lowing additional conditions applies: The file was created in a direc‐
tory owned by UID 0 or the effective UID of the calling process The
file did not inherit a default ACL from its directory
For more information, see the “ACL Inheritance” section in
acl(4). Deleting the default ACL on the new file and resetting
its mode to 0600 must succeed and, after doing so, there must be
no other processes accessing the file.
On failure of any check after a file descriptor is obtained,
safe_open() closes the file descriptor before returning failure status.
NOTES
HP reserves the right to revise the safe_open() implementation to
address additional security vulnerabilities in the future. It is possi‐
ble that these future additional checks might cause application code
that is designed to use the current implementation and that has a pre‐
viously-undetected vulnerability to fail under circumstances that exer‐
cise that vulnerability. This is appropriate for privileged programs
that must maintain the highest level of safe operation. For the benefit
of applications with looser security requirements, the validity check‐
ing on the sflags mask only rejects bits in the range reserved for
future expansion of the argument list. The start of that reserved range
of bit numbers is identified by the OPN_first_reserved symbol. Those
bits that are currently unassigned, but which may be assigned in the
future to disable new checks, are accepted by the current implementa‐
tion. The start of the unassigned range of bit numbers is identified by
the OPN_num_flags symbol. The full mask of acceptable unassigned bits
can thus be obtained by the following expression:
((1UL<<OPN_first_reserved) - (1UL<<OPN_num_flags))
RESTRICTIONS
Even if OPN_TRUST_STARTING_DIRS is included in sflags, it is possible
that the starting directory of the pathname parameter will be subject
to “writable directory” testing. This happens if the pathname resolu‐
tion process encounters a sequence like subdir/../file. This behavior
is considered a restriction rather than a bug due to the possibility of
races with other processes in the handling of pathnames and directory
modes.
For similar reasons, the target of a symbolic link is always subject to
the “writable directory” checks, even when it is the same as the start‐
ing directory.
RETURN VALUES
Success. The returned value is the file descriptor of the file system
object that was opened. Failure. In this case, errno is set to indi‐
cate the condition that caused the failure.
ERRORS
Except for a few cases, error conditions documented in this reference
page are specific to the safe_open() function. However, the safe_open()
function can also set errno in response to failure conditions returned
by any of the following calls: acl_get_fd(), acl_set_fd(), fcntl(),
fstat(), ftruncate(), fuser(), lstat(), open(), readlink(), realpath(),
stat(), and statfs(). Refer to SEE ALSO find the reference pages for
these functions. The oflags parameter had O_TRUNC set, but the access
portion of that parameter was O_RDONLY. (This error is propagated from
an ftruncate() call.) The type of the file system object was rejected
based on the OPN_TYPE_* values omitted from sflags, or the type of file
system type in which the object was to be opened was rejected, based on
the OPN_FSTYPE_* values omitted from sflags. The pathname parameter
was NULL or did not start with a / character and the sflags parameter
did not include OPN_RELATIVE, or the sflags parameter included bits
that are reserved for future argument expansion. One of the following
conditions was encountered: OPN_TRUST_NLINKS flag was not included in
sflags, an existing file system object was found that was either a
named pipe or a regular file, and its link count was greater than one.
OPN_TRUST_DEFAULT_ACLS was not included in sflags, a newly-created file
was found to have inherited a default access control list from a direc‐
tory owned by an inappropriate UID, and other processes were found to
be accessing the new file after resetting its ACL. One of the follow‐
ing conditions was encountered: The pathname parameter was an empty
string. The pathname parameter had at least one trailing / character.
If the intent was to specify a directory, it should be done by append‐
ing a dot (.) character. O_CREAT was specified without O_EXCL in
oflags and a file system object associated with the pathname parameter
existed at the start of the call but no longer existed when an attempt
was made to open the existing file. The safe_open() routine will have
retried this sequence of operations at least once before returning this
error. One of the following conditions was encountered: Some directory
permissions found were overly permissive (given the constraints not
relaxed by the OPN_TRUST_* flags). An attempt was made to create a new
file by traversing a symbolic link. One of the following conditions
was encountered: OPN_UNOWNED was not included in sflags, and the file
found was not owned by the effective UID of the process.
OPN_TRUST_SYMLINK_OWNERS was not included in sflags, and a symbolic
link was found that failed the symbolic link ownership checks during
resolution of pathname. Either some check on the pathname parameter or
on symbolic links that needed to be resolved for that parameter
returned inconsistent results, or the file originally tested (by means
of the pathname parameter) did not match the file object returned by an
internal call to open().
EXAMPLES
The following replacement example shows how file opening might be
updated for an application that intends to display a notice file to a
user. This example assumes that the application is not in complete
control of the pathname to be displayed, but that it is running with
the user's own UID, and that the application displays a regular file
(however it was found).
Original code:
int fd = open(pathname, O_RDONLY);
Replacement code:
const ulong_t soflags = (OPN_UNOWNED | OPN_TRUST_STICKY_BIT |
OPN_TRUST_NLINKS | OPN_TYPE_SYMLINK |
OPN_RELATIVE | OPN_FSTYPE_REMOTE); int
fd = safe_open(pathname, O_RDONLY, soflags); The following
replacement example shows how adding data to a user-specified
pathname can be done more safely. This example assumes that the
application has no control over the user-provided pathname, but
that it is running with the user's credentials. It further
assumes that the application is running as a daemon, so the
practice of accepting relative pathnames is inadvisable.
Finally, it assumes that the expected location for the user's
file is in either /tmp or /var/tmp, so the use of the
OPN_TRUST_STICKY_BIT is appropriate.
Original code:
int fd = open(pathname, O_CREAT|O_APPEND|O_WRONLY, 0644);
Replacement code:
The replacement code is complicated slightly by the need to
retry file creation attempts and to reset the file's mode if it
was newly created.
const ulong_t soflags = (OPN_TRUST_STICKY_BIT); int loop_limit =
3, fd; do {
fd = safe_open(pathname, O_CREAT|O_APPEND|O_WRONLY,soflags);
} while (-1 == fd && ENOENT == errno && --loop_limit); if (fd >=
0)
(void) fchmod(fd, 0644);
(The retry for [ENOENT] errors is only needed for robustness
because safe_open() does its own retry in this case.) The fol‐
lowing replacement example shows how an operation similar to the
>> redirection of the shells can be made safer against inadver‐
tent file substitution, while still allowing the user to write
to a hardcopy terminal, a magnetic tape, a named pipe, a file in
/tmp, or a file in the user's (possibly NFS-mounted) home direc‐
tory.
Original code:
int fd = open(pathname, O_CREAT|O_EXCL|O_WRONLY, 0666); if (-1
== fd && EEXIST == errno)
fd = open(pathname, O_APPEND|O_WRONLY);
Replacement code:
The replacement code uses the process umask setting to (possi‐
bly) open up the permissions on a newly-created file beyond the
0600 mode always applied by the safe_open() routine. This code
also deliberately excludes block-special devices because they
are not suitable for text output.
const ulong_t soflags_append = (OPN_TRUST_STICKY_BIT |
OPN_UNOWNED |
OPN_TRUST_NLINKS | OPN_RELATIVE
|
OPN_FSTYPE_REMOTE | OPN_TYPE_CHR
|
OPN_TYPE_FIFO | OPN_TYPE_SYMLINK
|
OPN_BLOCKING); const ulong_t
soflags_create = (OPN_TRUST_STICKY_BIT | OPN_RELATIVE |
OPN_FSTYPE_REMOTE | OPN_BLOCKING
|
OPN_TRUST_DEFAULT_ACLS); int fd
= safe_open(pathname, O_CREAT|O_EXCL|O_WRONLY, soflags_create);
if (fd >= 0) {
mode_t cmask = umask(077); /* Get prevailing umask. */
(void) umask(cmask); /* Restore it. */
(void) fchmod(fd, 0666 & ~cmask); /* Apply it. */ } else
fd = safe_open(pathname, O_APPEND|O_WRONLY, soflags_append);
(It should be noted that this method of obtaining the process
umask value, while simple, is not thread safe. A multi-threaded
application should use the TBL_UAREA function of the table()
system call, and examine the u_cmask structure member to obtain
the umask value.)
SEE ALSO
Functions: fcntl(2), ftruncate(2), fuser(2), open(2), readlink(2),
stat(2), statfs(2), table(2), umask(2), acl_get_fd(3), acl_set_fd(3),
fdopen(3), realpath(3)
Files: acl(4), fd(4), ffm(4), proc(4)safe_open(3)