/*
* libsandbox.c
*
* Main libsandbox related functions.
*
* Copyright 1999-2008 Gentoo Foundation
* Licensed under the GPL-2
*
* Partly Copyright (C) 1998-9 Pancrazio `Ezio' de Mauro
,
* as some of the InstallWatch code was used.
*/
/* Uncomment below to enable memory debugging. */
/* #define SB_MEM_DEBUG 1 */
#define open xxx_open
#define open64 xxx_open64
#include "headers.h"
#ifdef SB_MEM_DEBUG
# include
#endif
#undef open
#undef open64
#include "sbutil.h"
#include "libsandbox.h"
#include "wrappers.h"
#include "sb_nr.h"
#define LOG_VERSION "1.0"
#define LOG_STRING "VERSION " LOG_VERSION "\n"
#define LOG_FMT_FUNC "FORMAT: F - Function called\n"
#define LOG_FMT_ACCESS "FORMAT: S - Access Status\n"
#define LOG_FMT_PATH "FORMAT: P - Path as passed to function\n"
#define LOG_FMT_APATH "FORMAT: A - Absolute Path (not canonical)\n"
#define LOG_FMT_RPATH "FORMAT: R - Canonical Path\n"
#define LOG_FMT_CMDLINE "FORMAT: C - Command Line\n"
#define PROC_DIR "/proc"
#define PROC_SELF_FD PROC_DIR "/self/fd"
#define PROC_SELF_CMDLINE PROC_DIR "/self/cmdline"
char sandbox_lib[SB_PATH_MAX];
typedef struct {
int show_access_violation;
char **deny_prefixes;
int num_deny_prefixes;
char **read_prefixes;
int num_read_prefixes;
char **write_prefixes;
int num_write_prefixes;
char **predict_prefixes;
int num_predict_prefixes;
char **write_denied_prefixes;
int num_write_denied_prefixes;
} sbcontext_t;
static sbcontext_t sbcontext;
static char **cached_env_vars;
volatile int sandbox_on = 1;
static int sb_init = 0;
static int sb_path_size_warning = 0;
static char *resolve_path(const char *, int);
static int write_logfile(const char *, const char *, const char *,
const char *, const char *, bool);
static int check_prefixes(char **, int, const char *);
static void clean_env_entries(char ***, int *);
static void init_context(sbcontext_t *);
static void init_env_entries(char ***, int *, const char *, const char *, int);
/*
* Initialize the shabang
*/
static char log_domain[] = "libsandbox";
__attribute__((destructor))
void libsb_fini(void)
{
int x;
sb_init = 0;
if (NULL != cached_env_vars) {
for (x = 0; x < 4; ++x) {
if (NULL != cached_env_vars[x]) {
free(cached_env_vars[x]);
cached_env_vars[x] = NULL;
}
}
free(cached_env_vars);
cached_env_vars = NULL;
}
clean_env_entries(&(sbcontext.deny_prefixes),
&(sbcontext.num_deny_prefixes));
clean_env_entries(&(sbcontext.read_prefixes),
&(sbcontext.num_read_prefixes));
clean_env_entries(&(sbcontext.write_prefixes),
&(sbcontext.num_write_prefixes));
clean_env_entries(&(sbcontext.predict_prefixes),
&(sbcontext.num_predict_prefixes));
}
__attribute__((constructor))
void libsb_init(void)
{
int old_errno = errno;
#ifdef SB_MEM_DEBUG
mtrace();
#endif
rc_log_domain(log_domain);
sb_set_open(libsb_open);
/* Get the path and name to this library */
get_sandbox_lib(sandbox_lib);
// sb_init = 1;
errno = old_errno;
}
int canonicalize(const char *path, char *resolved_path)
{
int old_errno = errno;
char *retval;
*resolved_path = '\0';
/* If path == NULL, return or we get a segfault */
if (NULL == path) {
errno = EINVAL;
return -1;
}
/* Do not try to resolve an empty path */
if ('\0' == path[0]) {
errno = old_errno;
return 0;
}
retval = erealpath(path, resolved_path);
if ((NULL == retval) && (path[0] != '/')) {
/* The path could not be canonicalized, append it
* to the current working directory if it was not
* an absolute path
*/
if (ENAMETOOLONG == errno)
return -1;
if (NULL == egetcwd(resolved_path, SB_PATH_MAX - 2))
return -1;
snprintf((char *)(resolved_path + strlen(resolved_path)),
SB_PATH_MAX - strlen(resolved_path), "/%s", path);
if (NULL == erealpath(resolved_path, resolved_path)) {
if (errno == ENAMETOOLONG) {
/* The resolved path is too long for the buffer to hold */
return -1;
} else {
/* Whatever it resolved, is not a valid path */
errno = ENOENT;
return -1;
}
}
} else if ((NULL == retval) && (path[0] == '/')) {
/* Whatever it resolved, is not a valid path */
errno = ENOENT;
return -1;
}
errno = old_errno;
return 0;
}
static char *resolve_path(const char *path, int follow_link)
{
char *dname, *bname;
char *filtered_path;
if (NULL == path)
return NULL;
save_errno();
filtered_path = xmalloc(SB_PATH_MAX * sizeof(char));
if (NULL == filtered_path)
return NULL;
if (0 == follow_link) {
if (-1 == canonicalize(path, filtered_path)) {
free(filtered_path);
filtered_path = NULL;
}
} else {
/* Basically we get the realpath which should resolve symlinks,
* etc. If that fails (might not exist), we try to get the
* realpath of the parent directory, as that should hopefully
* exist. If all else fails, just go with canonicalize */
if (NULL == realpath(path, filtered_path)) {
char tmp_str1[SB_PATH_MAX];
snprintf(tmp_str1, SB_PATH_MAX, "%s", path);
dname = dirname(tmp_str1);
/* If not, then check if we can resolve the
* parent directory */
if (NULL == realpath(dname, filtered_path)) {
/* Fall back to canonicalize */
if (-1 == canonicalize(path, filtered_path)) {
free(filtered_path);
filtered_path = NULL;
}
} else {
char tmp_str2[SB_PATH_MAX];
/* OK, now add the basename to keep our access
* checking happy (don't want '/usr/lib' if we
* tried to do something with non-existing
* file '/usr/lib/cf*' ...) */
snprintf(tmp_str2, SB_PATH_MAX, "%s", path);
bname = rc_basename(tmp_str2);
snprintf((char *)(filtered_path + strlen(filtered_path)),
SB_PATH_MAX - strlen(filtered_path), "%s%s",
(filtered_path[strlen(filtered_path) - 1] != '/') ? "/" : "",
bname);
}
}
}
restore_errno();
return filtered_path;
}
/*
* Internal Functions
*/
char *egetcwd(char *buf, size_t size)
{
struct stat st;
char *tmpbuf, *oldbuf = buf;
int old_errno;
/* Need to disable sandbox, as on non-linux libc's, opendir() is
* used by some getcwd() implementations and resolves to the sandbox
* opendir() wrapper, causing infinit recursion and finially crashes.
*/
sandbox_on = 0;
errno = 0;
tmpbuf = libsb_getcwd(buf, size);
sandbox_on = 1;
/* We basically try to figure out if we can trust what getcwd()
* returned. If one of the following happens kernel/libc side,
* bad things will happen, but not much we can do about it:
* - Invalid pointer with errno = 0
* - Truncated path with errno = 0
* - Whatever I forgot about
*/
if ((tmpbuf) && (errno == 0)) {
old_errno = errno;
lstat(buf, &st);
if (errno == ENOENT) {
/* If getcwd() allocated the buffer, free it. */
if (NULL == oldbuf)
free(tmpbuf);
/* If lstat() failed with eerror = ENOENT, then its
* possible that we are running on an older kernel
* which had issues with returning invalid paths if
* they got too long. Return with errno = ENAMETOOLONG,
* so that canonicalize() and check_syscall() know
* what the issue is.
*/
errno = ENAMETOOLONG;
return NULL;
} else if (errno != 0) {
/* If getcwd() allocated the buffer, free it. */
if (NULL == oldbuf)
free(tmpbuf);
/* Not sure if we should quit here, but I guess if
* lstat() fails, getcwd could have messed up. Not
* sure what to do about errno - use lstat()'s for
* now.
*/
return NULL;
}
errno = old_errno;
} else if (errno != 0) {
/* Make sure we do not return garbage if the current libc or
* kernel's getcwd() is buggy.
*/
return NULL;
}
return tmpbuf;
}
static char *getcmdline(void)
{
rc_dynbuf_t *proc_data;
struct stat st;
char *buf;
size_t n;
int fd;
if (-1 == stat(PROC_SELF_CMDLINE, &st)) {
/* Don't care if it does not exist */
errno = 0;
return NULL;
}
proc_data = rc_dynbuf_new();
if (NULL == proc_data) {
SB_EERROR("ISE ", "Could not allocate dynamic buffer!\n");
return NULL;
}
fd = sb_open(PROC_SELF_CMDLINE, O_RDONLY, 0);
if (fd < 0) {
SB_EERROR("ISE ", "Failed to open '%s'!\n", PROC_SELF_CMDLINE);
return NULL;
}
/* Read PAGE_SIZE at a time -- whenever EOF or an error is found
* (don't care) give up and return.
* XXX: Some linux kernels especially needed read() to read PAGE_SIZE
* at a time. */
do {
n = rc_dynbuf_write_fd(proc_data, fd, getpagesize());
if (-1 == n) {
SB_EERROR("ISE ", "Failed to read from '%s'!\n", PROC_SELF_CMDLINE);
goto error;
}
} while (0 < n);
sb_close(fd);
rc_dynbuf_replace_char(proc_data, '\0', ' ');
buf = rc_dynbuf_read_line(proc_data);
if (NULL == buf)
goto error;
rc_dynbuf_free(proc_data);
return buf;
error:
if (NULL != proc_data)
rc_dynbuf_free(proc_data);
return NULL;
}
static int write_logfile(const char *logfile, const char *func, const char *path,
const char *apath, const char *rpath, bool access)
{
struct stat log_stat;
int stat_ret;
int logfd;
stat_ret = lstat(logfile, &log_stat);
/* Do not care about failure */
errno = 0;
if ((0 == stat_ret) &&
(0 == S_ISREG(log_stat.st_mode))) {
SB_EERROR("SECURITY BREACH", " '%s' %s\n", logfile,
"already exists and is not a regular file!");
abort();
} else {
logfd = sb_open(logfile, O_APPEND | O_WRONLY |
O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP |
S_IROTH);
if (logfd >= 0) {
char *cmdline;
if (0 != stat_ret) {
SB_WRITE(logfd, LOG_STRING, strlen(LOG_STRING), error);
SB_WRITE(logfd, LOG_FMT_FUNC, strlen(LOG_FMT_FUNC), error);
SB_WRITE(logfd, LOG_FMT_ACCESS, strlen(LOG_FMT_ACCESS), error);
SB_WRITE(logfd, LOG_FMT_PATH, strlen(LOG_FMT_PATH), error);
SB_WRITE(logfd, LOG_FMT_APATH, strlen(LOG_FMT_APATH), error);
SB_WRITE(logfd, LOG_FMT_RPATH, strlen(LOG_FMT_RPATH), error);
SB_WRITE(logfd, LOG_FMT_CMDLINE, strlen(LOG_FMT_CMDLINE), error);
SB_WRITE(logfd, "\n", 1, error);
} else {
/* Already have data in the log, so add a newline to space the
* log entries.
*/
SB_WRITE(logfd, "\n", 1, error);
}
SB_WRITE(logfd, "F: ", 3, error);
SB_WRITE(logfd, func, strlen(func), error);
SB_WRITE(logfd, "\n", 1, error);
SB_WRITE(logfd, "S: ", 3, error);
if (access)
SB_WRITE(logfd, "allow", 5, error);
else
SB_WRITE(logfd, "deny", 4, error);
SB_WRITE(logfd, "\n", 1, error);
SB_WRITE(logfd, "P: ", 3, error);
SB_WRITE(logfd, path, strlen(path), error);
SB_WRITE(logfd, "\n", 1, error);
SB_WRITE(logfd, "A: ", 3, error);
SB_WRITE(logfd, apath, strlen(apath), error);
SB_WRITE(logfd, "\n", 1, error);
SB_WRITE(logfd, "R: ", 3, error);
SB_WRITE(logfd, rpath, strlen(rpath), error);
SB_WRITE(logfd, "\n", 1, error);
cmdline = getcmdline();
if (NULL != cmdline) {
SB_WRITE(logfd, "C: ", 3, error);
SB_WRITE(logfd, cmdline, strlen(cmdline),
error);
SB_WRITE(logfd, "\n", 1, error);
free(cmdline);
} else if (0 != errno) {
goto error;
}
sb_close(logfd);
} else {
goto error;
}
}
return 0;
error:
return -1;
}
static void init_context(sbcontext_t *context)
{
context->show_access_violation = 1;
context->deny_prefixes = NULL;
context->num_deny_prefixes = 0;
context->read_prefixes = NULL;
context->num_read_prefixes = 0;
context->write_prefixes = NULL;
context->num_write_prefixes = 0;
context->predict_prefixes = NULL;
context->num_predict_prefixes = 0;
context->write_denied_prefixes = NULL;
context->num_write_denied_prefixes = 0;
}
static void clean_env_entries(char ***prefixes_array, int *prefixes_num)
{
int old_errno = errno;
int i = 0;
if (NULL != *prefixes_array) {
for (i = 0; i < *prefixes_num; i++) {
if (NULL != (*prefixes_array)[i]) {
free((*prefixes_array)[i]);
(*prefixes_array)[i] = NULL;
}
}
if (NULL != *prefixes_array)
free(*prefixes_array);
*prefixes_array = NULL;
*prefixes_num = 0;
}
errno = old_errno;
}
#define pfx_num (*prefixes_num)
#define pfx_array (*prefixes_array)
#define pfx_item ((*prefixes_array)[(*prefixes_num)])
static void init_env_entries(char ***prefixes_array, int *prefixes_num, const char *env, const char *prefixes_env, int warn)
{
char *token = NULL;
char *rpath = NULL;
char *buffer = NULL;
char *buffer_ptr = NULL;
int prefixes_env_length = strlen(prefixes_env);
int num_delimiters = 0;
int i = 0;
int old_errno = errno;
if (NULL == prefixes_env) {
/* Do not warn if this is in init stage, as we might get
* issues due to LD_PRELOAD already set (bug #91431). */
if (1 == sb_init)
fprintf(stderr,
"libsandbox: The '%s' env variable is not defined!\n",
env);
if (pfx_array) {
for (i = 0; i < pfx_num; i++)
free(pfx_item);
free(pfx_array);
}
pfx_num = 0;
goto done;
}
for (i = 0; i < prefixes_env_length; i++) {
if (':' == prefixes_env[i])
num_delimiters++;
}
/* num_delimiters might be 0, and we need 2 entries at least */
pfx_array = xmalloc(((num_delimiters * 2) + 2) * sizeof(char *));
if (NULL == pfx_array)
goto error;
buffer = rc_strndup(prefixes_env, prefixes_env_length);
if (NULL == buffer)
goto error;
buffer_ptr = buffer;
#ifdef HAVE_STRTOK_R
token = strtok_r(buffer_ptr, ":", &buffer_ptr);
#else
token = strtok(buffer_ptr, ":");
#endif
while ((NULL != token) && (strlen(token) > 0)) {
pfx_item = resolve_path(token, 0);
/* We do not care about errno here */
errno = 0;
if (NULL != pfx_item) {
pfx_num++;
/* Now add the realpath if it exists and
* are not a duplicate */
rpath = xmalloc(SB_PATH_MAX * sizeof(char));
if (NULL != rpath) {
pfx_item = realpath(*(&(pfx_item) - 1), rpath);
if ((NULL != pfx_item) &&
(0 != strcmp(*(&(pfx_item) - 1), pfx_item))) {
pfx_num++;
} else {
free(rpath);
pfx_item = NULL;
}
} else {
goto error;
}
}
#ifdef HAVE_STRTOK_R
token = strtok_r(NULL, ":", &buffer_ptr);
#else
token = strtok(NULL, ":");
#endif
}
free(buffer);
done:
errno = old_errno;
return;
error:
SB_EERROR("ISE ", "Unrecoverable error!\n");
abort();
}
static int check_prefixes(char **prefixes, int num_prefixes, const char *path)
{
int i = 0;
if (NULL == prefixes)
return 0;
for (i = 0; i < num_prefixes; i++) {
if (NULL != prefixes[i]) {
if (0 == strncmp(path, prefixes[i], strlen(prefixes[i])))
return 1;
}
}
return 0;
}
static int check_access(sbcontext_t *sbcontext, int sb_nr, const char *func, const char *abs_path, const char *resolv_path)
{
int old_errno = errno;
int result = 0;
int retval;
retval = check_prefixes(sbcontext->deny_prefixes,
sbcontext->num_deny_prefixes, resolv_path);
if (1 == retval)
/* Fall in a read/write denied path, Deny Access */
goto out;
/* Hardcode denying write to log dir */
if (0 == strncmp(resolv_path, SANDBOX_LOG_LOCATION,
strlen(SANDBOX_LOG_LOCATION)))
goto out;
if ((NULL != sbcontext->read_prefixes) &&
(sb_nr == SB_NR_ACCESS_RD ||
sb_nr == SB_NR_OPEN_RD ||
/*sb_nr == SB_NR_POPEN ||*/
sb_nr == SB_NR_OPENDIR ||
/*sb_nr == SB_NR_SYSTEM ||
sb_nr == SB_NR_EXECL ||
sb_nr == SB_NR_EXECLP ||
sb_nr == SB_NR_EXECLE ||
sb_nr == SB_NR_EXECV ||
sb_nr == SB_NR_EXECVP ||*/
sb_nr == SB_NR_EXECVE))
{
retval = check_prefixes(sbcontext->read_prefixes,
sbcontext->num_read_prefixes, resolv_path);
if (1 == retval) {
/* Fall in a readable path, Grant Access */
result = 1;
goto out;
}
/* If we are here, and still no joy, and its the access() call,
* do not log it, but just return -1 */
if (sb_nr == SB_NR_ACCESS_RD) {
sbcontext->show_access_violation = 0;
goto out;
}
}
if (sb_nr == SB_NR_ACCESS_WR ||
sb_nr == SB_NR_OPEN_WR ||
sb_nr == SB_NR_CREAT ||
sb_nr == SB_NR_CREAT64 ||
sb_nr == SB_NR_MKDIR ||
/*sb_nr == SB_NR_MKNOD ||*/
sb_nr == SB_NR___XMKNOD ||
sb_nr == SB_NR_MKFIFO ||
sb_nr == SB_NR_LINK ||
sb_nr == SB_NR_SYMLINK ||
sb_nr == SB_NR_RENAME ||
sb_nr == SB_NR_LUTIMES ||
sb_nr == SB_NR_UTIMENSAT ||
sb_nr == SB_NR_UTIME ||
sb_nr == SB_NR_UTIMES ||
sb_nr == SB_NR_FUTIMESAT ||
sb_nr == SB_NR_UNLINK ||
sb_nr == SB_NR_UNLINKAT ||
sb_nr == SB_NR_RMDIR ||
sb_nr == SB_NR_CHOWN ||
sb_nr == SB_NR_FCHOWNAT ||
sb_nr == SB_NR_LCHOWN ||
sb_nr == SB_NR_CHMOD ||
sb_nr == SB_NR_FCHMODAT ||
sb_nr == SB_NR_TRUNCATE ||
/*sb_nr == SB_NR_FTRUNCATE ||*/
sb_nr == SB_NR_TRUNCATE64/*||
sb_nr == SB_NR_FTRUNCATE64*/)
{
retval = check_prefixes(sbcontext->write_denied_prefixes,
sbcontext->num_write_denied_prefixes,
resolv_path);
if (1 == retval)
/* Falls in a write denied path, Deny Access */
goto out;
retval = check_prefixes(sbcontext->write_prefixes,
sbcontext->num_write_prefixes, resolv_path);
if (1 == retval) {
/* Falls in a writable path, Grant Access */
result = 1;
goto out;
}
/* XXX: Hack to enable us to remove symlinks pointing
* to protected stuff. First we make sure that the
* passed path is writable, and if so, check if its a
* symlink, and give access only if the resolved path
* of the symlink's parent also have write access. */
struct stat st;
if ((sb_nr == SB_NR_UNLINK ||
sb_nr == SB_NR_LCHOWN ||
sb_nr == SB_NR_RENAME ||
sb_nr == SB_NR_SYMLINK) &&
((-1 != lstat(abs_path, &st)) && (S_ISLNK(st.st_mode))))
{
/* Check if the symlink unresolved path have access */
retval = check_prefixes(sbcontext->write_prefixes,
sbcontext->num_write_prefixes, abs_path);
if (1 == retval) { /* Does have write access on path */
char tmp_buf[SB_PATH_MAX];
char *dname, *rpath;
snprintf(tmp_buf, SB_PATH_MAX, "%s", abs_path);
dname = dirname(tmp_buf);
/* Get symlink resolved path */
rpath = resolve_path(dname, 1);
if (NULL == rpath)
/* Don't really worry here about
* memory issues */
goto unlink_hack_end;
/* Now check if the symlink resolved path have access */
retval = check_prefixes(sbcontext->write_prefixes,
sbcontext->num_write_prefixes,
rpath);
free(rpath);
if (1 == retval) {
/* Does have write access on path, so
* enable the hack as it is a symlink */
result = 1;
goto out;
}
}
}
unlink_hack_end: ;
/* XXX: Hack to allow writing to '/proc/self/fd' (bug #91516)
* It needs to be here, as for each process '/proc/self'
* will differ ... */
char proc_self_fd[SB_PATH_MAX];
if ((0 == strncmp(resolv_path, PROC_DIR, strlen(PROC_DIR))) &&
(NULL != realpath(PROC_SELF_FD, proc_self_fd))) {
if (0 == strncmp(resolv_path, proc_self_fd,
strlen(proc_self_fd))) {
result = 1;
goto out;
}
}
retval = check_prefixes(sbcontext->predict_prefixes,
sbcontext->num_predict_prefixes, resolv_path);
if (1 == retval) {
/* Is a known access violation, so deny access,
* and do not log it */
sbcontext->show_access_violation = 0;
goto out;
}
/* If we are here, and still no joy, and its the access() call,
* do not log it, but just return -1 */
if (sb_nr == SB_NR_ACCESS_WR) {
sbcontext->show_access_violation = 0;
goto out;
}
}
out:
errno = old_errno;
return result;
}
static int check_syscall(sbcontext_t *sbcontext, int sb_nr, const char *func, const char *file)
{
char *absolute_path = NULL;
char *resolved_path = NULL;
char *log_path = NULL, *debug_log_path = NULL;
int old_errno = errno;
int result = 1;
int access = 0, debug = 0, verbose = 1;
absolute_path = resolve_path(file, 0);
if (NULL == absolute_path)
goto error;
resolved_path = resolve_path(file, 1);
if (NULL == resolved_path)
goto error;
log_path = getenv(ENV_SANDBOX_LOG);
if (is_env_on(ENV_SANDBOX_DEBUG)) {
debug_log_path = getenv(ENV_SANDBOX_DEBUG_LOG);
debug = 1;
}
if (is_env_off(ENV_SANDBOX_VERBOSE)) {
verbose = 0;
}
result = check_access(sbcontext, sb_nr, func, absolute_path, resolved_path);
if (1 == verbose) {
if ((0 == result) && (1 == sbcontext->show_access_violation)) {
SB_EERROR("ACCESS DENIED", " %s:%*s%s\n",
func, (int)(10 - strlen(func)), "", absolute_path);
} else if ((1 == debug) && (1 == sbcontext->show_access_violation)) {
SB_EINFO("ACCESS ALLOWED", " %s:%*s%s\n",
func, (int)(10 - strlen(func)), "", absolute_path);
} else if ((1 == debug) && (0 == sbcontext->show_access_violation)) {
SB_EWARN("ACCESS PREDICTED", " %s:%*s%s\n",
func, (int)(10 - strlen(func)), "", absolute_path);
}
}
if ((0 == result) && (1 == sbcontext->show_access_violation))
access = 1;
if ((NULL != log_path) && (1 == access)) {
if (-1 == write_logfile(log_path, func, file, absolute_path,
resolved_path, (access == 1) ? 0 : 1)) {
if (0 != errno)
goto error;
}
}
if ((NULL != debug_log_path) && (1 == debug)) {
if (-1 == write_logfile(debug_log_path, func, file, absolute_path,
resolved_path, (access == 1) ? 0 : 1)) {
if (0 != errno)
goto error;
}
}
if (NULL != absolute_path)
free(absolute_path);
if (NULL != resolved_path)
free(resolved_path);
errno = old_errno;
return result;
error:
if (NULL != absolute_path)
free(absolute_path);
if (NULL != resolved_path)
free(resolved_path);
/* The path is too long to be canonicalized, so just warn and let the
* function handle it (see bug #94630 and #21766 for more info) */
if (ENAMETOOLONG == errno) {
if (0 == sb_path_size_warning) {
SB_EWARN("PATH LENGTH", " %s:%*s%s\n",
func, (int)(10 - strlen(func)), "", file);
sb_path_size_warning = 1;
}
return 1;
}
/* If we get here, something bad happened */
SB_EERROR("ISE ", "Unrecoverable error!\n");
abort();
}
int is_sandbox_on(void)
{
int result;
save_errno();
/* $SANDBOX_ACTIVE is an env variable that should ONLY
* be used internal by sandbox.c and libsanbox.c. External
* sources should NEVER set it, else the sandbox is enabled
* in some cases when run in parallel with another sandbox,
* but not even in the sandbox shell.
*/
if ((is_env_on(ENV_SANDBOX_ON)) &&
(1 == sandbox_on) &&
(NULL != getenv(ENV_SANDBOX_ACTIVE)) &&
(0 == strncmp(getenv(ENV_SANDBOX_ACTIVE), SANDBOX_ACTIVE, 13))) {
result = 1;
} else
result = 0;
restore_errno();
return result;
}
int before_syscall(int dirfd, int sb_nr, const char *func, const char *file)
{
int old_errno = errno;
int result = 1;
// static sbcontext_t sbcontext;
char *deny = getenv(ENV_SANDBOX_DENY);
char *read = getenv(ENV_SANDBOX_READ);
char *write = getenv(ENV_SANDBOX_WRITE);
char *predict = getenv(ENV_SANDBOX_PREDICT);
char *at_file_buf = NULL;
if (NULL == file || 0 == strlen(file)) {
/* The file/directory does not exist */
errno = ENOENT;
return 0;
}
/* The *at style functions have the following semantics:
* - dirfd = AT_FDCWD: same as non-at func: file is based on CWD
* - file is absolute: dirfd is ignored
* - otherwise, file is relative to dirfd
* Since maintaining fd state based on open's, we'll just utilize
* the kernel doing it for us with /proc//fd/ ...
*/
if (dirfd != AT_FDCWD && file[0] != '/') {
at_file_buf = xmalloc(50 + strlen(file));
sprintf(at_file_buf, "/proc/%i/fd/%i/%s", getpid(), dirfd, file);
file = at_file_buf;
}
if (0 == sb_init) {
init_context(&sbcontext);
cached_env_vars = xcalloc(4, sizeof(char *));
if (NULL == cached_env_vars) {
SB_EERROR("ISE ", "Unrecoverable error!\n");
abort();
}
sb_init = 1;
}
if ((NULL == deny && cached_env_vars[0] != deny) || NULL == cached_env_vars[0] ||
strcmp(cached_env_vars[0], deny) != 0) {
clean_env_entries(&(sbcontext.deny_prefixes),
&(sbcontext.num_deny_prefixes));
if (NULL != cached_env_vars[0]) {
free(cached_env_vars[0]);
cached_env_vars[0] = NULL;
}
if (NULL != deny) {
init_env_entries(&(sbcontext.deny_prefixes),
&(sbcontext.num_deny_prefixes), ENV_SANDBOX_DENY, deny, 1);
cached_env_vars[0] = strdup(deny);
} else
cached_env_vars[0] = NULL;
}
if ((NULL == read && cached_env_vars[1] != read) || NULL == cached_env_vars[1] ||
strcmp(cached_env_vars[1], read) != 0) {
clean_env_entries(&(sbcontext.read_prefixes),
&(sbcontext.num_read_prefixes));
if (NULL != cached_env_vars[1]) {
free(cached_env_vars[1]);
cached_env_vars[1] = NULL;
}
if (NULL != read) {
init_env_entries(&(sbcontext.read_prefixes),
&(sbcontext.num_read_prefixes), ENV_SANDBOX_READ, read, 1);
cached_env_vars[1] = strdup(read);
} else
cached_env_vars[1] = NULL;
}
if ((NULL == write && cached_env_vars[2] != write) || NULL == cached_env_vars[2] ||
strcmp(cached_env_vars[2], write) != 0) {
clean_env_entries(&(sbcontext.write_prefixes),
&(sbcontext.num_write_prefixes));
if (NULL != cached_env_vars[2]) {
free(cached_env_vars[2]);
cached_env_vars[2] = NULL;
}
if (NULL != write) {
init_env_entries(&(sbcontext.write_prefixes),
&(sbcontext.num_write_prefixes), ENV_SANDBOX_WRITE, write, 1);
cached_env_vars[2] = strdup(write);
} else
cached_env_vars[2] = NULL;
}
if ((NULL == predict && cached_env_vars[3] != predict) || NULL == cached_env_vars[3] ||
strcmp(cached_env_vars[3], predict) != 0) {
clean_env_entries(&(sbcontext.predict_prefixes),
&(sbcontext.num_predict_prefixes));
if (NULL != cached_env_vars[3]) {
free(cached_env_vars[3]);
cached_env_vars[2] = NULL;
}
if (NULL != predict) {
init_env_entries(&(sbcontext.predict_prefixes),
&(sbcontext.num_predict_prefixes), ENV_SANDBOX_PREDICT, predict, 1);
cached_env_vars[3] = strdup(predict);
} else
cached_env_vars[3] = NULL;
}
/* Might have been reset in check_access() */
sbcontext.show_access_violation = 1;
result = check_syscall(&sbcontext, sb_nr, func, file);
if (at_file_buf)
free(at_file_buf);
errno = old_errno;
if (0 == result) {
if ((NULL != getenv(ENV_SANDBOX_PID)) && (is_env_on(ENV_SANDBOX_ABORT)))
kill(atoi(getenv(ENV_SANDBOX_PID)), SIGUSR1);
/* FIXME: Should probably audit errno, and enable some other
* error to be returned (EINVAL for invalid mode for
* fopen() and co, ETOOLONG, etc). */
errno = EACCES;
}
return result;
}
int before_syscall_access(int dirfd, int sb_nr, const char *func, const char *file, int flags)
{
const char *ext_func;
if (flags & W_OK)
sb_nr = SB_NR_ACCESS_WR, ext_func = "access_wr";
else
sb_nr = SB_NR_ACCESS_RD, ext_func = "access_rd";
return before_syscall(dirfd, sb_nr, ext_func, file);
}
int before_syscall_open_int(int dirfd, int sb_nr, const char *func, const char *file, int flags)
{
const char *ext_func;
if ((flags & O_WRONLY) || (flags & O_RDWR))
sb_nr = SB_NR_OPEN_WR, ext_func = "open_wr";
else
sb_nr = SB_NR_OPEN_RD, ext_func = "open_rd";
return before_syscall(dirfd, sb_nr, ext_func, file);
}
int before_syscall_open_char(int dirfd, int sb_nr, const char *func, const char *file, const char *mode)
{
if (NULL == mode)
return 0;
const char *ext_func;
if ((*mode == 'r') && ((0 == (strcmp(mode, "r"))) ||
/* The strspn accept args are known non-writable modifiers */
(strlen(++mode) == strspn(mode, "xbtmce"))))
sb_nr = SB_NR_OPEN_RD, ext_func = "open_rd";
else
sb_nr = SB_NR_OPEN_WR, ext_func = "open_wr";
return before_syscall(dirfd, sb_nr, ext_func, file);
}