diff --git a/pressure-vessel/flatpak-exports-private.h b/pressure-vessel/flatpak-exports-private.h index 2bb2f29b14f240ee5ff3c17f706cb3bd1d33d6cb..ccf405bbfb5ada0cd63ce17965400998ad1f6639 100644 --- a/pressure-vessel/flatpak-exports-private.h +++ b/pressure-vessel/flatpak-exports-private.h @@ -1,5 +1,5 @@ /* - * Taken from Flatpak, last updated: 1.11.2 + * Taken from Flatpak, last updated: 1.11.2-15-gc392e5ad * Copyright © 2014-2018 Red Hat, Inc * SPDX-License-Identifier: LGPL-2.1-or-later * @@ -65,8 +65,21 @@ FlatpakFilesystemMode flatpak_exports_path_get_mode (FlatpakExports *exports, G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakExports, flatpak_exports_free); +/* + * FlatpakExportsTestFlags: + * @FLATPAK_EXPORTS_TEST_FLAGS_AUTOFS: Pretend everything is an autofs. + * + * Flags used to provide mock behaviour during unit testing. + */ +typedef enum +{ + FLATPAK_EXPORTS_TEST_FLAGS_AUTOFS = (1 << 0), + FLATPAK_EXPORTS_TEST_FLAGS_NONE = 0 +} FlatpakExportsTestFlags; + void flatpak_exports_take_host_fd (FlatpakExports *exports, int fd); - +void flatpak_exports_set_test_flags (FlatpakExports *exports, + FlatpakExportsTestFlags flags); #endif /* __FLATPAK_EXPORTS_H__ */ diff --git a/pressure-vessel/flatpak-exports.c b/pressure-vessel/flatpak-exports.c index 5b224b57b49ba41b28a87aaea397c51b7c3f31d3..9bd653057359b5f8a3ddcb24087c17e5510afccc 100644 --- a/pressure-vessel/flatpak-exports.c +++ b/pressure-vessel/flatpak-exports.c @@ -1,5 +1,5 @@ /* - * Taken from Flatpak, last updated: 1.11.2 + * Taken from Flatpak, last updated: 1.11.2-15-gc392e5ad * Copyright © 2014-2019 Red Hat, Inc * SPDX-License-Identifier: LGPL-2.1-or-later * @@ -111,6 +111,7 @@ struct _FlatpakExports FlatpakFilesystemMode host_etc; FlatpakFilesystemMode host_os; int host_fd; + FlatpakExportsTestFlags test_flags; }; /* @@ -127,6 +128,13 @@ flatpak_exports_take_host_fd (FlatpakExports *exports, exports->host_fd = fd; } +void +flatpak_exports_set_test_flags (FlatpakExports *exports, + FlatpakExportsTestFlags flags) +{ + exports->test_flags = flags; +} + static gboolean flatpak_exports_stat_in_host (FlatpakExports *exports, const char *abs_path, @@ -140,7 +148,11 @@ flatpak_exports_stat_in_host (FlatpakExports *exports, { /* If abs_path is "/usr", then stat "usr" relative to host_fd. * As a special case, if abs_path is "/", stat host_fd itself, - * due to the use of AT_EMPTY_PATH. */ + * due to the use of AT_EMPTY_PATH. + * + * This won't work if ${host_fd}/${abs_path} contains symlinks + * that are absolute or otherwise escape from the mock root, + * so be careful not to do that in unit tests. */ return glnx_fstatat (exports->host_fd, &abs_path[1], buf, AT_EMPTY_PATH | flags, error); @@ -156,6 +168,8 @@ flatpak_exports_readlink_in_host (FlatpakExports *exports, { g_return_val_if_fail (abs_path[0] == '/', FALSE); + /* Similar to flatpak_exports_stat_in_host, this assumes the mock root + * doesn't contain symlinks that escape from the mock root. */ if (exports->host_fd >= 0) return glnx_readlinkat_malloc (exports->host_fd, &abs_path[1], NULL, error); @@ -163,6 +177,61 @@ flatpak_exports_readlink_in_host (FlatpakExports *exports, return glnx_readlinkat_malloc (AT_FDCWD, abs_path, NULL, error); } +/* path must be absolute, but this is not checked because assertions + * are not async-signal-safe. */ +static int +flatpak_exports_open_in_host_async_signal_safe (FlatpakExports *exports, + const char *abs_path, + int flags) +{ + flags |= O_CLOEXEC; + + if (exports->host_fd >= 0) + { + /* Similar to flatpak_exports_stat_in_host, this assumes the mock root + * doesn't contain symlinks that escape from the mock root. */ + return openat (exports->host_fd, &abs_path[1], + flags); + } + + return openat (AT_FDCWD, abs_path, flags); +} + +static int +flatpak_exports_open_in_host (FlatpakExports *exports, + const char *abs_path, + int flags) +{ + g_return_val_if_fail (abs_path[0] == '/', -1); + return flatpak_exports_open_in_host_async_signal_safe (exports, abs_path, flags); +} + +static char * +flatpak_exports_resolve_link_in_host (FlatpakExports *exports, + const char *abs_path, + GError **error) +{ + g_return_val_if_fail (abs_path[0] == '/', FALSE); + + if (exports->host_fd >= 0) + { + g_autofree char *fd_path = g_strdup_printf ("/proc/self/fd/%d/", + exports->host_fd); + g_autofree char *real_path = g_strdup_printf ("%s%s", fd_path, &abs_path[1]); + g_autofree char *resolved = flatpak_resolve_link (real_path, error); + + if (resolved == NULL) + return NULL; + + if (!g_str_has_prefix (resolved, fd_path)) + return glnx_null_throw (error, "Symbolic link escapes from mock root"); + + return g_strdup (resolved + strlen (fd_path) - 1); + } + + return flatpak_resolve_link (abs_path, error); +} + static void exported_path_free (ExportedPath *exported_path) { @@ -183,6 +252,7 @@ flatpak_exports_new (void) void flatpak_exports_free (FlatpakExports *exports) { + glnx_close_fd (&exports->host_fd); g_hash_table_destroy (exports->hash); g_free (exports); } @@ -271,22 +341,24 @@ compare_eps (const ExportedPath *a, /* 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) +path_is_dir (FlatpakExports *exports, + const char *path) { struct stat s; - if (lstat (path, &s) != 0) + if (!flatpak_exports_stat_in_host (exports, path, &s, AT_SYMLINK_NOFOLLOW, NULL)) return FALSE; return S_ISDIR (s.st_mode); } static gboolean -path_is_symlink (const char *path) +path_is_symlink (FlatpakExports *exports, + const char *path) { struct stat s; - if (lstat (path, &s) != 0) + if (!flatpak_exports_stat_in_host (exports, path, &s, AT_SYMLINK_NOFOLLOW, NULL)) return FALSE; return S_ISLNK (s.st_mode); @@ -339,7 +411,9 @@ flatpak_exports_append_bwrap_args (FlatpakExports *exports, { if (!path_parent_is_mapped (keys, n_keys, exports->hash, path)) { - g_autofree char *resolved = flatpak_resolve_link (path, NULL); + g_autofree char *resolved = flatpak_exports_resolve_link_in_host (exports, + path, + NULL); if (resolved) { g_autofree char *parent = g_path_get_dirname (path); @@ -352,7 +426,7 @@ flatpak_exports_append_bwrap_args (FlatpakExports *exports, { /* 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_is_dir (exports, 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 */ @@ -363,7 +437,7 @@ flatpak_exports_append_bwrap_args (FlatpakExports *exports, } else if (ep->mode == FAKE_MODE_DIR) { - if (path_is_dir (path)) + if (path_is_dir (exports, path)) flatpak_bwrap_add_args (bwrap, "--dir", path, NULL); } else @@ -509,6 +583,7 @@ flatpak_exports_path_get_mode (FlatpakExports *exports, g_qsort_with_data (keys, n_keys, sizeof (char *), (GCompareDataFunc) flatpak_strcmp0_ptr, NULL); + /* Syntactic canonicalization only, no need to use host_fd */ path = canonical = flatpak_canonicalize_filename (path); parts = g_strsplit (path + 1, "/", -1); @@ -526,9 +601,13 @@ flatpak_exports_path_get_mode (FlatpakExports *exports, if (path_is_mapped (keys, n_keys, exports->hash, path_builder->str, &is_readonly)) { - if (lstat (path_builder->str, &st) != 0) + g_autoptr(GError) stat_error = NULL; + + if (!flatpak_exports_stat_in_host (exports, path_builder->str, &st, AT_SYMLINK_NOFOLLOW, &stat_error)) { - if (errno == ENOENT && parts[i + 1] == NULL && !is_readonly) + if (g_error_matches (stat_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && + parts[i + 1] == NULL && + !is_readonly) { /* Last element was mapped but isn't there, this is * OK (used for the save case) if we the parent is @@ -543,7 +622,9 @@ flatpak_exports_path_get_mode (FlatpakExports *exports, if (S_ISLNK (st.st_mode)) { - g_autofree char *resolved = flatpak_resolve_link (path_builder->str, NULL); + g_autofree char *resolved = flatpak_exports_resolve_link_in_host (exports, + path_builder->str, + NULL); g_autoptr(GString) path2_builder = NULL; int j; @@ -618,7 +699,8 @@ do_export_path (FlatpakExports *exports, * have to mess with forks and stuff to be able to handle the timeout. */ static gboolean -check_if_autofs_works (const char *path) +check_if_autofs_works (FlatpakExports *exports, + const char *path) { int selfpipe[2]; struct timeval timeout; @@ -627,6 +709,8 @@ check_if_autofs_works (const char *path) int res; int wstatus; + g_return_val_if_fail (path[0] == '/', FALSE); + if (pipe2 (selfpipe, O_CLOEXEC) == -1) return FALSE; @@ -646,7 +730,10 @@ check_if_autofs_works (const char *path) /* Note: open, close and _exit are signal-async-safe, so it is ok to call in the child after fork */ close (selfpipe[0]); /* Close unused read end */ - int dir_fd = open (path, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_DIRECTORY); + + int dir_fd = flatpak_exports_open_in_host_async_signal_safe (exports, + path, + O_RDONLY | O_NONBLOCK | O_DIRECTORY); _exit (dir_fd == -1 ? 1 : 0); } @@ -678,6 +765,12 @@ check_if_autofs_works (const char *path) if (!WIFEXITED (wstatus) || WEXITSTATUS (wstatus) != 0) return FALSE; + if (G_UNLIKELY (exports->test_flags & FLATPAK_EXPORTS_TEST_FLAGS_AUTOFS)) + { + if (strcmp (path, "/broken-autofs") == 0) + return FALSE; + } + return TRUE; } @@ -710,7 +803,7 @@ _exports_path_expose (FlatpakExports *exports, } /* Check if it exists at all */ - o_path_fd = open (path, O_PATH | O_NOFOLLOW | O_CLOEXEC); + o_path_fd = flatpak_exports_open_in_host (exports, path, O_PATH | O_NOFOLLOW); if (o_path_fd == -1) return FALSE; @@ -728,15 +821,18 @@ _exports_path_expose (FlatpakExports *exports, if (fstatfs (o_path_fd, &stfs) != 0) return FALSE; - if (stfs.f_type == AUTOFS_SUPER_MAGIC) + if (stfs.f_type == AUTOFS_SUPER_MAGIC || + (G_UNLIKELY (exports->test_flags & FLATPAK_EXPORTS_TEST_FLAGS_AUTOFS) && + S_ISDIR (st.st_mode))) { - if (!check_if_autofs_works (path)) + if (!check_if_autofs_works (exports, path)) { g_debug ("ignoring blocking autofs path %s", path); return FALSE; } } + /* Syntactic canonicalization only, no need to use host_fd */ path = canonical = flatpak_canonicalize_filename (path); for (i = 0; dont_export_in[i] != NULL; i++) @@ -770,9 +866,9 @@ _exports_path_expose (FlatpakExports *exports, if (slash) *slash = 0; - if (path_is_symlink (path) && !never_export_as_symlink (path)) + if (path_is_symlink (exports, path) && !never_export_as_symlink (path)) { - g_autofree char *resolved = flatpak_resolve_link (path, NULL); + g_autofree char *resolved = flatpak_exports_resolve_link_in_host (exports, path, NULL); g_autofree char *new_target = NULL; if (resolved)