mirror of
https://github.com/flatpak/flatpak.git
synced 2026-03-26 10:54:59 -04:00
Several glibc functions now return a const pointer if the input is a const pointer and a non-const pointer if the input is non-const, causing a build failure. Fix this by declaring the output pointers as const if they are never modified and for the lone failure where the output is modified instead make the input non-const.
4529 lines
146 KiB
C
4529 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 correctly layer removal of conditional is to completely
|
|
remove everything 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 (©));
|
|
}
|
|
|
|
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)
|
|
{
|
|
const 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)
|
|
{
|
|
const 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;
|
|
|
|
/* Forbid /../ in paths */
|
|
if (g_str_has_suffix (filesystem, "/..") ||
|
|
strstr (filesystem, "/../") != NULL)
|
|
{
|
|
g_set_error (error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
|
|
_("Filesystem location \"%s\" contains \"..\""),
|
|
filesystem);
|
|
return FALSE;
|
|
}
|
|
|
|
slash = strchr (filesystem, '/');
|
|
|
|
if (slash != NULL)
|
|
{
|
|
/* 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;
|
|
const 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;
|
|
const 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);
|
|
}
|