genode/base-linux/src/core/pd_session_component.cc
Torsten Hilbrich 59eb8bf3a8 base-linux: Add chdir after performing chroot
This ensures that the cwd of the process is within the chroot
environment, improving security for root processes.

The cwd after the chroot is the same as before, this is needed to
start binaries given as relative path name.
2012-11-21 20:03:52 +01:00

362 lines
8.4 KiB
C++

/*
* \brief Core implementation of the PD session interface
* \author Norman Feske
* \date 2012-08-15
*/
/*
* Copyright (C) 2012 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
/* Genode includes */
#include <util/arg_string.h>
#include <base/printf.h>
#include <base/snprintf.h>
/* core-local includes */
#include <pd_session_component.h>
#include <dataspace_component.h>
/* Linux includes */
#include <core_linux_syscalls.h>
using namespace Genode;
/***********************************
** Utilities for chroot handling **
***********************************/
enum { MAX_PATH_LEN = 256 };
/**
* Return true if specified path is an existing directory
*/
static bool is_directory(char const *path)
{
struct stat64 s;
if (lx_stat(path, &s) != 0)
return false;
if (!(s.st_mode & S_IFDIR))
return false;
return true;
}
static bool is_path_delimiter(char c) { return c == '/'; }
static bool has_trailing_path_delimiter(char const *path)
{
char last_char = 0;
for (; *path; path++)
last_char = *path;
return is_path_delimiter(last_char);
}
/**
* Return number of path elements of given path
*/
static Genode::size_t num_path_elements(char const *path)
{
Genode::size_t count = 0;
/*
* If path starts with non-slash, the first characters belongs to a path
* element.
*/
if (*path && !is_path_delimiter(*path))
count = 1;
/* count slashes */
for (; *path; path++)
if (is_path_delimiter(*path))
count++;
return count;
}
static bool leading_path_elements(char const *path, unsigned num,
char *dst, Genode::size_t dst_len)
{
/* counter of path delimiters */
unsigned count = 0;
unsigned i = 0;
if (is_path_delimiter(path[0]))
num++;
for (; path[i] && (count < num) && (i < dst_len); i++)
{
if (is_path_delimiter(path[i]))
count++;
if (count == num)
break;
dst[i] = path[i];
}
if (i + 1 < dst_len) {
dst[i] = 0;
return true;
}
/* string is cut, append null termination anyway */
dst[dst_len - 1] = 0;
return false;
}
static void mirror_path_to_chroot(char const *chroot_path, char const *path)
{
char target_path[MAX_PATH_LEN];
Genode::snprintf(target_path, sizeof(target_path), "%s%s",
chroot_path, path);
/*
* Create directory hierarchy pointing to the target path except for the
* last element. The last element will be bind-mounted to refer to the
* original 'path'.
*/
for (unsigned i = 1; i <= num_path_elements(target_path); i++)
{
char buf[MAX_PATH_LEN];
leading_path_elements(target_path, i, buf, sizeof(buf));
/* skip existing directories */
if (is_directory(buf))
continue;
/* create new directory */
lx_mkdir(buf, 0777);
}
lx_umount(target_path);
int ret = 0;
if ((ret = lx_bindmount(path, target_path)))
PERR("bind mount failed (errno=%d)", ret);
}
/**
* Setup content of chroot environment as prerequisite to 'execve' new
* processes within the environment. I.e., the current working directory
* containing the ROM modules must be mounted at the same location within the
* chroot environment.
*/
static bool setup_chroot_environment(char const *chroot_path)
{
using namespace Genode;
static char cwd_path[MAX_PATH_LEN];
lx_getcwd(cwd_path, sizeof(cwd_path));
/*
* Validate chroot path
*/
if (!is_directory(chroot_path)) {
PERR("chroot path does not point to valid directory");
return false;
}
if (has_trailing_path_delimiter(chroot_path)) {
PERR("chroot path has trailing slash");
return false;
}
/*
* Hardlink directories needed for running Genode within the chroot
* environment.
*/
mirror_path_to_chroot(chroot_path, cwd_path);
return true;
}
/***************
** Utilities **
***************/
/**
* Argument frame for passing 'execve' paremeters through 'clone'
*/
struct Execve_args
{
char const *filename;
char const *root;
char *const *argv;
char *const *envp;
int parent_sd;
};
/**
* Startup code of the new child process
*/
static int _exec_child(Execve_args *arg)
{
lx_dup2(arg->parent_sd, PARENT_SOCKET_HANDLE);
/* change to chroot environment */
if (arg->root && arg->root[0]) {
char cwd[1024];
PDBG("arg->root='%s'", arg->root);
if (setup_chroot_environment(arg->root) == false) {
PERR("Could not setup chroot environment");
return -1;
}
if (!lx_getcwd(cwd, sizeof(cwd))) {
PERR("Failed to getcwd");
return -1;
}
PLOG("changing root of %s (PID %d) to %s",
arg->filename, lx_getpid(), arg->root);
int ret = lx_chroot(arg->root);
if (ret < 0) {
PERR("Syscall chroot failed (errno %d)", ret);
return -1;
}
ret = lx_chdir(cwd);
if (ret < 0) {
PERR("chdir to new chroot failed");
return -1;
}
}
return lx_execve(arg->filename, arg->argv, arg->envp);
}
/**
* List of Unix environment variables, initialized by the startup code
*/
extern char **lx_environ;
/**
* Read environment variable as string
*
* If no matching key exists, return an empty string.
*/
static const char *get_env(const char *key)
{
Genode::size_t key_len = Genode::strlen(key);
for (char **curr = lx_environ; curr && *curr; curr++)
if ((Genode::strcmp(*curr, key, key_len) == 0) && (*curr)[key_len] == '=')
return (const char *)(*curr + key_len + 1);
return "";
}
/**************************
** PD session interface **
**************************/
Pd_session_component::Pd_session_component(Rpc_entrypoint *ep, const char *args)
:
_pid(0), _ds_ep(ep)
{
Arg_string::find_arg(args, "label").string(_label, sizeof(_label),
"<unlabeled>");
Arg_string::find_arg(args, "root").string(_root, sizeof(_root), "");
}
Pd_session_component::~Pd_session_component()
{
if (_pid)
lx_kill(_pid, 9);
}
int Pd_session_component::bind_thread(Thread_capability) { return -1; }
int Pd_session_component::assign_parent(Parent_capability parent)
{
_parent = parent;
return 0;
}
void Pd_session_component::start(Capability<Dataspace> binary)
{
/* lookup binary dataspace */
Dataspace_component *ds =
reinterpret_cast<Dataspace_component *>(_ds_ep->obj_by_cap(binary));
if (!ds) {
PERR("could not lookup binary, aborted PD startup");
return; /* XXX reflect error to client */
}
Linux_dataspace::Filename filename = ds->fname();
/* pass parent capability as environment variable to the child */
enum { ENV_STR_LEN = 256 };
static char envbuf[5][ENV_STR_LEN];
Genode::snprintf(envbuf[1], ENV_STR_LEN, "parent_local_name=%lu",
_parent.local_name());
Genode::snprintf(envbuf[2], ENV_STR_LEN, "DISPLAY=%s",
get_env("DISPLAY"));
Genode::snprintf(envbuf[3], ENV_STR_LEN, "HOME=%s",
get_env("HOME"));
Genode::snprintf(envbuf[4], ENV_STR_LEN, "LD_LIBRARY_PATH=%s",
get_env("LD_LIBRARY_PATH"));
char *env[] = { &envbuf[0][0], &envbuf[1][0], &envbuf[2][0],
&envbuf[3][0], &envbuf[4][0], 0 };
/* prefix name of Linux program (helps killing some zombies) */
char const *prefix = "[Genode] ";
char pname_buf[sizeof(_label) + sizeof(prefix)];
snprintf(pname_buf, sizeof(pname_buf), "%s%s", prefix, _label);
char *argv_buf[2];
argv_buf[0] = pname_buf;
argv_buf[1] = 0;
/*
* We cannot create the new process via 'fork()' because all our used
* memory including stack memory is backed by dataspaces, which had been
* mapped with the 'MAP_SHARED' flag. Therefore, after being created, the
* new process starts using the stack with the same physical memory pages
* as used by parent process. This would ultimately lead to stack
* corruption. To prevent both processes from concurrently accessing the
* same stack, we pause the execution of the parent until the child calls
* 'execve'. From then on, the child has its private memory layout. The
* desired behaviour is normally provided by 'vfork' but we use the more
* modern 'clone' call for this purpose.
*/
enum { STACK_SIZE = 4096 };
static char stack[STACK_SIZE]; /* initial stack used by the child until
calling 'execve' */
/*
* Argument frame as passed to 'clone'. Because, we can only pass a single
* pointer, all arguments are embedded within the 'execve_args' struct.
*/
Execve_args arg = { filename.buf, _root, argv_buf, env, _parent.dst().socket };
_pid = lx_create_process((int (*)(void *))_exec_child,
stack + STACK_SIZE - sizeof(umword_t), &arg);
};