SessionHelper: Add calls to spawn processes on the host

This lets any client, possibly in a sandbox if it has access to the
session helper, spawn a process on the host, outside any sandbox.

Clearly this is not something you typically want a sandboxed app to
do. However, it is sometimes very useful when using flatpak mainly
for distribution. For instance, an IDE needs to use this to launch a
flatpak build operation inside the sandbox. (Because otherwise recursive
calls to flatpak will not work.)
This commit is contained in:
Alexander Larsson
2016-09-01 20:53:45 +02:00
parent aafc0f3b66
commit c8df0e6208
3 changed files with 338 additions and 0 deletions

View File

@@ -30,6 +30,11 @@
#include "flatpak-dir.h"
#include <ostree.h>
typedef enum {
FLATPAK_HOST_COMMAND_FLAGS_CLEAR_ENV = 1 << 0,
} FlatpakHostCommandFlags;
gboolean flatpak_fail (GError **error,
const char *format,
...);

View File

@@ -29,6 +29,30 @@
<method name="RequestMonitor">
<arg type='ay' name='path' direction='out'/>
</method>
<!-- This methods let you start processes, sandboxed or not on the
host, so its not generally safe to allow access to it to any
application. However, at times it is very useful to be able
to do this. For instance for developer tools this lets you
build flatpaks from inside a flatpak. -->
<method name="HostCommand">
<arg type='ay' name='cwd_path' direction='in'/>
<arg type='aay' name='argv' direction='in'/>
<arg type='a{uh}' name='fds' direction='in'/>
<arg type='a{ss}' name='envs' direction='in'/>
<arg type='u' name='flags' direction='in'/>
<arg type='u' name='pid' direction='out'/>
</method>
<method name="HostCommandSignal">
<arg type='u' name='pid' direction='in'/>
<arg type='u' name='signal' direction='in'/>
<arg type='b' name='to_process_group' direction='in'/>
</method>
<signal name="HostCommandExited">
<arg type='u' name='pid' direction='out'/>
<arg type='u' name='exit_status' direction='out'/>
</signal>
</interface>
<interface name='org.freedesktop.Flatpak.SystemHelper'>

View File

