Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| /* | |
| * 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.1 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 <http://www.gnu.org/licenses/>. | |
| * | |
| * Authors: | |
| * Alexander Larsson <alexl@redhat.com> | |
| */ | |
| #include "config.h" | |
| #include <string.h> | |
| #include <fcntl.h> | |
| #include <stdio.h> | |
| #include <unistd.h> | |
| #include <sys/utsname.h> | |
| #include <sys/socket.h> | |
| #include <sys/ioctl.h> | |
| #include <sys/personality.h> | |
| #include <grp.h> | |
| #include <unistd.h> | |
| #include <gio/gunixfdlist.h> | |
| #ifdef ENABLE_SECCOMP | |
| #include <seccomp.h> | |
| #endif | |
| #ifdef ENABLE_XAUTH | |
| #include <X11/Xauth.h> | |
| #endif | |
| #include <glib/gi18n.h> | |
| #include <gio/gio.h> | |
| #include "libglnx/libglnx.h" | |
| #include "flatpak-run.h" | |
| #include "flatpak-proxy.h" | |
| #include "flatpak-utils.h" | |
| #include "flatpak-dir.h" | |
| #include "flatpak-systemd-dbus.h" | |
| #include "document-portal/xdp-dbus.h" | |
| #include "lib/flatpak-error.h" | |
| #define DEFAULT_SHELL "/bin/sh" | |
| typedef enum { | |
| FLATPAK_CONTEXT_SHARED_NETWORK = 1 << 0, | |
| FLATPAK_CONTEXT_SHARED_IPC = 1 << 1, | |
| } FlatpakContextShares; | |
| /* In numerical order of more privs */ | |
| typedef enum { | |
| FLATPAK_FILESYSTEM_MODE_READ_ONLY = 1, | |
| FLATPAK_FILESYSTEM_MODE_READ_WRITE = 2, | |
| FLATPAK_FILESYSTEM_MODE_CREATE = 3, | |
| } FlatpakFilesystemMode; | |
| /* Same order as enum */ | |
| const char *flatpak_context_shares[] = { | |
| "network", | |
| "ipc", | |
| NULL | |
| }; | |
| typedef enum { | |
| FLATPAK_CONTEXT_SOCKET_X11 = 1 << 0, | |
| FLATPAK_CONTEXT_SOCKET_WAYLAND = 1 << 1, | |
| FLATPAK_CONTEXT_SOCKET_PULSEAUDIO = 1 << 2, | |
| FLATPAK_CONTEXT_SOCKET_SESSION_BUS = 1 << 3, | |
| FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS = 1 << 4, | |
| } FlatpakContextSockets; | |
| /* Same order as enum */ | |
| const char *flatpak_context_sockets[] = { | |
| "x11", | |
| "wayland", | |
| "pulseaudio", | |
| "session-bus", | |
| "system-bus", | |
| NULL | |
| }; | |
| const char *dont_mount_in_root[] = { | |
| ".", "..", "lib", "lib32", "lib64", "bin", "sbin", "usr", "boot", "root", | |
| "tmp", "etc", "app", "run", "proc", "sys", "dev", "var", NULL | |
| }; | |
| /* We don't want to export paths pointing into these, because they are readonly | |
| (so we can't create mountpoints there) and don't match whats on the host anyway */ | |
| const char *dont_export_in[] = { | |
| "/lib", "/lib32", "/lib64", "/bin", "/sbin", "/usr", "/etc", "/app", "/dev", NULL | |
| }; | |
| typedef enum { | |
| FLATPAK_CONTEXT_DEVICE_DRI = 1 << 0, | |
| FLATPAK_CONTEXT_DEVICE_ALL = 1 << 1, | |
| FLATPAK_CONTEXT_DEVICE_KVM = 1 << 2, | |
| } FlatpakContextDevices; | |
| const char *flatpak_context_devices[] = { | |
| "dri", | |
| "all", | |
| "kvm", | |
| NULL | |
| }; | |
| typedef enum { | |
| FLATPAK_CONTEXT_FEATURE_DEVEL = 1 << 0, | |
| FLATPAK_CONTEXT_FEATURE_MULTIARCH = 1 << 1, | |
| } FlatpakContextFeatures; | |
| const char *flatpak_context_features[] = { | |
| "devel", | |
| "multiarch", | |
| NULL | |
| }; | |
| static gboolean | |
| add_dbus_proxy_args (GPtrArray *argv_array, | |
| GPtrArray *session_dbus_proxy_argv, | |
| gboolean enable_session_logging, | |
| GPtrArray *system_dbus_proxy_argv, | |
| gboolean enable_system_logging, | |
| GPtrArray *a11y_dbus_proxy_argv, | |
| gboolean enable_a11y_logging, | |
| int sync_fds[2], | |
| const char *app_info_path, | |
| GError **error); | |
| struct FlatpakContext | |
| { | |
| FlatpakContextShares shares; | |
| FlatpakContextShares shares_valid; | |
| FlatpakContextSockets sockets; | |
| FlatpakContextSockets sockets_valid; | |
| FlatpakContextDevices devices; | |
| FlatpakContextDevices devices_valid; | |
| FlatpakContextFeatures features; | |
| FlatpakContextFeatures features_valid; | |
| GHashTable *env_vars; | |
| GHashTable *persistent; | |
| GHashTable *filesystems; | |
| GHashTable *session_bus_policy; | |
| GHashTable *system_bus_policy; | |
| GHashTable *generic_policy; | |
| }; | |
| FlatpakContext * | |
| flatpak_context_new (void) | |
| { | |
| FlatpakContext *context; | |
| context = g_slice_new0 (FlatpakContext); | |
| context->env_vars = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); | |
| context->persistent = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); | |
| context->filesystems = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); | |
| context->session_bus_policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); | |
| context->system_bus_policy = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); | |
| context->generic_policy = g_hash_table_new_full (g_str_hash, g_str_equal, | |
| g_free, (GDestroyNotify)g_strfreev); | |
| return context; | |
| } | |
| void | |
| flatpak_context_free (FlatpakContext *context) | |
| { | |
| g_hash_table_destroy (context->env_vars); | |
| g_hash_table_destroy (context->persistent); | |
| g_hash_table_destroy (context->filesystems); | |
| g_hash_table_destroy (context->session_bus_policy); | |
| g_hash_table_destroy (context->system_bus_policy); | |
| g_hash_table_destroy (context->generic_policy); | |
| g_slice_free (FlatpakContext, context); | |
| } | |
| static guint32 | |
| flatpak_context_bitmask_from_string (const char *name, const char **names) | |
| { | |
| guint32 i; | |
| for (i = 0; names[i] != NULL; i++) | |
| { | |
| if (strcmp (names[i], name) == 0) | |
| return 1 << i; | |
| } | |
| return 0; | |
| } | |
| static char ** | |
| flatpak_context_bitmask_to_string (guint32 enabled, guint32 valid, const char **names) | |
| { | |
| guint32 i; | |
| GPtrArray *array; | |
| array = g_ptr_array_new (); | |
| for (i = 0; names[i] != NULL; i++) | |
| { | |
| guint32 bitmask = 1 << i; | |
| if (valid & bitmask) | |
| { | |
| if (enabled & bitmask) | |
| g_ptr_array_add (array, g_strdup (names[i])); | |
| else | |
| g_ptr_array_add (array, g_strdup_printf ("!%s", names[i])); | |
| } | |
| } | |
| g_ptr_array_add (array, NULL); | |
| return (char **) g_ptr_array_free (array, FALSE); | |
| } | |
| static void | |
| flatpak_context_bitmask_to_args (guint32 enabled, guint32 valid, const char **names, | |
| const char *enable_arg, const char *disable_arg, | |
| GPtrArray *args) | |
| { | |
| guint32 i; | |
| for (i = 0; names[i] != NULL; i++) | |
| { | |
| guint32 bitmask = 1 << i; | |
| if (valid & bitmask) | |
| { | |
| if (enabled & bitmask) | |
| g_ptr_array_add (args, g_strdup_printf ("%s=%s", enable_arg, names[i])); | |
| else | |
| g_ptr_array_add (args, g_strdup_printf ("%s=%s", disable_arg, names[i])); | |
| } | |
| } | |
| } | |
| static FlatpakContextShares | |
| flatpak_context_share_from_string (const char *string, GError **error) | |
| { | |
| FlatpakContextShares shares = flatpak_context_bitmask_from_string (string, flatpak_context_shares); | |
| if (shares == 0) | |
| { | |
| g_autofree char *values = g_strjoinv (", ", (char **)flatpak_context_shares); | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, | |
| _("Unknown share type %s, valid types are: %s"), string, values); | |
| } | |
| return shares; | |
| } | |
| static char ** | |
| flatpak_context_shared_to_string (FlatpakContextShares shares, FlatpakContextShares valid) | |
| { | |
| return flatpak_context_bitmask_to_string (shares, valid, flatpak_context_shares); | |
| } | |
| static void | |
| flatpak_context_shared_to_args (FlatpakContextShares shares, | |
| FlatpakContextShares valid, | |
| GPtrArray *args) | |
| { | |
| return flatpak_context_bitmask_to_args (shares, valid, flatpak_context_shares, "--share", "--unshare", args); | |
| } | |
| static FlatpakPolicy | |
| flatpak_policy_from_string (const char *string, GError **error) | |
| { | |
| const char *policies[] = { "none", "see", "filtered", "talk", "own", NULL }; | |
| int i; | |
| g_autofree char *values = NULL; | |
| for (i = 0; policies[i]; i++) | |
| { | |
| if (strcmp (string, policies[i]) == 0) | |
| return i; | |
| } | |
| values = g_strjoinv (", ", (char **)policies); | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, | |
| _("Unknown policy type %s, valid types are: %s"), string, values); | |
| return -1; | |
| } | |
| static const char * | |
| flatpak_policy_to_string (FlatpakPolicy policy) | |
| { | |
| if (policy == FLATPAK_POLICY_SEE) | |
| return "see"; | |
| if (policy == FLATPAK_POLICY_TALK) | |
| return "talk"; | |
| if (policy == FLATPAK_POLICY_OWN) | |
| return "own"; | |
| return "none"; | |
| } | |
| static gboolean | |
| flatpak_verify_dbus_name (const char *name, GError **error) | |
| { | |
| const char *name_part; | |
| g_autofree char *tmp = NULL; | |
| if (g_str_has_suffix (name, ".*")) | |
| { | |
| tmp = g_strndup (name, strlen (name) - 2); | |
| name_part = tmp; | |
| } | |
| else | |
| { | |
| name_part = name; | |
| } | |
| if (g_dbus_is_name (name_part) && !g_dbus_is_unique_name (name_part)) | |
| return TRUE; | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, | |
| _("Invalid dbus name %s\n"), name); | |
| return FALSE; | |
| } | |
| static FlatpakContextSockets | |
| flatpak_context_socket_from_string (const char *string, GError **error) | |
| { | |
| FlatpakContextSockets sockets = flatpak_context_bitmask_from_string (string, flatpak_context_sockets); | |
| if (sockets == 0) | |
| { | |
| g_autofree char *values = g_strjoinv (", ", (char **)flatpak_context_sockets); | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, | |
| _("Unknown socket type %s, valid types are: %s"), string, values); | |
| } | |
| return sockets; | |
| } | |
| static char ** | |
| flatpak_context_sockets_to_string (FlatpakContextSockets sockets, FlatpakContextSockets valid) | |
| { | |
| return flatpak_context_bitmask_to_string (sockets, valid, flatpak_context_sockets); | |
| } | |
| static void | |
| flatpak_context_sockets_to_args (FlatpakContextSockets sockets, | |
| FlatpakContextSockets valid, | |
| GPtrArray *args) | |
| { | |
| return flatpak_context_bitmask_to_args (sockets, valid, flatpak_context_sockets, "--socket", "--nosocket", args); | |
| } | |
| static FlatpakContextDevices | |
| flatpak_context_device_from_string (const char *string, GError **error) | |
| { | |
| FlatpakContextDevices devices = flatpak_context_bitmask_from_string (string, flatpak_context_devices); | |
| if (devices == 0) | |
| { | |
| g_autofree char *values = g_strjoinv (", ", (char **)flatpak_context_devices); | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, | |
| _("Unknown device type %s, valid types are: %s"), string, values); | |
| } | |
| return devices; | |
| } | |
| static char ** | |
| flatpak_context_devices_to_string (FlatpakContextDevices devices, FlatpakContextDevices valid) | |
| { | |
| return flatpak_context_bitmask_to_string (devices, valid, flatpak_context_devices); | |
| } | |
| static void | |
| flatpak_context_devices_to_args (FlatpakContextDevices devices, | |
| FlatpakContextDevices valid, | |
| GPtrArray *args) | |
| { | |
| return flatpak_context_bitmask_to_args (devices, valid, flatpak_context_devices, "--device", "--nodevice", args); | |
| } | |
| static FlatpakContextFeatures | |
| flatpak_context_feature_from_string (const char *string, GError **error) | |
| { | |
| FlatpakContextFeatures feature = flatpak_context_bitmask_from_string (string, flatpak_context_features); | |
| if (feature == 0) | |
| { | |
| g_autofree char *values = g_strjoinv (", ", (char **)flatpak_context_features); | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, | |
| _("Unknown feature type %s, valid types are: %s"), string, values); | |
| } | |
| return feature; | |
| } | |
| static char ** | |
| flatpak_context_features_to_string (FlatpakContextFeatures features, FlatpakContextFeatures valid) | |
| { | |
| return flatpak_context_bitmask_to_string (features, valid, flatpak_context_features); | |
| } | |
| static void | |
| flatpak_context_features_to_args (FlatpakContextFeatures features, | |
| FlatpakContextFeatures valid, | |
| GPtrArray *args) | |
| { | |
| return flatpak_context_bitmask_to_args (features, valid, flatpak_context_features, "--allow", "--disallow", args); | |
| } | |
| static void | |
| flatpak_context_add_shares (FlatpakContext *context, | |
| FlatpakContextShares shares) | |
| { | |
| context->shares_valid |= shares; | |
| context->shares |= shares; | |
| } | |
| static void | |
| flatpak_context_remove_shares (FlatpakContext *context, | |
| FlatpakContextShares shares) | |
| { | |
| context->shares_valid |= shares; | |
| context->shares &= ~shares; | |
| } | |
| static void | |
| flatpak_context_add_sockets (FlatpakContext *context, | |
| FlatpakContextSockets sockets) | |
| { | |
| context->sockets_valid |= sockets; | |
| context->sockets |= sockets; | |
| } | |
| static void | |
| flatpak_context_remove_sockets (FlatpakContext *context, | |
| FlatpakContextSockets sockets) | |
| { | |
| context->sockets_valid |= sockets; | |
| context->sockets &= ~sockets; | |
| } | |
| static void | |
| flatpak_context_add_devices (FlatpakContext *context, | |
| FlatpakContextDevices devices) | |
| { | |
| context->devices_valid |= devices; | |
| context->devices |= devices; | |
| } | |
| static void | |
| flatpak_context_remove_devices (FlatpakContext *context, | |
| FlatpakContextDevices devices) | |
| { | |
| context->devices_valid |= devices; | |
| context->devices &= ~devices; | |
| } | |
| static void | |
| flatpak_context_add_features (FlatpakContext *context, | |
| FlatpakContextFeatures features) | |
| { | |
| context->features_valid |= features; | |
| context->features |= features; | |
| } | |
| static void | |
| flatpak_context_remove_features (FlatpakContext *context, | |
| FlatpakContextFeatures features) | |
| { | |
| context->features_valid |= features; | |
| context->features &= ~features; | |
| } | |
| static void | |
| flatpak_context_set_env_var (FlatpakContext *context, | |
| const char *name, | |
| const char *value) | |
| { | |
| g_hash_table_insert (context->env_vars, g_strdup (name), g_strdup (value)); | |
| } | |
| void | |
| flatpak_context_set_session_bus_policy (FlatpakContext *context, | |
| const char *name, | |
| FlatpakPolicy policy) | |
| { | |
| g_hash_table_insert (context->session_bus_policy, g_strdup (name), GINT_TO_POINTER (policy)); | |
| } | |
| void | |
| flatpak_context_set_system_bus_policy (FlatpakContext *context, | |
| const char *name, | |
| FlatpakPolicy policy) | |
| { | |
| g_hash_table_insert (context->system_bus_policy, g_strdup (name), GINT_TO_POINTER (policy)); | |
| } | |
| static void | |
| flatpak_context_apply_generic_policy (FlatpakContext *context, | |
| const char *key, | |
| const char *value) | |
| { | |
| GPtrArray *new = g_ptr_array_new (); | |
| const char **old_v; | |
| int i; | |
| g_assert (strchr (key, '.') != NULL); | |
| old_v = g_hash_table_lookup (context->generic_policy, key); | |
| for (i = 0; old_v != NULL && old_v[i] != NULL; i++) | |
| { | |
| const char *old = old_v[i]; | |
| const char *cmp1 = old; | |
| const char *cmp2 = value; | |
| if (*cmp1 == '!') | |
| cmp1++; | |
| if (*cmp2 == '!') | |
| cmp2++; | |
| if (strcmp (cmp1, cmp2) != 0) | |
| g_ptr_array_add (new, g_strdup (old)); | |
| } | |
| g_ptr_array_add (new, g_strdup (value)); | |
| g_ptr_array_add (new, NULL); | |
| g_hash_table_insert (context->generic_policy, g_strdup (key), | |
| g_ptr_array_free (new, FALSE)); | |
| } | |
| static void | |
| flatpak_context_set_persistent (FlatpakContext *context, | |
| const char *path) | |
| { | |
| g_hash_table_insert (context->persistent, g_strdup (path), GINT_TO_POINTER (1)); | |
| } | |
| static gboolean | |
| get_xdg_dir_from_prefix (const char *prefix, | |
| const char **where, | |
| const char **dir) | |
| { | |
| if (strcmp (prefix, "xdg-data") == 0) | |
| { | |
| if (where) | |
| *where = "data"; | |
| if (dir) | |
| *dir = g_get_user_data_dir (); | |
| return TRUE; | |
| } | |
| if (strcmp (prefix, "xdg-cache") == 0) | |
| { | |
| if (where) | |
| *where = "cache"; | |
| if (dir) | |
| *dir = g_get_user_cache_dir (); | |
| return TRUE; | |
| } | |
| if (strcmp (prefix, "xdg-config") == 0) | |
| { | |
| if (where) | |
| *where = "config"; | |
| if (dir) | |
| *dir = g_get_user_config_dir (); | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| /* This looks only in the xdg dirs (config, cache, data), not the user | |
| definable ones */ | |
| static char * | |
| get_xdg_dir_from_string (const char *filesystem, | |
| const char **suffix, | |
| const char **where) | |
| { | |
| char *slash; | |
| const char *rest; | |
| g_autofree char *prefix = NULL; | |
| const char *dir = NULL; | |
| gsize len; | |
| slash = strchr (filesystem, '/'); | |
| if (slash) | |
| len = slash - filesystem; | |
| else | |
| len = strlen (filesystem); | |
| rest = filesystem + len; | |
| while (*rest == '/') | |
| rest++; | |
| if (suffix != NULL) | |
| *suffix = rest; | |
| prefix = g_strndup (filesystem, len); | |
| if (get_xdg_dir_from_prefix (prefix, where, &dir)) | |
| return g_build_filename (dir, rest, NULL); | |
| return NULL; | |
| } | |
| static gboolean | |
| get_xdg_user_dir_from_string (const char *filesystem, | |
| const char **config_key, | |
| const char **suffix, | |
| const char **dir) | |
| { | |
| char *slash; | |
| const char *rest; | |
| g_autofree char *prefix = NULL; | |
| gsize len; | |
| slash = strchr (filesystem, '/'); | |
| if (slash) | |
| len = slash - filesystem; | |
| else | |
| len = strlen (filesystem); | |
| rest = filesystem + len; | |
| while (*rest == '/') | |
| rest++; | |
| if (suffix) | |
| *suffix = rest; | |
| prefix = g_strndup (filesystem, len); | |
| if (strcmp (prefix, "xdg-desktop") == 0) | |
| { | |
| if (config_key) | |
| *config_key = "XDG_DESKTOP_DIR"; | |
| if (dir) | |
| *dir = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP); | |
| return TRUE; | |
| } | |
| if (strcmp (prefix, "xdg-documents") == 0) | |
| { | |
| if (config_key) | |
| *config_key = "XDG_DOCUMENTS_DIR"; | |
| if (dir) | |
| *dir = g_get_user_special_dir (G_USER_DIRECTORY_DOCUMENTS); | |
| return TRUE; | |
| } | |
| if (strcmp (prefix, "xdg-download") == 0) | |
| { | |
| if (config_key) | |
| *config_key = "XDG_DOWNLOAD_DIR"; | |
| if (dir) | |
| *dir = g_get_user_special_dir (G_USER_DIRECTORY_DOWNLOAD); | |
| return TRUE; | |
| } | |
| if (strcmp (prefix, "xdg-music") == 0) | |
| { | |
| if (config_key) | |
| *config_key = "XDG_MUSIC_DIR"; | |
| if (dir) | |
| *dir = g_get_user_special_dir (G_USER_DIRECTORY_MUSIC); | |
| return TRUE; | |
| } | |
| if (strcmp (prefix, "xdg-pictures") == 0) | |
| { | |
| if (config_key) | |
| *config_key = "XDG_PICTURES_DIR"; | |
| if (dir) | |
| *dir = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES); | |
| return TRUE; | |
| } | |
| if (strcmp (prefix, "xdg-public-share") == 0) | |
| { | |
| if (config_key) | |
| *config_key = "XDG_PUBLICSHARE_DIR"; | |
| if (dir) | |
| *dir = g_get_user_special_dir (G_USER_DIRECTORY_PUBLIC_SHARE); | |
| return TRUE; | |
| } | |
| if (strcmp (prefix, "xdg-templates") == 0) | |
| { | |
| if (config_key) | |
| *config_key = "XDG_TEMPLATES_DIR"; | |
| if (dir) | |
| *dir = g_get_user_special_dir (G_USER_DIRECTORY_TEMPLATES); | |
| return TRUE; | |
| } | |
| if (strcmp (prefix, "xdg-videos") == 0) | |
| { | |
| if (config_key) | |
| *config_key = "XDG_VIDEOS_DIR"; | |
| if (dir) | |
| *dir = g_get_user_special_dir (G_USER_DIRECTORY_VIDEOS); | |
| return TRUE; | |
| } | |
| if (get_xdg_dir_from_prefix (prefix, NULL, dir)) | |
| { | |
| if (config_key) | |
| *config_key = NULL; | |
| return TRUE; | |
| } | |
| /* Don't support xdg-run without suffix, because that doesn't work */ | |
| if (strcmp (prefix, "xdg-run") == 0 && | |
| *rest != 0) | |
| { | |
| if (config_key) | |
| *config_key = NULL; | |
| if (dir) | |
| *dir = g_get_user_runtime_dir (); | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| static char * | |
| parse_filesystem_flags (const char *filesystem, FlatpakFilesystemMode *mode) | |
| { | |
| gsize len = strlen (filesystem); | |
| if (mode) | |
| *mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE; | |
| if (g_str_has_suffix (filesystem, ":ro")) | |
| { | |
| len -= 3; | |
| if (mode) | |
| *mode = FLATPAK_FILESYSTEM_MODE_READ_ONLY; | |
| } | |
| else if (g_str_has_suffix (filesystem, ":rw")) | |
| { | |
| len -= 3; | |
| if (mode) | |
| *mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE; | |
| } | |
| else if (g_str_has_suffix (filesystem, ":create")) | |
| { | |
| len -= 7; | |
| if (mode) | |
| *mode = FLATPAK_FILESYSTEM_MODE_CREATE; | |
| } | |
| return g_strndup (filesystem, len); | |
| } | |
| static gboolean | |
| flatpak_context_verify_filesystem (const char *filesystem_and_mode, | |
| GError **error) | |
| { | |
| g_autofree char *filesystem = parse_filesystem_flags (filesystem_and_mode, NULL); | |
| if (strcmp (filesystem, "host") == 0) | |
| return TRUE; | |
| if (strcmp (filesystem, "home") == 0) | |
| return TRUE; | |
| if (get_xdg_user_dir_from_string (filesystem, NULL, NULL, NULL)) | |
| return TRUE; | |
| if (g_str_has_prefix (filesystem, "~/")) | |
| return TRUE; | |
| if (g_str_has_prefix (filesystem, "/")) | |
| return TRUE; | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, | |
| _("Unknown filesystem location %s, valid locations are: host, home, xdg-*[/...], ~/dir, /dir"), filesystem); | |
| return FALSE; | |
| } | |
| static void | |
| flatpak_context_add_filesystem (FlatpakContext *context, | |
| const char *what) | |
| { | |
| FlatpakFilesystemMode mode; | |
| char *fs = parse_filesystem_flags (what, &mode); | |
| g_hash_table_insert (context->filesystems, fs, GINT_TO_POINTER (mode)); | |
| } | |
| static void | |
| flatpak_context_remove_filesystem (FlatpakContext *context, | |
| const char *what) | |
| { | |
| g_hash_table_insert (context->filesystems, | |
| parse_filesystem_flags (what, NULL), | |
| NULL); | |
| } | |
| void | |
| flatpak_context_merge (FlatpakContext *context, | |
| FlatpakContext *other) | |
| { | |
| GHashTableIter iter; | |
| gpointer key, value; | |
| context->shares &= ~other->shares_valid; | |
| context->shares |= other->shares; | |
| context->shares_valid |= other->shares_valid; | |
| context->sockets &= ~other->sockets_valid; | |
| context->sockets |= other->sockets; | |
| context->sockets_valid |= other->sockets_valid; | |
| context->devices &= ~other->devices_valid; | |
| context->devices |= other->devices; | |
| context->devices_valid |= other->devices_valid; | |
| context->features &= ~other->features_valid; | |
| context->features |= other->features; | |
| context->features_valid |= other->features_valid; | |
| g_hash_table_iter_init (&iter, other->env_vars); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| g_hash_table_insert (context->env_vars, g_strdup (key), g_strdup (value)); | |
| g_hash_table_iter_init (&iter, other->persistent); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| g_hash_table_insert (context->persistent, g_strdup (key), value); | |
| g_hash_table_iter_init (&iter, other->filesystems); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| g_hash_table_insert (context->filesystems, g_strdup (key), value); | |
| g_hash_table_iter_init (&iter, other->session_bus_policy); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| g_hash_table_insert (context->session_bus_policy, g_strdup (key), value); | |
| g_hash_table_iter_init (&iter, other->system_bus_policy); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| g_hash_table_insert (context->system_bus_policy, g_strdup (key), value); | |
| g_hash_table_iter_init (&iter, other->system_bus_policy); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| g_hash_table_insert (context->system_bus_policy, g_strdup (key), value); | |
| g_hash_table_iter_init (&iter, other->generic_policy); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| { | |
| const char **policy_values = (const char **)value; | |
| int i; | |
| for (i = 0; policy_values[i] != NULL; i++) | |
| flatpak_context_apply_generic_policy (context, (char *)key, policy_values[i]); | |
| } | |
| } | |
| static gboolean | |
| option_share_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| FlatpakContextShares share; | |
| share = flatpak_context_share_from_string (value, error); | |
| if (share == 0) | |
| return FALSE; | |
| flatpak_context_add_shares (context, share); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_unshare_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| FlatpakContextShares share; | |
| share = flatpak_context_share_from_string (value, error); | |
| if (share == 0) | |
| return FALSE; | |
| flatpak_context_remove_shares (context, share); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_socket_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| FlatpakContextSockets socket; | |
| socket = flatpak_context_socket_from_string (value, error); | |
| if (socket == 0) | |
| return FALSE; | |
| flatpak_context_add_sockets (context, socket); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_nosocket_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| FlatpakContextSockets socket; | |
| socket = flatpak_context_socket_from_string (value, error); | |
| if (socket == 0) | |
| return FALSE; | |
| flatpak_context_remove_sockets (context, socket); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_device_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| FlatpakContextDevices device; | |
| device = flatpak_context_device_from_string (value, error); | |
| if (device == 0) | |
| return FALSE; | |
| flatpak_context_add_devices (context, device); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_nodevice_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| FlatpakContextDevices device; | |
| device = flatpak_context_device_from_string (value, error); | |
| if (device == 0) | |
| return FALSE; | |
| flatpak_context_remove_devices (context, device); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_allow_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| FlatpakContextFeatures feature; | |
| feature = flatpak_context_feature_from_string (value, error); | |
| if (feature == 0) | |
| return FALSE; | |
| flatpak_context_add_features (context, feature); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_disallow_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| FlatpakContextFeatures feature; | |
| feature = flatpak_context_feature_from_string (value, error); | |
| if (feature == 0) | |
| return FALSE; | |
| flatpak_context_remove_features (context, feature); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_filesystem_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| if (!flatpak_context_verify_filesystem (value, error)) | |
| return FALSE; | |
| flatpak_context_add_filesystem (context, value); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_nofilesystem_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| if (!flatpak_context_verify_filesystem (value, error)) | |
| return FALSE; | |
| flatpak_context_remove_filesystem (context, value); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_env_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| g_auto(GStrv) split = g_strsplit (value, "=", 2); | |
| if (split == NULL || split[0] == NULL || split[0][0] == 0 || split[1] == NULL) | |
| { | |
| g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED, | |
| _("Invalid env format %s"), value); | |
| return FALSE; | |
| } | |
| flatpak_context_set_env_var (context, split[0], split[1]); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_own_name_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| if (!flatpak_verify_dbus_name (value, error)) | |
| return FALSE; | |
| flatpak_context_set_session_bus_policy (context, value, FLATPAK_POLICY_OWN); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_talk_name_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| if (!flatpak_verify_dbus_name (value, error)) | |
| return FALSE; | |
| flatpak_context_set_session_bus_policy (context, value, FLATPAK_POLICY_TALK); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_system_own_name_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| if (!flatpak_verify_dbus_name (value, error)) | |
| return FALSE; | |
| flatpak_context_set_system_bus_policy (context, value, FLATPAK_POLICY_OWN); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_system_talk_name_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| if (!flatpak_verify_dbus_name (value, error)) | |
| return FALSE; | |
| flatpak_context_set_system_bus_policy (context, value, FLATPAK_POLICY_TALK); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_add_generic_policy_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| char *t; | |
| g_autofree char *key = NULL; | |
| const char *policy_value; | |
| t = strchr (value, '='); | |
| if (t == NULL) | |
| return flatpak_fail (error, "--policy arguments must be in the form SUBSYSTEM.KEY=[!]VALUE"); | |
| policy_value = t + 1; | |
| key = g_strndup (value, t - value); | |
| if (strchr (key, '.') == NULL) | |
| return flatpak_fail (error, "--policy arguments must be in the form SUBSYSTEM.KEY=[!]VALUE"); | |
| if (policy_value[0] == '!') | |
| return flatpak_fail (error, "--policy values can't start with \"!\""); | |
| flatpak_context_apply_generic_policy (context, key, policy_value); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_remove_generic_policy_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| char *t; | |
| g_autofree char *key = NULL; | |
| const char *policy_value; | |
| g_autofree char *extended_value = NULL; | |
| t = strchr (value, '='); | |
| if (t == NULL) | |
| return flatpak_fail (error, "--policy arguments must be in the form SUBSYSTEM.KEY=[!]VALUE"); | |
| policy_value = t + 1; | |
| key = g_strndup (value, t - value); | |
| if (strchr (key, '.') == NULL) | |
| return flatpak_fail (error, "--policy arguments must be in the form SUBSYSTEM.KEY=[!]VALUE"); | |
| if (policy_value[0] == '!') | |
| return flatpak_fail (error, "--policy values can't start with \"!\""); | |
| extended_value = g_strconcat ("!", policy_value, NULL); | |
| flatpak_context_apply_generic_policy (context, key, extended_value); | |
| return TRUE; | |
| } | |
| static gboolean | |
| option_persist_cb (const gchar *option_name, | |
| const gchar *value, | |
| gpointer data, | |
| GError **error) | |
| { | |
| FlatpakContext *context = data; | |
| flatpak_context_set_persistent (context, value); | |
| return TRUE; | |
| } | |
| static gboolean option_no_desktop_deprecated; | |
| static GOptionEntry context_options[] = { | |
| { "share", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_share_cb, N_("Share with host"), N_("SHARE") }, | |
| { "unshare", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_unshare_cb, N_("Unshare with host"), N_("SHARE") }, | |
| { "socket", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_socket_cb, N_("Expose socket to app"), N_("SOCKET") }, | |
| { "nosocket", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nosocket_cb, N_("Don't expose socket to app"), N_("SOCKET") }, | |
| { "device", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_device_cb, N_("Expose device to app"), N_("DEVICE") }, | |
| { "nodevice", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nodevice_cb, N_("Don't expose device to app"), N_("DEVICE") }, | |
| { "allow", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_allow_cb, N_("Allow feature"), N_("FEATURE") }, | |
| { "disallow", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_disallow_cb, N_("Don't allow feature"), N_("FEATURE") }, | |
| { "filesystem", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_filesystem_cb, N_("Expose filesystem to app (:ro for read-only)"), N_("FILESYSTEM[:ro]") }, | |
| { "nofilesystem", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nofilesystem_cb, N_("Don't expose filesystem to app"), N_("FILESYSTEM") }, | |
| { "env", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_env_cb, N_("Set environment variable"), N_("VAR=VALUE") }, | |
| { "own-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_own_name_cb, N_("Allow app to own name on the session bus"), N_("DBUS_NAME") }, | |
| { "talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_talk_name_cb, N_("Allow app to talk to name on the session bus"), N_("DBUS_NAME") }, | |
| { "system-own-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_own_name_cb, N_("Allow app to own name on the system bus"), N_("DBUS_NAME") }, | |
| { "system-talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_talk_name_cb, N_("Allow app to talk to name on the system bus"), N_("DBUS_NAME") }, | |
| { "add-policy", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_add_generic_policy_cb, N_("Add generic policy option"), N_("SUBSYSTEM.KEY=VALUE") }, | |
| { "remove-policy", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_remove_generic_policy_cb, N_("Remove generic policy option"), N_("SUBSYSTEM.KEY=VALUE") }, | |
| { "persist", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_persist_cb, N_("Persist home directory"), N_("FILENAME") }, | |
| /* This is not needed/used anymore, so hidden, but we accept it for backwards compat */ | |
| { "no-desktop", 0, G_OPTION_FLAG_IN_MAIN | G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &option_no_desktop_deprecated, N_("Don't require a running session (no cgroups creation)"), NULL }, | |
| { NULL } | |
| }; | |
| void | |
| flatpak_context_complete (FlatpakContext *context, FlatpakCompletion *completion) | |
| { | |
| flatpak_complete_options (completion, context_options); | |
| } | |
| GOptionGroup * | |
| flatpak_context_get_options (FlatpakContext *context) | |
| { | |
| GOptionGroup *group; | |
| group = g_option_group_new ("environment", | |
| "Runtime Environment", | |
| "Runtime Environment", | |
| context, | |
| NULL); | |
| g_option_group_set_translation_domain (group, GETTEXT_PACKAGE); | |
| g_option_group_add_entries (group, context_options); | |
| return group; | |
| } | |
| static const char * | |
| parse_negated (const char *option, gboolean *negated) | |
| { | |
| if (option[0] == '!') | |
| { | |
| option++; | |
| *negated = TRUE; | |
| } | |
| else | |
| { | |
| *negated = FALSE; | |
| } | |
| return option; | |
| } | |
| /* | |
| * Merge the FLATPAK_METADATA_GROUP_CONTEXT, | |
| * FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, | |
| * FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY and | |
| * FLATPAK_METADATA_GROUP_ENVIRONMENT groups, and all groups starting | |
| * with FLATPAK_METADATA_GROUP_PREFIX_POLICY, from metakey into context. | |
| * | |
| * This is a merge, not a replace! | |
| */ | |
| gboolean | |
| flatpak_context_load_metadata (FlatpakContext *context, | |
| GKeyFile *metakey, | |
| GError **error) | |
| { | |
| gboolean remove; | |
| g_auto(GStrv) groups = NULL; | |
| int i; | |
| if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_SHARED, NULL)) | |
| { | |
| g_auto(GStrv) shares = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_SHARED, NULL, error); | |
| if (shares == NULL) | |
| return FALSE; | |
| for (i = 0; shares[i] != NULL; i++) | |
| { | |
| FlatpakContextShares share; | |
| share = flatpak_context_share_from_string (parse_negated (shares[i], &remove), error); | |
| if (share == 0) | |
| return FALSE; | |
| if (remove) | |
| flatpak_context_remove_shares (context, share); | |
| else | |
| flatpak_context_add_shares (context, share); | |
| } | |
| } | |
| if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_SOCKETS, NULL)) | |
| { | |
| g_auto(GStrv) sockets = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_SOCKETS, NULL, error); | |
| if (sockets == NULL) | |
| return FALSE; | |
| for (i = 0; sockets[i] != NULL; i++) | |
| { | |
| FlatpakContextSockets socket = flatpak_context_socket_from_string (parse_negated (sockets[i], &remove), error); | |
| if (socket == 0) | |
| return FALSE; | |
| if (remove) | |
| flatpak_context_remove_sockets (context, socket); | |
| else | |
| flatpak_context_add_sockets (context, socket); | |
| } | |
| } | |
| if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_DEVICES, NULL)) | |
| { | |
| g_auto(GStrv) devices = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_DEVICES, NULL, error); | |
| if (devices == NULL) | |
| return FALSE; | |
| for (i = 0; devices[i] != NULL; i++) | |
| { | |
| FlatpakContextDevices device = flatpak_context_device_from_string (parse_negated (devices[i], &remove), error); | |
| if (device == 0) | |
| return FALSE; | |
| if (remove) | |
| flatpak_context_remove_devices (context, device); | |
| else | |
| flatpak_context_add_devices (context, device); | |
| } | |
| } | |
| if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_FEATURES, NULL)) | |
| { | |
| g_auto(GStrv) features = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_FEATURES, NULL, error); | |
| if (features == NULL) | |
| return FALSE; | |
| for (i = 0; features[i] != NULL; i++) | |
| { | |
| FlatpakContextFeatures feature = flatpak_context_feature_from_string (parse_negated (features[i], &remove), error); | |
| if (feature == 0) | |
| return FALSE; | |
| if (remove) | |
| flatpak_context_remove_features (context, feature); | |
| else | |
| flatpak_context_add_features (context, feature); | |
| } | |
| } | |
| if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_FILESYSTEMS, NULL)) | |
| { | |
| g_auto(GStrv) filesystems = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_FILESYSTEMS, NULL, error); | |
| if (filesystems == NULL) | |
| return FALSE; | |
| for (i = 0; filesystems[i] != NULL; i++) | |
| { | |
| const char *fs = parse_negated (filesystems[i], &remove); | |
| if (!flatpak_context_verify_filesystem (fs, error)) | |
| return FALSE; | |
| if (remove) | |
| flatpak_context_remove_filesystem (context, fs); | |
| else | |
| flatpak_context_add_filesystem (context, fs); | |
| } | |
| } | |
| if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_PERSISTENT, NULL)) | |
| { | |
| g_auto(GStrv) persistent = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_PERSISTENT, NULL, error); | |
| if (persistent == NULL) | |
| return FALSE; | |
| for (i = 0; persistent[i] != NULL; i++) | |
| flatpak_context_set_persistent (context, persistent[i]); | |
| } | |
| if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY)) | |
| { | |
| g_auto(GStrv) keys = NULL; | |
| gsize i, keys_count; | |
| keys = g_key_file_get_keys (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, &keys_count, NULL); | |
| for (i = 0; i < keys_count; i++) | |
| { | |
| const char *key = keys[i]; | |
| g_autofree char *value = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, key, NULL); | |
| FlatpakPolicy policy; | |
| if (!flatpak_verify_dbus_name (key, error)) | |
| return FALSE; | |
| policy = flatpak_policy_from_string (value, error); | |
| if ((int) policy == -1) | |
| return FALSE; | |
| flatpak_context_set_session_bus_policy (context, key, policy); | |
| } | |
| } | |
| if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY)) | |
| { | |
| g_auto(GStrv) keys = NULL; | |
| gsize i, keys_count; | |
| keys = g_key_file_get_keys (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, &keys_count, NULL); | |
| for (i = 0; i < keys_count; i++) | |
| { | |
| const char *key = keys[i]; | |
| g_autofree char *value = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, key, NULL); | |
| FlatpakPolicy policy; | |
| if (!flatpak_verify_dbus_name (key, error)) | |
| return FALSE; | |
| policy = flatpak_policy_from_string (value, error); | |
| if ((int) policy == -1) | |
| return FALSE; | |
| flatpak_context_set_system_bus_policy (context, key, policy); | |
| } | |
| } | |
| if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT)) | |
| { | |
| g_auto(GStrv) keys = NULL; | |
| gsize i, keys_count; | |
| keys = g_key_file_get_keys (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT, &keys_count, NULL); | |
| for (i = 0; i < keys_count; i++) | |
| { | |
| const char *key = keys[i]; | |
| g_autofree char *value = g_key_file_get_string (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT, key, NULL); | |
| flatpak_context_set_env_var (context, key, value); | |
| } | |
| } | |
| groups = g_key_file_get_groups (metakey, NULL); | |
| for (i = 0; groups[i] != NULL; i++) | |
| { | |
| const char *group = groups[i]; | |
| const char *subsystem; | |
| int j; | |
| if (g_str_has_prefix (group, FLATPAK_METADATA_GROUP_PREFIX_POLICY)) | |
| { | |
| g_auto(GStrv) keys = NULL; | |
| subsystem = group + strlen (FLATPAK_METADATA_GROUP_PREFIX_POLICY); | |
| keys = g_key_file_get_keys (metakey, group, NULL, NULL); | |
| for (j = 0; keys != NULL && keys[j] != NULL; j++) | |
| { | |
| const char *key = keys[j]; | |
| g_autofree char *policy_key = g_strdup_printf ("%s.%s", subsystem, key); | |
| g_auto(GStrv) values = NULL; | |
| int k; | |
| values = g_key_file_get_string_list (metakey, group, key, NULL, NULL); | |
| for (k = 0; values != NULL && values[k] != NULL; k++) | |
| flatpak_context_apply_generic_policy (context, policy_key, | |
| values[k]); | |
| } | |
| } | |
| } | |
| return TRUE; | |
| } | |
| /* | |
| * Save the FLATPAK_METADATA_GROUP_CONTEXT, | |
| * FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, | |
| * FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY and | |
| * FLATPAK_METADATA_GROUP_ENVIRONMENT groups, and all groups starting | |
| * with FLATPAK_METADATA_GROUP_PREFIX_POLICY, into metakey | |
| */ | |
| void | |
| flatpak_context_save_metadata (FlatpakContext *context, | |
| gboolean flatten, | |
| GKeyFile *metakey) | |
| { | |
| g_auto(GStrv) shared = NULL; | |
| g_auto(GStrv) sockets = NULL; | |
| g_auto(GStrv) devices = NULL; | |
| g_auto(GStrv) features = NULL; | |
| GHashTableIter iter; | |
| gpointer key, value; | |
| FlatpakContextShares shares_mask = context->shares; | |
| FlatpakContextShares shares_valid = context->shares_valid; | |
| FlatpakContextSockets sockets_mask = context->sockets; | |
| FlatpakContextSockets sockets_valid = context->sockets_valid; | |
| FlatpakContextDevices devices_mask = context->devices; | |
| FlatpakContextDevices devices_valid = context->devices_valid; | |
| FlatpakContextFeatures features_mask = context->features; | |
| FlatpakContextFeatures features_valid = context->features; | |
| g_auto(GStrv) groups = NULL; | |
| int i; | |
| if (flatten) | |
| { | |
| /* A flattened format means we don't expect this to be merged on top of | |
| another context. In that case we never need to negate any flags. | |
| We calculate this by removing the zero parts of the mask from the valid set. | |
| */ | |
| /* First we make sure only the valid parts of the mask are set, in case we | |
| got some leftover */ | |
| shares_mask &= shares_valid; | |
| sockets_mask &= sockets_valid; | |
| devices_mask &= devices_valid; | |
| features_mask &= features_valid; | |
| /* Then just set the valid set to be the mask set */ | |
| shares_valid = shares_mask; | |
| sockets_valid = sockets_mask; | |
| devices_valid = devices_mask; | |
| features_valid = features_mask; | |
| } | |
| shared = flatpak_context_shared_to_string (shares_mask, shares_valid); | |
| sockets = flatpak_context_sockets_to_string (sockets_mask, sockets_valid); | |
| devices = flatpak_context_devices_to_string (devices_mask, devices_valid); | |
| features = flatpak_context_features_to_string (features_mask, features_valid); | |
| if (shared[0] != NULL) | |
| { | |
| g_key_file_set_string_list (metakey, | |
| FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_SHARED, | |
| (const char * const *) shared, g_strv_length (shared)); | |
| } | |
| else | |
| { | |
| g_key_file_remove_key (metakey, | |
| FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_SHARED, | |
| NULL); | |
| } | |
| if (sockets[0] != NULL) | |
| { | |
| g_key_file_set_string_list (metakey, | |
| FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_SOCKETS, | |
| (const char * const *) sockets, g_strv_length (sockets)); | |
| } | |
| else | |
| { | |
| g_key_file_remove_key (metakey, | |
| FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_SOCKETS, | |
| NULL); | |
| } | |
| if (devices[0] != NULL) | |
| { | |
| g_key_file_set_string_list (metakey, | |
| FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_DEVICES, | |
| (const char * const *) devices, g_strv_length (devices)); | |
| } | |
| else | |
| { | |
| g_key_file_remove_key (metakey, | |
| FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_DEVICES, | |
| NULL); | |
| } | |
| if (features[0] != NULL) | |
| { | |
| g_key_file_set_string_list (metakey, | |
| FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_FEATURES, | |
| (const char * const *) features, g_strv_length (features)); | |
| } | |
| else | |
| { | |
| g_key_file_remove_key (metakey, | |
| FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_FEATURES, | |
| NULL); | |
| } | |
| if (g_hash_table_size (context->filesystems) > 0) | |
| { | |
| g_autoptr(GPtrArray) array = g_ptr_array_new_with_free_func (g_free); | |
| g_hash_table_iter_init (&iter, context->filesystems); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| { | |
| FlatpakFilesystemMode mode = GPOINTER_TO_INT (value); | |
| if (mode == FLATPAK_FILESYSTEM_MODE_READ_ONLY) | |
| g_ptr_array_add (array, g_strconcat (key, ":ro", NULL)); | |
| else if (mode == FLATPAK_FILESYSTEM_MODE_CREATE) | |
| g_ptr_array_add (array, g_strconcat (key, ":create", NULL)); | |
| else if (value != NULL) | |
| g_ptr_array_add (array, g_strdup (key)); | |
| else | |
| g_ptr_array_add (array, g_strconcat ("!", key, NULL)); | |
| } | |
| g_key_file_set_string_list (metakey, | |
| FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_FILESYSTEMS, | |
| (const char * const *) array->pdata, array->len); | |
| } | |
| else | |
| { | |
| g_key_file_remove_key (metakey, | |
| FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_FILESYSTEMS, | |
| NULL); | |
| } | |
| if (g_hash_table_size (context->persistent) > 0) | |
| { | |
| g_autofree char **keys = (char **) g_hash_table_get_keys_as_array (context->persistent, NULL); | |
| g_key_file_set_string_list (metakey, | |
| FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_PERSISTENT, | |
| (const char * const *) keys, g_strv_length (keys)); | |
| } | |
| else | |
| { | |
| g_key_file_remove_key (metakey, | |
| FLATPAK_METADATA_GROUP_CONTEXT, | |
| FLATPAK_METADATA_KEY_PERSISTENT, | |
| NULL); | |
| } | |
| g_key_file_remove_group (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, NULL); | |
| g_hash_table_iter_init (&iter, context->session_bus_policy); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| { | |
| FlatpakPolicy policy = GPOINTER_TO_INT (value); | |
| if (policy > 0) | |
| g_key_file_set_string (metakey, | |
| FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY, | |
| (char *) key, flatpak_policy_to_string (policy)); | |
| } | |
| g_key_file_remove_group (metakey, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, NULL); | |
| g_hash_table_iter_init (&iter, context->system_bus_policy); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| { | |
| FlatpakPolicy policy = GPOINTER_TO_INT (value); | |
| if (policy > 0) | |
| g_key_file_set_string (metakey, | |
| FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY, | |
| (char *) key, flatpak_policy_to_string (policy)); | |
| } | |
| g_key_file_remove_group (metakey, FLATPAK_METADATA_GROUP_ENVIRONMENT, NULL); | |
| g_hash_table_iter_init (&iter, context->env_vars); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| { | |
| g_key_file_set_string (metakey, | |
| FLATPAK_METADATA_GROUP_ENVIRONMENT, | |
| (char *) key, (char *) value); | |
| } | |
| groups = g_key_file_get_groups (metakey, NULL); | |
| for (i = 0; groups[i] != NULL; i++) | |
| { | |
| const char *group = groups[i]; | |
| if (g_str_has_prefix (group, FLATPAK_METADATA_GROUP_PREFIX_POLICY)) | |
| g_key_file_remove_group (metakey, group, NULL); | |
| } | |
| g_hash_table_iter_init (&iter, context->generic_policy); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| { | |
| g_auto(GStrv) parts = g_strsplit ((const char *)key, ".", 2); | |
| g_autofree char *group = NULL; | |
| g_assert (parts[1] != NULL); | |
| const char **policy_values = (const char **)value; | |
| g_autoptr(GPtrArray) new = g_ptr_array_new (); | |
| for (i = 0; policy_values[i] != NULL; i++) | |
| { | |
| const char *policy_value = policy_values[i]; | |
| if (!flatten || policy_value[0] != '!') | |
| g_ptr_array_add (new, (char *)policy_value); | |
| } | |
| if (new->len > 0) | |
| { | |
| group = g_strconcat (FLATPAK_METADATA_GROUP_PREFIX_POLICY, | |
| parts[0], NULL); | |
| g_key_file_set_string_list (metakey, group, parts[1], | |
| (const char * const*)new->pdata, | |
| new->len); | |
| } | |
| } | |
| } | |
| void | |
| flatpak_context_allow_host_fs (FlatpakContext *context) | |
| { | |
| flatpak_context_add_filesystem (context, "host"); | |
| } | |
| gboolean | |
| flatpak_context_get_needs_session_bus_proxy (FlatpakContext *context) | |
| { | |
| return g_hash_table_size (context->session_bus_policy) > 0; | |
| } | |
| gboolean | |
| flatpak_context_get_needs_system_bus_proxy (FlatpakContext *context) | |
| { | |
| return g_hash_table_size (context->system_bus_policy) > 0; | |
| } | |
| void | |
| flatpak_context_to_args (FlatpakContext *context, | |
| GPtrArray *args) | |
| { | |
| GHashTableIter iter; | |
| gpointer key, value; | |
| flatpak_context_shared_to_args (context->shares, context->shares_valid, args); | |
| flatpak_context_sockets_to_args (context->sockets, context->sockets_valid, args); | |
| flatpak_context_devices_to_args (context->devices, context->devices_valid, args); | |
| flatpak_context_features_to_args (context->features, context->features_valid, args); | |
| g_hash_table_iter_init (&iter, context->env_vars); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| g_ptr_array_add (args, g_strdup_printf ("--env=%s=%s", (char *)key, (char *)value)); | |
| g_hash_table_iter_init (&iter, context->persistent); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| g_ptr_array_add (args, g_strdup_printf ("--persist=%s", (char *)key)); | |
| g_hash_table_iter_init (&iter, context->session_bus_policy); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| { | |
| const char *name = key; | |
| FlatpakPolicy policy = GPOINTER_TO_INT (value); | |
| g_ptr_array_add (args, g_strdup_printf ("--%s-name=%s", flatpak_policy_to_string (policy), name)); | |
| } | |
| g_hash_table_iter_init (&iter, context->system_bus_policy); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| { | |
| const char *name = key; | |
| FlatpakPolicy policy = GPOINTER_TO_INT (value); | |
| g_ptr_array_add (args, g_strdup_printf ("--system-%s-name=%s", flatpak_policy_to_string (policy), name)); | |
| } | |
| g_hash_table_iter_init (&iter, context->filesystems); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| { | |
| FlatpakFilesystemMode mode = GPOINTER_TO_INT (value); | |
| if (mode == FLATPAK_FILESYSTEM_MODE_READ_ONLY) | |
| g_ptr_array_add (args, g_strdup_printf ("--filesystem=%s:ro", (char *)key)); | |
| else if (mode == FLATPAK_FILESYSTEM_MODE_READ_WRITE) | |
| g_ptr_array_add (args, g_strdup_printf ("--filesystem=%s", (char *)key)); | |
| else if (mode == FLATPAK_FILESYSTEM_MODE_CREATE) | |
| g_ptr_array_add (args, g_strdup_printf ("--filesystem=%s:create", (char *)key)); | |
| else | |
| g_ptr_array_add (args, g_strdup_printf ("--nofilesystem=%s", (char *)key)); | |
| } | |
| } | |
| static char * | |
| extract_unix_path_from_dbus_address (const char *address) | |
| { | |
| const char *path, *path_end; | |
| if (address == NULL) | |
| return NULL; | |
| if (!g_str_has_prefix (address, "unix:")) | |
| return NULL; | |
| path = strstr (address, "path="); | |
| if (path == NULL) | |
| return NULL; | |
| path += strlen ("path="); | |
| path_end = path; | |
| while (*path_end != 0 && *path_end != ',') | |
| path_end++; | |
| return g_strndup (path, path_end - path); | |
| } | |
| #ifdef ENABLE_XAUTH | |
| static gboolean | |
| auth_streq (char *str, | |
| char *au_str, | |
| int au_len) | |
| { | |
| return au_len == strlen (str) && memcmp (str, au_str, au_len) == 0; | |
| } | |
| static gboolean | |
| xauth_entry_should_propagate (Xauth *xa, | |
| char *hostname, | |
| char *number) | |
| { | |
| /* ensure entry isn't for remote access */ | |
| if (xa->family != FamilyLocal && xa->family != FamilyWild) | |
| return FALSE; | |
| /* ensure entry is for this machine */ | |
| if (xa->family == FamilyLocal && !auth_streq (hostname, xa->address, xa->address_length)) | |
| return FALSE; | |
| /* ensure entry is for this session */ | |
| if (xa->number != NULL && !auth_streq (number, xa->number, xa->number_length)) | |
| return FALSE; | |
| return TRUE; | |
| } | |
| static void | |
| write_xauth (char *number, FILE *output) | |
| { | |
| Xauth *xa, local_xa; | |
| char *filename; | |
| FILE *f; | |
| struct utsname unames; | |
| if (uname (&unames)) | |
| { | |
| g_warning ("uname failed"); | |
| return; | |
| } | |
| filename = XauFileName (); | |
| f = fopen (filename, "rb"); | |
| if (f == NULL) | |
| return; | |
| while (TRUE) | |
| { | |
| xa = XauReadAuth (f); | |
| if (xa == NULL) | |
| break; | |
| if (xauth_entry_should_propagate (xa, unames.nodename, number)) | |
| { | |
| local_xa = *xa; | |
| if (local_xa.number) | |
| { | |
| local_xa.number = "99"; | |
| local_xa.number_length = 2; | |
| } | |
| if (!XauWriteAuth (output, &local_xa)) | |
| g_warning ("xauth write error"); | |
| } | |
| XauDisposeAuth (xa); | |
| } | |
| fclose (f); | |
| } | |
| #endif /* ENABLE_XAUTH */ | |
| static void | |
| add_args (GPtrArray *argv_array, ...) | |
| { | |
| va_list args; | |
| const gchar *arg; | |
| va_start (args, argv_array); | |
| while ((arg = va_arg (args, const gchar *))) | |
| g_ptr_array_add (argv_array, g_strdup (arg)); | |
| va_end (args); | |
| } | |
| static void | |
| add_args_data_fd (GPtrArray *argv_array, | |
| GArray *fd_array, | |
| const char *op, | |
| int fd, | |
| const char *path_optional) | |
| { | |
| g_autofree char *fd_str = g_strdup_printf ("%d", fd); | |
| if (fd_array) | |
| g_array_append_val (fd_array, fd); | |
| add_args (argv_array, | |
| op, fd_str, path_optional, | |
| NULL); | |
| } | |
| /* If memfd_create() is available, generate a sealed memfd with contents of | |
| * @str. Otherwise use an O_TMPFILE @tmpf in anonymous mode, write @str to | |
| * @tmpf, and lseek() back to the start. See also similar uses in e.g. | |
| * rpm-ostree for running dracut. | |
| */ | |
| static gboolean | |
| buffer_to_sealed_memfd_or_tmpfile (GLnxTmpfile *tmpf, | |
| const char *name, | |
| const char *str, | |
| size_t len, | |
| GError **error) | |
| { | |
| if (len == -1) | |
| len = strlen (str); | |
| glnx_autofd int memfd = memfd_create (name, MFD_CLOEXEC | MFD_ALLOW_SEALING); | |
| int fd; /* Unowned */ | |
| if (memfd != -1) | |
| { | |
| fd = memfd; | |
| } | |
| else | |
| { | |
| /* We use an anonymous fd (i.e. O_EXCL) since we don't want | |
| * the target container to potentially be able to re-link it. | |
| */ | |
| if (!G_IN_SET (errno, ENOSYS, EOPNOTSUPP)) | |
| return glnx_throw_errno_prefix (error, "memfd_create"); | |
| if (!glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, tmpf, error)) | |
| return FALSE; | |
| fd = tmpf->fd; | |
| } | |
| if (ftruncate (fd, len) < 0) | |
| return glnx_throw_errno_prefix (error, "ftruncate"); | |
| if (glnx_loop_write (fd, str, len) < 0) | |
| return glnx_throw_errno_prefix (error, "write"); | |
| if (lseek (fd, 0, SEEK_SET) < 0) | |
| return glnx_throw_errno_prefix (error, "lseek"); | |
| if (memfd != -1) | |
| { | |
| if (fcntl (memfd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL) < 0) | |
| return glnx_throw_errno_prefix (error, "fcntl(F_ADD_SEALS)"); | |
| /* The other values can stay default */ | |
| tmpf->fd = glnx_steal_fd (&memfd); | |
| tmpf->initialized = TRUE; | |
| } | |
| return TRUE; | |
| } | |
| static void | |
| flatpak_run_add_x11_args (FlatpakBwrap *bwrap, | |
| gboolean allowed) | |
| { | |
| g_autofree char *x11_socket = NULL; | |
| const char *display; | |
| /* Always cover /tmp/.X11-unix, that way we never see the host one in case | |
| * we have access to the host /tmp. If you request X access we'll put the right | |
| * thing in this anyway. | |
| */ | |
| flatpak_bwrap_add_args (bwrap, | |
| "--tmpfs", "/tmp/.X11-unix", | |
| NULL); | |
| if (!allowed) | |
| { | |
| flatpak_bwrap_unset_env (bwrap, "DISPLAY"); | |
| return; | |
| } | |
| g_debug ("Allowing x11 access"); | |
| display = g_getenv ("DISPLAY"); | |
| if (display && display[0] == ':' && g_ascii_isdigit (display[1])) | |
| { | |
| const char *display_nr = &display[1]; | |
| const char *display_nr_end = display_nr; | |
| g_autofree char *d = NULL; | |
| while (g_ascii_isdigit (*display_nr_end)) | |
| display_nr_end++; | |
| d = g_strndup (display_nr, display_nr_end - display_nr); | |
| x11_socket = g_strdup_printf ("/tmp/.X11-unix/X%s", d); | |
| flatpak_bwrap_add_args (bwrap, | |
| "--bind", x11_socket, "/tmp/.X11-unix/X99", | |
| NULL); | |
| flatpak_bwrap_set_env (bwrap, "DISPLAY", ":99.0", TRUE); | |
| #ifdef ENABLE_XAUTH | |
| g_auto(GLnxTmpfile) xauth_tmpf = { 0, }; | |
| if (glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, &xauth_tmpf, NULL)) | |
| { | |
| FILE *output = fdopen (xauth_tmpf.fd, "wb"); | |
| if (output != NULL) | |
| { | |
| /* fd is now owned by output, steal it from the tmpfile */ | |
| int tmp_fd = dup (glnx_steal_fd (&xauth_tmpf.fd)); | |
| if (tmp_fd != -1) | |
| { | |
| g_autofree char *dest = g_strdup_printf ("/run/user/%d/Xauthority", getuid ()); | |
| write_xauth (d, output); | |
| flatpak_bwrap_add_args_data_fd (bwrap, "--bind-data", tmp_fd, dest); | |
| flatpak_bwrap_set_env (bwrap, "XAUTHORITY", dest, TRUE); | |
| } | |
| fclose (output); | |
| if (tmp_fd != -1) | |
| lseek (tmp_fd, 0, SEEK_SET); | |
| } | |
| } | |
| #endif | |
| } | |
| else | |
| { | |
| flatpak_bwrap_unset_env (bwrap, "DISPLAY"); | |
| } | |
| } | |
| static void | |
| flatpak_run_add_wayland_args (FlatpakBwrap *bwrap) | |
| { | |
| const char *wayland_display; | |
| g_autofree char *wayland_socket = NULL; | |
| g_autofree char *sandbox_wayland_socket = NULL; | |
| wayland_display = g_getenv ("WAYLAND_DISPLAY"); | |
| if (!wayland_display) | |
| wayland_display = "wayland-0"; | |
| wayland_socket = g_build_filename (g_get_user_runtime_dir (), wayland_display, NULL); | |
| sandbox_wayland_socket = g_strdup_printf ("/run/user/%d/%s", getuid (), wayland_display); | |
| if (g_file_test (wayland_socket, G_FILE_TEST_EXISTS)) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--bind", wayland_socket, sandbox_wayland_socket, | |
| NULL); | |
| } | |
| } | |
| static void | |
| flatpak_run_add_pulseaudio_args (FlatpakBwrap *bwrap) | |
| { | |
| g_autofree char *pulseaudio_socket = g_build_filename (g_get_user_runtime_dir (), "pulse/native", NULL); | |
| flatpak_bwrap_unset_env (bwrap, "PULSE_SERVER"); | |
| if (g_file_test (pulseaudio_socket, G_FILE_TEST_EXISTS)) | |
| { | |
| gboolean share_shm = FALSE; /* TODO: When do we add this? */ | |
| g_autofree char *client_config = g_strdup_printf ("enable-shm=%s\n", share_shm ? "yes" : "no"); | |
| g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/pulse/native", getuid ()); | |
| g_autofree char *pulse_server = g_strdup_printf ("unix:/run/user/%d/pulse/native", getuid ()); | |
| g_autofree char *config_path = g_strdup_printf ("/run/user/%d/pulse/config", getuid ()); | |
| /* FIXME - error handling */ | |
| if (!flatpak_bwrap_add_args_data (bwrap, "pulseaudio", client_config, -1, config_path, NULL)) | |
| return; | |
| flatpak_bwrap_add_args (bwrap, | |
| "--bind", pulseaudio_socket, sandbox_socket_path, | |
| NULL); | |
| flatpak_bwrap_set_env (bwrap, "PULSE_SERVER", pulse_server, TRUE); | |
| flatpak_bwrap_set_env (bwrap, "PULSE_CLIENTCONFIG", config_path, TRUE); | |
| } | |
| } | |
| static void | |
| flatpak_run_add_journal_args (FlatpakBwrap *bwrap) | |
| { | |
| g_autofree char *journal_socket_socket = g_strdup ("/run/systemd/journal/socket"); | |
| g_autofree char *journal_stdout_socket = g_strdup ("/run/systemd/journal/stdout"); | |
| if (g_file_test (journal_socket_socket, G_FILE_TEST_EXISTS)) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--bind", journal_socket_socket, journal_socket_socket, | |
| NULL); | |
| } | |
| if (g_file_test (journal_stdout_socket, G_FILE_TEST_EXISTS)) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--bind", journal_stdout_socket, journal_stdout_socket, | |
| NULL); | |
| } | |
| } | |
| static char * | |
| create_proxy_socket (char *template) | |
| { | |
| g_autofree char *proxy_socket_dir = g_build_filename (g_get_user_runtime_dir (), ".dbus-proxy", NULL); | |
| g_autofree char *proxy_socket = g_build_filename (proxy_socket_dir, template, NULL); | |
| int fd; | |
| if (!glnx_shutil_mkdir_p_at (AT_FDCWD, proxy_socket_dir, 0755, NULL, NULL)) | |
| return NULL; | |
| fd = g_mkstemp (proxy_socket); | |
| if (fd == -1) | |
| return NULL; | |
| close (fd); | |
| return g_steal_pointer (&proxy_socket); | |
| } | |
| static gboolean | |
| flatpak_run_add_system_dbus_args (FlatpakContext *context, | |
| FlatpakBwrap *bwrap, | |
| GPtrArray *dbus_proxy_argv, | |
| gboolean unrestricted) | |
| { | |
| const char *dbus_address = g_getenv ("DBUS_SYSTEM_BUS_ADDRESS"); | |
| g_autofree char *real_dbus_address = NULL; | |
| g_autofree char *dbus_system_socket = NULL; | |
| if (dbus_address != NULL) | |
| dbus_system_socket = extract_unix_path_from_dbus_address (dbus_address); | |
| else if (g_file_test ("/var/run/dbus/system_bus_socket", G_FILE_TEST_EXISTS)) | |
| dbus_system_socket = g_strdup ("/var/run/dbus/system_bus_socket"); | |
| if (dbus_system_socket != NULL && unrestricted) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--bind", dbus_system_socket, "/run/dbus/system_bus_socket", | |
| NULL); | |
| flatpak_bwrap_set_env (bwrap, "DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/run/dbus/system_bus_socket", TRUE); | |
| return TRUE; | |
| } | |
| else if (dbus_proxy_argv && | |
| g_hash_table_size (context->system_bus_policy) > 0) | |
| { | |
| g_autofree char *proxy_socket = create_proxy_socket ("system-bus-proxy-XXXXXX"); | |
| if (proxy_socket == NULL) | |
| return FALSE; | |
| if (dbus_address) | |
| real_dbus_address = g_strdup (dbus_address); | |
| else | |
| real_dbus_address = g_strdup_printf ("unix:path=%s", dbus_system_socket); | |
| g_ptr_array_add (dbus_proxy_argv, g_strdup (real_dbus_address)); | |
| g_ptr_array_add (dbus_proxy_argv, g_strdup (proxy_socket)); | |
| flatpak_bwrap_add_args (bwrap, | |
| "--bind", proxy_socket, "/run/dbus/system_bus_socket", | |
| NULL); | |
| flatpak_bwrap_set_env (bwrap, "DBUS_SYSTEM_BUS_ADDRESS", "unix:path=/run/dbus/system_bus_socket", TRUE); | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| static gboolean | |
| flatpak_run_add_session_dbus_args (FlatpakBwrap *bwrap, | |
| GPtrArray *dbus_proxy_argv, | |
| gboolean unrestricted) | |
| { | |
| const char *dbus_address = g_getenv ("DBUS_SESSION_BUS_ADDRESS"); | |
| char *dbus_session_socket = NULL; | |
| g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/bus", getuid ()); | |
| g_autofree char *sandbox_dbus_address = g_strdup_printf ("unix:path=/run/user/%d/bus", getuid ()); | |
| if (dbus_address == NULL) | |
| return FALSE; | |
| dbus_session_socket = extract_unix_path_from_dbus_address (dbus_address); | |
| if (dbus_session_socket != NULL && unrestricted) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--bind", dbus_session_socket, sandbox_socket_path, | |
| NULL); | |
| flatpak_bwrap_set_env (bwrap, "DBUS_SESSION_BUS_ADDRESS", sandbox_dbus_address, TRUE); | |
| return TRUE; | |
| } | |
| else if (dbus_proxy_argv && dbus_address != NULL) | |
| { | |
| g_autofree char *proxy_socket = create_proxy_socket ("session-bus-proxy-XXXXXX"); | |
| if (proxy_socket == NULL) | |
| return FALSE; | |
| g_ptr_array_add (dbus_proxy_argv, g_strdup (dbus_address)); | |
| g_ptr_array_add (dbus_proxy_argv, g_strdup (proxy_socket)); | |
| flatpak_bwrap_add_args (bwrap, | |
| "--bind", proxy_socket, sandbox_socket_path, | |
| NULL); | |
| flatpak_bwrap_set_env (bwrap, "DBUS_SESSION_BUS_ADDRESS", sandbox_dbus_address, TRUE); | |
| return TRUE; | |
| } | |
| return FALSE; | |
| } | |
| static void | |
| flatpak_add_bus_filters (GPtrArray *dbus_proxy_argv, | |
| GHashTable *ht, | |
| const char *app_id, | |
| FlatpakContext *context) | |
| { | |
| GHashTableIter iter; | |
| gpointer key, value; | |
| g_ptr_array_add (dbus_proxy_argv, g_strdup ("--filter")); | |
| if (app_id) | |
| { | |
| g_ptr_array_add (dbus_proxy_argv, g_strdup_printf ("--own=%s", app_id)); | |
| g_ptr_array_add (dbus_proxy_argv, g_strdup_printf ("--own=%s.*", app_id)); | |
| } | |
| g_hash_table_iter_init (&iter, ht); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| { | |
| FlatpakPolicy policy = GPOINTER_TO_INT (value); | |
| if (policy > 0) | |
| g_ptr_array_add (dbus_proxy_argv, g_strdup_printf ("--%s=%s", flatpak_policy_to_string (policy), (char *) key)); | |
| } | |
| } | |
| static int | |
| flatpak_extension_compare_by_path (gconstpointer _a, | |
| gconstpointer _b) | |
| { | |
| const FlatpakExtension *a = _a; | |
| const FlatpakExtension *b = _b; | |
| return g_strcmp0 (a->directory, b->directory); | |
| } | |
| gboolean | |
| flatpak_run_add_extension_args (FlatpakBwrap *bwrap, | |
| GKeyFile *metakey, | |
| const char *full_ref, | |
| gboolean use_ld_so_cache, | |
| char **extensions_out, | |
| GCancellable *cancellable, | |
| GError **error) | |
| { | |
| g_auto(GStrv) parts = NULL; | |
| g_autoptr(GString) used_extensions = g_string_new (""); | |
| gboolean is_app; | |
| GList *extensions, *path_sorted_extensions, *l; | |
| g_autoptr(GString) ld_library_path = g_string_new (""); | |
| int count = 0; | |
| g_autoptr(GHashTable) mounted_tmpfs = | |
| g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); | |
| g_autoptr(GHashTable) created_symlink = | |
| g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); | |
| parts = g_strsplit (full_ref, "/", 0); | |
| if (g_strv_length (parts) != 4) | |
| return flatpak_fail (error, "Failed to determine parts from ref: %s", full_ref); | |
| is_app = strcmp (parts[0], "app") == 0; | |
| extensions = flatpak_list_extensions (metakey, | |
| parts[2], parts[3]); | |
| /* First we apply all the bindings, they are sorted alphabetically in order for parent directory | |
| to be mounted before child directories */ | |
| path_sorted_extensions = g_list_copy (extensions); | |
| path_sorted_extensions = g_list_sort (path_sorted_extensions, flatpak_extension_compare_by_path); | |
| for (l = path_sorted_extensions; l != NULL; l = l->next) | |
| { | |
| FlatpakExtension *ext = l->data; | |
| g_autofree char *directory = g_build_filename (is_app ? "/app" : "/usr", ext->directory, NULL); | |
| g_autofree char *full_directory = g_build_filename (directory, ext->subdir_suffix, NULL); | |
| g_autofree char *ref = g_build_filename (full_directory, ".ref", NULL); | |
| g_autofree char *real_ref = g_build_filename (ext->files_path, ext->directory, ".ref", NULL); | |
| if (ext->needs_tmpfs) | |
| { | |
| g_autofree char *parent = g_path_get_dirname (directory); | |
| if (g_hash_table_lookup (mounted_tmpfs, parent) == NULL) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--tmpfs", parent, | |
| NULL); | |
| g_hash_table_insert (mounted_tmpfs, g_steal_pointer (&parent), "mounted"); | |
| } | |
| } | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", ext->files_path, full_directory, | |
| NULL); | |
| if (g_file_test (real_ref, G_FILE_TEST_EXISTS)) | |
| flatpak_bwrap_add_args (bwrap, | |
| "--lock-file", ref, | |
| NULL); | |
| } | |
| g_list_free (path_sorted_extensions); | |
| /* Then apply library directories and file merging, in extension prio order */ | |
| for (l = extensions; l != NULL; l = l->next) | |
| { | |
| FlatpakExtension *ext = l->data; | |
| g_autofree char *directory = g_build_filename (is_app ? "/app" : "/usr", ext->directory, NULL); | |
| g_autofree char *full_directory = g_build_filename (directory, ext->subdir_suffix, NULL); | |
| int i; | |
| if (used_extensions->len > 0) | |
| g_string_append (used_extensions, ";"); | |
| g_string_append (used_extensions, ext->installed_id); | |
| g_string_append (used_extensions, "="); | |
| if (ext->commit != NULL) | |
| g_string_append (used_extensions, ext->commit); | |
| else | |
| g_string_append (used_extensions, "local"); | |
| if (ext->add_ld_path) | |
| { | |
| g_autofree char *ld_path = g_build_filename (full_directory, ext->add_ld_path, NULL); | |
| if (use_ld_so_cache) | |
| { | |
| g_autofree char *contents = g_strconcat (ld_path, "\n", NULL); | |
| /* We prepend app or runtime and a counter in order to get the include order correct for the conf files */ | |
| g_autofree char *ld_so_conf_file = g_strdup_printf ("%s-%03d-%s.conf", parts[0], ++count, ext->installed_id); | |
| g_autofree char *ld_so_conf_file_path = g_build_filename ("/run/flatpak/ld.so.conf.d", ld_so_conf_file, NULL); | |
| if (!flatpak_bwrap_add_args_data (bwrap, "ld-so-conf", | |
| contents, -1, ld_so_conf_file_path, error)) | |
| return FALSE; | |
| } | |
| else | |
| { | |
| if (ld_library_path->len != 0) | |
| g_string_append (ld_library_path, ":"); | |
| g_string_append (ld_library_path, ld_path); | |
| } | |
| } | |
| for (i = 0; ext->merge_dirs != NULL && ext->merge_dirs[i] != NULL; i++) | |
| { | |
| g_autofree char *parent = g_path_get_dirname (directory); | |
| g_autofree char *merge_dir = g_build_filename (parent, ext->merge_dirs[i], NULL); | |
| g_autofree char *source_dir = g_build_filename (ext->files_path, ext->merge_dirs[i], NULL); | |
| g_auto(GLnxDirFdIterator) source_iter = { 0 }; | |
| struct dirent *dent; | |
| if (glnx_dirfd_iterator_init_at (AT_FDCWD, source_dir, TRUE, &source_iter, NULL)) | |
| { | |
| while (glnx_dirfd_iterator_next_dent (&source_iter, &dent, NULL, NULL) && dent != NULL) | |
| { | |
| g_autofree char *symlink_path = g_build_filename (merge_dir, dent->d_name, NULL); | |
| /* Only create the first, because extensions are listed in prio order */ | |
| if (g_hash_table_lookup (created_symlink, symlink_path) == NULL) | |
| { | |
| g_autofree char *symlink = g_build_filename (directory, ext->merge_dirs[i], dent->d_name, NULL); | |
| flatpak_bwrap_add_args (bwrap, | |
| "--symlink", symlink, symlink_path, | |
| NULL); | |
| g_hash_table_insert (created_symlink, g_steal_pointer (&symlink_path), "created"); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| g_list_free_full (extensions, (GDestroyNotify) flatpak_extension_free); | |
| if (ld_library_path->len != 0) | |
| { | |
| const gchar *old_ld_path = g_environ_getenv (bwrap->envp, "LD_LIBRARY_PATH"); | |
| if (old_ld_path != NULL && *old_ld_path != 0) | |
| { | |
| if (is_app) | |
| { | |
| g_string_append (ld_library_path, ":"); | |
| g_string_append (ld_library_path, old_ld_path); | |
| } | |
| else | |
| { | |
| g_string_prepend (ld_library_path, ":"); | |
| g_string_prepend (ld_library_path, old_ld_path); | |
| } | |
| } | |
| flatpak_bwrap_set_env (bwrap, "LD_LIBRARY_PATH", ld_library_path->str , TRUE); | |
| } | |
| if (extensions_out) | |
| *extensions_out = g_string_free (g_steal_pointer (&used_extensions), FALSE); | |
| return TRUE; | |
| } | |
| static char * | |
| make_relative (const char *base, const char *path) | |
| { | |
| GString *s = g_string_new (""); | |
| while (*base != 0) | |
| { | |
| while (*base == '/') | |
| base++; | |
| if (*base != 0) | |
| g_string_append (s, "../"); | |
| while (*base != '/' && *base != 0) | |
| base++; | |
| } | |
| while (*path == '/') | |
| path++; | |
| g_string_append (s, path); | |
| return g_string_free (s, FALSE); | |
| } | |
| #define FAKE_MODE_DIR -1 /* Ensure a dir, either on tmpfs or mapped parent */ | |
| #define FAKE_MODE_TMPFS 0 | |
| #define FAKE_MODE_SYMLINK G_MAXINT | |
| typedef struct { | |
| char *path; | |
| gint mode; | |
| } ExportedPath; | |
| struct _FlatpakExports { | |
| GHashTable *hash; | |
| FlatpakFilesystemMode host_fs; | |
| }; | |
| static void | |
| exported_path_free (ExportedPath *exported_path) | |
| { | |
| g_free (exported_path->path); | |
| g_free (exported_path); | |
| } | |
| static FlatpakExports * | |
| exports_new (void) | |
| { | |
| FlatpakExports *exports = g_new0 (FlatpakExports, 1); | |
| exports->hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GFreeFunc)exported_path_free); | |
| return exports; | |
| } | |
| void | |
| flatpak_exports_free (FlatpakExports *exports) | |
| { | |
| g_hash_table_destroy (exports->hash); | |
| g_free (exports); | |
| } | |
| /* Returns TRUE if the location of this export | |
| is not visible due to parents being exported */ | |
| static gboolean | |
| path_parent_is_mapped (const char **keys, | |
| guint n_keys, | |
| GHashTable *hash_table, | |
| const char *path) | |
| { | |
| guint i; | |
| gboolean is_mapped = FALSE; | |
| /* The keys are sorted so shorter (i.e. parents) are first */ | |
| for (i = 0; i < n_keys; i++) | |
| { | |
| const char *mounted_path = keys[i]; | |
| ExportedPath *ep = g_hash_table_lookup (hash_table, mounted_path); | |
| if (flatpak_has_path_prefix (path, mounted_path) && | |
| (strcmp (path, mounted_path) != 0)) | |
| { | |
| /* FAKE_MODE_DIR has same mapped value as parent */ | |
| if (ep->mode == FAKE_MODE_DIR) | |
| continue; | |
| is_mapped = ep->mode != FAKE_MODE_TMPFS; | |
| } | |
| } | |
| return is_mapped; | |
| } | |
| static gboolean | |
| path_is_mapped (const char **keys, | |
| guint n_keys, | |
| GHashTable *hash_table, | |
| const char *path) | |
| { | |
| guint i; | |
| gboolean is_mapped = FALSE; | |
| /* The keys are sorted so shorter (i.e. parents) are first */ | |
| for (i = 0; i < n_keys; i++) | |
| { | |
| const char *mounted_path = keys[i]; | |
| ExportedPath *ep = g_hash_table_lookup (hash_table, mounted_path); | |
| if (flatpak_has_path_prefix (path, mounted_path)) | |
| { | |
| /* FAKE_MODE_DIR has same mapped value as parent */ | |
| if (ep->mode == FAKE_MODE_DIR) | |
| continue; | |
| if (ep->mode == FAKE_MODE_SYMLINK) | |
| is_mapped = strcmp (path, mounted_path) == 0; | |
| else | |
| is_mapped = ep->mode != FAKE_MODE_TMPFS; | |
| } | |
| } | |
| return is_mapped; | |
| } | |
| static gint | |
| compare_eps (const ExportedPath *a, | |
| const ExportedPath *b) | |
| { | |
| return g_strcmp0 (a->path, b->path); | |
| } | |
| /* This differs from g_file_test (path, G_FILE_TEST_IS_DIR) which | |
| returns true if the path is a symlink to a dir */ | |
| static gboolean | |
| path_is_dir (const char *path) | |
| { | |
| struct stat s; | |
| if (lstat (path, &s) != 0) | |
| return FALSE; | |
| return S_ISDIR (s.st_mode); | |
| } | |
| static gboolean | |
| path_is_symlink (const char *path) | |
| { | |
| struct stat s; | |
| if (lstat (path, &s) != 0) | |
| return FALSE; | |
| return S_ISLNK (s.st_mode); | |
| } | |
| static void | |
| exports_add_bwrap_args (FlatpakExports *exports, | |
| FlatpakBwrap *bwrap) | |
| { | |
| guint n_keys; | |
| g_autofree const char **keys = (const char **)g_hash_table_get_keys_as_array (exports->hash, &n_keys); | |
| g_autoptr(GList) eps = NULL; | |
| GList *l; | |
| eps = g_hash_table_get_values (exports->hash); | |
| eps = g_list_sort (eps, (GCompareFunc)compare_eps); | |
| g_qsort_with_data (keys, n_keys, sizeof (char *), (GCompareDataFunc) flatpak_strcmp0_ptr, NULL); | |
| for (l = eps; l != NULL; l = l->next) | |
| { | |
| ExportedPath *ep = l->data; | |
| const char *path = ep->path; | |
| if (ep->mode == FAKE_MODE_SYMLINK) | |
| { | |
| if (!path_parent_is_mapped (keys, n_keys, exports->hash, path)) | |
| { | |
| g_autofree char *resolved = flatpak_resolve_link (path, NULL); | |
| if (resolved) | |
| { | |
| g_autofree char *parent = g_path_get_dirname (path); | |
| g_autofree char *relative = make_relative (parent, resolved); | |
| flatpak_bwrap_add_args (bwrap, "--symlink", relative, path, NULL); | |
| } | |
| } | |
| } | |
| else if (ep->mode == FAKE_MODE_TMPFS) | |
| { | |
| /* Mount a tmpfs to hide the subdirectory, but only if there | |
| is a pre-existing dir we can mount the path on. */ | |
| if (path_is_dir (path)) | |
| { | |
| if (!path_parent_is_mapped (keys, n_keys, exports->hash, path)) | |
| /* If the parent is not mapped, it will be a tmpfs, no need to mount another one */ | |
| flatpak_bwrap_add_args (bwrap, "--dir", path, NULL); | |
| else | |
| flatpak_bwrap_add_args (bwrap, "--tmpfs", path, NULL); | |
| } | |
| } | |
| else if (ep->mode == FAKE_MODE_DIR) | |
| { | |
| if (path_is_dir (path)) | |
| flatpak_bwrap_add_args (bwrap, "--dir", path, NULL); | |
| } | |
| else | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| (ep->mode == FLATPAK_FILESYSTEM_MODE_READ_ONLY) ? "--ro-bind" : "--bind", | |
| path, path, NULL); | |
| } | |
| } | |
| if (exports->host_fs != 0) | |
| { | |
| if (g_file_test ("/usr", G_FILE_TEST_IS_DIR)) | |
| flatpak_bwrap_add_args (bwrap, | |
| (exports->host_fs == FLATPAK_FILESYSTEM_MODE_READ_ONLY) ? "--ro-bind" : "--bind", | |
| "/usr", "/run/host/usr", NULL); | |
| if (g_file_test ("/etc", G_FILE_TEST_IS_DIR)) | |
| flatpak_bwrap_add_args (bwrap, | |
| (exports->host_fs == FLATPAK_FILESYSTEM_MODE_READ_ONLY) ? "--ro-bind" : "--bind", | |
| "/etc", "/run/host/etc", NULL); | |
| } | |
| } | |
| gboolean | |
| flatpak_exports_path_is_visible (FlatpakExports *exports, | |
| const char *path) | |
| { | |
| guint n_keys; | |
| g_autofree const char **keys = (const char **)g_hash_table_get_keys_as_array (exports->hash, &n_keys); | |
| g_autofree char *canonical = NULL; | |
| g_auto(GStrv) parts = NULL; | |
| int i; | |
| g_autoptr(GString) path_builder = g_string_new (""); | |
| struct stat st; | |
| g_qsort_with_data (keys, n_keys, sizeof (char *), (GCompareDataFunc) flatpak_strcmp0_ptr, NULL); | |
| path = canonical = flatpak_canonicalize_filename (path); | |
| parts = g_strsplit (path+1, "/", -1); | |
| /* A path is visible in the sandbox if no parent | |
| * path element that is mapped in the sandbox is | |
| * a symlink, and the final element is mapped. | |
| * If any parent is a symlink we resolve that and | |
| * continue with that instead. | |
| */ | |
| for (i = 0; parts[i] != NULL; i++) | |
| { | |
| g_string_append (path_builder, "/"); | |
| g_string_append (path_builder, parts[i]); | |
| if (path_is_mapped (keys, n_keys, exports->hash, path_builder->str)) | |
| { | |
| if (lstat (path_builder->str, &st) != 0) | |
| return FALSE; | |
| if (S_ISLNK (st.st_mode)) | |
| { | |
| g_autofree char *resolved = flatpak_resolve_link (path_builder->str, NULL); | |
| g_autoptr(GString) path2_builder = NULL; | |
| int j; | |
| if (resolved == NULL) | |
| return FALSE; | |
| path2_builder = g_string_new (resolved); | |
| for (j = i + 1; parts[j] != NULL; j++) | |
| { | |
| g_string_append (path2_builder, "/"); | |
| g_string_append (path2_builder, parts[j]); | |
| } | |
| return flatpak_exports_path_is_visible (exports, path2_builder->str); | |
| } | |
| } | |
| else if (parts[i+1] == NULL) | |
| return FALSE; /* Last part was not mapped */ | |
| } | |
| return TRUE; | |
| } | |
| static gboolean | |
| never_export_as_symlink (const char *path) | |
| { | |
| /* Don't export /tmp as a symlink even if it is on the host, because | |
| that will fail with the pre-existing directory we created for /tmp, | |
| and anyway, it being a symlink is not useful in the sandbox */ | |
| if (strcmp (path, "/tmp") == 0) | |
| return TRUE; | |
| return FALSE; | |
| } | |
| static void | |
| do_export_path (FlatpakExports *exports, | |
| const char *path, | |
| gint mode) | |
| { | |
| ExportedPath *old_ep = g_hash_table_lookup (exports->hash, path); | |
| ExportedPath *ep; | |
| ep = g_new0 (ExportedPath, 1); | |
| ep->path = g_strdup (path); | |
| if (old_ep != NULL) | |
| ep->mode = MAX (old_ep->mode, mode); | |
| else | |
| ep->mode = mode; | |
| g_hash_table_replace (exports->hash, ep->path, ep); | |
| } | |
| /* We use level to avoid infinite recursion */ | |
| static gboolean | |
| _exports_path_expose (FlatpakExports *exports, | |
| int mode, | |
| const char *path, | |
| int level) | |
| { | |
| g_autofree char *canonical = NULL; | |
| struct stat st; | |
| char *slash; | |
| int i; | |
| if (level > 40) /* 40 is the current kernel ELOOP check */ | |
| { | |
| g_debug ("Expose too deep, bail"); | |
| return FALSE; | |
| } | |
| if (!g_path_is_absolute (path)) | |
| { | |
| g_debug ("Not exposing relative path %s", path); | |
| return FALSE; | |
| } | |
| /* Check if it exists at all */ | |
| if (lstat (path, &st) != 0) | |
| return FALSE; | |
| /* Don't expose weird things */ | |
| if (!(S_ISDIR (st.st_mode) || | |
| S_ISREG (st.st_mode) || | |
| S_ISLNK (st.st_mode) || | |
| S_ISSOCK (st.st_mode))) | |
| return FALSE; | |
| path = canonical = flatpak_canonicalize_filename (path); | |
| for (i = 0; dont_export_in[i] != NULL; i++) | |
| { | |
| /* Don't expose files in non-mounted dirs like /app or /usr, as | |
| they are not the same as on the host, and we generally can't | |
| create the parents for them anyway */ | |
| if (flatpak_has_path_prefix (path, dont_export_in[i])) | |
| { | |
| g_debug ("skipping export for path %s", path); | |
| return FALSE; | |
| } | |
| } | |
| /* Handle any symlinks prior to the target itself. This includes path itself, | |
| because we expose the target of the symlink. */ | |
| slash = canonical; | |
| do | |
| { | |
| slash = strchr (slash + 1, '/'); | |
| if (slash) | |
| *slash = 0; | |
| if (path_is_symlink (path) && !never_export_as_symlink (path)) | |
| { | |
| g_autofree char *resolved = flatpak_resolve_link (path, NULL); | |
| g_autofree char *new_target = NULL; | |
| if (resolved) | |
| { | |
| if (slash) | |
| new_target = g_build_filename (resolved, slash + 1, NULL); | |
| else | |
| new_target = g_strdup (resolved); | |
| if (_exports_path_expose (exports, mode, new_target, level + 1)) | |
| { | |
| do_export_path (exports, path, FAKE_MODE_SYMLINK); | |
| return TRUE; | |
| } | |
| } | |
| return FALSE; | |
| } | |
| if (slash) | |
| *slash = '/'; | |
| } | |
| while (slash != NULL); | |
| do_export_path (exports, path, mode); | |
| return TRUE; | |
| } | |
| static void | |
| exports_path_expose (FlatpakExports *exports, | |
| FlatpakFilesystemMode mode, | |
| const char *path) | |
| { | |
| _exports_path_expose (exports, mode, path, 0); | |
| } | |
| static void | |
| exports_path_tmpfs (FlatpakExports *exports, | |
| const char *path) | |
| { | |
| _exports_path_expose (exports, FAKE_MODE_TMPFS, path, 0); | |
| } | |
| static void | |
| exports_path_expose_or_hide (FlatpakExports *exports, | |
| FlatpakFilesystemMode mode, | |
| const char *path) | |
| { | |
| if (mode == 0) | |
| exports_path_tmpfs (exports, path); | |
| else | |
| exports_path_expose (exports, mode, path); | |
| } | |
| static void | |
| exports_path_dir (FlatpakExports *exports, | |
| const char *path) | |
| { | |
| _exports_path_expose (exports, FAKE_MODE_DIR, path, 0); | |
| } | |
| static void | |
| export_paths_export_context (FlatpakContext *context, | |
| FlatpakExports *exports, | |
| GFile *app_id_dir, | |
| gboolean do_create, | |
| GString *xdg_dirs_conf, | |
| gboolean *home_access_out) | |
| { | |
| gboolean home_access = FALSE; | |
| FlatpakFilesystemMode fs_mode, home_mode; | |
| GHashTableIter iter; | |
| gpointer key, value; | |
| fs_mode = (FlatpakFilesystemMode) g_hash_table_lookup (context->filesystems, "host"); | |
| if (fs_mode != 0) | |
| { | |
| DIR *dir; | |
| struct dirent *dirent; | |
| g_debug ("Allowing host-fs access"); | |
| home_access = TRUE; | |
| /* Bind mount most dirs in / into the new root */ | |
| dir = opendir ("/"); | |
| if (dir != NULL) | |
| { | |
| while ((dirent = readdir (dir))) | |
| { | |
| g_autofree char *path = NULL; | |
| if (g_strv_contains (dont_mount_in_root, dirent->d_name)) | |
| continue; | |
| path = g_build_filename ("/", dirent->d_name, NULL); | |
| exports_path_expose (exports, fs_mode, path); | |
| } | |
| closedir (dir); | |
| } | |
| exports_path_expose (exports, fs_mode, "/run/media"); | |
| exports->host_fs = fs_mode; | |
| } | |
| home_mode = (FlatpakFilesystemMode) g_hash_table_lookup (context->filesystems, "home"); | |
| if (home_mode != 0) | |
| { | |
| g_debug ("Allowing homedir access"); | |
| home_access = TRUE; | |
| exports_path_expose (exports, MAX (home_mode, fs_mode), g_get_home_dir ()); | |
| } | |
| g_hash_table_iter_init (&iter, context->filesystems); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| { | |
| const char *filesystem = key; | |
| FlatpakFilesystemMode mode = GPOINTER_TO_INT (value); | |
| if (strcmp (filesystem, "host") == 0 || | |
| strcmp (filesystem, "home") == 0) | |
| continue; | |
| if (g_str_has_prefix (filesystem, "xdg-")) | |
| { | |
| const char *path, *rest = NULL; | |
| const char *config_key = NULL; | |
| g_autofree char *subpath = NULL; | |
| if (!get_xdg_user_dir_from_string (filesystem, &config_key, &rest, &path)) | |
| { | |
| g_warning ("Unsupported xdg dir %s", filesystem); | |
| continue; | |
| } | |
| if (path == NULL) | |
| continue; /* Unconfigured, ignore */ | |
| if (strcmp (path, g_get_home_dir ()) == 0) | |
| { | |
| /* xdg-user-dirs sets disabled dirs to $HOME, and its in general not a good | |
| idea to set full access to $HOME other than explicitly, so we ignore | |
| these */ | |
| g_debug ("Xdg dir %s is $HOME (i.e. disabled), ignoring", filesystem); | |
| continue; | |
| } | |
| subpath = g_build_filename (path, rest, NULL); | |
| if (mode == FLATPAK_FILESYSTEM_MODE_CREATE && do_create) | |
| g_mkdir_with_parents (subpath, 0755); | |
| if (g_file_test (subpath, G_FILE_TEST_EXISTS)) | |
| { | |
| if (config_key && xdg_dirs_conf) | |
| g_string_append_printf (xdg_dirs_conf, "%s=\"%s\"\n", | |
| config_key, path); | |
| exports_path_expose_or_hide (exports, mode, subpath); | |
| } | |
| } | |
| else if (g_str_has_prefix (filesystem, "~/")) | |
| { | |
| g_autofree char *path = NULL; | |
| path = g_build_filename (g_get_home_dir (), filesystem + 2, NULL); | |
| if (mode == FLATPAK_FILESYSTEM_MODE_CREATE && do_create) | |
| g_mkdir_with_parents (path, 0755); | |
| if (g_file_test (path, G_FILE_TEST_EXISTS)) | |
| exports_path_expose_or_hide (exports, mode, path); | |
| } | |
| else if (g_str_has_prefix (filesystem, "/")) | |
| { | |
| if (mode == FLATPAK_FILESYSTEM_MODE_CREATE && do_create) | |
| g_mkdir_with_parents (filesystem, 0755); | |
| if (g_file_test (filesystem, G_FILE_TEST_EXISTS)) | |
| exports_path_expose_or_hide (exports, mode, filesystem); | |
| } | |
| else | |
| { | |
| g_warning ("Unexpected filesystem arg %s", filesystem); | |
| } | |
| } | |
| if (app_id_dir) | |
| { | |
| g_autoptr(GFile) apps_dir = g_file_get_parent (app_id_dir); | |
| /* Hide the .var/app dir by default (unless explicitly made visible) */ | |
| exports_path_tmpfs (exports, flatpak_file_get_path_cached (apps_dir)); | |
| /* But let the app write to the per-app dir in it */ | |
| exports_path_expose (exports, FLATPAK_FILESYSTEM_MODE_READ_WRITE, | |
| flatpak_file_get_path_cached (app_id_dir)); | |
| } | |
| if (home_access_out != NULL) | |
| *home_access_out = home_access; | |
| } | |
| FlatpakExports * | |
| flatpak_exports_from_context (FlatpakContext *context, | |
| const char *app_id) | |
| { | |
| g_autoptr(FlatpakExports) exports = exports_new (); | |
| g_autoptr(GFile) app_id_dir = flatpak_get_data_dir (app_id); | |
| export_paths_export_context (context, exports, app_id_dir, FALSE, NULL, NULL); | |
| return g_steal_pointer (&exports); | |
| } | |
| gboolean | |
| flatpak_run_add_environment_args (FlatpakBwrap *bwrap, | |
| const char *app_info_path, | |
| FlatpakRunFlags flags, | |
| const char *app_id, | |
| FlatpakContext *context, | |
| GFile *app_id_dir, | |
| FlatpakExports **exports_out, | |
| GCancellable *cancellable, | |
| GError **error) | |
| { | |
| gboolean home_access = FALSE; | |
| GHashTableIter iter; | |
| gpointer key, value; | |
| gboolean unrestricted_session_bus; | |
| gboolean unrestricted_system_bus; | |
| g_autoptr(GString) xdg_dirs_conf = g_string_new (""); | |
| g_autoptr(GError) my_error = NULL; | |
| g_autoptr(GFile) user_flatpak_dir = NULL; | |
| g_autoptr(FlatpakExports) exports = exports_new (); | |
| g_autoptr(GPtrArray) session_bus_proxy_argv = NULL; | |
| g_autoptr(GPtrArray) system_bus_proxy_argv = NULL; | |
| g_autoptr(GPtrArray) a11y_bus_proxy_argv = NULL; | |
| int sync_fds[2] = {-1, -1}; | |
| if ((flags & FLATPAK_RUN_FLAG_NO_SESSION_BUS_PROXY) == 0) | |
| session_bus_proxy_argv = g_ptr_array_new_with_free_func (g_free); | |
| if ((flags & FLATPAK_RUN_FLAG_NO_SYSTEM_BUS_PROXY) == 0) | |
| system_bus_proxy_argv = g_ptr_array_new_with_free_func (g_free); | |
| if ((context->shares & FLATPAK_CONTEXT_SHARED_IPC) == 0) | |
| { | |
| g_debug ("Disallowing ipc access"); | |
| flatpak_bwrap_add_args (bwrap, "--unshare-ipc", NULL); | |
| } | |
| if ((context->shares & FLATPAK_CONTEXT_SHARED_NETWORK) == 0) | |
| { | |
| g_debug ("Disallowing network access"); | |
| flatpak_bwrap_add_args (bwrap, "--unshare-net", NULL); | |
| } | |
| if (context->devices & FLATPAK_CONTEXT_DEVICE_ALL) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--dev-bind", "/dev", "/dev", | |
| NULL); | |
| } | |
| else | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--dev", "/dev", | |
| NULL); | |
| if (context->devices & FLATPAK_CONTEXT_DEVICE_DRI) | |
| { | |
| g_debug ("Allowing dri access"); | |
| int i; | |
| char *dri_devices[] = { | |
| "/dev/dri", | |
| /* mali */ | |
| "/dev/mali", | |
| "/dev/mali0", | |
| "/dev/umplock", | |
| /* nvidia */ | |
| "/dev/nvidiactl", | |
| "/dev/nvidia0", | |
| "/dev/nvidia-modeset", | |
| }; | |
| for (i = 0; i < G_N_ELEMENTS(dri_devices); i++) | |
| { | |
| if (g_file_test (dri_devices[i], G_FILE_TEST_EXISTS)) | |
| flatpak_bwrap_add_args (bwrap, "--dev-bind", dri_devices[i], dri_devices[i], NULL); | |
| } | |
| } | |
| if (context->devices & FLATPAK_CONTEXT_DEVICE_KVM) | |
| { | |
| g_debug ("Allowing kvm access"); | |
| if (g_file_test ("/dev/kvm", G_FILE_TEST_EXISTS)) | |
| flatpak_bwrap_add_args (bwrap, "--dev-bind", "/dev/kvm", "/dev/kvm", NULL); | |
| } | |
| } | |
| export_paths_export_context (context, exports, app_id_dir, TRUE, xdg_dirs_conf, &home_access); | |
| if (app_id_dir != NULL) | |
| flatpak_run_apply_env_appid (bwrap, app_id_dir); | |
| if (!home_access) | |
| { | |
| /* Enable persistent mapping only if no access to real home dir */ | |
| g_hash_table_iter_init (&iter, context->persistent); | |
| while (g_hash_table_iter_next (&iter, &key, NULL)) | |
| { | |
| const char *persist = key; | |
| g_autofree char *src = g_build_filename (g_get_home_dir (), ".var/app", app_id, persist, NULL); | |
| g_autofree char *dest = g_build_filename (g_get_home_dir (), persist, NULL); | |
| g_mkdir_with_parents (src, 0755); | |
| /* We stick to flatpak_bwrap_add_args instead of flatpak_bwrap_add_bind_arg because persisted | |
| * folders don't need to exist outside the chroot. | |
| */ | |
| flatpak_bwrap_add_args (bwrap, "--bind", src, dest, NULL); | |
| } | |
| } | |
| { | |
| g_autofree char *run_user_app_dst = g_strdup_printf ("/run/user/%d/app/%s", getuid (), app_id); | |
| g_autofree char *run_user_app_src = g_build_filename (g_get_user_runtime_dir (), "app", app_id, NULL); | |
| if (glnx_shutil_mkdir_p_at (AT_FDCWD, | |
| run_user_app_src, | |
| 0700, | |
| NULL, | |
| NULL)) | |
| flatpak_bwrap_add_args (bwrap, | |
| "--bind", run_user_app_src, run_user_app_dst, | |
| NULL); | |
| } | |
| /* Hide the flatpak dir by default (unless explicitly made visible) */ | |
| user_flatpak_dir = flatpak_get_user_base_dir_location (); | |
| exports_path_tmpfs (exports, flatpak_file_get_path_cached (user_flatpak_dir)); | |
| /* Ensure we always have a homedir */ | |
| exports_path_dir (exports, g_get_home_dir ()); | |
| /* This actually outputs the args for the hide/expose operations above */ | |
| exports_add_bwrap_args (exports, bwrap); | |
| /* Special case subdirectories of the cache, config and data xdg | |
| * dirs. If these are accessible explicilty, then we bind-mount | |
| * these in the app-id dir. This allows applications to explicitly | |
| * opt out of keeping some config/cache/data in the app-specific | |
| * directory. | |
| */ | |
| if (app_id_dir) | |
| { | |
| g_hash_table_iter_init (&iter, context->filesystems); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| { | |
| const char *filesystem = key; | |
| FlatpakFilesystemMode mode = GPOINTER_TO_INT (value); | |
| g_autofree char *xdg_path = NULL; | |
| const char *rest, *where; | |
| xdg_path = get_xdg_dir_from_string (filesystem, &rest, &where); | |
| if (xdg_path != NULL && *rest != 0 && | |
| mode >= FLATPAK_FILESYSTEM_MODE_READ_ONLY) | |
| { | |
| g_autoptr(GFile) app_version = g_file_get_child (app_id_dir, where); | |
| g_autoptr(GFile) app_version_subdir = g_file_resolve_relative_path (app_version, rest); | |
| if (g_file_test (xdg_path, G_FILE_TEST_IS_DIR) || | |
| g_file_test (xdg_path, G_FILE_TEST_IS_REGULAR)) | |
| { | |
| g_autofree char *xdg_path_in_app = g_file_get_path (app_version_subdir); | |
| flatpak_bwrap_add_bind_arg (bwrap, | |
| mode == FLATPAK_FILESYSTEM_MODE_READ_ONLY ? "--ro-bind" : "--bind", | |
| xdg_path, xdg_path_in_app); | |
| } | |
| } | |
| } | |
| } | |
| if (home_access && app_id_dir != NULL) | |
| { | |
| g_autofree char *src_path = g_build_filename (g_get_user_config_dir (), | |
| "user-dirs.dirs", | |
| NULL); | |
| g_autofree char *path = g_build_filename (flatpak_file_get_path_cached (app_id_dir), | |
| "config/user-dirs.dirs", NULL); | |
| if (g_file_test (src_path, G_FILE_TEST_EXISTS)) | |
| flatpak_bwrap_add_bind_arg (bwrap, "--ro-bind", src_path, path); | |
| } | |
| else if (xdg_dirs_conf->len > 0 && app_id_dir != NULL) | |
| { | |
| g_autofree char *path = | |
| g_build_filename (flatpak_file_get_path_cached (app_id_dir), | |
| "config/user-dirs.dirs", NULL); | |
| flatpak_bwrap_add_args_data (bwrap, "xdg-config-dirs", | |
| xdg_dirs_conf->str, xdg_dirs_conf->len, path, NULL); | |
| } | |
| flatpak_run_add_x11_args (bwrap, | |
| (context->sockets & FLATPAK_CONTEXT_SOCKET_X11) != 0); | |
| if (context->sockets & FLATPAK_CONTEXT_SOCKET_WAYLAND) | |
| { | |
| g_debug ("Allowing wayland access"); | |
| flatpak_run_add_wayland_args (bwrap); | |
| } | |
| if (context->sockets & FLATPAK_CONTEXT_SOCKET_PULSEAUDIO) | |
| { | |
| g_debug ("Allowing pulseaudio access"); | |
| flatpak_run_add_pulseaudio_args (bwrap); | |
| } | |
| unrestricted_session_bus = (context->sockets & FLATPAK_CONTEXT_SOCKET_SESSION_BUS) != 0; | |
| if (unrestricted_session_bus) | |
| g_debug ("Allowing session-dbus access"); | |
| if (flatpak_run_add_session_dbus_args (bwrap, session_bus_proxy_argv, unrestricted_session_bus) && | |
| !unrestricted_session_bus && session_bus_proxy_argv) | |
| flatpak_add_bus_filters (session_bus_proxy_argv, context->session_bus_policy, app_id, context); | |
| unrestricted_system_bus = (context->sockets & FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS) != 0; | |
| if (unrestricted_system_bus) | |
| g_debug ("Allowing system-dbus access"); | |
| if (flatpak_run_add_system_dbus_args (context, bwrap, system_bus_proxy_argv, | |
| unrestricted_system_bus) && | |
| !unrestricted_system_bus && system_bus_proxy_argv) | |
| flatpak_add_bus_filters (system_bus_proxy_argv, context->system_bus_policy, NULL, context); | |
| if ((flags & FLATPAK_RUN_FLAG_NO_A11Y_BUS_PROXY) == 0) | |
| { | |
| g_autoptr(GDBusConnection) session_bus = NULL; | |
| g_autofree char *a11y_address = NULL; | |
| session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); | |
| if (session_bus) | |
| { | |
| g_autoptr(GError) local_error = NULL; | |
| g_autoptr(GDBusMessage) reply = NULL; | |
| g_autoptr(GDBusMessage) msg = | |
| g_dbus_message_new_method_call ("org.a11y.Bus", | |
| "/org/a11y/bus", | |
| "org.a11y.Bus", | |
| "GetAddress"); | |
| g_dbus_message_set_body (msg, g_variant_new ("()")); | |
| reply = | |
| g_dbus_connection_send_message_with_reply_sync (session_bus, msg, | |
| G_DBUS_SEND_MESSAGE_FLAGS_NONE, | |
| 30000, | |
| NULL, | |
| NULL, | |
| NULL); | |
| if (reply) | |
| { | |
| if (g_dbus_message_to_gerror (reply, &local_error)) | |
| { | |
| if (!g_error_matches (local_error, G_DBUS_ERROR, G_DBUS_ERROR_SERVICE_UNKNOWN)) | |
| g_message ("Can't find a11y bus: %s", local_error->message); | |
| } | |
| else | |
| { | |
| g_variant_get (g_dbus_message_get_body (reply), | |
| "(s)", &a11y_address); | |
| } | |
| } | |
| } | |
| if (a11y_address) | |
| { | |
| g_autofree char *proxy_socket = create_proxy_socket ("a11y-bus-proxy-XXXXXX"); | |
| if (proxy_socket) | |
| { | |
| g_autofree char *sandbox_socket_path = g_strdup_printf ("/run/user/%d/at-spi-bus", getuid ()); | |
| g_autofree char *sandbox_dbus_address = g_strdup_printf ("unix:path=/run/user/%d/at-spi-bus", getuid ()); | |
| a11y_bus_proxy_argv = g_ptr_array_new_with_free_func (g_free); | |
| g_ptr_array_add (a11y_bus_proxy_argv, g_strdup (a11y_address)); | |
| g_ptr_array_add (a11y_bus_proxy_argv, g_strdup (proxy_socket)); | |
| g_ptr_array_add (a11y_bus_proxy_argv, g_strdup ("--filter")); | |
| g_ptr_array_add (a11y_bus_proxy_argv, g_strdup ("--sloppy-names")); | |
| g_ptr_array_add (a11y_bus_proxy_argv, | |
| g_strdup ("--filter=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Embed@/org/a11y/atspi/accessible/root")); | |
| g_ptr_array_add (a11y_bus_proxy_argv, | |
| g_strdup ("--filter=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Unembed@/org/a11y/atspi/accessible/root")); | |
| g_ptr_array_add (a11y_bus_proxy_argv, | |
| g_strdup ("--filter=org.a11y.atspi.Registry=org.a11y.atspi.Registry.GetRegisteredEvents@/org/a11y/atspi/registry")); | |
| g_ptr_array_add (a11y_bus_proxy_argv, | |
| g_strdup ("--filter=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.GetKeystrokeListeners@/org/a11y/atspi/registry/deviceeventcontroller")); | |
| g_ptr_array_add (a11y_bus_proxy_argv, | |
| g_strdup ("--filter=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.GetDeviceEventListeners@/org/a11y/atspi/registry/deviceeventcontroller")); | |
| g_ptr_array_add (a11y_bus_proxy_argv, | |
| g_strdup ("--filter=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.NotifyListenersSync@/org/a11y/atspi/registry/deviceeventcontroller")); | |
| g_ptr_array_add (a11y_bus_proxy_argv, | |
| g_strdup ("--filter=org.a11y.atspi.Registry=org.a11y.atspi.DeviceEventController.NotifyListenersAsync@/org/a11y/atspi/registry/deviceeventcontroller")); | |
| flatpak_bwrap_add_args (bwrap, | |
| "--bind", proxy_socket, sandbox_socket_path, | |
| NULL); | |
| flatpak_bwrap_set_env (bwrap, "AT_SPI_BUS_ADDRESS", sandbox_dbus_address, TRUE); | |
| } | |
| } | |
| } | |
| if (g_environ_getenv (bwrap->envp, "LD_LIBRARY_PATH") != NULL) | |
| { | |
| /* LD_LIBRARY_PATH is overridden for setuid helper, so pass it as cmdline arg */ | |
| flatpak_bwrap_add_args (bwrap, | |
| "--setenv", "LD_LIBRARY_PATH", g_environ_getenv (bwrap->envp, "LD_LIBRARY_PATH"), | |
| NULL); | |
| flatpak_bwrap_unset_env (bwrap, "LD_LIBRARY_PATH"); | |
| } | |
| /* Must run this before spawning the dbus proxy, to ensure it | |
| ends up in the app cgroup */ | |
| if (!flatpak_run_in_transient_unit (app_id, &my_error)) | |
| { | |
| /* We still run along even if we don't get a cgroup, as nothing | |
| really depends on it. Its just nice to have */ | |
| g_debug ("Failed to run in transient scope: %s", my_error->message); | |
| g_clear_error (&my_error); | |
| } | |
| if (!add_dbus_proxy_args (bwrap->argv, | |
| session_bus_proxy_argv, (flags & FLATPAK_RUN_FLAG_LOG_SESSION_BUS) != 0, | |
| system_bus_proxy_argv, (flags & FLATPAK_RUN_FLAG_LOG_SYSTEM_BUS) != 0, | |
| a11y_bus_proxy_argv, (flags & FLATPAK_RUN_FLAG_LOG_A11Y_BUS) != 0, | |
| sync_fds, app_info_path, error)) | |
| return FALSE; | |
| if (sync_fds[1] != -1) | |
| close (sync_fds[1]); | |
| if (exports_out) | |
| *exports_out = g_steal_pointer (&exports); | |
| return TRUE; | |
| } | |
| typedef struct { | |
| const char *env; | |
| const char *val; | |
| } ExportData; | |
| static const ExportData default_exports[] = { | |
| {"PATH", "/app/bin:/usr/bin"}, | |
| /* We always want to unset LD_LIBRARY_PATH to avoid inheriting weird | |
| * dependencies from the host. But if not using ld.so.cache this is | |
| * later set. */ | |
| {"LD_LIBRARY_PATH", NULL}, | |
| {"XDG_CONFIG_DIRS", "/app/etc/xdg:/etc/xdg"}, | |
| {"XDG_DATA_DIRS", "/app/share:/usr/share"}, | |
| {"SHELL", "/bin/sh"}, | |
| {"TMPDIR", NULL}, /* Unset TMPDIR as it may not exist in the sandbox */ | |
| /* Some env vars are common enough and will affect the sandbox badly | |
| if set on the host. We clear these always. */ | |
| {"PYTHONPATH", NULL}, | |
| {"PERLLIB", NULL}, | |
| {"PERL5LIB", NULL}, | |
| {"XCURSOR_PATH", NULL}, | |
| }; | |
| static const ExportData no_ld_so_cache_exports[] = { | |
| {"LD_LIBRARY_PATH", "/app/lib"}, | |
| }; | |
| static const ExportData devel_exports[] = { | |
| {"ACLOCAL_PATH", "/app/share/aclocal"}, | |
| {"C_INCLUDE_PATH", "/app/include"}, | |
| {"CPLUS_INCLUDE_PATH", "/app/include"}, | |
| {"LDFLAGS", "-L/app/lib "}, | |
| {"PKG_CONFIG_PATH", "/app/lib/pkgconfig:/app/share/pkgconfig:/usr/lib/pkgconfig:/usr/share/pkgconfig"}, | |
| {"LC_ALL", "en_US.utf8"}, | |
| }; | |
| static void | |
| add_exports (GPtrArray *env_array, | |
| const ExportData *exports, | |
| gsize n_exports) | |
| { | |
| int i; | |
| for (i = 0; i < n_exports; i++) | |
| { | |
| if (exports[i].val) | |
| g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", exports[i].env, exports[i].val)); | |
| } | |
| } | |
| char ** | |
| flatpak_run_get_minimal_env (gboolean devel, gboolean use_ld_so_cache) | |
| { | |
| GPtrArray *env_array; | |
| static const char * const copy[] = { | |
| "PWD", | |
| "GDMSESSION", | |
| "XDG_CURRENT_DESKTOP", | |
| "XDG_SESSION_DESKTOP", | |
| "DESKTOP_SESSION", | |
| "EMAIL_ADDRESS", | |
| "HOME", | |
| "HOSTNAME", | |
| "LOGNAME", | |
| "REAL_NAME", | |
| "TERM", | |
| "USER", | |
| "USERNAME", | |
| }; | |
| static const char * const copy_nodevel[] = { | |
| "LANG", | |
| "LANGUAGE", | |
| "LC_ALL", | |
| "LC_ADDRESS", | |
| "LC_COLLATE", | |
| "LC_CTYPE", | |
| "LC_IDENTIFICATION", | |
| "LC_MEASUREMENT", | |
| "LC_MESSAGES", | |
| "LC_MONETARY", | |
| "LC_NAME", | |
| "LC_NUMERIC", | |
| "LC_PAPER", | |
| "LC_TELEPHONE", | |
| "LC_TIME", | |
| }; | |
| int i; | |
| env_array = g_ptr_array_new_with_free_func (g_free); | |
| add_exports (env_array, default_exports, G_N_ELEMENTS (default_exports)); | |
| if (!use_ld_so_cache) | |
| add_exports (env_array, no_ld_so_cache_exports, G_N_ELEMENTS (no_ld_so_cache_exports)); | |
| if (devel) | |
| add_exports (env_array, devel_exports, G_N_ELEMENTS (devel_exports)); | |
| for (i = 0; i < G_N_ELEMENTS (copy); i++) | |
| { | |
| const char *current = g_getenv (copy[i]); | |
| if (current) | |
| g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", copy[i], current)); | |
| } | |
| if (!devel) | |
| { | |
| for (i = 0; i < G_N_ELEMENTS (copy_nodevel); i++) | |
| { | |
| const char *current = g_getenv (copy_nodevel[i]); | |
| if (current) | |
| g_ptr_array_add (env_array, g_strdup_printf ("%s=%s", copy_nodevel[i], current)); | |
| } | |
| } | |
| g_ptr_array_add (env_array, NULL); | |
| return (char **) g_ptr_array_free (env_array, FALSE); | |
| } | |
| static char ** | |
| apply_exports (char **envp, | |
| const ExportData *exports, | |
| gsize n_exports) | |
| { | |
| int i; | |
| for (i = 0; i < n_exports; i++) | |
| { | |
| const char *value = exports[i].val; | |
| if (value) | |
| envp = g_environ_setenv (envp, exports[i].env, value, TRUE); | |
| else | |
| envp = g_environ_unsetenv (envp, exports[i].env); | |
| } | |
| return envp; | |
| } | |
| void | |
| flatpak_run_apply_env_default (FlatpakBwrap *bwrap, gboolean use_ld_so_cache) | |
| { | |
| bwrap->envp = apply_exports (bwrap->envp, default_exports, G_N_ELEMENTS (default_exports)); | |
| if (!use_ld_so_cache) | |
| bwrap->envp = apply_exports (bwrap->envp, no_ld_so_cache_exports, G_N_ELEMENTS (no_ld_so_cache_exports)); | |
| } | |
| void | |
| flatpak_run_apply_env_appid (FlatpakBwrap *bwrap, | |
| GFile *app_dir) | |
| { | |
| g_autoptr(GFile) app_dir_data = NULL; | |
| g_autoptr(GFile) app_dir_config = NULL; | |
| g_autoptr(GFile) app_dir_cache = NULL; | |
| app_dir_data = g_file_get_child (app_dir, "data"); | |
| app_dir_config = g_file_get_child (app_dir, "config"); | |
| app_dir_cache = g_file_get_child (app_dir, "cache"); | |
| flatpak_bwrap_set_env (bwrap, "XDG_DATA_HOME", flatpak_file_get_path_cached (app_dir_data), TRUE); | |
| flatpak_bwrap_set_env (bwrap, "XDG_CONFIG_HOME", flatpak_file_get_path_cached (app_dir_config), TRUE); | |
| flatpak_bwrap_set_env (bwrap, "XDG_CACHE_HOME", flatpak_file_get_path_cached (app_dir_cache), TRUE); | |
| } | |
| void | |
| flatpak_run_apply_env_vars (FlatpakBwrap *bwrap, FlatpakContext *context) | |
| { | |
| GHashTableIter iter; | |
| gpointer key, value; | |
| g_hash_table_iter_init (&iter, context->env_vars); | |
| while (g_hash_table_iter_next (&iter, &key, &value)) | |
| { | |
| const char *var = key; | |
| const char *val = value; | |
| if (val && val[0] != 0) | |
| flatpak_bwrap_set_env (bwrap, var, val, TRUE); | |
| else | |
| flatpak_bwrap_unset_env (bwrap, var); | |
| } | |
| } | |
| GFile * | |
| flatpak_get_data_dir (const char *app_id) | |
| { | |
| g_autoptr(GFile) home = g_file_new_for_path (g_get_home_dir ()); | |
| g_autoptr(GFile) var_app = g_file_resolve_relative_path (home, ".var/app"); | |
| return g_file_get_child (var_app, app_id); | |
| } | |
| GFile * | |
| flatpak_ensure_data_dir (const char *app_id, | |
| GCancellable *cancellable, | |
| GError **error) | |
| { | |
| g_autoptr(GFile) dir = flatpak_get_data_dir (app_id); | |
| g_autoptr(GFile) data_dir = g_file_get_child (dir, "data"); | |
| g_autoptr(GFile) cache_dir = g_file_get_child (dir, "cache"); | |
| g_autoptr(GFile) fontconfig_cache_dir = g_file_get_child (cache_dir, "fontconfig"); | |
| g_autoptr(GFile) tmp_dir = g_file_get_child (cache_dir, "tmp"); | |
| g_autoptr(GFile) config_dir = g_file_get_child (dir, "config"); | |
| if (!flatpak_mkdir_p (data_dir, cancellable, error)) | |
| return NULL; | |
| if (!flatpak_mkdir_p (cache_dir, cancellable, error)) | |
| return NULL; | |
| if (!flatpak_mkdir_p (fontconfig_cache_dir, cancellable, error)) | |
| return NULL; | |
| if (!flatpak_mkdir_p (tmp_dir, cancellable, error)) | |
| return NULL; | |
| if (!flatpak_mkdir_p (config_dir, cancellable, error)) | |
| return NULL; | |
| return g_object_ref (dir); | |
| } | |
| struct JobData | |
| { | |
| char *job; | |
| GMainLoop *main_loop; | |
| }; | |
| static void | |
| job_removed_cb (SystemdManager *manager, | |
| guint32 id, | |
| char *job, | |
| char *unit, | |
| char *result, | |
| struct JobData *data) | |
| { | |
| if (strcmp (job, data->job) == 0) | |
| g_main_loop_quit (data->main_loop); | |
| } | |
| gboolean | |
| flatpak_run_in_transient_unit (const char *appid, GError **error) | |
| { | |
| g_autoptr(GDBusConnection) conn = NULL; | |
| g_autofree char *path = NULL; | |
| g_autofree char *address = NULL; | |
| g_autofree char *name = NULL; | |
| g_autofree char *job = NULL; | |
| SystemdManager *manager = NULL; | |
| GVariantBuilder builder; | |
| GVariant *properties = NULL; | |
| GVariant *aux = NULL; | |
| guint32 pid; | |
| GMainContext *main_context = NULL; | |
| GMainLoop *main_loop = NULL; | |
| struct JobData data; | |
| gboolean res = FALSE; | |
| path = g_strdup_printf ("/run/user/%d/systemd/private", getuid ()); | |
| if (!g_file_test (path, G_FILE_TEST_EXISTS)) | |
| return flatpak_fail (error, | |
| "No systemd user session available, cgroups not available"); | |
| main_context = g_main_context_new (); | |
| main_loop = g_main_loop_new (main_context, FALSE); | |
| g_main_context_push_thread_default (main_context); | |
| address = g_strconcat ("unix:path=", path, NULL); | |
| conn = g_dbus_connection_new_for_address_sync (address, | |
| G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT, | |
| NULL, | |
| NULL, error); | |
| if (!conn) | |
| goto out; | |
| manager = systemd_manager_proxy_new_sync (conn, | |
| G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, | |
| NULL, | |
| "/org/freedesktop/systemd1", | |
| NULL, error); | |
| if (!manager) | |
| goto out; | |
| name = g_strdup_printf ("flatpak-%s-%d.scope", appid, getpid ()); | |
| g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(sv)")); | |
| pid = getpid (); | |
| g_variant_builder_add (&builder, "(sv)", | |
| "PIDs", | |
| g_variant_new_fixed_array (G_VARIANT_TYPE ("u"), | |
| &pid, 1, sizeof (guint32)) | |
| ); | |
| properties = g_variant_builder_end (&builder); | |
| aux = g_variant_new_array (G_VARIANT_TYPE ("(sa(sv))"), NULL, 0); | |
| if (!systemd_manager_call_start_transient_unit_sync (manager, | |
| name, | |
| "fail", | |
| properties, | |
| aux, | |
| &job, | |
| NULL, | |
| error)) | |
| goto out; | |
| data.job = job; | |
| data.main_loop = main_loop; | |
| g_signal_connect (manager, "job-removed", G_CALLBACK (job_removed_cb), &data); | |
| g_main_loop_run (main_loop); | |
| res = TRUE; | |
| out: | |
| if (main_context) | |
| { | |
| g_main_context_pop_thread_default (main_context); | |
| g_main_context_unref (main_context); | |
| } | |
| if (main_loop) | |
| g_main_loop_unref (main_loop); | |
| if (manager) | |
| g_object_unref (manager); | |
| return res; | |
| } | |
| static void | |
| add_font_path_args (FlatpakBwrap *bwrap) | |
| { | |
| g_autoptr(GFile) home = NULL; | |
| g_autoptr(GFile) user_font1 = NULL; | |
| g_autoptr(GFile) user_font2 = NULL; | |
| g_autoptr(GFile) user_font_cache = NULL; | |
| g_auto(GStrv) system_cache_dirs = NULL; | |
| gboolean found_cache = FALSE; | |
| int i; | |
| if (g_file_test (SYSTEM_FONTS_DIR, G_FILE_TEST_EXISTS)) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", SYSTEM_FONTS_DIR, "/run/host/fonts", | |
| NULL); | |
| } | |
| system_cache_dirs = g_strsplit (SYSTEM_FONT_CACHE_DIRS, ":", 0); | |
| for (i = 0; system_cache_dirs[i] != NULL; i++) | |
| { | |
| if (g_file_test (system_cache_dirs[i], G_FILE_TEST_EXISTS)) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", system_cache_dirs[i], "/run/host/fonts-cache", | |
| NULL); | |
| found_cache = TRUE; | |
| break; | |
| } | |
| } | |
| if (!found_cache) | |
| { | |
| /* We ensure these directories are never writable, or fontconfig | |
| will use them to write the default cache */ | |
| flatpak_bwrap_add_args (bwrap, | |
| "--tmpfs", "/run/host/fonts-cache", | |
| "--remount-ro", "/run/host/fonts-cache", | |
| NULL); | |
| } | |
| home = g_file_new_for_path (g_get_home_dir ()); | |
| user_font1 = g_file_resolve_relative_path (home, ".local/share/fonts"); | |
| user_font2 = g_file_resolve_relative_path (home, ".fonts"); | |
| if (g_file_query_exists (user_font1, NULL)) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", flatpak_file_get_path_cached (user_font1), "/run/host/user-fonts", | |
| NULL); | |
| } | |
| else if (g_file_query_exists (user_font2, NULL)) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", flatpak_file_get_path_cached (user_font2), "/run/host/user-fonts", | |
| NULL); | |
| } | |
| user_font_cache = g_file_resolve_relative_path (home, ".cache/fontconfig"); | |
| if (g_file_query_exists (user_font_cache, NULL)) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", flatpak_file_get_path_cached (user_font_cache), "/run/host/user-fonts-cache", | |
| NULL); | |
| } | |
| else | |
| { | |
| /* We ensure these directories are never writable, or fontconfig | |
| will use them to write the default cache */ | |
| flatpak_bwrap_add_args (bwrap, | |
| "--tmpfs", "/run/host/user-fonts-cache", | |
| "--remount-ro", "/run/host/user-fonts-cache", | |
| NULL); | |
| } | |
| } | |
| static void | |
| add_icon_path_args (FlatpakBwrap *bwrap) | |
| { | |
| if (g_file_test ("/usr/share/icons", G_FILE_TEST_IS_DIR)) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", "/usr/share/icons", "/run/host/share/icons", | |
| NULL); | |
| } | |
| } | |
| static void | |
| add_default_permissions (FlatpakContext *app_context) | |
| { | |
| flatpak_context_set_session_bus_policy (app_context, | |
| "org.freedesktop.portal.*", | |
| FLATPAK_POLICY_TALK); | |
| } | |
| FlatpakContext * | |
| flatpak_app_compute_permissions (GKeyFile *app_metadata, | |
| GKeyFile *runtime_metadata, | |
| GError **error) | |
| { | |
| g_autoptr(FlatpakContext) app_context = NULL; | |
| app_context = flatpak_context_new (); | |
| add_default_permissions (app_context); | |
| if (runtime_metadata != NULL && | |
| !flatpak_context_load_metadata (app_context, runtime_metadata, error)) | |
| return NULL; | |
| if (app_metadata != NULL && | |
| !flatpak_context_load_metadata (app_context, app_metadata, error)) | |
| return NULL; | |
| return g_steal_pointer (&app_context); | |
| } | |
| gboolean | |
| flatpak_run_add_app_info_args (FlatpakBwrap *bwrap, | |
| GFile *app_files, | |
| GVariant *app_deploy_data, | |
| const char *app_extensions, | |
| GFile *runtime_files, | |
| GVariant *runtime_deploy_data, | |
| const char *runtime_extensions, | |
| const char *app_id, | |
| const char *app_branch, | |
| const char *runtime_ref, | |
| FlatpakContext *final_app_context, | |
| char **app_info_path_out, | |
| GError **error) | |
| { | |
| g_autofree char *tmp_path = NULL; | |
| int fd, fd2; | |
| g_autoptr(GKeyFile) keyfile = NULL; | |
| g_autofree char *runtime_path = NULL; | |
| g_autofree char *old_dest = g_strdup_printf ("/run/user/%d/flatpak-info", getuid ()); | |
| const char *group; | |
| fd = g_file_open_tmp ("flatpak-context-XXXXXX", &tmp_path, NULL); | |
| if (fd < 0) | |
| { | |
| int errsv = errno; | |
| g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), | |
| _("Failed to open flatpak-info temp file: %s"), g_strerror (errsv)); | |
| return FALSE; | |
| } | |
| close (fd); | |
| keyfile = g_key_file_new (); | |
| if (app_files) | |
| group = FLATPAK_METADATA_GROUP_APPLICATION; | |
| else | |
| group = FLATPAK_METADATA_GROUP_RUNTIME; | |
| g_key_file_set_string (keyfile, group, FLATPAK_METADATA_KEY_NAME, app_id); | |
| g_key_file_set_string (keyfile, group, FLATPAK_METADATA_KEY_RUNTIME, | |
| runtime_ref); | |
| if (app_files) | |
| { | |
| g_autofree char *app_path = g_file_get_path (app_files); | |
| g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE, | |
| FLATPAK_METADATA_KEY_APP_PATH, app_path); | |
| } | |
| if (app_deploy_data) | |
| g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE, | |
| FLATPAK_METADATA_KEY_APP_COMMIT, flatpak_deploy_data_get_commit (app_deploy_data)); | |
| if (app_extensions && *app_extensions != 0) | |
| g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE, | |
| FLATPAK_METADATA_KEY_APP_EXTENSIONS, app_extensions); | |
| runtime_path = g_file_get_path (runtime_files); | |
| g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE, | |
| FLATPAK_METADATA_KEY_RUNTIME_PATH, runtime_path); | |
| if (runtime_deploy_data) | |
| g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE, | |
| FLATPAK_METADATA_KEY_RUNTIME_COMMIT, flatpak_deploy_data_get_commit (runtime_deploy_data)); | |
| if (runtime_extensions && *runtime_extensions != 0) | |
| g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE, | |
| FLATPAK_METADATA_KEY_RUNTIME_EXTENSIONS, runtime_extensions); | |
| if (app_branch != NULL) | |
| g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE, | |
| FLATPAK_METADATA_KEY_BRANCH, app_branch); | |
| g_key_file_set_string (keyfile, FLATPAK_METADATA_GROUP_INSTANCE, | |
| FLATPAK_METADATA_KEY_FLATPAK_VERSION, PACKAGE_VERSION); | |
| if ((final_app_context->sockets & FLATPAK_CONTEXT_SOCKET_SESSION_BUS) == 0) | |
| g_key_file_set_boolean (keyfile, FLATPAK_METADATA_GROUP_INSTANCE, | |
| FLATPAK_METADATA_KEY_SESSION_BUS_PROXY, TRUE); | |
| if ((final_app_context->sockets & FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS) == 0) | |
| g_key_file_set_boolean (keyfile, FLATPAK_METADATA_GROUP_INSTANCE, | |
| FLATPAK_METADATA_KEY_SYSTEM_BUS_PROXY, TRUE); | |
| flatpak_context_save_metadata (final_app_context, TRUE, keyfile); | |
| if (!g_key_file_save_to_file (keyfile, tmp_path, error)) | |
| return FALSE; | |
| /* We want to create a file on /.flatpak-info that the app cannot modify, which | |
| we do by creating a read-only bind mount. This way one can openat() | |
| /proc/$pid/root, and if that succeeds use openat via that to find the | |
| unfakable .flatpak-info file. However, there is a tiny race in that if | |
| you manage to open /proc/$pid/root, but then the pid dies, then | |
| every mount but the root is unmounted in the namespace, so the | |
| .flatpak-info will be empty. We fix this by first creating a real file | |
| with the real info in, then bind-mounting on top of that, the same info. | |
| This way even if the bind-mount is unmounted we can find the real data. | |
| */ | |
| fd = open (tmp_path, O_RDONLY); | |
| if (fd == -1) | |
| { | |
| int errsv = errno; | |
| g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), | |
| _("Failed to open temp file: %s"), g_strerror (errsv)); | |
| return FALSE; | |
| } | |
| fd2 = open (tmp_path, O_RDONLY); | |
| if (fd2 == -1) | |
| { | |
| close (fd); | |
| int errsv = errno; | |
| g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), | |
| _("Failed to open temp file: %s"), g_strerror (errsv)); | |
| return FALSE; | |
| } | |
| unlink (tmp_path); | |
| flatpak_bwrap_add_args_data_fd (bwrap, | |
| "--file", fd, "/.flatpak-info"); | |
| flatpak_bwrap_add_args_data_fd (bwrap, | |
| "--ro-bind-data", fd2, "/.flatpak-info"); | |
| flatpak_bwrap_add_args (bwrap, | |
| "--symlink", "../../../.flatpak-info", old_dest, | |
| NULL); | |
| if (app_info_path_out != NULL) | |
| *app_info_path_out = g_strdup_printf ("/proc/self/fd/%d", fd); | |
| return TRUE; | |
| } | |
| static void | |
| add_monitor_path_args (gboolean use_session_helper, | |
| FlatpakBwrap *bwrap) | |
| { | |
| g_autoptr(AutoFlatpakSessionHelper) session_helper = NULL; | |
| g_autofree char *monitor_path = NULL; | |
| if (use_session_helper) | |
| { | |
| session_helper = | |
| flatpak_session_helper_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, | |
| G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES | G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS, | |
| "org.freedesktop.Flatpak", | |
| "/org/freedesktop/Flatpak/SessionHelper", | |
| NULL, NULL); | |
| } | |
| if (session_helper && | |
| flatpak_session_helper_call_request_monitor_sync (session_helper, | |
| &monitor_path, | |
| NULL, NULL)) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", monitor_path, "/run/host/monitor", | |
| "--symlink", "/run/host/monitor/localtime", "/etc/localtime", | |
| "--symlink", "/run/host/monitor/resolv.conf", "/etc/resolv.conf", | |
| "--symlink", "/run/host/monitor/host.conf", "/etc/host.conf", | |
| "--symlink", "/run/host/monitor/hosts", "/etc/hosts", | |
| NULL); | |
| } | |
| else | |
| { | |
| /* /etc/localtime and /etc/resolv.conf can not exist (or be symlinks to | |
| * non-existing targets), in which case we don't want to attempt to create | |
| * bogus symlinks or bind mounts, as that will cause flatpak run to fail. | |
| */ | |
| if (g_file_test ("/etc/localtime", G_FILE_TEST_EXISTS)) | |
| { | |
| char localtime[PATH_MAX + 1]; | |
| ssize_t symlink_size; | |
| gboolean is_reachable = FALSE; | |
| symlink_size = readlink ("/etc/localtime", localtime, sizeof (localtime) - 1); | |
| if (symlink_size > 0) | |
| { | |
| g_autoptr(GFile) base_file = NULL; | |
| g_autoptr(GFile) target_file = NULL; | |
| g_autofree char *target_canonical = NULL; | |
| /* readlink() does not append a null byte to the buffer. */ | |
| localtime[symlink_size] = 0; | |
| base_file = g_file_new_for_path ("/etc"); | |
| target_file = g_file_resolve_relative_path (base_file, localtime); | |
| target_canonical = g_file_get_path (target_file); | |
| is_reachable = g_str_has_prefix (target_canonical, "/usr/"); | |
| } | |
| if (is_reachable) | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--symlink", localtime, "/etc/localtime", | |
| NULL); | |
| } | |
| else | |
| { | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", "/etc/localtime", "/etc/localtime", | |
| NULL); | |
| } | |
| } | |
| if (g_file_test ("/etc/resolv.conf", G_FILE_TEST_EXISTS)) | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", "/etc/resolv.conf", "/etc/resolv.conf", | |
| NULL); | |
| if (g_file_test ("/etc/host.conf", G_FILE_TEST_EXISTS)) | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", "/etc/host.conf", "/etc/host.conf", | |
| NULL); | |
| if (g_file_test ("/etc/hosts", G_FILE_TEST_EXISTS)) | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", "/etc/hosts", "/etc/hosts", | |
| NULL); | |
| } | |
| } | |
| static void | |
| add_document_portal_args (FlatpakBwrap *bwrap, | |
| const char *app_id, | |
| char **out_mount_path) | |
| { | |
| g_autoptr(GDBusConnection) session_bus = NULL; | |
| g_autofree char *doc_mount_path = NULL; | |
| session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL); | |
| if (session_bus) | |
| { | |
| g_autoptr(GError) local_error = NULL; | |
| g_autoptr(GDBusMessage) reply = NULL; | |
| g_autoptr(GDBusMessage) msg = | |
| g_dbus_message_new_method_call ("org.freedesktop.portal.Documents", | |
| "/org/freedesktop/portal/documents", | |
| "org.freedesktop.portal.Documents", | |
| "GetMountPoint"); | |
| g_dbus_message_set_body (msg, g_variant_new ("()")); | |
| reply = | |
| g_dbus_connection_send_message_with_reply_sync (session_bus, msg, | |
| G_DBUS_SEND_MESSAGE_FLAGS_NONE, | |
| 30000, | |
| NULL, | |
| NULL, | |
| NULL); | |
| if (reply) | |
| { | |
| if (g_dbus_message_to_gerror (reply, &local_error)) | |
| { | |
| g_message ("Can't get document portal: %s", local_error->message); | |
| } | |
| else | |
| { | |
| g_autofree char *src_path = NULL; | |
| g_autofree char *dst_path = NULL; | |
| g_variant_get (g_dbus_message_get_body (reply), | |
| "(^ay)", &doc_mount_path); | |
| src_path = g_strdup_printf ("%s/by-app/%s", | |
| doc_mount_path, app_id); | |
| dst_path = g_strdup_printf ("/run/user/%d/doc", getuid ()); | |
| flatpak_bwrap_add_args (bwrap, "--bind", src_path, dst_path, NULL); | |
| } | |
| } | |
| } | |
| *out_mount_path = g_steal_pointer (&doc_mount_path); | |
| } | |
| static gchar * | |
| join_args (GPtrArray *argv_array, gsize *len_out) | |
| { | |
| gchar *string; | |
| gchar *ptr; | |
| gint i; | |
| gsize len = 0; | |
| for (i = 0; i < argv_array->len && argv_array->pdata[i] != NULL; i++) | |
| len += strlen (argv_array->pdata[i]) + 1; | |
| string = g_new (gchar, len); | |
| *string = 0; | |
| ptr = string; | |
| for (i = 0; i < argv_array->len && argv_array->pdata[i] != NULL; i++) | |
| ptr = g_stpcpy (ptr, argv_array->pdata[i]) + 1; | |
| *len_out = len; | |
| return string; | |
| } | |
| typedef struct { | |
| int sync_fd; | |
| int app_info_fd; | |
| int bwrap_args_fd; | |
| } DbusProxySpawnData; | |
| static void | |
| dbus_spawn_child_setup (gpointer user_data) | |
| { | |
| DbusProxySpawnData *data = user_data; | |
| /* Unset CLOEXEC */ | |
| fcntl (data->sync_fd, F_SETFD, 0); | |
| fcntl (data->app_info_fd, F_SETFD, 0); | |
| fcntl (data->bwrap_args_fd, F_SETFD, 0); | |
| } | |
| /* This wraps the argv in a bwrap call, primary to allow the | |
| command to be run with a proper /.flatpak-info with data | |
| taken from app_info_fd */ | |
| static gboolean | |
| prepend_bwrap_argv_wrapper (GPtrArray *argv, | |
| int app_info_fd, | |
| int *bwrap_fd_out, | |
| GError **error) | |
| { | |
| int i = 0; | |
| g_auto(GLnxDirFdIterator) dir_iter = { 0 }; | |
| struct dirent *dent; | |
| g_autoptr(GPtrArray) bwrap_args = g_ptr_array_new_with_free_func (g_free); | |
| gsize bwrap_args_len; | |
| g_auto(GLnxTmpfile) args_tmpf = { 0, }; | |
| g_autofree char *bwrap_args_data = NULL; | |
| g_autofree char *proxy_socket_dir = g_build_filename (g_get_user_runtime_dir (), ".dbus-proxy/", NULL); | |
| if (!glnx_dirfd_iterator_init_at (AT_FDCWD, "/", FALSE, &dir_iter, error)) | |
| return FALSE; | |
| while (TRUE) | |
| { | |
| if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dir_iter, &dent, NULL, error)) | |
| return FALSE; | |
| if (dent == NULL) | |
| break; | |
| if (strcmp (dent->d_name, ".flatpak-info") == 0) | |
| continue; | |
| if (dent->d_type == DT_DIR) | |
| { | |
| if (strcmp (dent->d_name, "tmp") == 0 || | |
| strcmp (dent->d_name, "var") == 0 || | |
| strcmp (dent->d_name, "run") == 0) | |
| g_ptr_array_add (bwrap_args, g_strdup ("--bind")); | |
| else | |
| g_ptr_array_add (bwrap_args, g_strdup ("--ro-bind")); | |
| g_ptr_array_add (bwrap_args, g_strconcat ("/", dent->d_name, NULL)); | |
| g_ptr_array_add (bwrap_args, g_strconcat ("/", dent->d_name, NULL)); | |
| } | |
| else if (dent->d_type == DT_LNK) | |
| { | |
| ssize_t symlink_size; | |
| char path_buffer[PATH_MAX + 1]; | |
| symlink_size = readlinkat (dir_iter.fd, dent->d_name, path_buffer, sizeof (path_buffer) - 1); | |
| if (symlink_size < 0) | |
| { | |
| glnx_set_error_from_errno (error); | |
| return FALSE; | |
| } | |
| path_buffer[symlink_size] = 0; | |
| g_ptr_array_add (bwrap_args, g_strdup ("--symlink")); | |
| g_ptr_array_add (bwrap_args, g_strdup (path_buffer)); | |
| g_ptr_array_add (bwrap_args, g_strconcat ("/", dent->d_name, NULL)); | |
| } | |
| } | |
| g_ptr_array_add (bwrap_args, g_strdup ("--bind")); | |
| g_ptr_array_add (bwrap_args, g_strdup (proxy_socket_dir)); | |
| g_ptr_array_add (bwrap_args, g_strdup (proxy_socket_dir)); | |
| /* This is a file rather than a bind mount, because it will then | |
| not be unmounted from the namespace when the namespace dies. */ | |
| g_ptr_array_add (bwrap_args, g_strdup ("--file")); | |
| g_ptr_array_add (bwrap_args, g_strdup_printf ("%d", app_info_fd)); | |
| g_ptr_array_add (bwrap_args, g_strdup ("/.flatpak-info")); | |
| g_ptr_array_add (bwrap_args, NULL); | |
| { | |
| g_autofree char *commandline = flatpak_quote_argv ((const char **) bwrap_args->pdata); | |
| flatpak_debug2 ("bwrap args '%s'", commandline); | |
| } | |
| bwrap_args_data = join_args (bwrap_args, &bwrap_args_len); | |
| if (!buffer_to_sealed_memfd_or_tmpfile (&args_tmpf, "bwrap-args", bwrap_args_data, bwrap_args_len, error)) | |
| return FALSE; | |
| g_ptr_array_insert (argv, i++, g_strdup (flatpak_get_bwrap ())); | |
| g_ptr_array_insert (argv, i++, g_strdup ("--args")); | |
| g_ptr_array_insert (argv, i++, g_strdup_printf ("%d", args_tmpf.fd)); | |
| *bwrap_fd_out = glnx_steal_fd (&args_tmpf.fd); | |
| return TRUE; | |
| } | |
| static gboolean | |
| has_args (GPtrArray *args) | |
| { | |
| return args != NULL && args->len > 0; | |
| } | |
| static void | |
| append_proxy_args (GPtrArray *dbus_proxy_argv, | |
| GPtrArray *args, | |
| gboolean enable_logging) | |
| { | |
| if (has_args (args)) | |
| { | |
| int i; | |
| for (i = 0; i < args->len; i++) | |
| g_ptr_array_add (dbus_proxy_argv, g_strdup (args->pdata[i])); | |
| if (enable_logging) | |
| g_ptr_array_add (dbus_proxy_argv, g_strdup ("--log")); | |
| } | |
| } | |
| static gboolean | |
| add_dbus_proxy_args (GPtrArray *argv_array, | |
| GPtrArray *session_dbus_proxy_argv, | |
| gboolean enable_session_logging, | |
| GPtrArray *system_dbus_proxy_argv, | |
| gboolean enable_system_logging, | |
| GPtrArray *a11y_dbus_proxy_argv, | |
| gboolean enable_a11y_logging, | |
| int sync_fds[2], | |
| const char *app_info_path, | |
| GError **error) | |
| { | |
| char x = 'x'; | |
| const char *proxy; | |
| g_autofree char *commandline = NULL; | |
| DbusProxySpawnData spawn_data; | |
| glnx_autofd int app_info_fd = -1; | |
| glnx_autofd int bwrap_args_fd = -1; | |
| g_autoptr(GPtrArray) dbus_proxy_argv = NULL; | |
| if (!has_args (session_dbus_proxy_argv) && | |
| !has_args (system_dbus_proxy_argv) && | |
| !has_args (a11y_dbus_proxy_argv)) | |
| return TRUE; | |
| if (sync_fds[0] == -1) | |
| { | |
| access ("sync_fds", 0); | |
| if (pipe (sync_fds) < 0) | |
| { | |
| g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno), | |
| _("Unable to create sync pipe")); | |
| return FALSE; | |
| } | |
| add_args_data_fd (argv_array, NULL, | |
| "--sync-fd", sync_fds[0], NULL); | |
| } | |
| proxy = g_getenv ("FLATPAK_DBUSPROXY"); | |
| if (proxy == NULL) | |
| proxy = DBUSPROXY; | |
| dbus_proxy_argv = g_ptr_array_new_with_free_func (g_free); | |
| g_ptr_array_add (dbus_proxy_argv, g_strdup (proxy)); | |
| g_ptr_array_add (dbus_proxy_argv, g_strdup_printf ("--fd=%d", sync_fds[1])); | |
| append_proxy_args (dbus_proxy_argv, session_dbus_proxy_argv, enable_session_logging); | |
| append_proxy_args (dbus_proxy_argv, system_dbus_proxy_argv, enable_system_logging); | |
| append_proxy_args (dbus_proxy_argv, a11y_dbus_proxy_argv, enable_a11y_logging); | |
| g_ptr_array_add (dbus_proxy_argv, NULL); /* NULL terminate */ | |
| app_info_fd = open (app_info_path, O_RDONLY); | |
| if (app_info_fd == -1) | |
| { | |
| int errsv = errno; | |
| g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), | |
| _("Failed to open app info file: %s"), g_strerror (errsv)); | |
| return FALSE; | |
| } | |
| if (!prepend_bwrap_argv_wrapper (dbus_proxy_argv, app_info_fd, &bwrap_args_fd, error)) | |
| return FALSE; | |
| commandline = flatpak_quote_argv ((const char **) dbus_proxy_argv->pdata); | |
| flatpak_debug2 ("Running '%s'", commandline); | |
| spawn_data.sync_fd = sync_fds[1]; | |
| spawn_data.app_info_fd = app_info_fd; | |
| spawn_data.bwrap_args_fd = bwrap_args_fd; | |
| if (!g_spawn_async (NULL, | |
| (char **) dbus_proxy_argv->pdata, | |
| NULL, | |
| G_SPAWN_SEARCH_PATH, | |
| dbus_spawn_child_setup, | |
| &spawn_data, | |
| NULL, error)) | |
| { | |
| close (sync_fds[0]); | |
| close (sync_fds[1]); | |
| return FALSE; | |
| } | |
| /* Sync with proxy, i.e. wait until its listening on the sockets */ | |
| if (read (sync_fds[0], &x, 1) != 1) | |
| { | |
| g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno), | |
| _("Failed to sync with dbus proxy")); | |
| close (sync_fds[0]); | |
| close (sync_fds[1]); | |
| return FALSE; | |
| } | |
| return TRUE; | |
| } | |
| #ifdef ENABLE_SECCOMP | |
| static const uint32_t seccomp_x86_64_extra_arches[] = { SCMP_ARCH_X86, 0, }; | |
| #ifdef SCMP_ARCH_AARCH64 | |
| static const uint32_t seccomp_aarch64_extra_arches[] = { SCMP_ARCH_ARM, 0 }; | |
| #endif | |
| static inline void | |
| cleanup_seccomp (void *p) | |
| { | |
| scmp_filter_ctx *pp = (scmp_filter_ctx *) p; | |
| if (*pp) | |
| seccomp_release (*pp); | |
| } | |
| static gboolean | |
| setup_seccomp (FlatpakBwrap *bwrap, | |
| const char *arch, | |
| gulong allowed_personality, | |
| gboolean multiarch, | |
| gboolean devel, | |
| GError **error) | |
| { | |
| __attribute__((cleanup (cleanup_seccomp))) scmp_filter_ctx seccomp = NULL; | |
| /**** BEGIN NOTE ON CODE SHARING | |
| * | |
| * There are today a number of different Linux container | |
| * implementations. That will likely continue for long into the | |
| * future. But we can still try to share code, and it's important | |
| * to do so because it affects what library and application writers | |
| * can do, and we should support code portability between different | |
| * container tools. | |
| * | |
| * This syscall blacklist is copied from linux-user-chroot, which was in turn | |
| * clearly influenced by the Sandstorm.io blacklist. | |
| * | |
| * If you make any changes here, I suggest sending the changes along | |
| * to other sandbox maintainers. Using the libseccomp list is also | |
| * an appropriate venue: | |
| * https://groups.google.com/forum/#!topic/libseccomp | |
| * | |
| * A non-exhaustive list of links to container tooling that might | |
| * want to share this blacklist: | |
| * | |
| * https://github.com/sandstorm-io/sandstorm | |
| * in src/sandstorm/supervisor.c++ | |
| * http://cgit.freedesktop.org/xdg-app/xdg-app/ | |
| * in common/flatpak-run.c | |
| * https://git.gnome.org/browse/linux-user-chroot | |
| * in src/setup-seccomp.c | |
| * | |
| **** END NOTE ON CODE SHARING | |
| */ | |
| struct | |
| { | |
| int scall; | |
| struct scmp_arg_cmp *arg; | |
| } syscall_blacklist[] = { | |
| /* Block dmesg */ | |
| {SCMP_SYS (syslog)}, | |
| /* Useless old syscall */ | |
| {SCMP_SYS (uselib)}, | |
| /* Don't allow you to switch to bsd emulation or whatnot */ | |
| {SCMP_SYS (personality), &SCMP_A0(SCMP_CMP_NE, allowed_personality)}, | |
| /* Don't allow disabling accounting */ | |
| {SCMP_SYS (acct)}, | |
| /* 16-bit code is unnecessary in the sandbox, and modify_ldt is a | |
| historic source of interesting information leaks. */ | |
| {SCMP_SYS (modify_ldt)}, | |
| /* Don't allow reading current quota use */ | |
| {SCMP_SYS (quotactl)}, | |
| /* Don't allow access to the kernel keyring */ | |
| {SCMP_SYS (add_key)}, | |
| {SCMP_SYS (keyctl)}, | |
| {SCMP_SYS (request_key)}, | |
| /* Scary VM/NUMA ops */ | |
| {SCMP_SYS (move_pages)}, | |
| {SCMP_SYS (mbind)}, | |
| {SCMP_SYS (get_mempolicy)}, | |
| {SCMP_SYS (set_mempolicy)}, | |
| {SCMP_SYS (migrate_pages)}, | |
| /* Don't allow subnamespace setups: */ | |
| {SCMP_SYS (unshare)}, | |
| {SCMP_SYS (mount)}, | |
| {SCMP_SYS (pivot_root)}, | |
| {SCMP_SYS (clone), &SCMP_A0 (SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)}, | |
| /* Don't allow faking input to the controlling tty (CVE-2017-5226) */ | |
| {SCMP_SYS (ioctl), &SCMP_A1(SCMP_CMP_EQ, (int)TIOCSTI)}, | |
| }; | |
| struct | |
| { | |
| int scall; | |
| struct scmp_arg_cmp *arg; | |
| } syscall_nondevel_blacklist[] = { | |
| /* Profiling operations; we expect these to be done by tools from outside | |
| * the sandbox. In particular perf has been the source of many CVEs. | |
| */ | |
| {SCMP_SYS (perf_event_open)}, | |
| {SCMP_SYS (ptrace)} | |
| }; | |
| /* Blacklist all but unix, inet, inet6 and netlink */ | |
| int socket_family_blacklist[] = { | |
| AF_AX25, | |
| AF_IPX, | |
| AF_APPLETALK, | |
| AF_NETROM, | |
| AF_BRIDGE, | |
| AF_ATMPVC, | |
| AF_X25, | |
| AF_ROSE, | |
| AF_DECnet, | |
| AF_NETBEUI, | |
| AF_SECURITY, | |
| AF_KEY, | |
| AF_NETLINK + 1, /* Last gets CMP_GE, so order is important */ | |
| }; | |
| int i, r; | |
| g_auto(GLnxTmpfile) seccomp_tmpf = { 0, }; | |
| seccomp = seccomp_init (SCMP_ACT_ALLOW); | |
| if (!seccomp) | |
| return flatpak_fail (error, "Initialize seccomp failed"); | |
| if (arch != NULL) | |
| { | |
| uint32_t arch_id = 0; | |
| const uint32_t *extra_arches = NULL; | |
| if (strcmp (arch, "i386") == 0) | |
| { | |
| arch_id = SCMP_ARCH_X86; | |
| } | |
| else if (strcmp (arch, "x86_64") == 0) | |
| { | |
| arch_id = SCMP_ARCH_X86_64; | |
| extra_arches = seccomp_x86_64_extra_arches; | |
| } | |
| else if (strcmp (arch, "arm") == 0) | |
| { | |
| arch_id = SCMP_ARCH_ARM; | |
| } | |
| #ifdef SCMP_ARCH_AARCH64 | |
| else if (strcmp (arch, "aarch64") == 0) | |
| { | |
| arch_id = SCMP_ARCH_AARCH64; | |
| extra_arches = seccomp_aarch64_extra_arches; | |
| } | |
| #endif | |
| /* We only really need to handle arches on multiarch systems. | |
| * If only one arch is supported the default is fine */ | |
| if (arch_id != 0) | |
| { | |
| /* This *adds* the target arch, instead of replacing the | |
| native one. This is not ideal, because we'd like to only | |
| allow the target arch, but we can't really disallow the | |
| native arch at this point, because then bubblewrap | |
| couldn't continue running. */ | |
| r = seccomp_arch_add (seccomp, arch_id); | |
| if (r < 0 && r != -EEXIST) | |
| return flatpak_fail (error, "Failed to add architecture to seccomp filter"); | |
| if (multiarch && extra_arches != NULL) | |
| { | |
| unsigned i; | |
| for (i = 0; extra_arches[i] != 0; i++) | |
| { | |
| r = seccomp_arch_add (seccomp, extra_arches[i]); | |
| if (r < 0 && r != -EEXIST) | |
| return flatpak_fail (error, "Failed to add multiarch architecture to seccomp filter"); | |
| } | |
| } | |
| } | |
| } | |
| /* TODO: Should we filter the kernel keyring syscalls in some way? | |
| * We do want them to be used by desktop apps, but they could also perhaps | |
| * leak system stuff or secrets from other apps. | |
| */ | |
| for (i = 0; i < G_N_ELEMENTS (syscall_blacklist); i++) | |
| { | |
| int scall = syscall_blacklist[i].scall; | |
| if (syscall_blacklist[i].arg) | |
| r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 1, *syscall_blacklist[i].arg); | |
| else | |
| r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0); | |
| if (r < 0 && r == -EFAULT /* unknown syscall */) | |
| return flatpak_fail (error, "Failed to block syscall %d", scall); | |
| } | |
| if (!devel) | |
| { | |
| for (i = 0; i < G_N_ELEMENTS (syscall_nondevel_blacklist); i++) | |
| { | |
| int scall = syscall_nondevel_blacklist[i].scall; | |
| if (syscall_nondevel_blacklist[i].arg) | |
| r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 1, *syscall_nondevel_blacklist[i].arg); | |
| else | |
| r = seccomp_rule_add (seccomp, SCMP_ACT_ERRNO (EPERM), scall, 0); | |
| if (r < 0 && r == -EFAULT /* unknown syscall */) | |
| return flatpak_fail (error, "Failed to block syscall %d", scall); | |
| } | |
| } | |
| /* Socket filtering doesn't work on e.g. i386, so ignore failures here | |
| * However, we need to user seccomp_rule_add_exact to avoid libseccomp doing | |
| * something else: https://github.com/seccomp/libseccomp/issues/8 */ | |
| for (i = 0; i < G_N_ELEMENTS (socket_family_blacklist); i++) | |
| { | |
| int family = socket_family_blacklist[i]; | |
| if (i == G_N_ELEMENTS (socket_family_blacklist) - 1) | |
| seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 (SCMP_CMP_GE, family)); | |
| else | |
| seccomp_rule_add_exact (seccomp, SCMP_ACT_ERRNO (EAFNOSUPPORT), SCMP_SYS (socket), 1, SCMP_A0 (SCMP_CMP_EQ, family)); | |
| } | |
| if (!glnx_open_anonymous_tmpfile (O_RDWR | O_CLOEXEC, &seccomp_tmpf, error)) | |
| return FALSE; | |
| if (seccomp_export_bpf (seccomp, seccomp_tmpf.fd) != 0) | |
| return flatpak_fail (error, "Failed to export bpf"); | |
| lseek (seccomp_tmpf.fd, 0, SEEK_SET); | |
| flatpak_bwrap_add_args_data_fd (bwrap, | |
| "--seccomp", glnx_steal_fd (&seccomp_tmpf.fd), NULL); | |
| return TRUE; | |
| } | |
| #endif | |
| static void | |
| flatpak_run_setup_usr_links (FlatpakBwrap *bwrap, | |
| GFile *runtime_files) | |
| { | |
| const char *usr_links[] = {"lib", "lib32", "lib64", "bin", "sbin"}; | |
| int i; | |
| if (runtime_files == NULL) | |
| return; | |
| for (i = 0; i < G_N_ELEMENTS (usr_links); i++) | |
| { | |
| const char *subdir = usr_links[i]; | |
| g_autoptr(GFile) runtime_subdir = g_file_get_child (runtime_files, subdir); | |
| if (g_file_query_exists (runtime_subdir, NULL)) | |
| { | |
| g_autofree char *link = g_strconcat ("usr/", subdir, NULL); | |
| g_autofree char *dest = g_strconcat ("/", subdir, NULL); | |
| flatpak_bwrap_add_args (bwrap, | |
| "--symlink", link, dest, | |
| NULL); | |
| } | |
| } | |
| } | |
| gboolean | |
| flatpak_run_setup_base_argv (FlatpakBwrap *bwrap, | |
| GFile *runtime_files, | |
| GFile *app_id_dir, | |
| const char *arch, | |
| FlatpakRunFlags flags, | |
| GError **error) | |
| { | |
| g_autofree char *run_dir = g_strdup_printf ("/run/user/%d", getuid ()); | |
| g_autofree char *passwd_contents = NULL; | |
| g_autofree char *group_contents = NULL; | |
| struct group *g = getgrgid (getgid ()); | |
| gulong pers; | |
| g_autoptr(GFile) etc = NULL; | |
| passwd_contents = g_strdup_printf ("%s:x:%d:%d:%s:%s:%s\n" | |
| "nfsnobody:x:65534:65534:Unmapped user:/:/sbin/nologin\n", | |
| g_get_user_name (), | |
| getuid (), getgid (), | |
| g_get_real_name (), | |
| g_get_home_dir (), | |
| DEFAULT_SHELL); | |
| group_contents = g_strdup_printf ("%s:x:%d:%s\n" | |
| "nfsnobody:x:65534:\n", | |
| g->gr_name, | |
| getgid (), g_get_user_name ()); | |
| flatpak_bwrap_add_args (bwrap, | |
| "--unshare-pid", | |
| "--proc", "/proc", | |
| "--dir", "/tmp", | |
| "--dir", "/var/tmp", | |
| "--dir", "/run/host", | |
| "--dir", run_dir, | |
| "--setenv", "XDG_RUNTIME_DIR", run_dir, | |
| "--symlink", "../run", "/var/run", | |
| "--ro-bind", "/sys/block", "/sys/block", | |
| "--ro-bind", "/sys/bus", "/sys/bus", | |
| "--ro-bind", "/sys/class", "/sys/class", | |
| "--ro-bind", "/sys/dev", "/sys/dev", | |
| "--ro-bind", "/sys/devices", "/sys/devices", | |
| NULL); | |
| if (flags & FLATPAK_RUN_FLAG_DIE_WITH_PARENT) | |
| flatpak_bwrap_add_args (bwrap, | |
| "--die-with-parent", | |
| NULL); | |
| if (flags & FLATPAK_RUN_FLAG_WRITABLE_ETC) | |
| flatpak_bwrap_add_args (bwrap, | |
| "--dir", "/usr/etc", | |
| "--symlink", "usr/etc", "/etc", | |
| NULL); | |
| if (!flatpak_bwrap_add_args_data (bwrap, "passwd", passwd_contents, -1, "/etc/passwd", error)) | |
| return FALSE; | |
| if (!flatpak_bwrap_add_args_data (bwrap, "group", group_contents, -1, "/etc/group", error)) | |
| return FALSE; | |
| if (g_file_test ("/etc/machine-id", G_FILE_TEST_EXISTS)) | |
| flatpak_bwrap_add_args (bwrap, "--ro-bind", "/etc/machine-id", "/etc/machine-id", NULL); | |
| else if (g_file_test ("/var/lib/dbus/machine-id", G_FILE_TEST_EXISTS)) | |
| flatpak_bwrap_add_args (bwrap, "--ro-bind", "/var/lib/dbus/machine-id", "/etc/machine-id", NULL); | |
| if (runtime_files) | |
| etc = g_file_get_child (runtime_files, "etc"); | |
| if (etc != NULL && | |
| (flags & FLATPAK_RUN_FLAG_WRITABLE_ETC) == 0 && | |
| g_file_query_exists (etc, NULL)) | |
| { | |
| g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; | |
| struct dirent *dent; | |
| char path_buffer[PATH_MAX + 1]; | |
| ssize_t symlink_size; | |
| gboolean inited; | |
| inited = glnx_dirfd_iterator_init_at (AT_FDCWD, flatpak_file_get_path_cached (etc), FALSE, &dfd_iter, NULL); | |
| while (inited) | |
| { | |
| g_autofree char *src = NULL; | |
| g_autofree char *dest = NULL; | |
| if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, NULL, NULL) || dent == NULL) | |
| break; | |
| if (strcmp (dent->d_name, "passwd") == 0 || | |
| strcmp (dent->d_name, "group") == 0 || | |
| strcmp (dent->d_name, "machine-id") == 0 || | |
| strcmp (dent->d_name, "resolv.conf") == 0 || | |
| strcmp (dent->d_name, "host.conf") == 0 || | |
| strcmp (dent->d_name, "hosts") == 0 || | |
| strcmp (dent->d_name, "localtime") == 0) | |
| continue; | |
| src = g_build_filename (flatpak_file_get_path_cached (etc), dent->d_name, NULL); | |
| dest = g_build_filename ("/etc", dent->d_name, NULL); | |
| if (dent->d_type == DT_LNK) | |
| { | |
| symlink_size = readlinkat (dfd_iter.fd, dent->d_name, path_buffer, sizeof (path_buffer) - 1); | |
| if (symlink_size < 0) | |
| { | |
| glnx_set_error_from_errno (error); | |
| return FALSE; | |
| } | |
| path_buffer[symlink_size] = 0; | |
| flatpak_bwrap_add_args (bwrap, "--symlink", path_buffer, dest, NULL); | |
| } | |
| else | |
| { | |
| flatpak_bwrap_add_args (bwrap, "--bind", src, dest, NULL); | |
| } | |
| } | |
| } | |
| if (app_id_dir != NULL) | |
| { | |
| g_autoptr(GFile) app_cache_dir = g_file_get_child (app_id_dir, "cache"); | |
| g_autoptr(GFile) app_tmp_dir = g_file_get_child (app_cache_dir, "tmp"); | |
| g_autoptr(GFile) app_data_dir = g_file_get_child (app_id_dir, "data"); | |
| g_autoptr(GFile) app_config_dir = g_file_get_child (app_id_dir, "config"); | |
| flatpak_bwrap_add_args (bwrap, | |
| /* These are nice to have as a fixed path */ | |
| "--bind", flatpak_file_get_path_cached (app_cache_dir), "/var/cache", | |
| "--bind", flatpak_file_get_path_cached (app_data_dir), "/var/data", | |
| "--bind", flatpak_file_get_path_cached (app_config_dir), "/var/config", | |
| "--bind", flatpak_file_get_path_cached (app_tmp_dir), "/var/tmp", | |
| NULL); | |
| } | |
| flatpak_run_setup_usr_links (bwrap, runtime_files); | |
| pers = PER_LINUX; | |
| if ((flags & FLATPAK_RUN_FLAG_SET_PERSONALITY) && | |
| flatpak_is_linux32_arch (arch)) | |
| { | |
| g_debug ("Setting personality linux32"); | |
| pers = PER_LINUX32; | |
| } | |
| /* Always set the personallity, and clear all weird flags */ | |
| personality (pers); | |
| #ifdef ENABLE_SECCOMP | |
| if (!setup_seccomp (bwrap, | |
| arch, | |
| pers, | |
| (flags & FLATPAK_RUN_FLAG_MULTIARCH) != 0, | |
| (flags & FLATPAK_RUN_FLAG_DEVEL) != 0, | |
| error)) | |
| return FALSE; | |
| #endif | |
| if ((flags & FLATPAK_RUN_FLAG_WRITABLE_ETC) == 0) | |
| add_monitor_path_args ((flags & FLATPAK_RUN_FLAG_NO_SESSION_HELPER) == 0, bwrap); | |
| return TRUE; | |
| } | |
| static void | |
| clear_fd (gpointer data) | |
| { | |
| int *fd_p = data; | |
| if (fd_p != NULL && *fd_p != -1) | |
| close (*fd_p); | |
| } | |
| /* Unset FD_CLOEXEC on the array of fds passed in @user_data */ | |
| static void | |
| child_setup (gpointer user_data) | |
| { | |
| GArray *fd_array = user_data; | |
| int i; | |
| /* If no fd_array was specified, don't care. */ | |
| if (fd_array == NULL) | |
| return; | |
| /* Otherwise, mark not - close-on-exec all the fds in the array */ | |
| for (i = 0; i < fd_array->len; i++) | |
| { | |
| int fd = g_array_index (fd_array, int, i); | |
| /* We also seek all fds to the start, because this lets | |
| us use the same fd_array multiple times */ | |
| if (lseek (fd, 0, SEEK_SET) < 0) | |
| g_printerr ("lseek error in child setup"); | |
| fcntl (fd, F_SETFD, 0); | |
| } | |
| } | |
| static gboolean | |
| forward_file (XdpDbusDocuments *documents, | |
| const char *app_id, | |
| const char *file, | |
| char **out_doc_id, | |
| GError **error) | |
| { | |
| int fd, fd_id; | |
| g_autofree char *doc_id = NULL; | |
| g_autoptr(GUnixFDList) fd_list = NULL; | |
| const char *perms[] = { "read", "write", NULL }; | |
| fd = open (file, O_PATH | O_CLOEXEC); | |
| if (fd == -1) | |
| return flatpak_fail (error, "Failed to open '%s'", file); | |
| fd_list = g_unix_fd_list_new (); | |
| fd_id = g_unix_fd_list_append (fd_list, fd, error); | |
| close (fd); | |
| if (!xdp_dbus_documents_call_add_sync (documents, | |
| g_variant_new ("h", fd_id), | |
| TRUE, /* reuse */ | |
| FALSE, /* not persistent */ | |
| fd_list, | |
| &doc_id, | |
| NULL, | |
| NULL, | |
| error)) | |
| return FALSE; | |
| if (!xdp_dbus_documents_call_grant_permissions_sync (documents, | |
| doc_id, | |
| app_id, | |
| perms, | |
| NULL, | |
| error)) | |
| return FALSE; | |
| *out_doc_id = g_steal_pointer (&doc_id); | |
| return TRUE; | |
| } | |
| static gboolean | |
| add_rest_args (const char *app_id, | |
| FlatpakExports *exports, | |
| gboolean file_forwarding, | |
| const char *doc_mount_path, | |
| GPtrArray *argv_array, | |
| char *args[], | |
| int n_args, | |
| GError **error) | |
| { | |
| g_autoptr(XdpDbusDocuments) documents = NULL; | |
| gboolean forwarding = FALSE; | |
| gboolean forwarding_uri = FALSE; | |
| gboolean can_forward = TRUE; | |
| int i; | |
| if (file_forwarding && doc_mount_path == NULL) | |
| { | |
| g_message ("Can't get document portal mount path"); | |
| can_forward = FALSE; | |
| } | |
| else if (file_forwarding) | |
| { | |
| g_autoptr(GError) local_error = NULL; | |
| documents = xdp_dbus_documents_proxy_new_for_bus_sync (G_BUS_TYPE_SESSION, 0, | |
| "org.freedesktop.portal.Documents", | |
| "/org/freedesktop/portal/documents", | |
| NULL, | |
| &local_error); | |
| if (documents == NULL) | |
| { | |
| g_message ("Can't get document portal: %s", local_error->message); | |
| can_forward = FALSE; | |
| } | |
| } | |
| for (i = 0; i < n_args; i++) | |
| { | |
| g_autoptr(GFile) file = NULL; | |
| if (file_forwarding && | |
| (strcmp (args[i], "@@") == 0 || | |
| strcmp (args[i], "@@u") == 0)) | |
| { | |
| forwarding_uri = strcmp (args[i], "@@u") == 0; | |
| forwarding = !forwarding; | |
| continue; | |
| } | |
| if (can_forward && forwarding) | |
| { | |
| if (forwarding_uri) | |
| { | |
| if (g_str_has_prefix (args[i], "file:")) | |
| file = g_file_new_for_uri (args[i]); | |
| else if (G_IS_DIR_SEPARATOR(args[i][0])) | |
| file = g_file_new_for_path (args[i]); | |
| } | |
| else | |
| file = g_file_new_for_path (args[i]); | |
| } | |
| if (file && !flatpak_exports_path_is_visible (exports, | |
| flatpak_file_get_path_cached (file))) | |
| { | |
| g_autofree char *doc_id = NULL; | |
| g_autofree char *basename = NULL; | |
| char *doc_path; | |
| if (!forward_file (documents, app_id, flatpak_file_get_path_cached (file), | |
| &doc_id, error)) | |
| return FALSE; | |
| basename = g_file_get_basename (file); | |
| doc_path = g_build_filename (doc_mount_path, doc_id, basename, NULL); | |
| if (forwarding_uri) | |
| { | |
| g_autofree char *path = doc_path; | |
| doc_path = g_filename_to_uri (path, NULL, NULL); | |
| /* This should never fail */ | |
| g_assert (doc_path != NULL); | |
| } | |
| g_debug ("Forwarding file '%s' as '%s' to %s", args[i], doc_path, app_id); | |
| g_ptr_array_add (argv_array, doc_path); | |
| } | |
| else | |
| g_ptr_array_add (argv_array, g_strdup (args[i])); | |
| } | |
| return TRUE; | |
| } | |
| FlatpakContext * | |
| flatpak_context_load_for_app (const char *app_id, | |
| GError **error) | |
| { | |
| g_autofree char *app_ref = NULL; | |
| g_autoptr(FlatpakContext) app_context = NULL; | |
| g_autoptr(FlatpakDeploy) app_deploy = NULL; | |
| g_autoptr(FlatpakContext) overrides = NULL; | |
| g_autoptr(GKeyFile) metakey = NULL; | |
| app_ref = flatpak_find_current_ref (app_id, NULL, error); | |
| if (app_ref == NULL) | |
| return NULL; | |
| app_deploy = flatpak_find_deploy_for_ref (app_ref, NULL, error); | |
| if (app_deploy == NULL) | |
| return NULL; | |
| metakey = flatpak_deploy_get_metadata (app_deploy); | |
| app_context = flatpak_app_compute_permissions (metakey, NULL, error); | |
| if (app_context == NULL) | |
| return NULL; | |
| overrides = flatpak_deploy_get_overrides (app_deploy); | |
| flatpak_context_merge (app_context, overrides); | |
| return g_steal_pointer (&app_context); | |
| } | |
| FlatpakBwrap * | |
| flatpak_bwrap_new (char **env) | |
| { | |
| FlatpakBwrap *bwrap = g_new0 (FlatpakBwrap, 1); | |
| bwrap->argv = g_ptr_array_new_with_free_func (g_free); | |
| bwrap->fds = g_array_new (FALSE, TRUE, sizeof (int)); | |
| g_array_set_clear_func (bwrap->fds, clear_fd); | |
| if (env) | |
| bwrap->envp = g_strdupv (env); | |
| else | |
| bwrap->envp = g_get_environ (); | |
| return bwrap; | |
| } | |
| void | |
| flatpak_bwrap_free (FlatpakBwrap *bwrap) | |
| { | |
| g_ptr_array_unref (bwrap->argv); | |
| g_array_unref (bwrap->fds); | |
| g_strfreev (bwrap->envp); | |
| g_free (bwrap); | |
| } | |
| void | |
| flatpak_bwrap_set_env (FlatpakBwrap *bwrap, | |
| const char *variable, | |
| const char *value, | |
| gboolean overwrite) | |
| { | |
| bwrap->envp = g_environ_setenv (bwrap->envp, variable, value, overwrite); | |
| } | |
| void | |
| flatpak_bwrap_unset_env (FlatpakBwrap *bwrap, | |
| const char *variable) | |
| { | |
| bwrap->envp = g_environ_unsetenv (bwrap->envp, variable); | |
| } | |
| void | |
| flatpak_bwrap_add_args (FlatpakBwrap *bwrap, ...) | |
| { | |
| va_list args; | |
| const gchar *arg; | |
| va_start (args, bwrap); | |
| while ((arg = va_arg (args, const gchar *))) | |
| g_ptr_array_add (bwrap->argv, g_strdup (arg)); | |
| va_end (args); | |
| } | |
| void | |
| flatpak_bwrap_append_argsv (FlatpakBwrap *bwrap, | |
| char **args, | |
| int len) | |
| { | |
| int i; | |
| if (len < 0) | |
| len = g_strv_length (args); | |
| for (i = 0; i < len; i++) | |
| g_ptr_array_add (bwrap->argv, g_strdup (args[i])); | |
| } | |
| void | |
| flatpak_bwrap_append_args (FlatpakBwrap *bwrap, | |
| GPtrArray *other_array) | |
| { | |
| flatpak_bwrap_append_argsv (bwrap, | |
| (char **)other_array->pdata, | |
| other_array->len); | |
| } | |
| void | |
| flatpak_bwrap_add_args_data_fd (FlatpakBwrap *bwrap, | |
| const char *op, | |
| int fd, | |
| const char *path_optional) | |
| { | |
| g_autofree char *fd_str = g_strdup_printf ("%d", fd); | |
| g_array_append_val (bwrap->fds, fd); | |
| flatpak_bwrap_add_args (bwrap, | |
| op, fd_str, path_optional, | |
| NULL); | |
| } | |
| /* Given a buffer @content of size @content_size, generate a fd (memfd if available) | |
| * of the data. The @name parameter is used by memfd_create() as a debugging aid; | |
| * it has no semantic meaning. The bwrap command line will inject it into the target | |
| * container as @path. | |
| */ | |
| gboolean | |
| flatpak_bwrap_add_args_data (FlatpakBwrap *bwrap, | |
| const char *name, | |
| const char *content, | |
| gssize content_size, | |
| const char *path, | |
| GError **error) | |
| { | |
| g_auto(GLnxTmpfile) args_tmpf = { 0, }; | |
| if (!buffer_to_sealed_memfd_or_tmpfile (&args_tmpf, name, content, content_size, error)) | |
| return FALSE; | |
| flatpak_bwrap_add_args_data_fd (bwrap, "--bind-data", glnx_steal_fd (&args_tmpf.fd), path); | |
| return TRUE; | |
| } | |
| /* This resolves the target here rather than in bwrap, because it may | |
| * not resolve in bwrap setup due to absolute symlinks conflicting | |
| * with /newroot root. For example, dest could be inside | |
| * ~/.var/app/XXX where XXX is an absolute symlink. However, in the | |
| * usecases here the destination file often doesn't exist, so we | |
| * only resolve the directory part. | |
| */ | |
| void | |
| flatpak_bwrap_add_bind_arg (FlatpakBwrap *bwrap, | |
| const char *type, | |
| const char *src, | |
| const char *dest) | |
| { | |
| g_autofree char *dest_dirname = g_path_get_dirname (dest); | |
| g_autofree char *dest_dirname_real = realpath (dest_dirname, NULL); | |
| if (dest_dirname_real) | |
| { | |
| g_autofree char *dest_basename = g_path_get_basename (dest); | |
| g_autofree char *dest_real = g_build_filename (dest_dirname_real, dest_basename, NULL); | |
| flatpak_bwrap_add_args (bwrap, type, src, dest_real, NULL); | |
| } | |
| } | |
| static char * | |
| calculate_ld_cache_checksum (GVariant *app_deploy_data, | |
| GVariant *runtime_deploy_data, | |
| const char *app_extensions, | |
| const char *runtime_extensions) | |
| { | |
| g_autoptr(GChecksum) ld_so_checksum = g_checksum_new (G_CHECKSUM_SHA256); | |
| if (app_deploy_data) | |
| g_checksum_update (ld_so_checksum, (guchar *)flatpak_deploy_data_get_commit (app_deploy_data), -1); | |
| g_checksum_update (ld_so_checksum, (guchar *)flatpak_deploy_data_get_commit (runtime_deploy_data), -1); | |
| if (app_extensions) | |
| g_checksum_update (ld_so_checksum, (guchar *)app_extensions, -1); | |
| if (runtime_extensions) | |
| g_checksum_update (ld_so_checksum, (guchar *)runtime_extensions, -1); | |
| return g_strdup (g_checksum_get_string (ld_so_checksum)); | |
| } | |
| static gboolean | |
| add_ld_so_conf (FlatpakBwrap *bwrap, | |
| GError **error) | |
| { | |
| const char *contents = | |
| "include /run/flatpak/ld.so.conf.d/app-*.conf\n" | |
| "include /app/etc/ld.so.conf\n" | |
| "/app/lib\n" | |
| "include /run/flatpak/ld.so.conf.d/runtime-*.conf\n"; | |
| return flatpak_bwrap_add_args_data (bwrap, "ld-so-conf", | |
| contents, -1, "/etc/ld.so.conf", error); | |
| } | |
| static int | |
| regenerate_ld_cache (GPtrArray *base_argv_array, | |
| GArray *base_fd_array, | |
| GFile *app_id_dir, | |
| const char *checksum, | |
| GFile *runtime_files, | |
| gboolean generate_ld_so_conf, | |
| GCancellable *cancellable, | |
| GError **error) | |
| { | |
| g_autoptr(FlatpakBwrap) bwrap = NULL; | |
| g_autoptr(GArray) combined_fd_array = NULL; | |
| g_autoptr(GFile) ld_so_cache = NULL; | |
| g_autofree char *sandbox_cache_path = NULL; | |
| g_auto(GStrv) minimal_envp = NULL; | |
| g_autofree char *commandline = NULL; | |
| int exit_status; | |
| glnx_autofd int ld_so_fd = -1; | |
| g_autoptr(GFile) ld_so_dir = NULL; | |
| if (app_id_dir) | |
| ld_so_dir = g_file_get_child (app_id_dir, ".ld.so"); | |
| else | |
| { | |
| g_autoptr(GFile) base_dir = g_file_new_for_path (g_get_user_cache_dir ()); | |
| ld_so_dir = g_file_resolve_relative_path (base_dir, "flatpak/ld.so"); | |
| } | |
| ld_so_cache = g_file_get_child (ld_so_dir, checksum); | |
| ld_so_fd = open (flatpak_file_get_path_cached (ld_so_cache), O_RDONLY); | |
| if (ld_so_fd >= 0) | |
| return glnx_steal_fd (&ld_so_fd); | |
| g_debug ("Regenerating ld.so.cache %s", flatpak_file_get_path_cached (ld_so_cache)); | |
| if (!flatpak_mkdir_p (ld_so_dir, cancellable, error)) | |
| return FALSE; | |
| minimal_envp = flatpak_run_get_minimal_env (FALSE, FALSE); | |
| bwrap = flatpak_bwrap_new (minimal_envp); | |
| flatpak_bwrap_add_args (bwrap, flatpak_get_bwrap (), NULL); | |
| flatpak_bwrap_append_args (bwrap, base_argv_array); | |
| flatpak_run_setup_usr_links (bwrap, runtime_files); | |
| if (generate_ld_so_conf) | |
| { | |
| if (!add_ld_so_conf (bwrap, error)) | |
| return -1; | |
| } | |
| else | |
| flatpak_bwrap_add_args (bwrap, | |
| "--symlink", "../usr/etc/ld.so.conf", "/etc/ld.so.conf", | |
| NULL); | |
| sandbox_cache_path = g_build_filename ("/run/ld-so-cache-dir", checksum, NULL); | |
| flatpak_bwrap_add_args (bwrap, | |
| "--unshare-pid", | |
| "--unshare-ipc", | |
| "--unshare-net", | |
| "--proc", "/proc", | |
| "--dev", "/dev", | |
| "--bind", flatpak_file_get_path_cached (ld_so_dir), "/run/ld-so-cache-dir", | |
| "ldconfig", "-X", "-C", sandbox_cache_path, NULL); | |
| g_ptr_array_add (bwrap->argv, NULL); | |
| commandline = flatpak_quote_argv ((const char **) bwrap->argv->pdata); | |
| flatpak_debug2 ("Running: '%s'", commandline); | |
| combined_fd_array = g_array_new (FALSE, TRUE, sizeof (int)); | |
| g_array_append_vals (combined_fd_array, base_fd_array->data, base_fd_array->len); | |
| g_array_append_vals (combined_fd_array, bwrap->fds->data, bwrap->fds->len); | |
| if (!g_spawn_sync (NULL, | |
| (char **) bwrap->argv->pdata, | |
| bwrap->envp, | |
| G_SPAWN_SEARCH_PATH, | |
| child_setup, combined_fd_array, | |
| NULL, NULL, | |
| &exit_status, | |
| error)) | |
| return -1; | |
| if (!WIFEXITED(exit_status) || WEXITSTATUS(exit_status) != 0) | |
| { | |
| g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, | |
| _("ldconfig failed, exit status %d"), exit_status); | |
| return -1; | |
| } | |
| ld_so_fd = open (flatpak_file_get_path_cached (ld_so_cache), O_RDONLY); | |
| if (ld_so_fd < 0) | |
| { | |
| flatpak_fail (error, "Can't open generated ld.so.cache"); | |
| return -1; | |
| } | |
| if (app_id_dir == NULL) | |
| { | |
| /* For runs without an app id dir we always regenerate the ld.so.cache */ | |
| unlink (flatpak_file_get_path_cached (ld_so_cache)); | |
| } | |
| else | |
| { | |
| g_autoptr(GFile) active = g_file_get_child (ld_so_dir, "active"); | |
| /* For app-dirs we keep one checksum alive, by pointing the active symlink to it */ | |
| if (!flatpak_switch_symlink_and_remove (flatpak_file_get_path_cached (active), | |
| checksum, error)) | |
| return -1; | |
| } | |
| return glnx_steal_fd (&ld_so_fd); | |
| } | |
| gboolean | |
| flatpak_run_app (const char *app_ref, | |
| FlatpakDeploy *app_deploy, | |
| FlatpakContext *extra_context, | |
| const char *custom_runtime, | |
| const char *custom_runtime_version, | |
| FlatpakRunFlags flags, | |
| const char *custom_command, | |
| char *args[], | |
| int n_args, | |
| GCancellable *cancellable, | |
| GError **error) | |
| { | |
| g_autoptr(FlatpakDeploy) runtime_deploy = NULL; | |
| g_autoptr(GVariant) runtime_deploy_data = NULL; | |
| g_autoptr(GVariant) app_deploy_data = NULL; | |
| g_autoptr(GFile) app_files = NULL; | |
| g_autoptr(GFile) runtime_files = NULL; | |
| g_autoptr(GFile) bin_ldconfig = NULL; | |
| g_autoptr(GFile) app_id_dir = NULL; | |
| g_autofree char *default_runtime = NULL; | |
| g_autofree char *default_command = NULL; | |
| g_autofree char *runtime_ref = NULL; | |
| g_autoptr(GKeyFile) metakey = NULL; | |
| g_autoptr(GKeyFile) runtime_metakey = NULL; | |
| g_autoptr(FlatpakBwrap) bwrap = NULL; | |
| g_auto(GLnxTmpfile) arg_tmpf = { 0, }; | |
| g_autoptr(GPtrArray) real_argv_array = NULL; | |
| const char *command = "/bin/sh"; | |
| g_autoptr(GError) my_error = NULL; | |
| g_auto(GStrv) runtime_parts = NULL; | |
| int i; | |
| g_autofree char *app_info_path = NULL; | |
| g_autoptr(FlatpakContext) app_context = NULL; | |
| g_autoptr(FlatpakContext) overrides = NULL; | |
| g_autoptr(FlatpakExports) exports = NULL; | |
| g_auto(GStrv) app_ref_parts = NULL; | |
| g_autofree char *commandline = NULL; | |
| int commandline_2_start; | |
| g_autofree char *commandline2 = NULL; | |
| g_autofree char *doc_mount_path = NULL; | |
| g_autofree char *app_extensions = NULL; | |
| g_autofree char *runtime_extensions = NULL; | |
| g_autofree char *checksum = NULL; | |
| int ld_so_fd = -1; | |
| g_autoptr(GFile) runtime_ld_so_conf = NULL; | |
| gboolean generate_ld_so_conf = TRUE; | |
| gboolean use_ld_so_cache = TRUE; | |
| struct stat s; | |
| app_ref_parts = flatpak_decompose_ref (app_ref, error); | |
| if (app_ref_parts == NULL) | |
| return FALSE; | |
| bwrap = flatpak_bwrap_new (NULL); | |
| if (app_deploy == NULL) | |
| { | |
| g_assert (g_str_has_prefix (app_ref, "runtime/")); | |
| default_runtime = g_strdup (app_ref + strlen ("runtime/")); | |
| } | |
| else | |
| { | |
| const gchar *key; | |
| app_deploy_data = flatpak_deploy_get_deploy_data (app_deploy, cancellable, error); | |
| if (app_deploy_data == NULL) | |
| return FALSE; | |
| if ((flags & FLATPAK_RUN_FLAG_DEVEL) != 0) | |
| key = FLATPAK_METADATA_KEY_SDK; | |
| else | |
| key = FLATPAK_METADATA_KEY_RUNTIME; | |
| metakey = flatpak_deploy_get_metadata (app_deploy); | |
| default_runtime = g_key_file_get_string (metakey, | |
| FLATPAK_METADATA_GROUP_APPLICATION, | |
| key, &my_error); | |
| if (my_error) | |
| { | |
| g_propagate_error (error, g_steal_pointer (&my_error)); | |
| return FALSE; | |
| } | |
| } | |
| runtime_parts = g_strsplit (default_runtime, "/", 0); | |
| if (g_strv_length (runtime_parts) != 3) | |
| return flatpak_fail (error, "Wrong number of components in runtime %s", default_runtime); | |
| if (custom_runtime) | |
| { | |
| g_auto(GStrv) custom_runtime_parts = g_strsplit (custom_runtime, "/", 0); | |
| for (i = 0; i < 3 && custom_runtime_parts[i] != NULL; i++) | |
| { | |
| if (strlen (custom_runtime_parts[i]) > 0) | |
| { | |
| g_free (runtime_parts[i]); | |
| runtime_parts[i] = g_steal_pointer (&custom_runtime_parts[i]); | |
| } | |
| } | |
| } | |
| if (custom_runtime_version) | |
| { | |
| g_free (runtime_parts[2]); | |
| runtime_parts[2] = g_strdup (custom_runtime_version); | |
| } | |
| runtime_ref = flatpak_compose_ref (FALSE, | |
| runtime_parts[0], | |
| runtime_parts[2], | |
| runtime_parts[1], | |
| error); | |
| if (runtime_ref == NULL) | |
| return FALSE; | |
| runtime_deploy = flatpak_find_deploy_for_ref (runtime_ref, cancellable, error); | |
| if (runtime_deploy == NULL) | |
| return FALSE; | |
| runtime_deploy_data = flatpak_deploy_get_deploy_data (runtime_deploy, cancellable, error); | |
| if (runtime_deploy_data == NULL) | |
| return FALSE; | |
| runtime_metakey = flatpak_deploy_get_metadata (runtime_deploy); | |
| app_context = flatpak_app_compute_permissions (metakey, runtime_metakey, error); | |
| if (app_context == NULL) | |
| return FALSE; | |
| if (app_deploy != NULL) | |
| { | |
| overrides = flatpak_deploy_get_overrides (app_deploy); | |
| flatpak_context_merge (app_context, overrides); | |
| } | |
| if (extra_context) | |
| flatpak_context_merge (app_context, extra_context); | |
| runtime_files = flatpak_deploy_get_files (runtime_deploy); | |
| bin_ldconfig = g_file_resolve_relative_path (runtime_files, "bin/ldconfig"); | |
| if (!g_file_query_exists (bin_ldconfig, NULL)) | |
| use_ld_so_cache = FALSE; | |
| if (app_deploy != NULL) | |
| { | |
| app_files = flatpak_deploy_get_files (app_deploy); | |
| if ((app_id_dir = flatpak_ensure_data_dir (app_ref_parts[1], cancellable, error)) == NULL) | |
| return FALSE; | |
| } | |
| flatpak_run_apply_env_default (bwrap, use_ld_so_cache); | |
| flatpak_run_apply_env_vars (bwrap, app_context); | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", flatpak_file_get_path_cached (runtime_files), "/usr", | |
| "--lock-file", "/usr/.ref", | |
| NULL); | |
| if (app_files != NULL) | |
| flatpak_bwrap_add_args (bwrap, | |
| "--ro-bind", flatpak_file_get_path_cached (app_files), "/app", | |
| "--lock-file", "/app/.ref", | |
| NULL); | |
| else | |
| flatpak_bwrap_add_args (bwrap, | |
| "--dir", "/app", | |
| NULL); | |
| if (metakey != NULL && | |
| !flatpak_run_add_extension_args (bwrap, metakey, app_ref, use_ld_so_cache, &app_extensions, cancellable, error)) | |
| return FALSE; | |
| if (!flatpak_run_add_extension_args (bwrap, runtime_metakey, runtime_ref, use_ld_so_cache, &runtime_extensions, cancellable, error)) | |
| return FALSE; | |
| runtime_ld_so_conf = g_file_resolve_relative_path (runtime_files, "etc/ld.so.conf"); | |
| if (lstat (flatpak_file_get_path_cached (runtime_ld_so_conf), &s) == 0) | |
| generate_ld_so_conf = S_ISREG (s.st_mode) && s.st_size == 0; | |
| /* At this point we have the minimal argv set up, with just the app, runtime and extensions. | |
| We can reuse this to generate the ld.so.cache (if needed) */ | |
| if (use_ld_so_cache) | |
| { | |
| checksum = calculate_ld_cache_checksum (app_deploy_data, runtime_deploy_data, | |
| app_extensions, runtime_extensions); | |
| ld_so_fd = regenerate_ld_cache (bwrap->argv, | |
| bwrap->fds, | |
| app_id_dir, | |
| checksum, | |
| runtime_files, | |
| generate_ld_so_conf, | |
| cancellable, error); | |
| if (ld_so_fd == -1) | |
| return FALSE; | |
| g_array_append_val (bwrap->fds, ld_so_fd); | |
| } | |
| if (app_context->features & FLATPAK_CONTEXT_FEATURE_DEVEL) | |
| flags |= FLATPAK_RUN_FLAG_DEVEL; | |
| if (app_context->features & FLATPAK_CONTEXT_FEATURE_MULTIARCH) | |
| flags |= FLATPAK_RUN_FLAG_MULTIARCH; | |
| if (!flatpak_run_setup_base_argv (bwrap, runtime_files, app_id_dir, app_ref_parts[2], flags, error)) | |
| return FALSE; | |
| if (generate_ld_so_conf) | |
| { | |
| if (!add_ld_so_conf (bwrap, error)) | |
| return FALSE; | |
| } | |
| if (ld_so_fd != -1) | |
| { | |
| /* Don't add to fd_array, its already there */ | |
| add_args_data_fd (bwrap->argv, NULL, "--ro-bind-data", ld_so_fd, "/etc/ld.so.cache"); | |
| } | |
| if (!flatpak_run_add_app_info_args (bwrap, | |
| app_files, app_deploy_data, app_extensions, | |
| runtime_files, runtime_deploy_data, runtime_extensions, | |
| app_ref_parts[1], app_ref_parts[3], | |
| runtime_ref, app_context, &app_info_path, error)) | |
| return FALSE; | |
| add_document_portal_args (bwrap, app_ref_parts[1], &doc_mount_path); | |
| if (!flatpak_run_add_environment_args (bwrap, app_info_path, flags, | |
| app_ref_parts[1], app_context, app_id_dir, &exports, cancellable, error)) | |
| return FALSE; | |
| flatpak_run_add_journal_args (bwrap); | |
| add_font_path_args (bwrap); | |
| add_icon_path_args (bwrap); | |
| flatpak_bwrap_add_args (bwrap, | |
| /* Not in base, because we don't want this for flatpak build */ | |
| "--symlink", "/app/lib/debug/source", "/run/build", | |
| "--symlink", "/usr/lib/debug/source", "/run/build-runtime", | |
| NULL); | |
| if (custom_command) | |
| { | |
| command = custom_command; | |
| } | |
| else if (metakey) | |
| { | |
| default_command = g_key_file_get_string (metakey, | |
| FLATPAK_METADATA_GROUP_APPLICATION, | |
| FLATPAK_METADATA_KEY_COMMAND, | |
| &my_error); | |
| if (my_error) | |
| { | |
| g_propagate_error (error, g_steal_pointer (&my_error)); | |
| return FALSE; | |
| } | |
| command = default_command; | |
| } | |
| real_argv_array = g_ptr_array_new_with_free_func (g_free); | |
| g_ptr_array_add (real_argv_array, g_strdup (flatpak_get_bwrap ())); | |
| { | |
| gsize len; | |
| g_autofree char *args = join_args (bwrap->argv, &len); | |
| if (!buffer_to_sealed_memfd_or_tmpfile (&arg_tmpf, "bwrap-args", args, len, error)) | |
| return FALSE; | |
| add_args_data_fd (real_argv_array, bwrap->fds, | |
| "--args", glnx_steal_fd (&arg_tmpf.fd), NULL); | |
| } | |
| commandline_2_start = real_argv_array->len; | |
| g_ptr_array_add (real_argv_array, g_strdup (command)); | |
| if (!add_rest_args (app_ref_parts[1], exports, (flags & FLATPAK_RUN_FLAG_FILE_FORWARDING) != 0, | |
| doc_mount_path, | |
| real_argv_array, args, n_args, error)) | |
| return FALSE; | |
| g_ptr_array_add (real_argv_array, NULL); | |
| g_ptr_array_add (bwrap->argv, NULL); | |
| commandline = flatpak_quote_argv ((const char **) bwrap->argv->pdata); | |
| commandline2 = flatpak_quote_argv (((const char **) real_argv_array->pdata) + commandline_2_start); | |
| flatpak_debug2 ("Running '%s %s'", commandline, commandline2); | |
| if ((flags & FLATPAK_RUN_FLAG_BACKGROUND) != 0) | |
| { | |
| if (!g_spawn_async (NULL, | |
| (char **) real_argv_array->pdata, | |
| bwrap->envp, | |
| G_SPAWN_SEARCH_PATH, | |
| child_setup, bwrap->fds, | |
| NULL, | |
| error)) | |
| return FALSE; | |
| } | |
| else | |
| { | |
| /* Ensure we unset O_CLOEXEC */ | |
| child_setup (bwrap->fds); | |
| if (execvpe (flatpak_get_bwrap (), (char **) real_argv_array->pdata, bwrap->envp) == -1) | |
| { | |
| g_set_error_literal (error, G_IO_ERROR, g_io_error_from_errno (errno), | |
| _("Unable to start app")); | |
| return FALSE; | |
| } | |
| /* Not actually reached... */ | |
| } | |
| return TRUE; | |
| } |