/*
* Copyright 2007,2008,2009,2011,2012,2013,2016 Red Hat, Inc. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of Red Hat, Inc. nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* File-opening wrappers for creating correctly-labeled files. So far, we can
* assume that this is Linux-specific, so we make many simplifying assumptions.
*/
#include "../../include/autoconf.h"
#ifdef USE_SELINUX
#include <k5-label.h>
#include <k5-platform.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <selinux/selinux.h>
#include <selinux/context.h>
#include <selinux/label.h>
/* #define DEBUG 1 */
static void
debug_log(const char *fmt, ...)
{
#ifdef DEBUG
va_list ap;
va_start(ap, fmt);
if (isatty(fileno(stderr))) {
vfprintf(stderr, fmt, ap);
}
va_end(ap);
#endif
return;
}
/* Mutex used to serialize use of the process-global file creation context. */
k5_mutex_t labeled_mutex = K5_MUTEX_PARTIAL_INITIALIZER;
/* Make sure we finish initializing that mutex before attempting to use it. */
k5_once_t labeled_once = K5_ONCE_INIT;
static void
label_mutex_init(void)
{
k5_mutex_finish_init(&labeled_mutex);
}
static struct selabel_handle *selabel_ctx;
static time_t selabel_last_changed;
MAKE_FINI_FUNCTION(cleanup_fscreatecon);
static void
cleanup_fscreatecon(void)
{
if (selabel_ctx != NULL) {
selabel_close(selabel_ctx);
selabel_ctx = NULL;
}
}
static security_context_t
push_fscreatecon(const char *pathname, mode_t mode)
{
security_context_t previous, configuredsc, currentsc, derivedsc;
context_t current, derived;
const char *fullpath, *currentuser;
char *genpath;
previous = configuredsc = currentsc = derivedsc = NULL;
current = derived = NULL;
genpath = NULL;
fullpath = pathname;
if (!is_selinux_enabled()) {
goto fail;
}
if (getfscreatecon(&previous) != 0) {
goto fail;
}
/* Canonicalize pathname */
if (pathname[0] != '/') {
char *wd;
size_t len;
len = 0;
wd = getcwd(NULL, len);
if (wd == NULL) {
goto fail;
}
len = strlen(wd) + 1 + strlen(pathname) + 1;
genpath = malloc(len);
if (genpath == NULL) {
free(wd);
goto fail;
}
sprintf(genpath, "%s/%s", wd, pathname);
free(wd);
fullpath = genpath;
}
debug_log("Looking up context for \"%s\"(%05o).\n", fullpath, mode);
/* Check whether context file has changed under us */
if (selabel_ctx != NULL || selabel_last_changed == 0) {
const char *cpath;
struct stat st;
int i = -1;
cpath = selinux_file_context_path();
if (cpath == NULL || (i = stat(cpath, &st)) != 0 ||
st.st_mtime != selabel_last_changed) {
cleanup_fscreatecon();
selabel_last_changed = i ? time(NULL) : st.st_mtime;
}
}
if (selabel_ctx == NULL) {
selabel_ctx = selabel_open(SELABEL_CTX_FILE, NULL, 0);
}
if (selabel_ctx != NULL &&
selabel_lookup(selabel_ctx, &configuredsc, fullpath, mode) != 0) {
goto fail;
}
if (genpath != NULL) {
free(genpath);
genpath = NULL;
}
if (configuredsc == NULL) {
goto fail;
}
getcon(¤tsc);
/* AAAAAAAA */
if (currentsc != NULL) {
derived = context_new(configuredsc);
if (derived != NULL) {
current = context_new(currentsc);
if (current != NULL) {
currentuser = context_user_get(current);
if (currentuser != NULL) {
if (context_user_set(derived,
currentuser) == 0) {
derivedsc = context_str(derived);
if (derivedsc != NULL) {
freecon(configuredsc);
configuredsc = strdup(derivedsc);
}
}
}
context_free(current);
}
context_free(derived);
}
freecon(currentsc);
}
debug_log("Setting file creation context to \"%s\".\n", configuredsc);
if (setfscreatecon(configuredsc) != 0) {
debug_log("Unable to determine current context.\n");
goto fail;
}
freecon(configuredsc);
return previous;
fail:
if (previous != NULL) {
freecon(previous);
}
if (genpath != NULL) {
free(genpath);
}
if (configuredsc != NULL) {
freecon(configuredsc);
}
cleanup_fscreatecon();
return NULL;
}
static void
pop_fscreatecon(security_context_t previous)
{
if (!is_selinux_enabled()) {
return;
}
if (previous != NULL) {
debug_log("Resetting file creation context to \"%s\".\n", previous);
} else {
debug_log("Resetting file creation context to default.\n");
}
/* NULL resets to default */
setfscreatecon(previous);
if (previous != NULL) {
freecon(previous);
}
/* Need to clean this up here otherwise it leaks */
cleanup_fscreatecon();
}
void *
krb5int_push_fscreatecon_for(const char *pathname)
{
struct stat st;
void *retval;
k5_once(&labeled_once, label_mutex_init);
k5_mutex_lock(&labeled_mutex);
if (stat(pathname, &st) != 0) {
st.st_mode = S_IRUSR | S_IWUSR;
}
retval = push_fscreatecon(pathname, st.st_mode);
return retval ? retval : (void *) -1;
}
void
krb5int_pop_fscreatecon(void *con)
{
if (con != NULL) {
pop_fscreatecon((con == (void *) -1) ? NULL : con);
k5_mutex_unlock(&labeled_mutex);
}
}
FILE *
krb5int_labeled_fopen(const char *path, const char *mode)
{
FILE *fp;
int errno_save;
security_context_t ctx;
if ((strcmp(mode, "r") == 0) ||
(strcmp(mode, "rb") == 0)) {
return fopen(path, mode);
}
k5_once(&labeled_once, label_mutex_init);
k5_mutex_lock(&labeled_mutex);
ctx = push_fscreatecon(path, 0);
fp = fopen(path, mode);
errno_save = errno;
pop_fscreatecon(ctx);
k5_mutex_unlock(&labeled_mutex);
errno = errno_save;
return fp;
}
int
krb5int_labeled_creat(const char *path, mode_t mode)
{
int fd;
int errno_save;
security_context_t ctx;
k5_once(&labeled_once, label_mutex_init);
k5_mutex_lock(&labeled_mutex);
ctx = push_fscreatecon(path, 0);
fd = creat(path, mode);
errno_save = errno;
pop_fscreatecon(ctx);
k5_mutex_unlock(&labeled_mutex);
errno = errno_save;
return fd;
}
int
krb5int_labeled_mknod(const char *path, mode_t mode, dev_t dev)
{
int ret;
int errno_save;
security_context_t ctx;
k5_once(&labeled_once, label_mutex_init);
k5_mutex_lock(&labeled_mutex);
ctx = push_fscreatecon(path, mode);
ret = mknod(path, mode, dev);
errno_save = errno;
pop_fscreatecon(ctx);
k5_mutex_unlock(&labeled_mutex);
errno = errno_save;
return ret;
}
int
krb5int_labeled_mkdir(const char *path, mode_t mode)
{
int ret;
int errno_save;
security_context_t ctx;
k5_once(&labeled_once, label_mutex_init);
k5_mutex_lock(&labeled_mutex);
ctx = push_fscreatecon(path, S_IFDIR);
ret = mkdir(path, mode);
errno_save = errno;
pop_fscreatecon(ctx);
k5_mutex_unlock(&labeled_mutex);
errno = errno_save;
return ret;
}
int
krb5int_labeled_open(const char *path, int flags, ...)
{
int fd;
int errno_save;
security_context_t ctx;
mode_t mode;
va_list ap;
if ((flags & O_CREAT) == 0) {
return open(path, flags);
}
k5_once(&labeled_once, label_mutex_init);
k5_mutex_lock(&labeled_mutex);
ctx = push_fscreatecon(path, 0);
va_start(ap, flags);
mode = va_arg(ap, mode_t);
fd = open(path, flags, mode);
va_end(ap);
errno_save = errno;
pop_fscreatecon(ctx);
k5_mutex_unlock(&labeled_mutex);
errno = errno_save;
return fd;
}
#endif /* USE_SELINUX */