/* bz-app-permissions.c * * Copyright 2026 Alexander Vanhee * Copyright (C) 2022 Red Hat * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "config.h" #include #include #include #include #include "bz-app-permissions.h" #define DOES_NOT_CONTAIN ((guint) ~0) G_DEFINE_FLAGS_TYPE ( BzAppPermissionsFlags, bz_app_permissions_flags, G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_NONE, "none"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_NETWORK, "network"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_SYSTEM_BUS, "system-bus"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_SESSION_BUS, "session-bus"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_DEVICES, "devices"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_HOME_FULL, "home-full"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_HOME_READ, "home-read"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL, "filesystem-full"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ, "filesystem-read"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL, "downloads-full"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_READ, "downloads-read"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_SETTINGS, "settings"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_X11, "x11"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX, "escape-sandbox"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_OTHER, "filesystem-other"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_BUS_POLICY_OTHER, "bus-policy-other"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_AUDIO_DEVICES, "audio-devices"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_INPUT_DEVICES, "input-devices"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_SYSTEM_DEVICES, "system-devices"), G_DEFINE_ENUM_VALUE (BZ_APP_PERMISSIONS_FLAGS_SCREEN, "screen")) G_DEFINE_ENUM_TYPE ( BzFilesystemPathType, bz_filesystem_path_type, G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_SYSTEM_ROOT, "system-root"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_HOME_SUBDIR, "home-subdir"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_HOST_OS, "host-os"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_HOST_ETC, "host-etc"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_XDG_DESKTOP, "xdg-desktop"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_XDG_DOCUMENTS, "xdg-documents"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_XDG_MUSIC, "xdg-music"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_XDG_PICTURES, "xdg-pictures"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_XDG_PUBLIC_SHARE, "xdg-public-share"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_XDG_VIDEOS, "xdg-videos"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_XDG_TEMPLATES, "xdg-templates"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_XDG_CACHE, "xdg-cache"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_XDG_CONFIG, "xdg-config"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_XDG_DATA, "xdg-data"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_XDG_RUN, "xdg-run"), G_DEFINE_ENUM_VALUE (BZ_FILESYSTEM_PATH_CUSTOM, "custom")) G_DEFINE_ENUM_TYPE ( BzBusPolicyPermission, bz_bus_policy_permission, G_DEFINE_ENUM_VALUE (BZ_BUS_POLICY_PERMISSION_UNKNOWN, "unknown"), G_DEFINE_ENUM_VALUE (BZ_BUS_POLICY_PERMISSION_NONE, "none"), G_DEFINE_ENUM_VALUE (BZ_BUS_POLICY_PERMISSION_SEE, "see"), G_DEFINE_ENUM_VALUE (BZ_BUS_POLICY_PERMISSION_TALK, "talk"), G_DEFINE_ENUM_VALUE (BZ_BUS_POLICY_PERMISSION_OWN, "own")) struct _BzAppPermissions { GObject parent; gboolean is_sealed; BzAppPermissionsFlags flags; GPtrArray *filesystem_read; GPtrArray *filesystem_full; GPtrArray *bus_policies; }; G_DEFINE_FINAL_TYPE (BzAppPermissions, bz_app_permissions, G_TYPE_OBJECT) static gint cmp_filesystem_path_pointers (gconstpointer item1, gconstpointer item2); static int cmp_bus_policy_qsort (const void *item1, const void *item2); static guint app_permissions_get_array_index (GPtrArray *array, BzFilesystemPathType type, const char *subpath); static guint get_strv_index (const gchar *const *strv, const gchar *value); static gboolean array_contains_filesystem_path (GPtrArray *array, BzFilesystemPathType type, const char *subpath); static int bus_policy_compare (const BzBusPolicy *policy1, const BzBusPolicy *policy2); static int bus_policy_compare_lopsided_with_permissions (const BzBusPolicy *policy1, const BzBusPolicy *policy2); static BzBusPolicy *bus_policy_copy (const BzBusPolicy *policy, void *user_data); static GPtrArray *ptr_array_diff (GPtrArray *array1, GPtrArray *array2, GCompareFunc compare_func, GCopyFunc copy_func, void *copy_func_data, GDestroyNotify free_func); static void bz_app_permissions_finalize (GObject *object) { BzAppPermissions *self = BZ_APP_PERMISSIONS (object); g_clear_pointer (&self->filesystem_read, g_ptr_array_unref); g_clear_pointer (&self->filesystem_full, g_ptr_array_unref); g_clear_pointer (&self->bus_policies, g_ptr_array_unref); G_OBJECT_CLASS (bz_app_permissions_parent_class)->finalize (object); } static void bz_app_permissions_class_init (BzAppPermissionsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = bz_app_permissions_finalize; } static void bz_app_permissions_init (BzAppPermissions *self) { } BzFilesystemPath * bz_filesystem_path_new (BzFilesystemPathType type, const char *subpath) { BzFilesystemPath *path = g_new0 (BzFilesystemPath, 1); path->type = type; path->subpath = g_strdup (subpath); return path; } void bz_filesystem_path_free (BzFilesystemPath *path) { g_return_if_fail (path != NULL); g_free (path->subpath); g_free (path); } char * bz_filesystem_path_to_display_string (const BzFilesystemPath *path) { g_return_val_if_fail (path != NULL, NULL); switch (path->type) { case BZ_FILESYSTEM_PATH_SYSTEM_ROOT: return g_strdup_printf (_ ("System folder %s"), path->subpath ? path->subpath : "/"); case BZ_FILESYSTEM_PATH_HOME_SUBDIR: return g_strdup_printf (_ ("Home subfolder %s"), path->subpath); case BZ_FILESYSTEM_PATH_HOST_OS: return g_strdup (_ ("Host system folders")); case BZ_FILESYSTEM_PATH_HOST_ETC: return g_strdup (_ ("Host system configuration from /etc")); case BZ_FILESYSTEM_PATH_XDG_DESKTOP: if (path->subpath) return g_strdup_printf (_ ("Desktop subfolder %s"), path->subpath); return g_strdup (_ ("Desktop folder")); case BZ_FILESYSTEM_PATH_XDG_DOCUMENTS: if (path->subpath) return g_strdup_printf (_ ("Documents subfolder %s"), path->subpath); return g_strdup (_ ("Documents folder")); case BZ_FILESYSTEM_PATH_XDG_MUSIC: if (path->subpath) return g_strdup_printf (_ ("Music subfolder %s"), path->subpath); return g_strdup (_ ("Music folder")); case BZ_FILESYSTEM_PATH_XDG_PICTURES: if (path->subpath) return g_strdup_printf (_ ("Pictures subfolder %s"), path->subpath); return g_strdup (_ ("Pictures folder")); case BZ_FILESYSTEM_PATH_XDG_PUBLIC_SHARE: if (path->subpath) return g_strdup_printf (_ ("Public Share subfolder %s"), path->subpath); return g_strdup (_ ("Public Share folder")); case BZ_FILESYSTEM_PATH_XDG_VIDEOS: if (path->subpath) return g_strdup_printf (_ ("Videos subfolder %s"), path->subpath); return g_strdup (_ ("Videos folder")); case BZ_FILESYSTEM_PATH_XDG_TEMPLATES: if (path->subpath) return g_strdup_printf (_ ("Templates subfolder %s"), path->subpath); return g_strdup (_ ("Templates folder")); case BZ_FILESYSTEM_PATH_XDG_CACHE: if (path->subpath) return g_strdup_printf (_ ("User cache subfolder %s"), path->subpath); return g_strdup (_ ("User cache folder")); case BZ_FILESYSTEM_PATH_XDG_CONFIG: if (path->subpath) return g_strdup_printf (_ ("User configuration subfolder %s"), path->subpath); return g_strdup (_ ("User configuration folder")); case BZ_FILESYSTEM_PATH_XDG_DATA: if (path->subpath) return g_strdup_printf (_ ("User data subfolder %s"), path->subpath); return g_strdup (_ ("User data folder")); case BZ_FILESYSTEM_PATH_XDG_RUN: if (path->subpath) return g_strdup_printf (_ ("User runtime subfolder %s"), path->subpath); return g_strdup (_ ("User runtime folder")); case BZ_FILESYSTEM_PATH_CUSTOM: return g_strdup_printf (_ ("Filesystem access to %s"), path->subpath); default: return g_strdup (_ ("Unknown filesystem path")); } } const char * bz_filesystem_path_to_icon_name (const BzFilesystemPath *path) { switch (path->type) { case BZ_FILESYSTEM_PATH_HOST_OS: return "computer-symbolic"; case BZ_FILESYSTEM_PATH_HOST_ETC: return "emblem-system-symbolic"; case BZ_FILESYSTEM_PATH_XDG_DESKTOP: return "user-desktop-symbolic"; case BZ_FILESYSTEM_PATH_XDG_DOCUMENTS: return "folder-documents-symbolic"; case BZ_FILESYSTEM_PATH_XDG_MUSIC: return "folder-music-symbolic"; case BZ_FILESYSTEM_PATH_XDG_PICTURES: return "folder-pictures-symbolic"; case BZ_FILESYSTEM_PATH_XDG_PUBLIC_SHARE: return "folder-publicshare-symbolic"; case BZ_FILESYSTEM_PATH_XDG_VIDEOS: return "folder-videos-symbolic"; case BZ_FILESYSTEM_PATH_XDG_CONFIG: return "emblem-system-symbolic"; case BZ_FILESYSTEM_PATH_SYSTEM_ROOT: case BZ_FILESYSTEM_PATH_HOME_SUBDIR: case BZ_FILESYSTEM_PATH_XDG_TEMPLATES: case BZ_FILESYSTEM_PATH_XDG_CACHE: case BZ_FILESYSTEM_PATH_XDG_DATA: case BZ_FILESYSTEM_PATH_XDG_RUN: case BZ_FILESYSTEM_PATH_CUSTOM: default: return "folder-symbolic"; } } BzBusPolicy * bz_bus_policy_new (GBusType bus_type, const char *bus_name, BzBusPolicyPermission permission) { BzBusPolicy *policy = NULL; g_return_val_if_fail (bus_type != G_BUS_TYPE_NONE, NULL); g_return_val_if_fail (bus_name != NULL && *bus_name != '\0', NULL); policy = g_new0 (BzBusPolicy, 1); policy->bus_type = bus_type; policy->bus_name = g_strdup (bus_name); policy->permission = permission; return policy; } void bz_bus_policy_free (BzBusPolicy *self) { g_return_if_fail (self != NULL); g_free (self->bus_name); g_free (self); } BzAppPermissions * bz_app_permissions_new (void) { return g_object_new (BZ_TYPE_APP_PERMISSIONS, NULL); } void bz_app_permissions_seal (BzAppPermissions *self) { g_return_if_fail (BZ_IS_APP_PERMISSIONS (self)); if (self->is_sealed) return; self->is_sealed = TRUE; if (self->filesystem_read) qsort (self->filesystem_read->pdata, self->filesystem_read->len, sizeof (gpointer), cmp_filesystem_path_pointers); if (self->filesystem_full) qsort (self->filesystem_full->pdata, self->filesystem_full->len, sizeof (gpointer), cmp_filesystem_path_pointers); if (self->bus_policies) qsort (self->bus_policies->pdata, self->bus_policies->len, sizeof (gpointer), cmp_bus_policy_qsort); } gboolean bz_app_permissions_is_sealed (BzAppPermissions *self) { g_return_val_if_fail (BZ_IS_APP_PERMISSIONS (self), TRUE); return self->is_sealed; } gboolean bz_app_permissions_is_empty (BzAppPermissions *self) { g_return_val_if_fail (BZ_IS_APP_PERMISSIONS (self), TRUE); return (self->flags == BZ_APP_PERMISSIONS_FLAGS_NONE && (self->filesystem_read == NULL || self->filesystem_read->len == 0) && (self->filesystem_full == NULL || self->filesystem_full->len == 0) && (self->bus_policies == NULL || self->bus_policies->len == 0)); } static gboolean array_contains_filesystem_path (GPtrArray *array, BzFilesystemPathType type, const char *subpath) { if (array == NULL) return FALSE; for (guint i = 0; i < array->len; i++) { const BzFilesystemPath *path = g_ptr_array_index (array, i); if (path->type == type && g_strcmp0 (path->subpath, subpath) == 0) return TRUE; } return FALSE; } static int bus_policy_compare (const BzBusPolicy *policy1, const BzBusPolicy *policy2) { if (policy1->bus_type != policy2->bus_type) return policy1->bus_type - policy2->bus_type; return strcmp (policy1->bus_name, policy2->bus_name); } static int bus_policy_compare_lopsided_with_permissions (const BzBusPolicy *policy1, const BzBusPolicy *policy2) { int compare_keys = bus_policy_compare (policy1, policy2); if (compare_keys != 0) return compare_keys; if (policy2->permission > policy1->permission) return 1; return 0; } static BzBusPolicy * bus_policy_copy (const BzBusPolicy *policy, void *user_data) { return bz_bus_policy_new (policy->bus_type, policy->bus_name, policy->permission); } static GPtrArray * ptr_array_diff (GPtrArray *array1, GPtrArray *array2, GCompareFunc compare_func, GCopyFunc copy_func, void *copy_func_data, GDestroyNotify free_func) { g_autoptr (GPtrArray) diff = NULL; if (array2 == NULL) return NULL; if (array1 == NULL) return g_ptr_array_copy (array2, copy_func, copy_func_data); diff = g_ptr_array_new_with_free_func (free_func); for (unsigned int index1 = 0, index2 = 0; index1 < array1->len || index2 < array2->len; ) { const void *element1, *element2; int comparison; if (index1 >= array1->len) { element2 = g_ptr_array_index (array2, index2); comparison = 1; } else if (index2 >= array2->len) { element1 = g_ptr_array_index (array1, index1); comparison = -1; } else { element1 = g_ptr_array_index (array1, index1); element2 = g_ptr_array_index (array2, index2); comparison = compare_func (element1, element2); } if (comparison < 0) { index1++; } else if (comparison == 0) { index1++; index2++; } else { g_ptr_array_add (diff, copy_func (element2, copy_func_data)); index2++; } } return (diff->len > 0) ? g_steal_pointer (&diff) : NULL; } // this is all from GNOME Software BzAppPermissions * bz_app_permissions_diff (BzAppPermissions *self, BzAppPermissions *other) { g_autoptr (BzAppPermissions) diff = bz_app_permissions_new (); const GPtrArray *new_paths; g_return_val_if_fail (BZ_IS_APP_PERMISSIONS (self), NULL); g_return_val_if_fail (self->is_sealed, NULL); g_return_val_if_fail (BZ_IS_APP_PERMISSIONS (other), NULL); g_return_val_if_fail (other->is_sealed, NULL); bz_app_permissions_set_flags (diff, other->flags & ~self->flags); new_paths = bz_app_permissions_get_filesystem_read (other); for (guint i = 0; new_paths != NULL && i < new_paths->len; i++) { const BzFilesystemPath *path = g_ptr_array_index (new_paths, i); if (!array_contains_filesystem_path (self->filesystem_read, path->type, path->subpath)) bz_app_permissions_add_filesystem_read (diff, path->type, path->subpath); } new_paths = bz_app_permissions_get_filesystem_full (other); for (guint i = 0; new_paths != NULL && i < new_paths->len; i++) { const BzFilesystemPath *path = g_ptr_array_index (new_paths, i); if (!array_contains_filesystem_path (self->filesystem_full, path->type, path->subpath)) bz_app_permissions_add_filesystem_full (diff, path->type, path->subpath); } diff->bus_policies = ptr_array_diff (self->bus_policies, other->bus_policies, (GCompareFunc) bus_policy_compare_lopsided_with_permissions, (GCopyFunc) bus_policy_copy, NULL, (GDestroyNotify) bz_bus_policy_free); bz_app_permissions_seal (diff); return g_steal_pointer (&diff); } void bz_app_permissions_set_flags (BzAppPermissions *self, BzAppPermissionsFlags flags) { g_return_if_fail (BZ_IS_APP_PERMISSIONS (self)); g_assert (!self->is_sealed); self->flags = flags; } BzAppPermissionsFlags bz_app_permissions_get_flags (BzAppPermissions *self) { g_return_val_if_fail (BZ_IS_APP_PERMISSIONS (self), BZ_APP_PERMISSIONS_FLAGS_NONE); return self->flags; } void bz_app_permissions_add_flag (BzAppPermissions *self, BzAppPermissionsFlags flags) { g_return_if_fail (BZ_IS_APP_PERMISSIONS (self)); g_return_if_fail (flags != BZ_APP_PERMISSIONS_FLAGS_NONE); g_assert (!self->is_sealed); self->flags = self->flags | flags; } void bz_app_permissions_remove_flag (BzAppPermissions *self, BzAppPermissionsFlags flags) { g_return_if_fail (BZ_IS_APP_PERMISSIONS (self)); g_return_if_fail (flags != BZ_APP_PERMISSIONS_FLAGS_NONE); g_assert (!self->is_sealed); self->flags = (self->flags & (~flags)); } void bz_app_permissions_add_filesystem_read (BzAppPermissions *self, BzFilesystemPathType type, const char *subpath) { g_return_if_fail (BZ_IS_APP_PERMISSIONS (self)); g_assert (!self->is_sealed); if (app_permissions_get_array_index (self->filesystem_read, type, subpath) != DOES_NOT_CONTAIN || app_permissions_get_array_index (self->filesystem_full, type, subpath) != DOES_NOT_CONTAIN) return; if (self->filesystem_read == NULL) self->filesystem_read = g_ptr_array_new_with_free_func ((GDestroyNotify) bz_filesystem_path_free); g_ptr_array_add (self->filesystem_read, bz_filesystem_path_new (type, subpath)); } const GPtrArray * bz_app_permissions_get_filesystem_read (BzAppPermissions *self) { g_return_val_if_fail (BZ_IS_APP_PERMISSIONS (self), NULL); return self->filesystem_read; } void bz_app_permissions_add_filesystem_full (BzAppPermissions *self, BzFilesystemPathType type, const char *subpath) { guint read_index = DOES_NOT_CONTAIN; g_return_if_fail (BZ_IS_APP_PERMISSIONS (self)); g_assert (!self->is_sealed); if (app_permissions_get_array_index (self->filesystem_full, type, subpath) != DOES_NOT_CONTAIN) return; if (self->filesystem_full == NULL) self->filesystem_full = g_ptr_array_new_with_free_func ((GDestroyNotify) bz_filesystem_path_free); g_ptr_array_add (self->filesystem_full, bz_filesystem_path_new (type, subpath)); read_index = app_permissions_get_array_index (self->filesystem_read, type, subpath); if (read_index != DOES_NOT_CONTAIN) { g_ptr_array_remove_index (self->filesystem_read, read_index); if (self->filesystem_read->len == 0) g_clear_pointer (&self->filesystem_read, g_ptr_array_unref); } } const GPtrArray * bz_app_permissions_get_filesystem_full (BzAppPermissions *self) { g_return_val_if_fail (BZ_IS_APP_PERMISSIONS (self), NULL); return self->filesystem_full; } void bz_app_permissions_add_bus_policy (BzAppPermissions *self, GBusType bus_type, const char *bus_name, BzBusPolicyPermission permission) { g_return_if_fail (BZ_IS_APP_PERMISSIONS (self)); g_return_if_fail (bus_type != G_BUS_TYPE_NONE); g_return_if_fail (bus_name != NULL && *bus_name != '\0'); g_return_if_fail (permission != BZ_BUS_POLICY_PERMISSION_UNKNOWN); g_assert (!self->is_sealed); for (unsigned int i = 0; self->bus_policies != NULL && i < self->bus_policies->len; i++) { BzBusPolicy *policy = g_ptr_array_index (self->bus_policies, i); if (policy->bus_type == bus_type && g_str_equal (policy->bus_name, bus_name)) { policy->permission = MAX (policy->permission, permission); return; } } if (permission == BZ_BUS_POLICY_PERMISSION_NONE) return; if (self->bus_policies == NULL) self->bus_policies = g_ptr_array_new_with_free_func ((GDestroyNotify) bz_bus_policy_free); g_ptr_array_add (self->bus_policies, bz_bus_policy_new (bus_type, bus_name, permission)); } const BzBusPolicy *const * bz_app_permissions_get_bus_policies (BzAppPermissions *self, size_t *out_n_bus_policies) { g_return_val_if_fail (BZ_IS_APP_PERMISSIONS (self), NULL); g_return_val_if_fail (self->is_sealed, NULL); if (out_n_bus_policies != NULL) *out_n_bus_policies = (self->bus_policies != NULL) ? self->bus_policies->len : 0; return (self->bus_policies != NULL && self->bus_policies->len > 0) ? (const BzBusPolicy *const *) self->bus_policies->pdata : NULL; } BzAppPermissions * bz_app_permissions_new_from_metadata (GKeyFile *keyfile, GError **error) { char **strv = NULL; BzAppPermissions *permissions = NULL; BzAppPermissionsFlags flags = BZ_APP_PERMISSIONS_FLAGS_NONE; g_autofree char *app_id = NULL; g_autofree char *mpris_id = NULL; g_autofree char *app_id_non_devel = NULL; g_autofree char *mpris_id_non_devel = NULL; g_return_val_if_fail (keyfile != NULL, NULL); permissions = bz_app_permissions_new (); app_id = g_key_file_get_value (keyfile, "Application", "name", NULL); strv = g_key_file_get_string_list (keyfile, "Context", "sockets", NULL, NULL); if (strv != NULL && g_strv_contains ((const gchar *const *) strv, "system-bus")) flags |= BZ_APP_PERMISSIONS_FLAGS_SYSTEM_BUS | BZ_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX; if (strv != NULL && g_strv_contains ((const gchar *const *) strv, "session-bus")) flags |= BZ_APP_PERMISSIONS_FLAGS_SESSION_BUS | BZ_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX; if (strv != NULL && !g_strv_contains ((const gchar *const *) strv, "fallback-x11") && g_strv_contains ((const gchar *const *) strv, "x11")) flags |= BZ_APP_PERMISSIONS_FLAGS_X11; if (strv != NULL && g_strv_contains ((const gchar *const *) strv, "fallback-x11") && !g_strv_contains ((const gchar *const *) strv, "wayland")) flags |= BZ_APP_PERMISSIONS_FLAGS_X11; if (strv != NULL && g_strv_contains ((const gchar *const *) strv, "pulseaudio")) flags |= BZ_APP_PERMISSIONS_FLAGS_AUDIO_DEVICES; if (strv != NULL && g_strv_contains ((const char *const *) strv, "gpg-agent")) flags |= BZ_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX; g_strfreev (strv); strv = g_key_file_get_string_list (keyfile, "Context", "devices", NULL, NULL); if (strv != NULL && g_strv_contains ((const gchar *const *) strv, "all")) flags |= BZ_APP_PERMISSIONS_FLAGS_DEVICES; if (strv != NULL && g_strv_contains ((const gchar *const *) strv, "input")) flags |= BZ_APP_PERMISSIONS_FLAGS_INPUT_DEVICES; if (strv != NULL && (g_strv_contains ((const gchar *const *) strv, "shm") || g_strv_contains ((const gchar *const *) strv, "kvm"))) flags |= BZ_APP_PERMISSIONS_FLAGS_SYSTEM_DEVICES; g_strfreev (strv); strv = g_key_file_get_string_list (keyfile, "Context", "shared", NULL, NULL); if (strv != NULL && g_strv_contains ((const gchar *const *) strv, "network")) flags |= BZ_APP_PERMISSIONS_FLAGS_NETWORK; g_strfreev (strv); strv = g_key_file_get_string_list (keyfile, "Context", "filesystems", NULL, NULL); if (strv != NULL) { const struct { const gchar *key; BzAppPermissionsFlags perm; } filesystems_access[] = { { "home", BZ_APP_PERMISSIONS_FLAGS_HOME_FULL }, { "home:rw", BZ_APP_PERMISSIONS_FLAGS_HOME_FULL }, { "home:ro", BZ_APP_PERMISSIONS_FLAGS_HOME_READ }, { "~", BZ_APP_PERMISSIONS_FLAGS_HOME_FULL }, { "~:rw", BZ_APP_PERMISSIONS_FLAGS_HOME_FULL }, { "~:ro", BZ_APP_PERMISSIONS_FLAGS_HOME_READ }, { "host", BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL }, { "host:rw", BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL }, { "host:ro", BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ }, { "xdg-config/kdeglobals:ro", BZ_APP_PERMISSIONS_FLAGS_NONE }, { "xdg-download", BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL }, { "xdg-download:rw", BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL }, { "xdg-download:ro", BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_READ }, { "xdg-data/flatpak/overrides:create", BZ_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX }, { "xdg-run/pipewire-0", BZ_APP_PERMISSIONS_FLAGS_SCREEN | BZ_APP_PERMISSIONS_FLAGS_AUDIO_DEVICES }, { "xdg-run/pipewire-0:rw", BZ_APP_PERMISSIONS_FLAGS_SCREEN | BZ_APP_PERMISSIONS_FLAGS_AUDIO_DEVICES }, { "xdg-run/pipewire-0:ro", BZ_APP_PERMISSIONS_FLAGS_SCREEN | BZ_APP_PERMISSIONS_FLAGS_AUDIO_DEVICES }, { "xdg-run/gvfsd", BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL }, }; guint filesystems_hits = 0; guint strv_len = g_strv_length (strv); for (guint i = 0; i < G_N_ELEMENTS (filesystems_access); i++) { guint index = get_strv_index ((const gchar *const *) strv, filesystems_access[i].key); if (index < strv_len) { flags |= filesystems_access[i].perm; filesystems_hits++; strv[index][0] = '\0'; } } if ((flags & BZ_APP_PERMISSIONS_FLAGS_HOME_FULL) != 0) flags = flags & ~BZ_APP_PERMISSIONS_FLAGS_HOME_READ; if ((flags & BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL) != 0) flags = flags & ~BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_READ; if ((flags & BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_FULL) != 0) flags = flags & ~BZ_APP_PERMISSIONS_FLAGS_DOWNLOADS_READ; if (strv_len > filesystems_hits) { const struct { const gchar *prefix; BzFilesystemPathType type; } filesystems_other[] = { { "/", BZ_FILESYSTEM_PATH_SYSTEM_ROOT }, { "home/", BZ_FILESYSTEM_PATH_HOME_SUBDIR }, { "~/", BZ_FILESYSTEM_PATH_HOME_SUBDIR }, { "host-os", BZ_FILESYSTEM_PATH_HOST_OS }, { "host-etc", BZ_FILESYSTEM_PATH_HOST_ETC }, { "xdg-desktop", BZ_FILESYSTEM_PATH_XDG_DESKTOP }, { "xdg-documents", BZ_FILESYSTEM_PATH_XDG_DOCUMENTS }, { "xdg-music", BZ_FILESYSTEM_PATH_XDG_MUSIC }, { "xdg-pictures", BZ_FILESYSTEM_PATH_XDG_PICTURES }, { "xdg-public-share", BZ_FILESYSTEM_PATH_XDG_PUBLIC_SHARE }, { "xdg-videos", BZ_FILESYSTEM_PATH_XDG_VIDEOS }, { "xdg-templates", BZ_FILESYSTEM_PATH_XDG_TEMPLATES }, { "xdg-cache", BZ_FILESYSTEM_PATH_XDG_CACHE }, { "xdg-config", BZ_FILESYSTEM_PATH_XDG_CONFIG }, { "xdg-data", BZ_FILESYSTEM_PATH_XDG_DATA }, { "xdg-run", BZ_FILESYSTEM_PATH_XDG_RUN } }; flags |= BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_OTHER; for (guint j = 0; strv[j]; j++) { gchar *perm = strv[j]; gboolean is_readonly = FALSE; gchar *colon = NULL; guint i = 0; const gchar *subpath = NULL; if (!perm[0]) continue; is_readonly = g_str_has_suffix (perm, ":ro"); colon = strrchr (perm, ':'); if (colon) *colon = '\0'; for (i = 0; i < G_N_ELEMENTS (filesystems_other); i++) { if (g_str_has_prefix (perm, filesystems_other[i].prefix)) { const gchar *slash = NULL; if (filesystems_other[i].type == BZ_FILESYSTEM_PATH_SYSTEM_ROOT) { if (perm[1] != '\0') subpath = perm + 1; else subpath = NULL; } else { slash = strchr (perm, '/'); if (slash && slash != perm) subpath = slash + 1; } if (is_readonly) bz_app_permissions_add_filesystem_read (permissions, filesystems_other[i].type, subpath); else bz_app_permissions_add_filesystem_full (permissions, filesystems_other[i].type, subpath); break; } } if (i == G_N_ELEMENTS (filesystems_other)) { if (is_readonly) bz_app_permissions_add_filesystem_read (permissions, BZ_FILESYSTEM_PATH_CUSTOM, perm); else bz_app_permissions_add_filesystem_full (permissions, BZ_FILESYSTEM_PATH_CUSTOM, perm); } } } } g_strfreev (strv); if (!(flags & (BZ_APP_PERMISSIONS_FLAGS_SYSTEM_BUS | BZ_APP_PERMISSIONS_FLAGS_SESSION_BUS))) { const struct { GBusType bus_type; const char *keyfile_group; BzAppPermissionsFlags unfiltered_flag; } bus_policy_types[] = { { G_BUS_TYPE_SESSION, "Session Bus Policy", BZ_APP_PERMISSIONS_FLAGS_SESSION_BUS }, { G_BUS_TYPE_SYSTEM, "System Bus Policy", BZ_APP_PERMISSIONS_FLAGS_SYSTEM_BUS }, }; if (app_id != NULL) { mpris_id = g_strconcat ("org.mpris.MediaPlayer2.", app_id, NULL); app_id_non_devel = g_str_has_suffix (app_id, ".Devel") ? g_strndup (app_id, strlen (app_id) - strlen (".Devel")) : NULL; mpris_id_non_devel = (app_id_non_devel != NULL) ? g_strconcat ("org.mpris.MediaPlayer2.", app_id_non_devel, NULL) : NULL; } for (size_t h = 0; h < G_N_ELEMENTS (bus_policy_types); h++) { g_auto (GStrv) bus_policies = NULL; if (flags & bus_policy_types[h].unfiltered_flag) continue; bus_policies = g_key_file_get_keys (keyfile, bus_policy_types[h].keyfile_group, NULL, NULL); for (size_t i = 0; bus_policies != NULL && bus_policies[i] != NULL; i++) { const struct { GBusType bus_type; const char *bus_name; gboolean is_prefix; BzBusPolicyPermission permission_is_at_least; BzAppPermissionsFlags flags; } bus_policy_permissions[] = { { G_BUS_TYPE_SESSION, "ca.desrt.dconf", FALSE, BZ_BUS_POLICY_PERMISSION_TALK, BZ_APP_PERMISSIONS_FLAGS_SETTINGS }, { G_BUS_TYPE_SESSION, "org.freedesktop.Flatpak", FALSE, BZ_BUS_POLICY_PERMISSION_TALK, BZ_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX }, { G_BUS_TYPE_SESSION, "org.freedesktop.impl.portal.PermissionStore", FALSE, BZ_BUS_POLICY_PERMISSION_TALK, BZ_APP_PERMISSIONS_FLAGS_ESCAPE_SANDBOX }, { G_BUS_TYPE_SESSION, "org.gtk.vfs.", TRUE, BZ_BUS_POLICY_PERMISSION_TALK, BZ_APP_PERMISSIONS_FLAGS_FILESYSTEM_FULL }, }; const char *bus_name_pattern = bus_policies[i]; g_autofree char *bus_policy_str = NULL; BzBusPolicyPermission bus_policy = BZ_BUS_POLICY_PERMISSION_UNKNOWN; size_t j = 0; bus_policy_str = g_key_file_get_string (keyfile, bus_policy_types[h].keyfile_group, bus_name_pattern, NULL); g_assert (bus_policy_str != NULL); if (g_str_equal (bus_policy_str, "none")) bus_policy = BZ_BUS_POLICY_PERMISSION_NONE; else if (g_str_equal (bus_policy_str, "see")) bus_policy = BZ_BUS_POLICY_PERMISSION_SEE; else if (g_str_equal (bus_policy_str, "talk")) bus_policy = BZ_BUS_POLICY_PERMISSION_TALK; else if (g_str_equal (bus_policy_str, "own")) bus_policy = BZ_BUS_POLICY_PERMISSION_OWN; else continue; if (app_id != NULL && bus_policy_types[h].bus_type == G_BUS_TYPE_SESSION && (g_str_equal (bus_name_pattern, app_id) || (g_str_has_prefix (bus_name_pattern, app_id) && bus_name_pattern[strlen (app_id)] == '.') || g_str_equal (bus_name_pattern, mpris_id) || g_str_equal (bus_name_pattern, "org.freedesktop.DBus") || g_str_has_prefix (bus_name_pattern, "org.freedesktop.portal."))) continue; if (app_id_non_devel != NULL && bus_policy_types[h].bus_type == G_BUS_TYPE_SESSION && (g_str_equal (bus_name_pattern, app_id_non_devel) || (g_str_has_prefix (bus_name_pattern, app_id_non_devel) && bus_name_pattern[strlen (app_id_non_devel)] == '.') || g_str_equal (bus_name_pattern, mpris_id_non_devel))) continue; for (j = 0; j < G_N_ELEMENTS (bus_policy_permissions); j++) { if (bus_policy_permissions[j].bus_type == bus_policy_types[h].bus_type && ((!bus_policy_permissions[j].is_prefix && g_str_equal (bus_name_pattern, bus_policy_permissions[j].bus_name)) || (bus_policy_permissions[j].is_prefix && g_str_has_prefix (bus_name_pattern, bus_policy_permissions[j].bus_name))) && bus_policy >= bus_policy_permissions[j].permission_is_at_least) { flags |= bus_policy_permissions[j].flags; break; } } if (j == G_N_ELEMENTS (bus_policy_permissions)) { flags |= BZ_APP_PERMISSIONS_FLAGS_BUS_POLICY_OTHER; bz_app_permissions_add_bus_policy (permissions, bus_policy_types[h].bus_type, bus_name_pattern, bus_policy); } } } } bz_app_permissions_set_flags (permissions, flags); bz_app_permissions_seal (permissions); return permissions; } void bz_app_permissions_serialize (BzAppPermissions *self, GVariantBuilder *builder) { GFlagsClass *flags_class = NULL; guint i = 0; g_return_if_fail (BZ_IS_APP_PERMISSIONS (self)); g_return_if_fail (builder != NULL); flags_class = g_type_class_ref (BZ_TYPE_APP_PERMISSIONS_FLAGS); if (self->flags != BZ_APP_PERMISSIONS_FLAGS_NONE) { g_autoptr (GVariantBuilder) sub_builder = g_variant_builder_new (G_VARIANT_TYPE ("as")); for (i = 0; i < flags_class->n_values; i++) { if (self->flags & flags_class->values[i].value) g_variant_builder_add (sub_builder, "s", flags_class->values[i].value_nick); } g_variant_builder_add (builder, "{sv}", "permissions-flags", g_variant_builder_end (sub_builder)); } g_type_class_unref (flags_class); if (self->filesystem_read != NULL && self->filesystem_read->len > 0) { GEnumClass *enum_class = NULL; g_autoptr (GVariantBuilder) sub_builder = NULL; enum_class = g_type_class_ref (BZ_TYPE_FILESYSTEM_PATH_TYPE); sub_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ss)")); for (i = 0; i < self->filesystem_read->len; i++) { const BzFilesystemPath *path = g_ptr_array_index (self->filesystem_read, i); GEnumValue *enum_value = g_enum_get_value (enum_class, path->type); g_variant_builder_add (sub_builder, "(ss)", enum_value->value_nick, path->subpath ? path->subpath : ""); } g_variant_builder_add (builder, "{sv}", "permissions-filesystem-read", g_variant_builder_end (sub_builder)); g_type_class_unref (enum_class); } if (self->filesystem_full != NULL && self->filesystem_full->len > 0) { GEnumClass *enum_class = NULL; g_autoptr (GVariantBuilder) sub_builder = NULL; enum_class = g_type_class_ref (BZ_TYPE_FILESYSTEM_PATH_TYPE); sub_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(ss)")); for (i = 0; i < self->filesystem_full->len; i++) { const BzFilesystemPath *path = g_ptr_array_index (self->filesystem_full, i); GEnumValue *enum_value = g_enum_get_value (enum_class, path->type); g_variant_builder_add (sub_builder, "(ss)", enum_value->value_nick, path->subpath ? path->subpath : ""); } g_variant_builder_add (builder, "{sv}", "permissions-filesystem-full", g_variant_builder_end (sub_builder)); g_type_class_unref (enum_class); } if (self->bus_policies != NULL && self->bus_policies->len > 0) { GEnumClass *enum_class = NULL; g_autoptr (GVariantBuilder) sub_builder = NULL; enum_class = g_type_class_ref (BZ_TYPE_BUS_POLICY_PERMISSION); sub_builder = g_variant_builder_new (G_VARIANT_TYPE ("a(iss)")); for (i = 0; i < self->bus_policies->len; i++) { BzBusPolicy *policy = g_ptr_array_index (self->bus_policies, i); GEnumValue *enum_value = g_enum_get_value (enum_class, policy->permission); g_variant_builder_add (sub_builder, "(iss)", (gint32) policy->bus_type, policy->bus_name, enum_value->value_nick); } g_variant_builder_add (builder, "{sv}", "permissions-bus-policies", g_variant_builder_end (sub_builder)); g_type_class_unref (enum_class); } } gboolean bz_app_permissions_deserialize (BzAppPermissions *self, GVariant *import, GError **error) { g_autoptr (GVariantIter) iter = NULL; g_return_val_if_fail (BZ_IS_APP_PERMISSIONS (self), FALSE); g_return_val_if_fail (import != NULL, FALSE); iter = g_variant_iter_new (import); for (;;) { g_autofree char *key = NULL; g_autoptr (GVariant) value = NULL; if (!g_variant_iter_next (iter, "{sv}", &key, &value)) break; if (g_strcmp0 (key, "permissions-flags") == 0) { GFlagsClass *flags_class = NULL; g_autoptr (GVariantIter) flags_iter = NULL; flags_class = g_type_class_ref (BZ_TYPE_APP_PERMISSIONS_FLAGS); flags_iter = g_variant_iter_new (value); self->flags = BZ_APP_PERMISSIONS_FLAGS_NONE; for (;;) { g_autofree char *flag_nick = NULL; if (!g_variant_iter_next (flags_iter, "s", &flag_nick)) break; for (guint i = 0; i < flags_class->n_values; i++) { if (g_str_equal (flag_nick, flags_class->values[i].value_nick)) { self->flags |= flags_class->values[i].value; break; } } } g_type_class_unref (flags_class); } else if (g_strcmp0 (key, "permissions-filesystem-read") == 0) { GEnumClass *enum_class = NULL; g_autoptr (GVariantIter) paths_iter = NULL; enum_class = g_type_class_ref (BZ_TYPE_FILESYSTEM_PATH_TYPE); if (self->filesystem_read == NULL) self->filesystem_read = g_ptr_array_new_with_free_func ((GDestroyNotify) bz_filesystem_path_free); paths_iter = g_variant_iter_new (value); for (;;) { g_autofree char *type_nick = NULL; g_autofree char *subpath = NULL; if (!g_variant_iter_next (paths_iter, "(ss)", &type_nick, &subpath)) break; for (guint i = 0; i < enum_class->n_values; i++) { if (g_str_equal (type_nick, enum_class->values[i].value_nick)) { g_ptr_array_add (self->filesystem_read, bz_filesystem_path_new (enum_class->values[i].value, subpath[0] ? subpath : NULL)); break; } } } g_type_class_unref (enum_class); } else if (g_strcmp0 (key, "permissions-filesystem-full") == 0) { GEnumClass *enum_class = NULL; g_autoptr (GVariantIter) paths_iter = NULL; enum_class = g_type_class_ref (BZ_TYPE_FILESYSTEM_PATH_TYPE); if (self->filesystem_full == NULL) self->filesystem_full = g_ptr_array_new_with_free_func ((GDestroyNotify) bz_filesystem_path_free); paths_iter = g_variant_iter_new (value); for (;;) { g_autofree char *type_nick = NULL; g_autofree char *subpath = NULL; if (!g_variant_iter_next (paths_iter, "(ss)", &type_nick, &subpath)) break; for (guint i = 0; i < enum_class->n_values; i++) { if (g_str_equal (type_nick, enum_class->values[i].value_nick)) { g_ptr_array_add (self->filesystem_full, bz_filesystem_path_new (enum_class->values[i].value, subpath[0] ? subpath : NULL)); break; } } } g_type_class_unref (enum_class); } else if (g_strcmp0 (key, "permissions-bus-policies") == 0) { GEnumClass *enum_class = NULL; g_autoptr (GVariantIter) policies_iter = NULL; enum_class = g_type_class_ref (BZ_TYPE_BUS_POLICY_PERMISSION); if (self->bus_policies == NULL) self->bus_policies = g_ptr_array_new_with_free_func ((GDestroyNotify) bz_bus_policy_free); policies_iter = g_variant_iter_new (value); for (;;) { gint32 bus_type_int = 0; g_autofree char *bus_name = NULL; g_autofree char *permission_nick = NULL; if (!g_variant_iter_next (policies_iter, "(iss)", &bus_type_int, &bus_name, &permission_nick)) break; for (guint i = 0; i < enum_class->n_values; i++) { if (g_str_equal (permission_nick, enum_class->values[i].value_nick)) { g_ptr_array_add (self->bus_policies, bz_bus_policy_new ((GBusType) bus_type_int, bus_name, enum_class->values[i].value)); break; } } } g_type_class_unref (enum_class); } } bz_app_permissions_seal (self); return TRUE; } static gint cmp_filesystem_path_pointers (gconstpointer item1, gconstpointer item2) { const BzFilesystemPath *const *pitem1 = item1; const BzFilesystemPath *const *pitem2 = item2; const BzFilesystemPath *path1 = *pitem1; const BzFilesystemPath *path2 = *pitem2; if (path1->type != path2->type) return path1->type - path2->type; return g_strcmp0 (path1->subpath, path2->subpath); } static int cmp_bus_policy_qsort (const void *item1, const void *item2) { const BzBusPolicy *const *pitem1 = item1; const BzBusPolicy *const *pitem2 = item2; const BzBusPolicy *policy1 = *pitem1; const BzBusPolicy *policy2 = *pitem2; if (policy1->bus_type != policy2->bus_type) return policy1->bus_type - policy2->bus_type; return strcmp (policy1->bus_name, policy2->bus_name); } static guint app_permissions_get_array_index (GPtrArray *array, BzFilesystemPathType type, const char *subpath) { if (array == NULL) return DOES_NOT_CONTAIN; for (guint i = 0; i < array->len; i++) { const BzFilesystemPath *path = g_ptr_array_index (array, i); if (path->type == type && g_strcmp0 (path->subpath, subpath) == 0) return i; } return DOES_NOT_CONTAIN; } static guint get_strv_index (const gchar *const *strv, const gchar *value) { guint ii; for (ii = 0; strv[ii]; ii++) { if (g_str_equal (strv[ii], value)) break; } return ii; }