Change how exposing symlinks are handled, allowing $HOME to be a symlink

We used to only expose symlinks if the path to one was explicitly exposed,
like --filesystem=/symlink, but if you did say --filesystem=/symlink/dir, then
we would make /symlink be a regular directory in the sandbox tmpfs.

This means all exports are fully resolved, which allows use to remove the
workaround for symlinks that we had with sorting by "level".

We also add a FAKE_MODE_DIR, which is used to ensure the homedir is there,
created on tmpfs if needed only, while handling symlinks correctly.

This means that flatpak works well even if $HOME is on a symlink.
This commit is contained in:
Alexander Larsson
2017-09-25 14:12:44 +02:00
parent f1df5cb1d9
commit 08aef072c9

View File

@@ -2357,13 +2357,13 @@ make_relative (const char *base, const char *path)
return g_string_free (s, FALSE);
}
#define FAKE_MODE_HIDDEN 0
#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;
int level;
guint mode;
gint mode;
} ExportedPath;
struct _FlatpakExports {
@@ -2395,13 +2395,13 @@ flatpak_exports_free (FlatpakExports *exports)
/* Returns TRUE if the location of this export
is not visible due to parents being exported */
static gboolean
path_needs_creation (const char **keys,
guint n_keys,
GHashTable *hash_table,
const char *path)
path_parent_is_mapped (const char **keys,
guint n_keys,
GHashTable *hash_table,
const char *path)
{
guint i;
gboolean needs_creation = TRUE;
gboolean is_mapped = FALSE;
/* The keys are sorted so shorter (i.e. parents) are first */
for (i = 0; i < n_keys; i++)
@@ -2410,27 +2410,55 @@ path_needs_creation (const char **keys,
ExportedPath *ep = g_hash_table_lookup (hash_table, mounted_path);
if (flatpak_has_path_prefix (path, mounted_path) &&
strcmp (path, mounted_path) != 0)
(strcmp (path, mounted_path) != 0))
{
if (ep->mode == FAKE_MODE_HIDDEN)
/* If the parent needs creation, then we don't have to create a mountpoint to hide it */
needs_creation = !needs_creation;
else if (ep->mode != FAKE_MODE_SYMLINK)
needs_creation = FALSE;
/* FAKE_MODE_DIR has same mapped value as parent */
if (ep->mode == FAKE_MODE_DIR)
continue;
is_mapped = ep->mode != FAKE_MODE_TMPFS;
}
}
return needs_creation;
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)
{
if (a->level == b->level)
return g_strcmp0 (a->path, b->path);
else
return b->level - a->level;
return g_strcmp0 (a->path, b->path);
}
/* This differs from g_file_test (path, G_FILE_TEST_IS_DIR) which
@@ -2478,7 +2506,7 @@ exports_add_bwrap_args (FlatpakExports *exports,
if (ep->mode == FAKE_MODE_SYMLINK)
{
if (path_needs_creation (keys, n_keys, exports->hash, path))
if (!path_parent_is_mapped (keys, n_keys, exports->hash, path))
{
g_autofree char *resolved = flatpak_resolve_link (path, NULL);
if (resolved)
@@ -2489,15 +2517,23 @@ exports_add_bwrap_args (FlatpakExports *exports,
}
}
}
else if (ep->mode == FAKE_MODE_HIDDEN)
else if (ep->mode == FAKE_MODE_TMPFS)
{
/* Mount a tmpfs to hide the subdirectory, but only if
either its not visible (then we can always create the
dir on the tmpfs, or if there is a pre-existing dir
we can mount the path on. */
if (path_needs_creation (keys, n_keys, exports->hash, path) ||
path_is_dir (path))
add_args (argv_array, "--tmpfs", path, NULL);
/* 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 */
add_args (argv_array, "--dir", path, NULL);
else
add_args (argv_array, "--tmpfs", path, NULL);
}
}
else if (ep->mode == FAKE_MODE_DIR)
{
if (path_is_dir (path))
add_args (argv_array, "--dir", path, NULL);
}
else
{
@@ -2508,42 +2544,6 @@ exports_add_bwrap_args (FlatpakExports *exports,
}
}
static ExportedPath *
find_innermost_export (const char **keys,
guint n_keys,
GHashTable *hash_table,
const char *path)
{
guint i;
ExportedPath *export = NULL;
/* The keys are sorted so shorter (i.e. parents) are first */
for (i = 0; i < n_keys; i++)
{
const char *mounted_path = keys[i];
if (flatpak_has_path_prefix (path, mounted_path))
export = g_hash_table_lookup (hash_table, mounted_path);
}
return export;
}
static gboolean
path_is_mapped (const char **keys,
guint n_keys,
GHashTable *hash_table,
const char *path)
{
ExportedPath *ep;
ep = find_innermost_export (keys, n_keys, hash_table, path);
return
ep != NULL &&
ep->mode != FAKE_MODE_HIDDEN;
}
gboolean
flatpak_exports_path_is_visible (FlatpakExports *exports,
const char *path)
@@ -2605,36 +2605,6 @@ flatpak_exports_path_is_visible (FlatpakExports *exports,
return TRUE;
}
static void
exports_path_hide (FlatpakExports *exports,
const char *path)
{
guint old_mode;
ExportedPath *ep = g_new0 (ExportedPath, 1);
ExportedPath *old_ep;
g_autofree char *canonical = NULL;
path = canonical = flatpak_canonicalize_filename (path);
old_ep = g_hash_table_lookup (exports->hash, path);
if (old_ep)
old_mode = old_ep->mode;
else
old_mode = 0;
ep->path = g_strdup (path);
ep->level = 0;
ep->mode = MAX (old_mode, FAKE_MODE_HIDDEN);
g_hash_table_replace (exports->hash, ep->path, ep);
if (path_is_symlink (path))
{
g_autofree char *resolved = flatpak_resolve_link (path, NULL);
if (resolved)
exports_path_hide (exports, resolved);
}
}
static gboolean
never_export_as_symlink (const char *path)
{
@@ -2647,13 +2617,26 @@ never_export_as_symlink (const char *path)
return FALSE;
}
/* We use the level to make sure we get the ordering somewhat right.
* For instance if /symlink -> /z_dir is exported, then we want to create
* /z_dir before /symlink, because otherwise an export like /symlink/foo
* will fail. The approach we use is to just bump the sort prio based on the
* symlink resolve depth. This it not perfect, but gets the common situation
* such as --filesystem=/link --filesystem=/link/dir right.
*/
static void
do_export_path (FlatpakExports *exports,
const char *path,
gint mode)
{
ExportedPath *old_ep = g_hash_table_lookup (exports->hash, path);
gint old_mode = 0;
ExportedPath *ep;
if (old_ep != NULL)
old_mode = old_ep->mode;
ep = g_new0 (ExportedPath, 1);
ep->path = g_strdup (path);
ep->mode = MAX (old_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,
FlatpakFilesystemMode mode,
@@ -2662,14 +2645,32 @@ _exports_path_expose (FlatpakExports *exports,
{
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++)
@@ -2684,50 +2685,65 @@ _exports_path_expose (FlatpakExports *exports,
}
}
if (lstat (path, &st) != 0)
return FALSE;
if (S_ISDIR (st.st_mode) ||
S_ISREG (st.st_mode) ||
S_ISLNK (st.st_mode) ||
S_ISSOCK (st.st_mode))
/* Handle any symlinks prior to the target itself. This includes path itself,
because we expose the target of the symlink. */
slash = canonical;
do
{
ExportedPath *old_ep = g_hash_table_lookup (exports->hash, path);
guint old_mode = 0;
slash = strchr (slash + 1, '/');
if (slash)
*slash = 0;
if (old_ep != NULL)
old_mode = old_ep->mode;
if (S_ISLNK (st.st_mode) && !never_export_as_symlink (path))
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 && _exports_path_expose (exports, mode, resolved, level + 1))
mode = FAKE_MODE_SYMLINK;
else
mode = 0;
}
if (resolved)
{
if (slash)
new_target = g_build_filename (resolved, slash + 1, NULL);
else
new_target = g_strdup (resolved);
if (mode > 0)
{
ExportedPath *ep = g_new0 (ExportedPath, 1);
ep->path = g_strdup (path);
ep->mode = MAX (old_mode, mode);
ep->level = level;
g_hash_table_replace (exports->hash, ep->path, ep);
return TRUE;
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);
return FALSE;
do_export_path (exports, path, mode);
return TRUE;
}
static gboolean
static void
exports_path_expose (FlatpakExports *exports,
FlatpakFilesystemMode mode,
const char *path)
{
return _exports_path_expose (exports, mode, path, 0);
_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_dir (FlatpakExports *exports,
const char *path)
{
_exports_path_expose (exports, FAKE_MODE_DIR, path, 0);
}
static void
@@ -2859,7 +2875,7 @@ export_paths_export_context (FlatpakContext *context,
{
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_hide (exports, flatpak_file_get_path_cached (apps_dir));
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));
@@ -3017,16 +3033,14 @@ flatpak_run_add_environment_args (GPtrArray *argv_array,
/* Hide the flatpak dir by default (unless explicitly made visible) */
user_flatpak_dir = flatpak_get_user_base_dir_location ();
exports_path_hide (exports, flatpak_file_get_path_cached (user_flatpak_dir));
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, argv_array);
/* Ensure we always have a homedir */
add_args (argv_array,
"--dir", g_get_home_dir (),
NULL);
/* 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