Files
flatpak/tests/test-exports.c
Weng Xuetian 0402e1614c Limit the usage of WAYLAND_SOCKET to an opt-in feature
1. For security context creation, only relies on WAYLAND_DISPLAY, do not
   use WAYLAND_SOCKET since the file descriptor defined by WAYLAND_SOCKET
   can be only consumed once.
2. Due to the incompatiblity between WAYLAND_SOCKET and the security
   context, add a new permission --socket=inherit-wayland-socket
   to limit the usage of WAYLAND_SOCKET to an opt-in feature. Only when
   this flag is set, WAYLAND_SOCKET will be passed to the sandbox.
3. When WAYLAND_SOCKET is not inherited, set FD_CLOEXEC to avoid it to
   be leaked the to sandbox.

Closes: #5614
2024-02-14 19:39:50 +00:00

1504 lines
56 KiB
C

/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s:
* Copyright © 2020 Collabora Ltd.
*
* 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/>.
*/
#include "config.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <glib.h>
#include "flatpak.h"
#include "flatpak-bwrap-private.h"
#include "flatpak-context-private.h"
#include "flatpak-exports-private.h"
#include "flatpak-metadata-private.h"
#include "flatpak-utils-base-private.h"
#include "flatpak-utils-private.h"
#include "tests/testlib.h"
/*
* Assert that the next few arguments starting from @i are setting up
* /run/host/os-release. Return the next argument that hasn't been used.
*/
G_GNUC_WARN_UNUSED_RESULT static gsize
assert_next_is_os_release (FlatpakBwrap *bwrap,
gsize i)
{
if (g_file_test ("/etc/os-release", G_FILE_TEST_EXISTS))
{
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "--ro-bind");
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "/etc/os-release");
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "/run/host/os-release");
}
else if (g_file_test ("/usr/lib/os-release", G_FILE_TEST_EXISTS))
{
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "--ro-bind");
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "/usr/lib/os-release");
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "/run/host/os-release");
}
return i;
}
/* Assert that arguments starting from @i are --dir @dir.
* Return the new @i. */
G_GNUC_WARN_UNUSED_RESULT static gsize
assert_next_is_dir (FlatpakBwrap *bwrap,
gsize i,
const char *dir)
{
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "--dir");
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, dir);
return i;
}
/* Assert that arguments starting from @i are --tmpfs @dir.
* Return the new @i. */
G_GNUC_WARN_UNUSED_RESULT static gsize
assert_next_is_tmpfs (FlatpakBwrap *bwrap,
gsize i,
const char *dir)
{
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "--tmpfs");
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, dir);
return i;
}
/* Assert that arguments starting from @i are @how @path @path.
* Return the new @i. */
G_GNUC_WARN_UNUSED_RESULT static gsize
assert_next_is_bind (FlatpakBwrap *bwrap,
gsize i,
const char *how,
const char *path,
const char *dest)
{
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, how);
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, path);
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, dest);
return i;
}
/* Assert that arguments starting from @i are --symlink @rel_target @path,
* where @rel_target goes up from @path to the root and back down to the
* target of the symlink. Return the new @i. */
G_GNUC_WARN_UNUSED_RESULT static gsize
assert_next_is_symlink (FlatpakBwrap *bwrap,
gsize i,
const char *target,
const char *path)
{
const char *got_target;
g_autofree gchar *dir = NULL;
g_autofree gchar *resolved = NULL;
g_autofree gchar *canon = NULL;
g_autofree gchar *resolved_target = NULL;
g_autofree gchar *expected = NULL;
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "--symlink");
g_assert_cmpuint (i, <, bwrap->argv->len);
got_target = bwrap->argv->pdata[i++];
g_assert_false (g_path_is_absolute (got_target));
dir = g_path_get_dirname (path);
resolved = g_build_filename (dir, got_target, NULL);
canon = flatpak_canonicalize_filename (resolved);
if (g_path_is_absolute (target))
resolved_target = g_strdup (target);
else
resolved_target = g_build_filename (dir, target, NULL);
expected = flatpak_canonicalize_filename (resolved_target);
g_assert_cmpstr (canon, ==, expected);
g_assert_true (g_str_has_suffix (got_target, target));
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, path);
return i;
}
/* Print the arguments of a call to bwrap. */
static void
print_bwrap (FlatpakBwrap *bwrap)
{
guint i;
for (i = 0; i < bwrap->argv->len && bwrap->argv->pdata[i] != NULL; i++)
g_test_message ("%s", (const char *) bwrap->argv->pdata[i]);
g_test_message ("--");
}
static void
test_empty_context (void)
{
g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL);
g_autoptr(FlatpakContext) context = flatpak_context_new ();
g_autoptr(FlatpakExports) exports = NULL;
g_autofree char *xdg_dirs_conf = NULL;
gboolean home_access = FALSE;
g_assert_cmpuint (g_hash_table_size (context->env_vars), ==, 0);
g_assert_cmpuint (g_hash_table_size (context->persistent), ==, 0);
g_assert_cmpuint (g_hash_table_size (context->filesystems), ==, 0);
g_assert_cmpuint (g_hash_table_size (context->session_bus_policy), ==, 0);
g_assert_cmpuint (g_hash_table_size (context->system_bus_policy), ==, 0);
g_assert_cmpuint (g_hash_table_size (context->generic_policy), ==, 0);
g_assert_cmpuint (context->shares, ==, 0);
g_assert_cmpuint (context->shares_valid, ==, 0);
g_assert_cmpuint (context->sockets, ==, 0);
g_assert_cmpuint (context->sockets_valid, ==, 0);
g_assert_cmpuint (context->devices, ==, 0);
g_assert_cmpuint (context->devices_valid, ==, 0);
g_assert_cmpuint (context->features, ==, 0);
g_assert_cmpuint (context->features_valid, ==, 0);
g_assert_cmpuint (flatpak_context_get_run_flags (context), ==, 0);
exports = flatpak_context_get_exports (context, "com.example.App");
g_assert_nonnull (exports);
g_clear_pointer (&exports, flatpak_exports_free);
exports = flatpak_context_get_exports_full (context,
NULL, NULL,
TRUE, TRUE,
&xdg_dirs_conf, &home_access);
g_assert_nonnull (exports);
g_assert_nonnull (xdg_dirs_conf);
flatpak_context_append_bwrap_filesystem (context, bwrap,
"com.example.App",
NULL, exports, xdg_dirs_conf,
home_access);
print_bwrap (bwrap);
}
static void
test_full_context (void)
{
g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL);
g_autoptr(FlatpakContext) context = flatpak_context_new ();
g_autoptr(FlatpakExports) exports = NULL;
g_autoptr(GError) error = NULL;
g_autoptr(GKeyFile) keyfile = g_key_file_new ();
g_autofree gchar *text = NULL;
g_autofree char *xdg_dirs_conf = NULL;
g_auto(GStrv) strv = NULL;
gsize i, n;
gboolean home_access = FALSE;
g_key_file_set_value (keyfile,
FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_SHARED,
"network;ipc;");
g_key_file_set_value (keyfile,
FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_SOCKETS,
"x11;wayland;pulseaudio;session-bus;system-bus;"
"fallback-x11;ssh-auth;pcsc;cups;inherit-wayland-socket;");
g_key_file_set_value (keyfile,
FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_DEVICES,
"dri;all;kvm;shm;");
g_key_file_set_value (keyfile,
FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_FEATURES,
"devel;multiarch;bluetooth;canbus;per-app-dev-shm;");
g_key_file_set_value (keyfile,
FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_FILESYSTEMS,
"host;/home;!/opt");
g_key_file_set_value (keyfile,
FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_PERSISTENT,
".openarena;");
g_key_file_set_value (keyfile,
FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY,
"org.example.SessionService",
"own");
g_key_file_set_value (keyfile,
FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY,
"net.example.SystemService",
"talk");
g_key_file_set_value (keyfile,
FLATPAK_METADATA_GROUP_ENVIRONMENT,
"HYPOTHETICAL_PATH", "/foo:/bar");
g_key_file_set_value (keyfile,
FLATPAK_METADATA_GROUP_ENVIRONMENT,
"LD_PRELOAD", "");
g_key_file_set_value (keyfile,
FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_UNSET_ENVIRONMENT,
"LD_PRELOAD;LD_AUDIT;");
g_key_file_set_value (keyfile,
FLATPAK_METADATA_GROUP_PREFIX_POLICY "MyPolicy",
"Colours", "blue;green;");
flatpak_context_load_metadata (context, keyfile, &error);
g_assert_no_error (error);
g_assert_cmpuint (context->shares, ==,
(FLATPAK_CONTEXT_SHARED_NETWORK |
FLATPAK_CONTEXT_SHARED_IPC));
g_assert_cmpuint (context->shares_valid, ==, context->shares);
g_assert_cmpuint (context->devices, ==,
(FLATPAK_CONTEXT_DEVICE_DRI |
FLATPAK_CONTEXT_DEVICE_ALL |
FLATPAK_CONTEXT_DEVICE_KVM |
FLATPAK_CONTEXT_DEVICE_SHM));
g_assert_cmpuint (context->devices_valid, ==, context->devices);
g_assert_cmpuint (context->sockets, ==,
(FLATPAK_CONTEXT_SOCKET_X11 |
FLATPAK_CONTEXT_SOCKET_WAYLAND |
FLATPAK_CONTEXT_SOCKET_INHERIT_WAYLAND_SOCKET |
FLATPAK_CONTEXT_SOCKET_PULSEAUDIO |
FLATPAK_CONTEXT_SOCKET_SESSION_BUS |
FLATPAK_CONTEXT_SOCKET_SYSTEM_BUS |
FLATPAK_CONTEXT_SOCKET_FALLBACK_X11 |
FLATPAK_CONTEXT_SOCKET_SSH_AUTH |
FLATPAK_CONTEXT_SOCKET_PCSC |
FLATPAK_CONTEXT_SOCKET_CUPS));
g_assert_cmpuint (context->sockets_valid, ==, context->sockets);
g_assert_cmpuint (context->features, ==,
(FLATPAK_CONTEXT_FEATURE_DEVEL |
FLATPAK_CONTEXT_FEATURE_MULTIARCH |
FLATPAK_CONTEXT_FEATURE_BLUETOOTH |
FLATPAK_CONTEXT_FEATURE_CANBUS |
FLATPAK_CONTEXT_FEATURE_PER_APP_DEV_SHM));
g_assert_cmpuint (context->features_valid, ==, context->features);
g_assert_cmpuint (flatpak_context_get_run_flags (context), ==,
(FLATPAK_RUN_FLAG_DEVEL |
FLATPAK_RUN_FLAG_MULTIARCH |
FLATPAK_RUN_FLAG_BLUETOOTH |
FLATPAK_RUN_FLAG_CANBUS));
g_assert_cmpuint (g_hash_table_size (context->env_vars), ==, 3);
g_assert_true (g_hash_table_contains (context->env_vars, "LD_AUDIT"));
g_assert_null (g_hash_table_lookup (context->env_vars, "LD_AUDIT"));
g_assert_true (g_hash_table_contains (context->env_vars, "LD_PRELOAD"));
g_assert_null (g_hash_table_lookup (context->env_vars, "LD_PRELOAD"));
g_assert_true (g_hash_table_contains (context->env_vars, "HYPOTHETICAL_PATH"));
g_assert_cmpstr (g_hash_table_lookup (context->env_vars, "HYPOTHETICAL_PATH"),
==, "/foo:/bar");
exports = flatpak_context_get_exports (context, "com.example.App");
g_assert_nonnull (exports);
g_clear_pointer (&exports, flatpak_exports_free);
exports = flatpak_context_get_exports_full (context,
NULL, NULL,
TRUE, TRUE,
&xdg_dirs_conf, &home_access);
g_assert_nonnull (exports);
g_assert_nonnull (xdg_dirs_conf);
flatpak_context_append_bwrap_filesystem (context, bwrap,
"com.example.App",
NULL, exports, xdg_dirs_conf,
home_access);
print_bwrap (bwrap);
g_clear_pointer (&keyfile, g_key_file_unref);
keyfile = g_key_file_new ();
flatpak_context_save_metadata (context, FALSE, keyfile);
text = g_key_file_to_data (keyfile, NULL, NULL);
g_test_message ("Saved:\n%s", text);
g_clear_pointer (&text, g_free);
/* Test that keys round-trip back into the file */
strv = g_key_file_get_string_list (keyfile, FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_FILESYSTEMS,
&n, &error);
g_assert_nonnull (strv);
/* The order is undefined, so sort them first */
g_qsort_with_data (strv, n, sizeof (char *),
(GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
i = 0;
g_assert_cmpstr (strv[i++], ==, "!/opt");
g_assert_cmpstr (strv[i++], ==, "/home");
g_assert_cmpstr (strv[i++], ==, "host");
g_assert_cmpstr (strv[i], ==, NULL);
g_assert_cmpuint (i, ==, n);
g_clear_pointer (&strv, g_strfreev);
strv = g_key_file_get_string_list (keyfile, FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_SHARED,
&n, &error);
g_assert_no_error (error);
g_assert_nonnull (strv);
g_qsort_with_data (strv, n, sizeof (char *),
(GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
i = 0;
g_assert_cmpstr (strv[i++], ==, "ipc");
g_assert_cmpstr (strv[i++], ==, "network");
g_assert_cmpstr (strv[i], ==, NULL);
g_assert_cmpuint (i, ==, n);
g_clear_pointer (&strv, g_strfreev);
strv = g_key_file_get_string_list (keyfile, FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_SOCKETS,
&n, &error);
g_assert_no_error (error);
g_assert_nonnull (strv);
g_qsort_with_data (strv, n, sizeof (char *),
(GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
i = 0;
g_assert_cmpstr (strv[i++], ==, "cups");
g_assert_cmpstr (strv[i++], ==, "fallback-x11");
g_assert_cmpstr (strv[i++], ==, "inherit-wayland-socket");
g_assert_cmpstr (strv[i++], ==, "pcsc");
g_assert_cmpstr (strv[i++], ==, "pulseaudio");
g_assert_cmpstr (strv[i++], ==, "session-bus");
g_assert_cmpstr (strv[i++], ==, "ssh-auth");
g_assert_cmpstr (strv[i++], ==, "system-bus");
g_assert_cmpstr (strv[i++], ==, "wayland");
g_assert_cmpstr (strv[i++], ==, "x11");
g_assert_cmpstr (strv[i], ==, NULL);
g_assert_cmpuint (i, ==, n);
g_clear_pointer (&strv, g_strfreev);
strv = g_key_file_get_string_list (keyfile, FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_DEVICES,
&n, &error);
g_assert_no_error (error);
g_assert_nonnull (strv);
g_qsort_with_data (strv, n, sizeof (char *),
(GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
i = 0;
g_assert_cmpstr (strv[i++], ==, "all");
g_assert_cmpstr (strv[i++], ==, "dri");
g_assert_cmpstr (strv[i++], ==, "kvm");
g_assert_cmpstr (strv[i++], ==, "shm");
g_assert_cmpstr (strv[i], ==, NULL);
g_assert_cmpuint (i, ==, n);
g_clear_pointer (&strv, g_strfreev);
strv = g_key_file_get_string_list (keyfile, FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_PERSISTENT,
&n, &error);
g_assert_no_error (error);
g_assert_nonnull (strv);
g_qsort_with_data (strv, n, sizeof (char *),
(GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
i = 0;
g_assert_cmpstr (strv[i++], ==, ".openarena");
g_assert_cmpstr (strv[i], ==, NULL);
g_assert_cmpuint (i, ==, n);
g_clear_pointer (&strv, g_strfreev);
strv = g_key_file_get_string_list (keyfile, FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_UNSET_ENVIRONMENT,
&n, &error);
g_assert_no_error (error);
g_assert_nonnull (strv);
g_qsort_with_data (strv, n, sizeof (char *),
(GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
i = 0;
g_assert_cmpstr (strv[i++], ==, "LD_AUDIT");
g_assert_cmpstr (strv[i++], ==, "LD_PRELOAD");
g_assert_cmpstr (strv[i], ==, NULL);
g_assert_cmpuint (i, ==, n);
g_clear_pointer (&strv, g_strfreev);
strv = g_key_file_get_keys (keyfile, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY,
&n, &error);
g_assert_no_error (error);
g_assert_nonnull (strv);
g_qsort_with_data (strv, n, sizeof (char *),
(GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
i = 0;
g_assert_cmpstr (strv[i++], ==, "org.example.SessionService");
g_assert_cmpstr (strv[i], ==, NULL);
g_assert_cmpuint (i, ==, n);
g_clear_pointer (&strv, g_strfreev);
text = g_key_file_get_string (keyfile, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY,
"org.example.SessionService", &error);
g_assert_no_error (error);
g_assert_cmpstr (text, ==, "own");
g_clear_pointer (&text, g_free);
strv = g_key_file_get_keys (keyfile, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY,
&n, &error);
g_assert_no_error (error);
g_assert_nonnull (strv);
g_qsort_with_data (strv, n, sizeof (char *),
(GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
i = 0;
g_assert_cmpstr (strv[i++], ==, "net.example.SystemService");
g_assert_cmpstr (strv[i], ==, NULL);
g_assert_cmpuint (i, ==, n);
g_clear_pointer (&strv, g_strfreev);
text = g_key_file_get_string (keyfile, FLATPAK_METADATA_GROUP_SYSTEM_BUS_POLICY,
"net.example.SystemService", &error);
g_assert_no_error (error);
g_assert_cmpstr (text, ==, "talk");
g_clear_pointer (&text, g_free);
strv = g_key_file_get_keys (keyfile, FLATPAK_METADATA_GROUP_ENVIRONMENT,
&n, &error);
g_assert_no_error (error);
g_assert_nonnull (strv);
g_qsort_with_data (strv, n, sizeof (char *),
(GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
i = 0;
g_assert_cmpstr (strv[i++], ==, "HYPOTHETICAL_PATH");
g_assert_cmpstr (strv[i++], ==, "LD_AUDIT");
g_assert_cmpstr (strv[i++], ==, "LD_PRELOAD");
g_assert_cmpstr (strv[i], ==, NULL);
g_assert_cmpuint (i, ==, n);
g_clear_pointer (&strv, g_strfreev);
text = g_key_file_get_string (keyfile, FLATPAK_METADATA_GROUP_ENVIRONMENT,
"HYPOTHETICAL_PATH", &error);
g_assert_no_error (error);
g_assert_cmpstr (text, ==, "/foo:/bar");
g_clear_pointer (&text, g_free);
text = g_key_file_get_string (keyfile, FLATPAK_METADATA_GROUP_ENVIRONMENT,
"LD_AUDIT", &error);
g_assert_no_error (error);
g_assert_cmpstr (text, ==, "");
g_clear_pointer (&text, g_free);
text = g_key_file_get_string (keyfile, FLATPAK_METADATA_GROUP_ENVIRONMENT,
"LD_PRELOAD", &error);
g_assert_no_error (error);
g_assert_cmpstr (text, ==, "");
g_clear_pointer (&text, g_free);
strv = g_key_file_get_keys (keyfile, FLATPAK_METADATA_GROUP_PREFIX_POLICY "MyPolicy",
&n, &error);
g_assert_no_error (error);
g_assert_nonnull (strv);
g_qsort_with_data (strv, n, sizeof (char *),
(GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
i = 0;
g_assert_cmpstr (strv[i++], ==, "Colours");
g_assert_cmpstr (strv[i], ==, NULL);
g_assert_cmpuint (i, ==, n);
g_clear_pointer (&strv, g_strfreev);
strv = g_key_file_get_string_list (keyfile, FLATPAK_METADATA_GROUP_PREFIX_POLICY "MyPolicy",
"Colours", &n, &error);
g_assert_no_error (error);
g_assert_nonnull (strv);
g_qsort_with_data (strv, n, sizeof (char *),
(GCompareDataFunc) flatpak_strcmp0_ptr, NULL);
i = 0;
g_assert_cmpstr (strv[i++], ==, "blue");
g_assert_cmpstr (strv[i++], ==, "green");
g_assert_cmpstr (strv[i], ==, NULL);
g_assert_cmpuint (i, ==, n);
g_clear_pointer (&strv, g_strfreev);
}
typedef struct
{
const char *input;
GOptionError code;
} NotFilesystem;
static const NotFilesystem not_filesystems[] =
{
{ "", G_OPTION_ERROR_FAILED },
{ "homework", G_OPTION_ERROR_FAILED },
{ "xdg-download/foo/bar/..", G_OPTION_ERROR_BAD_VALUE },
{ "xdg-download/../foo/bar", G_OPTION_ERROR_BAD_VALUE },
{ "xdg-download/foo/../bar", G_OPTION_ERROR_BAD_VALUE },
{ "xdg-run", G_OPTION_ERROR_FAILED },
{ "/", G_OPTION_ERROR_BAD_VALUE },
{ "/////././././././//////", G_OPTION_ERROR_BAD_VALUE },
{ "host:reset", G_OPTION_ERROR_FAILED },
{ "host-reset", G_OPTION_ERROR_FAILED },
{ "host-reset:rw", G_OPTION_ERROR_FAILED },
{ "host-reset:reset", G_OPTION_ERROR_FAILED },
{ "!host-reset:reset", G_OPTION_ERROR_FAILED },
{ "/foo:reset", G_OPTION_ERROR_FAILED },
{ "!/foo:reset", G_OPTION_ERROR_FAILED },
};
typedef struct
{
const char *input;
FlatpakFilesystemMode mode;
const char *fs;
} Filesystem;
static const Filesystem filesystems[] =
{
{ "home", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "host", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "host-etc", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "host-os", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "host:ro", FLATPAK_FILESYSTEM_MODE_READ_ONLY, "host" },
{ "home:rw", FLATPAK_FILESYSTEM_MODE_READ_WRITE, "home" },
{ "~/Music", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "/srv/obs/debian\\:sid\\:main:create", FLATPAK_FILESYSTEM_MODE_CREATE,
"/srv/obs/debian:sid:main" },
{ "/srv/c\\:\\\\Program Files\\\\Steam", FLATPAK_FILESYSTEM_MODE_READ_WRITE,
"/srv/c:\\Program Files\\Steam" },
{ "/srv/escaped\\unnecessarily", FLATPAK_FILESYSTEM_MODE_READ_WRITE,
"/srv/escapedunnecessarily" },
{ "xdg-desktop", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-desktop/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-documents", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-documents/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-download", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-download/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-music", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-music/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-pictures", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-pictures/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-public-share", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-public-share/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-templates", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-templates/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-videos", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-videos/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-data", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-data/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-cache", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-cache/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-config", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-config/Stuff", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "xdg-config/././///.///././.", FLATPAK_FILESYSTEM_MODE_READ_WRITE, "xdg-config" },
{ "xdg-config/////", FLATPAK_FILESYSTEM_MODE_READ_WRITE, "xdg-config" },
{ "xdg-run/dbus", FLATPAK_FILESYSTEM_MODE_READ_WRITE },
{ "~", FLATPAK_FILESYSTEM_MODE_READ_WRITE, "home" },
{ "~/.", FLATPAK_FILESYSTEM_MODE_READ_WRITE, "home" },
{ "~/", FLATPAK_FILESYSTEM_MODE_READ_WRITE, "home" },
{ "~///././//", FLATPAK_FILESYSTEM_MODE_READ_WRITE, "home" },
{ "home/", FLATPAK_FILESYSTEM_MODE_READ_WRITE, "home" },
{ "home/Projects", FLATPAK_FILESYSTEM_MODE_READ_WRITE, "~/Projects" },
{ "!home", FLATPAK_FILESYSTEM_MODE_NONE, "home" },
{ "!host:reset", FLATPAK_FILESYSTEM_MODE_NONE, "host-reset" },
{ "!host-reset", FLATPAK_FILESYSTEM_MODE_NONE, "host-reset" },
};
static void
test_filesystems (void)
{
gsize i;
for (i = 0; i < G_N_ELEMENTS (filesystems); i++)
{
const Filesystem *fs = &filesystems[i];
const char *input = fs->input;
gboolean negated = FALSE;
g_autoptr(GError) error = NULL;
g_autofree char *normalized;
FlatpakFilesystemMode mode;
gboolean ret;
g_test_message ("%s", fs->input);
if (input[0] == '!')
{
g_test_message ("-> input is negated");
negated = TRUE;
input++;
}
ret = flatpak_context_parse_filesystem (input, negated,
&normalized, &mode, &error);
g_assert_no_error (error);
g_assert_true (ret);
g_test_message ("-> mode: %u", mode);
g_test_message ("-> normalized filesystem: %s", normalized);
if (fs->fs == NULL)
g_assert_cmpstr (normalized, ==, input);
else
g_assert_cmpstr (normalized, ==, fs->fs);
g_assert_cmpuint (mode, ==, fs->mode);
}
for (i = 0; i < G_N_ELEMENTS (not_filesystems); i++)
{
const NotFilesystem *not = &not_filesystems[i];
const char *input = not->input;
gboolean negated = FALSE;
g_autoptr(GError) error = NULL;
char *normalized = NULL;
FlatpakFilesystemMode mode;
gboolean ret;
g_test_message ("%s", not->input);
if (input[0] == '!')
{
negated = TRUE;
input++;
}
ret = flatpak_context_parse_filesystem (input, negated,
&normalized, &mode, &error);
g_test_message ("-> %s", error ? error->message : "(no error)");
g_assert_error (error, G_OPTION_ERROR, not->code);
g_assert_false (ret);
g_assert_null (normalized);
}
}
static void
test_empty (void)
{
g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL);
g_autoptr(FlatpakExports) exports = flatpak_exports_new ();
gsize i;
g_assert_false (flatpak_exports_path_is_visible (exports, "/run"));
g_assert_cmpint (flatpak_exports_path_get_mode (exports, "/tmp"), ==,
FLATPAK_FILESYSTEM_MODE_NONE);
flatpak_bwrap_add_arg (bwrap, "bwrap");
flatpak_exports_append_bwrap_args (exports, bwrap);
flatpak_bwrap_finish (bwrap);
print_bwrap (bwrap);
i = 0;
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "bwrap");
i = assert_next_is_os_release (bwrap, i);
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, NULL);
g_assert_cmpuint (i, ==, bwrap->argv->len);
}
static void
test_full (void)
{
g_autoptr(GError) error = NULL;
g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL);
g_autoptr(FlatpakExports) exports = flatpak_exports_new ();
g_autofree gchar *subdir = g_build_filename (isolated_test_dir, "test_full", NULL);
g_autofree gchar *expose_rw = g_build_filename (subdir, "expose-rw", NULL);
g_autofree gchar *in_expose_rw = g_build_filename (subdir, "expose-rw",
"file", NULL);
g_autofree gchar *dangling_link_in_expose_rw = g_build_filename (subdir,
"expose-rw",
"dangling",
NULL);
g_autofree gchar *expose_ro = g_build_filename (subdir, "expose-ro", NULL);
g_autofree gchar *in_expose_ro = g_build_filename (subdir, "expose-ro",
"file", NULL);
g_autofree gchar *hide = g_build_filename (subdir, "hide", NULL);
g_autofree gchar *dont_hide = g_build_filename (subdir, "dont-hide", NULL);
g_autofree gchar *hide_below_expose = g_build_filename (subdir,
"expose-ro",
"hide-me",
NULL);
g_autofree gchar *enoent = g_build_filename (subdir, "ENOENT", NULL);
g_autofree gchar *one = g_build_filename (subdir, "1", NULL);
g_autofree gchar *rel_link = g_build_filename (subdir, "1", "rel-link", NULL);
g_autofree gchar *abs_link = g_build_filename (subdir, "1", "abs-link", NULL);
g_autofree gchar *in_abs_link = g_build_filename (subdir, "1", "abs-link",
"file", NULL);
g_autofree gchar *dangling = g_build_filename (subdir, "1", "dangling", NULL);
g_autofree gchar *in_dangling = g_build_filename (subdir, "1", "dangling",
"file", NULL);
g_autofree gchar *abs_target = g_build_filename (subdir, "2", "abs-target", NULL);
g_autofree gchar *target = g_build_filename (subdir, "2", "target", NULL);
g_autofree gchar *create_dir = g_build_filename (subdir, "create-dir", NULL);
g_autofree gchar *create_dir2 = g_build_filename (subdir, "create-dir2", NULL);
gsize i;
gboolean ok;
glnx_shutil_rm_rf_at (-1, subdir, NULL, &error);
if (error != NULL)
{
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
g_clear_error (&error);
}
if (g_mkdir_with_parents (expose_rw, S_IRWXU) != 0)
g_error ("mkdir: %s", g_strerror (errno));
if (g_mkdir_with_parents (expose_ro, S_IRWXU) != 0)
g_error ("mkdir: %s", g_strerror (errno));
if (g_mkdir_with_parents (hide_below_expose, S_IRWXU) != 0)
g_error ("mkdir: %s", g_strerror (errno));
if (g_mkdir_with_parents (hide, S_IRWXU) != 0)
g_error ("mkdir: %s", g_strerror (errno));
if (g_mkdir_with_parents (dont_hide, S_IRWXU) != 0)
g_error ("mkdir: %s", g_strerror (errno));
if (g_mkdir_with_parents (dont_hide, S_IRWXU) != 0)
g_error ("mkdir: %s", g_strerror (errno));
if (g_mkdir_with_parents (abs_target, S_IRWXU) != 0)
g_error ("mkdir: %s", g_strerror (errno));
if (g_mkdir_with_parents (target, S_IRWXU) != 0)
g_error ("mkdir: %s", g_strerror (errno));
if (g_mkdir_with_parents (one, S_IRWXU) != 0)
g_error ("mkdir: %s", g_strerror (errno));
if (g_mkdir_with_parents (create_dir, S_IRWXU) != 0)
g_error ("mkdir: %s", g_strerror (errno));
if (symlink (abs_target, abs_link) != 0)
g_error ("symlink: %s", g_strerror (errno));
if (symlink ("nope", dangling) != 0)
g_error ("symlink: %s", g_strerror (errno));
if (symlink ("nope", dangling_link_in_expose_rw) != 0)
g_error ("symlink: %s", g_strerror (errno));
if (symlink ("../2/target", rel_link) != 0)
g_error ("symlink: %s", g_strerror (errno));
flatpak_exports_add_host_etc_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE);
flatpak_exports_add_host_os_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY);
ok = flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE,
expose_rw, &error);
g_assert_no_error (error);
g_assert_true (ok);
ok = flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
expose_ro, &error);
g_assert_no_error (error);
g_assert_true (ok);
ok = flatpak_exports_add_path_tmpfs (exports, hide_below_expose, &error);
g_assert_no_error (error);
g_assert_true (ok);
ok = flatpak_exports_add_path_expose_or_hide (exports,
FLATPAK_FILESYSTEM_MODE_NONE,
hide, &error);
g_assert_no_error (error);
g_assert_true (ok);
ok = flatpak_exports_add_path_expose_or_hide (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
dont_hide, &error);
g_assert_no_error (error);
g_assert_true (ok);
ok = flatpak_exports_add_path_expose_or_hide (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
enoent, &error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
g_assert_false (ok);
g_clear_error (&error);
ok = flatpak_exports_add_path_expose_or_hide (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE,
rel_link, &error);
g_assert_no_error (error);
g_assert_true (ok);
ok = flatpak_exports_add_path_expose_or_hide (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE,
abs_link, &error);
g_assert_no_error (error);
g_assert_true (ok);
ok = flatpak_exports_add_path_dir (exports, create_dir, &error);
g_assert_no_error (error);
g_assert_true (ok);
ok = flatpak_exports_add_path_dir (exports, create_dir2, &error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
g_assert_false (ok);
g_clear_error (&error);
g_assert_cmpuint (flatpak_exports_path_get_mode (exports, expose_rw), ==,
FLATPAK_FILESYSTEM_MODE_READ_WRITE);
g_assert_cmpuint (flatpak_exports_path_get_mode (exports, expose_ro), ==,
FLATPAK_FILESYSTEM_MODE_READ_ONLY);
g_assert_cmpuint (flatpak_exports_path_get_mode (exports, hide_below_expose), ==,
FLATPAK_FILESYSTEM_MODE_NONE);
g_assert_cmpuint (flatpak_exports_path_get_mode (exports, hide), ==,
FLATPAK_FILESYSTEM_MODE_NONE);
g_assert_cmpuint (flatpak_exports_path_get_mode (exports, dont_hide), ==,
FLATPAK_FILESYSTEM_MODE_READ_ONLY);
/* It knows enoent didn't really exist */
g_assert_cmpuint (flatpak_exports_path_get_mode (exports, enoent), ==,
FLATPAK_FILESYSTEM_MODE_NONE);
g_assert_cmpuint (flatpak_exports_path_get_mode (exports, abs_link), ==,
FLATPAK_FILESYSTEM_MODE_READ_WRITE);
g_assert_cmpuint (flatpak_exports_path_get_mode (exports, rel_link), ==,
FLATPAK_FILESYSTEM_MODE_READ_WRITE);
/* Files the app would be allowed to create count as exposed */
g_assert_cmpuint (flatpak_exports_path_get_mode (exports, in_expose_ro), ==,
FLATPAK_FILESYSTEM_MODE_NONE);
g_assert_cmpuint (flatpak_exports_path_get_mode (exports, in_expose_rw), ==,
FLATPAK_FILESYSTEM_MODE_READ_WRITE);
g_assert_cmpuint (flatpak_exports_path_get_mode (exports, in_abs_link), ==,
FLATPAK_FILESYSTEM_MODE_READ_WRITE);
g_assert_cmpuint (flatpak_exports_path_get_mode (exports, in_dangling), ==,
FLATPAK_FILESYSTEM_MODE_NONE);
flatpak_bwrap_add_arg (bwrap, "bwrap");
flatpak_exports_append_bwrap_args (exports, bwrap);
flatpak_bwrap_finish (bwrap);
print_bwrap (bwrap);
i = 0;
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "bwrap");
i = assert_next_is_symlink (bwrap, i, abs_target, abs_link);
i = assert_next_is_symlink (bwrap, i, "../2/target", rel_link);
i = assert_next_is_bind (bwrap, i, "--bind", abs_target, abs_target);
i = assert_next_is_bind (bwrap, i, "--bind", target, target);
i = assert_next_is_dir (bwrap, i, create_dir);
/* create_dir2 is not currently created with --dir inside the container
* because it doesn't exist outside the container.
* (Is this correct? For now, tolerate either way) */
if (i + 2 < bwrap->argv->len &&
g_strcmp0 (bwrap->argv->pdata[i], "--dir") == 0 &&
g_strcmp0 (bwrap->argv->pdata[i + 1], create_dir2) == 0)
i += 2;
i = assert_next_is_bind (bwrap, i, "--ro-bind", dont_hide, dont_hide);
i = assert_next_is_bind (bwrap, i, "--ro-bind", expose_ro, expose_ro);
/* We don't create a FAKE_MODE_TMPFS in the container unless there is
* a directory on the host to mount it on.
* Hiding $subdir/expose-ro/hide-me has to use --tmpfs because
* $subdir/expose-ro *is* exposed. */
i = assert_next_is_tmpfs (bwrap, i, hide_below_expose);
i = assert_next_is_bind (bwrap, i, "--bind", expose_rw, expose_rw);
/* Hiding $subdir/hide just uses --dir, because $subdir is not
* exposed. */
i = assert_next_is_dir (bwrap, i, hide);
while (i < bwrap->argv->len && bwrap->argv->pdata[i] != NULL)
{
/* An unknown number of --bind, --ro-bind and --symlink,
* depending how your /usr and /etc are set up.
* About the only thing we can say is that they are in threes. */
g_assert_cmpuint (i++, <, bwrap->argv->len);
g_assert_cmpuint (i++, <, bwrap->argv->len);
g_assert_cmpuint (i++, <, bwrap->argv->len);
}
g_assert_cmpuint (i, ==, bwrap->argv->len - 1);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, NULL);
g_assert_cmpuint (i, ==, bwrap->argv->len);
glnx_shutil_rm_rf_at (-1, subdir, NULL, &error);
if (error != NULL)
{
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
g_clear_error (&error);
}
}
typedef enum
{
FAKE_DIR,
FAKE_FILE,
FAKE_SYMLINK,
} FakeFileType;
typedef struct
{
const char *name;
FakeFileType type;
const char *target;
} FakeFile;
static void
create_fake_files (const FakeFile *files)
{
gsize i;
for (i = 0; files[i].name != NULL; i++)
{
g_autoptr(GError) error = NULL;
g_autofree gchar *path = g_build_filename (isolated_test_dir, "host",
files[i].name, NULL);
g_assert (files[i].name[0] != '/');
switch (files[i].type)
{
case FAKE_DIR:
if (g_mkdir_with_parents (path, S_IRWXU) != 0)
g_error ("mkdir: %s", g_strerror (errno));
break;
case FAKE_FILE:
g_file_set_contents (path, "", 0, &error);
g_assert_no_error (error);
break;
case FAKE_SYMLINK:
g_assert (files[i].target != NULL);
if (symlink (files[i].target, path) != 0)
g_error ("symlink: %s", g_strerror (errno));
break;
default:
g_return_if_reached ();
}
}
}
static FlatpakExports *
test_host_exports_setup (const FakeFile *files,
FlatpakFilesystemMode etc_mode,
FlatpakFilesystemMode os_mode)
{
g_autoptr(FlatpakExports) exports = flatpak_exports_new ();
g_autoptr(GError) error = NULL;
g_autofree gchar *host = g_build_filename (isolated_test_dir, "host", NULL);
glnx_autofd int fd = -1;
glnx_shutil_rm_rf_at (-1, host, NULL, &error);
if (error != NULL)
{
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
g_clear_error (&error);
}
create_fake_files (files);
glnx_openat_rdonly (AT_FDCWD, host, TRUE, &fd, &error);
g_assert_no_error (error);
g_assert_cmpint (fd, >=, 0);
flatpak_exports_take_host_fd (exports, g_steal_fd (&fd));
if (etc_mode > FLATPAK_FILESYSTEM_MODE_NONE)
flatpak_exports_add_host_etc_expose (exports, etc_mode);
if (os_mode > FLATPAK_FILESYSTEM_MODE_NONE)
flatpak_exports_add_host_os_expose (exports, os_mode);
return g_steal_pointer (&exports);
}
static void
test_host_exports_finish (FlatpakExports *exports,
FlatpakBwrap *bwrap)
{
g_autofree gchar *host = g_build_filename (isolated_test_dir, "host", NULL);
g_autoptr(GError) error = NULL;
flatpak_bwrap_add_arg (bwrap, "bwrap");
flatpak_exports_append_bwrap_args (exports, bwrap);
flatpak_bwrap_finish (bwrap);
print_bwrap (bwrap);
glnx_shutil_rm_rf_at (-1, host, NULL, &error);
if (error != NULL)
{
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
g_clear_error (&error);
}
}
static void
test_host_exports (const FakeFile *files,
FlatpakBwrap *bwrap,
FlatpakFilesystemMode etc_mode,
FlatpakFilesystemMode os_mode)
{
g_autoptr(FlatpakExports) exports = NULL;
exports = test_host_exports_setup (files, etc_mode, os_mode);
test_host_exports_finish (exports, bwrap);
}
/*
* Test --filesystem=host-os with an OS that looks like Arch Linux.
*/
static void
test_exports_arch (void)
{
static const FakeFile files[] =
{
{ "etc", FAKE_DIR },
{ "etc/ld.so.cache", FAKE_FILE },
{ "etc/ld.so.conf", FAKE_FILE },
{ "etc/ld.so.conf.d", FAKE_DIR },
{ "bin", FAKE_SYMLINK, "usr/bin" },
{ "lib", FAKE_SYMLINK, "usr/lib" },
{ "lib64", FAKE_SYMLINK, "usr/lib" },
{ "sbin", FAKE_SYMLINK, "usr/bin" },
{ "usr/bin", FAKE_DIR },
{ "usr/lib", FAKE_DIR },
{ "usr/lib32", FAKE_DIR },
{ "usr/lib64", FAKE_SYMLINK, "lib" },
{ "usr/sbin", FAKE_SYMLINK, "bin" },
{ "usr/share", FAKE_DIR },
{ NULL }
};
g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL);
gsize i;
test_host_exports (files, bwrap, FLATPAK_FILESYSTEM_MODE_NONE,
FLATPAK_FILESYSTEM_MODE_READ_ONLY);
i = 0;
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "bwrap");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/usr", "/run/host/usr");
i = assert_next_is_symlink (bwrap, i, "usr/bin", "/run/host/bin");
i = assert_next_is_symlink (bwrap, i, "usr/lib", "/run/host/lib");
i = assert_next_is_symlink (bwrap, i, "usr/lib", "/run/host/lib64");
i = assert_next_is_symlink (bwrap, i, "usr/bin", "/run/host/sbin");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/etc/ld.so.cache",
"/run/host/etc/ld.so.cache");
g_assert_cmpuint (i, ==, bwrap->argv->len - 1);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, NULL);
g_assert_cmpuint (i, ==, bwrap->argv->len);
}
/*
* Test --filesystem=host-os with an OS that looks like Fedora.
*/
static void
test_exports_fedora (void)
{
static const FakeFile files[] =
{
{ "etc", FAKE_DIR },
{ "etc/ld.so.cache", FAKE_FILE },
{ "etc/ld.so.conf", FAKE_FILE },
{ "etc/ld.so.conf.d", FAKE_DIR },
{ "bin", FAKE_SYMLINK, "usr/bin" },
{ "lib", FAKE_SYMLINK, "usr/lib" },
{ "lib64", FAKE_SYMLINK, "usr/lib64" },
{ "sbin", FAKE_SYMLINK, "usr/sbin" },
{ "usr/bin", FAKE_DIR },
{ "usr/lib", FAKE_DIR },
{ "usr/lib64", FAKE_DIR },
{ "usr/local", FAKE_SYMLINK, "../var/usrlocal" },
{ "usr/sbin", FAKE_DIR },
{ "usr/share", FAKE_DIR },
{ "var/usrlocal", FAKE_DIR },
{ NULL }
};
g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL);
gsize i;
test_host_exports (files, bwrap, FLATPAK_FILESYSTEM_MODE_NONE,
FLATPAK_FILESYSTEM_MODE_READ_ONLY);
i = 0;
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "bwrap");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/usr", "/run/host/usr");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/var/usrlocal",
"/run/host/var/usrlocal");
i = assert_next_is_symlink (bwrap, i, "usr/bin", "/run/host/bin");
i = assert_next_is_symlink (bwrap, i, "usr/lib", "/run/host/lib");
i = assert_next_is_symlink (bwrap, i, "usr/lib64", "/run/host/lib64");
i = assert_next_is_symlink (bwrap, i, "usr/sbin", "/run/host/sbin");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/etc/ld.so.cache",
"/run/host/etc/ld.so.cache");
g_assert_cmpuint (i, ==, bwrap->argv->len - 1);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, NULL);
g_assert_cmpuint (i, ==, bwrap->argv->len);
}
/*
* Test --filesystem=host-os with an OS that looks like Debian,
* without the /usr merge, and with x86 and x32 multilib.
*/
static void
test_exports_debian (void)
{
static const FakeFile files[] =
{
{ "etc", FAKE_DIR },
{ "etc/alternatives", FAKE_DIR },
{ "etc/ld.so.cache", FAKE_FILE },
{ "etc/ld.so.conf", FAKE_FILE },
{ "etc/ld.so.conf.d", FAKE_DIR },
{ "etc/os-release", FAKE_FILE },
{ "bin", FAKE_DIR },
{ "lib", FAKE_DIR },
{ "lib32", FAKE_DIR },
{ "lib64", FAKE_DIR },
{ "libx32", FAKE_DIR },
{ "sbin", FAKE_DIR },
{ "usr/bin", FAKE_DIR },
{ "usr/lib", FAKE_DIR },
{ "usr/lib/os-release", FAKE_FILE },
{ "usr/lib32", FAKE_DIR },
{ "usr/lib64", FAKE_DIR },
{ "usr/libexec", FAKE_DIR },
{ "usr/libx32", FAKE_DIR },
{ "usr/sbin", FAKE_DIR },
{ "usr/share", FAKE_DIR },
{ NULL }
};
g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL);
gsize i;
test_host_exports (files, bwrap, FLATPAK_FILESYSTEM_MODE_NONE,
FLATPAK_FILESYSTEM_MODE_READ_ONLY);
i = 0;
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "bwrap");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/usr", "/run/host/usr");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/bin", "/run/host/bin");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/lib", "/run/host/lib");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/lib32", "/run/host/lib32");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/lib64", "/run/host/lib64");
/* libx32 is not currently implemented */
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/sbin", "/run/host/sbin");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/etc/ld.so.cache",
"/run/host/etc/ld.so.cache");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/etc/alternatives",
"/run/host/etc/alternatives");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/etc/os-release",
"/run/host/os-release");
g_assert_cmpuint (i, ==, bwrap->argv->len - 1);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, NULL);
g_assert_cmpuint (i, ==, bwrap->argv->len);
}
/*
* Test --filesystem=host-os and --filesystem=host-etc with an OS that
* looks like Debian, with the /usr merge.
*/
static void
test_exports_debian_merged (void)
{
static const FakeFile files[] =
{
{ "etc", FAKE_DIR },
{ "etc/alternatives", FAKE_DIR },
{ "etc/ld.so.cache", FAKE_FILE },
{ "etc/ld.so.conf", FAKE_FILE },
{ "etc/ld.so.conf.d", FAKE_DIR },
{ "bin", FAKE_SYMLINK, "usr/bin" },
{ "lib", FAKE_SYMLINK, "usr/lib" },
/* This one uses an absolute symlink just to check that we handle
* that correctly */
{ "sbin", FAKE_SYMLINK, "/usr/sbin" },
{ "usr/bin", FAKE_DIR },
{ "usr/lib", FAKE_DIR },
{ "usr/lib/os-release", FAKE_FILE },
{ "usr/libexec", FAKE_DIR },
{ "usr/sbin", FAKE_DIR },
{ "usr/share", FAKE_DIR },
{ NULL }
};
g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL);
gsize i;
test_host_exports (files, bwrap, FLATPAK_FILESYSTEM_MODE_READ_ONLY,
FLATPAK_FILESYSTEM_MODE_READ_ONLY);
i = 0;
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "bwrap");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/usr", "/run/host/usr");
i = assert_next_is_symlink (bwrap, i, "usr/bin", "/run/host/bin");
i = assert_next_is_symlink (bwrap, i, "usr/lib", "/run/host/lib");
i = assert_next_is_symlink (bwrap, i, "usr/sbin", "/run/host/sbin");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/etc", "/run/host/etc");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/usr/lib/os-release",
"/run/host/os-release");
g_assert_cmpuint (i, ==, bwrap->argv->len - 1);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, NULL);
g_assert_cmpuint (i, ==, bwrap->argv->len);
}
static const struct
{
const char *tried;
const char *because;
}
reserved_filesystems[] =
{
{ "/", "/.flatpak-info" },
{ "/.flatpak-info", "/.flatpak-info" },
{ "/app", "/app" },
{ "/app/foo", "/app" },
{ "/bin", "/bin" },
{ "/bin/sh", "/bin" },
{ "/dev", "/dev" },
{ "/etc", "/etc" },
{ "/etc/passwd", "/etc" },
{ "/lib", "/lib" },
{ "/lib/ld-linux.so.2", "/lib" },
{ "/lib64", "/lib64" },
{ "/lib64/ld-linux-x86-64.so.2", "/lib64" },
{ "/proc", "/proc" },
{ "/proc/1", "/proc" },
{ "/proc/sys/net", "/proc" },
{ "/run", "/run/flatpak" },
{ "/run/flatpak/foo/bar", "/run/flatpak" },
{ "/run/host/foo", "/run/host" },
{ "/sbin", "/sbin" },
{ "/sbin/ldconfig", "/sbin" },
{ "/usr", "/usr" },
{ "/usr/bin/env", "/usr" },
{ "/usr/foo/bar", "/usr" },
};
static void
test_exports_ignored (void)
{
g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL);
g_autoptr(FlatpakExports) exports = flatpak_exports_new ();
gsize i;
for (i = 0; i < G_N_ELEMENTS (reserved_filesystems); i++)
{
const char *tried = reserved_filesystems[i].tried;
const char *because = reserved_filesystems[i].because;
g_autoptr(GError) error = NULL;
gboolean ok;
ok = flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
tried,
&error);
g_assert_nonnull (error);
g_assert_nonnull (error->message);
g_test_message ("Trying to export %s -> %s", tried, error->message);
g_assert_false (ok);
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTABLE_FILE))
{
g_autofree char *pattern = g_strdup_printf ("Path \"%s\" is reserved by Flatpak",
because);
g_test_message ("Expecting to see pattern: %s", pattern);
g_assert_nonnull (strstr (error->message, pattern));
}
}
flatpak_bwrap_add_arg (bwrap, "bwrap");
flatpak_exports_append_bwrap_args (exports, bwrap);
flatpak_bwrap_finish (bwrap);
print_bwrap (bwrap);
i = 0;
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "bwrap");
i = assert_next_is_os_release (bwrap, i);
g_assert_cmpuint (i, ==, bwrap->argv->len - 1);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, NULL);
g_assert_cmpuint (i, ==, bwrap->argv->len);
}
/*
* Test various corner-cases using a mock root.
*/
static void
test_exports_unusual (void)
{
static const FakeFile files[] =
{
{ "TMP", FAKE_DIR },
{ "dangling-link", FAKE_SYMLINK, "nonexistent" },
{ "etc", FAKE_DIR },
{ "etc/ld.so.cache", FAKE_FILE },
{ "etc/ld.so.conf", FAKE_FILE },
{ "etc/ld.so.conf.d", FAKE_DIR },
{ "bin", FAKE_SYMLINK, "usr/bin" },
{ "broken-autofs", FAKE_DIR },
{ "home", FAKE_SYMLINK, "var/home" },
{ "lib", FAKE_SYMLINK, "usr/lib" },
{ "recursion", FAKE_SYMLINK, "recursion" },
{ "symlink-to-root", FAKE_SYMLINK, "." },
{ "tmp", FAKE_SYMLINK, "TMP" },
{ "usr/bin", FAKE_DIR },
{ "usr/lib", FAKE_DIR },
{ "usr/share", FAKE_DIR },
{ "var/home/me", FAKE_DIR },
{ "var/volatile/tmp", FAKE_DIR },
{ "var/tmp", FAKE_SYMLINK, "volatile/tmp" },
{ NULL }
};
g_autoptr(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL);
g_autoptr(FlatpakExports) exports = NULL;
gsize i;
g_autoptr(GError) error = NULL;
gboolean ok;
exports = test_host_exports_setup (files,
FLATPAK_FILESYSTEM_MODE_NONE,
FLATPAK_FILESYSTEM_MODE_READ_ONLY);
flatpak_exports_set_test_flags (exports, FLATPAK_EXPORTS_TEST_FLAGS_AUTOFS);
ok = flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
"/broken-autofs", &error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK);
g_test_message ("attempting to export /broken-autofs: %s", error->message);
g_assert_false (ok);
g_clear_error (&error);
ok = flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
"/dangling-link", &error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
g_test_message ("attempting to export /dangling-link: %s", error->message);
g_assert_false (ok);
g_clear_error (&error);
ok = flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
"/home/me", &error);
g_assert_no_error (error);
g_assert_true (ok);
ok = flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
"/nonexistent", &error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND);
g_test_message ("attempting to export /nonexistent: %s", error->message);
g_assert_false (ok);
g_clear_error (&error);
ok = flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
"/recursion", &error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_TOO_MANY_LINKS);
g_test_message ("attempting to export /recursion: %s", error->message);
g_assert_false (ok);
g_clear_error (&error);
ok = flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
"/symlink-to-root", &error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTABLE_FILE);
g_test_message ("attempting to export /symlink-to-root: %s", error->message);
g_assert_false (ok);
g_clear_error (&error);
ok = flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
"/tmp", &error);
g_assert_no_error (error);
g_assert_true (ok);
ok = flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE,
"/var/tmp", &error);
g_assert_no_error (error);
g_assert_true (ok);
ok = flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_ONLY,
"not-absolute", &error);
g_assert_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME);
g_test_message ("attempting to export not-absolute: %s", error->message);
g_assert_false (ok);
g_clear_error (&error);
test_host_exports_finish (exports, bwrap);
i = 0;
g_assert_cmpuint (i, <, bwrap->argv->len);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, "bwrap");
i = assert_next_is_bind (bwrap, i, "--symlink", "var/home", "/home");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/tmp", "/tmp");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/var/home/me",
"/var/home/me");
i = assert_next_is_bind (bwrap, i, "--bind", "/var/tmp",
"/var/tmp");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/usr", "/run/host/usr");
i = assert_next_is_symlink (bwrap, i, "usr/bin", "/run/host/bin");
i = assert_next_is_symlink (bwrap, i, "usr/lib", "/run/host/lib");
i = assert_next_is_bind (bwrap, i, "--ro-bind", "/etc/ld.so.cache",
"/run/host/etc/ld.so.cache");
g_assert_cmpuint (i, ==, bwrap->argv->len - 1);
g_assert_cmpstr (bwrap->argv->pdata[i++], ==, NULL);
g_assert_cmpuint (i, ==, bwrap->argv->len);
}
int
main (int argc, char *argv[])
{
int res;
/* Do not call setlocale() here: some tests look at untranslated error
* messages. */
g_test_init (&argc, &argv, NULL);
isolated_test_dir_global_setup ();
g_test_add_func ("/context/empty", test_empty_context);
g_test_add_func ("/context/filesystems", test_filesystems);
g_test_add_func ("/context/full", test_full_context);
g_test_add_func ("/exports/empty", test_empty);
g_test_add_func ("/exports/full", test_full);
g_test_add_func ("/exports/host/arch", test_exports_arch);
g_test_add_func ("/exports/host/debian", test_exports_debian);
g_test_add_func ("/exports/host/debian-usrmerge", test_exports_debian_merged);
g_test_add_func ("/exports/host/fedora", test_exports_fedora);
g_test_add_func ("/exports/ignored", test_exports_ignored);
g_test_add_func ("/exports/unusual", test_exports_unusual);
res = g_test_run ();
isolated_test_dir_global_teardown ();
return res;
}