Linux: move process creation into core

Genode used to create new processes by directly forking from the
respective Genode parent using the process library. The forking process
created a PD session at core merely for propagating the PID of the new
process into core (for later destruction). This traditional mechanisms
has the following disadvantages:

First, the PID reported by the creating process to core cannot easily be
validated by core. Therefore core has to trust the PD client to not
specify a PID of an existing process, which would happen to be killed
once the PD session gets destructed. This problem is documented by
issue #318. Second, there is no way for a Genode process to detect the
failure of its any grandchildren. The immediate parent of a faulting
process could use the SIGCHLD-and-waitpid mechanism to observe its
children but this mechanism does not work transitively.

By performing the process creation exclusively within core, all Genode
processes become immediate child processes of core. Hence, core can
respond to failures of any of those processes and reflect such
conditions via core's session interfaces. Furthermore, the PID
associated to a PD session is locally known within core and cannot be
forged anymore. In fact, there is actually no need at all to make
processes aware of any PIDs of other processes.

Please note that this patch breaks the 'chroot' mechanism that comes in
the form of the 'os/src/app/chroot' program. Because all processes are
forked from core, a chroot'ed process could sneak outside its chroot
environment by just creating a new Genode process. To address this
issue, the chroot mechanism must be added to core.
This commit is contained in:
Norman Feske 2012-08-15 19:14:05 +02:00
parent 7e23b2d569
commit 20d8655a7f
8 changed files with 269 additions and 197 deletions

View File

@ -79,4 +79,4 @@ namespace Genode {
};
}
#endif /* _INCLUDE__CPU_SESSION__CLIENT_H_ */
#endif /* _INCLUDE__LINUX_CPU_SESSION__CLIENT_H_ */

View File

@ -0,0 +1,43 @@
/*
* \brief Client-side 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.
*/
#ifndef _INCLUDE__LINUX_PD_SESSION__CLIENT_H_
#define _INCLUDE__LINUX_PD_SESSION__CLIENT_H_
#include <linux_pd_session/linux_pd_session.h>
#include <base/rpc_client.h>
namespace Genode {
struct Linux_pd_session_client : Rpc_client<Linux_pd_session>
{
explicit Linux_pd_session_client(Capability<Linux_pd_session> session)
: Rpc_client<Linux_pd_session>(session) { }
int bind_thread(Thread_capability thread) {
return call<Rpc_bind_thread>(thread); }
int assign_parent(Parent_capability parent) {
return call<Rpc_assign_parent>(parent); }
/*****************************
* Linux-specific extension **
*****************************/
void start(Capability<Dataspace> binary, Name const &name) {
call<Rpc_start>(binary, name); }
};
}
#endif /* _INCLUDE__LINUX_PD_SESSION__CLIENT_H_ */

View File

@ -0,0 +1,38 @@
/*
* \brief Linux-specific extension 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.
*/
#ifndef _INCLUDE__LINUX_PD_SESSION__LINUX_PD_SESSION_H_
#define _INCLUDE__LINUX_PD_SESSION__LINUX_PD_SESSION_H_
#include <pd_session/pd_session.h>
#include <dataspace/dataspace.h>
namespace Genode {
struct Linux_pd_session : Pd_session
{
typedef Rpc_in_buffer<64> Name;
void start(Capability<Dataspace> binary, Name const &name);
/*********************
** RPC declaration **
*********************/
GENODE_RPC(Rpc_start, void, start, Capability<Dataspace>, Name const &);
GENODE_RPC_INTERFACE_INHERIT(Pd_session, Rpc_start);
};
}
#endif /* _INCLUDE__LINUX_PD_SESSION__LINUX_PD_SESSION_H_ */

View File

