safe_open - Open a file for I/O with comprehensive or customized
security checks
#include <safe_open.h>
int safe_open(
char const *pathname,
int oflags,
ulong_t sflags,
... );
Standard C Library (libc)
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 specify 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 setting this flag excludes (at
least) NFS and DCE file systems. For support 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 possibly-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 containing 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 effective 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 directory" 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 characterspecial
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 effective UID to be opened. Reserved for
future expansion.
The safe_open() function attempts to open the file system
object identified by its pathname parameter and makes
validity checks, as controlled 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
vulnerabilities when opening files, the safe_open()
function will be a drop-in replacement for an open() call,
after selecting a suitable set of values 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_NDELAY 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 groupwritable)
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 pathname to the writabledirectory
check. Other directory components are checked,
depending on whether OPN_TRUST_PARENT_DIRS,
OPN_TRUST_STARTING_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 traversal
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 pathname that was obtained during the per-component,
access-checking traversal of pathname. (This is
always done on file creation.) If OPN_BLOCKING was not
included in sflags, the open is always done as a nonblocking
open and fcntl() is called to clear the nonblocking
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 specified in sflags, safe_open() makes sure that
at least one of the following additional conditions
applies: The file was created in a directory 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.
HP reserves the right to revise the safe_open() implementation
to address additional security vulnerabilities in
the future. It is possible that these future additional
checks might cause application code that is designed to
use the current implementation and that has a previouslyundetected
vulnerability to fail under circumstances that
exercise 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 checking 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 implementation.
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))
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 resolution 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 starting directory.
Success. The returned value is the file descriptor of the
file system object that was opened. Failure. In this
case, errno is set to indicate the condition that caused
the failure.
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 directory owned by an inappropriate
UID, and other processes were found to be accessing
the new file after resetting its ACL. One of the following
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 appending 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().
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 userprovided
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 following replacement example
shows how an operation similar to the >> redirection
of the shells can be made safer against inadvertent
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 directory.
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 (possibly) open up the permissions on a newlycreated
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.)
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)
[ Back ] |