Skip to content
Snippets Groups Projects
wrap.c 78.2 KiB
Newer Older
        }

      flatpak_run_add_pulseaudio_args (bwrap, extra_locked_vars_to_unset);
      flatpak_run_add_session_dbus_args (bwrap);
      flatpak_run_add_system_dbus_args (bwrap);
    }

  if (is_flatpak_env)
    {
      /* These are the environment variables that will be wrong, or useless,
       * in the new container that will be created. */
      g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("FLATPAK_ID"));
      g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("FLATPAK_SANDBOX_DIR"));
      g_hash_table_add (extra_locked_vars_to_unset, g_strdup ("LD_AUDIT"));

      /* These are the environment variables that might differ in the host
       * system. However from inside a container we are not able to know the
       * host's value. So we allow them to inherit the value from the host. */
      g_hash_table_add (extra_locked_vars_to_inherit, g_strdup ("DBUS_SESSION_BUS_ADDRESS"));
      g_hash_table_add (extra_locked_vars_to_inherit, g_strdup ("DBUS_SYSTEM_BUS_ADDRESS"));
      g_hash_table_add (extra_locked_vars_to_inherit, g_strdup ("DISPLAY"));
      g_hash_table_add (extra_locked_vars_to_inherit, g_strdup ("XDG_RUNTIME_DIR"));

      /* The bwrap envp will be completely ignored when calling
       * 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);

          g_assert (split != NULL);
          g_assert (split[0] != NULL);
          g_assert (split[1] != NULL);

          flatpak_bwrap_add_args (bwrap,
                                  "--setenv", split[0], split[1],
                                  NULL);
        }
    }

  for (i = 0; bwrap->envp != NULL && bwrap->envp[i] != NULL; i++)
    {
      /* List only the variables names and not their values */
      char *equals = strchr (bwrap->envp[i], '=');

      g_assert (equals != NULL);

      /* Never lock LD_PRELOAD, otherwise, for example, we might miss the
       * overlay renderer library that the Steam client adds to LD_PRELOAD */
      if (g_str_has_prefix (bwrap->envp[i], "LD_PRELOAD="))
        continue;

      *equals = '\0';

      g_string_append (lock_env, bwrap->envp[i]);

      *equals = '=';

      g_string_append_c (lock_env, '\0');
    }

  g_hash_table_iter_init (&iter, extra_locked_vars_to_unset);
  while (g_hash_table_iter_next (&iter, &k, NULL))
    {
      env_var = k;
      g_string_append (lock_env, env_var);
      g_string_append_c (lock_env, '\0');

      flatpak_bwrap_add_args (bwrap,
                              "--unsetenv", env_var,
                              NULL);
    }

  g_hash_table_iter_init (&iter, extra_locked_vars_to_inherit);
  while (g_hash_table_iter_next (&iter, &k, NULL))
    {
      /* To inherit the variable value, we just use the lock
       * mechanism without `--setenv` or '--unsetenv'. In this way, when we
       * execute `launcher` with `--host`, it will inherit the value from the
       * host system. */
      env_var = k;
      g_string_append (lock_env, env_var);
      g_string_append_c (lock_env, '\0');
    }

  /* Populate the bwrap environ with the variables that are not in the
   * `extra_locked_vars_to_unset` list or `extra_locked_vars_to_inherit` list,
   * and without overriding the values that are already present in
   * bwrap->envp.
   * We skip this if we are in a Flatpak environment, because in that case
   * we already used `--setenv` for all the variables that we care about and
   * the bwrap envp will be ignored anyway. */
  if (!is_flatpak_env)
    {
      for (i = 0; original_environ[i] != NULL; i++)
        {
          char *eq = strchr (original_environ[i], '=');
          if (eq)
            {
              g_autofree gchar *key = g_strndup (original_environ[i],
                                                 eq - original_environ[i]);
              if (!g_hash_table_contains (extra_locked_vars_to_unset, key)
                  && !g_hash_table_contains (extra_locked_vars_to_inherit, key))
                flatpak_bwrap_set_env (bwrap, key, eq + 1, FALSE);
            }
        }

      /* The setuid bwrap will filter out some of the environment variables,
       * so we have to go via --setenv */
      for (i = 0; unsecure_environment_variables[i] != NULL; i++)
        {
          const gchar *val = g_environ_getenv (bwrap->envp, unsecure_environment_variables[i]);
          if (val != NULL)
            {
              flatpak_bwrap_add_args (bwrap, "--setenv",
                                      unsecure_environment_variables[i], val, NULL);
            }
        }
    }

  if (!flatpak_buffer_to_sealed_memfd_or_tmpfile (&lock_env_tmpf, "lock-env",
                                                  lock_env->str, lock_env->len,
                                                  error))
    goto out;

  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);

      for (i = 0; i < bwrap->argv->len; i++)
        {
          g_autofree gchar *quoted = NULL;

          quoted = g_shell_quote (g_ptr_array_index (bwrap->argv, i));
          g_message ("\t%s", quoted);
        }
    }

  if (!opt_only_prepare)
    {
      if (!flatpak_bwrap_bundle_args (bwrap, 1, -1, FALSE, error))
        goto out;
    }
  adverb_args = flatpak_bwrap_new (flatpak_bwrap_empty_env);
  if (runtime != NULL)
    adverb_in_container = pv_runtime_get_adverb (runtime, adverb_args);

  if (opt_terminate_timeout >= 0.0)
    {
      if (opt_terminate_idle_timeout > 0.0)
        flatpak_bwrap_add_arg_printf (adverb_args,
                                      "--terminate-idle-timeout=%f",
                                      opt_terminate_idle_timeout);

      flatpak_bwrap_add_arg_printf (adverb_args,
                                    "--terminate-timeout=%f",
                                    opt_terminate_timeout);
    }

  /* If not using a runtime, the adverb in the container has the
   * same path as outside */
  if (adverb_in_container == NULL)
    adverb_in_container = g_build_filename (tools_dir,
                                            "pressure-vessel-adverb",
                                            NULL);
  flatpak_bwrap_add_args (bwrap,
                          adverb_in_container,
                          "--exit-with-parent",
                          "--subreaper",
                          NULL);
  if (opt_pass_fds != NULL)
    {
      for (i = 0; i < opt_pass_fds->len; i++)
        {
          int fd = g_array_index (opt_pass_fds, int, i);

          flatpak_bwrap_add_fd (bwrap, fd);
          flatpak_bwrap_add_arg_printf (bwrap, "--pass-fd=%d", fd);
        }
    }

  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)
    {
      case PV_SHELL_AFTER:
        flatpak_bwrap_add_arg (bwrap, "--shell=after");
        break;

      case PV_SHELL_FAIL:
        flatpak_bwrap_add_arg (bwrap, "--shell=fail");
        break;

      case PV_SHELL_INSTEAD:
        flatpak_bwrap_add_arg (bwrap, "--shell=instead");
        break;

      case PV_SHELL_NONE:
        flatpak_bwrap_add_arg (bwrap, "--shell=none");
        break;

      default:
        g_warn_if_reached ();
    }

  switch (opt_terminal)
    {
      case PV_TERMINAL_AUTO:
        flatpak_bwrap_add_arg (bwrap, "--terminal=auto");
        break;

      case PV_TERMINAL_NONE:
        flatpak_bwrap_add_arg (bwrap, "--terminal=none");
        break;

      case PV_TERMINAL_TTY:
        flatpak_bwrap_add_arg (bwrap, "--terminal=tty");
        break;

      case PV_TERMINAL_XTERM:
        flatpak_bwrap_add_arg (bwrap, "--terminal=xterm");
        break;

      default:
        g_warn_if_reached ();
        break;
    }

  if (opt_verbose)
    flatpak_bwrap_add_arg (bwrap, "--verbose");
  flatpak_bwrap_append_bwrap (bwrap, adverb_args);
  flatpak_bwrap_add_arg (bwrap, "--");
  if (opt_launcher)
    {
      g_autofree gchar *pressure_vessel_launcher = g_build_filename (tools_dir,
                                                                     "pressure-vessel-launcher",
                                                                     NULL);
      g_debug ("Adding pressure-vessel-launcher '%s'...", pressure_vessel_launcher);
      flatpak_bwrap_add_arg (bwrap, pressure_vessel_launcher);

      g_debug ("Adding locked environment variables...");
      flatpak_bwrap_add_args (bwrap, "--lock-env-from-fd", lock_env_fd, NULL);
  g_debug ("Adding wrapped command...");
  flatpak_bwrap_append_args (bwrap, wrapped_command->argv);
  if (is_flatpak_env)
    {
      /* Just use the envp from @bwrap */
      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 (launch_on_host, fd_str);
      /* 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 (launch_on_host, "--directory=/");

      flatpak_bwrap_add_arg (launch_on_host, "--");
      flatpak_bwrap_append_bwrap (launch_on_host, bwrap);
      g_clear_pointer (&bwrap, flatpak_bwrap_free);
      bwrap = g_steal_pointer (&launch_on_host);
      g_message ("Final %s options:", bwrap_executable);
      for (i = 0; i < bwrap->argv->len; i++)
        {
          g_autofree gchar *quoted = NULL;

          quoted = g_shell_quote (g_ptr_array_index (bwrap->argv, i));
          g_message ("\t%s", quoted);
        }

      g_message ("%s environment:", bwrap_executable);

      for (i = 0; bwrap->envp != NULL && bwrap->envp[i] != NULL; i++)
        {
          g_autofree gchar *quoted = NULL;

          quoted = g_shell_quote (bwrap->envp[i]);
          g_message ("\t%s", quoted);
        }
  /* Clean up temporary directory before running our long-running process */
  if (runtime != NULL)
    pv_runtime_cleanup (runtime);
  if (opt_write_bwrap != NULL)
    {
      FILE *file = fopen (opt_write_bwrap, "w");
      if (file == NULL)
        {
          g_warning ("An error occurred trying to write the bwrap arguments: %s",
                    g_strerror (errno));
          /* This is not a fatal error, try to continue */
        }
      else
        {
          for (i = 0; i < bwrap->argv->len; i++)
            fprintf (file, "%s%c", (gchar *)g_ptr_array_index (bwrap->argv, i), '\0');

          fclose (file);
        }
    }

  if (opt_only_prepare)
    ret = 0;
  else
    pv_bwrap_execve (bwrap, fileno (original_stdout), error);

out:
  if (local_error != NULL)
    g_warning ("%s", local_error->message);

  g_clear_pointer (&opt_ld_preload, g_ptr_array_unref);
  g_clear_pointer (&opt_env_if_host, g_strfreev);
  g_clear_pointer (&opt_freedesktop_app_id, g_free);
  g_clear_pointer (&opt_steam_app_id, g_free);
  g_clear_pointer (&opt_home, g_free);
  g_clear_pointer (&opt_fake_home, g_free);
  g_clear_pointer (&opt_runtime_base, g_free);
  g_clear_pointer (&opt_runtime, g_free);
  g_clear_pointer (&opt_pass_fds, g_array_unref);
  g_debug ("Exiting with status %d", ret);