Files
flatpak/common/flatpak-context.c
Sebastian Wick 2a4441382f context: Add --share-if and --allow-if context options
Actually make it possible to use the command line to use the new
conditional permission system.
2025-12-08 19:33:09 +00:00

4530 lines
146 KiB
C

/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s:
* Copyright © 2014-2018 Red Hat, Inc
* Copyright © 2024 GNOME Foundation, 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>
* Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
* Hubert Figuière <hub@figuiere.net>
*/
#include "config.h"
#include "flatpak-context-private.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>
#include <glib/gi18n-lib.h>
#include <gio/gio.h>
#include "libglnx.h"
#include "flatpak-error.h"
#include "flatpak-metadata-private.h"
#include "flatpak-usb-private.h"
#include "flatpak-utils-base-private.h"
#include "flatpak-utils-private.h"
/* Same order as enum */
const char *flatpak_context_shares[] = {
"network",
"ipc",
NULL
};
/* Same order as enum */
const char *flatpak_context_sockets[] = {
"x11",
"wayland",
"pulseaudio",
"session-bus",
"system-bus",
"fallback-x11",
"ssh-auth",
"pcsc",
"cups",
"gpg-agent",
"inherit-wayland-socket",
NULL
};
const char *flatpak_context_devices[] = {
"dri",
"all",
"kvm",
"shm",
"input",
"usb",
NULL
};
const char *flatpak_context_features[] = {
"devel",
"multiarch",
"bluetooth",
"canbus",
"per-app-dev-shm",
NULL
};
const char *flatpak_context_special_filesystems[] = {
"home",
"host",
"host-etc",
"host-os",
"host-reset",
"host-root",
NULL
};
const char *flatpak_context_conditions[] = {
"true",
"false",
"has-input-device",
"has-wayland",
NULL
};
FlatpakContextConditions flatpak_context_true_conditions =
FLATPAK_CONTEXT_CONDITION_TRUE |
FLATPAK_CONTEXT_CONDITION_HAS_INPUT_DEV;
static const char *parse_negated (const char *option, gboolean *negated);
static guint32 flatpak_context_bitmask_from_string (const char *name, const char **names);
typedef struct FlatpakPermission FlatpakPermission;
struct FlatpakPermission {
/* Is the permission unconditionally allowed */
gboolean allowed;
/* When layering, reset all permissions below */
gboolean reset;
/* Assumes allowed is false */
GPtrArray *conditionals;
/* Only used during deserialization */
gboolean disallow_if_conditional;
gboolean disallow_if_conditional_original_reset;
GPtrArray *disallow_if_conditional_original_conditionals;
};
static FlatpakPermission *
flatpak_permission_new (void)
{
FlatpakPermission *permission;
permission = g_slice_new0 (FlatpakPermission);
permission->conditionals = g_ptr_array_new_with_free_func (g_free);
permission->disallow_if_conditional_original_conditionals =
g_ptr_array_new_with_free_func (g_free);
return permission;
};
static void
flatpak_permission_free (FlatpakPermission *permission)
{
g_ptr_array_free (permission->conditionals, TRUE);
g_ptr_array_free (permission->disallow_if_conditional_original_conditionals,
TRUE);
g_slice_free (FlatpakPermission, permission);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (FlatpakPermission, flatpak_permission_free)
static FlatpakPermission *
flatpak_permission_dup (FlatpakPermission *permission)
{
FlatpakPermission *copy = NULL;
copy = flatpak_permission_new ();
if (!permission)
return copy;
copy->allowed = permission->allowed;
copy->reset = permission->reset;
for (size_t i = 0; i < permission->conditionals->len; i++) {
const char *condition = permission->conditionals->pdata[i];
g_ptr_array_add (copy->conditionals, g_strdup (condition));
}
return copy;
}
static void
flatpak_permission_set_not_allowed (FlatpakPermission *permission)
{
permission->allowed = FALSE;
permission->reset = TRUE;
g_ptr_array_set_size (permission->conditionals, 0);
}
static void
flatpak_permission_set_allowed (FlatpakPermission *permission)
{
permission->allowed = TRUE;
/* We reset even when allowed, because lower layer conditionals being added
* at merge would make this non-conditional layer conditional. */
permission->reset = TRUE;
g_ptr_array_set_size (permission->conditionals, 0);
}
static void
flatpak_permission_set_allowed_if (FlatpakPermission *permission,
const char *condition)
{
/* If we are already unconditionally allowed, don't add useless conditionals */
if (permission->allowed)
return;
/* Check if its already there */
if (g_ptr_array_find_with_equal_func (permission->conditionals,
condition,
g_str_equal, NULL))
return;
g_ptr_array_add (permission->conditionals, g_strdup (condition));
g_ptr_array_sort (permission->conditionals, flatpak_strcmp0_ptr);
}
static void
flatpak_permission_remove_conditional (FlatpakPermission *permission,
const char *condition)
{
guint index;
/* If we are already unconditionally allowed, we don't have conditions */
if (permission->allowed)
return;
/* The only way to correcly layer removal of conditional is to completely
remove eveything from the lower layer */
permission->reset = TRUE;
if (!g_ptr_array_find_with_equal_func (permission->conditionals,
condition,
g_str_equal, &index))
return;
g_ptr_array_remove_index (permission->conditionals, index);
}
static void
flatpak_permission_serialize (FlatpakPermission *permission,
const char *name,
GPtrArray *res,
gboolean flatten)
{
if (permission->allowed)
{
/* Completely allowed */
g_ptr_array_add (res, g_strdup (name));
g_assert (permission->conditionals->len == 0);
/* A non-conditional add always implies reset, so no need to serialize that */
}
else if (permission->conditionals->len > 0)
{
/* Partially allowed */
if (permission->reset && !flatten)
g_ptr_array_add (res, g_strdup_printf ("!%s", name));
/* As backwards compat for pre-conditional flatpaks we unconditionally
* add this first. New versions will ignore this if there are
* any conditionals.
* Note: This may result in both "!foo" and "foo", but that
* is fine as the "foo" is last and wins for older flatpaks.
*/
g_ptr_array_add (res, g_strdup (name));
for (size_t i = 0; i < permission->conditionals->len; i++)
{
const char *conditional = permission->conditionals->pdata[i];
g_ptr_array_add (res, g_strdup_printf ("if:%s:%s", name, conditional));
}
}
else
{
/* Completely disallowed */
if (!flatten)
g_ptr_array_add (res, g_strdup_printf ("!%s", name));
}
}
static void
flatpak_permission_to_args (FlatpakPermission *permission,
const char *argname,
const char *noargname,
const char *name,
GPtrArray *args)
{
if (permission->allowed)
{
/* Completely allowed */
g_ptr_array_add (args, g_strdup_printf ("--%s=%s", argname, name));
}
else if (permission->conditionals->len > 0)
{
/* Partially allowed */
if (permission->reset)
g_ptr_array_add (args, g_strdup_printf ("--%s=%s", noargname, name));
for (size_t i = 0; i < permission->conditionals->len; i++)
{
const char *conditional = permission->conditionals->pdata[i];
g_ptr_array_add (args, g_strdup_printf ("--%s-if=%s:%s",
argname, name, conditional));
}
}
else
{
/* Completely disallowed */
g_ptr_array_add (args, g_strdup_printf ("--no%s=%s", argname, name));
}
}
static void
flatpak_permission_deserialize (FlatpakPermission *permission,
gboolean negated,
const char *maybe_condition)
{
/* This can't use the flatpak_permission_set_ helpers, because we
* have to be wary of the backward compat non-conditional permission
* in case conditionals are used. */
if (maybe_condition == NULL)
{
/* Non-conditional option, these are always before conditionals,
* but if non-negated could be backwards compat for later conditional. */
if (negated)
{
permission->allowed = FALSE;
permission->reset = TRUE;
}
else
{
GPtrArray *tmp;
/* Allow us to revert this if it is a backwards compat */
permission->disallow_if_conditional = TRUE;
permission->disallow_if_conditional_original_reset = permission->reset;
tmp = permission->conditionals;
permission->conditionals =
permission->disallow_if_conditional_original_conditionals;
permission->disallow_if_conditional_original_conditionals = tmp;
permission->allowed = TRUE;
permission->reset = TRUE;
}
}
else
{
/* Conditional option */
if (permission->disallow_if_conditional)
{
GPtrArray *tmp;
/* Previous allow was a backward compat, revert it */
permission->allowed = FALSE;
permission->reset = permission->disallow_if_conditional_original_reset;
permission->disallow_if_conditional = FALSE;
tmp = permission->disallow_if_conditional_original_conditionals;
permission->disallow_if_conditional_original_conditionals =
permission->conditionals;
permission->conditionals = tmp;
g_ptr_array_set_size (
permission->disallow_if_conditional_original_conditionals, 0);
}
g_ptr_array_add (permission->conditionals, g_strdup (maybe_condition));
g_ptr_array_sort (permission->conditionals, flatpak_strcmp0_ptr);
}
}
static void
flatpak_permission_merge (FlatpakPermission *permission,
FlatpakPermission *other_permission)
{
if (other_permission->reset)
{
permission->reset = TRUE;
g_ptr_array_set_size (permission->conditionals, 0);
}
permission->allowed = other_permission->allowed;
for (size_t i = 0; i < other_permission->conditionals->len; i++)
{
const char *conditional = other_permission->conditionals->pdata[i];
/* Check if its already there */
if (g_ptr_array_find_with_equal_func (permission->conditionals,
conditional,
g_str_equal, NULL))
return;
g_ptr_array_add (permission->conditionals, g_strdup (conditional));
}
g_ptr_array_sort (permission->conditionals, flatpak_strcmp0_ptr);
/* Internal consistency check */
if (permission->allowed)
g_assert (permission->conditionals->len == 0);
}
static gboolean
flatpak_permission_compute_allowed (FlatpakPermission *permission,
FlatpakContextConditionEvaluator evaluator)
{
if (permission->allowed)
return TRUE;
for (size_t i = 0; i < permission->conditionals->len; i++)
{
const char *conditional = permission->conditionals->pdata[i];
gboolean negated;
const char *condition_str;
guint32 condition;
condition_str = parse_negated (conditional, &negated);
condition =
flatpak_context_bitmask_from_string (condition_str,
flatpak_context_conditions);
/* If condition is 0 it means this version of flatpak doesn't know
* about the condition and it cannot be satisfied. */
if (condition == 0)
continue;
/* Conditions which are always true in this version of flatpak */
if ((condition & flatpak_context_true_conditions) && !negated)
return TRUE;
/* Conditions which need runtime evaluation */
if (evaluator && evaluator (condition) == !negated)
return TRUE;
}
/* No condition evaluated to TRUE, so disable the thing */
return FALSE;
}
static gboolean
flatpak_permission_adds_permissions (FlatpakPermission *old,
FlatpakPermission *new)
{
size_t i = 0, j = 0;
if (old->allowed)
return FALSE;
if (new->allowed)
return TRUE;
if (new->conditionals->len > old->conditionals->len)
return TRUE;
while (TRUE)
{
const char *old_cond = old->conditionals->pdata[i];
const char *new_cond = new->conditionals->pdata[j];
int res;
if (old_cond == NULL)
return new_cond != NULL;
if (new_cond == NULL)
return FALSE;
res = strcmp (old_cond, new_cond);
if (res == 0) /* Same conditional */
{
i++;
j++;
}
else if (res < 0) /* Old conditional was removed */
{
i++;
}
else /* new conditional */
{
return FALSE;
}
}
return FALSE;
}
static GHashTable *
flatpak_permissions_new (void)
{
return g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) flatpak_permission_free);
}
static GHashTable *
flatpak_permissions_dup (GHashTable *old)
{
GHashTable *new;
const char *name;
FlatpakPermission *old_permission;
GHashTableIter iter;
new = flatpak_permissions_new ();
g_hash_table_iter_init (&iter, old);
while (g_hash_table_iter_next (&iter,
(gpointer *) &name,
(gpointer *) &old_permission))
{
g_hash_table_insert (new,
g_strdup (name),
flatpak_permission_dup (old_permission));
}
return new;
}
static FlatpakPermission *
flatpak_permissions_ensure (GHashTable *permissions,
const char *name)
{
FlatpakPermission *permission = g_hash_table_lookup (permissions, name);
if (permission == NULL)
{
permission = flatpak_permission_new ();
g_hash_table_insert (permissions, g_strdup (name), permission);
}
return permission;
}
static void
flatpak_permissions_set_not_allowed (GHashTable *permissions,
const char *name)
{
flatpak_permission_set_not_allowed (flatpak_permissions_ensure (permissions,
name));
}
static void
flatpak_permissions_set_allowed (GHashTable *permissions,
const char *name)
{
flatpak_permission_set_allowed (flatpak_permissions_ensure (permissions,
name));
}
static void
flatpak_permissions_set_allowed_if (GHashTable *permissions,
const char *name,
const char *condition)
{
flatpak_permission_set_allowed_if (flatpak_permissions_ensure (permissions,
name),
condition);
}
static void
flatpak_permissions_remove_conditional (GHashTable *permissions,
const char *name,
const char *condition)
{
flatpak_permission_remove_conditional (flatpak_permissions_ensure (permissions,
name),
condition);
}
static gboolean
flatpak_permissions_allows_unconditionally (GHashTable *permissions,
const char *name)
{
FlatpakPermission *permission = g_hash_table_lookup (permissions, name);
if (permission)
return permission->allowed;
return FALSE;
}
static void
flatpak_permissions_to_args (GHashTable *permissions,
const char *argname,
const char *noargname,
GPtrArray *args)
{
g_autoptr(GList) ordered_keys = NULL;
ordered_keys = g_hash_table_get_keys (permissions);
ordered_keys = g_list_sort (ordered_keys, (GCompareFunc) strcmp);
for (GList *l = ordered_keys; l != NULL; l = l->next)
{
const char *name = l->data;
FlatpakPermission *permission = g_hash_table_lookup (permissions, name);
flatpak_permission_to_args (permission, argname, noargname, name, args);
}
}
static char **
flatpak_permissions_to_strv (GHashTable *permissions,
gboolean flatten)
{
g_autoptr(GList) ordered_keys = NULL;
g_autoptr(GPtrArray) res = g_ptr_array_new ();
ordered_keys = g_hash_table_get_keys (permissions);
ordered_keys = g_list_sort (ordered_keys, (GCompareFunc) strcmp);
for (GList *l = ordered_keys; l != NULL; l = l->next)
{
const char *name = l->data;
FlatpakPermission *permission = g_hash_table_lookup (permissions, name);
flatpak_permission_serialize (permission, name, res, flatten);
}
g_ptr_array_add (res, NULL);
return (char **)g_ptr_array_free (g_steal_pointer (&res), FALSE);
}
static guint32
flatpak_permissions_compute_allowed (GHashTable *permissions,
const char **names,
FlatpakContextConditionEvaluator evaluator)
{
guint32 bitmask = 0;
for (size_t i = 0; names[i] != NULL; i++)
{
const char *name = names[i];
FlatpakPermission *permission = g_hash_table_lookup (permissions, name);
if (permission &&
flatpak_permission_compute_allowed (permission, evaluator))
bitmask |= 1 << i;
}
return bitmask;
}
static void
flatpak_canonicalize_x11_permissions (GHashTable *permissions)
{
/* The on-disk format for sockets supports the old fallback-x11
* permission, but in-memory we remove that converting it to a modern.
* conditional check if-wayland.
*/
FlatpakPermission *fallback_x11 = g_hash_table_lookup (permissions, "fallback-x11");
if (fallback_x11)
{
/* Remove full-access plain x11, which used to be added when
fallback-x11 was added. */
FlatpakPermission *x11 = flatpak_permissions_ensure (permissions, "x11");
x11->allowed = FALSE;
x11->reset = FALSE;
if (fallback_x11->allowed)
flatpak_permission_set_allowed_if (x11, "!has-wayland");
else
flatpak_permission_remove_conditional (x11, "!has-wayland");
/* Remove fallback-x11 (which is deprecated) */
g_hash_table_remove (permissions, "fallback-x11");
}
}
static GHashTable *
flatpak_decanonicalize_x11_permissions (GHashTable *permissions)
{
/* Convert from internal format to on-disk backwards compatible format.
* Note: This only handles the specific case where there is only
* the fallback-x11 conditional. More complex cases are handled
* with the full conditional syntax.
*/
FlatpakPermission *x11 = g_hash_table_lookup (permissions, "x11");
if (x11 != NULL && !x11->allowed && x11->conditionals->len == 1 &&
strcmp (x11->conditionals->pdata[0], "!has-wayland") == 0)
{
GHashTable *copy = flatpak_permissions_dup (permissions);
flatpak_permissions_set_allowed (copy, "fallback-x11");
g_hash_table_remove (copy, "x11");
return copy;
}
return g_hash_table_ref (permissions);
}
static gboolean
flatpak_permissions_from_strv (GHashTable *permissions,
const char **strv,
GError **error)
{
for (size_t i = 0; strv[i] != NULL; i++)
{
g_auto(GStrv) tokens = g_strsplit (strv[i], ":", 3);
const char *name = NULL;
gboolean negated = FALSE;
const char *condition = NULL;
FlatpakPermission *permission;
if (strcmp (tokens[0], "if") == 0)
{
if (g_strv_length (tokens) != 3)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
_("Invalid permission syntax: %s"), strv[i]);
return FALSE;
}
name = tokens[1];
condition = tokens[2];
}
else
{
if (g_strv_length (tokens) != 1)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
_("Invalid permission syntax: %s"), strv[i]);
return FALSE;
}
name = parse_negated (tokens[0], &negated);
}
permission = flatpak_permissions_ensure (permissions, name);
flatpak_permission_deserialize (permission, negated, condition);
}
return TRUE;
}
static void
flatpak_permissions_merge (GHashTable *permissions,
GHashTable *other)
{
const char *name;
FlatpakPermission *other_permission;
GHashTableIter iter;
g_hash_table_iter_init (&iter, other);
while (g_hash_table_iter_next (&iter,
(gpointer *) &name,
(gpointer *) &other_permission))
{
FlatpakPermission *permission = g_hash_table_lookup (permissions, name);
if (permission)
{
flatpak_permission_merge (permission, other_permission);
}
else
{
g_hash_table_insert (permissions,
g_strdup (name),
flatpak_permission_dup (other_permission));
}
}
}
static gboolean
flatpak_permissions_adds_permissions (GHashTable *old,
GHashTable *new)
{
const char *name;
FlatpakPermission *new_permission;
GHashTableIter iter;
g_hash_table_iter_init (&iter, new);
while (g_hash_table_iter_next (&iter,
(gpointer *) &name,
(gpointer *) &new_permission))
{
FlatpakPermission *old_permission = g_hash_table_lookup (old, name);
if (old_permission)
{
if (flatpak_permission_adds_permissions (old_permission,
new_permission))
return TRUE;
}
else
{
if (new_permission->allowed ||
new_permission->conditionals->len > 0)
return TRUE; /* new is completely new permission */
}
}
return FALSE;
}
#ifdef INCLUDE_INTERNAL_TESTS
static void flatpak_permissions_test_basic (void)
{
/* This is in canonical form, so must be kept sorted by name */
const char *perms_strv[] =
{
/* Regular unconditional allowed (resets) */
"allowed",
/* conditional allowed with two conditions (doesn't reset) */
"cond1", /* backwards compat */
"if:cond1:check1",
"if:cond1:check2",
/* conditional allowed with one conditions (doesn't reset) */
"cond2", /* backwards compat */
"if:cond2:check3",
/* conditional allowed (resets) */
"!cond3", /* reset */
"cond3", /* backwards compat */
"if:cond3:check3",
/* Regular unconditional disallowed (resets) */
"!disallowed",
NULL,
};
const char *perms_args[] =
{
"--socket=allowed",
"--socket-if=cond1:check1",
"--socket-if=cond1:check2",
"--socket-if=cond2:check3",
/* conditional allowed (resets) */
"--nosocket=cond3",
"--socket-if=cond3:check3",
/* Regular unconditional disallowed (resets) */
"--nosocket=disallowed",
NULL,
};
GError *error = NULL;
/* Test parsing */
g_autoptr(GHashTable) perms = flatpak_permissions_new ();
gboolean ok = flatpak_permissions_from_strv (perms, perms_strv, &error);
g_assert_true(ok);
g_assert_no_error(error);
g_assert_nonnull(perms);
g_assert_cmpint(g_hash_table_size (perms), ==, 5);
FlatpakPermission *allowed = g_hash_table_lookup (perms, "allowed");
g_assert_nonnull(allowed);
g_assert_true(allowed->allowed);
g_assert_true(allowed->reset);
g_assert(allowed->conditionals->len == 0);
FlatpakPermission *disallowed = g_hash_table_lookup (perms, "disallowed");
g_assert_nonnull(disallowed);
g_assert_false(disallowed->allowed);
g_assert_true(disallowed->reset);
g_assert(disallowed->conditionals->len == 0);
FlatpakPermission *cond1 = g_hash_table_lookup (perms, "cond1");
g_assert_nonnull(cond1);
g_assert_false(cond1->allowed);
g_assert_false(cond1->reset);
g_assert(cond1->conditionals->len == 2);
g_assert_cmpstr(cond1->conditionals->pdata[0], ==, "check1");
g_assert_cmpstr(cond1->conditionals->pdata[1], ==, "check2");
FlatpakPermission *cond2 = g_hash_table_lookup (perms, "cond2");
g_assert_nonnull(cond2);
g_assert_false(cond2->allowed);
g_assert_false(cond2->reset);
g_assert(cond2->conditionals->len == 1);
g_assert_cmpstr(cond2->conditionals->pdata[0], ==, "check3");
FlatpakPermission *cond3 = g_hash_table_lookup (perms, "cond3");
g_assert_nonnull(cond3);
g_assert_false(cond3->allowed);
g_assert_true(cond3->reset);
g_assert(cond3->conditionals->len == 1);
g_assert_cmpstr(cond3->conditionals->pdata[0], ==, "check3");
/* Test roundtrip */
g_auto(GStrv) new_strv = flatpak_permissions_to_strv (perms, FALSE);
g_assert_cmpstrv (perms_strv, new_strv);
g_autoptr(GPtrArray) args = g_ptr_array_new_with_free_func (g_free);
flatpak_permissions_to_args (perms, "socket", "nosocket", args);
g_ptr_array_add(args, NULL);
g_assert_cmpstrv (perms_args, args->pdata);
/* Test copy */
g_autoptr(FlatpakPermission) cond1_copy = flatpak_permission_dup(cond1);
g_assert_nonnull(cond1_copy);
g_assert_false(cond1_copy->allowed);
g_assert_false(cond1_copy->reset);
g_assert(cond1_copy->conditionals->len == 2);
g_assert_cmpstr(cond1_copy->conditionals->pdata[0], ==, "check1");
g_assert_cmpstr(cond1_copy->conditionals->pdata[1], ==, "check2");
/* Test setters: */
{
g_autoptr(FlatpakPermission) copy = flatpak_permission_dup(cond1);
flatpak_permission_set_allowed (copy);
g_assert_true (copy->allowed);
g_assert_true (copy->reset);
g_assert(copy->conditionals->len == 0);
}
{
g_autoptr(FlatpakPermission) copy = flatpak_permission_dup(cond1);
flatpak_permission_set_not_allowed (copy);
g_assert_false (copy->allowed);
g_assert_true (copy->reset);
g_assert(copy->conditionals->len == 0);
}
{
g_autoptr(FlatpakPermission) copy = flatpak_permission_dup(cond1);
flatpak_permission_set_allowed_if (copy, "check0");
g_assert_false (copy->allowed);
g_assert_false (copy->reset);
g_assert(copy->conditionals->len == 3);
g_assert_cmpstr(copy->conditionals->pdata[0], ==, "check0");
g_assert_cmpstr(copy->conditionals->pdata[1], ==, "check1");
g_assert_cmpstr(copy->conditionals->pdata[2], ==, "check2");
}
/* Test merge */
{
g_autoptr(FlatpakPermission) copy = flatpak_permission_dup(cond1);
flatpak_permission_merge (copy, allowed);
g_assert_true (copy->allowed);
g_assert_true (copy->reset);
g_assert(copy->conditionals->len == 0);
}
{
g_autoptr(FlatpakPermission) copy = flatpak_permission_dup(cond1);
flatpak_permission_merge (copy, disallowed);
g_assert_false (copy->allowed);
g_assert_true (copy->reset);
g_assert(copy->conditionals->len == 0);
}
{
/* Merge from non-reset conditional */
g_autoptr(FlatpakPermission) copy = flatpak_permission_dup(cond1);
flatpak_permission_merge (copy, cond2);
g_assert_false (copy->allowed);
g_assert_false (copy->reset);
g_assert(copy->conditionals->len == 3);
}
{
/* Merge from reset conditional */
g_autoptr(FlatpakPermission) copy = flatpak_permission_dup(cond1);
flatpak_permission_merge (copy, cond3);
g_assert_false (copy->allowed);
g_assert_true (copy->reset);
g_assert(copy->conditionals->len == 1);
}
}
static void flatpak_permissions_test_backwards_compat (void)
{
{
/* Deserialize if:wayland:foo;wayland
* The last wayland makes it unconditional. */
g_autoptr(FlatpakPermission) perm = flatpak_permission_new ();
flatpak_permission_deserialize (perm, FALSE, "foo");
flatpak_permission_deserialize (perm, FALSE, NULL);
g_assert_true (perm->allowed);
g_assert_true (perm->reset);
g_assert_cmpint (perm->conditionals->len, ==, 0);
}
{
/* Deserialize wayland;if:wayland:foo;wayland
* Should be the same as the one above. The first wayland is just for
* backwards compat. */
g_autoptr(FlatpakPermission) perm = flatpak_permission_new ();
flatpak_permission_deserialize (perm, FALSE, NULL);
flatpak_permission_deserialize (perm, FALSE, "foo");
flatpak_permission_deserialize (perm, FALSE, NULL);
g_assert_true (perm->allowed);
g_assert_true (perm->reset);
g_assert_cmpint (perm->conditionals->len, ==, 0);
}
{
/* Deserialize if:wayland:foo;wayland;if:wayland:bar
* Now the wayland is before a conditional, so it acts as backwards
* compat. */
g_autoptr(FlatpakPermission) perm = flatpak_permission_new ();
flatpak_permission_deserialize (perm, FALSE, "foo");
flatpak_permission_deserialize (perm, FALSE, NULL);
flatpak_permission_deserialize (perm, FALSE, "bar");
g_assert_false (perm->allowed);
g_assert_false (perm->reset);
g_assert_cmpint (perm->conditionals->len, ==, 2);
g_assert_cmpstr (perm->conditionals->pdata[0], ==, "bar");
g_assert_cmpstr (perm->conditionals->pdata[1], ==, "foo");
}
}
static void flatpak_permissions_test_fallback_x11 (void)
{
g_autoptr(GHashTable) perms = NULL;
{
FlatpakPermission *x11;
FlatpakPermission *wayland;
g_autoptr(GError) error = NULL;
gboolean ok;
perms = flatpak_permissions_new ();
ok = flatpak_permissions_from_strv (perms,
(const char * []) {
"fallback-x11",
"wayland",
NULL,
},
&error);
g_assert_true (ok);
g_assert_no_error (error);
g_assert_nonnull (perms);
flatpak_canonicalize_x11_permissions (perms);
g_assert_cmpint (g_hash_table_size (perms), ==, 2);
x11 = g_hash_table_lookup (perms, "x11");
g_assert_nonnull (x11);
wayland = g_hash_table_lookup (perms, "wayland");
g_assert_nonnull (wayland);
g_assert_false (x11->allowed);
g_assert_cmpint (x11->conditionals->len, ==, 1);
g_assert_cmpstr (x11->conditionals->pdata[0], ==, "!has-wayland");
g_assert_true (wayland->allowed);
}
{
g_autoptr(GHashTable) perms2 = NULL;
FlatpakPermission *x11;
FlatpakPermission *wayland;
g_autoptr(GError) error = NULL;
gboolean ok;
perms2 = flatpak_permissions_new ();
ok = flatpak_permissions_from_strv (perms2,
(const char * []) {
"if:x11:!has-wayland",
NULL,
},
&error);
g_assert_true (ok);
g_assert_no_error (error);
g_assert_nonnull (perms2);
flatpak_canonicalize_x11_permissions (perms2);
g_assert_cmpint (g_hash_table_size (perms2), ==, 1);
x11 = g_hash_table_lookup (perms, "x11");
g_assert_nonnull (x11);
g_assert_false (x11->allowed);
g_assert_cmpint (x11->conditionals->len, ==, 1);
g_assert_cmpstr (x11->conditionals->pdata[0], ==, "!has-wayland");
/* lower: fallback-x11
* upper: if:x11:!has-wayland
* -> if:x11:!has-wayland */
flatpak_permissions_merge (perms, perms2);
g_assert_cmpint (g_hash_table_size (perms), ==, 2);
x11 = g_hash_table_lookup (perms, "x11");
g_assert_nonnull (x11);
wayland = g_hash_table_lookup (perms, "wayland");
g_assert_nonnull (wayland);
g_assert_false (x11->allowed);
g_assert_cmpint (x11->conditionals->len, ==, 1);
g_assert_cmpstr (x11->conditionals->pdata[0], ==, "!has-wayland");
g_assert_true (wayland->allowed);
}
{
g_autoptr(GHashTable) perms2 = NULL;
g_autoptr(GHashTable) perms3 = NULL;
FlatpakPermission *x11;
g_autoptr(GError) error = NULL;
gboolean ok;
perms2 = flatpak_permissions_new ();
ok = flatpak_permissions_from_strv (perms2,
(const char * []) {
"fallback-x11",
"if:x11:foo",
NULL,
},
&error);
g_assert_true (ok);
g_assert_no_error (error);
g_assert_nonnull (perms2);
flatpak_canonicalize_x11_permissions (perms2);
g_assert_cmpint (g_hash_table_size (perms2), ==, 1);
x11 = g_hash_table_lookup (perms2, "x11");
g_assert_nonnull (x11);
g_assert_false (x11->allowed);
g_assert_false (x11->reset);
g_assert_cmpint (x11->conditionals->len, ==, 2);
g_assert_cmpstr (x11->conditionals->pdata[0], ==, "!has-wayland");
g_assert_cmpstr (x11->conditionals->pdata[1], ==, "foo");
perms3 = flatpak_permissions_new ();
ok = flatpak_permissions_from_strv (perms3,
(const char * []) {
"if:x11:!has-wayland",
"!fallback-x11",
"if:x11:bar",
NULL,
},
&error);
g_assert_true (ok);
g_assert_no_error (error);
g_assert_nonnull (perms3);
flatpak_canonicalize_x11_permissions (perms3);
g_assert_cmpint (g_hash_table_size (perms3), ==, 1);
x11 = g_hash_table_lookup (perms3, "x11");
g_assert_nonnull (x11);
g_assert_false (x11->allowed);
g_assert_true (x11->reset);
g_assert_cmpint (x11->conditionals->len, ==, 1);
g_assert_cmpstr (x11->conditionals->pdata[0], ==, "bar");
/* lower: fallback-x11;if:x11:foo
* upper: if:x11:!has-wayland;!fallback-x11;if:x11:bar
* -> if:x11:bar
* The !fallback-x11 removes the if:x11:!has-wayland conditional which
* turns into !x11. */
flatpak_permissions_merge (perms2, perms3);
g_assert_cmpint (g_hash_table_size (perms2), ==, 1);
x11 = g_hash_table_lookup (perms2, "x11");
g_assert_nonnull (x11);
g_assert_false (x11->allowed);
g_assert_cmpint (x11->conditionals->len, ==, 1);
g_assert_cmpstr (x11->conditionals->pdata[0], ==, "bar");
}
}
FLATPAK_INTERNAL_TEST("/context/permissions/basic",
flatpak_permissions_test_basic);
FLATPAK_INTERNAL_TEST("/context/permissions/backwards-compat",
flatpak_permissions_test_backwards_compat);
FLATPAK_INTERNAL_TEST("/context/permissions/fallback-x11",
flatpak_permissions_test_fallback_x11);
#endif /* INCLUDE_INTERNAL_TESTS */
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);
/* filename or special filesystem name => FlatpakFilesystemMode */
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->a11y_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);
context->enumerable_usb_devices = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) flatpak_usb_query_free);
context->hidden_usb_devices = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) flatpak_usb_query_free);
context->shares_permissions = flatpak_permissions_new ();
context->socket_permissions = flatpak_permissions_new ();
context->device_permissions = flatpak_permissions_new ();
context->features_permissions = flatpak_permissions_new ();
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->a11y_bus_policy);
g_hash_table_destroy (context->generic_policy);
g_hash_table_destroy (context->enumerable_usb_devices);
g_hash_table_destroy (context->hidden_usb_devices);
g_hash_table_destroy (context->shares_permissions);
g_hash_table_destroy (context->device_permissions);
g_hash_table_destroy (context->socket_permissions);
g_hash_table_destroy (context->features_permissions);
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 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 FlatpakPolicy
flatpak_policy_from_string (const char *string, GError **error)
{
const char *policies[] = { "none", "see", "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"), 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 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 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 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_a11y_bus_policy (FlatpakContext *context,
const char *name,
FlatpakPolicy policy)
{
g_hash_table_insert (context->a11y_bus_policy, g_strdup (name), GINT_TO_POINTER (policy));
}
GStrv
flatpak_context_get_session_bus_policy_allowed_own_names (FlatpakContext *context)
{
GHashTableIter iter;
gpointer key, value;
g_autoptr(GPtrArray) names = g_ptr_array_new_with_free_func (g_free);
g_hash_table_iter_init (&iter, context->session_bus_policy);
while (g_hash_table_iter_next (&iter, &key, &value))
if (GPOINTER_TO_INT (value) == FLATPAK_POLICY_OWN)
g_ptr_array_add (names, g_strdup (key));
g_ptr_array_add (names, NULL);
return (GStrv) g_ptr_array_free (g_steal_pointer (&names), FALSE);
}
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_add_query_to (GHashTable *queries,
const FlatpakUsbQuery *usb_query)
{
g_autoptr(FlatpakUsbQuery) copy = NULL;
g_autoptr(GString) string = NULL;
g_assert (queries != NULL);
g_assert (usb_query != NULL && usb_query->rules != NULL);
copy = flatpak_usb_query_copy (usb_query);
string = g_string_new (NULL);
flatpak_usb_query_print (usb_query, string);
g_hash_table_insert (queries,
g_strdup (string->str),
g_steal_pointer (&copy));
}
static void
flatpak_context_add_usb_query (FlatpakContext *context,
const FlatpakUsbQuery *usb_query)
{
flatpak_context_add_query_to (context->enumerable_usb_devices, usb_query);
}
static void
flatpak_context_add_nousb_query (FlatpakContext *context,
const FlatpakUsbQuery *usb_query)
{
flatpak_context_add_query_to (context->hidden_usb_devices, usb_query);
}
static gboolean
flatpak_context_add_usb_list (FlatpakContext *context,
const char *list,
GError **error)
{
return flatpak_usb_parse_usb_list (list, context->enumerable_usb_devices,
context->hidden_usb_devices, error);
}
static gboolean
flatpak_context_add_usb_list_from_file (FlatpakContext *context,
const char *path,
GError **error)
{
g_autofree char *contents = NULL;
if (!flatpak_validate_path_characters (path, error))
return FALSE;
if (!g_file_get_contents (path, &contents, NULL, error))
return FALSE;
return flatpak_usb_parse_usb_list (contents, context->enumerable_usb_devices,
context->hidden_usb_devices, error);
}
static gboolean
flatpak_context_set_persistent (FlatpakContext *context,
const char *path,
GError **error)
{
if (!flatpak_validate_path_characters (path, error))
return FALSE;
g_hash_table_insert (context->persistent, g_strdup (path), GINT_TO_POINTER (1));
return TRUE;
}
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,
char **dir)
{
char *slash;
const char *rest;
g_autofree char *prefix = NULL;
gsize len;
const char *dir_out = NULL;
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_strdup (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_strdup (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_strdup (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_strdup (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_strdup (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_strdup (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_strdup (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_strdup (g_get_user_special_dir (G_USER_DIRECTORY_VIDEOS));
return TRUE;
}
if (get_xdg_dir_from_prefix (prefix, NULL, &dir_out))
{
if (config_key)
*config_key = NULL;
if (dir)
*dir = g_strdup (dir_out);
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 = flatpak_get_real_xdg_runtime_dir ();
return TRUE;
}
return FALSE;
}
static char *
unparse_filesystem_flags (const char *path,
FlatpakFilesystemMode mode)
{
g_autoptr(GString) s = g_string_new ("");
const char *p;
for (p = path; *p != 0; p++)
{
if (*p == ':')
g_string_append (s, "\\:");
else if (*p == '\\')
g_string_append (s, "\\\\");
else
g_string_append_c (s, *p);
}
switch (mode)
{
case FLATPAK_FILESYSTEM_MODE_READ_ONLY:
g_string_append (s, ":ro");
break;
case FLATPAK_FILESYSTEM_MODE_CREATE:
g_string_append (s, ":create");
break;
case FLATPAK_FILESYSTEM_MODE_READ_WRITE:
break;
case FLATPAK_FILESYSTEM_MODE_NONE:
g_string_insert_c (s, 0, '!');
if (g_str_has_suffix (s->str, "-reset"))
{
g_string_truncate (s, s->len - 6);
g_string_append (s, ":reset");
}
break;
default:
g_warning ("Unexpected filesystem mode %d", mode);
break;
}
return g_string_free (g_steal_pointer (&s), FALSE);
}
static char *
parse_filesystem_flags (const char *filesystem,
gboolean negated,
FlatpakFilesystemMode *mode_out,
GError **error)
{
g_autoptr(GString) s = g_string_new ("");
const char *p, *suffix;
FlatpakFilesystemMode mode;
gboolean reset = FALSE;
p = filesystem;
while (*p != 0 && *p != ':')
{
if (*p == '\\')
{
p++;
if (*p != 0)
g_string_append_c (s, *p++);
}
else
g_string_append_c (s, *p++);
}
if (negated)
mode = FLATPAK_FILESYSTEM_MODE_NONE;
else
mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE;
if (g_str_equal (s->str, "host-reset"))
{
reset = TRUE;
if (!negated)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
"Filesystem token \"%s\" is only applicable for --nofilesystem",
s->str);
return NULL;
}
if (*p != '\0')
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
"Filesystem token \"%s\" cannot be used with a suffix",
s->str);
return NULL;
}
}
if (*p == ':')
{
suffix = p + 1;
if (strcmp (suffix, "ro") == 0)
mode = FLATPAK_FILESYSTEM_MODE_READ_ONLY;
else if (strcmp (suffix, "rw") == 0)
mode = FLATPAK_FILESYSTEM_MODE_READ_WRITE;
else if (strcmp (suffix, "create") == 0)
mode = FLATPAK_FILESYSTEM_MODE_CREATE;
else if (strcmp (suffix, "reset") == 0)
reset = TRUE;
else if (*suffix != 0)
g_warning ("Unexpected filesystem suffix %s, ignoring", suffix);
if (negated && mode != FLATPAK_FILESYSTEM_MODE_NONE)
{
g_warning ("Filesystem suffix \"%s\" is not applicable for --nofilesystem",
suffix);
mode = FLATPAK_FILESYSTEM_MODE_NONE;
}
if (reset)
{
if (!negated)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
"Filesystem suffix \"%s\" only applies to --nofilesystem",
suffix);
return NULL;
}
if (!g_str_equal (s->str, "host"))
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
"Filesystem suffix \"%s\" can only be applied to "
"--nofilesystem=host",
suffix);
return NULL;
}
/* We internally handle host:reset (etc) as host-reset, only exposing it as a flag in the public
part to allow it to be ignored (with a warning) for old flatpak versions */
g_string_append (s, "-reset");
}
}
/* Postcondition check: the code above should make some results
* impossible */
if (negated)
{
g_assert (mode == FLATPAK_FILESYSTEM_MODE_NONE);
}
else
{
g_assert (mode > FLATPAK_FILESYSTEM_MODE_NONE);
/* This flag is only applicable to --nofilesystem */
g_assert (!reset);
}
/* Postcondition check: filesystem token is host-reset iff reset flag
* was found */
if (reset)
g_assert (g_str_equal (s->str, "host-reset"));
else
g_assert (!g_str_equal (s->str, "host-reset"));
if (mode_out)
*mode_out = mode;
return g_string_free (g_steal_pointer (&s), FALSE);
}
gboolean
flatpak_context_parse_filesystem (const char *filesystem_and_mode,
gboolean negated,
char **filesystem_out,
FlatpakFilesystemMode *mode_out,
GError **error)
{
g_autofree char *filesystem = NULL;
char *slash;
if (!flatpak_validate_path_characters (filesystem_and_mode, error))
return FALSE;
filesystem = parse_filesystem_flags (filesystem_and_mode, negated, mode_out, error);
if (filesystem == NULL)
return FALSE;
slash = strchr (filesystem, '/');
/* Forbid /../ in paths */
if (slash != NULL)
{
if (g_str_has_prefix (slash + 1, "../") ||
g_str_has_suffix (slash + 1, "/..") ||
strstr (slash + 1, "/../") != NULL)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("Filesystem location \"%s\" contains \"..\""),
filesystem);
return FALSE;
}
/* Convert "//" and "/./" to "/" */
for (; slash != NULL; slash = strchr (slash + 1, '/'))
{
while (TRUE)
{
if (slash[1] == '/')
memmove (slash + 1, slash + 2, strlen (slash + 2) + 1);
else if (slash[1] == '.' && slash[2] == '/')
memmove (slash + 1, slash + 3, strlen (slash + 3) + 1);
else
break;
}
}
/* Eliminate trailing "/." or "/". */
while (TRUE)
{
slash = strrchr (filesystem, '/');
if (slash != NULL &&
((slash != filesystem && slash[1] == '\0') ||
(slash[1] == '.' && slash[2] == '\0')))
*slash = '\0';
else
break;
}
if (filesystem[0] == '/' && filesystem[1] == '\0')
{
/* We don't allow --filesystem=/ as equivalent to host, because
* it doesn't do what you'd think: --filesystem=host mounts some
* host directories in /run/host, not in the root. */
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("--filesystem=/ is not available, "
"use --filesystem=host for a similar result"));
return FALSE;
}
}
if (g_strv_contains (flatpak_context_special_filesystems, filesystem) ||
get_xdg_user_dir_from_string (filesystem, NULL, NULL, NULL) ||
g_str_has_prefix (filesystem, "~/") ||
g_str_has_prefix (filesystem, "/"))
{
if (filesystem_out != NULL)
*filesystem_out = g_steal_pointer (&filesystem);
return TRUE;
}
if (strcmp (filesystem, "~") == 0)
{
if (filesystem_out != NULL)
*filesystem_out = g_strdup ("home");
return TRUE;
}
if (g_str_has_prefix (filesystem, "home/"))
{
if (filesystem_out != NULL)
*filesystem_out = g_strconcat ("~/", filesystem + 5, NULL);
return TRUE;
}
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
_("Unknown filesystem location %s, valid locations are: host, host-os, host-etc, host-root, home, xdg-*[/…], ~/dir, /dir"), filesystem);
return FALSE;
}
static void
flatpak_context_take_filesystem (FlatpakContext *context,
char *fs,
FlatpakFilesystemMode mode)
{
/* Special case: --nofilesystem=host-reset implies --nofilesystem=host.
* --filesystem=host-reset (or host:reset) is not allowed. */
if (g_str_equal (fs, "host-reset"))
{
g_return_if_fail (mode == FLATPAK_FILESYSTEM_MODE_NONE);
g_hash_table_insert (context->filesystems, g_strdup ("host"), GINT_TO_POINTER (mode));
}
g_hash_table_insert (context->filesystems, fs, GINT_TO_POINTER (mode));
}
void
flatpak_context_merge (FlatpakContext *context,
FlatpakContext *other)
{
GHashTableIter iter;
gpointer key, value;
flatpak_permissions_merge (context->shares_permissions,
other->shares_permissions);
flatpak_permissions_merge (context->socket_permissions,
other->socket_permissions);
flatpak_permissions_merge (context->device_permissions,
other->device_permissions);
flatpak_permissions_merge (context->features_permissions,
other->features_permissions);
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);
/* We first handle host:reset, as it overrides all other keys from the parent */
if (g_hash_table_lookup_extended (other->filesystems, "host-reset", NULL, &value))
{
g_warn_if_fail (GPOINTER_TO_INT (value) == FLATPAK_FILESYSTEM_MODE_NONE);
g_hash_table_remove_all (context->filesystems);
}
/* Then set the new ones, which includes propagating host:reset. */
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->a11y_bus_policy);
while (g_hash_table_iter_next (&iter, &key, &value))
g_hash_table_insert (context->a11y_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]);
}
g_hash_table_iter_init (&iter, other->enumerable_usb_devices);
while (g_hash_table_iter_next (&iter, NULL, &value))
flatpak_context_add_usb_query (context, value);
g_hash_table_iter_init (&iter, other->hidden_usb_devices);
while (g_hash_table_iter_next (&iter, NULL, &value))
flatpak_context_add_nousb_query (context, value);
}
static gboolean
parse_if_option (const char *option_name,
const char *value,
char **name_out,
char **condition_out,
GError **error)
{
g_auto(GStrv) tokens = g_strsplit (value, ":", 2);
if (g_strv_length (tokens) != 2)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
_("Invalid syntax for %s: %s"), option_name, value);
return FALSE;
}
*name_out = g_strdup (tokens[0]);
*condition_out = g_strdup (tokens[1]);
return TRUE;
}
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_permissions_set_allowed (context->shares_permissions, value);
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_permissions_set_not_allowed (context->shares_permissions, value);
return TRUE;
}
static gboolean
option_share_if_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
FlatpakContext *context = data;
g_autofree char *name = NULL;
g_autofree char *condition = NULL;
FlatpakContextShares share;
if (!parse_if_option (option_name, value, &name, &condition, error))
return FALSE;
share = flatpak_context_share_from_string (name, error);
if (share == 0)
return FALSE;
flatpak_permissions_set_allowed_if (context->shares_permissions,
name, condition);
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;
if (socket == FLATPAK_CONTEXT_SOCKET_FALLBACK_X11)
{
flatpak_permissions_set_allowed_if (context->socket_permissions,
"x11",
"!has-wayland");
return TRUE;
}
flatpak_permissions_set_allowed (context->socket_permissions,
value);
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;
if (socket == FLATPAK_CONTEXT_SOCKET_FALLBACK_X11)
{
flatpak_permissions_remove_conditional (context->socket_permissions,
"x11", "!has-wayland");
return TRUE;
}
flatpak_permissions_set_not_allowed (context->socket_permissions,
value);
return TRUE;
}
static gboolean
option_socket_if_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
FlatpakContext *context = data;
g_autofree char *name = NULL;
g_autofree char *condition = NULL;
FlatpakContextSockets socket;
if (!parse_if_option (option_name, value, &name, &condition, error))
return FALSE;
socket = flatpak_context_socket_from_string (name, error);
if (socket == 0)
return FALSE;
if (socket == FLATPAK_CONTEXT_SOCKET_FALLBACK_X11)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
_("fallback-x11 can not be conditional"));
return FALSE;
}
flatpak_permissions_set_allowed_if (context->socket_permissions,
name, condition);
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_permissions_set_allowed (context->device_permissions,
value);
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_permissions_set_not_allowed (context->device_permissions,
value);
return TRUE;
}
static gboolean
option_device_if_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
FlatpakContext *context = data;
g_autofree char *name = NULL;
g_autofree char *condition = NULL;
FlatpakContextDevices device;
if (!parse_if_option (option_name, value, &name, &condition, error))
return FALSE;
device = flatpak_context_device_from_string (name, error);
if (device == 0)
return FALSE;
flatpak_permissions_set_allowed_if (context->device_permissions,
name, condition);
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_permissions_set_allowed (context->features_permissions, value);
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_permissions_set_not_allowed (context->features_permissions, value);
return TRUE;
}
static gboolean
option_allow_if_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
FlatpakContext *context = data;
g_autofree char *name = NULL;
g_autofree char *condition = NULL;
FlatpakContextFeatures feature;
if (!parse_if_option (option_name, value, &name, &condition, error))
return FALSE;
feature = flatpak_context_feature_from_string (name, error);
if (feature == 0)
return FALSE;
flatpak_permissions_set_allowed_if (context->features_permissions,
name, condition);
return TRUE;
}
static gboolean
option_filesystem_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
FlatpakContext *context = data;
g_autofree char *fs = NULL;
FlatpakFilesystemMode mode;
if (!flatpak_context_parse_filesystem (value, FALSE, &fs, &mode, error))
return FALSE;
flatpak_context_take_filesystem (context, g_steal_pointer (&fs), mode);
return TRUE;
}
static gboolean
option_nofilesystem_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
FlatpakContext *context = data;
g_autofree char *fs = NULL;
FlatpakFilesystemMode mode;
if (!flatpak_context_parse_filesystem (value, TRUE, &fs, &mode, error))
return FALSE;
flatpak_context_take_filesystem (context, g_steal_pointer (&fs),
FLATPAK_FILESYSTEM_MODE_NONE);
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;
}
gboolean
flatpak_context_parse_env_block (FlatpakContext *context,
const char *data,
gsize length,
GError **error)
{
g_auto(GStrv) env_vars = NULL;
int i;
env_vars = flatpak_parse_env_block (data, length, error);
if (env_vars == NULL)
return FALSE;
for (i = 0; env_vars[i] != NULL; i++)
{
g_auto(GStrv) split = g_strsplit (env_vars[i], "=", 2);
g_assert (g_strv_length (split) == 2);
g_assert (split[0][0] != '\0');
flatpak_context_set_env_var (context,
split[0], split[1]);
}
return TRUE;
}
gboolean
flatpak_context_parse_env_fd (FlatpakContext *context,
int fd,
GError **error)
{
g_autoptr(GBytes) env_block = NULL;
const char *data;
gsize len;
env_block = glnx_fd_readall_bytes (fd, NULL, error);
if (env_block == NULL)
return FALSE;
data = g_bytes_get_data (env_block, &len);
return flatpak_context_parse_env_block (context, data, len, error);
}
static gboolean
option_env_fd_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
FlatpakContext *context = data;
guint64 fd;
gchar *endptr;
gboolean ret;
fd = g_ascii_strtoull (value, &endptr, 10);
if (endptr == NULL || *endptr != '\0' || fd > G_MAXINT)
return glnx_throw (error, "Not a valid file descriptor: %s", value);
ret = flatpak_context_parse_env_fd (context, (int) fd, error);
if (fd >= 3)
close (fd);
return ret;
}
static gboolean
option_unset_env_cb (const gchar *option_name,
const gchar *value,
gpointer data,
GError **error)
{
FlatpakContext *context = data;
if (strchr (value, '=') != NULL)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
_("Environment variable name must not contain '=': %s"), value);
return FALSE;
}
flatpak_context_set_env_var (context, value, NULL);
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_a11y_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_a11y_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_no_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_NONE);
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_system_no_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_NONE);
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)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("--add-policy arguments must be in the form SUBSYSTEM.KEY=VALUE"));
return FALSE;
}
policy_value = t + 1;
key = g_strndup (value, t - value);
if (strchr (key, '.') == NULL)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("--add-policy arguments must be in the form SUBSYSTEM.KEY=VALUE"));
return FALSE;
}
if (policy_value[0] == '!')
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("--add-policy values can't start with \"!\""));
return FALSE;
}
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)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("--remove-policy arguments must be in the form SUBSYSTEM.KEY=VALUE"));
return FALSE;
}
policy_value = t + 1;
key = g_strndup (value, t - value);
if (strchr (key, '.') == NULL)
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("--remove-policy arguments must be in the form SUBSYSTEM.KEY=VALUE"));
return FALSE;
}
if (policy_value[0] == '!')
{
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
_("--remove-policy values can't start with \"!\""));
return FALSE;
}
extended_value = g_strconcat ("!", policy_value, NULL);
flatpak_context_apply_generic_policy (context, key, extended_value);
return TRUE;
}
static gboolean
option_usb_cb (const char *option_name,
const char *value,
gpointer data,
GError **error)
{
g_autoptr(FlatpakUsbQuery) usb_query = NULL;
FlatpakContext *context = data;
if (!flatpak_usb_parse_usb (value, &usb_query, error))
return FALSE;
flatpak_context_add_usb_query (context, usb_query);
return TRUE;
}
static gboolean
option_nousb_cb (const char *option_name,
const char *value,
gpointer data,
GError **error)
{
g_autoptr(FlatpakUsbQuery) usb_query = NULL;
FlatpakContext *context = data;
if (!flatpak_usb_parse_usb (value, &usb_query, error))
return FALSE;
flatpak_context_add_nousb_query (context, usb_query);
return TRUE;
}
static gboolean
option_usb_list_file_cb (const char *option_name,
const char *value,
gpointer data,
GError **error)
{
return flatpak_context_add_usb_list_from_file (data, value, error);
}
static gboolean
option_usb_list_cb (const char *option_name,
const char *value,
gpointer data,
GError **error)
{
return flatpak_context_add_usb_list (data, value, error);
}
static gboolean
option_persist_cb (const char *option_name,
const char *value,
gpointer data,
GError **error)
{
FlatpakContext *context = data;
return flatpak_context_set_persistent (context, value, error);
}
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") },
{ "share-if", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_share_if_cb, N_("Require conditions to be met for a subsystem to get shared"), N_("SHARE:CONDITION") },
{ "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") },
{ "socket-if", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_socket_if_cb, N_("Require conditions to be met for a socket to get exposed"), N_("SOCKET:CONDITION") },
{ "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") },
{ "device-if", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_device_if_cb, N_("Require conditions to be met for a device to get exposed"), N_("DEVICE:CONDITION") },
{ "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") },
{ "allow-if", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_allow_if_cb, N_("Require conditions to be met for a feature to get allowed"), N_("FEATURE:CONDITION") },
{ "filesystem", 0, G_OPTION_FLAG_IN_MAIN | G_OPTION_FLAG_FILENAME, 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_FLAG_FILENAME, 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") },
{ "env-fd", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_env_fd_cb, N_("Read environment variables in env -0 format from FD"), N_("FD") },
{ "unset-env", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_unset_env_cb, N_("Remove variable from environment"), N_("VAR") },
{ "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") },
{ "no-talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_no_talk_name_cb, N_("Don't 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") },
{ "system-no-talk-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_system_no_talk_name_cb, N_("Don't allow app to talk to name on the system bus"), N_("DBUS_NAME") },
{ "a11y-own-name", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_a11y_own_name_cb, N_("Allow app to own name on the a11y 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") },
{ "usb", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_usb_cb, N_("Add USB device to enumerables"), N_("VENDOR_ID:PRODUCT_ID") },
{ "nousb", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_nousb_cb, N_("Add USB device to hidden list"), N_("VENDOR_ID:PRODUCT_ID") },
{ "usb-list", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_usb_list_cb, N_("A list of USB devices that are enumerable"), N_("LIST") },
{ "usb-list-file", 0, G_OPTION_FLAG_IN_MAIN | G_OPTION_FLAG_FILENAME, G_OPTION_ARG_CALLBACK, &option_usb_list_file_cb, N_("File containing a list of USB devices to make enumerable"), N_("FILENAME") },
{ "persist", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_CALLBACK, &option_persist_cb, N_("Persist home directory subpath"), 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 }
};
GOptionEntry *
flatpak_context_get_option_entries (void)
{
return 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;
gsize 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;
if (!flatpak_permissions_from_strv (context->shares_permissions, (const char **)shares, error))
return FALSE;
}
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;
if (!flatpak_permissions_from_strv (context->socket_permissions, (const char **)sockets, error))
return FALSE;
flatpak_canonicalize_x11_permissions (context->socket_permissions);
}
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;
if (!flatpak_permissions_from_strv (context->device_permissions, (const char **)devices, error))
return FALSE;
}
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;
if (!flatpak_permissions_from_strv (context->features_permissions, (const char **)features, error))
return FALSE;
}
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);
g_autofree char *filesystem = NULL;
g_autoptr(GError) local_error = NULL;
FlatpakFilesystemMode mode;
if (!flatpak_context_parse_filesystem (fs, remove,
&filesystem, &mode, &local_error))
{
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA))
{
/* Invalid characters, so just hard-fail. */
g_propagate_error (error, g_steal_pointer (&local_error));
return FALSE;
}
else
{
g_info ("Unknown filesystem type %s", filesystems[i]);
g_clear_error (&local_error);
}
}
else
{
g_assert (mode == FLATPAK_FILESYSTEM_MODE_NONE || !remove);
flatpak_context_take_filesystem (context, g_steal_pointer (&filesystem), mode);
}
}
}
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++)
if (!flatpak_context_set_persistent (context, persistent[i], error))
return FALSE;
}
if (g_key_file_has_group (metakey, FLATPAK_METADATA_GROUP_SESSION_BUS_POLICY))
{
g_auto(GStrv) keys = NULL;
gsize 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, NULL);
if ((int) policy != -1)
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 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, NULL);
if ((int) policy != -1)
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 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);
}
}
/* unset-environment is higher precedence than Environment, so that
* we can put unset keys in both places. Old versions of Flatpak will
* interpret the empty string as unset; new versions will obey
* unset-environment. */
if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT, FLATPAK_METADATA_KEY_UNSET_ENVIRONMENT, NULL))
{
g_auto(GStrv) vars = NULL;
gsize vars_count;
vars = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_UNSET_ENVIRONMENT,
&vars_count, error);
if (vars == NULL)
return FALSE;
for (i = 0; i < vars_count; i++)
{
const char *var = vars[i];
flatpak_context_set_env_var (context, var, NULL);
}
}
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]);
}
}
}
if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_USB_DEVICES, FLATPAK_METADATA_KEY_USB_ENUMERABLE_DEVICES, NULL))
{
g_auto(GStrv) values = NULL;
size_t count;
values = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_USB_DEVICES,
FLATPAK_METADATA_KEY_USB_ENUMERABLE_DEVICES,
&count, error);
if (!values)
return FALSE;
for (i = 0; i < count; i++)
{
g_autoptr(FlatpakUsbQuery) usb_query = NULL;
if (!flatpak_usb_parse_usb (values[i], &usb_query, error))
return FALSE;
flatpak_context_add_usb_query (context, usb_query);
}
}
if (g_key_file_has_key (metakey, FLATPAK_METADATA_GROUP_USB_DEVICES, FLATPAK_METADATA_KEY_USB_HIDDEN_DEVICES, NULL))
{
g_auto(GStrv) values = NULL;
size_t count;
values = g_key_file_get_string_list (metakey, FLATPAK_METADATA_GROUP_USB_DEVICES,
FLATPAK_METADATA_KEY_USB_HIDDEN_DEVICES,
&count, error);
if (!values)
return FALSE;
for (i = 0; i < count; i++)
{
g_autoptr(FlatpakUsbQuery) usb_query = NULL;
if (!flatpak_usb_parse_usb (values[i], &usb_query, error))
return FALSE;
flatpak_context_add_nousb_query (context, usb_query);
}
}
return TRUE;
}
static void
flatpak_context_save_usb_devices (GHashTable *devices, GKeyFile *keyfile, const char *key)
{
GHashTableIter iter;
gpointer value;
if (g_hash_table_size (devices) > 0)
{
g_autoptr(GPtrArray) usb_devices = g_ptr_array_new ();
g_hash_table_iter_init (&iter, devices);
while (g_hash_table_iter_next (&iter, &value, NULL))
g_ptr_array_add (usb_devices, (char *) value);
if (usb_devices->len > 0)
{
g_key_file_set_string_list (keyfile,
FLATPAK_METADATA_GROUP_USB_DEVICES,
key,
(const char * const *) usb_devices->pdata,
usb_devices->len);
}
}
}
/*
* 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;
g_autoptr(GPtrArray) unset_env = NULL;
GHashTableIter iter;
gpointer key, value;
g_auto(GStrv) groups = NULL;
int i;
shared = flatpak_permissions_to_strv (context->shares_permissions, flatten);
g_autoptr(GHashTable) socket_permissions = flatpak_decanonicalize_x11_permissions (context->socket_permissions);
sockets = flatpak_permissions_to_strv (socket_permissions, flatten);
devices = flatpak_permissions_to_strv (context->device_permissions, flatten);
features = flatpak_permissions_to_strv (context->features_permissions, flatten);
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);
/* Serialize host-reset first, because order can matter in
* corner cases. */
if (g_hash_table_lookup_extended (context->filesystems, "host-reset",
NULL, &value))
{
g_warn_if_fail (GPOINTER_TO_INT (value) == FLATPAK_FILESYSTEM_MODE_NONE);
if (!flatten)
g_ptr_array_add (array, g_strdup ("!host:reset"));
}
g_hash_table_iter_init (&iter, context->filesystems);
while (g_hash_table_iter_next (&iter, &key, &value))
{
FlatpakFilesystemMode mode = GPOINTER_TO_INT (value);
if (flatten && mode == FLATPAK_FILESYSTEM_MODE_NONE)
continue;
/* We already did this */
if (g_str_equal (key, "host-reset"))
continue;
g_ptr_array_add (array, unparse_filesystem_flags (key, mode));
}
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 (flatten && (policy == 0))
continue;
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 (flatten && (policy == 0))
continue;
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_A11Y_BUS_POLICY, NULL);
g_hash_table_iter_init (&iter, context->a11y_bus_policy);
while (g_hash_table_iter_next (&iter, &key, &value))
{
FlatpakPolicy policy = GPOINTER_TO_INT (value);
if (flatten && (policy == 0))
continue;
g_key_file_set_string (metakey,
FLATPAK_METADATA_GROUP_A11Y_BUS_POLICY,
(char *) key, flatpak_policy_to_string (policy));
}
/* Elements are borrowed from context->env_vars */
unset_env = g_ptr_array_new ();
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))
{
if (value != NULL)
{
g_key_file_set_string (metakey,
FLATPAK_METADATA_GROUP_ENVIRONMENT,
(char *) key, (char *) value);
}
else
{
/* In older versions of Flatpak, [Environment] FOO=
* was interpreted as unsetting - so let's do both. */
g_key_file_set_string (metakey,
FLATPAK_METADATA_GROUP_ENVIRONMENT,
(char *) key, "");
g_ptr_array_add (unset_env, key);
}
}
if (unset_env->len > 0)
{
g_ptr_array_add (unset_env, NULL);
g_key_file_set_string_list (metakey, FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_UNSET_ENVIRONMENT,
(const char * const *) unset_env->pdata,
unset_env->len - 1);
}
else
{
g_key_file_remove_key (metakey, FLATPAK_METADATA_GROUP_CONTEXT,
FLATPAK_METADATA_KEY_UNSET_ENVIRONMENT, NULL);
}
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);
}
}
g_key_file_remove_group (metakey, FLATPAK_METADATA_GROUP_USB_DEVICES, NULL);
flatpak_context_save_usb_devices (context->enumerable_usb_devices, metakey,
FLATPAK_METADATA_KEY_USB_ENUMERABLE_DEVICES);
flatpak_context_save_usb_devices (context->hidden_usb_devices, metakey,
FLATPAK_METADATA_KEY_USB_HIDDEN_DEVICES);
}
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;
}
static gboolean
adds_bus_policy (GHashTable *old, GHashTable *new)
{
GLNX_HASH_TABLE_FOREACH_KV (new, const char *, name, gpointer, _new_policy)
{
int new_policy = GPOINTER_TO_INT (_new_policy);
int old_policy = GPOINTER_TO_INT (g_hash_table_lookup (old, name));
if (new_policy > old_policy)
return TRUE;
}
return FALSE;
}
static gboolean
adds_generic_policy (GHashTable *old, GHashTable *new)
{
GLNX_HASH_TABLE_FOREACH_KV (new, const char *, key, GPtrArray *, new_values)
{
GPtrArray *old_values = g_hash_table_lookup (old, key);
int i;
if (new_values == NULL || new_values->len == 0)
continue;
if (old_values == NULL || old_values->len == 0)
return TRUE;
for (i = 0; i < new_values->len; i++)
{
const char *new_value = g_ptr_array_index (new_values, i);
if (!flatpak_g_ptr_array_contains_string (old_values, new_value))
return TRUE;
}
}
return FALSE;
}
static gboolean
adds_filesystem_access (GHashTable *old, GHashTable *new)
{
FlatpakFilesystemMode old_host_mode = GPOINTER_TO_INT (g_hash_table_lookup (old, "host"));
GLNX_HASH_TABLE_FOREACH_KV (new, const char *, location, gpointer, _new_mode)
{
FlatpakFilesystemMode new_mode = GPOINTER_TO_INT (_new_mode);
FlatpakFilesystemMode old_mode = GPOINTER_TO_INT (g_hash_table_lookup (old, location));
/* Allow more limited access to the same thing */
if (new_mode <= old_mode)
continue;
/* Allow more limited access if we used to have access to everything */
if (new_mode <= old_host_mode)
continue;
/* For the remainder we have to be pessimistic, for instance even
if we have home access we can't allow adding access to ~/foo,
because foo might be a symlink outside home which didn't work
before but would work with an explicit access to that
particular file. */
return TRUE;
}
return FALSE;
}
static gboolean
adds_usb_device (FlatpakContext *old, FlatpakContext *new)
{
GHashTableIter iter;
gpointer value;
/* Does it add new devices to the allowlist? */
g_hash_table_iter_init (&iter, new->enumerable_usb_devices);
while (g_hash_table_iter_next (&iter, &value, NULL))
{
if (!g_hash_table_contains (old->enumerable_usb_devices, value))
return TRUE;
}
/* Does it remove devices from the blocklist? */
g_hash_table_iter_init (&iter, old->hidden_usb_devices);
while (g_hash_table_iter_next (&iter, &value, NULL))
{
if (!g_hash_table_contains (new->hidden_usb_devices, value))
return TRUE;
}
return FALSE;
}
gboolean
flatpak_context_adds_permissions (FlatpakContext *old,
FlatpakContext *new)
{
g_autoptr(GHashTable) old_features_permissions = NULL;
g_autoptr(GHashTable) old_socket_permissions = NULL;
old_features_permissions = flatpak_permissions_dup (old->features_permissions);
/* We allow upgrade to multiarch, that is really not a huge problem.
* Similarly, having sensible semantics for /dev/shm is
* not a security concern. */
flatpak_permissions_set_allowed (old_features_permissions, "multiarch");
flatpak_permissions_set_allowed (old_features_permissions, "per-app-dev-shm");
old_socket_permissions = flatpak_permissions_dup (old->socket_permissions);
/* If we used to allow X11, also allow new fallback X11,
as that is actually less permissions */
if (flatpak_permissions_allows_unconditionally (old_socket_permissions, "x11"))
flatpak_permissions_set_allowed (old_socket_permissions, "fallback-x11");
if (flatpak_permissions_adds_permissions (old->shares_permissions,
new->shares_permissions))
return TRUE;
if (flatpak_permissions_adds_permissions (old_socket_permissions,
new->socket_permissions))
return TRUE;
if (flatpak_permissions_adds_permissions (old->device_permissions,
new->device_permissions))
return TRUE;
if (flatpak_permissions_adds_permissions (old_features_permissions,
new->features_permissions))
return TRUE;
if (adds_bus_policy (old->session_bus_policy, new->session_bus_policy))
return TRUE;
if (adds_bus_policy (old->system_bus_policy, new->system_bus_policy))
return TRUE;
if (adds_bus_policy (old->a11y_bus_policy, new->a11y_bus_policy))
return TRUE;
if (adds_generic_policy (old->generic_policy, new->generic_policy))
return TRUE;
if (adds_filesystem_access (old->filesystems, new->filesystems))
return TRUE;
if (adds_usb_device (old, new))
return TRUE;
return FALSE;
}
char *
flatpak_context_devices_to_usb_list (GHashTable *devices,
gboolean hidden)
{
GString *list = g_string_new (NULL);
GHashTableIter iter;
gpointer value;
g_hash_table_iter_init (&iter, devices);
while (g_hash_table_iter_next (&iter, &value, NULL))
{
if (hidden)
g_string_append_printf (list, "!%s;", (const char *) value);
else
g_string_append_printf (list, "%s;", (const char *) value);
}
return g_string_free (list, FALSE);
}
void
flatpak_context_to_args (FlatpakContext *context,
GPtrArray *args)
{
GHashTableIter iter;
gpointer key, value;
char *usb_list = NULL;
flatpak_permissions_to_args (context->features_permissions,
"allow", "disallow", args);
flatpak_permissions_to_args (context->shares_permissions,
"share", "unshare", args);
flatpak_permissions_to_args (context->device_permissions,
"device", "nodevice", args);
flatpak_permissions_to_args (context->socket_permissions,
"socket", "nosocket", args);
g_hash_table_iter_init (&iter, context->env_vars);
while (g_hash_table_iter_next (&iter, &key, &value))
{
if (value != NULL)
g_ptr_array_add (args, g_strdup_printf ("--env=%s=%s", (char *) key, (char *) value));
else
g_ptr_array_add (args, g_strdup_printf ("--unset-env=%s", (char *) key));
}
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));
}
/* Serialize host-reset first, because order can matter in
* corner cases. */
if (g_hash_table_lookup_extended (context->filesystems, "host-reset",
NULL, &value))
{
g_warn_if_fail (GPOINTER_TO_INT (value) == FLATPAK_FILESYSTEM_MODE_NONE);
g_ptr_array_add (args, g_strdup ("--nofilesystem=host:reset"));
}
g_hash_table_iter_init (&iter, context->filesystems);
while (g_hash_table_iter_next (&iter, &key, &value))
{
g_autofree char *fs = NULL;
FlatpakFilesystemMode mode = GPOINTER_TO_INT (value);
/* We already did this */
if (g_str_equal (key, "host-reset"))
continue;
fs = unparse_filesystem_flags (key, mode);
if (mode != FLATPAK_FILESYSTEM_MODE_NONE)
{
g_ptr_array_add (args, g_strdup_printf ("--filesystem=%s", fs));
}
else
{
g_assert (fs[0] == '!');
g_ptr_array_add (args, g_strdup_printf ("--nofilesystem=%s", &fs[1]));
}
}
usb_list = flatpak_context_devices_to_usb_list (context->enumerable_usb_devices, FALSE);
g_ptr_array_add (args, g_strdup_printf ("--usb-list=%s", usb_list));
g_free (usb_list);
usb_list = flatpak_context_devices_to_usb_list (context->hidden_usb_devices, TRUE);
g_ptr_array_add (args, g_strdup_printf ("--usb-list=%s", usb_list));
g_free (usb_list);
}
void
flatpak_context_add_bus_filters (FlatpakContext *context,
const char *app_id,
FlatpakBus bus,
gboolean sandboxed,
FlatpakBwrap *bwrap)
{
GHashTable *ht;
GHashTableIter iter;
gpointer key, value;
flatpak_bwrap_add_arg (bwrap, "--filter");
switch (bus)
{
case FLATPAK_SESSION_BUS:
if (app_id)
{
if (!sandboxed)
{
flatpak_bwrap_add_arg_printf (bwrap, "--own=%s.*", app_id);
flatpak_bwrap_add_arg_printf (bwrap, "--own=org.mpris.MediaPlayer2.%s.*", app_id);
}
else
{
flatpak_bwrap_add_arg_printf (bwrap, "--own=%s.Sandboxed.*", app_id);
flatpak_bwrap_add_arg_printf (bwrap, "--own=org.mpris.MediaPlayer2.%s.Sandboxed.*", app_id);
}
}
ht = context->session_bus_policy;
break;
case FLATPAK_SYSTEM_BUS:
ht = context->system_bus_policy;
break;
case FLATPAK_A11Y_BUS:
ht = context->a11y_bus_policy;
break;
default:
g_assert_not_reached ();
}
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)
flatpak_bwrap_add_arg_printf (bwrap, "--%s=%s",
flatpak_policy_to_string (policy),
(char *) key);
}
}
void
flatpak_context_reset_non_permissions (FlatpakContext *context)
{
g_hash_table_remove_all (context->env_vars);
}
void
flatpak_context_reset_permissions (FlatpakContext *context)
{
g_hash_table_remove_all (context->shares_permissions);
g_hash_table_remove_all (context->socket_permissions);
g_hash_table_remove_all (context->device_permissions);
g_hash_table_remove_all (context->features_permissions);
g_hash_table_remove_all (context->persistent);
g_hash_table_remove_all (context->filesystems);
g_hash_table_remove_all (context->session_bus_policy);
g_hash_table_remove_all (context->system_bus_policy);
g_hash_table_remove_all (context->a11y_bus_policy);
g_hash_table_remove_all (context->generic_policy);
}
void
flatpak_context_make_sandboxed (FlatpakContext *context)
{
/* We drop almost everything from the app permission, except
* multiarch which is inherited, to make sure app code keeps
* running. */
FlatpakPermission *multiarch =
g_hash_table_lookup (context->features_permissions, "multiarch");
g_hash_table_remove_all (context->shares_permissions);
g_hash_table_remove_all (context->socket_permissions);
g_hash_table_remove_all (context->device_permissions);
g_hash_table_remove_all (context->features_permissions);
if (multiarch)
{
g_hash_table_insert (context->features_permissions,
g_strdup ("multiarch"),
flatpak_permission_dup (multiarch));
}
g_hash_table_remove_all (context->persistent);
g_hash_table_remove_all (context->filesystems);
g_hash_table_remove_all (context->session_bus_policy);
g_hash_table_remove_all (context->system_bus_policy);
g_hash_table_remove_all (context->a11y_bus_policy);
g_hash_table_remove_all (context->generic_policy);
}
const char *dont_mount_in_root[] = {
".",
"..",
"app",
"bin",
"boot",
"dev",
"efi",
"etc",
"lib",
"lib32",
"lib64",
"proc",
"root",
"run",
"sbin",
"sys",
"tmp",
"usr",
"var",
NULL
};
static void
log_cannot_export_error (FlatpakFilesystemMode mode,
const char *path,
const GError *error)
{
GLogLevelFlags level = G_LOG_LEVEL_MESSAGE;
/* By default we don't show a log message if the reason we are not sharing
* something with the sandbox is simply "it doesn't exist" (or something
* very close): otherwise it would be very noisy to launch apps that
* opportunistically share things they might benefit from, like Steam
* having access to $XDG_RUNTIME_DIR/app/com.discordapp.Discord if it
* happens to exist. */
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND))
level = G_LOG_LEVEL_INFO;
/* Some callers specifically suppress warnings for particular errors
* by setting this code. */
else if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_FAILED_HANDLED))
level = G_LOG_LEVEL_INFO;
switch (mode)
{
case FLATPAK_FILESYSTEM_MODE_NONE:
g_log (G_LOG_DOMAIN, level, _("Not replacing \"%s\" with tmpfs: %s"),
path, error->message);
break;
case FLATPAK_FILESYSTEM_MODE_CREATE:
case FLATPAK_FILESYSTEM_MODE_READ_ONLY:
case FLATPAK_FILESYSTEM_MODE_READ_WRITE:
g_log (G_LOG_DOMAIN, level,
_("Not sharing \"%s\" with sandbox: %s"),
path, error->message);
break;
}
}
static void
flatpak_context_export (FlatpakContext *context,
FlatpakExports *exports,
GFile *app_id_dir,
GPtrArray *extra_app_id_dirs,
gboolean do_create,
gchar **xdg_dirs_conf_out,
gboolean *home_access_out)
{
gboolean home_access = FALSE;
g_autoptr(GString) xdg_dirs_conf = NULL;
FlatpakFilesystemMode fs_mode, os_mode, etc_mode, root_mode, home_mode;
GHashTableIter iter;
gpointer key, value;
g_autoptr(GError) local_error = NULL;
if (xdg_dirs_conf_out != NULL)
xdg_dirs_conf = g_string_new ("");
fs_mode = GPOINTER_TO_INT (g_hash_table_lookup (context->filesystems, "host"));
if (fs_mode != FLATPAK_FILESYSTEM_MODE_NONE)
{
DIR *dir;
struct dirent *dirent;
g_info ("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);
if (!flatpak_exports_add_path_expose (exports, fs_mode, path, &local_error))
{
/* Failure to share something like /lib32 because it's
* actually a symlink to /usr/lib32 is less of a problem
* here than it would be for an explicit
* --filesystem=/lib32, so the warning that would normally
* be produced in that situation is downgraded to a
* debug message. */
if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTABLE_FILE))
local_error->code = G_IO_ERROR_FAILED_HANDLED;
log_cannot_export_error (fs_mode, path, local_error);
g_clear_error (&local_error);
}
}
closedir (dir);
}
if (!flatpak_exports_add_path_expose (exports, fs_mode, "/run/media", &local_error))
{
log_cannot_export_error (fs_mode, "/run/media", local_error);
g_clear_error (&local_error);
}
}
os_mode = MAX (GPOINTER_TO_INT (g_hash_table_lookup (context->filesystems, "host-os")),
fs_mode);
if (os_mode != FLATPAK_FILESYSTEM_MODE_NONE)
flatpak_exports_add_host_os_expose (exports, os_mode);
etc_mode = MAX (GPOINTER_TO_INT (g_hash_table_lookup (context->filesystems, "host-etc")),
fs_mode);
if (etc_mode != FLATPAK_FILESYSTEM_MODE_NONE)
flatpak_exports_add_host_etc_expose (exports, etc_mode);
root_mode = MAX (GPOINTER_TO_INT (g_hash_table_lookup (context->filesystems, "host-root")),
fs_mode);
if (root_mode != FLATPAK_FILESYSTEM_MODE_NONE)
flatpak_exports_add_host_root_expose (exports, root_mode);
home_mode = GPOINTER_TO_INT (g_hash_table_lookup (context->filesystems, "home"));
if (home_mode != FLATPAK_FILESYSTEM_MODE_NONE)
{
g_info ("Allowing homedir access");
home_access = TRUE;
if (!flatpak_exports_add_path_expose (exports, MAX (home_mode, fs_mode), g_get_home_dir (), &local_error))
{
/* Even if the error is one that we would normally silence, like
* the path not existing, it seems reasonable to make more of a fuss
* about the home directory not existing or otherwise being unusable,
* so this is intentionally not using cannot_export() */
g_warning (_("Not allowing home directory access: %s"),
local_error->message);
g_clear_error (&local_error);
}
}
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 (g_strv_contains (flatpak_context_special_filesystems, filesystem))
continue;
if (g_str_has_prefix (filesystem, "xdg-"))
{
g_autofree char *path = NULL;
const char *rest = NULL;
const char *config_key = NULL;
g_autofree char *subpath = NULL;
g_autofree char *canonical_path = NULL;
g_autofree char *canonical_home = 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 */
canonical_path = flatpak_canonicalize_filename (path);
canonical_home = flatpak_canonicalize_filename (g_get_home_dir ());
if (strcmp (canonical_path, canonical_home) == 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_info ("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)
{
if (g_mkdir_with_parents (subpath, 0755) != 0)
g_info ("Unable to create directory %s", subpath);
}
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);
if (!flatpak_exports_add_path_expose_or_hide (exports, mode, subpath, &local_error))
{
log_cannot_export_error (mode, subpath, local_error);
g_clear_error (&local_error);
}
}
}
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)
{
if (g_mkdir_with_parents (path, 0755) != 0)
g_info ("Unable to create directory %s", path);
}
if (!flatpak_exports_add_path_expose_or_hide (exports, mode, path, &local_error))
{
log_cannot_export_error (mode, path, local_error);
g_clear_error (&local_error);
}
}
else if (g_str_has_prefix (filesystem, "/"))
{
if (mode == FLATPAK_FILESYSTEM_MODE_CREATE && do_create)
{
if (g_mkdir_with_parents (filesystem, 0755) != 0)
g_info ("Unable to create directory %s", filesystem);
}
if (!flatpak_exports_add_path_expose_or_hide (exports, mode, filesystem, &local_error))
{
log_cannot_export_error (mode, filesystem, local_error);
g_clear_error (&local_error);
}
}
else
{
g_warning ("Unexpected filesystem arg %s", filesystem);
}
}
if (app_id_dir)
{
g_autoptr(GFile) apps_dir = g_file_get_parent (app_id_dir);
int i;
/* Hide the .var/app dir by default (unless explicitly made visible) */
if (!flatpak_exports_add_path_tmpfs (exports,
flatpak_file_get_path_cached (apps_dir),
&local_error))
{
log_cannot_export_error (FLATPAK_FILESYSTEM_MODE_NONE,
flatpak_file_get_path_cached (apps_dir),
local_error);
g_clear_error (&local_error);
}
/* But let the app write to the per-app dir in it */
if (!flatpak_exports_add_path_expose (exports, FLATPAK_FILESYSTEM_MODE_READ_WRITE,
flatpak_file_get_path_cached (app_id_dir),
&local_error))
{
log_cannot_export_error (FLATPAK_FILESYSTEM_MODE_READ_WRITE,
flatpak_file_get_path_cached (apps_dir),
local_error);
g_clear_error (&local_error);
}
if (extra_app_id_dirs != NULL)
{
for (i = 0; i < extra_app_id_dirs->len; i++)
{
GFile *extra_app_id_dir = g_ptr_array_index (extra_app_id_dirs, i);
if (!flatpak_exports_add_path_expose (exports,
FLATPAK_FILESYSTEM_MODE_READ_WRITE,
flatpak_file_get_path_cached (extra_app_id_dir),
&local_error))
{
log_cannot_export_error (FLATPAK_FILESYSTEM_MODE_READ_WRITE,
flatpak_file_get_path_cached (extra_app_id_dir),
local_error);
g_clear_error (&local_error);
}
}
}
}
if (home_access_out != NULL)
*home_access_out = home_access;
if (xdg_dirs_conf_out != NULL)
{
g_assert (xdg_dirs_conf != NULL);
*xdg_dirs_conf_out = g_string_free (g_steal_pointer (&xdg_dirs_conf), FALSE);
}
}
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);
}
FlatpakExports *
flatpak_context_get_exports (FlatpakContext *context,
const char *app_id)
{
g_autoptr(GFile) app_id_dir = flatpak_get_data_dir (app_id);
return flatpak_context_get_exports_full (context,
app_id_dir, NULL,
FALSE, FALSE, NULL, NULL);
}
FlatpakRunFlags
flatpak_context_features_to_run_flags (FlatpakContextFeatures features)
{
FlatpakRunFlags flags = 0;
if (features & FLATPAK_CONTEXT_FEATURE_DEVEL)
flags |= FLATPAK_RUN_FLAG_DEVEL;
if (features & FLATPAK_CONTEXT_FEATURE_MULTIARCH)
flags |= FLATPAK_RUN_FLAG_MULTIARCH;
if (features & FLATPAK_CONTEXT_FEATURE_BLUETOOTH)
flags |= FLATPAK_RUN_FLAG_BLUETOOTH;
if (features & FLATPAK_CONTEXT_FEATURE_CANBUS)
flags |= FLATPAK_RUN_FLAG_CANBUS;
return flags;
}
FlatpakExports *
flatpak_context_get_exports_full (FlatpakContext *context,
GFile *app_id_dir,
GPtrArray *extra_app_id_dirs,
gboolean do_create,
gboolean include_default_dirs,
gchar **xdg_dirs_conf_out,
gboolean *home_access_out)
{
g_autoptr(FlatpakExports) exports = flatpak_exports_new ();
flatpak_context_export (context, exports,
app_id_dir, extra_app_id_dirs,
do_create, xdg_dirs_conf_out, home_access_out);
if (include_default_dirs)
{
g_autoptr(GFile) user_flatpak_dir = NULL;
g_autoptr(GError) local_error = NULL;
/* Hide the flatpak dir by default (unless explicitly made visible) */
user_flatpak_dir = flatpak_get_user_base_dir_location ();
if (!flatpak_exports_add_path_tmpfs (exports,
flatpak_file_get_path_cached (user_flatpak_dir),
&local_error))
{
log_cannot_export_error (FLATPAK_FILESYSTEM_MODE_NONE,
flatpak_file_get_path_cached (user_flatpak_dir),
local_error);
g_clear_error (&local_error);
}
/* Ensure we always have a homedir */
if (!flatpak_exports_add_path_dir (exports, g_get_home_dir (), &local_error))
{
g_warning (_("Unable to provide a temporary home directory in the sandbox: %s"),
local_error->message);
g_clear_error (&local_error);
}
}
return g_steal_pointer (&exports);
}
static void
flatpak_context_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;
g_autoptr(GFile) app_dir_state = 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");
/* Yes, this is inconsistent with data, config and cache. However, using
* this path lets apps provide backwards-compatibility with older Flatpak
* versions by using `--persist=.local/state --unset-env=XDG_STATE_DIR`. */
app_dir_state = g_file_get_child (app_dir, ".local/state");
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);
flatpak_bwrap_set_env (bwrap, "XDG_STATE_HOME", flatpak_file_get_path_cached (app_dir_state), TRUE);
if (g_getenv ("XDG_DATA_HOME"))
flatpak_bwrap_set_env (bwrap, "HOST_XDG_DATA_HOME", g_getenv ("XDG_DATA_HOME"), TRUE);
if (g_getenv ("XDG_CONFIG_HOME"))
flatpak_bwrap_set_env (bwrap, "HOST_XDG_CONFIG_HOME", g_getenv ("XDG_CONFIG_HOME"), TRUE);
if (g_getenv ("XDG_CACHE_HOME"))
flatpak_bwrap_set_env (bwrap, "HOST_XDG_CACHE_HOME", g_getenv ("XDG_CACHE_HOME"), TRUE);
if (g_getenv ("XDG_STATE_HOME"))
flatpak_bwrap_set_env (bwrap, "HOST_XDG_STATE_HOME", g_getenv ("XDG_STATE_HOME"), TRUE);
}
/* This creates zero or more directories unders base_fd+basedir, each
* being guaranteed to either exist and be a directory (no symlinks)
* or be created as a directory. The last directory is opened
* and the fd is returned.
*/
static gboolean
mkdir_p_open_nofollow_at (int base_fd,
const char *basedir,
int mode,
const char *subdir,
int *out_fd,
GError **error)
{
glnx_autofd int parent_fd = -1;
if (g_path_is_absolute (subdir))
{
const char *skipped_prefix = subdir;
while (*skipped_prefix == '/')
skipped_prefix++;
g_warning ("--persist=\"%s\" is deprecated, treating it as --persist=\"%s\"", subdir, skipped_prefix);
subdir = skipped_prefix;
}
g_autofree char *subdir_dirname = g_path_get_dirname (subdir);
if (strcmp (subdir_dirname, ".") == 0)
{
/* It is ok to open basedir with follow=true */
if (!glnx_opendirat (base_fd, basedir, TRUE, &parent_fd, error))
return FALSE;
}
else if (strcmp (subdir_dirname, "..") == 0)
{
return glnx_throw (error, "'..' not supported in --persist paths");
}
else
{
if (!mkdir_p_open_nofollow_at (base_fd, basedir, mode,
subdir_dirname, &parent_fd, error))
return FALSE;
}
g_autofree char *subdir_basename = g_path_get_basename (subdir);
if (strcmp (subdir_basename, ".") == 0)
{
*out_fd = glnx_steal_fd (&parent_fd);
return TRUE;
}
else if (strcmp (subdir_basename, "..") == 0)
{
return glnx_throw (error, "'..' not supported in --persist paths");
}
if (!glnx_shutil_mkdir_p_at (parent_fd, subdir_basename, mode, NULL, error))
return FALSE;
int fd = openat (parent_fd, subdir_basename, O_PATH | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW);
if (fd == -1)
{
int saved_errno = errno;
struct stat stat_buf;
/* If it's a symbolic link, that could be a user trying to offload
* large data to another filesystem, but it could equally well be
* a malicious or compromised app trying to exploit GHSA-7hgv-f2j8-xw87.
* Produce a clearer error message in this case.
* Unfortunately the errno we get in this case is ENOTDIR, so we have
* to ask again to find out whether it's really a symlink. */
if (saved_errno == ENOTDIR &&
fstatat (parent_fd, subdir_basename, &stat_buf, AT_SYMLINK_NOFOLLOW) == 0 &&
S_ISLNK (stat_buf.st_mode))
return glnx_throw (error, "Symbolic link \"%s\" not allowed to avoid sandbox escape", subdir_basename);
return glnx_throw_errno_prefix (error, "openat(%s)", subdir_basename);
}
*out_fd = fd;
return TRUE;
}
void
flatpak_context_append_bwrap_filesystem (FlatpakContext *context,
FlatpakBwrap *bwrap,
const char *app_id,
GFile *app_id_dir,
FlatpakExports *exports,
const char *xdg_dirs_conf,
gboolean home_access)
{
GHashTableIter iter;
gpointer key, value;
if (app_id_dir != NULL)
flatpak_context_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 *appdir = g_build_filename (g_get_home_dir (), ".var/app", app_id, NULL);
g_autofree char *dest = g_build_filename (g_get_home_dir (), persist, NULL);
g_autoptr(GError) local_error = NULL;
if (g_mkdir_with_parents (appdir, 0755) != 0)
{
g_warning ("Unable to create directory %s", appdir);
continue;
}
/* Don't follow symlinks from the persist directory, as it is under user control */
glnx_autofd int src_fd = -1;
if (!mkdir_p_open_nofollow_at (AT_FDCWD, appdir, 0755,
persist, &src_fd,
&local_error))
{
g_warning ("Failed to create persist path %s: %s", persist, local_error->message);
continue;
}
g_autofree char *src_via_proc = g_strdup_printf ("%d", src_fd);
flatpak_bwrap_add_fd (bwrap, g_steal_fd (&src_fd));
flatpak_bwrap_add_bind_arg (bwrap, "--bind-fd", src_via_proc, dest);
}
}
if (app_id_dir != NULL)
{
g_autofree char *user_runtime_dir = flatpak_get_real_xdg_runtime_dir ();
g_autofree char *run_user_app_dst = g_strdup_printf ("/run/flatpak/app/%s", app_id);
g_autofree char *run_user_app_src = g_build_filename (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);
/* Later, we'll make $XDG_RUNTIME_DIR/app a symlink to /run/flatpak/app */
flatpak_bwrap_add_runtime_dir_member (bwrap, "app");
}
/* This actually outputs the args for the hide/expose operations
* in the exports */
flatpak_exports_append_bwrap_args (exports, bwrap);
/* Special case subdirectories of the cache, config and data xdg
* dirs. If these are accessible explicitly, 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 != NULL && xdg_dirs_conf[0] != '\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, strlen (xdg_dirs_conf), path, NULL);
}
}
gboolean
flatpak_context_get_allowed_exports (FlatpakContext *context,
const char *source_path,
const char *app_id,
char ***allowed_extensions_out,
char ***allowed_prefixes_out,
gboolean *require_exact_match_out)
{
g_autoptr(GPtrArray) allowed_extensions = g_ptr_array_new_with_free_func (g_free);
g_autoptr(GPtrArray) allowed_prefixes = g_ptr_array_new_with_free_func (g_free);
gboolean require_exact_match = FALSE;
g_ptr_array_add (allowed_prefixes, g_strdup_printf ("%s.*", app_id));
if (strcmp (source_path, "share/applications") == 0)
{
g_ptr_array_add (allowed_extensions, g_strdup (".desktop"));
}
else if (flatpak_has_path_prefix (source_path, "share/icons"))
{
g_ptr_array_add (allowed_extensions, g_strdup (".svgz"));
g_ptr_array_add (allowed_extensions, g_strdup (".png"));
g_ptr_array_add (allowed_extensions, g_strdup (".svg"));
g_ptr_array_add (allowed_extensions, g_strdup (".ico"));
}
else if (strcmp (source_path, "share/dbus-1/services") == 0)
{
g_auto(GStrv) owned_dbus_names = flatpak_context_get_session_bus_policy_allowed_own_names (context);
g_ptr_array_add (allowed_extensions, g_strdup (".service"));
for (GStrv iter = owned_dbus_names; *iter != NULL; ++iter)
g_ptr_array_add (allowed_prefixes, g_strdup (*iter));
/* We need an exact match with no extra garbage, because the filename refers to busnames
* and we can *only* match exactly these */
require_exact_match = TRUE;
}
else if (strcmp (source_path, "share/gnome-shell/search-providers") == 0)
{
g_ptr_array_add (allowed_extensions, g_strdup (".ini"));
}
else if (strcmp (source_path, "share/krunner/dbusplugins") == 0)
{
g_ptr_array_add (allowed_extensions, g_strdup (".desktop"));
}
else if (strcmp (source_path, "share/mime/packages") == 0)
{
g_ptr_array_add (allowed_extensions, g_strdup (".xml"));
}
else if (strcmp (source_path, "share/metainfo") == 0 ||
strcmp (source_path, "share/appdata") == 0)
{
g_ptr_array_add (allowed_extensions, g_strdup (".xml"));
}
else if (strcmp (source_path, "share/metainfo/releases") == 0)
{
g_ptr_array_add (allowed_extensions, g_strdup (".releases.xml"));
}
else
return FALSE;
g_ptr_array_add (allowed_extensions, NULL);
g_ptr_array_add (allowed_prefixes, NULL);
if (allowed_extensions_out)
*allowed_extensions_out = (char **) g_ptr_array_free (g_steal_pointer (&allowed_extensions), FALSE);
if (allowed_prefixes_out)
*allowed_prefixes_out = (char **) g_ptr_array_free (g_steal_pointer (&allowed_prefixes), FALSE);
if (require_exact_match_out)
*require_exact_match_out = require_exact_match;
return TRUE;
}
void
flatpak_context_dump (FlatpakContext *context,
const char *title)
{
if (flatpak_is_debugging ())
{
g_autoptr(GError) local_error = NULL;
g_autoptr(GKeyFile) metakey = NULL;
g_autofree char *data = NULL;
char *saveptr = NULL;
const char *line;
metakey = g_key_file_new ();
flatpak_context_save_metadata (context, FALSE, metakey);
data = g_key_file_to_data (metakey, NULL, &local_error);
if (data == NULL)
{
g_debug ("%s: (unable to serialize: %s)",
title, local_error->message);
return;
}
g_debug ("%s:", title);
for (line = strtok_r (data, "\n", &saveptr);
line != NULL;
line = strtok_r (NULL, "\n", &saveptr))
g_debug ("\t%s", line);
g_debug ("\t#");
}
}
FlatpakContextShares
flatpak_context_compute_allowed_shares (FlatpakContext *context,
FlatpakContextConditionEvaluator evaluator)
{
return flatpak_permissions_compute_allowed (context->shares_permissions,
flatpak_context_shares,
evaluator);
}
FlatpakContextSockets
flatpak_context_compute_allowed_sockets (FlatpakContext *context,
FlatpakContextConditionEvaluator evaluator)
{
return flatpak_permissions_compute_allowed (context->socket_permissions,
flatpak_context_sockets,
evaluator);
}
FlatpakContextDevices
flatpak_context_compute_allowed_devices (FlatpakContext *context,
FlatpakContextConditionEvaluator evaluator)
{
return flatpak_permissions_compute_allowed (context->device_permissions,
flatpak_context_devices,
evaluator);
}
FlatpakContextFeatures
flatpak_context_compute_allowed_features (FlatpakContext *context,
FlatpakContextConditionEvaluator evaluator)
{
return flatpak_permissions_compute_allowed (context->features_permissions,
flatpak_context_features,
evaluator);
}