@ -16,10 +16,9 @@
#include <base/env.h>
#include <base/process.h>
#include <base/printf.h>
#include <base/snprintf.h>
#include <linux_dataspace/client.h>
#include <linux_pd_session/client.h>
/* Framework-internal includes */
/* framework-internal includes */
#include <linux_syscalls.h>
@ -27,50 +26,6 @@ using namespace Genode;
Dataspace_capability Process::_dynamic_linker_cap;
/**
* Argument frame for passing 'execve' paremeters through 'clone'
*/
struct Execve_args
{
const char *filename;
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);
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 "";
}
/**
* Check for dynamic ELF header
@ -97,93 +52,6 @@ static bool _check_dynamic_elf(Dataspace_capability elf_ds_cap)
}
const char *Process::_priv_pd_args(Parent_capability parent_cap,
Dataspace_capability elf_data_ds_cap,
const char *name, char *const argv[])
{
/*
* Serialize calls of this function because it uses the static 'envbuf' and
* 'stack' variables.
*/
static Lock _priv_pd_args_lock;
Lock::Guard _lock_guard(_priv_pd_args_lock);
/* check for dynamic program header */
if (_check_dynamic_elf(elf_data_ds_cap)) {
if (!_dynamic_linker_cap.valid()) {
PERR("Dynamically linked file found, but no dynamic linker binary present");
return 0;
}
elf_data_ds_cap = _dynamic_linker_cap;
}
/* 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_cap.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 };
/* determine name of binary to start */
Linux_dataspace_client elf_data_ds(elf_data_ds_cap);
Linux_dataspace::Filename fname = elf_data_ds.fname();
fname.buf[sizeof(fname.buf) - 1] = 0;
/* prefix name of Linux program (helps killing some zombies) */
char pname_buf[9 + Linux_dataspace::FNAME_LEN];
snprintf(pname_buf, sizeof(pname_buf), "[Genode] %s", name);
/* it may happen, that argv is null */
char *argv_buf[2];
if (!argv) {
argv_buf[0] = pname_buf;
argv_buf[1] = 0;
argv = argv_buf;
} else
((char **)argv)[0] = pname_buf;
/*
* 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 = { fname.buf, argv, env, parent_cap.dst().socket };
pid_t pid = lx_create_process((int (*)(void *))_exec_child,
stack + STACK_SIZE - sizeof(umword_t), &arg);
/*
* We create a pseudo pd session with the new pd's pid as argument
* to enable Core to kill the process when the pd session gets closed.
*/
snprintf(_priv_pd_argbuf, sizeof(_priv_pd_argbuf), "PID=%d", pid);
return _priv_pd_argbuf;
}
Process::Process(Dataspace_capability elf_data_ds_cap,
Ram_session_capability ram_session_cap,
Cpu_session_capability cpu_session_cap,
@ -192,10 +60,23 @@ Process::Process(Dataspace_capability elf_data_ds_cap,
const char *name,
char *const argv[])
:
_pd(_priv_pd_args(parent_cap, elf_data_ds_cap, name, argv)),
_cpu_session_client(Cpu_session_capability()),
_rm_session_client(Rm_session_capability())
{ }
{
/* check for dynamic program header */
if (_check_dynamic_elf(elf_data_ds_cap)) {
if (!_dynamic_linker_cap.valid()) {
PERR("Dynamically linked file found, but no dynamic linker binary present");
return;
}
elf_data_ds_cap = _dynamic_linker_cap;
}
Linux_pd_session_client lx_pd(static_cap_cast<Linux_pd_session>(_pd.cap()));
lx_pd.assign_parent(parent_cap);
lx_pd.start(elf_data_ds_cap, name);
}
Process::~Process() { }

View File

@ -65,6 +65,13 @@ inline int lx_stat(const char *path, struct stat64 *buf)
** Process creation and destruction **
**************************************/
inline int lx_execve(const char *filename, char *const argv[],
char *const envp[])
{
return lx_syscall(SYS_execve, filename, argv, envp);
}
/**
* Send signal to process
*
@ -76,6 +83,13 @@ inline int lx_kill(int pid, int signal)
}
inline int lx_create_process(int (*entry)(void *), void *stack, void *arg)
{
int flags = CLONE_VFORK | SIGCHLD;
return lx_clone((int (*)(void *))entry, stack, flags, arg);
}
/********************************************
** Communication over Unix-domain sockets **
********************************************/

View File

@ -1,61 +1,62 @@
/*
* \brief CORE-specific instance of the PD session interface for Linux
* \brief Linux-specific PD session
* \author Norman Feske
* \date 2006-08-14
*
* On Linux, we use a pd session only for keeping the information about the
* existence of protection domains to enable us to destruct all pds of a whole
* subtree. A pd is killed by CORE when closing the corresponding pd session.
* The PID of the process is passed to CORE as an argument of the session
* construction.
* \date 2012-08-15
*/
/*
* Copyright (C) 2006-2012 Genode Labs GmbH
* 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.
*/
#ifndef _CORE__INCLUDE__LINUX__PD_SESSION_COMPONENT_H_
#define _CORE__INCLUDE__LINUX__PD_SESSION_COMPONENT_H_
#ifndef _CORE__INCLUDE__PD_SESSION_COMPONENT_H_
#define _CORE__INCLUDE__PD_SESSION_COMPONENT_H_
/* Genode includes */
#include <util/arg_string.h>
#include <base/printf.h>
#include <base/rpc_server.h>
#include <pd_session/pd_session.h>
#include <linux_pd_session/linux_pd_session.h>
/* local includes */
#include "platform.h"
/* core includes */
#include <platform_pd.h>
namespace Genode {
class Pd_session_component : public Rpc_object<Pd_session>
class Pd_session_component : public Rpc_object<Linux_pd_session, Pd_session_component>
{
private:
unsigned long _pid;
unsigned long _pid;
Parent_capability _parent;
Rpc_entrypoint *_ds_ep;
public:
Pd_session_component(Rpc_entrypoint *thread_ep,
const char *args);
/**
* Constructor
*
* \param ds_ep entrypoint where the dataspaces are managed
*/
Pd_session_component(Rpc_entrypoint *ds_ep, const char *args);
~Pd_session_component();
/****************************/
/** Pd session interface **/
/****************************/
/**************************
** PD session interface **
**************************/
/*
* This interface is not functional on Linux.
*/
int bind_thread(Thread_capability);
int assign_parent(Parent_capability parent);
int bind_thread(Thread_capability thread);
int assign_parent(Parent_capability);
/******************************
** Linux-specific extension **
******************************/
void start(Capability<Dataspace> binary, Name const &name);
};
}
#endif /* _CORE__INCLUDE__LINUX__PD_SESSION_COMPONENT_H_ */
#endif /* _CORE__INCLUDE__PD_SESSION_COMPONENT_H_ */

View File

@ -1,23 +1,23 @@
/**
/*
* \brief Core implementation of the PD session interface
* \author Christian Helmuth
* \date 2006-07-17
*
* FIXME arg_string and quota missing
* \author Norman Feske
* \date 2012-08-15
*/
/*
* Copyright (C) 2006-2012 Genode Labs GmbH
* 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 */
/* Genode includes */
#include <base/printf.h>
#include <base/snprintf.h>
/* Core */
/* core-local includes */
#include <pd_session_component.h>
#include <dataspace_component.h>
/* Linux includes */
#include <core_linux_syscalls.h>
@ -25,13 +25,63 @@
using namespace Genode;
Pd_session_component::Pd_session_component(Rpc_entrypoint *thread_ep,
const char *args)
/***************
** Utilities **
***************/
/**
* Argument frame for passing 'execve' paremeters through 'clone'
*/
struct Execve_args
{
_pid = Arg_string::find_arg(args, "PID").long_value(0);
const char *filename;
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);
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 *ds_ep, const char *)
: _pid(0), _ds_ep(ds_ep) { }
Pd_session_component::~Pd_session_component()
{
if (_pid)
@ -39,13 +89,72 @@ Pd_session_component::~Pd_session_component()
}
int Pd_session_component::bind_thread(Thread_capability)
{
return -1;
int Pd_session_component::bind_thread(Thread_capability) { return -1;
}
int Pd_session_component::assign_parent(Parent_capability)
int Pd_session_component::assign_parent(Parent_capability parent)
{
return -1;
_parent = parent;
return 0;
}
void Pd_session_component::start(Capability<Dataspace> binary, Name const &name)
{
/* 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 pname_buf[9 + Linux_dataspace::FNAME_LEN];
snprintf(pname_buf, sizeof(pname_buf), "[Genode] %s", name.string());
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, argv_buf, env, _parent.dst().socket };
_pid = lx_create_process((int (*)(void *))_exec_child,
stack + STACK_SIZE - sizeof(umword_t), &arg);
};

View File

@ -10,7 +10,7 @@
* interface directly rather than relying on convenient libc functions to be
* able to link this part of the framework to a custom libc. Otherwise, we
* would end up with very nasty cyclic dependencies when using framework
* functions such as IPC from the libC back end.
* functions such as IPC from the libc back end.
*
* The Linux syscall interface looks different for 32bit and 64bit system, in
* particular regarding the socket interface. On 32bit systems, all socket
@ -165,13 +165,6 @@ inline int lx_getpeername(int sockfd, struct sockaddr *name, socklen_t *namelen)
** Functions used by the process library **
*******************************************/
inline int lx_execve(const char *filename, char *const argv[],
char *const envp[])
{
return lx_syscall(SYS_execve, filename, argv, envp);
}
inline void lx_exit(int status)
{
lx_syscall(SYS_exit, status);
@ -327,13 +320,6 @@ inline int lx_create_thread(void (*entry)(void *), void *stack, void *arg)
}
inline int lx_create_process(int (*entry)(void *), void *stack, void *arg)
{
int flags = CLONE_VFORK | SIGCHLD;
return lx_clone((int (*)(void *))entry, stack, flags, arg);
}
inline pid_t lx_getpid() { return lx_syscall(SYS_getpid); }
inline pid_t lx_gettid() { return lx_syscall(SYS_gettid); }
inline uid_t lx_getuid() { return lx_syscall(SYS_getuid); }