mirror of
https://github.com/flatpak/flatpak.git
synced 2026-07-29 17:28:16 -04:00
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:
@@ -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,
|
||||
...);
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user