diff --git a/tests/Makefile.am.inc b/tests/Makefile.am.inc index cefd9822..e43e8972 100644 --- a/tests/Makefile.am.inc +++ b/tests/Makefile.am.inc @@ -60,6 +60,10 @@ testcommon_LDADD = \ $(NULL) testcommon_SOURCES = tests/testcommon.c +test_exports_CFLAGS = $(testcommon_CFLAGS) +test_exports_LDADD = $(testcommon_LDADD) +test_exports_SOURCES = tests/test-exports.c + tests_httpcache_CFLAGS = $(AM_CFLAGS) $(BASE_CFLAGS) $(OSTREE_CFLAGS) $(SOUP_CFLAGS) $(JSON_CFLAGS) $(APPSTREAM_GLIB_CFLAGS) \ -DFLATPAK_COMPILATION \ -DLOCALEDIR=\"$(localedir)\" @@ -216,7 +220,7 @@ test_scripts = ${TEST_MATRIX} dist_test_scripts = ${TEST_MATRIX_DIST} dist_installed_test_extra_scripts += ${TEST_MATRIX_EXTRA_DIST} -test_programs = testlibrary testcommon +test_programs = testlibrary testcommon test-exports test_extra_programs = tests/httpcache tests/test-update-portal tests/test-portal-impl tests/test-authenticator @VALGRIND_CHECK_RULES@ diff --git a/tests/test-exports.c b/tests/test-exports.c new file mode 100644 index 00000000..21d8329e --- /dev/null +++ b/tests/test-exports.c @@ -0,0 +1,481 @@ +/* + * 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 . + */ + +#include "config.h" + +#include +#include +#include + +#include +#include "flatpak.h" +#include "flatpak-bwrap-private.h" +#include "flatpak-context-private.h" +#include "flatpak-exports-private.h" +#include "flatpak-run-private.h" + +/* 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); +} + +/* + * 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) +{ + 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++], ==, 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_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); + flatpak_context_append_bwrap_filesystem (context, bwrap, + "com.example.App", + NULL, + NULL, + &exports); + print_bwrap (bwrap); + g_assert_nonnull (exports); +} + +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_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;"); + 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;"); + g_key_file_set_value (keyfile, + FLATPAK_METADATA_GROUP_CONTEXT, + FLATPAK_METADATA_KEY_FILESYSTEMS, + "host;/home;"); + 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_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_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)); + 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)); + + exports = flatpak_context_get_exports (context, "com.example.App"); + g_assert_nonnull (exports); + + g_clear_pointer (&exports, flatpak_exports_free); + flatpak_context_append_bwrap_filesystem (context, bwrap, + "com.example.App", + NULL, + NULL, + &exports); + print_bwrap (bwrap); + g_assert_nonnull (exports); +} + +typedef struct +{ + const char *input; + GOptionError code; +} NotFilesystem; + +static const NotFilesystem not_filesystems[] = +{ + { "homework", G_OPTION_ERROR_FAILED }, + { "xdg-run", 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-run/dbus", FLATPAK_FILESYSTEM_MODE_READ_WRITE }, +}; + +static void +test_filesystems (void) +{ + gsize i; + + for (i = 0; i < G_N_ELEMENTS (filesystems); i++) + { + const Filesystem *fs = &filesystems[i]; + g_autoptr(GError) error = NULL; + g_autofree char *normalized; + FlatpakFilesystemMode mode; + gboolean ret; + + g_test_message ("%s", fs->input); + ret = flatpak_context_parse_filesystem (fs->input, &normalized, &mode, + &error); + g_assert_no_error (error); + g_assert_true (ret); + + if (fs->fs == NULL) + g_assert_cmpstr (normalized, ==, fs->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 = ¬_filesystems[i]; + g_autoptr(GError) error = NULL; + char *normalized = NULL; + FlatpakFilesystemMode mode; + gboolean ret; + + g_test_message ("%s", not->input); + ret = flatpak_context_parse_filesystem (not->input, &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(FlatpakBwrap) bwrap = flatpak_bwrap_new (NULL); + g_autoptr(FlatpakExports) exports = flatpak_exports_new (); + gsize i; + + flatpak_exports_add_host_etc_expose (exports, + FLATPAK_FILESYSTEM_MODE_READ_WRITE); + flatpak_exports_add_host_os_expose (exports, + FLATPAK_FILESYSTEM_MODE_READ_ONLY); + flatpak_exports_add_path_expose (exports, + FLATPAK_FILESYSTEM_MODE_READ_WRITE, + "/tmp"); + flatpak_exports_add_path_expose (exports, + FLATPAK_FILESYSTEM_MODE_READ_ONLY, + "/var"); + flatpak_exports_add_path_tmpfs (exports, "/var/tmp"); + flatpak_exports_add_path_expose_or_hide (exports, + FLATPAK_FILESYSTEM_MODE_NONE, + "/home"); + flatpak_exports_add_path_expose_or_hide (exports, + FLATPAK_FILESYSTEM_MODE_READ_ONLY, + "/srv"); + + 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"); + + /* Hiding /home just uses --dir because / is not exposed. */ + if (path_is_dir ("/home")) + i = assert_next_is_dir (bwrap, i, "/home"); + + if (path_is_dir ("/srv")) + i = assert_next_is_bind (bwrap, i, "--ro-bind", "/srv"); + + if (path_is_dir ("/tmp")) + i = assert_next_is_bind (bwrap, i, "--bind", "/tmp"); + + if (path_is_dir ("/var")) + i = assert_next_is_bind (bwrap, i, "--ro-bind", "/var"); + + /* We don't create a FAKE_MODE_TMPFS in the container unless there is + * a directory on the host to mount it on. + * Hiding /var/tmp has to use --tmpfs because /var *is* exposed. */ + if (path_is_dir ("/var") && path_is_dir ("/var/tmp")) + i = assert_next_is_tmpfs (bwrap, i, "/var/tmp"); + + 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); +} + +int +main (int argc, char *argv[]) +{ + int res; + + g_test_init (&argc, &argv, NULL); + + 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); + + res = g_test_run (); + + return res; +}