diff --git a/pressure-vessel/flatpak-run-private.h b/pressure-vessel/flatpak-run-private.h index cfe8492dc8585bbcc11802c9b8b5dfa7156938a7..b9a7ac2ec2d9539d8e7c8ce9a5df4668e404ca2f 100644 --- a/pressure-vessel/flatpak-run-private.h +++ b/pressure-vessel/flatpak-run-private.h @@ -52,4 +52,8 @@ GFile *flatpak_get_data_dir (const char *app_id); void flatpak_run_add_font_path_args (FlatpakBwrap *bwrap); extern const char * const *flatpak_abs_usrmerged_dirs; + +int open_namespace_fd_if_needed (const char *path, + const char *other_path); + #endif /* __FLATPAK_RUN_H__ */ diff --git a/pressure-vessel/flatpak-run.c b/pressure-vessel/flatpak-run.c index a096361f6f66d5e12f76d50f943266933e6a1e2d..5b244f72528ad0bfa9367ab28109d84c02616387 100644 --- a/pressure-vessel/flatpak-run.c +++ b/pressure-vessel/flatpak-run.c @@ -3418,8 +3418,9 @@ check_parental_controls (const char *app_ref, return TRUE; } +#endif -static int +int open_namespace_fd_if_needed (const char *path, const char *other_path) { struct stat s, other_s; @@ -3438,6 +3439,7 @@ open_namespace_fd_if_needed (const char *path, return -1; } +#if 0 static gboolean check_sudo (GError **error) { diff --git a/pressure-vessel/launch.1.md b/pressure-vessel/launch.1.md index d3322f551f6afb877eb1de231184d8991c698872..7baef52445b9a659f643e27a26a93fca56afa622 100644 --- a/pressure-vessel/launch.1.md +++ b/pressure-vessel/launch.1.md @@ -60,9 +60,7 @@ as a subprocess of **pressure-vessel-launcher**. owned by the **flatpak-session-helper** per-user service, and the *COMMAND* is launched on the host system, similar to the **--host** option of **flatpak-spawn**(1). - The **--terminate** option is not allowed in this mode, and most - options that would unset environment variables, such as **--unset-env**, - are ignored with a warning (but **--clear-env** is still possible). + The **--terminate** option is not allowed in this mode. As another special case, if the *NAME* is **org.freedesktop.portal.Flatpak**, then it is assumed to be @@ -72,8 +70,7 @@ as a subprocess of **pressure-vessel-launcher**. Options that alter how the sub-sandbox is created, such as **--sandbox-flag**, are not currently supported. As with **org.freedesktop.Flatpak**, the - **--terminate** option is not allowed in this mode, and most - options that would unset environment variables are ignored with a warning. + **--terminate** option is not allowed in this mode. **--clear-env** : The *COMMAND* runs in an empty environment, apart from any environment diff --git a/pressure-vessel/launch.c b/pressure-vessel/launch.c index d000bc32eb3e0ba462084df750fe68b5eed3a902..cc9023917f82596d312a1ca997c148e7cad31ec7 100644 --- a/pressure-vessel/launch.c +++ b/pressure-vessel/launch.c @@ -534,6 +534,7 @@ main (int argc, g_auto(GStrv) original_environ = NULL; g_autoptr(GMainLoop) loop = NULL; g_autoptr(GOptionContext) context = NULL; + g_autoptr(GPtrArray) replacement_command_and_args = NULL; g_autoptr(GError) local_error = NULL; GError **error = &local_error; char **command_and_args; @@ -865,14 +866,14 @@ main (int argc, if (g_hash_table_size (opt_unsetenv) > 0) { - GVariantBuilder strv_builder; - - g_variant_builder_init (&strv_builder, G_VARIANT_TYPE_STRING_ARRAY); - g_hash_table_iter_init (&iter, opt_unsetenv); if (api == &launcher_api) { + GVariantBuilder strv_builder; + + g_variant_builder_init (&strv_builder, G_VARIANT_TYPE_STRING_ARRAY); + while (g_hash_table_iter_next (&iter, &key, NULL)) g_variant_builder_add (&strv_builder, "s", key); @@ -881,9 +882,29 @@ main (int argc, } else { + replacement_command_and_args = g_ptr_array_new_with_free_func (g_free); + + g_ptr_array_add (replacement_command_and_args, g_strdup ("/usr/bin/env")); + while (g_hash_table_iter_next (&iter, &key, NULL)) - g_warning ("Cannot unset %s when using Flatpak services", - (const char *) key); + { + g_ptr_array_add (replacement_command_and_args, g_strdup ("-u")); + g_ptr_array_add (replacement_command_and_args, g_strdup (key)); + } + + if (strchr (command_and_args[0], '=') != NULL) + { + g_ptr_array_add (replacement_command_and_args, g_strdup ("/bin/sh")); + g_ptr_array_add (replacement_command_and_args, g_strdup ("-euc")); + g_ptr_array_add (replacement_command_and_args, g_strdup ("exec \"$@\"")); + g_ptr_array_add (replacement_command_and_args, g_strdup ("sh")); /* argv[0] */ + } + + for (i = 0; command_and_args[i] != NULL; i++) + g_ptr_array_add (replacement_command_and_args, g_strdup (command_and_args[i])); + + g_ptr_array_add (replacement_command_and_args, NULL); + command_and_args = (char **) replacement_command_and_args->pdata; } } diff --git a/pressure-vessel/utils.c b/pressure-vessel/utils.c index 7834bcd79d3a84e99f47c472cd7f3098b60e15ed..c1614207d561882d9911984e5db2943bcedd88be 100644 --- a/pressure-vessel/utils.c +++ b/pressure-vessel/utils.c @@ -795,6 +795,50 @@ pv_terminate_all_child_processes (GTimeSpan wait_period, return TRUE; } +/* + * @str: A path + * @prefix: A possible prefix + * + * The same as flatpak_has_path_prefix(), but instead of a boolean, + * return the part of @str after @prefix (non-%NULL but possibly empty) + * if @str has prefix @prefix, or %NULL if it does not. + * + * Returns: (nullable) (transfer none): the part of @str after @prefix, + * or %NULL if @str is not below @prefix + */ +const char * +pv_get_path_after (const char *str, + const char *prefix) +{ + while (TRUE) + { + /* Skip consecutive slashes to reach next path + element */ + while (*str == '/') + str++; + while (*prefix == '/') + prefix++; + + /* No more prefix path elements? Done! */ + if (*prefix == 0) + return str; + + /* Compare path element */ + while (*prefix != 0 && *prefix != '/') + { + if (*str != *prefix) + return NULL; + str++; + prefix++; + } + + /* Matched prefix path element, + must be entire str path element */ + if (*str != '/' && *str != 0) + return NULL; + } +} + /** * pv_current_namespace_path_to_host_path: * @current_env_path: a path in the current environment @@ -807,40 +851,32 @@ gchar * pv_current_namespace_path_to_host_path (const gchar *current_env_path) { gchar *path_on_host = NULL; - g_autofree gchar *home_env_guarded = NULL; - const gchar *home_env = g_getenv ("HOME"); g_return_val_if_fail (g_path_is_absolute (current_env_path), g_strdup (current_env_path)); - if (home_env == NULL) - home_env = g_get_home_dir (); - - if (home_env != NULL) - { - /* Avoid the edge case where e.g. current_env_path is - * '/home/melanie/Games' and home_env is '/home/me' */ - if (g_str_has_suffix (home_env, "/")) - home_env_guarded = g_strdup (home_env); - else - home_env_guarded = g_strdup_printf ("%s/", home_env); - } - if (g_file_test ("/.flatpak-info", G_FILE_TEST_IS_REGULAR)) { struct stat via_current_env_stat; struct stat via_persist_stat; + const gchar *home = g_getenv ("HOME"); + const gchar *after = NULL; + + if (home == NULL) + home = g_get_home_dir (); + + if (home != NULL) + after = pv_get_path_after (current_env_path, home); /* If we are inside a Flatpak container, usually, the home * folder is '${HOME}/.var/app/${FLATPAK_ID}' on the host system */ - if (home_env != NULL - && g_str_has_prefix (current_env_path, home_env_guarded)) + if (after != NULL) { - path_on_host = g_build_filename (home_env, + path_on_host = g_build_filename (home, ".var", "app", g_getenv ("FLATPAK_ID"), - current_env_path + strlen (home_env), + after, NULL); if (lstat (path_on_host, &via_persist_stat) < 0) @@ -860,12 +896,12 @@ pv_current_namespace_path_to_host_path (const gchar *current_env_path) } } + after = pv_get_path_after (current_env_path, "/run/host"); + /* In a Flatpak container, usually, '/run/host' is the root of the * host system */ - if (g_str_has_prefix (current_env_path, "/run/host/")) - path_on_host = g_strdup (current_env_path + strlen ("/run/host")); - else if (g_strcmp0 (current_env_path, "/run/host") == 0) - path_on_host = g_strdup ("/"); + if (after != NULL && path_on_host == NULL) + path_on_host = g_build_filename ("/", after, NULL); } /* Either we are not in a Flatpak container or it's not obvious how the * container to host translation should happen. Just keep the same path. */ diff --git a/pressure-vessel/utils.h b/pressure-vessel/utils.h index 46fa32ecfccaf62cff4329ee54111e94a4c976b4..266deebf35b9269b8800b3ecc68717c1f49dbce1 100644 --- a/pressure-vessel/utils.h +++ b/pressure-vessel/utils.h @@ -70,3 +70,6 @@ gboolean pv_terminate_all_child_processes (GTimeSpan wait_period, GError **error); gchar *pv_current_namespace_path_to_host_path (const gchar *current_env_path); + +const char *pv_get_path_after (const char *str, + const char *prefix); diff --git a/pressure-vessel/wrap.1.md b/pressure-vessel/wrap.1.md index 8dad6766e34dca1bcb99b4558b7a5f696b3a109a..417419914903a722a589637487c57a7da031f00b 100644 --- a/pressure-vessel/wrap.1.md +++ b/pressure-vessel/wrap.1.md @@ -58,12 +58,6 @@ pressure-vessel-wrap - run programs in a bubblewrap container `--home` *DIR* : Use *DIR* as the home directory. This implies `--unshare-home`. -`--host-fallback` -: If **bwrap**(1) cannot be run, attempt to run *COMMAND* in the - current execution environment using the `LD_LIBRARY_PATH` - Steam Runtime instead. In practice this will only work for - Steam Runtime version 1, "scout". - `--host-ld-preload` *MODULE* : Add *MODULE* from the host system to `LD_PRELOAD` when executing *COMMAND*. If *COMMAND* is run in a container, the path of the diff --git a/pressure-vessel/wrap.c b/pressure-vessel/wrap.c index 0662960e2ef5fb4bd25bb136cdc4835fea00fd9c..b9709f86171fe0a267b622e33d5997426d89303a 100644 --- a/pressure-vessel/wrap.c +++ b/pressure-vessel/wrap.c @@ -206,35 +206,26 @@ check_bwrap (const char *tools_dir, return NULL; } -static gchar * -check_flatpak_spawn (void) +static gboolean +check_launch_on_host (const char *launch_executable, + GError **error) { - g_autoptr(GError) local_error = NULL; - GError **error = &local_error; - g_autofree gchar *spawn_exec = NULL; g_autofree gchar *child_stdout = NULL; g_autofree gchar *child_stderr = NULL; int wait_status; - const char *spawn_test_argv[] = + const char *test_argv[] = { NULL, - "--host", - "--directory=/", + "--bus-name=org.freedesktop.Flatpak", + "--", "true", NULL }; - /* All known Flatpak runtimes have flatpak-spawn in the PATH */ - spawn_exec = g_find_program_in_path ("flatpak-spawn"); - - if (spawn_exec == NULL - || !g_file_test (spawn_exec, G_FILE_TEST_IS_EXECUTABLE)) - return NULL; - - spawn_test_argv[0] = spawn_exec; + test_argv[0] = launch_executable; if (!g_spawn_sync (NULL, /* cwd */ - (gchar **) spawn_test_argv, + (gchar **) test_argv, NULL, /* environ */ G_SPAWN_DEFAULT, NULL, NULL, /* child setup */ @@ -243,25 +234,24 @@ check_flatpak_spawn (void) &wait_status, error)) { - g_warning ("Cannot run flatpak-spawn: %s", local_error->message); - g_clear_error (&local_error); + return FALSE; } - else if (wait_status != 0) + + if (wait_status != 0) { - g_warning ("Cannot run flatpak-spawn: wait status %d", wait_status); + g_warning ("Cannot run commands on host system: wait status %d", + wait_status); if (child_stdout != NULL && child_stdout[0] != '\0') g_warning ("Output:\n%s", child_stdout); if (child_stderr != NULL && child_stderr[0] != '\0') g_warning ("Diagnostic output:\n%s", child_stderr); - } - else - { - return g_steal_pointer (&spawn_exec); + + return glnx_throw (error, "Unable to run a command on the host system"); } - return NULL; + return TRUE; } /* @@ -723,7 +713,6 @@ static char *opt_steam_app_id = NULL; static gboolean opt_gc_runtimes = TRUE; static gboolean opt_generate_locales = TRUE; static char *opt_home = NULL; -static gboolean opt_host_fallback = FALSE; static char *opt_graphics_provider = NULL; static char *graphics_provider_mount_point = NULL; static gboolean opt_launcher = FALSE; @@ -1021,9 +1010,6 @@ static GOptionEntry options[] = G_OPTION_FLAG_NONE, G_OPTION_ARG_FILENAME, &opt_home, "Use HOME as home directory. Implies --unshare-home. " "[Default: $PRESSURE_VESSEL_HOME if set]", "HOME" }, - { "host-fallback", '\0', - G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &opt_host_fallback, - "Run COMMAND on the host system if we cannot run it in a container.", NULL }, { "host-ld-preload", '\0', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, &opt_host_ld_preload_cb, "Add MODULE from the host system to LD_PRELOAD when executing COMMAND.", @@ -1231,7 +1217,7 @@ main (int argc, g_autoptr(FlatpakBwrap) adverb_args = NULL; g_autofree gchar *adverb_in_container = NULL; g_autoptr(FlatpakBwrap) wrapped_command = NULL; - g_autofree gchar *spawn_executable = NULL; + g_autofree gchar *launch_executable = NULL; g_autofree gchar *bwrap_executable = NULL; g_autoptr(GString) adjusted_ld_preload = g_string_new (""); g_autofree gchar *cwd_p = NULL; @@ -1255,6 +1241,7 @@ main (int argc, g_str_equal, g_free, NULL); g_autofree char *lock_env_fd = NULL; + g_autoptr(GArray) pass_fds_through_adverb = g_array_new (FALSE, FALSE, sizeof (int)); my_pid = getpid (); @@ -1625,76 +1612,33 @@ main (int argc, /* If we are in a Flatpak environment we can't use bwrap directly */ if (is_flatpak_env) { - g_debug ("Checking for flatpak-spawn..."); - spawn_executable = check_flatpak_spawn (); + launch_executable = g_build_filename (tools_dir, + "pressure-vessel-launch", + NULL); /* Assume "bwrap" to exist in the host system and to be in its PATH */ bwrap_executable = g_strdup ("bwrap"); + + /* If we can't launch a command on the host, just fail. */ + if (!check_launch_on_host (launch_executable, error)) + goto out; } else { g_debug ("Checking for bwrap..."); + + /* if this fails, it will warn */ bwrap_executable = check_bwrap (tools_dir, opt_only_prepare); - } - if (opt_test) - { - if ((is_flatpak_env && spawn_executable == NULL) - || bwrap_executable == NULL) - { - ret = 1; - goto out; - } - else - { - if (spawn_executable != NULL) - g_debug ("OK (%s) (%s)", spawn_executable, bwrap_executable); - else - g_debug ("OK (%s)", bwrap_executable); - ret = 0; - goto out; - } + if (bwrap_executable == NULL) + goto out; + + g_debug ("OK (%s)", bwrap_executable); } - if ((is_flatpak_env && spawn_executable == NULL) - || bwrap_executable == NULL) + if (opt_test) { - /* TODO in a Flatpak environment, host is not what we expect it to be */ - if (opt_host_fallback) - { - g_message ("Falling back to executing wrapped command directly"); - - if (opt_env_if_host != NULL) - { - for (i = 0; opt_env_if_host[i] != NULL; i++) - { - char *equals = strchr (opt_env_if_host[i], '='); - - g_assert (equals != NULL); - - *equals = '\0'; - flatpak_bwrap_set_env (wrapped_command, opt_env_if_host[i], - equals + 1, TRUE); - } - } - - flatpak_bwrap_finish (wrapped_command); - - /* flatpak_bwrap_finish did this */ - g_assert (g_ptr_array_index (wrapped_command->argv, - wrapped_command->argv->len - 1) == NULL); - - execvpe (g_ptr_array_index (wrapped_command->argv, 0), - (char * const *) wrapped_command->argv->pdata, - wrapped_command->envp); - - glnx_throw_errno_prefix (error, "execvpe %s", - (gchar *) g_ptr_array_index (wrapped_command->argv, 0)); - goto out; - } - else - { - goto out; - } + ret = 0; + goto out; } if (!is_flatpak_env) @@ -2090,8 +2034,9 @@ main (int argc, g_hash_table_add (extra_locked_vars_to_inherit, g_strdup ("XDG_RUNTIME_DIR")); /* The bwrap envp will be completely ignored when calling - * `flatpak-spawn --host`. For this reason we convert them to - * `--setenv`. */ + * pv-launch. For this reason we convert them to `--setenv`. + * (TODO: Now that we're using pv-launch instead of flatpak-spawn, + * we could use --pass-env) */ for (i = 0; bwrap->envp != NULL && bwrap->envp[i] != NULL; i++) { g_auto(GStrv) split = g_strsplit (bwrap->envp[i], "=", 2); @@ -2194,6 +2139,49 @@ main (int argc, lock_env_fd = g_strdup_printf ("%d", lock_env_tmpf.fd); flatpak_bwrap_add_fd (bwrap, glnx_steal_fd (&lock_env_tmpf.fd)); + if (is_flatpak_env) + { + int userns_fd, userns2_fd, pidns_fd; + + /* Tell the bwrap instance on the host to join the same user and pid + * namespaces as Steam in Flatpak. Otherwise, pid-based IPC between + * the Steam client and the game will fail. + * + * This is not expected to work if bwrap on the host is setuid, + * so it will not work for users of Debian, Arch linux-hardened, etc., + * but it's better than nothing. */ + userns_fd = open ("/run/.userns", O_RDONLY | O_CLOEXEC); + + if (userns_fd >= 0) + { + g_array_append_val (pass_fds_through_adverb, userns_fd); + flatpak_bwrap_add_args_data_fd (bwrap, "--userns", + glnx_steal_fd (&userns_fd), + NULL); + + userns2_fd = open_namespace_fd_if_needed ("/proc/self/ns/user", + "/run/.userns"); + + if (userns2_fd >= 0) + { + g_array_append_val (pass_fds_through_adverb, userns2_fd); + flatpak_bwrap_add_args_data_fd (bwrap, "--userns2", + glnx_steal_fd (&userns2_fd), + NULL); + } + } + + pidns_fd = open ("/proc/self/ns/pid", O_RDONLY | O_CLOEXEC); + + if (pidns_fd >= 0) + { + g_array_append_val (pass_fds_through_adverb, pidns_fd); + flatpak_bwrap_add_args_data_fd (bwrap, "--pidns", + glnx_steal_fd (&pidns_fd), + NULL); + } + } + if (opt_verbose) { g_message ("%s options before bundling:", bwrap_executable); @@ -2254,6 +2242,13 @@ main (int argc, } } + for (i = 0; i < pass_fds_through_adverb->len; i++) + { + int fd = g_array_index (pass_fds_through_adverb, int, i); + + flatpak_bwrap_add_arg_printf (bwrap, "--pass-fd=%d", fd); + } + flatpak_bwrap_add_arg_printf (bwrap, "--pass-fd=%s", lock_env_fd); switch (opt_shell) @@ -2325,24 +2320,26 @@ main (int argc, if (is_flatpak_env) { /* Just use the envp from @bwrap */ - g_autoptr(FlatpakBwrap) flatpak_spawn = flatpak_bwrap_new (flatpak_bwrap_empty_env); - flatpak_bwrap_add_arg (flatpak_spawn, spawn_executable); - flatpak_bwrap_add_arg (flatpak_spawn, "--host"); + g_autoptr(FlatpakBwrap) launch_on_host = flatpak_bwrap_new (flatpak_bwrap_empty_env); + flatpak_bwrap_add_arg (launch_on_host, launch_executable); + flatpak_bwrap_add_arg (launch_on_host, "--bus-name=org.freedesktop.Flatpak"); for (i = 0; i < bwrap->fds->len; i++) { g_autofree char *fd_str = g_strdup_printf ("--forward-fd=%d", g_array_index (bwrap->fds, int, i)); - flatpak_bwrap_add_arg (flatpak_spawn, fd_str); + flatpak_bwrap_add_arg (launch_on_host, fd_str); } - /* Change the current working directory where flatpak-spawn will run. + /* Change the current working directory where pv-launch will run bwrap. * Bwrap will then set its directory by itself. For this reason here * we just need a directory that it's known to exist. */ - flatpak_bwrap_add_arg (flatpak_spawn, "--directory=/"); + flatpak_bwrap_add_arg (launch_on_host, "--directory=/"); + + flatpak_bwrap_add_arg (launch_on_host, "--"); - flatpak_bwrap_append_bwrap (flatpak_spawn, bwrap); + flatpak_bwrap_append_bwrap (launch_on_host, bwrap); g_clear_pointer (&bwrap, flatpak_bwrap_free); - bwrap = g_steal_pointer (&flatpak_spawn); + bwrap = g_steal_pointer (&launch_on_host); } if (opt_verbose) diff --git a/tests/pressure-vessel/utils.c b/tests/pressure-vessel/utils.c index 8c123b760bbfd1c0b7536b1ab01029d7b29dc4dd..ed06bd7fba7aeef3a8a66a940bf54cfc914a3d3d 100644 --- a/tests/pressure-vessel/utils.c +++ b/tests/pressure-vessel/utils.c @@ -182,6 +182,46 @@ test_envp_cmp (Fixture *f, g_free (sort_this); } +static void +test_get_path_after (Fixture *f, + gconstpointer context) +{ + static const struct + { + const char *str; + const char *prefix; + const char *expected; + } tests[] = + { + { "/run/host/usr", "/run/host", "usr" }, + { "/run/host/usr", "/run/host/", "usr" }, + { "/run/host", "/run/host", "" }, + { "////run///host////usr", "//run//host", "usr" }, + { "////run///host////usr", "//run//host////", "usr" }, + { "/run/hostage", "/run/host", NULL }, + /* Any number of leading slashes is ignored, even zero */ + { "foo/bar", "/foo", "bar" }, + { "/foo/bar", "foo", "bar" }, + }; + gsize i; + + for (i = 0; i < G_N_ELEMENTS (tests); i++) + { + const char *str = tests[i].str; + const char *prefix = tests[i].prefix; + const char *expected = tests[i].expected; + + if (expected == NULL) + g_test_message ("%s should not have path prefix %s", + str, prefix); + else + g_test_message ("%s should have path prefix %s followed by %s", + str, prefix, expected); + + g_assert_cmpstr (pv_get_path_after (str, prefix), ==, expected); + } +} + static void test_search_path_append (Fixture *f, gconstpointer context) @@ -223,6 +263,8 @@ main (int argc, g_test_add ("/capture-output", Fixture, NULL, setup, test_capture_output, teardown); g_test_add ("/envp-cmp", Fixture, NULL, setup, test_envp_cmp, teardown); + g_test_add ("/get-path-after", Fixture, NULL, + setup, test_get_path_after, teardown); g_test_add ("/search-path-append", Fixture, NULL, setup, test_search_path_append, teardown);