@@ -23,12 +23,32 @@
#include <locale.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <gio/gio.h>
#include <gio/gunixfdlist.h>
#include "flatpak-dbus.h"
#include "flatpak-utils.h"
static char *monitor_dir;
static guint32 next_client_pid = 0;
static GHashTable *client_pid_data_hash = NULL;
static GDBusConnection *session_bus = NULL;
typedef struct {
GPid pid;
guint32 client_pid;
char *client;
guint child_watch;
} PidData;
static void
pid_data_free (PidData *data)
{
g_free (data->client);
g_free (data);
}
static gboolean
handle_request_monitor (FlatpakSessionHelper *object,
GDBusMethodInvocation *invocation,
@@ -40,6 +60,284 @@ handle_request_monitor (FlatpakSessionHelper *object,
return TRUE;
}
static void
child_watch_died (GPid pid,
gint status,
gpointer user_data)
{
PidData *pid_data = user_data;
guint32 client_pid = pid_data->client_pid;
g_autoptr(GVariant) signal_variant = NULL;
g_debug ("Client Pid %d (%d) died", pid_data->client_pid, pid);
signal_variant = g_variant_ref_sink (g_variant_new ("(uu)", client_pid, status));
g_dbus_connection_emit_signal (session_bus,
pid_data->client,
"/org/freedesktop/Flatpak/SessionHelper",
"org.freedesktop.Flatpak.SessionHelper",
"HostCommandExited",
signal_variant,
NULL);
/* This frees the pid_data, so be careful */
g_hash_table_remove (client_pid_data_hash, GUINT_TO_POINTER(client_pid));
}
typedef struct {
int from;
int to;
int final;
} FdMapEntry;
typedef struct {
FdMapEntry *fd_map;
int fd_map_len;
gboolean set_tty;
int tty;
} ChildSetupData;
static void
child_setup_func (gpointer user_data)
{
ChildSetupData *data = (ChildSetupData *)user_data;
FdMapEntry *fd_map = data->fd_map;
sigset_t set;
int i;
/* Unblock all signals */
sigemptyset (&set);
if (pthread_sigmask (SIG_SETMASK, &set, NULL) == -1)
{
g_error ("Failed to unblock signals when starting child");
return;
}
/* Reset the handlers for all signals to their defaults. */
for (i = 1; i < NSIG; i++)
{
if (i != SIGSTOP && i != SIGKILL)
signal (i, SIG_DFL);
}
for (i = 0; i < data->fd_map_len; i++)
{
if (fd_map[i].from != fd_map[i].to)
{
dup2 (fd_map[i].from, fd_map[i].to);
close (fd_map[i].from);
}
}
/* Second pass in case we needed an inbetween fd value to avoid conflicts */
for (i = 0; i < data->fd_map_len; i++)
{
if (fd_map[i].to != fd_map[i].final)
{
dup2 (fd_map[i].to, fd_map[i].final);
close (fd_map[i].to);
}
}
/* We become our own session and process group, because it never makes sense
to share the flatpak-session-helper dbus activated process group */
setsid ();
setpgid (0, 0);
if (data->set_tty)
ioctl (data->tty, TIOCSCTTY, 0);
}
static gboolean
handle_host_command (FlatpakSessionHelper *object,
GDBusMethodInvocation *invocation,
const gchar *arg_cwd_path,
const gchar *const *arg_argv,
GVariant *arg_fds,
GVariant *arg_envs,
guint flags)
{
g_autoptr(GError) error = NULL;
GDBusMessage *message = g_dbus_method_invocation_get_message (invocation);
GUnixFDList *fd_list = g_dbus_message_get_unix_fd_list (message);
ChildSetupData child_setup_data = { NULL };
GPid pid;
PidData *pid_data;
gsize i, j, n_fds, n_envs;
const gint *fds;
g_autofree FdMapEntry *fd_map = NULL;
gchar **env;
gint32 max_fd;
if (*arg_cwd_path == 0)
arg_cwd_path = NULL;
if (*arg_argv[0] == 0)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_INVALID_ARGS,
"No command given");
return TRUE;
}
g_debug ("Running host command %s", arg_argv[0]);
n_fds = 0;
fds = NULL;
if (fd_list != NULL)
{
n_fds = g_variant_n_children (arg_fds);
fds = g_unix_fd_list_peek_fds (fd_list, NULL);
}
fd_map = g_new0 (FdMapEntry, n_fds);
child_setup_data.fd_map = fd_map;
child_setup_data.fd_map_len = n_fds;
max_fd = -1;
for (i = 0; i < n_fds; i++)
{
gint32 handle, fd;
g_variant_get_child (arg_fds, i, "{uh}", &fd, &handle);
fd_map[i].to = fd;
fd_map[i].from = fds[i];
fd_map[i].final = fd_map[i].to;
/* If stdin/out/err is a tty we try to set it as the controlling
tty for the app, this way we can use this to run in a terminal. */
if ((fd == 0 || fd == 1 || fd == 2) &&
!child_setup_data.set_tty &&
isatty (fds[i]))
{
child_setup_data.set_tty = TRUE;
child_setup_data.tty = fds[i];
}
max_fd = MAX (max_fd, fd_map[i].to);
max_fd = MAX (max_fd, fd_map[i].from);
}
/* We make a second pass over the fds to find if any "to" fd index
overlaps an already in use fd (i.e. one in the "from" category
that are allocated randomly). If a fd overlaps "to" fd then its
a caller issue and not our fault, so we ignore that. */
for (i = 0; i < n_fds; i++)
{
int to_fd = fd_map[i].to;
gboolean conflict = FALSE;
/* At this point we're fine with using "from" values for this
value (because we handle to==from in the code), or values
that are before "i" in the fd_map (because those will be
closed at this point when dup:ing). However, we can't
reuse a fd that is in "from" for j > i. */
for (j = i + 1; j < n_fds; j++)
{
int from_fd = fd_map[j].from;
if (from_fd == to_fd)
{
conflict = TRUE;
break;
}
}
if (conflict)
fd_map[i].to = ++max_fd;
}
if (flags & FLATPAK_HOST_COMMAND_FLAGS_CLEAR_ENV)
{
char *empty[] = { NULL };
env = g_strdupv (empty);
}
else
env = g_get_environ ();
n_envs = g_variant_n_children (arg_envs);
for (i = 0; i < n_envs; i++)
{
const char *var = NULL;
const char *val = NULL;
g_variant_get_child (arg_envs, i, "{&s&s}", &var, &val);
env = g_environ_setenv (env, var, val, TRUE);
}
if (!g_spawn_async_with_pipes (arg_cwd_path,
(char **)arg_argv,
env,
G_SPAWN_SEARCH_PATH|G_SPAWN_DO_NOT_REAP_CHILD,
child_setup_func, &child_setup_data,
&pid,
NULL,
NULL,
NULL,
&error))
{
gint code = G_DBUS_ERROR_FAILED;
if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_ACCES))
code = G_DBUS_ERROR_ACCESS_DENIED;
else if (g_error_matches (error, G_SPAWN_ERROR, G_SPAWN_ERROR_NOENT))
code = G_DBUS_ERROR_FILE_NOT_FOUND;
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, code,
"Failed to start command: %s",
error->message);
}
pid_data = g_new0 (PidData, 1);
pid_data->pid = pid;
pid_data->client_pid = ++next_client_pid;
pid_data->client = g_strdup (g_dbus_method_invocation_get_sender (invocation));
pid_data->child_watch = g_child_watch_add_full (G_PRIORITY_DEFAULT,
pid,
child_watch_died,
pid_data,
NULL);
g_debug ("Client Pid is %d (%d)", pid_data->client_pid, pid);
g_hash_table_replace (client_pid_data_hash, GUINT_TO_POINTER(pid_data->client_pid),
pid_data);
flatpak_session_helper_complete_host_command (object, invocation,
pid_data->client_pid);
return TRUE;
}
static gboolean
handle_host_command_signal (FlatpakSessionHelper *object,
GDBusMethodInvocation *invocation,
guint arg_pid,
guint arg_signal,
gboolean to_process_group)
{
PidData *pid_data = NULL;
pid_data = g_hash_table_lookup (client_pid_data_hash, GUINT_TO_POINTER(arg_pid));
if (pid_data == NULL ||
strcmp (pid_data->client, g_dbus_method_invocation_get_sender (invocation)) != 0)
{
g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR,
G_DBUS_ERROR_UNIX_PROCESS_ID_UNKNOWN,
"No such pid");
return TRUE;
}
g_debug ("Sending signal %d to client pid %d", arg_signal, arg_pid);
if (to_process_group)
killpg (pid_data->pid, arg_signal);
else
kill (pid_data->pid, arg_signal);
flatpak_session_helper_complete_host_command_signal (object, invocation);
return TRUE;
}
static void
on_bus_acquired (GDBusConnection *connection,
const gchar *name,
@@ -51,6 +349,8 @@ on_bus_acquired (GDBusConnection *connection,
helper = flatpak_session_helper_skeleton_new ();
g_signal_connect (helper, "handle-request-monitor", G_CALLBACK (handle_request_monitor), NULL);
g_signal_connect (helper, "handle-host-command", G_CALLBACK (handle_host_command), NULL);
g_signal_connect (helper, "handle-host-command-signal", G_CALLBACK (handle_host_command_signal), NULL);
if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (helper),
connection,
@@ -190,6 +490,15 @@ main (int argc,
flatpak_migrate_from_xdg_app ();
client_pid_data_hash = g_hash_table_new_full (NULL, NULL, NULL, (GDestroyNotify)pid_data_free);
session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
if (session_bus == NULL)
{
g_printerr ("Can't find bus: %s\n", error->message);
return 1;
}
monitor_dir = g_build_filename (g_get_user_runtime_dir (), "flatpak-monitor", NULL);
if (g_mkdir_with_parents (monitor_dir, 0755) != 0)
{