diff --git a/app/Makefile.am.inc b/app/Makefile.am.inc index 4b9c72a0..7b8839fc 100644 --- a/app/Makefile.am.inc +++ b/app/Makefile.am.inc @@ -16,6 +16,7 @@ xdg_app_SOURCES = \ app/xdg-app-builtins-uninstall.c \ app/xdg-app-builtins-list.c \ app/xdg-app-builtins-run.c \ + app/xdg-app-builtins-enter.c \ app/xdg-app-builtins-build-init.c \ app/xdg-app-builtins-build.c \ app/xdg-app-builtins-build-finish.c \ diff --git a/app/xdg-app-builtins-enter.c b/app/xdg-app-builtins-enter.c new file mode 100644 index 00000000..64cd9860 --- /dev/null +++ b/app/xdg-app-builtins-enter.c @@ -0,0 +1,288 @@ +/* + * Copyright © 2014 Red Hat, Inc + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: + * Alexander Larsson + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#include "libgsystem.h" +#include "libglnx/libglnx.h" + +#include "xdg-app-builtins.h" +#include "xdg-app-utils.h" +#include "xdg-app-dbus.h" +#include "xdg-app-run.h" + + +static GOptionEntry options[] = { + { NULL } +}; + +static gboolean +write_to_file (int fd, const char *content, ssize_t len) +{ + ssize_t res; + + while (len > 0) + { + res = write (fd, content, len); + if (res < 0 && errno == EINTR) + continue; + if (res <= 0) + return FALSE; + len -= res; + content += res; + } + + return TRUE; +} + +static gboolean +write_file (const char *path, const char *content) +{ + int fd; + gboolean res; + int errsv; + + fd = open (path, O_RDWR | O_CLOEXEC, 0); + if (fd == -1) + return FALSE; + + res = TRUE; + if (content) + res = write_to_file (fd, content, strlen (content)); + + errsv = errno; + close (fd); + errno = errsv; + + return res; +} + +static uid_t uid; +static gid_t gid; + +static void +child_setup (gpointer user_data) +{ +#ifndef DISABLE_USERNS + g_autofree char *uid_map = NULL; + g_autofree char *gid_map = NULL; + + /* Work around user namespace devpts issue by creating a new + userspace and map our uid like the helper does */ + + if (unshare (CLONE_NEWUSER)) + { + g_warning ("Can't unshare user namespace: %s", strerror (errno)); + return; + } + + uid_map = g_strdup_printf ("%d 0 1\n", uid); + if (!write_file ("/proc/self/uid_map", uid_map)) + g_warning ("setting up uid map"); + + gid_map = g_strdup_printf ("%d 0 1\n", gid); + if (!write_file ("/proc/self/gid_map", gid_map)) + g_warning ("setting up gid map"); + +#endif +} + +gboolean +xdg_app_builtin_enter (int argc, + char **argv, + GCancellable *cancellable, + GError **error) +{ + g_autoptr(GOptionContext) context = NULL; + int rest_argv_start, rest_argc; + g_autoptr(XdgAppContext) arg_context = NULL; + const char *ns_name[5] = { "user", "ipc", "net", "pid", "mnt" }; + int ns_fd[G_N_ELEMENTS(ns_name)]; + char pid_ns[256]; + ssize_t pid_ns_len; + char self_ns[256]; + ssize_t self_ns_len; + char *pid_s; + int pid, i; + g_autofree char *environment_path = NULL; + g_autoptr(GPtrArray) argv_array = NULL; + g_autoptr(GPtrArray) envp_array = NULL; + g_autofree char *environment = NULL; + gsize environment_len; + char *e; + g_autofree char *pulse_path = NULL; + g_autofree char *session_bus_path = NULL; + g_autofree char *xdg_runtime_dir = NULL; + int status; + + uid = getuid (); + gid = getgid (); + + context = g_option_context_new ("MONITORPID [COMMAND [args...]] - Run a command inside a running sandbox"); + + rest_argc = 0; + for (i = 1; i < argc; i++) + { + /* The non-option is the command, take it out of the arguments */ + if (argv[i][0] != '-') + { + rest_argv_start = i; + rest_argc = argc - i; + argc = i; + break; + } + } + + arg_context = xdg_app_context_new (); + g_option_context_add_group (context, xdg_app_context_get_options (arg_context)); + + if (!xdg_app_option_context_parse (context, options, &argc, &argv, XDG_APP_BUILTIN_FLAG_NO_DIR, NULL, cancellable, error)) + return FALSE; + + if (rest_argc < 2) + { + usage_error (context, "MONITORPID and COMMAND must be specified", error); + return FALSE; + } + + pid_s = argv[rest_argv_start]; + + pid = atoi (pid_s); + if (pid <= 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid pid %s\n", pid_s); + return FALSE; + } + + environment_path = g_strdup_printf ("/proc/%d/environ", pid); + if (!g_file_get_contents (environment_path, &environment, &environment_len, error)) + return FALSE; + + for (i = 0; i < G_N_ELEMENTS(ns_name); i++) + { + g_autofree char *path = g_strdup_printf ("/proc/%d/ns/%s", pid, ns_name[i]); + g_autofree char *self_path = g_strdup_printf ("/proc/self/ns/%s", ns_name[i]); + + pid_ns_len = readlink (path, pid_ns, sizeof (pid_ns) - 1); + if (pid_ns_len <= 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid %s namespace for pid %d\n", ns_name[i], pid); + return FALSE; + } + pid_ns[pid_ns_len] = 0; + + self_ns_len = readlink (self_path, self_ns, sizeof (self_ns) - 1); + if (self_ns_len <= 0) + if (pid_ns_len <= 0) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Invalid %s namespace for self\n", ns_name[i]); + return FALSE; + } + self_ns[self_ns_len] = 0; + + if (strcmp (self_ns, pid_ns) == 0) + { + /* No need to setns to the same namespace, it will only fail */ + ns_fd[i] = -1; + } + else + { + ns_fd[i] = open (path, O_RDONLY); + if (ns_fd[i] == -1) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't open %s namespace: %s", ns_name[i], strerror (errno)); + return FALSE; + } + } + } + + for (i = 0; i < G_N_ELEMENTS(ns_fd); i++) + { + if (ns_fd[i] != -1) + { + if (setns (ns_fd[i], 0) == -1) + { + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "Can't enter %s namespace: %s", ns_name[i], strerror (errno)); + return FALSE; + } + close (ns_fd[i]); + } + } + + envp_array = g_ptr_array_new_with_free_func (g_free); + for (e = environment; e < environment + environment_len; e = e + strlen (e) + 1) + { + if (*e != 0 && + !g_str_has_prefix (e, "DISPLAY=") && + !g_str_has_prefix (e, "PULSE_SERVER=") && + !g_str_has_prefix (e, "PULSE_CLIENTCONFIG=") && + !g_str_has_prefix (e, "XDG_RUNTIME_DIR=") && + !g_str_has_prefix (e, "DBUS_SYSTEM_BUS_ADDRESS=") && + !g_str_has_prefix (e, "DBUS_SESSION_BUS_ADDRESS=")) + { + if (g_str_has_prefix (e, "_LD_LIBRARY_PATH=")) + e++; + g_ptr_array_add (envp_array, g_strdup (e)); + } + } + + xdg_runtime_dir = g_strdup_printf ("/run/user/%d", uid); + g_ptr_array_add (envp_array, g_strdup_printf ("XDG_RUNTIME_DIR=%s", xdg_runtime_dir)); + + if (g_file_test ("/tmp/.X11-unix/X99", G_FILE_TEST_EXISTS)) + g_ptr_array_add (envp_array, g_strdup ("DISPLAY=:99.0")); + + pulse_path = g_strdup_printf ("/run/user/%d/pulse/native", uid); + if (g_file_test (pulse_path, G_FILE_TEST_EXISTS)) + { + g_ptr_array_add (envp_array, g_strdup_printf ("PULSE_SERVER=unix:%s", pulse_path)); + g_ptr_array_add (envp_array, g_strdup_printf ("PULSE_CLIENTCONFIG=/run/user/%d/pulse/config", uid)); + } + + session_bus_path = g_strdup_printf ("/run/user/%d/bus", uid); + if (g_file_test (session_bus_path, G_FILE_TEST_EXISTS)) + g_ptr_array_add (envp_array, g_strdup_printf ("DBUS_SESSION_BUS_ADDRESS=unix:%s", session_bus_path)); + + if (g_file_test ("/run/dbus/system_bus_socket", G_FILE_TEST_EXISTS)) + g_ptr_array_add (envp_array, g_strdup ("DBUS_SYSTEM_BUS_ADDRESS=unix:/run/dbus/system_bus_socket")); + + g_ptr_array_add (envp_array, NULL); + + argv_array = g_ptr_array_new_with_free_func (g_free); + for (i = 1; i < rest_argc; i++) + g_ptr_array_add (argv_array, g_strdup (argv[rest_argv_start + i])); + g_ptr_array_add (argv_array, NULL); + + if (!g_spawn_sync (NULL, (char **)argv_array->pdata, (char **)envp_array->pdata, + G_SPAWN_SEARCH_PATH_FROM_ENVP | G_SPAWN_CHILD_INHERITS_STDIN, + child_setup, NULL, + NULL, NULL, + &status, error)) + return FALSE; + + exit (status); +} diff --git a/app/xdg-app-builtins.h b/app/xdg-app-builtins.h index 5f446d58..b5198487 100644 --- a/app/xdg-app-builtins.h +++ b/app/xdg-app-builtins.h @@ -63,6 +63,7 @@ BUILTINPROTO(update_app); BUILTINPROTO(uninstall_app); BUILTINPROTO(list_apps); BUILTINPROTO(run); +BUILTINPROTO(enter); BUILTINPROTO(build_init); BUILTINPROTO(build); BUILTINPROTO(build_finish); diff --git a/app/xdg-app-main.c b/app/xdg-app-main.c index b9ac78a6..0bd50173 100644 --- a/app/xdg-app-main.c +++ b/app/xdg-app-main.c @@ -56,6 +56,7 @@ static XdgAppCommand commands[] = { { "uninstall-app", xdg_app_builtin_uninstall_app }, { "list-apps", xdg_app_builtin_list_apps }, { "run", xdg_app_builtin_run }, + { "enter", xdg_app_builtin_enter }, { "override", xdg_app_builtin_override }, { "export-file", xdg_app_builtin_export_file }, { "build-init", xdg_app_builtin_build_init }, diff --git a/completion/xdg-app b/completion/xdg-app index 6b9c9925..3adb211c 100755 --- a/completion/xdg-app +++ b/completion/xdg-app @@ -23,7 +23,7 @@ _xdg-app() { local dir cmd sdk loc local -A VERBS=( - [ALL]='add-remote modify-remote delete-remote ls-remote list-remotes install-runtime update-runtime uninstall-runtime list-runtimes install-app update-app uninstall-app list-apps run override export-file build-init build build-finish build-export repo-update make-app-current' + [ALL]='add-remote modify-remote delete-remote ls-remote list-remotes install-runtime update-runtime uninstall-runtime list-runtimes install-app update-app uninstall-app list-apps run override enter export-file build-init build build-finish build-export repo-update make-app-current' [MODE]='add-remote modify-remote delete-remote ls-remote list-remotes install-runtime update-runtime uninstall-runtime list-runtimes install-app update-app uninstall-app list-apps make-app-current' [PERMS]='run override build build-finish' [UNINSTALL]='uninstall-runtime uninstall-app' diff --git a/doc/Makefile.am b/doc/Makefile.am index b7549764..a4519120 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -30,6 +30,7 @@ man_MANS = \ xdg-app-list-apps.1 \ xdg-app-run.1 \ xdg-app-override.1 \ + xdg-app-enter.1 \ xdg-app-export-file.1 \ xdg-app-build-init.1 \ xdg-app-build.1 \ diff --git a/doc/xdg-app-enter.xml b/doc/xdg-app-enter.xml new file mode 100644 index 00000000..e0f6a129 --- /dev/null +++ b/doc/xdg-app-enter.xml @@ -0,0 +1,110 @@ + + + + + + + xdg-app enter + xdg-app + + + + Developer + Alexander + Larsson + alexl@redhat.com + + + + + + xdg-app enter + 1 + + + + xdg-app-enter + Enter an application + + + + + xdg-app enter + OPTION + MONITORPID + COMMAND + ARG + + + + + Description + + + Enter a running sandbox. + MONITORPID must be the pid of the monitor process for a running sandbox. + COMMAND is the command to run in the sandbox. + Extra arguments are passed on to the command. + + + This creates a new process within the running sandbox, with the same environment. This is useful + when you want to debug a problem with a running application. + + + + + + Options + + The following options are understood: + + + + + + + + Show help options and exit. + + + + + + + + + Print debug information during command processing. + + + + + + + + Print version information and exit. + + + + + + + Examples + + + $ xdg-app enter 15345 sh + + + + + + See also + + + xdg-app1 + xdg-app-run1 + + + + + diff --git a/doc/xdg-app-run.xml b/doc/xdg-app-run.xml index 1d92743f..53f17562 100644 --- a/doc/xdg-app-run.xml +++ b/doc/xdg-app-run.xml @@ -274,6 +274,7 @@ xdg-app1 xdg-app-override1 + xdg-app-enter1 diff --git a/lib/xdg-app-helper.c b/lib/xdg-app-helper.c index 18ef2482..3cc58054 100644 --- a/lib/xdg-app-helper.c +++ b/lib/xdg-app-helper.c @@ -2428,29 +2428,6 @@ main (int argc, free (tz_val); } -#ifndef DISABLE_USERNS - { - char *uid_map, *gid_map; - /* Now that devpts is mounted we can create a new userspace and map - our uid 1:1 */ - if (unshare (CLONE_NEWUSER)) - die_with_error ("unshare user ns"); - - uid_map = strdup_printf ("%d 0 1\n", uid); - if (!write_file ("/proc/self/uid_map", uid_map)) - die_with_error ("setting up uid map"); - free (uid_map); - - gid_map = strdup_printf ("%d 0 1\n", gid); - if (!write_file ("/proc/self/gid_map", gid_map)) - die_with_error ("setting up gid map"); - free (gid_map); - } -#endif - - __debug__(("setting up seccomp\n")); - setup_seccomp (devel); - __debug__(("forking for child\n")); pid = fork (); @@ -2461,6 +2438,30 @@ main (int argc, { __debug__(("launch executable %s\n", args[0])); +#ifndef DISABLE_USERNS + { + char *uid_map, *gid_map; + /* Now that devpts is mounted we can create a new userspace and map + our uid 1:1 */ + + if (unshare (CLONE_NEWUSER)) + die_with_error ("unshare user ns"); + + uid_map = strdup_printf ("%d 0 1\n", uid); + if (!write_file ("/proc/self/uid_map", uid_map)) + die_with_error ("setting up uid map"); + free (uid_map); + + gid_map = strdup_printf ("%d 0 1\n", gid); + if (!write_file ("/proc/self/gid_map", gid_map)) + die_with_error ("setting up gid map"); + free (gid_map); + } +#endif + + __debug__(("setting up seccomp in child\n")); + setup_seccomp (devel); + if (sync_fd != -1) close (sync_fd); @@ -2469,6 +2470,9 @@ main (int argc, return 0; } + __debug__(("setting up seccomp in monitor\n")); + setup_seccomp (devel); + /* Close all extra fds in pid 1. Any passed in fds have been passed on to the child anyway. */ {