Version in base suite: 1.16.6-1~deb13u1 Base version: flatpak_1.16.6-1~deb13u1 Target version: flatpak_1.16.6-1~deb13u2 Base file: /srv/ftp-master.debian.org/ftp/pool/main/f/flatpak/flatpak_1.16.6-1~deb13u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/f/flatpak/flatpak_1.16.6-1~deb13u2.dsc changelog | 68 + patches/GHSA-8688-9x26-hhxj/run-Harden-ensure_data_dir-and-var-setup-against-symlink-.patch | 132 +++ patches/GHSA-8688-9x26-hhxj/test-run-Check-that-the-way-com.valvesoftware.Steam-behav.patch | 107 ++ patches/GHSA-8688-9x26-hhxj/tests-Add-a-regression-test-for-GHSA-8688-9x26-hhxj.patch | 78 + patches/GHSA-99wv-m8rp-g58x/common-Fix-return-value-and-typos-in-flatpak_switch_symli.patch | 50 + patches/GHSA-99wv-m8rp-g58x/common-Use-fd-based-operations-in-flatpak_switch_symlink_.patch | 193 ++++ patches/GHSA-99wv-m8rp-g58x/run-Harden-regenerate_ld_cache-against-symlink-attacks.patch | 173 ++++ patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/build-init-Use-fd-relative-operations-to-prevent-path-tra.patch | 341 ++++++++ patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Use-chaseat-in-apply_extra_data-to-prevent-path-trave.patch | 255 ++++++ patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Use-chaseat-in-extract_extra_data-to-prevent-path-tra.patch | 159 ++++ patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Validate-extra-data-name.patch | 42 + patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Add-flatpak_cp_a_at-a-fd-based-flatpak_cp_a-variant.patch | 127 +++ patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Make-flatpak_cp_a-a-wrapper-around-flatpak_cp_a_at.patch | 196 ++++ patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Remove-unused-GFile-based-flatpak_cp_a.patch | 67 + patches/GHSA-jr92-2v97-wgvc/oci-Avoid-overflow-in-delta_read_data.patch | 36 patches/GHSA-jr92-2v97-wgvc/oci-Limit-delta-path-length-to-PATH_MAX.patch | 66 + patches/GHSA-jr92-2v97-wgvc/oci-Use-gsize-for-OCI-delta-sizes.patch | 76 + patches/GHSA-q4gr-vc25-57m5/dir-Fall-back-to-the-deployed-ref-commit-when-up-downgrad.patch | 36 patches/GHSA-q4gr-vc25-57m5/system-dir-Prevent-removing-of-deployed-refs-via-the-syst.patch | 193 ++++ patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-chmod.patch | 114 ++ patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-link-rename.patch | 111 ++ patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-symlink.patch | 87 ++ patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-out-of-basefd-in-si.patch | 219 +++++ patches/GHSA-r7hp-698j-2h6c/run-dbus-Correct-broadcast-rules-for-the-AT-SPI-bus.patch | 34 patches/GHSA-v2gw-v9h5-9q4x/ref-utils-Make-remote-name-validation-public-and-take-an-.patch | 96 ++ patches/GHSA-v2gw-v9h5-9q4x/system-helper-Validate-each-remote-name-and-architecture-.patch | 139 +++ patches/GHSA-v2gw-v9h5-9q4x/tests-Add-some-unit-tests-for-ref-utils-name-component-va.patch | 394 ++++++++++ patches/GHSA-w69g-9x8j-7p8f/dir-utils-Reject-path-traversal-in-extension-metadata.patch | 92 ++ patches/GHSA-w69g-9x8j-7p8f/run-Use-RESOLVE_BENEATH-for-host-side-extension-file-acce.patch | 106 ++ patches/bwrap-Clarify-a-comment.patch | 29 patches/dir-Use-flatpak_bwrap_child_setup_inherit_fds_cb-to-apply.patch | 64 + patches/hardening/dir-Harden-confining-subpath-deploy-to-the-deploy-base.patch | 62 + patches/hardening/dir-Use-fd-based-traversal-for-marker-files-in-deploy-dir.patch | 100 ++ patches/libglnx/chase-Add-glnx_chase_and_mkdirat.patch | 232 +++++ patches/libglnx/chase-Add-internal-glnx_chaseat_full-for-a-strategic-call.patch | 201 +++++ patches/portal-Actually-use-the-AppInfo-hash-table.patch | 41 + patches/series | 38 patches/subprojects-Ignore-.wraplock-file-generated-by-recent-Mes.patch | 19 patches/subprojects-Update-dbus-proxy.wrap-to-v0.1.7.patch | 25 patches/tests/tests-Fix-checks-where-we-expect-a-command-to-fail.patch | 133 +++ 40 files changed, 4731 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpiau_bs7m/flatpak_1.16.6-1~deb13u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpiau_bs7m/flatpak_1.16.6-1~deb13u2.dsc: no acceptable signature found diff -Nru flatpak-1.16.6/debian/changelog flatpak-1.16.6/debian/changelog --- flatpak-1.16.6/debian/changelog 2026-04-10 19:03:53.000000000 +0000 +++ flatpak-1.16.6/debian/changelog 2026-08-11 13:03:38.000000000 +0000 @@ -1,3 +1,71 @@ +flatpak (1.16.6-1~deb13u2) trixie-security; urgency=high + + * d/patches: Backport security fixes from 1.18.1 (Closes: #1144130) + - d/p/libglnx/*.patch: + Backport glnx_chase_and_mkdirat() utility function, required by some + of the security fixes below + - d/p/tests/*.patch: + Backport unit tests fixes which are required by the tests for some + of the security fixes below + - d/p/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/*.patch: + + GHSA-fqx6-vh4p-42cg: + Fix writing outside installation directory via crafted commit metadata. + A malicious or compromised Flatpak repository could write + attacker-controlled files outside /var/lib/flatpak as root. + + GHSA-8qxj-x646-phcm: + Fix writing outside working directory in `flatpak build-init`. + A malicious or compromised SDK could write outside the intended + working directory when a developer starts using it for a build. + - d/p/GHSA-qrwq-7qwx-q9rp/*.patch: + Fix local privilege escalation involving revokefs. + A malicious local user could write files outside /var/lib/flatpak + as root by tampering with OSTree objects after signature verification. + - d/p/GHSA-8688-9x26-hhxj/*.patch: + Fix a sandbox escape involving directories inside ~/.var/app/APP_ID. + A malicious or compromised Flatpak app could write to arbitrary files + outside its sandbox. + - d/p/GHSA-99wv-m8rp-g58x/*.patch: + Fix a sandbox escape involving the ld.so cache. + A malicious or compromised Flatpak app could write files with a fixed + name and limited control over content outside the sandbox. + - d/p/GHSA-v2gw-v9h5-9q4x/*.patch: + Fix local privilege escalation involving crafted OCI architecture names. + A malicious local user on a system with an OCI remote configured + (unusual on non-Fedora systems) could trick the flatpak-system-helper + process into writing outside /var/lib/flatpak. + - d/p/GHSA-w69g-9x8j-7p8f/*.patch: + Fix reading outside sandbox involving crafted extension metadata. + A malicious or compromised Flatpak app could find out whether specific + files exist outside the sandbox. + - d/p/GHSA-q4gr-vc25-57m5/*.patch: + Fix anti-downgrade checks for components installed system-wide. + A malicious local user with an active local login session could + downgrade an app, runtime or extension to an older, known-vulnerable + version and use this to attack other local users. + - d/p/GHSA-jr92-2v97-wgvc/*.patch: + Fix a buffer overflow when installing or updating from a malicious OCI + registry, not believed to be practically exploitable on 64-bit systems. + - d/p/hardening/*.patch: + Harden file accesses against path traversal, fixing issues that + were initially thought to be security vulnerabilities similar to + those above, but on further analysis do not seem to be exploitable. + - d/p/GHSA-r7hp-698j-2h6c/*.patch: + Correct xdg-dbus-proxy rules for receiving selected AT-SPI broadcasts + so that GTK accessibility features work as intended. + Previously, these accessibility features only worked accidentally as a + result of an xdg-dbus-proxy security issue, fixed in 0.1.8. + * d/patches: Add additional bug fixes from upstream 1.16.x branch + - d/p/subprojects-Ignore-.wraplock-file-generated-by-recent-Mes.patch, + d/p/bwrap-Clarify-a-comment.patch, + d/p/subprojects-Update-dbus-proxy.wrap-to-v0.1.7.patch: + Resync with upstream source, no functional changes + - d/p/dir-Use-flatpak_bwrap_child_setup_inherit_fds_cb-to-apply.patch: + Silence a spurious warning when apps use the extra_data mechanism + - d/p/portal-Actually-use-the-AppInfo-hash-table.patch: + Fix a memory leak and potential rare crashes in flatpak-portal + + -- Simon McVittie Tue, 11 Aug 2026 14:03:38 +0100 + flatpak (1.16.6-1~deb13u1) trixie-security; urgency=high * Backport new upstream stable release for Debian 13 diff -Nru flatpak-1.16.6/debian/patches/GHSA-8688-9x26-hhxj/run-Harden-ensure_data_dir-and-var-setup-against-symlink-.patch flatpak-1.16.6/debian/patches/GHSA-8688-9x26-hhxj/run-Harden-ensure_data_dir-and-var-setup-against-symlink-.patch --- flatpak-1.16.6/debian/patches/GHSA-8688-9x26-hhxj/run-Harden-ensure_data_dir-and-var-setup-against-symlink-.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-8688-9x26-hhxj/run-Harden-ensure_data_dir-and-var-setup-against-symlink-.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,132 @@ +From: Sebastian Wick +Date: Tue, 16 Jun 2026 21:12:48 +0200 +Subject: run: Harden ensure_data_dir and /var setup against symlink attacks + +Replace path-based flatpak_mkdir_p calls in flatpak_ensure_data_dir +with glnx_chase_and_mkdirat(RESOLVE_NO_SYMLINKS) to prevent an app +from replacing subdirectories of its data dir with symlinks between +runs and having them followed during the next sandbox setup. + +In flatpak_run_setup_base_argv, replace path-based --bind args for +the app cache/data/config/tmp directories with --bind-fd using fds +obtained via glnx_chaseat(RESOLVE_NO_SYMLINKS), preventing both +symlink following and TOCTOU races when setting up these bind mounts. + +Assisted-by: Claude:opus-4.6 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-8688-9x26-hhxj +Origin: upstream, 1.18.1 +--- + common/flatpak-run.c | 88 ++++++++++++++++++++++++++++++++++------------------ + 1 file changed, 57 insertions(+), 31 deletions(-) + +diff --git a/common/flatpak-run.c b/common/flatpak-run.c +index 2793d1a..11e0748 100644 +--- a/common/flatpak-run.c ++++ b/common/flatpak-run.c +@@ -761,30 +761,35 @@ flatpak_ensure_data_dir (GFile *app_id_dir, + GCancellable *cancellable, + GError **error) + { +- g_autoptr(GFile) data_dir = g_file_get_child (app_id_dir, "data"); +- g_autoptr(GFile) cache_dir = g_file_get_child (app_id_dir, "cache"); +- g_autoptr(GFile) fontconfig_cache_dir = g_file_get_child (cache_dir, "fontconfig"); +- g_autoptr(GFile) tmp_dir = g_file_get_child (cache_dir, "tmp"); +- g_autoptr(GFile) config_dir = g_file_get_child (app_id_dir, "config"); +- g_autoptr(GFile) state_dir = g_file_get_child (app_id_dir, ".local/state"); +- +- if (!flatpak_mkdir_p (data_dir, cancellable, error)) +- return FALSE; +- +- if (!flatpak_mkdir_p (cache_dir, cancellable, error)) +- return FALSE; +- +- if (!flatpak_mkdir_p (fontconfig_cache_dir, cancellable, error)) +- return FALSE; ++ glnx_autofd int app_id_dir_fd = -1; ++ static const char *const subdirs[] = { ++ "data", ++ "cache", ++ "cache/fontconfig", ++ "cache/tmp", ++ "config", ++ ".local/state", ++ }; + +- if (!flatpak_mkdir_p (tmp_dir, cancellable, error)) ++ app_id_dir_fd = glnx_chase_and_mkdirat (AT_FDCWD, ++ flatpak_file_get_path_cached (app_id_dir), ++ GLNX_CHASE_DEFAULT, ++ 0755, ++ error); ++ if (app_id_dir_fd < 0) + return FALSE; + +- if (!flatpak_mkdir_p (config_dir, cancellable, error)) +- return FALSE; ++ for (size_t i = 0; i < G_N_ELEMENTS (subdirs); i++) ++ { ++ glnx_autofd int fd = -1; + +- if (!flatpak_mkdir_p (state_dir, cancellable, error)) +- return FALSE; ++ fd = glnx_chase_and_mkdirat (app_id_dir_fd, subdirs[i], ++ GLNX_CHASE_RESOLVE_BENEATH, ++ 0755, ++ error); ++ if (fd < 0) ++ return FALSE; ++ } + + return TRUE; + } +@@ -2441,18 +2446,39 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap, + + if (app_id_dir != NULL) + { +- g_autoptr(GFile) app_cache_dir = g_file_get_child (app_id_dir, "cache"); +- g_autoptr(GFile) app_tmp_dir = g_file_get_child (app_cache_dir, "tmp"); +- g_autoptr(GFile) app_data_dir = g_file_get_child (app_id_dir, "data"); +- g_autoptr(GFile) app_config_dir = g_file_get_child (app_id_dir, "config"); ++ glnx_autofd int app_id_dir_fd = -1; ++ static const struct ++ { ++ const char *src; ++ const char *dst; ++ } ++ mounts[] = { ++ { "cache", "/var/cache" }, ++ { "data", "/var/data" }, ++ { "config", "/var/config" }, ++ { "cache/tmp", "/var/tmp" }, ++ }; ++ ++ app_id_dir_fd = glnx_chaseat (AT_FDCWD, ++ flatpak_file_get_path_cached (app_id_dir), ++ GLNX_CHASE_MUST_BE_DIRECTORY, ++ error); ++ if (app_id_dir_fd < 0) ++ return FALSE; + +- flatpak_bwrap_add_args (bwrap, +- /* These are nice to have as a fixed path */ +- "--bind", flatpak_file_get_path_cached (app_cache_dir), "/var/cache", +- "--bind", flatpak_file_get_path_cached (app_data_dir), "/var/data", +- "--bind", flatpak_file_get_path_cached (app_config_dir), "/var/config", +- "--bind", flatpak_file_get_path_cached (app_tmp_dir), "/var/tmp", +- NULL); ++ for (i = 0; i < G_N_ELEMENTS (mounts); i++) ++ { ++ glnx_autofd int fd = -1; ++ ++ fd = glnx_chase_and_mkdirat (app_id_dir_fd, mounts[i].src, ++ GLNX_CHASE_RESOLVE_BENEATH, ++ 0755, error); ++ if (fd < 0) ++ return FALSE; ++ ++ flatpak_bwrap_add_args_data_fd (bwrap, "--bind-fd", ++ g_steal_fd (&fd), mounts[i].dst); ++ } + } + + flatpak_run_setup_usr_links (bwrap, runtime_fd, NULL); diff -Nru flatpak-1.16.6/debian/patches/GHSA-8688-9x26-hhxj/test-run-Check-that-the-way-com.valvesoftware.Steam-behav.patch flatpak-1.16.6/debian/patches/GHSA-8688-9x26-hhxj/test-run-Check-that-the-way-com.valvesoftware.Steam-behav.patch --- flatpak-1.16.6/debian/patches/GHSA-8688-9x26-hhxj/test-run-Check-that-the-way-com.valvesoftware.Steam-behav.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-8688-9x26-hhxj/test-run-Check-that-the-way-com.valvesoftware.Steam-behav.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,107 @@ +From: Simon McVittie +Date: Sun, 9 Aug 2026 12:08:24 +0100 +Subject: test-run: Check that the way com.valvesoftware.Steam behaves still + works + +This use of symlinks to ensure that the canonicalized paths of +`$XDG_CACHE_HOME`, `$XDG_CONFIG_HOME`, `$XDG_DATA_HOME` are +`~/.cache`, `~/.config`, `~/.local/share` is the sort of thing that +could easily regress if not tested. + +Signed-off-by: Simon McVittie +[backported to 1.18.x: adjust test numbering] +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-8688-9x26-hhxj +Origin: upstream, 1.18.1 +--- + tests/test-run.sh | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 70 insertions(+), 1 deletion(-) + +diff --git a/tests/test-run.sh b/tests/test-run.sh +index d5bf37d..45abd56 100644 +--- a/tests/test-run.sh ++++ b/tests/test-run.sh +@@ -24,7 +24,7 @@ set -euo pipefail + skip_without_bwrap + skip_revokefs_without_fuse + +-echo "1..27" ++echo "1..28" + + # Use stable rather than master as the branch so we can test that the run + # command automatically finds the branch correctly +@@ -595,6 +595,75 @@ assert_file_has_content "$HOME/.var/app/org.test.Hello/.persistrc" "can-persist" + + ok "--persist=. persists all files" + ++# com.valvesoftware.Steam is an example of an app that wants to use the ++# canonical paths ~/.cache, ~/.config and ~/.local/share, to work around ++# games that don't support the XDG basedir spec properly ++app_dir="$HOME/.var/app/org.test.Hello" ++rm -fr "$app_dir" ++mkdir -p "$app_dir" ++for subdir in .cache .config .local .local/share; do ++ mkdir -m700 "$app_dir/$subdir" ++done ++# This is the symlink that com.valvesoftware.Steam uses to achieve that ++ln -fns .cache "$app_dir/cache" ++ln -fns .config "$app_dir/config" ++ln -fns .local/share "$app_dir/data" ++run --command=sh --persist=. org.test.Hello -c ' ++echo "HOME=$HOME" ++echo "XDG_CACHE_HOME=$XDG_CACHE_HOME" ++echo "XDG_CONFIG_HOME=$XDG_CONFIG_HOME" ++echo "XDG_DATA_HOME=$XDG_DATA_HOME" ++echo "via-HOME" > ~/via-HOME ++echo "via-var-app" > ~/.var/app/org.test.Hello/via-var-app ++echo "via-XDG_CACHE_HOME" > "$XDG_CACHE_HOME/via-XDG_CACHE_HOME" ++echo "via-XDG_CONFIG_HOME" > "$XDG_CONFIG_HOME/via-XDG_CONFIG_HOME" ++echo "via-XDG_DATA_HOME" > "$XDG_DATA_HOME/via-XDG_DATA_HOME" ++echo "via-.cache" > ~/.cache/via-.cache ++echo "via-.config" > ~/.config/via-.config ++echo "via-.local-share" > ~/.local/share/via-.local-share ++echo "via-cache" > ~/cache/via-cache ++echo "via-config" > ~/config/via-config ++echo "via-data" > ~/data/via-data ++echo "via-var-cache" > /var/cache/via-var-cache ++echo "via-var-config" > /var/config/via-var-config ++echo "via-var-data" > /var/data/via-var-data ++' >&2 ++# $HOME and $app_dir in the sandbox are both the same as $app_dir outside ++assert_file_has_content "$app_dir/via-HOME" '^via-HOME$' ++assert_file_has_content "$app_dir/via-var-app" '^via-var-app$' ++# $app_dir/.cache, $app_dir/cache, /var/cache are the same place ++assert_file_has_content "$app_dir/cache/via-XDG_CACHE_HOME" '^via-XDG_CACHE_HOME$' ++assert_file_has_content "$app_dir/cache/via-.cache" '^via-\.cache$' ++assert_file_has_content "$app_dir/cache/via-cache" '^via-cache$' ++assert_file_has_content "$app_dir/cache/via-var-cache" '^via-var-cache$' ++# Same for config ++assert_file_has_content "$app_dir/config/via-XDG_CONFIG_HOME" '^via-XDG_CONFIG_HOME$' ++assert_file_has_content "$app_dir/config/via-.config" '^via-\.config$' ++assert_file_has_content "$app_dir/config/via-config" '^via-config$' ++assert_file_has_content "$app_dir/config/via-var-config" '^via-var-config$' ++# Same for data, except the canonical name is .local/share ++assert_file_has_content "$app_dir/data/via-XDG_DATA_HOME" '^via-XDG_DATA_HOME$' ++assert_file_has_content "$app_dir/data/via-.local-share" '^via-\.local-share$' ++assert_file_has_content "$app_dir/data/via-data" '^via-data$' ++assert_file_has_content "$app_dir/data/via-var-data" '^via-var-data$' ++# The realpath of $HOME/.cache canonicalizes to (realpath of $HOME)/.cache. ++# Experimentally, this is the arrangement that is least likely to break ++# non-spec-compliant game engines ++run --command=sh --persist=. org.test.Hello -c 'readlink -f ~/.cache' > "$app_dir/cache/realpath" ++echo "$(readlink -f "$HOME")/.cache" > "$app_dir/cache/expected-realpath" ++diff -u "$app_dir/cache/expected-realpath" "$app_dir/cache/realpath" >&2 ++# Similarly $XDG_CONFIG_HOME canonicalizes to $HOME/.config ++run --command=sh --persist=. org.test.Hello -c 'readlink -f ~/.config' > "$app_dir/config/realpath" ++echo "$(readlink -f "$HOME")/.config" > "$app_dir/config/expected-realpath" ++diff -u "$app_dir/config/expected-realpath" "$app_dir/config/realpath" >&2 ++# Similarly $XDG_DATA_HOME canonicalizes to $HOME/.local/share ++run --command=sh --persist=. org.test.Hello -c 'readlink -f ~/.local/share' > "$app_dir/data/realpath" ++echo "$(readlink -f "$HOME")/.local/share" > "$app_dir/data/expected-realpath" ++diff -u "$app_dir/data/expected-realpath" "$app_dir/data/realpath" >&2 ++rm -fr "$app_dir" ++ ++ok "--persist=. behaving like com.valvesoftware.Steam" ++ + mkdir "${TEST_DATA_DIR}/inaccessible" + echo FOO > ${TEST_DATA_DIR}/inaccessible/secret-file + rm -fr "$HOME/.var/app/org.test.Hello" diff -Nru flatpak-1.16.6/debian/patches/GHSA-8688-9x26-hhxj/tests-Add-a-regression-test-for-GHSA-8688-9x26-hhxj.patch flatpak-1.16.6/debian/patches/GHSA-8688-9x26-hhxj/tests-Add-a-regression-test-for-GHSA-8688-9x26-hhxj.patch --- flatpak-1.16.6/debian/patches/GHSA-8688-9x26-hhxj/tests-Add-a-regression-test-for-GHSA-8688-9x26-hhxj.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-8688-9x26-hhxj/tests-Add-a-regression-test-for-GHSA-8688-9x26-hhxj.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,78 @@ +From: Simon McVittie +Date: Sun, 9 Aug 2026 12:09:12 +0100 +Subject: tests: Add a regression test for GHSA-8688-9x26-hhxj + +The original reporter used various tricks to find a way for the sandboxed +app to overwrite these locations with symlinks, but for the purposes +of this test, I'm doing the setup outside the sandbox instead: probably +not all of these potential exploit routes are actually possible, but we +defend against all of them symmetrically. + +Signed-off-by: Simon McVittie +[backported to 1.18.x: adjust test numbering] +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-8688-9x26-hhxj +Origin: upstream, 1.18.1 +--- + tests/test-run.sh | 42 +++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 41 insertions(+), 1 deletion(-) + +diff --git a/tests/test-run.sh b/tests/test-run.sh +index 45abd56..2e5cc5c 100644 +--- a/tests/test-run.sh ++++ b/tests/test-run.sh +@@ -24,7 +24,7 @@ set -euo pipefail + skip_without_bwrap + skip_revokefs_without_fuse + +-echo "1..28" ++echo "1..29" + + # Use stable rather than master as the branch so we can test that the run + # command automatically finds the branch correctly +@@ -664,6 +664,46 @@ rm -fr "$app_dir" + + ok "--persist=. behaving like com.valvesoftware.Steam" + ++app_dir="$HOME/.var/app/org.test.Hello" ++inaccessible="${TEST_DATA_DIR}/inaccessible" ++ ++for path in cache data config cache/tmp; do ++ rm -fr "$app_dir" "$inaccessible" ++ mkdir -p "$app_dir" "$inaccessible" ++ case "$path" in ++ (cache/tmp) ++ mkdir -m700 "$app_dir/cache" ++ escape=../../../../../inaccessible ++ test "$app_dir/cache/$escape" -ef "$inaccessible" ++ ;; ++ (*) ++ escape=../../../../inaccessible ++ test "$app_dir/$escape" -ef "$inaccessible" ++ ;; ++ esac ++ for target in "$inaccessible" "$escape"; do ++ # There are various ways that an app could manipulate the contents ++ # of its per-app directory so that the next run of the same app would ++ # have a sandbox escape, but for the purposes of this test we cheat ++ # by setting up the same situation from outside the sandbox. ++ ln -fns "$target" "$app_dir/$path" ++ # The symlink target has been chosen to make these paths, ++ # outside the container, equivalent ++ test "$app_dir/$path" -ef "$inaccessible" ++ # The "|| :" inside the command ensures that if the app runs, then ++ # it succeeds, and then the assert_fail assertion fails. ++ # This is done because in the exploitable situations, we want Flatpak ++ # to refuse to run the app at all ++ assert_fail run --command=sh org.test.Hello -c "echo pwned > ~/$path/pwned || :" ++ assert_fail test -e "$inaccessible/pwned" ++ assert_fail run --command=sh org.test.Hello -c "echo pwned > /var/${path#*/}/pwned || :" ++ assert_fail test -e "$inaccessible/pwned" ++ done ++done ++ ++ok "various attempts to escape app directory via symlinks (GHSA-8688-9x26-hhxj)" ++ ++rm -fr "${TEST_DATA_DIR}/inaccessible" + mkdir "${TEST_DATA_DIR}/inaccessible" + echo FOO > ${TEST_DATA_DIR}/inaccessible/secret-file + rm -fr "$HOME/.var/app/org.test.Hello" diff -Nru flatpak-1.16.6/debian/patches/GHSA-99wv-m8rp-g58x/common-Fix-return-value-and-typos-in-flatpak_switch_symli.patch flatpak-1.16.6/debian/patches/GHSA-99wv-m8rp-g58x/common-Fix-return-value-and-typos-in-flatpak_switch_symli.patch --- flatpak-1.16.6/debian/patches/GHSA-99wv-m8rp-g58x/common-Fix-return-value-and-typos-in-flatpak_switch_symli.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-99wv-m8rp-g58x/common-Fix-return-value-and-typos-in-flatpak_switch_symli.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,50 @@ +From: Sebastian Wick +Date: Thu, 18 Jun 2026 17:58:10 +0200 +Subject: common: Fix return value and typos in + flatpak_switch_symlink_and_remove + +One error path returned -1 instead of FALSE. Since gboolean is gint, +-1 is truthy and the caller would skip error handling. + +Assisted-by: Claude:opus-4.6 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-99wv-m8rp-g58x +Origin: upstream, 1.18.1 +--- + common/flatpak-utils.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c +index 09102fd..e1f7712 100644 +--- a/common/flatpak-utils.c ++++ b/common/flatpak-utils.c +@@ -939,10 +939,10 @@ flatpak_str_is_alphanumeric (const char *arg) + } + + /* This atomically replaces a symlink with a new value, removing the +- * existing symlink target, if it exstis and is different from ++ * existing symlink target, if it exists and is different from + * @target. This is atomic in the sense that we're guaranteed to + * remove any existing symlink target (once), independent of how many +- * processes do the same operation in parallele. However, it is still ++ * processes do the same operation in parallel. However, it is still + * possible that we remove the old and then fail to create the new + * symlink for some reason, ending up with neither the old or the new + * target. That is fine if the reason for the symlink is keeping a +@@ -994,7 +994,7 @@ flatpak_switch_symlink_and_remove (const char *symlink_path, + if (old_target == NULL) + return FALSE; + +- /* Don't remove old file if its the same as the new one */ ++ /* Don't remove old file if it's the same as the new one */ + if (strcmp (old_target, target) != 0) + { + if (flatpak_str_is_alphanumeric (old_target)) +@@ -1014,7 +1014,7 @@ flatpak_switch_symlink_and_remove (const char *symlink_path, + { + glnx_set_error_from_errno (error); + unlink (tmp_path); +- return -1; ++ return FALSE; + } + unlink (tmp_path); + diff -Nru flatpak-1.16.6/debian/patches/GHSA-99wv-m8rp-g58x/common-Use-fd-based-operations-in-flatpak_switch_symlink_.patch flatpak-1.16.6/debian/patches/GHSA-99wv-m8rp-g58x/common-Use-fd-based-operations-in-flatpak_switch_symlink_.patch --- flatpak-1.16.6/debian/patches/GHSA-99wv-m8rp-g58x/common-Use-fd-based-operations-in-flatpak_switch_symlink_.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-99wv-m8rp-g58x/common-Use-fd-based-operations-in-flatpak_switch_symlink_.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,193 @@ +From: Sebastian Wick +Date: Thu, 18 Jun 2026 17:59:23 +0200 +Subject: common: Use fd-based operations in flatpak_switch_symlink_and_remove + +Replace path-based syscalls with fd-relative equivalents so callers +can pin the directory identity and prevent symlink substitution by a +concurrent process. + +Assisted-by: Claude:opus-4.6 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-99wv-m8rp-g58x +Origin: upstream, 1.18.1 +--- + common/flatpak-run.c | 10 ++++- + common/flatpak-utils-private.h | 3 +- + common/flatpak-utils.c | 84 +++++++++++++++++++++++------------------- + 3 files changed, 56 insertions(+), 41 deletions(-) + +diff --git a/common/flatpak-run.c b/common/flatpak-run.c +index 7d46dd6..cc9edca 100644 +--- a/common/flatpak-run.c ++++ b/common/flatpak-run.c +@@ -2801,7 +2801,7 @@ regenerate_ld_cache (GPtrArray *base_argv_array, + } + else + { +- g_autoptr(GFile) active = g_file_get_child (ld_so_dir, "active"); ++ glnx_autofd int ld_so_dir_fd = -1; + + /* For app-dirs we keep one checksum alive, by pointing the active symlink to it */ + +@@ -2812,7 +2812,13 @@ regenerate_ld_cache (GPtrArray *base_argv_array, + return -1; + } + +- if (!flatpak_switch_symlink_and_remove (flatpak_file_get_path_cached (active), ++ if (!glnx_opendirat (AT_FDCWD, ++ flatpak_file_get_path_cached (ld_so_dir), ++ FALSE, &ld_so_dir_fd, ++ error)) ++ return -1; ++ ++ if (!flatpak_switch_symlink_and_remove (ld_so_dir_fd, "active", + checksum, error)) + return -1; + } +diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h +index 7553839..b071e7e 100644 +--- a/common/flatpak-utils-private.h ++++ b/common/flatpak-utils-private.h +@@ -154,7 +154,8 @@ flatpak_auto_lock_helper (GMutex *mutex) + return mutex; + } + +-gboolean flatpak_switch_symlink_and_remove (const char *symlink_path, ++gboolean flatpak_switch_symlink_and_remove (int dir_fd, ++ const char *symlink_name, + const char *target, + GError **error); + +diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c +index e1f7712..2a4b002 100644 +--- a/common/flatpak-utils.c ++++ b/common/flatpak-utils.c +@@ -938,34 +938,38 @@ flatpak_str_is_alphanumeric (const char *arg) + return TRUE; + } + +-/* This atomically replaces a symlink with a new value, removing the +- * existing symlink target, if it exists and is different from +- * @target. This is atomic in the sense that we're guaranteed to +- * remove any existing symlink target (once), independent of how many +- * processes do the same operation in parallel. However, it is still +- * possible that we remove the old and then fail to create the new +- * symlink for some reason, ending up with neither the old or the new +- * target. That is fine if the reason for the symlink is keeping a +- * cache though. +- * The target shall only be a file in the same directory as the symlink, and +- * shall only contain the characters a-zA-Z0-9. This is so that the target of +- * the symlink that gets removed is in the same directory as the link. ++/* Atomically replace the @symlink_name symlink in @dir_fd with one ++ * pointing to @target, removing the previous target file (if it ++ * exists and differs from @target). ++ * ++ * This is atomic in the sense that we're guaranteed to remove any ++ * existing symlink target (once), independent of how many processes ++ * do the same operation in parallel. However, it is still possible ++ * that we remove the old and then fail to create the new symlink for ++ * some reason, ending up with neither the old or the new target. ++ * That is fine if the reason for the symlink is keeping a cache ++ * though. ++ * ++ * All filesystem operations go through @dir_fd so the caller can ++ * guarantee the directory identity even if the path to it is ++ * attacker-controlled. ++ * ++ * @target shall only contain the characters a-zA-Z0-9 so that the ++ * old target file to be removed is always in the same directory. + */ + gboolean +-flatpak_switch_symlink_and_remove (const char *symlink_path, ++flatpak_switch_symlink_and_remove (int dir_fd, ++ const char *symlink_name, + const char *target, + GError **error) + { +- g_autofree char *symlink_dir = g_path_get_dirname (symlink_path); +- int try; +- +- for (try = 0; try < 100; try++) ++ for (size_t try = 0; try < 100; try++) + { +- g_autofree char *tmp_path = NULL; +- int fd; ++ g_autofree char *tmp_name = NULL; ++ glnx_autofd int fd = -1; + + /* Try to atomically create the symlink */ +- if (TEMP_FAILURE_RETRY (symlink (target, symlink_path)) == 0) ++ if (TEMP_FAILURE_RETRY (symlinkat (target, dir_fd, symlink_name)) == 0) + return TRUE; + + if (errno != EEXIST) +@@ -975,22 +979,32 @@ flatpak_switch_symlink_and_remove (const char *symlink_path, + return FALSE; + } + +- /* The symlink existed, move it to a temporary name atomically, and remove target +- if that succeeded. */ +- tmp_path = g_build_filename (symlink_dir, ".switched-symlink-XXXXXX", NULL); ++ /* The symlink existed, move it to a temporary name atomically, ++ * and remove its target if that succeeded. ++ * This generated filename doesn't need to be securely unique: ++ * we're only protecting against accidental collisions if two ++ * processes try to regenerate ld.so.cache at the same time. */ ++ tmp_name = g_strdup_printf (".switched-symlink-%08X", g_random_int ()); + +- fd = g_mkstemp_full (tmp_path, O_RDWR, 0644); ++ fd = openat (dir_fd, tmp_name, ++ O_CREAT | O_WRONLY | O_EXCL | O_CLOEXEC | O_NOFOLLOW, 0644); + if (fd == -1) + { ++ if (errno == EEXIST) ++ continue; ++ + glnx_set_error_from_errno (error); + return FALSE; + } +- close (fd); ++ g_clear_fd (&fd, NULL); + +- if (TEMP_FAILURE_RETRY (rename (symlink_path, tmp_path)) == 0) ++ if (TEMP_FAILURE_RETRY (renameat (dir_fd, symlink_name, ++ dir_fd, tmp_name)) == 0) + { + /* The move succeeded, now we can remove the old target */ +- g_autofree char *old_target = flatpak_readlink (tmp_path, error); ++ g_autofree char *old_target = NULL; ++ ++ old_target = glnx_readlinkat_malloc (dir_fd, tmp_name, NULL, error); + if (old_target == NULL) + return FALSE; + +@@ -998,25 +1012,19 @@ flatpak_switch_symlink_and_remove (const char *symlink_path, + if (strcmp (old_target, target) != 0) + { + if (flatpak_str_is_alphanumeric (old_target)) +- { +- g_autofree char *old_target_path = NULL; +- +- old_target_path = g_build_filename (symlink_dir, old_target, NULL); +- unlink (old_target_path); +- } ++ unlinkat (dir_fd, old_target, 0); + else +- { +- g_warning ("Refusing to delete old link target %s", old_target); +- } ++ g_warning ("Refusing to delete old link target %s", old_target); + } + } + else if (errno != ENOENT) + { + glnx_set_error_from_errno (error); +- unlink (tmp_path); ++ unlinkat (dir_fd, tmp_name, 0); + return FALSE; + } +- unlink (tmp_path); ++ ++ unlinkat (dir_fd, tmp_name, 0); + + /* An old target was removed, try again */ + } diff -Nru flatpak-1.16.6/debian/patches/GHSA-99wv-m8rp-g58x/run-Harden-regenerate_ld_cache-against-symlink-attacks.patch flatpak-1.16.6/debian/patches/GHSA-99wv-m8rp-g58x/run-Harden-regenerate_ld_cache-against-symlink-attacks.patch --- flatpak-1.16.6/debian/patches/GHSA-99wv-m8rp-g58x/run-Harden-regenerate_ld_cache-against-symlink-attacks.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-99wv-m8rp-g58x/run-Harden-regenerate_ld_cache-against-symlink-attacks.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,173 @@ +From: Sebastian Wick +Date: Thu, 18 Jun 2026 17:59:47 +0200 +Subject: run: Harden regenerate_ld_cache against symlink attacks + +A sandboxed app can replace ~/.var/app/$appid/.ld.so with a symlink, +causing regenerate_ld_cache to write files at an arbitrary location. +A concurrent app instance makes this a TOCTOU even after the initial +directory verification. + +Replace all path-based operations with fd-based equivalents using +ld_so_dir_fd obtained via glnx_chase_and_mkdirat, and pass the +directory to bwrap via --bind-fd instead of --bind. + +Assisted-by: Claude:opus-4.6 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-99wv-m8rp-g58x +Origin: upstream, 1.18.1 +--- + common/flatpak-run.c | 88 ++++++++++++++++++++++++++++++++++++---------------- + 1 file changed, 61 insertions(+), 27 deletions(-) + +diff --git a/common/flatpak-run.c b/common/flatpak-run.c +index cc9edca..2793d1a 100644 +--- a/common/flatpak-run.c ++++ b/common/flatpak-run.c +@@ -2691,33 +2691,72 @@ regenerate_ld_cache (GPtrArray *base_argv_array, + { + g_autoptr(FlatpakBwrap) bwrap = NULL; + g_autoptr(GArray) combined_fd_array = NULL; +- g_autoptr(GFile) ld_so_cache = NULL; +- g_autoptr(GFile) ld_so_cache_tmp = NULL; + g_autofree char *sandbox_cache_path = NULL; + g_autofree char *tmp_basename = NULL; + g_auto(GStrv) minimal_envp = NULL; + g_autofree char *commandline = NULL; + int exit_status; + glnx_autofd int ld_so_fd = -1; +- g_autoptr(GFile) ld_so_dir = NULL; ++ glnx_autofd int ld_so_dir_fd = -1; ++ g_autofree char *ld_so_dir_path = NULL; + + if (app_id_dir) +- ld_so_dir = g_file_get_child (app_id_dir, ".ld.so"); ++ { ++ glnx_autofd int app_id_dir_fd = -1; ++ ++ app_id_dir_fd = glnx_chase_and_mkdirat (AT_FDCWD, ++ flatpak_file_get_path_cached (app_id_dir), ++ GLNX_CHASE_DEFAULT, ++ 0700, error); ++ if (app_id_dir_fd < 0) ++ { ++ g_prefix_error (error, "cannot open %s: ", ++ flatpak_file_get_path_cached (app_id_dir)); ++ return -1; ++ } ++ ++ ld_so_dir_fd = glnx_chase_and_mkdirat (app_id_dir_fd, ".ld.so", ++ GLNX_CHASE_RESOLVE_NO_SYMLINKS, ++ 0755, error); ++ ++ ld_so_dir_path = g_build_filename (flatpak_file_get_path_cached (app_id_dir), ++ ".ld.so", ++ NULL); ++ } + else + { +- g_autoptr(GFile) base_dir = g_file_new_for_path (g_get_user_cache_dir ()); +- ld_so_dir = g_file_resolve_relative_path (base_dir, "flatpak/ld.so"); ++ glnx_autofd int cache_dir_fd = -1; ++ ++ cache_dir_fd = glnx_chase_and_mkdirat (AT_FDCWD, ++ g_get_user_cache_dir (), ++ GLNX_CHASE_DEFAULT, ++ 0700, error); ++ if (cache_dir_fd < 0) ++ { ++ g_prefix_error (error, "cannot open %s: ", ++ g_get_user_cache_dir ()); ++ return -1; ++ } ++ ++ ld_so_dir_fd = glnx_chase_and_mkdirat (cache_dir_fd, "flatpak/ld.so", ++ GLNX_CHASE_RESOLVE_NO_SYMLINKS, ++ 0755, error); ++ ++ ld_so_dir_path = g_build_filename (g_get_user_cache_dir (), ++ "flatpak/ld.so", ++ NULL); + } + +- ld_so_cache = g_file_get_child (ld_so_dir, checksum); +- ld_so_fd = open (flatpak_file_get_path_cached (ld_so_cache), O_RDONLY); +- if (ld_so_fd >= 0) +- return g_steal_fd (&ld_so_fd); ++ if (ld_so_dir_fd < 0) ++ { ++ g_prefix_error (error, "cannot open %s: ", ld_so_dir_path); ++ return -1; ++ } + +- g_info ("Regenerating ld.so.cache %s", flatpak_file_get_path_cached (ld_so_cache)); ++ if (glnx_openat_rdonly (ld_so_dir_fd, checksum, FALSE, &ld_so_fd, NULL)) ++ return g_steal_fd (&ld_so_fd); + +- if (!flatpak_mkdir_p (ld_so_dir, cancellable, error)) +- return FALSE; ++ g_info ("Regenerating ld.so.cache %s/%s", ld_so_dir_path, checksum); + + minimal_envp = flatpak_run_get_minimal_env (FALSE, FALSE); + bwrap = flatpak_bwrap_new (minimal_envp); +@@ -2740,7 +2779,6 @@ regenerate_ld_cache (GPtrArray *base_argv_array, + glnx_gen_temp_name (tmp_basename); + + sandbox_cache_path = g_build_filename ("/run/ld-so-cache-dir", tmp_basename, NULL); +- ld_so_cache_tmp = g_file_get_child (ld_so_dir, tmp_basename); + + flatpak_bwrap_add_args (bwrap, + "--unshare-pid", +@@ -2748,8 +2786,13 @@ regenerate_ld_cache (GPtrArray *base_argv_array, + "--unshare-net", + "--proc", "/proc", + "--dev", "/dev", +- "--bind", flatpak_file_get_path_cached (ld_so_dir), "/run/ld-so-cache-dir", + NULL); ++ ++ if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, "--bind-fd", ++ ld_so_dir_fd, "/run/ld-so-cache-dir", ++ error)) ++ return -1; ++ + flatpak_bwrap_sort_envp (bwrap); + flatpak_bwrap_envp_to_args (bwrap); + +@@ -2787,8 +2830,7 @@ regenerate_ld_cache (GPtrArray *base_argv_array, + return -1; + } + +- ld_so_fd = open (flatpak_file_get_path_cached (ld_so_cache_tmp), O_RDONLY); +- if (ld_so_fd < 0) ++ if (!glnx_openat_rdonly (ld_so_dir_fd, tmp_basename, FALSE, &ld_so_fd, NULL)) + { + flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Can't open generated ld.so.cache")); + return -1; +@@ -2797,27 +2839,19 @@ regenerate_ld_cache (GPtrArray *base_argv_array, + if (app_id_dir == NULL) + { + /* For runs without an app id dir we always regenerate the ld.so.cache */ +- unlink (flatpak_file_get_path_cached (ld_so_cache_tmp)); ++ unlinkat (ld_so_dir_fd, tmp_basename, 0); + } + else + { +- glnx_autofd int ld_so_dir_fd = -1; +- + /* For app-dirs we keep one checksum alive, by pointing the active symlink to it */ + + /* Rename to known name, possibly overwriting existing ref if race */ +- if (rename (flatpak_file_get_path_cached (ld_so_cache_tmp), flatpak_file_get_path_cached (ld_so_cache)) == -1) ++ if (renameat (ld_so_dir_fd, tmp_basename, ld_so_dir_fd, checksum) == -1) + { + glnx_set_error_from_errno (error); + return -1; + } + +- if (!glnx_opendirat (AT_FDCWD, +- flatpak_file_get_path_cached (ld_so_dir), +- FALSE, &ld_so_dir_fd, +- error)) +- return -1; +- + if (!flatpak_switch_symlink_and_remove (ld_so_dir_fd, "active", + checksum, error)) + return -1; diff -Nru flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/build-init-Use-fd-relative-operations-to-prevent-path-tra.patch flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/build-init-Use-fd-relative-operations-to-prevent-path-tra.patch --- flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/build-init-Use-fd-relative-operations-to-prevent-path-tra.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/build-init-Use-fd-relative-operations-to-prevent-path-tra.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,341 @@ +From: Sebastian Wick +Date: Mon, 29 Jun 2026 17:34:02 +0200 +Subject: build-init: Use fd-relative operations to prevent path traversal + +ensure_extensions() used g_file_resolve_relative_path() with +ext->directory, which comes from runtime/SDK metadata and can contain +".." components. This allowed a malicious extension to write outside +the build directory. + +Replace the GFile-based path resolution with glnx_chase_and_mkdirat() +using GLNX_CHASE_RESOLVE_BENEATH | GLNX_CHASE_RESOLVE_NO_SYMLINKS, +which rejects any path that escapes the directory fd. + +Convert the rest of flatpak_builtin_build_init to fd-relative +operations (mkdirat, symlinkat, glnx_opendirat, glnx_shutil_rm_rf_at, +glnx_file_replace_contents_at, flatpak_cp_a_at) anchored to a +base_dfd opened from the build directory. + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-8qxj-x646-phcm +Origin: upstream, 1.18.1 +--- + app/flatpak-builtins-build-init.c | 218 +++++++++++++++++++++++--------------- + 1 file changed, 134 insertions(+), 84 deletions(-) + +diff --git a/app/flatpak-builtins-build-init.c b/app/flatpak-builtins-build-init.c +index bcfa3be..ef6611d 100644 +--- a/app/flatpak-builtins-build-init.c ++++ b/app/flatpak-builtins-build-init.c +@@ -72,7 +72,7 @@ static GOptionEntry options[] = { + + static gboolean + ensure_extensions (FlatpakDeploy *src_deploy, const char *default_arch, const char *default_branch, +- char *src_extensions[], GFile *top_dir, GCancellable *cancellable, GError **error) ++ char *src_extensions[], int top_dfd, GCancellable *cancellable, GError **error) + { + g_autoptr(GKeyFile) metakey = flatpak_deploy_get_metadata (src_deploy); + GList *extensions = NULL, *l; +@@ -118,22 +118,47 @@ ensure_extensions (FlatpakDeploy *src_deploy, const char *default_arch, const ch + return flatpak_fail (error, _("Requested extension %s/%s/%s is only partially installed"), ext->installed_id, default_arch, default_branch); + } + +- if (top_dir) ++ if (top_dfd >= 0) + { +- g_autoptr(GFile) target = g_file_resolve_relative_path (top_dir, ext->directory); +- g_autoptr(GFile) target_parent = g_file_get_parent (target); +- g_autoptr(GFile) ext_deploy_files = g_file_new_for_path (ext->files_path); ++ glnx_autofd int ext_src_dfd = -1; ++ glnx_autofd int parent_dfd = -1; ++ g_autofree char *dir_parent = g_path_get_dirname (ext->directory); ++ const char *dir_basename = glnx_basename (ext->directory); ++ ++ if (strcmp (dir_parent, ".") != 0) ++ { ++ glnx_autofd int parent_opath_fd = -1; ++ ++ parent_opath_fd = glnx_chase_and_mkdirat (top_dfd, dir_parent, ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_RESOLVE_NO_SYMLINKS, ++ 0755, error); ++ if (parent_opath_fd < 0) ++ return FALSE; ++ ++ parent_dfd = glnx_fd_reopen (parent_opath_fd, ++ O_RDONLY | O_DIRECTORY | O_CLOEXEC, ++ error); ++ if (parent_dfd < 0) ++ return FALSE; ++ } ++ else ++ { ++ parent_dfd = dup (top_dfd); ++ if (parent_dfd < 0) ++ return glnx_throw_errno_prefix (error, "dup"); ++ } + +- if (!flatpak_mkdir_p (target_parent, cancellable, error)) ++ /* An extension overrides whatever is there before, so we clean up first */ ++ if (!glnx_shutil_rm_rf_at (parent_dfd, dir_basename, cancellable, error)) + return FALSE; + +- /* An extension overrides whatever is there before, so we clean up first */ +- if (!flatpak_rm_rf (target, cancellable, error)) ++ if (!glnx_opendirat (AT_FDCWD, ext->files_path, FALSE, &ext_src_dfd, error)) + return FALSE; + +- if (!flatpak_cp_a (ext_deploy_files, target, +- FLATPAK_CP_FLAGS_NO_CHOWN, +- cancellable, error)) ++ if (!flatpak_cp_a_at (ext_src_dfd, parent_dfd, dir_basename, ++ FLATPAK_CP_FLAGS_NO_CHOWN, ++ cancellable, error)) + return FALSE; + } + +@@ -170,15 +195,8 @@ flatpak_builtin_build_init (int argc, char **argv, GCancellable *cancellable, GE + g_autoptr(GOptionContext) context = NULL; + g_autoptr(GFile) var_deploy_files = NULL; + g_autoptr(GFile) base = NULL; +- g_autoptr(GFile) files_dir = NULL; +- g_autoptr(GFile) usr_dir = NULL; +- g_autoptr(GFile) var_dir = NULL; +- g_autoptr(GFile) var_tmp_dir = NULL; +- g_autoptr(GFile) var_run_dir = NULL; +- g_autoptr(GFile) metadata_file = NULL; +- g_autoptr(GFile) gitignore_file = NULL; ++ glnx_autofd int base_dfd = -1; + g_autoptr(GString) metadata_contents = NULL; +- g_autoptr(GError) my_error = NULL; + g_autoptr(FlatpakDeploy) runtime_deploy = NULL; + g_autoptr(FlatpakDeploy) sdk_deploy = NULL; + const char *app_id; +@@ -237,8 +255,12 @@ flatpak_builtin_build_init (int argc, char **argv, GCancellable *cancellable, GE + else + is_app = TRUE; + +- if (!flatpak_is_valid_name (app_id, -1, &my_error)) +- return flatpak_fail (error, _("'%s' is not a valid application name: %s"), app_id, my_error->message); ++ { ++ g_autoptr(GError) my_error = NULL; ++ ++ if (!flatpak_is_valid_name (app_id, -1, &my_error)) ++ return flatpak_fail (error, _("'%s' is not a valid application name: %s"), app_id, my_error->message); ++ } + + + kinds = FLATPAK_KINDS_RUNTIME; +@@ -283,16 +305,18 @@ flatpak_builtin_build_init (int argc, char **argv, GCancellable *cancellable, GE + if (!flatpak_mkdir_p (base, cancellable, error)) + return FALSE; + +- files_dir = g_file_get_child (base, "files"); +- var_dir = g_file_get_child (base, "var"); +- var_tmp_dir = g_file_get_child (var_dir, "tmp"); +- var_run_dir = g_file_get_child (var_dir, "run"); +- metadata_file = g_file_get_child (base, "metadata"); +- gitignore_file = g_file_get_child (base, ".gitignore"); ++ if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (base), FALSE, &base_dfd, error)) ++ return FALSE; + +- if (!opt_update && +- g_file_query_exists (files_dir, cancellable)) +- return flatpak_fail (error, _("Build directory %s already initialized"), directory); ++ { ++ struct stat stbuf; ++ ++ if (!glnx_fstatat_allow_noent (base_dfd, "files", &stbuf, AT_SYMLINK_NOFOLLOW, error)) ++ return FALSE; ++ ++ if (!opt_update && errno == 0) ++ return flatpak_fail (error, _("Build directory %s already initialized"), directory); ++ } + + sdk_deploy = flatpak_dir_load_deployed (sdk_dir, sdk_ref, NULL, cancellable, error); + if (sdk_deploy == NULL) +@@ -300,36 +324,41 @@ flatpak_builtin_build_init (int argc, char **argv, GCancellable *cancellable, GE + + if (opt_writable_sdk || is_runtime) + { ++ const char *usr_dir_name = opt_sdk_dir ? opt_sdk_dir : "usr"; + g_autoptr(GFile) sdk_deploy_files = NULL; ++ glnx_autofd int sdk_dfd = -1; + +- if (opt_sdk_dir) +- usr_dir = g_file_get_child (base, opt_sdk_dir); +- else +- usr_dir = g_file_get_child (base, "usr"); +- +- if (!flatpak_rm_rf (usr_dir, NULL, &my_error)) +- { +- if (!g_error_matches (my_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) +- { +- g_propagate_error (error, g_steal_pointer (&my_error)); +- return FALSE; +- } +- +- g_clear_error (&my_error); +- } ++ if (!glnx_shutil_rm_rf_at (base_dfd, usr_dir_name, cancellable, error)) ++ return FALSE; + + sdk_deploy_files = flatpak_deploy_get_files (sdk_deploy); +- if (!flatpak_cp_a (sdk_deploy_files, usr_dir, FLATPAK_CP_FLAGS_NO_CHOWN, cancellable, error)) ++ if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (sdk_deploy_files), ++ FALSE, &sdk_dfd, error)) ++ return FALSE; ++ if (!flatpak_cp_a_at (sdk_dfd, base_dfd, usr_dir_name, ++ FLATPAK_CP_FLAGS_NO_CHOWN, cancellable, error)) + return FALSE; + } + + sdk_branch = flatpak_decomposed_dup_branch (sdk_ref); + sdk_arch = flatpak_decomposed_dup_arch (sdk_ref); + +- if (opt_sdk_extensions && +- !ensure_extensions (sdk_deploy, sdk_arch, sdk_branch, +- opt_sdk_extensions, usr_dir, cancellable, error)) +- return FALSE; ++ if (opt_sdk_extensions) ++ { ++ glnx_autofd int usr_dfd = -1; ++ ++ if (opt_writable_sdk || is_runtime) ++ { ++ const char *usr_dir_name = opt_sdk_dir ? opt_sdk_dir : "usr"; ++ ++ if (!glnx_opendirat (base_dfd, usr_dir_name, FALSE, &usr_dfd, error)) ++ return FALSE; ++ } ++ ++ if (!ensure_extensions (sdk_deploy, sdk_arch, sdk_branch, ++ opt_sdk_extensions, usr_dfd, cancellable, error)) ++ return FALSE; ++ } + + if (opt_var) + { +@@ -345,14 +374,15 @@ flatpak_builtin_build_init (int argc, char **argv, GCancellable *cancellable, GE + if (opt_update) + return TRUE; + +- if (!g_file_make_directory (files_dir, cancellable, error)) +- return FALSE; ++ if (TEMP_FAILURE_RETRY (mkdirat (base_dfd, "files", 0777)) != 0) ++ return glnx_throw_errno_prefix (error, "mkdirat(files)"); + + if (opt_base) + { + const char *base_branch; + g_autoptr(GFile) base_deploy_files = NULL; + g_autoptr(FlatpakDeploy) base_deploy = NULL; ++ glnx_autofd int base_src_dfd = -1; + + base_branch = opt_base_version ? opt_base_version : "master"; + base_ref = flatpak_build_app_ref (opt_base, base_branch, opt_arch); +@@ -361,35 +391,55 @@ flatpak_builtin_build_init (int argc, char **argv, GCancellable *cancellable, GE + return FALSE; + + base_deploy_files = flatpak_deploy_get_files (base_deploy); +- if (!flatpak_cp_a (base_deploy_files, files_dir, +- FLATPAK_CP_FLAGS_MERGE | FLATPAK_CP_FLAGS_NO_CHOWN, +- cancellable, error)) ++ if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (base_deploy_files), ++ FALSE, &base_src_dfd, error)) + return FALSE; +- +- +- if (opt_base_extensions && +- !ensure_extensions (base_deploy, opt_arch, base_branch, +- opt_base_extensions, files_dir, cancellable, error)) ++ if (!flatpak_cp_a_at (base_src_dfd, base_dfd, "files", ++ FLATPAK_CP_FLAGS_MERGE | FLATPAK_CP_FLAGS_NO_CHOWN, ++ cancellable, error)) + return FALSE; +- } + +- if (var_deploy_files) +- { +- if (!flatpak_cp_a (var_deploy_files, var_dir, FLATPAK_CP_FLAGS_NONE, cancellable, error)) +- return FALSE; +- } +- else +- { +- if (!g_file_make_directory (var_dir, cancellable, error)) +- return FALSE; ++ if (opt_base_extensions) ++ { ++ glnx_autofd int files_dfd = -1; ++ if (!glnx_opendirat (base_dfd, "files", FALSE, &files_dfd, error)) ++ return FALSE; ++ if (!ensure_extensions (base_deploy, opt_arch, base_branch, ++ opt_base_extensions, files_dfd, cancellable, error)) ++ return FALSE; ++ } + } + +- if (!flatpak_mkdir_p (var_tmp_dir, cancellable, error)) +- return FALSE; ++ { ++ glnx_autofd int var_dfd = -1; + +- if (!g_file_query_exists (var_run_dir, cancellable) && +- !g_file_make_symbolic_link (var_run_dir, "/run", cancellable, error)) +- return FALSE; ++ if (var_deploy_files) ++ { ++ glnx_autofd int var_src_dfd = -1; ++ ++ if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (var_deploy_files), ++ FALSE, &var_src_dfd, error)) ++ return FALSE; ++ ++ if (!flatpak_cp_a_at (var_src_dfd, base_dfd, "var", ++ FLATPAK_CP_FLAGS_NONE, cancellable, error)) ++ return FALSE; ++ } ++ else ++ { ++ if (TEMP_FAILURE_RETRY (mkdirat (base_dfd, "var", 0777)) != 0) ++ return glnx_throw_errno_prefix (error, "mkdirat(var)"); ++ } ++ ++ if (!glnx_opendirat (base_dfd, "var", FALSE, &var_dfd, error)) ++ return FALSE; ++ ++ if (!glnx_shutil_mkdir_p_at (var_dfd, "tmp", 0777, cancellable, error)) ++ return FALSE; ++ ++ if (TEMP_FAILURE_RETRY (symlinkat ("/run", var_dfd, "run")) != 0 && errno != EEXIST) ++ return glnx_throw_errno_prefix (error, "symlinkat(var/run)"); ++ } + + + metadata_contents = g_string_new (""); +@@ -468,17 +518,17 @@ flatpak_builtin_build_init (int argc, char **argv, GCancellable *cancellable, GE + + keyfile_data = g_key_file_to_data (keyfile, &keyfile_data_len, NULL); + +- if (!g_file_replace_contents (metadata_file, +- keyfile_data, keyfile_data_len, NULL, FALSE, +- G_FILE_CREATE_REPLACE_DESTINATION, +- NULL, cancellable, error)) ++ if (!glnx_file_replace_contents_at (base_dfd, "metadata", ++ (const guint8 *) keyfile_data, keyfile_data_len, ++ GLNX_FILE_REPLACE_NODATASYNC, ++ cancellable, error)) + return FALSE; + +- if (!g_file_replace_contents (gitignore_file, +- GIT_IGNORE_FILE, sizeof (GIT_IGNORE_FILE), +- NULL, FALSE, +- G_FILE_CREATE_REPLACE_DESTINATION, +- NULL, cancellable, error)) ++ if (!glnx_file_replace_contents_at (base_dfd, ".gitignore", ++ (const guint8 *) GIT_IGNORE_FILE, ++ sizeof (GIT_IGNORE_FILE), ++ GLNX_FILE_REPLACE_NODATASYNC, ++ cancellable, error)) + return FALSE; + + return TRUE; diff -Nru flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Use-chaseat-in-apply_extra_data-to-prevent-path-trave.patch flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Use-chaseat-in-apply_extra_data-to-prevent-path-trave.patch --- flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Use-chaseat-in-apply_extra_data-to-prevent-path-trave.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Use-chaseat-in-apply_extra_data-to-prevent-path-trave.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,255 @@ +From: Sebastian Wick +Date: Fri, 7 Aug 2026 23:18:59 +0200 +Subject: dir: Use chaseat in apply_extra_data to prevent path traversal + +apply_extra_data() used GFile path-based operations that follow +symlinks: --ro-bind/--bind with resolved paths, chmod on the extra +directory path, and flatpak_cp_a for the extra/export merge. + +These are not independently exploitable: apply_extra_data only runs +after extract_extra_data has already created files/extra as a real +directory, so there is no symlink left to follow. The apply_extra +script itself runs in a restricted sandbox with dropped caps and no +/proc. The extra/export merge only adds to the app's own export +directory, which is already app-controlled and whose desktop file +Exec keys are rewritten to flatpak run. + +Replace with fd-relative operations as defense in depth: +glnx_chaseat with GLNX_CHASE_RESOLVE_NO_SYMLINKS and +GLNX_CHASE_RESOLVE_BENEATH for directory traversal, +--ro-bind-fd/--bind-fd for bwrap mounts, fchmod on the directory fd, +and flatpak_cp_a_at for the export merge. + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-fqx6-vh4p-42cg +[smcv: Use glnx_fd_reopen() to reopen O_PATH fd as readable] +Co-authored-by: Simon McVittie +Origin: upstream, 1.18.1 +--- + common/flatpak-dir.c | 142 ++++++++++++++++++++++++++++++++++----------------- + 1 file changed, 94 insertions(+), 48 deletions(-) + +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index da5d887..5aa29ac 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -8352,11 +8352,10 @@ extract_extra_data (FlatpakDir *self, + + static gboolean + apply_extra_data (FlatpakDir *self, +- GFile *checkoutdir, ++ int checkoutdir_dfd, + GCancellable *cancellable, + GError **error) + { +- g_autoptr(GFile) metadata = NULL; + g_autofree char *metadata_contents = NULL; + gsize metadata_size; + g_autoptr(GKeyFile) metakey = NULL; +@@ -8365,27 +8364,60 @@ apply_extra_data (FlatpakDir *self, + g_autoptr(FlatpakDecomposed) runtime_ref = NULL; + g_autoptr(FlatpakDeploy) runtime_deploy = NULL; + g_autoptr(FlatpakBwrap) bwrap = NULL; +- g_autoptr(GFile) app_files = NULL; +- g_autoptr(GFile) apply_extra_file = NULL; +- g_autoptr(GFile) app_export_file = NULL; +- g_autoptr(GFile) extra_export_file = NULL; +- g_autoptr(GFile) extra_files = NULL; + g_autoptr(GFile) runtime_files = NULL; + g_autoptr(FlatpakContext) app_context = NULL; + g_auto(GStrv) minimal_envp = NULL; + g_autofree char *runtime_arch = NULL; ++ glnx_autofd int app_files_dfd = -1; ++ glnx_autofd int metadata_path_fd = -1; ++ glnx_autofd int metadata_read_fd = -1; ++ glnx_autofd int extra_dfd = -1; ++ glnx_autofd int usr_fd = -1; + int exit_status; + const char *group = FLATPAK_METADATA_GROUP_APPLICATION; + g_autoptr(GError) local_error = NULL; + FlatpakRunFlags run_flags; + +- apply_extra_file = g_file_resolve_relative_path (checkoutdir, "files/bin/apply_extra"); +- if (!g_file_query_exists (apply_extra_file, cancellable)) +- return TRUE; ++ app_files_dfd = glnx_chaseat (checkoutdir_dfd, "files", ++ GLNX_CHASE_RESOLVE_NO_SYMLINKS | ++ GLNX_CHASE_MUST_BE_DIRECTORY, ++ error); ++ if (app_files_dfd < 0) ++ return FALSE; + +- metadata = g_file_get_child (checkoutdir, "metadata"); ++ { ++ glnx_autofd int apply_extra_fd = -1; + +- if (!g_file_load_contents (metadata, cancellable, &metadata_contents, &metadata_size, NULL, error)) ++ apply_extra_fd = glnx_chaseat (app_files_dfd, "bin/apply_extra", ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_REGULAR, ++ &local_error); ++ if (apply_extra_fd < 0) ++ { ++ if (g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) ++ { ++ g_clear_error (&local_error); ++ return TRUE; ++ } ++ g_propagate_error (error, g_steal_pointer (&local_error)); ++ return FALSE; ++ } ++ } ++ ++ metadata_path_fd = glnx_chaseat (checkoutdir_dfd, "metadata", ++ GLNX_CHASE_RESOLVE_NO_SYMLINKS | ++ GLNX_CHASE_MUST_BE_REGULAR, ++ error); ++ if (metadata_path_fd < 0) ++ return FALSE; ++ ++ metadata_read_fd = glnx_fd_reopen (metadata_path_fd, O_RDONLY, error); ++ if (metadata_read_fd < 0) ++ return FALSE; ++ ++ metadata_contents = glnx_fd_readall_utf8 (metadata_read_fd, &metadata_size, ++ cancellable, error); ++ if (metadata_contents == NULL) + return FALSE; + + metakey = g_key_file_new (); +@@ -8438,24 +8470,36 @@ apply_extra_data (FlatpakDir *self, + runtime_files = flatpak_deploy_get_files (runtime_deploy); + } + +- app_files = g_file_get_child (checkoutdir, "files"); +- app_export_file = g_file_get_child (checkoutdir, "export"); +- extra_files = g_file_get_child (app_files, "extra"); +- extra_export_file = g_file_get_child (extra_files, "export"); ++ extra_dfd = glnx_chaseat (app_files_dfd, "extra", ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_DIRECTORY, ++ error); ++ if (extra_dfd < 0) ++ return FALSE; + + minimal_envp = flatpak_run_get_minimal_env (FALSE, FALSE); + bwrap = flatpak_bwrap_new (minimal_envp); + flatpak_bwrap_add_args (bwrap, flatpak_get_bwrap (), NULL); + + if (runtime_files) +- flatpak_bwrap_add_args (bwrap, +- "--ro-bind", flatpak_file_get_path_cached (runtime_files), "/usr", +- "--lock-file", "/usr/.ref", +- NULL); ++ { ++ usr_fd = glnx_chaseat (AT_FDCWD, flatpak_file_get_path_cached (runtime_files), ++ GLNX_CHASE_MUST_BE_DIRECTORY, ++ error); ++ if (usr_fd < 0) ++ return FALSE; ++ ++ if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, "--ro-bind-fd", usr_fd, "/usr", error)) ++ return FALSE; ++ flatpak_bwrap_add_args (bwrap, "--lock-file", "/usr/.ref", NULL); ++ } ++ ++ if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, "--ro-bind-fd", app_files_dfd, "/app", error)) ++ return FALSE; ++ if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, "--bind-fd", extra_dfd, "/app/extra", error)) ++ return FALSE; + + flatpak_bwrap_add_args (bwrap, +- "--ro-bind", flatpak_file_get_path_cached (app_files), "/app", +- "--bind", flatpak_file_get_path_cached (extra_files), "/app/extra", + "--chdir", "/app/extra", + /* We run as root in the system-helper case, so drop all caps */ + "--cap-drop", "ALL", +@@ -8477,16 +8521,6 @@ apply_extra_data (FlatpakDir *self, + * Disable /proc entirely in this context. */ + run_flags |= FLATPAK_RUN_FLAG_NO_PROC; + +- glnx_autofd int usr_fd = -1; +- +- if (runtime_files != NULL) +- { +- usr_fd = open (flatpak_file_get_path_cached (runtime_files), +- O_PATH | O_CLOEXEC | O_NOFOLLOW); +- if (usr_fd < 0) +- return glnx_throw_errno_prefix (error, "Failed to open runtime files"); +- } +- + if (!flatpak_run_setup_base_argv (bwrap, usr_fd, NULL, runtime_arch, + run_flags, error)) + return FALSE; +@@ -8513,11 +8547,19 @@ apply_extra_data (FlatpakDir *self, + * this runs as root in the system helper case. We canonicalize the permissions at the + * end, but to avoid non-canonical permissions leaking out before then we make the + * toplevel dir only accessible to the user */ +- if (chmod (flatpak_file_get_path_cached (extra_files), 0700) != 0) +- { +- glnx_set_error_from_errno (error); ++ { ++ glnx_autofd int extra_real_dfd = ++ glnx_fd_reopen (extra_dfd, O_RDONLY | O_DIRECTORY, error); ++ ++ if (extra_real_dfd < 0) + return FALSE; +- } ++ ++ if (fchmod (extra_real_dfd, 0700) != 0) ++ { ++ glnx_set_error_from_errno (error); ++ return FALSE; ++ } ++ } + + if (!g_spawn_sync (NULL, + (char **) bwrap->argv->pdata, +@@ -8529,7 +8571,7 @@ apply_extra_data (FlatpakDir *self, + error)) + return FALSE; + +- if (!flatpak_canonicalize_permissions (AT_FDCWD, flatpak_file_get_path_cached (extra_files), ++ if (!flatpak_canonicalize_permissions (app_files_dfd, "extra", + getuid () == 0 ? 0 : -1, + getuid () == 0 ? 0 : -1, + error)) +@@ -8542,16 +8584,20 @@ apply_extra_data (FlatpakDir *self, + return FALSE; + } + +- if (g_file_query_exists (extra_export_file, cancellable)) +- { +- if (!flatpak_mkdir_p (app_export_file, cancellable, error)) +- return FALSE; +- if (!flatpak_cp_a (extra_export_file, +- app_export_file, +- FLATPAK_CP_FLAGS_MERGE, +- cancellable, error)) +- return FALSE; +- } ++ { ++ glnx_autofd int extra_export_dfd = -1; ++ ++ extra_export_dfd = glnx_chaseat (extra_dfd, "export", ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_DIRECTORY, ++ NULL); ++ if (extra_export_dfd >= 0) ++ { ++ if (!flatpak_cp_a_at (extra_export_dfd, checkoutdir_dfd, "export", ++ FLATPAK_CP_FLAGS_MERGE, cancellable, error)) ++ return FALSE; ++ } ++ } + + return TRUE; + } +@@ -8942,7 +8988,7 @@ flatpak_dir_deploy (FlatpakDir *self, + + if (created_extra_data) + { +- if (!apply_extra_data (self, checkoutdir, cancellable, error)) ++ if (!apply_extra_data (self, checkoutdir_dfd, cancellable, error)) + { + g_prefix_error (error, _("While trying to apply extra data: ")); + return FALSE; diff -Nru flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Use-chaseat-in-extract_extra_data-to-prevent-path-tra.patch flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Use-chaseat-in-extract_extra_data-to-prevent-path-tra.patch --- flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Use-chaseat-in-extract_extra_data-to-prevent-path-tra.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Use-chaseat-in-extract_extra_data-to-prevent-path-tra.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,159 @@ +From: Sebastian Wick +Date: Fri, 7 Aug 2026 23:31:49 +0200 +Subject: dir: Use chaseat in extract_extra_data to prevent path traversal + +extract_extra_data() had two vulnerabilities: + +1. It resolved "files/extra" using g_file_resolve_relative_path() + which follows symlinks. A crafted OSTree commit with "files" as a + symlink causes extra-data blobs to be written at the symlink target. + On system installs this runs as root via the system helper, which + validates signatures and checksums but not tree structure. + +2. It used g_file_get_child(extradir, name) where name comes from + xa.extra-data-sources in the commit metadata. Names containing ".." + escape the extra/ directory. This is exploitable through the normal + build flow: flatpak build-export rejects "/" but not "..". + +Replace GFile path operations with fd-relative operations: open +"files" with glnx_chaseat using GLNX_CHASE_RESOLVE_NO_SYMLINKS, +create "extra" with glnx_chase_and_mkdirat using +GLNX_CHASE_RESOLVE_BENEATH, validate extra-data names against ".", +"..", and "/", and write with glnx_file_replace_contents_at anchored +to the extra directory fd. + +[smcv: Open checkoutdir_dfd before trying to open its files subdir] +Co-authored-by: Simon McVittie +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-fqx6-vh4p-42cg +Origin: upstream, 1.18.1 +--- + common/flatpak-dir.c | 47 +++++++++++++++++++++++++++++++---------------- + 1 file changed, 31 insertions(+), 16 deletions(-) + +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index 7273cda..da5d887 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -8208,7 +8208,7 @@ out: + static gboolean + extract_extra_data (FlatpakDir *self, + const char *checksum, +- GFile *extradir, ++ int app_files_dfd, + gboolean *created_extra_data, + GCancellable *cancellable, + GError **error) +@@ -8217,6 +8217,7 @@ extract_extra_data (FlatpakDir *self, + g_autoptr(GVariant) extra_data = NULL; + g_autoptr(GVariant) extra_data_sources = NULL; + g_autoptr(GError) local_error = NULL; ++ glnx_autofd int extra_dfd = -1; + gsize i, n_extra_data = 0; + gsize n_extra_data_sources; + +@@ -8239,7 +8240,7 @@ extract_extra_data (FlatpakDir *self, + if (n_extra_data_sources == 0) + return TRUE; + +- g_info ("extracting extra data to %s", flatpak_file_get_path_cached (extradir)); ++ g_info ("extracting extra data"); + + if (!ostree_repo_read_commit_detached_metadata (self->repo, checksum, &detached_metadata, + cancellable, error)) +@@ -8260,7 +8261,10 @@ extract_extra_data (FlatpakDir *self, + if (n_extra_data < n_extra_data_sources) + return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Extra data missing in detached metadata")); + +- if (!flatpak_mkdir_p (extradir, cancellable, error)) ++ extra_dfd = glnx_chase_and_mkdirat (app_files_dfd, "extra", ++ GLNX_CHASE_RESOLVE_BENEATH, ++ 0777, error); ++ if (extra_dfd < 0) + { + g_prefix_error (error, _("While creating extradir: ")); + return FALSE; +@@ -8295,7 +8299,6 @@ extract_extra_data (FlatpakDir *self, + for (j = 0; j < n_extra_data; j++) + { + g_autoptr(GVariant) content = NULL; +- g_autoptr(GFile) dest = NULL; + g_autofree char *sha256 = NULL; + const char *extra_data_name = NULL; + const guchar *data; +@@ -8308,6 +8311,13 @@ extract_extra_data (FlatpakDir *self, + if (strcmp (extra_data_source_name, extra_data_name) != 0) + continue; + ++ if (extra_data_name[0] == '\0' || ++ strcmp (extra_data_name, ".") == 0 || ++ strcmp (extra_data_name, "..") == 0 || ++ strchr (extra_data_name, '/') != NULL) ++ return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, ++ _("Invalid extra data filename '%s'"), extra_data_name); ++ + data = g_variant_get_data (content); + len = g_variant_get_size (content); + +@@ -8318,12 +8328,10 @@ extract_extra_data (FlatpakDir *self, + if (strcmp (sha256, extra_data_sha256) != 0) + return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Invalid checksum for extra data")); + +- dest = g_file_get_child (extradir, extra_data_name); +- if (!g_file_replace_contents (dest, +- g_variant_get_data (content), +- g_variant_get_size (content), +- NULL, FALSE, G_FILE_CREATE_REPLACE_DESTINATION, +- NULL, cancellable, error)) ++ if (!glnx_file_replace_contents_at (extra_dfd, extra_data_name, ++ data, len, ++ GLNX_FILE_REPLACE_NODATASYNC, ++ cancellable, error)) + { + g_prefix_error (error, _("While writing extra data file '%s': "), extra_data_name); + return FALSE; +@@ -8755,6 +8763,7 @@ flatpak_dir_deploy (FlatpakDir *self, + OstreeRepoCheckoutAtOptions options = { 0, }; + const char *checksum; + glnx_autofd int checkoutdir_dfd = -1; ++ glnx_autofd int app_files_dfd = -1; + const char *xa_ref = NULL; + g_autofree char *checkout_basename = NULL; + gboolean created_extra_data = FALSE; +@@ -8911,15 +8920,24 @@ flatpak_dir_deploy (FlatpakDir *self, + } + } + ++ if (!glnx_opendirat (deploy_base_dfd, checkoutdir_basename, FALSE, &checkoutdir_dfd, error)) ++ return FALSE; ++ + /* Extract any extra data */ +- extradir = g_file_resolve_relative_path (checkoutdir, "files/extra"); +- if (!flatpak_rm_rf (extradir, cancellable, error)) ++ app_files_dfd = glnx_chaseat (checkoutdir_dfd, "files", ++ GLNX_CHASE_RESOLVE_NO_SYMLINKS | ++ GLNX_CHASE_MUST_BE_DIRECTORY, ++ error); ++ if (app_files_dfd < 0) ++ return FALSE; ++ ++ if (!glnx_shutil_rm_rf_at (app_files_dfd, "extra", cancellable, error)) + { + g_prefix_error (error, _("While trying to remove existing extra dir: ")); + return FALSE; + } + +- if (!extract_extra_data (self, checksum, extradir, &created_extra_data, cancellable, error)) ++ if (!extract_extra_data (self, checksum, app_files_dfd, &created_extra_data, cancellable, error)) + return FALSE; + + if (created_extra_data) +@@ -9128,9 +9146,6 @@ flatpak_dir_deploy (FlatpakDir *self, + if (!flatpak_bytes_save (deploy_data_file, deploy_data, cancellable, error)) + return FALSE; + +- if (!glnx_opendirat (deploy_base_dfd, checkoutdir_basename, TRUE, &checkoutdir_dfd, error)) +- return FALSE; +- + if (syncfs (checkoutdir_dfd) != 0) + { + glnx_set_error_from_errno (error); diff -Nru flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Validate-extra-data-name.patch flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Validate-extra-data-name.patch --- flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Validate-extra-data-name.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Validate-extra-data-name.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,42 @@ +From: Sebastian Wick +Date: Fri, 7 Aug 2026 22:59:35 +0200 +Subject: dir: Validate extra-data name + +Reject extra-data names containing ".", "..", or "/" early during the +download phase. The name is used to construct a local cache file path +via flatpak_build_file() which uses g_file_resolve_relative_path(), +so ".." components could traverse outside the cache directory. + +In practice this is not exploitable: the local cache path is only +used for a g_file_query_exists() check followed by g_file_load_contents(), +and the loaded content is verified against the expected sha256 from +the commit metadata. + +Bug: https://github.com/flatpak/xdg-dbus-proxy/security/advisories/GHSA-fqx6-vh4p-42cg +[smcv: Backport to 1.16.x where pull_extra_data_to_bytes() wasn't factored out] +Origin: backport, 1.18.1 +--- + common/flatpak-dir.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index f4c3d27..7273cda 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -5810,8 +5810,14 @@ flatpak_dir_pull_extra_data (FlatpakDir *self, + + extra_data_sha256 = ostree_checksum_from_bytes (sha256_bytes); + +- if (*extra_data_name == 0) +- return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, _("Empty name for extra data uri %s"), extra_data_uri); ++ if (extra_data_name == NULL || *extra_data_name == '\0' || ++ strcmp (extra_data_name, ".") == 0 || ++ strcmp (extra_data_name, "..") == 0 || ++ strchr (extra_data_name, '/') != NULL) ++ { ++ return flatpak_fail_error (error, FLATPAK_ERROR_INVALID_DATA, ++ _("Invalid extra data name '%s'"), extra_data_name); ++ } + + /* Don't allow file uris here as that could read local files based on remote data */ + if (!g_str_has_prefix (extra_data_uri, "http:") && diff -Nru flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Add-flatpak_cp_a_at-a-fd-based-flatpak_cp_a-variant.patch flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Add-flatpak_cp_a_at-a-fd-based-flatpak_cp_a-variant.patch --- flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Add-flatpak_cp_a_at-a-fd-based-flatpak_cp_a-variant.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Add-flatpak_cp_a_at-a-fd-based-flatpak_cp_a-variant.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,127 @@ +From: Sebastian Wick +Date: Mon, 29 Jun 2026 17:02:41 +0200 +Subject: utils: Add flatpak_cp_a_at, a fd-based flatpak_cp_a variant + +For all the usual reasons where an attacker might control a symlink. + +For simplicity this does not implement the unused FLATPAK_CP_FLAGS_MOVE +flag. + +[smcv: Separated from a larger commit] + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-fqx6-vh4p-42cg +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-8qxj-x646-phcm +Origin: upstream, 1.18.1 +--- + common/flatpak-utils-private.h | 7 ++++ + common/flatpak-utils.c | 79 ++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 86 insertions(+) + +diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h +index b071e7e..858aa13 100644 +--- a/common/flatpak-utils-private.h ++++ b/common/flatpak-utils-private.h +@@ -205,6 +205,13 @@ gboolean flatpak_cp_a (GFile *src, + GCancellable *cancellable, + GError **error); + ++gboolean flatpak_cp_a_at (int src_dfd, ++ int dest_parent_dfd, ++ const char *dest_name, ++ FlatpakCpFlags flags, ++ GCancellable *cancellable, ++ GError **error); ++ + gboolean flatpak_mkdir_p (GFile *dir, + GCancellable *cancellable, + GError **error); +diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c +index 2a4b002..f39b4a0 100644 +--- a/common/flatpak-utils.c ++++ b/common/flatpak-utils.c +@@ -1309,6 +1309,85 @@ out: + return ret; + } + ++gboolean ++flatpak_cp_a_at (int src_dfd, ++ int dest_parent_dfd, ++ const char *dest_name, ++ FlatpakCpFlags flags, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ gboolean merge = (flags & FLATPAK_CP_FLAGS_MERGE) != 0; ++ gboolean no_chown = (flags & FLATPAK_CP_FLAGS_NO_CHOWN) != 0; ++ struct stat src_stbuf; ++ glnx_autofd int dest_dfd = -1; ++ g_auto(GLnxDirFdIterator) src_iter = { 0, }; ++ ++ if (fstat (src_dfd, &src_stbuf) != 0) ++ return glnx_throw_errno_prefix (error, "fstat"); ++ ++ if (TEMP_FAILURE_RETRY (mkdirat (dest_parent_dfd, dest_name, 0755)) != 0 && ++ (!merge || errno != EEXIST)) ++ return glnx_throw_errno_prefix (error, "mkdirat(%s)", dest_name); ++ ++ if (!glnx_opendirat (dest_parent_dfd, dest_name, FALSE, &dest_dfd, error)) ++ return FALSE; ++ ++ if (!no_chown) ++ { ++ if (TEMP_FAILURE_RETRY (fchown (dest_dfd, src_stbuf.st_uid, src_stbuf.st_gid)) != 0) ++ return glnx_throw_errno_prefix (error, "fchown"); ++ } ++ ++ if (TEMP_FAILURE_RETRY (fchmod (dest_dfd, src_stbuf.st_mode & 07777)) != 0) ++ return glnx_throw_errno_prefix (error, "fchmod"); ++ ++ if (!glnx_dirfd_iterator_init_at (src_dfd, ".", FALSE, &src_iter, error)) ++ return FALSE; ++ ++ while (TRUE) ++ { ++ struct dirent *dent; ++ const char *name; ++ ++ if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&src_iter, &dent, ++ cancellable, error)) ++ return FALSE; ++ ++ if (dent == NULL) ++ break; ++ ++ name = dent->d_name; ++ ++ if (dent->d_type == DT_DIR) ++ { ++ glnx_autofd int child_src_dfd = -1; ++ ++ if (!glnx_opendirat (src_iter.fd, name, FALSE, &child_src_dfd, error)) ++ return FALSE; ++ ++ if (!flatpak_cp_a_at (child_src_dfd, dest_dfd, ++ name, flags, cancellable, error)) ++ return FALSE; ++ } ++ else ++ { ++ GLnxFileCopyFlags copyflags = GLNX_FILE_COPY_OVERWRITE; ++ if (no_chown) ++ copyflags |= GLNX_FILE_COPY_NOCHOWN; ++ ++ (void) unlinkat (dest_dfd, name, 0); ++ ++ if (!glnx_file_copy_at (src_iter.fd, name, NULL, ++ dest_dfd, name, copyflags, ++ cancellable, error)) ++ return FALSE; ++ } ++ } ++ ++ return TRUE; ++} ++ + static gboolean + _flatpak_canonicalize_permissions (int parent_dfd, + const char *rel_path, diff -Nru flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Make-flatpak_cp_a-a-wrapper-around-flatpak_cp_a_at.patch flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Make-flatpak_cp_a-a-wrapper-around-flatpak_cp_a_at.patch --- flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Make-flatpak_cp_a-a-wrapper-around-flatpak_cp_a_at.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Make-flatpak_cp_a-a-wrapper-around-flatpak_cp_a_at.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,196 @@ +From: Sebastian Wick +Date: Mon, 29 Jun 2026 17:02:41 +0200 +Subject: utils: Make flatpak_cp_a a wrapper around flatpak_cp_a_at + +[smcv: Separated from a larger commit] + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-fqx6-vh4p-42cg +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-8qxj-x646-phcm +Origin: upstream, 1.18.1 +--- + common/flatpak-utils-private.h | 1 - + common/flatpak-utils.c | 152 +++++++---------------------------------- + 2 files changed, 24 insertions(+), 129 deletions(-) + +diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h +index 858aa13..5cdab1a 100644 +--- a/common/flatpak-utils-private.h ++++ b/common/flatpak-utils-private.h +@@ -196,7 +196,6 @@ typedef enum { + FLATPAK_CP_FLAGS_NONE = 0, + FLATPAK_CP_FLAGS_MERGE = 1 << 0, + FLATPAK_CP_FLAGS_NO_CHOWN = 1 << 1, +- FLATPAK_CP_FLAGS_MOVE = 1 << 2, + } FlatpakCpFlags; + + gboolean flatpak_cp_a (GFile *src, +diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c +index f39b4a0..0f95931 100644 +--- a/common/flatpak-utils.c ++++ b/common/flatpak-utils.c +@@ -1181,134 +1181,6 @@ flatpak_openat_noatime (int dfd, + } + } + +-gboolean +-flatpak_cp_a (GFile *src, +- GFile *dest, +- FlatpakCpFlags flags, +- GCancellable *cancellable, +- GError **error) +-{ +- gboolean ret = FALSE; +- GFileEnumerator *enumerator = NULL; +- GFileInfo *src_info = NULL; +- GFile *dest_child = NULL; +- int dest_dfd = -1; +- gboolean merge = (flags & FLATPAK_CP_FLAGS_MERGE) != 0; +- gboolean no_chown = (flags & FLATPAK_CP_FLAGS_NO_CHOWN) != 0; +- gboolean move = (flags & FLATPAK_CP_FLAGS_MOVE) != 0; +- g_autoptr(GFileInfo) child_info = NULL; +- GError *temp_error = NULL; +- int r; +- +- enumerator = g_file_enumerate_children (src, "standard::type,standard::name,unix::uid,unix::gid,unix::mode", +- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, +- cancellable, error); +- if (!enumerator) +- goto out; +- +- src_info = g_file_query_info (src, "standard::name,unix::mode,unix::uid,unix::gid," \ +- "time::modified,time::modified-usec,time::access,time::access-usec", +- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, +- cancellable, error); +- if (!src_info) +- goto out; +- +- do +- r = mkdir (flatpak_file_get_path_cached (dest), 0755); +- while (G_UNLIKELY (r == -1 && errno == EINTR)); +- if (r == -1 && +- (!merge || errno != EEXIST)) +- { +- glnx_set_error_from_errno (error); +- goto out; +- } +- +- if (!glnx_opendirat (AT_FDCWD, flatpak_file_get_path_cached (dest), TRUE, +- &dest_dfd, error)) +- goto out; +- +- if (!no_chown) +- { +- do +- r = fchown (dest_dfd, +- g_file_info_get_attribute_uint32 (src_info, "unix::uid"), +- g_file_info_get_attribute_uint32 (src_info, "unix::gid")); +- while (G_UNLIKELY (r == -1 && errno == EINTR)); +- if (r == -1) +- { +- glnx_set_error_from_errno (error); +- goto out; +- } +- } +- +- do +- r = fchmod (dest_dfd, g_file_info_get_attribute_uint32 (src_info, "unix::mode")); +- while (G_UNLIKELY (r == -1 && errno == EINTR)); +- +- if (dest_dfd != -1) +- { +- (void) close (dest_dfd); +- dest_dfd = -1; +- } +- +- while ((child_info = g_file_enumerator_next_file (enumerator, cancellable, &temp_error))) +- { +- const char *name = g_file_info_get_name (child_info); +- g_autoptr(GFile) src_child = g_file_get_child (src, name); +- +- if (dest_child) +- g_object_unref (dest_child); +- dest_child = g_file_get_child (dest, name); +- +- if (g_file_info_get_file_type (child_info) == G_FILE_TYPE_DIRECTORY) +- { +- if (!flatpak_cp_a (src_child, dest_child, flags, +- cancellable, error)) +- goto out; +- } +- else +- { +- (void) unlink (flatpak_file_get_path_cached (dest_child)); +- GFileCopyFlags copyflags = G_FILE_COPY_OVERWRITE | G_FILE_COPY_NOFOLLOW_SYMLINKS; +- if (!no_chown) +- copyflags |= G_FILE_COPY_ALL_METADATA; +- if (move) +- { +- if (!g_file_move (src_child, dest_child, copyflags, +- cancellable, NULL, NULL, error)) +- goto out; +- } +- else +- { +- if (!g_file_copy (src_child, dest_child, copyflags, +- cancellable, NULL, NULL, error)) +- goto out; +- } +- } +- +- g_clear_object (&child_info); +- } +- +- if (temp_error != NULL) +- { +- g_propagate_error (error, temp_error); +- goto out; +- } +- +- if (move && +- !g_file_delete (src, NULL, error)) +- goto out; +- +- ret = TRUE; +-out: +- if (dest_dfd != -1) +- (void) close (dest_dfd); +- g_clear_object (&src_info); +- g_clear_object (&enumerator); +- g_clear_object (&dest_child); +- return ret; +-} +- + gboolean + flatpak_cp_a_at (int src_dfd, + int dest_parent_dfd, +@@ -1388,6 +1260,30 @@ flatpak_cp_a_at (int src_dfd, + return TRUE; + } + ++gboolean ++flatpak_cp_a (GFile *src, ++ GFile *dest, ++ FlatpakCpFlags flags, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ const char *src_path = flatpak_file_get_path_cached (src); ++ g_autoptr(GFile) dest_parent = g_file_get_parent (dest); ++ const char *dest_parent_path = flatpak_file_get_path_cached (dest_parent); ++ const char *dest_name = glnx_basename (flatpak_file_get_path_cached (dest)); ++ glnx_autofd int src_dfd = -1; ++ glnx_autofd int dest_parent_dfd = -1; ++ ++ if (!glnx_opendirat (AT_FDCWD, src_path, FALSE, &src_dfd, error)) ++ return FALSE; ++ ++ if (!glnx_opendirat (AT_FDCWD, dest_parent_path, FALSE, &dest_parent_dfd, error)) ++ return FALSE; ++ ++ return flatpak_cp_a_at (src_dfd, dest_parent_dfd, dest_name, flags, ++ cancellable, error); ++} ++ + static gboolean + _flatpak_canonicalize_permissions (int parent_dfd, + const char *rel_path, diff -Nru flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Remove-unused-GFile-based-flatpak_cp_a.patch flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Remove-unused-GFile-based-flatpak_cp_a.patch --- flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Remove-unused-GFile-based-flatpak_cp_a.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Remove-unused-GFile-based-flatpak_cp_a.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,67 @@ +From: Sebastian Wick +Date: Mon, 29 Jun 2026 20:18:40 +0200 +Subject: utils: Remove unused GFile based flatpak_cp_a + +We use the fd based flatpak_cp_a_at now everywhere, so we can drop this +one. + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-fqx6-vh4p-42cg +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-8qxj-x646-phcm +Origin: upstream, 1.18.1 +--- + common/flatpak-utils-private.h | 6 ------ + common/flatpak-utils.c | 24 ------------------------ + 2 files changed, 30 deletions(-) + +diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h +index 5cdab1a..b56a354 100644 +--- a/common/flatpak-utils-private.h ++++ b/common/flatpak-utils-private.h +@@ -198,12 +198,6 @@ typedef enum { + FLATPAK_CP_FLAGS_NO_CHOWN = 1 << 1, + } FlatpakCpFlags; + +-gboolean flatpak_cp_a (GFile *src, +- GFile *dest, +- FlatpakCpFlags flags, +- GCancellable *cancellable, +- GError **error); +- + gboolean flatpak_cp_a_at (int src_dfd, + int dest_parent_dfd, + const char *dest_name, +diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c +index 0f95931..c549514 100644 +--- a/common/flatpak-utils.c ++++ b/common/flatpak-utils.c +@@ -1260,30 +1260,6 @@ flatpak_cp_a_at (int src_dfd, + return TRUE; + } + +-gboolean +-flatpak_cp_a (GFile *src, +- GFile *dest, +- FlatpakCpFlags flags, +- GCancellable *cancellable, +- GError **error) +-{ +- const char *src_path = flatpak_file_get_path_cached (src); +- g_autoptr(GFile) dest_parent = g_file_get_parent (dest); +- const char *dest_parent_path = flatpak_file_get_path_cached (dest_parent); +- const char *dest_name = glnx_basename (flatpak_file_get_path_cached (dest)); +- glnx_autofd int src_dfd = -1; +- glnx_autofd int dest_parent_dfd = -1; +- +- if (!glnx_opendirat (AT_FDCWD, src_path, FALSE, &src_dfd, error)) +- return FALSE; +- +- if (!glnx_opendirat (AT_FDCWD, dest_parent_path, FALSE, &dest_parent_dfd, error)) +- return FALSE; +- +- return flatpak_cp_a_at (src_dfd, dest_parent_dfd, dest_name, flags, +- cancellable, error); +-} +- + static gboolean + _flatpak_canonicalize_permissions (int parent_dfd, + const char *rel_path, diff -Nru flatpak-1.16.6/debian/patches/GHSA-jr92-2v97-wgvc/oci-Avoid-overflow-in-delta_read_data.patch flatpak-1.16.6/debian/patches/GHSA-jr92-2v97-wgvc/oci-Avoid-overflow-in-delta_read_data.patch --- flatpak-1.16.6/debian/patches/GHSA-jr92-2v97-wgvc/oci-Avoid-overflow-in-delta_read_data.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-jr92-2v97-wgvc/oci-Avoid-overflow-in-delta_read_data.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,36 @@ +From: Sebastian Wick +Date: Mon, 27 Jul 2026 16:00:52 +0200 +Subject: oci: Avoid overflow in delta_read_data +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 8bit + +delta_read_data computed g_malloc(size + 1) where size came from the +delta stream. If size equals G_MAXSIZE, size + 1 wraps to zero and +g_malloc returns a minimal allocation, then g_input_stream_read_all +writes size bytes into it — a heap buffer overflow. + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-jr92-2v97-wgvc +Origin: upstream, 1.18.1 +--- + common/flatpak-oci-registry.c | 7 ++++++- + 1 file changed, 6 insertions(+), 1 deletion(-) + +diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c +index 8490b60..f1e2f43 100644 +--- a/common/flatpak-oci-registry.c ++++ b/common/flatpak-oci-registry.c +@@ -1830,7 +1830,12 @@ delta_read_data (GInputStream *in, + GCancellable *cancellable, + GError **error) + { +- g_autofree guchar *buf = g_malloc (size+1); ++ g_autofree guchar *buf = NULL; ++ ++ if (size > SIZE_MAX - 1) ++ return glnx_null_throw (error, _("Invalid delta file format")); ++ ++ buf = g_malloc (size + 1); + + if (!g_input_stream_read_all (in, buf, size, NULL, cancellable, error)) + return NULL; diff -Nru flatpak-1.16.6/debian/patches/GHSA-jr92-2v97-wgvc/oci-Limit-delta-path-length-to-PATH_MAX.patch flatpak-1.16.6/debian/patches/GHSA-jr92-2v97-wgvc/oci-Limit-delta-path-length-to-PATH_MAX.patch --- flatpak-1.16.6/debian/patches/GHSA-jr92-2v97-wgvc/oci-Limit-delta-path-length-to-PATH_MAX.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-jr92-2v97-wgvc/oci-Limit-delta-path-length-to-PATH_MAX.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,66 @@ +From: Sebastian Wick +Date: Mon, 6 Jul 2026 15:19:16 +0200 +Subject: oci: Limit delta path length to PATH_MAX + +The previous commit ensures that there isn't a heap overflow when +reading a huge delta path, but we should also just reject unreasonably +long paths. So we chose the arbitrary limit of PATH_MAX and assume that +anything beyond that arbitrary limit is probably abusive. + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-jr92-2v97-wgvc +Origin: upstream, 1.18.1 +--- + common/flatpak-oci-registry.c | 25 +++++++++++++++---------- + 1 file changed, 15 insertions(+), 10 deletions(-) + +diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c +index f1e2f43..6021f03 100644 +--- a/common/flatpak-oci-registry.c ++++ b/common/flatpak-oci-registry.c +@@ -1933,8 +1933,6 @@ flatpak_oci_registry_apply_delta_stream (FlatpakOciRegistry *self, + { + guint8 op; + gsize size; +- g_autofree char *path = NULL; +- g_autofree char *clean_path = NULL; + g_autoptr(GError) local_error = NULL; + gboolean eof; + +@@ -1957,22 +1955,29 @@ flatpak_oci_registry_apply_delta_stream (FlatpakOciRegistry *self, + break; + + case DELTA_OP_OPEN: +- path = (char *)delta_read_data (in, size, cancellable, error); +- if (path == NULL) +- return FALSE; +- clean_path = delta_clean_path (path); +- +- g_clear_object (&content_file); +- + { +- g_autoptr(GFile) child = g_file_resolve_relative_path (content_dir, clean_path); ++ g_autofree char *path = NULL; ++ g_autofree char *clean_path = NULL; ++ g_autoptr(GFile) child = NULL; + g_autoptr(GFileInputStream) child_in = NULL; + ++ if (size > PATH_MAX) ++ return flatpak_fail (error, _("Invalid delta file format")); ++ ++ path = (char *) delta_read_data (in, size, cancellable, error); ++ if (path == NULL) ++ return FALSE; ++ ++ clean_path = delta_clean_path (path); ++ ++ child = g_file_resolve_relative_path (content_dir, clean_path); ++ + child_in = g_file_read (child, cancellable, error); + if (child_in == NULL) + return FALSE; + + /* We can't seek in the ostree repo file, so copy it to temp file */ ++ g_clear_object (&content_file); + content_file = copy_stream_to_file (self, G_INPUT_STREAM (child_in), cancellable, error); + if (content_file == NULL) + return FALSE; diff -Nru flatpak-1.16.6/debian/patches/GHSA-jr92-2v97-wgvc/oci-Use-gsize-for-OCI-delta-sizes.patch flatpak-1.16.6/debian/patches/GHSA-jr92-2v97-wgvc/oci-Use-gsize-for-OCI-delta-sizes.patch --- flatpak-1.16.6/debian/patches/GHSA-jr92-2v97-wgvc/oci-Use-gsize-for-OCI-delta-sizes.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-jr92-2v97-wgvc/oci-Use-gsize-for-OCI-delta-sizes.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,76 @@ +From: Sebastian Wick +Date: Mon, 29 Jun 2026 22:42:30 +0200 +Subject: oci: Use gsize for OCI delta sizes + +The delta varint parser decoded into guint64 values which were then +passed to GLib I/O and allocation functions that take gsize. On 32-bit +systems where gsize is 32 bits this silently truncated the values. + +Change the varint output and all delta operation size parameters to +gsize, and clamp the parsed value to G_MAXSIZE. + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-jr92-2v97-wgvc +Origin: upstream, 1.18.1 +--- + common/flatpak-oci-registry.c | 13 ++++++++----- + 1 file changed, 8 insertions(+), 5 deletions(-) + +diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c +index 942bb47..8490b60 100644 +--- a/common/flatpak-oci-registry.c ++++ b/common/flatpak-oci-registry.c +@@ -1731,7 +1731,7 @@ delta_read_byte (GInputStream *in, + + static gboolean + delta_read_varuint (GInputStream *in, +- guint64 *out, ++ gsize *out, + GCancellable *cancellable, + GError **error) + { +@@ -1755,6 +1755,9 @@ delta_read_varuint (GInputStream *in, + } + while (more_data); + ++ if (res > G_MAXSIZE) ++ return flatpak_fail (error, _("Invalid delta file format")); ++ + *out = res; + return TRUE; + } +@@ -1762,7 +1765,7 @@ delta_read_varuint (GInputStream *in, + static gboolean + delta_copy_data (GInputStream *in, + GOutputStream *out, +- guint64 size, ++ gsize size, + guchar *buffer, + GCancellable *cancellable, + GError **error) +@@ -1790,7 +1793,7 @@ static gboolean + delta_add_data (GInputStream *in1, + GInputStream *in2, + GOutputStream *out, +- guint64 size, ++ gsize size, + guchar *buffer1, + guchar *buffer2, + GCancellable *cancellable, +@@ -1823,7 +1826,7 @@ delta_add_data (GInputStream *in1, + + static guchar * + delta_read_data (GInputStream *in, +- guint64 size, ++ gsize size, + GCancellable *cancellable, + GError **error) + { +@@ -1924,7 +1927,7 @@ flatpak_oci_registry_apply_delta_stream (FlatpakOciRegistry *self, + while (TRUE) + { + guint8 op; +- guint64 size; ++ gsize size; + g_autofree char *path = NULL; + g_autofree char *clean_path = NULL; + g_autoptr(GError) local_error = NULL; diff -Nru flatpak-1.16.6/debian/patches/GHSA-q4gr-vc25-57m5/dir-Fall-back-to-the-deployed-ref-commit-when-up-downgrad.patch flatpak-1.16.6/debian/patches/GHSA-q4gr-vc25-57m5/dir-Fall-back-to-the-deployed-ref-commit-when-up-downgrad.patch --- flatpak-1.16.6/debian/patches/GHSA-q4gr-vc25-57m5/dir-Fall-back-to-the-deployed-ref-commit-when-up-downgrad.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-q4gr-vc25-57m5/dir-Fall-back-to-the-deployed-ref-commit-when-up-downgrad.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,36 @@ +From: Sebastian Wick +Date: Mon, 3 Aug 2026 18:17:00 +0200 +Subject: dir: Fall back to the deployed ref commit when up/downgrading + +Otherwise, if an unprivileged user manages to remove the remote ref, the +downgrading protection is not in effect. + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-q4gr-vc25-57m5 +Origin: upstream, 1.18.1 +--- + common/flatpak-dir.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index 4378212..400ad3e 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -6384,6 +6384,18 @@ flatpak_dir_pull_untrusted_local (FlatpakDir *self, + ¤t_checksum, NULL, error)) + return FALSE; + ++ /* The remote tracking ref may be absent (e.g. the remote was removed and ++ * re-added, the app was installed from a bundle, or the ref was deleted by ++ * ostree-prune/flatpak-repair). Fall back to the deploy/ ref that flatpak ++ * always writes on every successful deploy so that our downgrade check can ++ * work. */ ++ if (current_checksum == NULL) ++ { ++ g_autofree char *deploy_ref = g_strconcat ("deploy/", ref, NULL); ++ flatpak_repo_resolve_rev (self->repo, NULL, NULL, deploy_ref, TRUE, ++ ¤t_checksum, NULL, NULL); ++ } ++ + if (current_checksum != NULL && + !ostree_repo_load_commit (self->repo, current_checksum, &old_commit, NULL, error)) + return FALSE; diff -Nru flatpak-1.16.6/debian/patches/GHSA-q4gr-vc25-57m5/system-dir-Prevent-removing-of-deployed-refs-via-the-syst.patch flatpak-1.16.6/debian/patches/GHSA-q4gr-vc25-57m5/system-dir-Prevent-removing-of-deployed-refs-via-the-syst.patch --- flatpak-1.16.6/debian/patches/GHSA-q4gr-vc25-57m5/system-dir-Prevent-removing-of-deployed-refs-via-the-syst.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-q4gr-vc25-57m5/system-dir-Prevent-removing-of-deployed-refs-via-the-syst.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,193 @@ +From: Sebastian Wick +Date: Mon, 3 Aug 2026 19:20:01 +0200 +Subject: system-dir: Prevent removing of deployed refs via the system-helper + +We have two use cases for removing refs: uninstalling, and pruning of +undeployed refs. Pruning undeployed refs is something we want anyone to +be able to do, because they can also pull updates and then not deploy +them. Uninstalling arbitrary refs on the other hand is problematic, and +its possible to remove the AppStream ref, and a deployed ref. + +So we split removing a ref into a function which removes any ref, and +use it internally to implement e.g. uninstalling, and a function to +remove refs which are safe to remove. Only the latter one will escalate +through the system helper, making it harder for unprivileged users to +get into a state which can be exploited. + +See the previous commit for such a state. + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-q4gr-vc25-57m5 +Origin: upstream, 1.18.1 +--- + common/flatpak-dir-private.h | 5 +++ + common/flatpak-dir.c | 72 +++++++++++++++++++++++++++-------- + common/flatpak-installation.c | 8 ++-- + system-helper/flatpak-system-helper.c | 2 +- + 4 files changed, 68 insertions(+), 19 deletions(-) + +diff --git a/common/flatpak-dir-private.h b/common/flatpak-dir-private.h +index 1e25cb1..50793b2 100644 +--- a/common/flatpak-dir-private.h ++++ b/common/flatpak-dir-private.h +@@ -772,6 +772,11 @@ gboolean flatpak_dir_remove_ref (Fla + const char *ref, + GCancellable *cancellable, + GError **error); ++gboolean flatpak_dir_remove_undeployed_ref (FlatpakDir *self, ++ const char *remote_name, ++ const char *ref, ++ GCancellable *cancellable, ++ GError **error); + gboolean flatpak_dir_update_exports (FlatpakDir *self, + const char *app, + GCancellable *cancellable, +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index 400ad3e..f4c3d27 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -11420,25 +11420,61 @@ flatpak_dir_undeploy_all (FlatpakDir *self, + * flatpak_dir_remove_ref: + * @self: a #FlatpakDir + * @remote_name: the name of the remote +- * @ref: the flatpak ref to remove ++ * @ref: the ref to remove + * @cancellable: (nullable) (optional): a #GCancellable + * @error: a #GError + * +- * Remove the flatpak ref given by @remote_name:@ref from the underlying +- * OSTree repo. Attempting to remove a ref that is currently deployed +- * is an error, you need to uninstall the flatpak first. Note that this does +- * not remove the objects bound to @ref from the disk, you will need to +- * call flatpak_dir_prune() to do that. ++ * Remove the ref given by @remote_name:@ref from the underlying ++ * OSTree repo. Note that this does not remove the objects bound to ++ * @ref from the disk, you will need to call flatpak_dir_prune() ++ * to do that. + * + * Returns: %TRUE if removing the ref succeeded, %FALSE otherwise. + */ + gboolean + flatpak_dir_remove_ref (FlatpakDir *self, + const char *remote_name, +- const char *ref, /* NOTE: Not necessarily a app/runtime ref */ ++ const char *ref, + GCancellable *cancellable, + GError **error) + { ++ if (!ostree_repo_set_ref_immediate (self->repo, ++ remote_name, ++ ref, ++ NULL, ++ cancellable, ++ error)) ++ return FALSE; ++ ++ return TRUE; ++} ++ ++/** ++ * flatpak_dir_remove_undeployed_ref: ++ * @self: a #FlatpakDir ++ * @remote_name: the name of the remote ++ * @ref: the flatpak ref to remove ++ * @cancellable: (nullable) (optional): a #GCancellable ++ * @error: a #GError ++ * ++ * Remove the flatpak ref given by @remote_name:@ref from the underlying ++ * OSTree repo. The ref must be a valid app or runtime ref, and it must ++ * not be currently deployed. Note that this does not remove the objects ++ * bound to @ref from the disk, you will need to call ++ * flatpak_dir_prune() to do that. ++ * ++ * Returns: %TRUE if removing the ref succeeded, %FALSE otherwise. ++ */ ++gboolean ++flatpak_dir_remove_undeployed_ref (FlatpakDir *self, ++ const char *remote_name, ++ const char *ref, ++ GCancellable *cancellable, ++ GError **error) ++{ ++ g_autoptr(FlatpakDecomposed) decomposed = NULL; ++ g_autoptr(GBytes) deploy_data = NULL; ++ + if (flatpak_dir_use_system_helper (self, NULL)) + { + const char *installation = flatpak_dir_get_id (self); +@@ -11455,15 +11491,21 @@ flatpak_dir_remove_ref (FlatpakDir *self, + return TRUE; + } + +- if (!ostree_repo_set_ref_immediate (self->repo, +- remote_name, +- ref, +- NULL, +- cancellable, +- error)) ++ decomposed = flatpak_decomposed_new_from_ref (ref, error); ++ if (decomposed == NULL) + return FALSE; + +- return TRUE; ++ deploy_data = flatpak_dir_get_deploy_data (self, decomposed, ++ FLATPAK_DEPLOY_VERSION_ANY, ++ NULL, NULL); ++ if (deploy_data != NULL) ++ { ++ g_set_error (error, FLATPAK_ERROR, FLATPAK_ERROR_ALREADY_INSTALLED, ++ _("%s is currently deployed, you need to uninstall it first"), ref); ++ return FALSE; ++ } ++ ++ return flatpak_dir_remove_ref (self, remote_name, ref, cancellable, error); + } + + gboolean +@@ -13941,7 +13983,7 @@ flatpak_dir_cleanup_undeployed_refs (FlatpakDir *self, + FlatpakDecomposed *ref = g_ptr_array_index (undeployed_refs, i); + g_autofree gchar *remote = flatpak_decomposed_dup_remote (ref); + +- if (!flatpak_dir_remove_ref (self, remote, flatpak_decomposed_get_ref (ref), cancellable, error)) ++ if (!flatpak_dir_remove_undeployed_ref (self, remote, flatpak_decomposed_get_ref (ref), cancellable, error)) + return FALSE; + } + +diff --git a/common/flatpak-installation.c b/common/flatpak-installation.c +index fe5e575..2e43697 100644 +--- a/common/flatpak-installation.c ++++ b/common/flatpak-installation.c +@@ -2911,8 +2911,10 @@ flatpak_installation_list_remote_related_refs_for_installed_sync (FlatpakInstall + * @error: return location for a #GError + * + * Remove the OSTree ref given by @remote_name:@ref from the local flatpak +- * repository. The next time the underlying OSTree repo is pruned, objects +- * which were attached to that ref will be removed. This is useful if you ++ * repository. The ref must not be currently deployed; attempting to remove ++ * a deployed ref will return an error. Use flatpak_installation_uninstall_full() ++ * to remove deployed refs. The next time the underlying OSTree repo is pruned, ++ * objects which were attached to that ref will be removed. This is useful if you + * pulled a flatpak ref using flatpak_installation_install_full() and + * specified %FLATPAK_INSTALL_FLAGS_NO_DEPLOY but then decided not to + * deploy the ref later on and want to remove the local ref to prevent it +@@ -2936,7 +2938,7 @@ flatpak_installation_remove_local_ref_sync (FlatpakInstallation *self, + if (dir == NULL) + return FALSE; + +- return flatpak_dir_remove_ref (dir, remote_name, ref, cancellable, error); ++ return flatpak_dir_remove_undeployed_ref (dir, remote_name, ref, cancellable, error); + } + + /** +diff --git a/system-helper/flatpak-system-helper.c b/system-helper/flatpak-system-helper.c +index de56e84..f9ff1aa 100644 +--- a/system-helper/flatpak-system-helper.c ++++ b/system-helper/flatpak-system-helper.c +@@ -1318,7 +1318,7 @@ handle_remove_local_ref (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- if (!flatpak_dir_remove_ref (system, arg_remote, arg_ref, NULL, &error)) ++ if (!flatpak_dir_remove_undeployed_ref (system, arg_remote, arg_ref, NULL, &error)) + { + flatpak_invocation_return_error (invocation, error, "Error removing ref"); + return G_DBUS_METHOD_INVOCATION_HANDLED; diff -Nru flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-chmod.patch flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-chmod.patch --- flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-chmod.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-chmod.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,114 @@ +From: Sebastian Wick +Date: Wed, 29 Jul 2026 21:10:32 +0200 +Subject: revokefs: Avoid symlink path traversal in chmod() + +Use glnx_chaseat to ensure that all the attacker-controlled paths end up +inside the basefd directory, and then AT_SYMLINK_NOFOLLOW to make sure +that the last path component won't escape from the basefd. + +On kernels >= 6.6 we can now use fchmodat() with AT_SYMLINK_NOFOLLOW, +but on older kernels that didn't work, so if necessary fall back to +opening the file with O_NOFOLLOW and then calling fchmod() on it. + +Because this is the last syscall that used the previous (flawed) +validation mechanism, we can now remove the validation helpers and be +sure that everything is using the glnx-chaseat()-based replacements. + +[smcv: Separated from a larger commit for better reviewability] +Co-authored-by: Simon McVittie +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-qrwq-7qwx-q9rp +Origin: upstream, 1.18.1 +--- + revokefs/writer.c | 69 +++++++++++++++++-------------------------------------- + 1 file changed, 21 insertions(+), 48 deletions(-) + +diff --git a/revokefs/writer.c b/revokefs/writer.c +index f312c21..e30ade8 100644 +--- a/revokefs/writer.c ++++ b/revokefs/writer.c +@@ -290,46 +290,6 @@ chase_request_any_and_path (RevokefsRequest *request, + data_size - request->arg1, out_name); + } + +-static gboolean +-validate_path (char *path) +-{ +- char *end_segment; +- +- /* No absolute or empty paths */ +- if (*path == '/' || *path == 0) +- return FALSE; +- +- while (*path != 0) +- { +- end_segment = strchr (path, '/'); +- if (end_segment == NULL) +- end_segment = path + strlen (path); +- +- if (strncmp (path, "..", 2) == 0) +- return FALSE; +- +- path = end_segment; +- while (*path == '/') +- path++; +- } +- +- return TRUE; +-} +- +-static char * +-get_valid_path (guchar *data, size_t len) +-{ +- char *path = g_strndup ((const char *) data, len); +- +- if (!validate_path (path)) +- { +- g_printerr ("Invalid path argument %s\n", path); +- exit (1); +- } +- +- return path; +-} +- + static int + mask_mode (int mode) + { +@@ -496,17 +456,30 @@ handle_chmod (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) + { +- g_autofree char *path = get_valid_path (request->data, data_size); ++ g_autofree char *name = NULL; ++ glnx_autofd int parent_fd = chase_request_path (request, data_size, &name); ++ glnx_autofd int fd = -1; + int mode = request->arg1; + +- /* Note we can't use AT_SYMLINK_NOFOLLOW yet; +- * https://marc.info/?l=linux-kernel&m=148830147803162&w=2 +- * https://marc.info/?l=linux-fsdevel&m=149193779929561&w=2 +- */ +- if (fchmodat (basefd, path, mask_mode (mode), 0) != 0) +- response->result = -errno; ++ if (fchmodat (parent_fd, name, mask_mode (mode), AT_SYMLINK_NOFOLLOW) == 0) ++ { ++ response->result = 0; ++ } ++ else if (errno == ENOTSUP) ++ { ++ /* Linux < 6.6 didn't implement fchmodat() with AT_SYMLINK_NOFOLLOW */ ++ fd = openat (parent_fd, name, O_RDONLY | O_NOFOLLOW | O_NONBLOCK | O_CLOEXEC); ++ if (fd == -1) ++ response->result = -errno; ++ else if (fchmod (fd, mask_mode (mode)) != 0) ++ response->result = -errno; ++ else ++ response->result = 0; ++ } + else +- response->result = 0; ++ { ++ response->result = -errno; ++ } + + return 0; + } diff -Nru flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-link-rename.patch flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-link-rename.patch --- flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-link-rename.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-link-rename.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,111 @@ +From: Sebastian Wick +Date: Wed, 29 Jul 2026 21:10:32 +0200 +Subject: revokefs: Avoid symlink path traversal in link(), rename() + +Again, use glnx_chaseat to ensure that all the attacker-controlled paths +end up inside the basefd directory. These two syscalls are a bit more +complicated, and take two paths. + +[smcv: Separated from a larger commit for better reviewability] +Co-authored-by: Simon McVittie +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-qrwq-7qwx-q9rp +Origin: upstream, 1.18.1 +--- + revokefs/writer.c | 57 ++++++++++++++++++++++++++++++++----------------------- + 1 file changed, 33 insertions(+), 24 deletions(-) + +diff --git a/revokefs/writer.c b/revokefs/writer.c +index a380539..2f07f2d 100644 +--- a/revokefs/writer.c ++++ b/revokefs/writer.c +@@ -256,6 +256,23 @@ chase_request_path (RevokefsRequest *request, + return chase_parent (request->data, data_size, out_name); + } + ++static void ++chase_request_2path (RevokefsRequest *request, ++ gsize data_size, ++ int *out_fd1, char **out_name1, ++ int *out_fd2, char **out_name2) ++{ ++ if (request->arg1 >= data_size) ++ { ++ g_printerr ("Invalid path1 size\n"); ++ exit (1); ++ } ++ ++ *out_fd1 = chase_parent (request->data, request->arg1, out_name1); ++ *out_fd2 = chase_parent (request->data + request->arg1, ++ data_size - request->arg1, out_name2); ++} ++ + static gboolean + validate_path (char *path) + { +@@ -319,22 +336,6 @@ get_any_path_and_valid_path (RevokefsRequest *request, + *valid_path2 = get_valid_path (request->data + request->arg1, data_size - request->arg1); + } + +-static void +-get_valid_2path (RevokefsRequest *request, +- gsize data_size, +- char **path1, +- char **path2) +-{ +- if (request->arg1 >= data_size) +- { +- g_printerr ("Invalid path1 size\n"); +- exit (1); +- } +- +- *path1 = get_valid_path (request->data, request->arg1); +- *path2 = get_valid_path (request->data + request->arg1, data_size - request->arg1); +-} +- + static ssize_t + handle_mkdir (RevokefsRequest *request, + gsize data_size, +@@ -432,12 +433,16 @@ handle_link (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) + { +- g_autofree char *from = NULL; +- g_autofree char *to = NULL; ++ g_autofree char *from_name = NULL; ++ g_autofree char *to_name = NULL; ++ glnx_autofd int from_parent_fd = -1; ++ glnx_autofd int to_parent_fd = -1; + +- get_valid_2path (request, data_size, &from, &to); ++ chase_request_2path (request, data_size, ++ &from_parent_fd, &from_name, ++ &to_parent_fd, &to_name); + +- if (linkat (basefd, from, basefd, to, 0) == -1) ++ if (linkat (from_parent_fd, from_name, to_parent_fd, to_name, 0) == -1) + response->result = -errno; + else + response->result = 0; +@@ -456,14 +461,18 @@ handle_rename (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) + { +- g_autofree char *from = NULL; +- g_autofree char *to = NULL; ++ g_autofree char *from_name = NULL; ++ g_autofree char *to_name = NULL; ++ glnx_autofd int from_parent_fd = -1; ++ glnx_autofd int to_parent_fd = -1; + unsigned int flags; + +- get_valid_2path (request, data_size, &from, &to); ++ chase_request_2path (request, data_size, ++ &from_parent_fd, &from_name, ++ &to_parent_fd, &to_name); + flags = (unsigned int)request->arg2; + +- if (renameat2 (basefd, from, basefd, to, flags) == -1) ++ if (renameat2 (from_parent_fd, from_name, to_parent_fd, to_name, flags) == -1) + response->result = -errno; + else + response->result = 0; diff -Nru flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-symlink.patch flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-symlink.patch --- flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-symlink.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-symlink.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,87 @@ +From: Sebastian Wick +Date: Wed, 29 Jul 2026 21:10:32 +0200 +Subject: revokefs: Avoid symlink path traversal in symlink() + +Use glnx_chaseat to ensure that the attacker-controlled symlink name +ends up inside the basefd directory. Note that the symlink *target* is +also attacker-controlled, but it's OK for them to be able to set any +target of their choice: that can't immediately cause traversal outside +the base directory. + +[smcv: Separated from a larger commit for better reviewability] +Co-authored-by: Simon McVittie +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-qrwq-7qwx-q9rp +Origin: upstream, 1.18.1 +--- + revokefs/writer.c | 42 ++++++++++++++++++++++-------------------- + 1 file changed, 22 insertions(+), 20 deletions(-) + +diff --git a/revokefs/writer.c b/revokefs/writer.c +index 2f07f2d..f312c21 100644 +--- a/revokefs/writer.c ++++ b/revokefs/writer.c +@@ -273,6 +273,23 @@ chase_request_2path (RevokefsRequest *request, + data_size - request->arg1, out_name2); + } + ++static void ++chase_request_any_and_path (RevokefsRequest *request, ++ gsize data_size, ++ char **out_any_path, ++ int *out_fd, char **out_name) ++{ ++ if (request->arg1 >= data_size) ++ { ++ g_printerr ("Invalid path1 size\n"); ++ exit (1); ++ } ++ ++ *out_any_path = g_strndup ((const char *) request->data, request->arg1); ++ *out_fd = chase_parent (request->data + request->arg1, ++ data_size - request->arg1, out_name); ++} ++ + static gboolean + validate_path (char *path) + { +@@ -320,22 +337,6 @@ mask_mode (int mode) + return mode & ~S_ISUID & ~S_ISGID & ~(S_IWGRP | S_IWOTH); + } + +-static void +-get_any_path_and_valid_path (RevokefsRequest *request, +- gsize data_size, +- char **any_path1, +- char **valid_path2) +-{ +- if (request->arg1 >= data_size) +- { +- g_printerr ("Invalid path1 size\n"); +- exit (1); +- } +- +- *any_path1 = g_strndup ((const char *) request->data, request->arg1); +- *valid_path2 = get_valid_path (request->data + request->arg1, data_size - request->arg1); +-} +- + static ssize_t + handle_mkdir (RevokefsRequest *request, + gsize data_size, +@@ -409,12 +410,13 @@ handle_symlink (RevokefsRequest *request, + RevokefsResponse *response) + { + g_autofree char *from = NULL; +- g_autofree char *to = NULL; ++ g_autofree char *to_name = NULL; ++ glnx_autofd int to_parent_fd = -1; + +- /* from doesn't have to be a valid path, it can be absolute or whatever */ +- get_any_path_and_valid_path (request, data_size, &from, &to); ++ chase_request_any_and_path (request, data_size, ++ &from, &to_parent_fd, &to_name); + +- if (symlinkat (from, basefd, to) == -1) ++ if (symlinkat (from, to_parent_fd, to_name) == -1) + response->result = -errno; + else + response->result = 0; diff -Nru flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-out-of-basefd-in-si.patch flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-out-of-basefd-in-si.patch --- flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-out-of-basefd-in-si.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-out-of-basefd-in-si.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,219 @@ +From: Sebastian Wick +Date: Wed, 29 Jul 2026 21:10:32 +0200 +Subject: revokefs: Avoid symlink path traversal out of basefd in simple cases + +Use glnx_chaseat to ensure that all the attacker-controlled paths end up +inside the basefd directory. + +This requires some new helper functions, which will be used in more +complicated syscalls' implementations in subsequent commits. + +[smcv: Separated from a larger commit for better reviewability] +Co-authored-by: Simon McVittie +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-qrwq-7qwx-q9rp +Origin: upstream, 1.18.1 +--- + revokefs/writer.c | 106 +++++++++++++++++++++++++++++++++++++++++++----------- + 1 file changed, 85 insertions(+), 21 deletions(-) + +diff --git a/revokefs/writer.c b/revokefs/writer.c +index 2861487..a380539 100644 +--- a/revokefs/writer.c ++++ b/revokefs/writer.c +@@ -198,6 +198,64 @@ request_path_path (int writer_socket, RevokefsOps op, const char *path1, const c + return request_path_data (writer_socket, op, path1, path2, strlen(path2), 0); + } + ++/* Return an fd to the parent directory of path guaranteed to be under ++ * basefd, and set *out_name to the final path component. */ ++static int ++chase_parent (guchar *data, size_t len, char **out_name) ++{ ++ g_autofree char *path = g_strndup ((const char *) data, len); ++ g_autofree char *parent = NULL; ++ g_autofree char *name = NULL; ++ const char *last_slash; ++ int fd; ++ ++ if (*path == '/' || *path == 0) ++ { ++ g_printerr ("Invalid path: %s\n", path); ++ exit (1); ++ } ++ ++ last_slash = strrchr (path, '/'); ++ if (last_slash) ++ { ++ parent = g_strndup (path, last_slash - path); ++ name = g_strdup (last_slash + 1); ++ } ++ else ++ { ++ parent = g_strdup ("."); ++ name = g_strdup (path); ++ } ++ ++ if (name[0] == 0 || strcmp (name, ".") == 0 || strcmp (name, "..") == 0) ++ { ++ g_printerr ("Invalid path: %s\n", path); ++ exit (1); ++ } ++ ++ fd = glnx_chaseat (basefd, parent, ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_DIRECTORY, ++ NULL); ++ if (fd < 0) ++ { ++ g_printerr ("Path escapes base directory: %s\n", path); ++ exit (1); ++ } ++ ++ if (out_name != NULL) ++ *out_name = g_steal_pointer (&name); ++ return fd; ++} ++ ++static int ++chase_request_path (RevokefsRequest *request, ++ gsize data_size, ++ char **out_name) ++{ ++ return chase_parent (request->data, data_size, out_name); ++} ++ + static gboolean + validate_path (char *path) + { +@@ -282,10 +340,11 @@ handle_mkdir (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) + { +- g_autofree char *path = get_valid_path (request->data, data_size); ++ g_autofree char *name = NULL; ++ glnx_autofd int parent_fd = chase_request_path (request, data_size, &name); + int mode = request->arg1; + +- if (mkdirat (basefd, path, mask_mode (mode)) == -1) ++ if (mkdirat (parent_fd, name, mask_mode (mode)) == -1) + response->result = -errno; + else + response->result = 0; +@@ -304,9 +363,10 @@ handle_rmdir (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) + { +- g_autofree char *path = get_valid_path (request->data, data_size); ++ g_autofree char *name = NULL; ++ glnx_autofd int parent_fd = chase_request_path (request, data_size, &name); + +- if (unlinkat (basefd, path, AT_REMOVEDIR) == -1) ++ if (unlinkat (parent_fd, name, AT_REMOVEDIR) == -1) + response->result = -errno; + else + response->result = 0; +@@ -325,9 +385,10 @@ handle_unlink (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) + { +- g_autofree char *path = get_valid_path (request->data, data_size); ++ g_autofree char *name = NULL; ++ glnx_autofd int parent_fd = chase_request_path (request, data_size, &name); + +- if (unlinkat (basefd, path, 0) == -1) ++ if (unlinkat (parent_fd, name, 0) == -1) + response->result = -errno; + else + response->result = 0; +@@ -450,11 +511,12 @@ handle_chown (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) + { +- g_autofree char *path = get_valid_path (request->data, data_size); ++ g_autofree char *name = NULL; ++ glnx_autofd int parent_fd = chase_request_path (request, data_size, &name); + uid_t uid = request->arg1; + gid_t gid = request->arg2; + +- if (fchownat (basefd, path, uid, gid, AT_SYMLINK_NOFOLLOW) != 0) ++ if (fchownat (parent_fd, name, uid, gid, AT_SYMLINK_NOFOLLOW) != 0) + response->result = -errno; + else + response->result = 0; +@@ -473,10 +535,12 @@ handle_truncate (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) + { +- g_autofree char *path = get_valid_path (request->data, data_size); ++ g_autofree char *name = NULL; ++ glnx_autofd int parent_fd = chase_request_path (request, data_size, &name); + off_t size = request->arg1; ++ glnx_autofd int fd = -1; + +- glnx_autofd int fd = openat (basefd, path, O_NOFOLLOW|O_WRONLY); ++ fd = openat (parent_fd, name, O_NOFOLLOW | O_WRONLY); + if (fd == -1) + response->result = -errno; + else +@@ -501,7 +565,8 @@ handle_utimens (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) + { +- g_autofree char *path = NULL; ++ g_autofree char *name = NULL; ++ glnx_autofd int parent_fd = -1; + struct timespec *tv; + + if (request->arg1 + sizeof (struct timespec) * 2 != data_size) +@@ -510,10 +575,10 @@ handle_utimens (RevokefsRequest *request, + exit (1); + } + +- path = get_valid_path (request->data, request->arg1); ++ parent_fd = chase_request_path (request, request->arg1, &name); + tv = (struct timespec *)(request->data + request->arg1); + +- if (utimensat (basefd, path, tv, AT_SYMLINK_NOFOLLOW) == -1) ++ if (utimensat (parent_fd, name, tv, AT_SYMLINK_NOFOLLOW) == -1) + response->result = -errno; + else + response->result = 0; +@@ -533,16 +598,14 @@ handle_open (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) + { +- g_autofree char *path = get_valid_path (request->data, data_size); ++ g_autofree char *name = NULL; ++ glnx_autofd int parent_fd = chase_request_path (request, data_size, &name); + int mode = request->arg1; + int flags = request->arg2; + int fd; + +- +- /* We need to specially handle O_TRUNC. Also, Fuse should have already +- * resolved symlinks, but use O_NOFOLLOW to be safe to avoid following +- * symlinks to some other filesystem. */ +- fd = openat (basefd, path, (flags & ~O_TRUNC) | O_NOFOLLOW, mask_mode (mode)); ++ /* We handle O_TRUNC separately, a bit later */ ++ fd = openat (parent_fd, name, (flags & ~O_TRUNC) | O_NOFOLLOW, mask_mode (mode)); + if (fd == -1) + response->result = -errno; + else +@@ -747,14 +810,15 @@ handle_access (RevokefsRequest *request, + gsize data_size, + RevokefsResponse *response) + { +- g_autofree char *path = get_valid_path (request->data, data_size); ++ g_autofree char *name = NULL; ++ glnx_autofd int parent_fd = chase_request_path (request, data_size, &name); + int mode = request->arg1; + + /* Apparently at least GNU coreutils rm calls `faccessat(W_OK)` + * before trying to do an unlink. So...we'll just lie about + * writable access here. + */ +- if (faccessat (basefd, path, mode, AT_SYMLINK_NOFOLLOW) == -1) ++ if (faccessat (parent_fd, name, mode, AT_SYMLINK_NOFOLLOW) == -1) + response->result = -errno; + else + response->result = 0; diff -Nru flatpak-1.16.6/debian/patches/GHSA-r7hp-698j-2h6c/run-dbus-Correct-broadcast-rules-for-the-AT-SPI-bus.patch flatpak-1.16.6/debian/patches/GHSA-r7hp-698j-2h6c/run-dbus-Correct-broadcast-rules-for-the-AT-SPI-bus.patch --- flatpak-1.16.6/debian/patches/GHSA-r7hp-698j-2h6c/run-dbus-Correct-broadcast-rules-for-the-AT-SPI-bus.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-r7hp-698j-2h6c/run-dbus-Correct-broadcast-rules-for-the-AT-SPI-bus.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,34 @@ +From: Simon McVittie +Date: Sun, 26 Jul 2026 15:23:32 +0100 +Subject: run-dbus: Correct --broadcast rules for the AT-SPI bus + +These had the *INTERFACE*.*METHOD* where the bus name should have been, +and a blank interface/method (meaning match any interface/method). +Because there's no bus name of that name on the AT-SPI bus, the only reason +why Flatpak apps were able to receive these broadcasts is that there was +a vulnerability in xdg-dbus-proxy, tracked as GHSA-r7hp-698j-2h6c. To +avoid regressions in Flatpak when the x-d-p vulnerability is fixed, +we need to correct these rules to have the intended bus name. + +Signed-off-by: Simon McVittie +Bug: https://github.com/flatpak/xdg-dbus-proxy/security/advisories/GHSA-r7hp-698j-2h6c +Origin: upstream, 1.18.1 +--- + common/flatpak-run-dbus.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/common/flatpak-run-dbus.c b/common/flatpak-run-dbus.c +index 20f0231..1d60c14 100644 +--- a/common/flatpak-run-dbus.c ++++ b/common/flatpak-run-dbus.c +@@ -451,8 +451,8 @@ flatpak_run_add_a11y_dbus_args (FlatpakBwrap *app_bwrap, + flatpak_bwrap_add_args (proxy_arg_bwrap, + a11y_address, + proxy_socket, "--filter", "--sloppy-names", +- "--broadcast=org.a11y.atspi.Registry.EventListenerRegistered=@/org/a11y/atspi/registry", +- "--broadcast=org.a11y.atspi.Registry.EventListenerDeregistered=@/org/a11y/atspi/registry", ++ "--broadcast=org.a11y.atspi.Registry=org.a11y.atspi.Registry.EventListenerRegistered@/org/a11y/atspi/registry", ++ "--broadcast=org.a11y.atspi.Registry=org.a11y.atspi.Registry.EventListenerDeregistered@/org/a11y/atspi/registry", + "--call=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Embed@/org/a11y/atspi/accessible/root", + "--call=org.a11y.atspi.Registry=org.a11y.atspi.Socket.Unembed@/org/a11y/atspi/accessible/root", + "--call=org.a11y.atspi.Registry=org.a11y.atspi.Registry.GetRegisteredEvents@/org/a11y/atspi/registry", diff -Nru flatpak-1.16.6/debian/patches/GHSA-v2gw-v9h5-9q4x/ref-utils-Make-remote-name-validation-public-and-take-an-.patch flatpak-1.16.6/debian/patches/GHSA-v2gw-v9h5-9q4x/ref-utils-Make-remote-name-validation-public-and-take-an-.patch --- flatpak-1.16.6/debian/patches/GHSA-v2gw-v9h5-9q4x/ref-utils-Make-remote-name-validation-public-and-take-an-.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-v2gw-v9h5-9q4x/ref-utils-Make-remote-name-validation-public-and-take-an-.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,96 @@ +From: Sebastian Wick +Date: Mon, 3 Aug 2026 22:04:03 +0200 +Subject: ref-utils: Make remote name validation public and take an error + +We should be consistent with the validation in the session-helper, so +let's make this validation function available to it. + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-v2gw-v9h5-9q4x +Origin: upstream, 1.18.1 +--- + common/flatpak-ref-utils-private.h | 3 +++ + common/flatpak-ref-utils.c | 43 ++++++++++++++++++++++++++------------ + 2 files changed, 33 insertions(+), 13 deletions(-) + +diff --git a/common/flatpak-ref-utils-private.h b/common/flatpak-ref-utils-private.h +index 74d91bd..7f3e363 100644 +--- a/common/flatpak-ref-utils-private.h ++++ b/common/flatpak-ref-utils-private.h +@@ -33,6 +33,9 @@ gboolean flatpak_is_valid_branch (const char *string, + gboolean flatpak_is_valid_arch (const char *string, + gssize len, + GError **error); ++gboolean flatpak_is_valid_remote_name (const char *string, ++ gssize len, ++ GError **error); + gboolean flatpak_has_name_prefix (const char *string, + const char *name); + gboolean flatpak_name_matches_one_wildcard_prefix (const char *string, +diff --git a/common/flatpak-ref-utils.c b/common/flatpak-ref-utils.c +index 3e3b015..aabc8f5 100644 +--- a/common/flatpak-ref-utils.c ++++ b/common/flatpak-ref-utils.c +@@ -567,25 +567,42 @@ is_valid_remote_name_character (gint c) + c == '.'; + } + +-static gboolean +-is_valid_remote_name (const char *remote, +- gsize len) ++gboolean ++flatpak_is_valid_remote_name (const char *string, ++ gssize len, ++ GError **error) + { + const char *end; + +- if (len == 0) +- return FALSE; ++ if (len < 0) ++ len = strlen (string); + +- end = remote + len; ++ if (G_UNLIKELY (len == 0)) ++ { ++ flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, ++ _("Remote name can't be empty")); ++ return FALSE; ++ } + +- if (!is_valid_initial_remote_name_character (*remote++)) +- return FALSE; ++ end = string + len; + +- while (remote < end) ++ if (G_UNLIKELY (!is_valid_initial_remote_name_character (*string))) + { +- char c = *remote++; +- if (!is_valid_remote_name_character (c)) +- return FALSE; ++ flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, ++ _("Remote name can't start with %c"), *string); ++ return FALSE; ++ } ++ string++; ++ ++ while (string < end) ++ { ++ if (G_UNLIKELY (!is_valid_remote_name_character (*string))) ++ { ++ flatpak_fail_error (error, FLATPAK_ERROR_INVALID_NAME, ++ _("Remote name can't contain %c"), *string); ++ return FALSE; ++ } ++ string++; + } + + return TRUE; +@@ -622,7 +639,7 @@ _flatpak_decomposed_new (char *ref, + const char *colon = strchr (p, ':'); + if (colon != NULL) + { +- if (!is_valid_remote_name (ref, colon - ref)) ++ if (!flatpak_is_valid_remote_name (ref, colon - ref, NULL)) + { + flatpak_fail_error (error, FLATPAK_ERROR_INVALID_REF, _("Invalid remote name")); + return NULL; diff -Nru flatpak-1.16.6/debian/patches/GHSA-v2gw-v9h5-9q4x/system-helper-Validate-each-remote-name-and-architecture-.patch flatpak-1.16.6/debian/patches/GHSA-v2gw-v9h5-9q4x/system-helper-Validate-each-remote-name-and-architecture-.patch --- flatpak-1.16.6/debian/patches/GHSA-v2gw-v9h5-9q4x/system-helper-Validate-each-remote-name-and-architecture-.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-v2gw-v9h5-9q4x/system-helper-Validate-each-remote-name-and-architecture-.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,139 @@ +From: Sebastian Wick +Date: Sun, 9 Aug 2026 12:10:11 +0100 +Subject: system-helper: Validate each remote name and architecture argument + +We pass them on to internal functions which assume that they are valid, +and specifically also create paths which contain those strings which can +be used for path traversal attacks. + +Let's simply consistently validate all of those arguments. + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-v2gw-v9h5-9q4x +[backported to 1.18.x: previous incomplete validity checks weren't on this branch] +Origin: upstream, 1.18.1 +--- + system-helper/flatpak-system-helper.c | 59 +++++++++++++++++++++++++---------- + 1 file changed, 43 insertions(+), 16 deletions(-) + +diff --git a/system-helper/flatpak-system-helper.c b/system-helper/flatpak-system-helper.c +index f709f94..de56e84 100644 +--- a/system-helper/flatpak-system-helper.c ++++ b/system-helper/flatpak-system-helper.c +@@ -432,6 +432,19 @@ handle_deploy (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + ++ ref = flatpak_decomposed_new_from_ref (arg_ref, &error); ++ if (ref == NULL) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ if (!flatpak_is_valid_remote_name (arg_origin, -1, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ + if (strlen (arg_repo_path) > 0) + { + g_autoptr(GError) local_error = NULL; +@@ -475,13 +488,6 @@ handle_deploy (FlatpakSystemHelper *object, + } + } + +- ref = flatpak_decomposed_new_from_ref (arg_ref, &error); +- if (ref == NULL) +- { +- g_dbus_method_invocation_return_gerror (invocation, error); +- return G_DBUS_METHOD_INVOCATION_HANDLED; +- } +- + no_deploy = (arg_flags & FLATPAK_HELPER_DEPLOY_FLAGS_NO_DEPLOY) != 0; + local_pull = (arg_flags & FLATPAK_HELPER_DEPLOY_FLAGS_LOCAL_PULL) != 0; + reinstall = (arg_flags & FLATPAK_HELPER_DEPLOY_FLAGS_REINSTALL) != 0; +@@ -783,6 +789,18 @@ handle_deploy_appstream (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + ++ if (!flatpak_is_valid_remote_name (arg_origin, -1, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ if (!flatpak_is_valid_arch (arg_arch, -1, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ + if (strlen (arg_repo_path) > 0) + { + g_autoptr(GFile) repo_file = g_file_new_for_path (arg_repo_path); +@@ -1012,6 +1030,12 @@ handle_install_bundle (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + ++ if (!flatpak_is_valid_remote_name (arg_remote, -1, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ + if (!g_file_query_exists (bundle_file, NULL)) + { + g_dbus_method_invocation_return_error (invocation, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, +@@ -1057,10 +1081,9 @@ handle_configure_remote (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- if (*arg_remote == 0 || strchr (arg_remote, '/') != NULL) ++ if (!flatpak_is_valid_remote_name (arg_remote, -1, &error)) + { +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, +- "Invalid remote name: %s", arg_remote); ++ g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +@@ -1198,10 +1221,9 @@ handle_update_remote (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- if (*arg_remote == 0 || strchr (arg_remote, '/') != NULL) ++ if (!flatpak_is_valid_remote_name (arg_remote, -1, &error)) + { +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, +- "Invalid remote name: %s", arg_remote); ++ g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +@@ -1284,10 +1306,9 @@ handle_remove_local_ref (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- if (*arg_remote == 0 || strchr (arg_remote, '/') != NULL) ++ if (!flatpak_is_valid_remote_name (arg_remote, -1, &error)) + { +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, +- "Invalid remote name: %s", arg_remote); ++ g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +@@ -1851,6 +1872,12 @@ handle_generate_oci_summary (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + ++ if (!flatpak_is_valid_remote_name (arg_origin, -1, &error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ + only_cached = (arg_flags & FLATPAK_HELPER_GENERATE_OCI_SUMMARY_FLAGS_ONLY_CACHED) != 0; + + if (!flatpak_dir_ensure_repo (system, NULL, &error)) diff -Nru flatpak-1.16.6/debian/patches/GHSA-v2gw-v9h5-9q4x/tests-Add-some-unit-tests-for-ref-utils-name-component-va.patch flatpak-1.16.6/debian/patches/GHSA-v2gw-v9h5-9q4x/tests-Add-some-unit-tests-for-ref-utils-name-component-va.patch --- flatpak-1.16.6/debian/patches/GHSA-v2gw-v9h5-9q4x/tests-Add-some-unit-tests-for-ref-utils-name-component-va.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-v2gw-v9h5-9q4x/tests-Add-some-unit-tests-for-ref-utils-name-component-va.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,394 @@ +From: Simon McVittie +Date: Tue, 4 Aug 2026 11:21:20 +0100 +Subject: tests: Add some unit tests for ref-utils name component validation + +In the fix for GHSA-v2gw-v9h5-9q4x these functions are used to avoid +path traversal, so we'd better assert that they do detect and avoid it. + +Signed-off-by: Simon McVittie +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-v2gw-v9h5-9q4x +Origin: upstream, 1.18.1 +--- + tests/meson.build | 1 + + tests/test-ref-utils.c | 360 +++++++++++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 361 insertions(+) + create mode 100644 tests/test-ref-utils.c + +diff --git a/tests/meson.build b/tests/meson.build +index 464ed0b..8a9db82 100644 +--- a/tests/meson.build ++++ b/tests/meson.build +@@ -127,6 +127,7 @@ c_tests = [ + portal_gdbus[1], + ], + }], ++ ['test-ref-utils', {}], + ] + + foreach testcase : c_tests +diff --git a/tests/test-ref-utils.c b/tests/test-ref-utils.c +new file mode 100644 +index 0000000..845e41f +--- /dev/null ++++ b/tests/test-ref-utils.c +@@ -0,0 +1,360 @@ ++/* vi:set et sw=2 sts=2 cin cino=t0,f0,(0,{s,>2s,n-s,^-s,e-s: ++ * Copyright © 2020-2026 Collabora Ltd. ++ * SPDX-License-Identifier: LGPL-2.1-or-later ++ */ ++ ++#include "config.h" ++ ++#include ++ ++#include "flatpak.h" ++#include "flatpak-ref-utils-private.h" ++ ++#include "tests/testlib.h" ++ ++static void ++test_valid_arch (void) ++{ ++ static const char * const good[] = ++ { ++ "i386", ++ "mips64el", ++ "x86_64", ++ }; ++ static const char * const bad[] = ++ { ++ "", ++ "--overrule-some-option", ++ ".", ++ "..", ++ "a/b", ++ "x86-64", ++ "../path_traversal", ++ "path/../traversal", ++ "path_traversal/..", ++ "\xc3\xa1", /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */ ++ "a\xc3\xa1", ++ }; ++ static const struct ++ { ++ const char *name; ++ int len; ++ } ++ initially_good[] = ++ { ++ { "abc\xc3\xa1", 3 }, ++ { "_/", 1 }, ++ }; ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (good); i++) ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_arch (good[i], -1, &local_error); ++ ++ g_test_message ("good architecture \"%s\" -> %s", ++ good[i], ok ? "OK" : local_error->message); ++ g_assert_no_error (local_error); ++ g_assert_true (ok); ++ } ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (initially_good); i++) ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_arch (initially_good[i].name, ++ initially_good[i].len, ++ &local_error); ++ ++ g_test_message ("good architecture \"%.*s\" -> %s", ++ initially_good[i].len, ++ initially_good[i].name, ++ ok ? "OK" : local_error->message); ++ g_assert_no_error (local_error); ++ g_assert_true (ok); ++ } ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (bad); i++) ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_arch (bad[i], -1, &local_error); ++ ++ g_test_message ("bad architecture \"%s\" -> %s", ++ bad[i], ok ? "wrongly accepted" : local_error->message); ++ g_assert_nonnull (local_error); ++ g_assert_false (ok); ++ } ++ ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_arch (good[0], 0, &local_error); ++ ++ g_test_message ("empty architecture -> %s", ++ ok ? "wrongly accepted" : local_error->message); ++ g_assert_nonnull (local_error); ++ g_assert_false (ok); ++ } ++} ++ ++static void ++test_valid_branch (void) ++{ ++ static const char * const good[] = ++ { ++ "stable", ++ "private-beta", ++ "public_beta", ++ "1.0", ++ "--whatever--", ++ "x", ++ }; ++ static const char * const bad[] = ++ { ++ "", ++ ".", ++ "..", ++ "a/b", ++ "../path-traversal", ++ "path-traversal/..", ++ "path/../traversal", ++ "\xc3\xa1", /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */ ++ "a\xc3\xa1", ++ }; ++ static const struct ++ { ++ const char *name; ++ int len; ++ } ++ initially_good[] = ++ { ++ { "abc\xc3\xa1", 3 }, ++ { "x/", 1 }, ++ }; ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (good); i++) ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_branch (good[i], -1, &local_error); ++ ++ g_test_message ("good branch \"%s\" -> %s", ++ good[i], ok ? "OK" : local_error->message); ++ g_assert_no_error (local_error); ++ g_assert_true (ok); ++ } ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (initially_good); i++) ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_branch (initially_good[i].name, ++ initially_good[i].len, ++ &local_error); ++ ++ g_test_message ("good branch \"%.*s\" -> %s", ++ initially_good[i].len, ++ initially_good[i].name, ++ ok ? "OK" : local_error->message); ++ g_assert_no_error (local_error); ++ g_assert_true (ok); ++ } ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (bad); i++) ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_branch (bad[i], -1, &local_error); ++ ++ g_test_message ("bad branch \"%s\" -> %s", ++ bad[i], ok ? "wrongly accepted" : local_error->message); ++ g_assert_nonnull (local_error); ++ g_assert_false (ok); ++ } ++ ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_branch (good[0], 0, &local_error); ++ ++ g_test_message ("empty branch -> %s", ++ ok ? "wrongly accepted" : local_error->message); ++ g_assert_nonnull (local_error); ++ g_assert_false (ok); ++ } ++} ++ ++#define NAME_CHAR_x16 "z12345678.abcdef" ++#define NAME_CHAR_x32 NAME_CHAR_x16 NAME_CHAR_x16 ++#define NAME_CHAR_x64 NAME_CHAR_x32 NAME_CHAR_x32 ++#define NAME_CHAR_x128 NAME_CHAR_x64 NAME_CHAR_x64 ++#define NAME_CHAR_x256 NAME_CHAR_x128 NAME_CHAR_x128 ++ ++static void ++test_valid_name (void) ++{ ++ static const char * const good[] = ++ { ++ "com.example.Foo", ++ "a.b.c", ++ "org._7_zip.Archiver", ++ "org._7_zip._7-zip", /* "-" in last element discouraged but allowed */ ++ "uk.co.pseudorandom.name.has.many.components", ++ }; ++ static const char * const bad[] = ++ { ++ "", ++ "a.b", /* not enough elements */ ++ "org.7_zip.Archiver", ++ "org._7-zip.Archiver", ++ "\xc3\xa1", /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */ ++ "a\xc3\xa1", ++ NAME_CHAR_x256, ++ }; ++ static const struct ++ { ++ const char *name; ++ int len; ++ } ++ initially_good[] = ++ { ++ { "a.b.c\xc3\xa1", 5 }, ++ { NAME_CHAR_x256, 255 }, ++ }; ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (good); i++) ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_name (good[i], -1, &local_error); ++ ++ g_test_message ("good app name \"%s\" -> %s", ++ good[i], ok ? "OK" : local_error->message); ++ g_assert_no_error (local_error); ++ g_assert_true (ok); ++ } ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (initially_good); i++) ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_name (initially_good[i].name, ++ initially_good[i].len, ++ &local_error); ++ ++ g_test_message ("good app name \"%.*s\" -> %s", ++ initially_good[i].len, ++ initially_good[i].name, ++ ok ? "OK" : local_error->message); ++ g_assert_no_error (local_error); ++ g_assert_true (ok); ++ } ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (bad); i++) ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_name (bad[i], -1, &local_error); ++ ++ g_test_message ("bad app name \"%s\" -> %s", ++ bad[i], ok ? "wrongly accepted" : local_error->message); ++ g_assert_nonnull (local_error); ++ g_assert_false (ok); ++ } ++ ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_name (good[0], 0, &local_error); ++ ++ g_test_message ("empty app name -> %s", ++ ok ? "wrongly accepted" : local_error->message); ++ g_assert_nonnull (local_error); ++ g_assert_false (ok); ++ } ++} ++ ++static void ++test_valid_remote_name (void) ++{ ++ static const char * const good[] = ++ { ++ "7zip", ++ "_abc123", ++ "a", ++ "a_b", ++ "remote-1.0", ++ }; ++ static const char * const bad[] = ++ { ++ "", ++ "--overrule-some-option", ++ ".", ++ "..", ++ "a/b", ++ "../path-traversal", ++ "path/../traversal", ++ "path-traversal/..", ++ "\xc3\xa1", /* U+00E1 LATIN SMALL LETTER A WITH ACUTE */ ++ "a\xc3\xa1", ++ }; ++ static const struct ++ { ++ const char *name; ++ int len; ++ } ++ initially_good[] = ++ { ++ { "abc\xc3\xa1", 3 }, ++ { "x/", 1 }, ++ }; ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (good); i++) ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_remote_name (good[i], -1, &local_error); ++ ++ g_test_message ("good remote name \"%s\" -> %s", ++ good[i], ok ? "OK" : local_error->message); ++ g_assert_no_error (local_error); ++ g_assert_true (ok); ++ } ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (initially_good); i++) ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_remote_name (initially_good[i].name, ++ initially_good[i].len, ++ &local_error); ++ ++ g_test_message ("good remote name \"%.*s\" -> %s", ++ initially_good[i].len, ++ initially_good[i].name, ++ ok ? "OK" : local_error->message); ++ g_assert_no_error (local_error); ++ g_assert_true (ok); ++ } ++ ++ for (size_t i = 0; i < G_N_ELEMENTS (bad); i++) ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_remote_name (bad[i], -1, &local_error); ++ ++ g_test_message ("bad remote name \"%s\" -> %s", ++ bad[i], ok ? "wrongly accepted" : local_error->message); ++ g_assert_nonnull (local_error); ++ g_assert_false (ok); ++ } ++ ++ { ++ g_autoptr(GError) local_error = NULL; ++ gboolean ok = flatpak_is_valid_remote_name (good[0], 0, &local_error); ++ ++ g_test_message ("empty remote name -> %s", ++ ok ? "wrongly accepted" : local_error->message); ++ g_assert_nonnull (local_error); ++ g_assert_false (ok); ++ } ++} ++ ++int ++main (int argc, char *argv[]) ++{ ++ g_test_init (&argc, &argv, NULL); ++ ++ g_test_add_func ("/ref-utils/valid-arch", test_valid_arch); ++ g_test_add_func ("/ref-utils/valid-branch", test_valid_branch); ++ g_test_add_func ("/ref-utils/valid-name", test_valid_name); ++ g_test_add_func ("/ref-utils/valid-remote-name", test_valid_remote_name); ++ ++ return g_test_run (); ++} diff -Nru flatpak-1.16.6/debian/patches/GHSA-w69g-9x8j-7p8f/dir-utils-Reject-path-traversal-in-extension-metadata.patch flatpak-1.16.6/debian/patches/GHSA-w69g-9x8j-7p8f/dir-utils-Reject-path-traversal-in-extension-metadata.patch --- flatpak-1.16.6/debian/patches/GHSA-w69g-9x8j-7p8f/dir-utils-Reject-path-traversal-in-extension-metadata.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-w69g-9x8j-7p8f/dir-utils-Reject-path-traversal-in-extension-metadata.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,92 @@ +From: Sebastian Wick +Date: Mon, 29 Jun 2026 21:51:46 +0200 +Subject: dir-utils: Reject path traversal in extension metadata + +Validate extension directory, subdir_suffix, add_ld_path, and +merge_dirs at parse time, rejecting absolute paths and paths with +.. components that escape the intended mount point. This prevents +extension metadata from mounting content at unintended locations +inside the sandbox. + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-w69g-9x8j-7p8f +Origin: upstream, 1.18.1 +--- + common/flatpak-dir-utils.c | 51 ++++++++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 49 insertions(+), 2 deletions(-) + +diff --git a/common/flatpak-dir-utils.c b/common/flatpak-dir-utils.c +index e34b950..807fbc5 100644 +--- a/common/flatpak-dir-utils.c ++++ b/common/flatpak-dir-utils.c +@@ -24,6 +24,7 @@ + #include "flatpak-dir-private.h" + #include "flatpak-metadata-private.h" + #include "flatpak-utils-private.h" ++#include "flatpak-utils-base-private.h" + + char ** + flatpak_list_deployed_refs (const char *type, +@@ -393,6 +394,21 @@ flatpak_extension_new (const char *id, + return ext; + } + ++static gboolean ++relative_path_has_no_traversal (const char *path) ++{ ++ if (path == NULL || *path == '\0' || *path == '/') ++ return FALSE; ++ ++ if (strcmp (path, "..") == 0 || ++ g_str_has_prefix (path, "../") || ++ strstr (path, "/../") != NULL || ++ g_str_has_suffix (path, "/..")) ++ return FALSE; ++ ++ return TRUE; ++} ++ + static GList * + add_extension (GKeyFile *metakey, + const char *group, +@@ -423,8 +439,39 @@ add_extension (GKeyFile *metakey, + g_autoptr(GFile) deploy_dir = NULL; + g_autoptr(FlatpakDir) dir = NULL; + +- if (directory == NULL) +- return res; ++ if (directory == NULL || !relative_path_has_no_traversal (directory)) ++ { ++ g_warning ("Extension %s key %s is missing or has directory traversal, ignoring", ++ extension, FLATPAK_METADATA_KEY_DIRECTORY); ++ return res; ++ } ++ ++ if (subdir_suffix != NULL && !relative_path_has_no_traversal (subdir_suffix)) ++ { ++ g_warning ("Extension %s key %s is missing or has directory traversal, ignoring", ++ extension, FLATPAK_METADATA_KEY_SUBDIRECTORY_SUFFIX); ++ return res; ++ } ++ ++ if (add_ld_path != NULL && !relative_path_has_no_traversal (add_ld_path)) ++ { ++ g_warning ("Extension %s key %s is missing or has directory traversal, ignoring", ++ extension, FLATPAK_METADATA_KEY_ADD_LD_PATH); ++ return res; ++ } ++ ++ if (merge_dirs != NULL) ++ { ++ for (size_t i = 0; merge_dirs[i] != NULL; i++) ++ { ++ if (!relative_path_has_no_traversal (merge_dirs[i])) ++ { ++ g_warning ("Extension %s key %s is missing or has directory traversal, ignoring", ++ extension, FLATPAK_METADATA_KEY_MERGE_DIRS); ++ return res; ++ } ++ } ++ } + + ref = flatpak_decomposed_new_from_parts (FLATPAK_KINDS_RUNTIME, extension, arch, branch, NULL); + if (ref == NULL) diff -Nru flatpak-1.16.6/debian/patches/GHSA-w69g-9x8j-7p8f/run-Use-RESOLVE_BENEATH-for-host-side-extension-file-acce.patch flatpak-1.16.6/debian/patches/GHSA-w69g-9x8j-7p8f/run-Use-RESOLVE_BENEATH-for-host-side-extension-file-acce.patch --- flatpak-1.16.6/debian/patches/GHSA-w69g-9x8j-7p8f/run-Use-RESOLVE_BENEATH-for-host-side-extension-file-acce.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/GHSA-w69g-9x8j-7p8f/run-Use-RESOLVE_BENEATH-for-host-side-extension-file-acce.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,106 @@ +From: Sebastian Wick +Date: Mon, 29 Jun 2026 22:10:41 +0200 +Subject: run: Use RESOLVE_BENEATH for host-side extension file access + +Extension content is controlled by the extension developer. When setting +up the sandbox, Flatpak accesses the .ref file and iterates merge_dirs +within the extension's files directory on the host using path-based +operations that follow symlinks. A malicious extension can place symlinks +that escape the extension directory, causing Flatpak's host process to +access arbitrary host paths. This discloses host directory listings to +the sandboxed application via the resulting merge symlinks. + +Use glnx_chaseat with GLNX_CHASE_RESOLVE_BENEATH to confine path +resolution to the extension's files directory. + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-w69g-9x8j-7p8f +Origin: upstream, 1.18.1 +--- + common/flatpak-run.c | 48 +++++++++++++++++++++++++++++++++++++++++------- + 1 file changed, 41 insertions(+), 7 deletions(-) + +diff --git a/common/flatpak-run.c b/common/flatpak-run.c +index 11e0748..2ba1099 100644 +--- a/common/flatpak-run.c ++++ b/common/flatpak-run.c +@@ -149,7 +149,8 @@ flatpak_run_add_extension_args (FlatpakBwrap *bwrap, + g_autofree char *directory = g_build_filename (target_path, ext->directory, NULL); + g_autofree char *full_directory = g_build_filename (directory, ext->subdir_suffix, NULL); + g_autofree char *ref_file = g_build_filename (full_directory, ".ref", NULL); +- g_autofree char *real_ref = g_build_filename (ext->files_path, ext->directory, ".ref", NULL); ++ g_autofree char *ref_subpath = g_build_filename (ext->directory, ".ref", NULL); ++ glnx_autofd int ext_files_dfd = -1; + + if (ext->needs_tmpfs) + { +@@ -168,10 +169,16 @@ flatpak_run_add_extension_args (FlatpakBwrap *bwrap, + "--ro-bind", ext->files_path, full_directory, + NULL); + +- if (g_file_test (real_ref, G_FILE_TEST_EXISTS)) +- flatpak_bwrap_add_args (bwrap, +- "--lock-file", ref_file, +- NULL); ++ if (glnx_opendirat (AT_FDCWD, ext->files_path, FALSE, &ext_files_dfd, NULL)) ++ { ++ glnx_autofd int ref_fd = glnx_chaseat (ext_files_dfd, ref_subpath, ++ GLNX_CHASE_RESOLVE_BENEATH, ++ NULL); ++ if (ref_fd >= 0) ++ flatpak_bwrap_add_args (bwrap, ++ "--lock-file", ref_file, ++ NULL); ++ } + } + + g_list_free (path_sorted_extensions); +@@ -183,8 +190,16 @@ flatpak_run_add_extension_args (FlatpakBwrap *bwrap, + FlatpakExtension *ext = l->data; + g_autofree char *directory = g_build_filename (target_path, ext->directory, NULL); + g_autofree char *full_directory = g_build_filename (directory, ext->subdir_suffix, NULL); ++ glnx_autofd int files_dfd = -1; + int i; + ++ if (!glnx_opendirat (AT_FDCWD, ext->files_path, FALSE, &files_dfd, NULL)) ++ { ++ return flatpak_fail_error (error, FLATPAK_ERROR, ++ "Failed to open extension %s files", ++ ext->installed_id); ++ } ++ + if (used_extensions->len > 0) + g_string_append (used_extensions, ";"); + g_string_append (used_extensions, ext->installed_id); +@@ -221,11 +236,30 @@ flatpak_run_add_extension_args (FlatpakBwrap *bwrap, + { + g_autofree char *parent = g_path_get_dirname (directory); + g_autofree char *merge_dir = g_build_filename (parent, ext->merge_dirs[i], NULL); +- g_autofree char *source_dir = g_build_filename (ext->files_path, ext->merge_dirs[i], NULL); ++ glnx_autofd int source_dfd = -1; ++ glnx_autofd int source_read_dfd = -1; + g_auto(GLnxDirFdIterator) source_iter = { 0 }; + struct dirent *dent; + +- if (glnx_dirfd_iterator_init_at (AT_FDCWD, source_dir, TRUE, &source_iter, NULL)) ++ source_dfd = glnx_chaseat (files_dfd, ext->merge_dirs[i], ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_DIRECTORY, ++ NULL); ++ if (source_dfd < 0) ++ { ++ if (errno == ENOENT || errno == ENOTDIR) ++ continue; ++ ++ return flatpak_fail_error (error, FLATPAK_ERROR, ++ "Extension %s has invalid merge-dirs", ++ ext->installed_id); ++ } ++ ++ source_read_dfd = glnx_fd_reopen (source_dfd, O_RDONLY, error); ++ if (source_read_dfd < 0) ++ return FALSE; ++ ++ if (glnx_dirfd_iterator_init_take_fd (&source_read_dfd, &source_iter, NULL)) + { + while (glnx_dirfd_iterator_next_dent (&source_iter, &dent, NULL, NULL) && dent != NULL) + { diff -Nru flatpak-1.16.6/debian/patches/bwrap-Clarify-a-comment.patch flatpak-1.16.6/debian/patches/bwrap-Clarify-a-comment.patch --- flatpak-1.16.6/debian/patches/bwrap-Clarify-a-comment.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/bwrap-Clarify-a-comment.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,29 @@ +From: Simon McVittie +Date: Sat, 11 Apr 2026 16:59:10 +0100 +Subject: bwrap: Clarify a comment + +Now that we're passing the app's /app and /usr down to bwrap as O_PATH +file descriptors, it will be even more common to have non-seekable fds +in the array. + +Signed-off-by: Simon McVittie +(cherry picked from commit dc9173b2d330f3ac411b135f261e81551db05f3f) +Origin: upstream, 1.16.7, commit:57a5f51199ea8f35e1236857d962e4ac6cbe2a57 +--- + common/flatpak-bwrap.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/common/flatpak-bwrap.c b/common/flatpak-bwrap.c +index a0eddfd..acec2ab 100644 +--- a/common/flatpak-bwrap.c ++++ b/common/flatpak-bwrap.c +@@ -533,7 +533,8 @@ flatpak_bwrap_child_setup (GArray *fd_array, + us use the same fd_array multiple times */ + if (lseek (fd, 0, SEEK_SET) < 0) + { +- /* Ignore the error, this happens on e.g. pipe fds */ ++ /* Ignore the error, not all fds are seekable ++ * (for example pipes and O_PATH fds are not) */ + } + + fcntl (fd, F_SETFD, 0); diff -Nru flatpak-1.16.6/debian/patches/dir-Use-flatpak_bwrap_child_setup_inherit_fds_cb-to-apply.patch flatpak-1.16.6/debian/patches/dir-Use-flatpak_bwrap_child_setup_inherit_fds_cb-to-apply.patch --- flatpak-1.16.6/debian/patches/dir-Use-flatpak_bwrap_child_setup_inherit_fds_cb-to-apply.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/dir-Use-flatpak_bwrap_child_setup_inherit_fds_cb-to-apply.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,64 @@ +From: Simon McVittie +Date: Sat, 11 Apr 2026 17:06:03 +0100 +Subject: dir: Use flatpak_bwrap_child_setup_inherit_fds_cb() to apply + extra-data + +This is functionally equivalent to the local child_setup() deleted by +this commit, except that it ignores lseek() errors, which can +legitimately happen when inheriting a non-seekable file descriptor. +Since commit ac62ebe "run: Use O_PATH fds for the runtime and app +deploy directories", any extra-data helper that runs inside a runtime +will receive a non-seekable O_PATH fd as its /usr. + +Resolves: https://github.com/flatpak/flatpak/issues/6608 +Signed-off-by: Simon McVittie +(cherry picked from commit 333459c8835ff95b245d97ee98cffec1c9f781d9) +Origin: upstream, 1.16.7, commit:fb1ed94d3c37c072f5e501e5b02c34073081d464 +--- + common/flatpak-dir.c | 26 +------------------------- + 1 file changed, 1 insertion(+), 25 deletions(-) + +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index e381b16..6e3f891 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -8324,30 +8324,6 @@ extract_extra_data (FlatpakDir *self, + return TRUE; + } + +-static void +-child_setup (gpointer user_data) +-{ +- GArray *fd_array = user_data; +- int i; +- +- /* If no fd_array was specified, don't care. */ +- if (fd_array == NULL) +- return; +- +- /* Otherwise, mark not - close-on-exec all the fds in the array */ +- for (i = 0; i < fd_array->len; i++) +- { +- int fd = g_array_index (fd_array, int, i); +- +- /* We also seek all fds to the start, because this lets +- us use the same fd_array multiple times */ +- if (lseek (fd, 0, SEEK_SET) < 0) +- g_printerr ("lseek error in child setup"); +- +- fcntl (fd, F_SETFD, 0); +- } +-} +- + static gboolean + apply_extra_data (FlatpakDir *self, + GFile *checkoutdir, +@@ -8521,7 +8497,7 @@ apply_extra_data (FlatpakDir *self, + (char **) bwrap->argv->pdata, + bwrap->envp, + G_SPAWN_SEARCH_PATH, +- child_setup, bwrap->fds, ++ flatpak_bwrap_child_setup_inherit_fds_cb, bwrap->fds, + NULL, NULL, + &exit_status, + error)) diff -Nru flatpak-1.16.6/debian/patches/hardening/dir-Harden-confining-subpath-deploy-to-the-deploy-base.patch flatpak-1.16.6/debian/patches/hardening/dir-Harden-confining-subpath-deploy-to-the-deploy-base.patch --- flatpak-1.16.6/debian/patches/hardening/dir-Harden-confining-subpath-deploy-to-the-deploy-base.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/hardening/dir-Harden-confining-subpath-deploy-to-the-deploy-base.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,62 @@ +From: Sebastian Wick +Date: Mon, 29 Jun 2026 23:14:37 +0200 +Subject: dir: Harden confining subpath deploy to the deploy base + +Subpaths passed via D-Bus to the system helper are not validated, so +a value containing ".." components is accepted. + +The only reason why this isn't exploitable is because we resolve the +path relative to `root` via g_file_resolve_relative_path, where root is +a OstreeRepoFile which will return FALSE when it encounters ".." or +symlinks. + +Harden the code by using glnx_chase_and_mkdirat with RESOLVE_BENEATH to +create parent directories, and check out into the resulting fd. + +Origin: upstream, 1.18.1 +--- + common/flatpak-dir.c | 19 +++++++++---------- + 1 file changed, 9 insertions(+), 10 deletions(-) + +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index 6a9c192..4378212 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -8863,28 +8863,27 @@ flatpak_dir_deploy (FlatpakDir *self, + for (i = 0; subpaths[i] != NULL; i++) + { + g_autofree char *subpath = g_build_filename ("files", subpaths[i], NULL); +- g_autofree char *dstpath = g_build_filename (checkoutdirpath, "/files", subpaths[i], NULL); +- g_autofree char *dstpath_parent = g_path_get_dirname (dstpath); +- g_autofree char *dstpath_relative_to_deploy_base = g_build_filename (checkoutdir_basename, "/files", subpaths[i], NULL); ++ g_autofree char *dstpath = g_build_filename (checkoutdir_basename, subpath, NULL); ++ g_autofree char *dstdir = g_path_get_dirname (dstpath); ++ g_autofree char *dstbase = g_path_get_basename (dstpath); ++ glnx_autofd int parent_dfd = -1; + g_autoptr(GFile) child = NULL; + + child = g_file_resolve_relative_path (root, subpath); +- + if (!g_file_query_exists (child, cancellable)) + { + g_info ("subpath %s not in tree", subpaths[i]); + continue; + } + +- if (g_mkdir_with_parents (dstpath_parent, 0755)) +- { +- glnx_set_error_from_errno (error); +- return FALSE; +- } ++ parent_dfd = glnx_chase_and_mkdirat (deploy_base_dfd, dstdir, ++ GLNX_CHASE_RESOLVE_BENEATH, 0755, error); ++ if (parent_dfd < 0) ++ return FALSE; + + options.subpath = subpath; + if (!ostree_repo_checkout_at (self->repo, &options, +- deploy_base_dfd, dstpath_relative_to_deploy_base, ++ parent_dfd, dstbase, + checksum, + cancellable, error)) + { diff -Nru flatpak-1.16.6/debian/patches/hardening/dir-Use-fd-based-traversal-for-marker-files-in-deploy-dir.patch flatpak-1.16.6/debian/patches/hardening/dir-Use-fd-based-traversal-for-marker-files-in-deploy-dir.patch --- flatpak-1.16.6/debian/patches/hardening/dir-Use-fd-based-traversal-for-marker-files-in-deploy-dir.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/hardening/dir-Use-fd-based-traversal-for-marker-files-in-deploy-dir.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,100 @@ +From: Sebastian Wick +Date: Fri, 3 Jul 2026 16:36:48 +0200 +Subject: dir: Use fd-based traversal for marker files in deploy directories + +The paths up to files/ are not attacker-controlled, and +g_file_replace_contents with G_FILE_CREATE_REPLACE_DESTINATION already +replaces symlinks rather than following them, so this is not a security +fix. The only reason why is that the file is in the root of the attacker +controlled directory. + +This is a bit subtle, so lets do the right thing and concistently use +fd-relative operations when doing anything inside attacker/app +controlled directories. + +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-w69g-9x8j-7p8f +Origin: upstream, 1.18.1 +--- + common/flatpak-dir.c | 48 ++++++++++++++++++++++++++++++------------------ + 1 file changed, 30 insertions(+), 18 deletions(-) + +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index 6e3f891..6a9c192 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -8726,7 +8726,6 @@ flatpak_dir_deploy (FlatpakDir *self, + g_autofree char *checkoutdirpath = NULL; + const char *checkoutdir_basename; + g_autoptr(GFile) real_checkoutdir = NULL; +- g_autoptr(GFile) dotref = NULL; + g_autoptr(GFile) files_etc = NULL; + g_autoptr(GFile) deploy_data_file = NULL; + g_autoptr(GVariant) commit_data = NULL; +@@ -8986,10 +8985,19 @@ flatpak_dir_deploy (FlatpakDir *self, + metadata_contents, metadata_size, error)) + return FALSE; + +- dotref = g_file_resolve_relative_path (checkoutdir, "files/.ref"); +- if (!g_file_replace_contents (dotref, "", 0, NULL, FALSE, +- G_FILE_CREATE_REPLACE_DESTINATION, NULL, cancellable, error)) +- return FALSE; ++ { ++ g_autofree char *files_path = g_build_filename (checkoutdir_basename, "files", NULL); ++ glnx_autofd int files_dfd = -1; ++ ++ if (!glnx_opendirat (deploy_base_dfd, files_path, FALSE, &files_dfd, error)) ++ return FALSE; ++ ++ if (!glnx_file_replace_contents_at (files_dfd, ".ref", ++ (const guint8 *) "", 0, ++ GLNX_FILE_REPLACE_NODATASYNC, ++ cancellable, error)) ++ return FALSE; ++ } + + export = g_file_get_child (checkoutdir, "export"); + +@@ -11211,7 +11219,6 @@ flatpak_dir_undeploy (FlatpakDir *self, + g_autofree char *id = NULL; + g_autofree char *dirname = NULL; + g_autofree char *current_active = NULL; +- g_autoptr(GFile) change_file = NULL; + g_autoptr(GError) child_error = NULL; + int i, retry; + +@@ -11303,18 +11310,23 @@ flatpak_dir_undeploy (FlatpakDir *self, + } + + +- if (is_update) +- change_file = g_file_resolve_relative_path (removed_subdir, "files/.updated"); +- else +- change_file = g_file_resolve_relative_path (removed_subdir, "files/.removed"); +- +- if (!g_file_replace_contents (change_file, "", 0, NULL, FALSE, +- G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, &child_error)) +- { +- g_autofree gchar *path = g_file_get_path (change_file); +- g_warning ("Unable to clear %s: %s", path, child_error->message); +- g_clear_error (&child_error); +- } ++ { ++ glnx_autofd int files_dfd = -1; ++ const char *marker = is_update ? ".updated" : ".removed"; ++ g_autofree char *files_path = g_build_filename (flatpak_file_get_path_cached (removed_subdir), "files", NULL); ++ ++ if (!glnx_opendirat (AT_FDCWD, files_path, ++ FALSE, &files_dfd, &child_error) || ++ !glnx_file_replace_contents_at (files_dfd, marker, ++ (const guint8 *) "", 0, ++ GLNX_FILE_REPLACE_NODATASYNC, ++ NULL, &child_error)) ++ { ++ g_warning ("Unable to write %s marker: %s", marker, ++ child_error ? child_error->message : g_strerror (errno)); ++ g_clear_error (&child_error); ++ } ++ } + + if (force_remove || !dir_is_locked (removed_subdir)) + { diff -Nru flatpak-1.16.6/debian/patches/libglnx/chase-Add-glnx_chase_and_mkdirat.patch flatpak-1.16.6/debian/patches/libglnx/chase-Add-glnx_chase_and_mkdirat.patch --- flatpak-1.16.6/debian/patches/libglnx/chase-Add-glnx_chase_and_mkdirat.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/libglnx/chase-Add-glnx_chase_and_mkdirat.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,232 @@ +From: Sebastian Wick +Date: Fri, 12 Jun 2026 13:45:22 +0200 +Subject: chase: Add glnx_chase_and_mkdirat + +We found that there is a common use case where we need to get a +subdirectory (potentially multiple levels) which might not exist yet. +Adding another flag for this to GlnxChaseFlags is what systemd has done, +but creating a directory takes a mode, so the flag creates directories +with a fixed mode. This approach instead takes the mode as argument. + +[This is required for various later security fixes in Flatpak -smcv] + +(cherry-picked from commit ae7355612a68d39472ff3699104845d2cc5dfb4d) + +Origin: upstream, 1.18.1, commit:ae7355612a68d39472ff3699104845d2cc5dfb4d +--- + subprojects/libglnx/glnx-chase.c | 87 ++++++++++++++++++++++++++ + subprojects/libglnx/glnx-chase.h | 6 ++ + subprojects/libglnx/tests/test-libglnx-chase.c | 69 ++++++++++++++++++++ + 3 files changed, 162 insertions(+) + +diff --git a/subprojects/libglnx/glnx-chase.c b/subprojects/libglnx/glnx-chase.c +index d7edf34..2251cb7 100644 +--- a/subprojects/libglnx/glnx-chase.c ++++ b/subprojects/libglnx/glnx-chase.c +@@ -17,6 +17,7 @@ + #include + + #include ++#include + #include + #include + #include +@@ -823,3 +824,89 @@ glnx_chase_and_statxat (int dirfd, + + return g_steal_fd (&fd); + } ++ ++static int ++chase_and_mkdir (int next_fd, ++ int current_fd, ++ const char *segment, ++ G_GNUC_UNUSED int open_tree_flags, ++ gpointer user_data, ++ GError **error) ++{ ++ mode_t mode = GPOINTER_TO_INT (user_data); ++ glnx_autofd int new_fd = -1; ++ ++ /* if chase managed to get the next segment, we already got our answer */ ++ if (next_fd >= 0) ++ return next_fd; ++ ++ /* if the problem isn't that the file doesn't exist, we propagate the error */ ++ g_assert (*error != NULL); ++ if (!g_error_matches (*error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) ++ return -1; ++ g_clear_error (error); ++ ++ /* create the directory with the specified mode */ ++ if (!glnx_ensure_dir (current_fd, segment, mode, error)) ++ return -1; ++ ++ /* Get a fd to the created dir. ++ * This is racy, meaning another process can modify the filesystem and we ++ * might open a directory that we did not create and might have a different ++ * mode. There isn't a kernel API which returns a fd or an inode which makes ++ * this race unavoidable. This is fine though because the semantics of ++ * `glnx_chase_and_mkdirat` accept arbitrary directories (even with different ++ * modes) when chasing the path. ++ */ ++ new_fd = openat (current_fd, segment, ++ O_CLOEXEC | O_PATH | O_DIRECTORY | O_NOFOLLOW); ++ if (new_fd < 0) ++ return glnx_fd_throw_errno_prefix (error, "opening created dir failed"); ++ ++ return g_steal_fd (&new_fd); ++} ++ ++/** ++ * glnx_chase_and_mkdirat: ++ * @dirfd: a directory file descriptor ++ * @path: a path ++ * @flags: restricted combination of GlnxChaseFlags flags ++ * @mode: the mode for new directories ++ * @error: a #GError ++ * ++ * Same as glnx_chase with `GLNX_CHASE_MUST_BE_DIRECTORY`, but when a path ++ * segment does not exist, a directory is created for the segment with the mode ++ * @mode. ++ * This essentially implement a fd-relative equivalent of g_mkdir_with_parents() ++ * or `mkdir -p`. ++ * Note, that directories in the path which already exist can have arbitrary ++ * modes. ++ * ++ * See glnx_chaseat for the meaning of @dirfd and @path. ++ * ++ * The @flags argument is the same as in glnx_chaseat, but setting ++ * `GLNX_CHASE_MUST_BE_REGULAR`, `GLNX_CHASE_MUST_BE_SOCKET`, or ++ * `GLNX_CHASE_MUST_BE_DIRECTORY` is an error. ++ * ++ * Returns: the chased file, or -1 with @error set on error ++ */ ++int ++glnx_chase_and_mkdirat (int dirfd, ++ const char *path, ++ GlnxChaseFlags flags, ++ mode_t mode, ++ GError **error) ++{ ++ g_return_val_if_fail ((flags & ~(GLNX_CHASE_ALL_FLAGS)) == 0, -1); ++ g_return_val_if_fail ((flags & (GLNX_CHASE_MUST_BE_REGULAR | ++ GLNX_CHASE_MUST_BE_SOCKET | ++ GLNX_CHASE_MUST_BE_DIRECTORY)) == 0, -1); ++ g_return_val_if_fail ((mode & ~((mode_t) 07777)) == 0, -1); ++ ++ /* This function always implies GLNX_CHASE_MUST_BE_DIRECTORY */ ++ flags |= GLNX_CHASE_MUST_BE_DIRECTORY; ++ ++ return glnx_chaseat_full (dirfd, path, flags, ++ chase_and_mkdir, GINT_TO_POINTER (mode), ++ error); ++} +diff --git a/subprojects/libglnx/glnx-chase.h b/subprojects/libglnx/glnx-chase.h +index 05dac12..af5f2bf 100644 +--- a/subprojects/libglnx/glnx-chase.h ++++ b/subprojects/libglnx/glnx-chase.h +@@ -48,4 +48,10 @@ int glnx_chase_and_statxat (int dirfd, + struct glnx_statx *statbuf, + GError **error); + ++int glnx_chase_and_mkdirat (int dirfd, ++ const char *path, ++ GlnxChaseFlags flags, ++ mode_t mode, ++ GError **error); ++ + G_END_DECLS +diff --git a/subprojects/libglnx/tests/test-libglnx-chase.c b/subprojects/libglnx/tests/test-libglnx-chase.c +index b0ce1b4..0a7a655 100644 +--- a/subprojects/libglnx/tests/test-libglnx-chase.c ++++ b/subprojects/libglnx/tests/test-libglnx-chase.c +@@ -50,6 +50,18 @@ path_get_ino (const char *path) + return st.st_ino; + } + ++static mode_t ++get_mode (int fd) ++{ ++ int r; ++ struct stat st; ++ ++ r = fstatat (fd, "", &st, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW); ++ g_assert_cmpint (r, >=, 0); ++ ++ return st.st_mode & 0777; ++} ++ + static char * + get_abspath (int dfd, + const char *path) +@@ -586,6 +598,62 @@ test_chase_and_statxat_permissions (void) + g_clear_fd (&chase_fd, NULL); + } + ++static void ++test_chase_and_mkdir (void) ++{ ++ g_autoptr(GError) error = NULL; ++ glnx_autofd int dfd = -1; ++ glnx_autofd int file_fd = -1; ++ glnx_autofd int chase_fd = -1; ++ ino_t expected_ino; ++ ++ g_assert_true (glnx_shutil_mkdir_p_at_open (AT_FDCWD, "d1", 0755, ++ &dfd, ++ NULL, &error)); ++ g_assert_no_error (error); ++ ++ chase_fd = glnx_chase_and_mkdirat (AT_FDCWD, "d1/d2/d3", ++ GLNX_CHASE_DEFAULT, ++ 0755, ++ &error); ++ g_assert_cmpint (chase_fd, >=, 0); ++ g_assert_no_error (error); ++ ++ file_fd = openat (AT_FDCWD, "d1/d2/d3", O_PATH | O_CLOEXEC | O_NOFOLLOW); ++ g_assert_cmpint (file_fd, >=, 0); ++ expected_ino = get_ino (file_fd); ++ ++ g_assert_cmpint (get_ino (chase_fd), ==, expected_ino); ++ ++ g_assert_cmpint (get_mode (dfd), ==, 0755); ++ g_assert_cmpint (get_mode (chase_fd), ==, 0755); ++ ++ g_clear_fd (&chase_fd, NULL); ++ g_clear_fd (&file_fd, NULL); ++ ++ chase_fd = glnx_chase_and_mkdirat (AT_FDCWD, "d1/d4/d5", ++ GLNX_CHASE_DEFAULT, ++ 0700, ++ &error); ++ g_assert_cmpint (chase_fd, >=, 0); ++ g_assert_no_error (error); ++ ++ file_fd = openat (AT_FDCWD, "d1", O_PATH | O_CLOEXEC | O_NOFOLLOW); ++ g_assert_cmpint (file_fd, >=, 0); ++ g_assert_cmpint (get_mode (file_fd), ==, 0755); ++ g_clear_fd (&file_fd, NULL); ++ ++ file_fd = openat (AT_FDCWD, "d1/d4", O_PATH | O_CLOEXEC | O_NOFOLLOW); ++ g_assert_cmpint (file_fd, >=, 0); ++ g_assert_cmpint (get_mode (file_fd), ==, 0700); ++ g_clear_fd (&file_fd, NULL); ++ ++ file_fd = openat (AT_FDCWD, "d1/d4/d5", O_PATH | O_CLOEXEC | O_NOFOLLOW); ++ g_assert_cmpint (file_fd, >=, 0); ++ g_assert_cmpint (get_mode (file_fd), ==, 0700); ++ g_clear_fd (&file_fd, NULL); ++} ++ + int main (int argc, char **argv) + { + _GLNX_TEST_SCOPED_TEMP_DIR; +@@ -602,6 +670,7 @@ int main (int argc, char **argv) + g_test_add_func ("/chase-and-statxat-basic", test_chase_and_statxat_basic); + g_test_add_func ("/chase-and-statxat-symlink", test_chase_and_statxat_symlink); + g_test_add_func ("/chase-and-statxat-permissions", test_chase_and_statxat_permissions); ++ g_test_add_func ("/chase-and-mkdir", test_chase_and_mkdir); + + ret = g_test_run(); + diff -Nru flatpak-1.16.6/debian/patches/libglnx/chase-Add-internal-glnx_chaseat_full-for-a-strategic-call.patch flatpak-1.16.6/debian/patches/libglnx/chase-Add-internal-glnx_chaseat_full-for-a-strategic-call.patch --- flatpak-1.16.6/debian/patches/libglnx/chase-Add-internal-glnx_chaseat_full-for-a-strategic-call.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/libglnx/chase-Add-internal-glnx_chaseat_full-for-a-strategic-call.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,201 @@ +From: Sebastian Wick +Date: Fri, 12 Jun 2026 13:42:13 +0200 +Subject: chase: Add internal glnx_chaseat_full for a strategic callback + +It takes a callback which gets called every time we try to open the next +segment of the path. This allows implementing more specific and advanced +use cases to be implemented without adding more complexity to the chase +algorithm itself. + +[This is required for various later security fixes in Flatpak -smcv] + +(cherry-picked from commit 4f8674709dce57e7f5422a475f61556162f930d8) + +Origin: upstream, 1.18.1, commit:4f8674709dce57e7f5422a475f61556162f930d8 +--- + subprojects/libglnx/glnx-chase.c | 110 ++++++++++++++++++++++++++------------- + 1 file changed, 73 insertions(+), 37 deletions(-) + +diff --git a/subprojects/libglnx/glnx-chase.c b/subprojects/libglnx/glnx-chase.c +index 9ad2fe3..d7edf34 100644 +--- a/subprojects/libglnx/glnx-chase.c ++++ b/subprojects/libglnx/glnx-chase.c +@@ -46,6 +46,13 @@ + #define GLNX_CHASE_ALL_FLAGS \ + (GLNX_CHASE_ALL_DEBUG_FLAGS | GLNX_CHASE_ALL_REGULAR_FLAGS) + ++typedef int (* GlnxChaseCallback)(int next_fd, ++ int current_fd, ++ const char *segment, ++ int open_tree_flags, ++ gpointer user_data, ++ GError **error); ++ + typedef GQueue GlnxStatxQueue; + + static void +@@ -328,10 +335,12 @@ extract_next_segment (const char **remaining, + * we're in full control over the resolving. + */ + static int +-chase_manual (int dirfd, +- const char *path, +- GlnxChaseFlags flags, +- GError **error) ++chase_manual (int dirfd, ++ const char *path, ++ GlnxChaseFlags flags, ++ GlnxChaseCallback callback, ++ gpointer user_data, ++ GError **error) + { + gboolean is_absolute; + g_autofree char *buffer = NULL; +@@ -349,6 +358,7 @@ chase_manual (int dirfd, + * - none of the resolve flags are set (they would require work here) + * - NO_AUTOMOUNT is set (chase_open_tree only triggers the automount for + * last component in some cases) ++ * - there is no callback + * + * TODO: if we have a guarantee that the open_tree syscall works, we can + * shortcut even without GLNX_CHASE_NO_AUTOMOUNT +@@ -356,7 +366,8 @@ chase_manual (int dirfd, + if ((flags & (GLNX_CHASE_NO_AUTOMOUNT | + GLNX_CHASE_RESOLVE_BENEATH | + GLNX_CHASE_RESOLVE_IN_ROOT | +- GLNX_CHASE_RESOLVE_NO_SYMLINKS)) == GLNX_CHASE_NO_AUTOMOUNT) ++ GLNX_CHASE_RESOLVE_NO_SYMLINKS)) == GLNX_CHASE_NO_AUTOMOUNT && ++ callback == NULL) + { + GlnxChaseFlags open_tree_flags = + (flags & (GLNX_CHASE_NOFOLLOW | GLNX_CHASE_ALL_DEBUG_FLAGS)); +@@ -466,10 +477,25 @@ chase_manual (int dirfd, + GlnxChaseFlags open_tree_flags = + GLNX_CHASE_NOFOLLOW | + (flags & (GLNX_CHASE_NO_AUTOMOUNT | GLNX_CHASE_ALL_DEBUG_FLAGS)); ++ g_autoptr(GError) local_error = NULL; ++ ++ next_fd = chase_open_tree (fd, segment, open_tree_flags, &local_error); ++ ++ /* Note that the callback can be called with next_fd < 0. ++ * If so, the error is already set, but may be cleared by ++ * the callback if it can recover from an error that already ++ * occurred. */ ++ if (callback) ++ { ++ next_fd = callback (next_fd, fd, segment, open_tree_flags, ++ user_data, &local_error); ++ } + +- next_fd = chase_open_tree (fd, segment, open_tree_flags, error); + if (next_fd < 0) +- return -1; ++ { ++ g_propagate_error (error, g_steal_pointer (&local_error)); ++ return -1; ++ } + } + + if (!glnx_chase_statx (next_fd, no_automount, &st, error)) +@@ -602,30 +628,13 @@ chase_manual (int dirfd, + return g_steal_fd (&owned_fd); + } + +-/** +- * glnx_chaseat: +- * @dirfd: a directory file descriptor +- * @path: a path +- * @flags: combination of GlnxChaseFlags flags +- * @error: a #GError +- * +- * Behaves similar to openat, but with a number of differences: +- * +- * - All file descriptors which get returned are O_PATH and O_CLOEXEC. If you +- * want to actually open the file for reading or writing, use glnx_fd_reopen, +- * openat, or other at-style functions. +- * - By default, automounts get triggered and the O_PATH fd will point to inodes +- * in the newly mounted filesystem if an automount is encountered. This can be +- * turned off with GLNX_CHASE_NO_AUTOMOUNT. +- * - The GLNX_CHASE_RESOLVE_ flags can be used to safely deal with symlinks. +- * +- * Returns: the chased file, or -1 with @error set on error +- */ +-int +-glnx_chaseat (int dirfd, +- const char *path, +- GlnxChaseFlags flags, +- GError **error) ++static int ++glnx_chaseat_full (int dirfd, ++ const char *path, ++ GlnxChaseFlags flags, ++ GlnxChaseCallback callback, ++ gpointer user_data, ++ GError **error) + { + static gboolean can_openat2 = TRUE; + glnx_autofd int fd = -1; +@@ -643,15 +652,14 @@ glnx_chaseat (int dirfd, + g_return_val_if_fail ((must_flags & (must_flags - 1)) == 0, -1); + } + +- /* TODO: Add a callback which is called for every resolved path segment, to +- * allow users to verify and expand the functionality safely. */ +- + /* We need the manual impl for NO_AUTOMOUNT, and we can skip this, if we don't + * have openat2 at all. + * Technically racy (static, not synced), but both paths work fine so it + * doesn't matter. */ +- if (can_openat2 && (flags & GLNX_CHASE_NO_AUTOMOUNT) == 0 && +- (flags & GLNX_CHASE_DEBUG_NO_OPENAT2) == 0) ++ if (can_openat2 && ++ (flags & GLNX_CHASE_NO_AUTOMOUNT) == 0 && ++ (flags & GLNX_CHASE_DEBUG_NO_OPENAT2) == 0 && ++ callback == NULL) + { + uint64_t openat2_flags = 0; + uint64_t openat2_resolve = 0; +@@ -690,7 +698,7 @@ glnx_chaseat (int dirfd, + + if (fd < 0) + { +- fd = chase_manual (dirfd, path, flags, error); ++ fd = chase_manual (dirfd, path, flags, callback, user_data, error); + if (fd < 0) + return -1; + } +@@ -744,6 +752,34 @@ glnx_chaseat (int dirfd, + return g_steal_fd (&fd); + } + ++/** ++ * glnx_chaseat: ++ * @dirfd: a directory file descriptor ++ * @path: a path ++ * @flags: combination of GlnxChaseFlags flags ++ * @error: a #GError ++ * ++ * Behaves similar to openat, but with a number of differences: ++ * ++ * - All file descriptors which get returned are O_PATH and O_CLOEXEC. If you ++ * want to actually open the file for reading or writing, use glnx_fd_reopen, ++ * openat, or other at-style functions. ++ * - By default, automounts get triggered and the O_PATH fd will point to inodes ++ * in the newly mounted filesystem if an automount is encountered. This can be ++ * turned off with GLNX_CHASE_NO_AUTOMOUNT. ++ * - The GLNX_CHASE_RESOLVE_ flags can be used to safely deal with symlinks. ++ * ++ * Returns: the chased file, or -1 with @error set on error ++ */ ++int ++glnx_chaseat (int dirfd, ++ const char *path, ++ GlnxChaseFlags flags, ++ GError **error) ++{ ++ return glnx_chaseat_full (dirfd, path, flags, NULL, NULL, error); ++} ++ + /** + * glnx_chase_and_statxat: + * @dirfd: a directory file descriptor diff -Nru flatpak-1.16.6/debian/patches/portal-Actually-use-the-AppInfo-hash-table.patch flatpak-1.16.6/debian/patches/portal-Actually-use-the-AppInfo-hash-table.patch --- flatpak-1.16.6/debian/patches/portal-Actually-use-the-AppInfo-hash-table.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/portal-Actually-use-the-AppInfo-hash-table.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,41 @@ +From: Sebastian Wick +Date: Mon, 13 Apr 2026 13:37:55 +0200 +Subject: portal: Actually use the AppInfo hash table + +Before this change, the hash table was populated with the keyfile as key +and value, but the lookup and invalidate was done on the sender as the +key. + +We never found a previous sender, and never invalidated one. + +Fix this by actually using the sender as the key. + +(cherry picked from commit 993cc143d84aa08410979bf49358eba47cab1066): +Origin: upstream, 1.16.7, commit:6a98dceb56edff29234a7493ae07d9607661f13b +--- + portal/flatpak-portal-app-info.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/portal/flatpak-portal-app-info.c b/portal/flatpak-portal-app-info.c +index f6c8162..9dd741f 100644 +--- a/portal/flatpak-portal-app-info.c ++++ b/portal/flatpak-portal-app-info.c +@@ -33,7 +33,8 @@ ensure_app_infos (void) + { + if (app_infos == NULL) + app_infos = g_hash_table_new_full (g_str_hash, g_str_equal, +- NULL, (GDestroyNotify) g_key_file_unref); ++ g_free, ++ (GDestroyNotify) g_key_file_unref); + } + + static GKeyFile * +@@ -62,7 +63,7 @@ static void + add_cached_app_info_by_sender (const char *sender, GKeyFile *keyfile) + { + G_LOCK (app_infos); +- g_hash_table_add (app_infos, g_key_file_ref (keyfile)); ++ g_hash_table_insert (app_infos, g_strdup (sender), g_key_file_ref (keyfile)); + G_UNLOCK (app_infos); + } + diff -Nru flatpak-1.16.6/debian/patches/series flatpak-1.16.6/debian/patches/series --- flatpak-1.16.6/debian/patches/series 2026-04-10 19:03:53.000000000 +0000 +++ flatpak-1.16.6/debian/patches/series 2026-08-11 13:03:38.000000000 +0000 @@ -1 +1,39 @@ debian/system-helper-Set-Debian-s-canonical-home-directory-for-s.patch +subprojects-Ignore-.wraplock-file-generated-by-recent-Mes.patch +bwrap-Clarify-a-comment.patch +dir-Use-flatpak_bwrap_child_setup_inherit_fds_cb-to-apply.patch +subprojects-Update-dbus-proxy.wrap-to-v0.1.7.patch +portal-Actually-use-the-AppInfo-hash-table.patch +tests/tests-Fix-checks-where-we-expect-a-command-to-fail.patch +libglnx/chase-Add-internal-glnx_chaseat_full-for-a-strategic-call.patch +libglnx/chase-Add-glnx_chase_and_mkdirat.patch +GHSA-99wv-m8rp-g58x/common-Fix-return-value-and-typos-in-flatpak_switch_symli.patch +GHSA-99wv-m8rp-g58x/common-Use-fd-based-operations-in-flatpak_switch_symlink_.patch +GHSA-99wv-m8rp-g58x/run-Harden-regenerate_ld_cache-against-symlink-attacks.patch +GHSA-8688-9x26-hhxj/test-run-Check-that-the-way-com.valvesoftware.Steam-behav.patch +GHSA-8688-9x26-hhxj/run-Harden-ensure_data_dir-and-var-setup-against-symlink-.patch +GHSA-8688-9x26-hhxj/tests-Add-a-regression-test-for-GHSA-8688-9x26-hhxj.patch +GHSA-jr92-2v97-wgvc/oci-Use-gsize-for-OCI-delta-sizes.patch +GHSA-jr92-2v97-wgvc/oci-Avoid-overflow-in-delta_read_data.patch +GHSA-jr92-2v97-wgvc/oci-Limit-delta-path-length-to-PATH_MAX.patch +GHSA-r7hp-698j-2h6c/run-dbus-Correct-broadcast-rules-for-the-AT-SPI-bus.patch +GHSA-v2gw-v9h5-9q4x/ref-utils-Make-remote-name-validation-public-and-take-an-.patch +GHSA-v2gw-v9h5-9q4x/system-helper-Validate-each-remote-name-and-architecture-.patch +GHSA-v2gw-v9h5-9q4x/tests-Add-some-unit-tests-for-ref-utils-name-component-va.patch +GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-out-of-basefd-in-si.patch +GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-link-rename.patch +GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-symlink.patch +GHSA-qrwq-7qwx-q9rp/revokefs-Avoid-symlink-path-traversal-in-chmod.patch +GHSA-w69g-9x8j-7p8f/dir-utils-Reject-path-traversal-in-extension-metadata.patch +GHSA-w69g-9x8j-7p8f/run-Use-RESOLVE_BENEATH-for-host-side-extension-file-acce.patch +hardening/dir-Use-fd-based-traversal-for-marker-files-in-deploy-dir.patch +hardening/dir-Harden-confining-subpath-deploy-to-the-deploy-base.patch +GHSA-q4gr-vc25-57m5/dir-Fall-back-to-the-deployed-ref-commit-when-up-downgrad.patch +GHSA-q4gr-vc25-57m5/system-dir-Prevent-removing-of-deployed-refs-via-the-syst.patch +GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Add-flatpak_cp_a_at-a-fd-based-flatpak_cp_a-variant.patch +GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Make-flatpak_cp_a-a-wrapper-around-flatpak_cp_a_at.patch +GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/build-init-Use-fd-relative-operations-to-prevent-path-tra.patch +GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Validate-extra-data-name.patch +GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Use-chaseat-in-extract_extra_data-to-prevent-path-tra.patch +GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/dir-Use-chaseat-in-apply_extra_data-to-prevent-path-trave.patch +GHSA-fqx6-vh4p-42cg-GHSA-8qxj-x646-phcm/utils-Remove-unused-GFile-based-flatpak_cp_a.patch diff -Nru flatpak-1.16.6/debian/patches/subprojects-Ignore-.wraplock-file-generated-by-recent-Mes.patch flatpak-1.16.6/debian/patches/subprojects-Ignore-.wraplock-file-generated-by-recent-Mes.patch --- flatpak-1.16.6/debian/patches/subprojects-Ignore-.wraplock-file-generated-by-recent-Mes.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/subprojects-Ignore-.wraplock-file-generated-by-recent-Mes.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,19 @@ +From: Simon McVittie +Date: Sat, 11 Apr 2026 17:31:48 +0100 +Subject: subprojects: Ignore .wraplock file generated by recent Meson + +Signed-off-by: Simon McVittie +(cherry picked from commit 0337cfde00886d7377da1ded7d2c8520f1d61415) +Origin: upstream, 1.16.7, commit:a554bae3b2474c1ae8c086a953a1d65be03a380b +--- + subprojects/.gitignore | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/subprojects/.gitignore b/subprojects/.gitignore +index f71f340..6078eec 100644 +--- a/subprojects/.gitignore ++++ b/subprojects/.gitignore +@@ -1,2 +1,3 @@ ++/.wraplock + bubblewrap/ + dbus-proxy/ diff -Nru flatpak-1.16.6/debian/patches/subprojects-Update-dbus-proxy.wrap-to-v0.1.7.patch flatpak-1.16.6/debian/patches/subprojects-Update-dbus-proxy.wrap-to-v0.1.7.patch --- flatpak-1.16.6/debian/patches/subprojects-Update-dbus-proxy.wrap-to-v0.1.7.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/subprojects-Update-dbus-proxy.wrap-to-v0.1.7.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,25 @@ +From: Sebastian Wick +Date: Mon, 13 Apr 2026 15:53:16 +0200 +Subject: subprojects: Update dbus-proxy.wrap to v0.1.7 + +Versions up to 0.1.6 contain a security vulnerability, so let's bump it +to a version which does not. + +(cherry picked from commit 2b942431d28c71b4046c34010525f400a6cf4ac7): +Origin: upstream, 1.16.7, commit:e39581a798987d96ed786854fca75606d13fe5d7 +--- + subprojects/dbus-proxy.wrap | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/subprojects/dbus-proxy.wrap b/subprojects/dbus-proxy.wrap +index 50127f3..29dcdce 100644 +--- a/subprojects/dbus-proxy.wrap ++++ b/subprojects/dbus-proxy.wrap +@@ -1,5 +1,5 @@ + [wrap-git] + url = https://github.com/flatpak/xdg-dbus-proxy +-# 0.1.6 +-revision = 1c1989e56f94b9eb3b7567f8a6e8a0aa16cba496 ++# 0.1.7 ++revision = 6a170fa77e3cbecb48f9dd2478fe5c0a119eb467 + depth = 1 diff -Nru flatpak-1.16.6/debian/patches/tests/tests-Fix-checks-where-we-expect-a-command-to-fail.patch flatpak-1.16.6/debian/patches/tests/tests-Fix-checks-where-we-expect-a-command-to-fail.patch --- flatpak-1.16.6/debian/patches/tests/tests-Fix-checks-where-we-expect-a-command-to-fail.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.16.6/debian/patches/tests/tests-Fix-checks-where-we-expect-a-command-to-fail.patch 2026-08-11 13:03:38.000000000 +0000 @@ -0,0 +1,133 @@ +From: Sebastian Wick +Date: Tue, 14 Apr 2026 21:16:21 +0200 +Subject: tests: Fix checks where we expect a command to fail + +I was convinced that the pattern `! command` with -e aborts when +`command` fails. This is not the case (the result of `false` is the same +as `! true` but somehow this doesn't matter). + +Fix the tests and use the newly introduced `assert_fail` function. One +could also use `command && assert_not_reached "message"` but who has +time to write error messages for all the cases. + +[We need this for some of the tests for later security fixes -smcv] + +(cherry-picked from commit 68343e938f66051462e0ae3003d0acaca9a76a72) + +Origin: upstream, 1.17.7, commit:68343e938f66051462e0ae3003d0acaca9a76a72 +--- + tests/libtest.sh | 9 +++++++++ + tests/test-run-custom.sh | 22 +++++++++++----------- + 2 files changed, 20 insertions(+), 11 deletions(-) + +diff --git a/tests/libtest.sh b/tests/libtest.sh +index 7004f2d..058e7d9 100644 +--- a/tests/libtest.sh ++++ b/tests/libtest.sh +@@ -282,6 +282,15 @@ assert_remote_has_no_config () { + } 3> /dev/null + } + ++assert_fail () { ++ if "$@"; then ++ { { local BASH_XTRACEFD=3; } 2> /dev/null ++ echo "Command '$*' should not have succeeded at $(basename ${BASH_SOURCE[1]}):${BASH_LINENO[0]}" >&2 ++ exit 1 ++ } 3> /dev/null ++ fi ++} ++ + export FL_GPG_HOMEDIR=${TEST_DATA_DIR}/gpghome + export FL_GPG_HOMEDIR2=${TEST_DATA_DIR}/gpghome2 + mkdir -p ${FL_GPG_HOMEDIR} +diff --git a/tests/test-run-custom.sh b/tests/test-run-custom.sh +index 4868b8b..7736866 100755 +--- a/tests/test-run-custom.sh ++++ b/tests/test-run-custom.sh +@@ -51,7 +51,7 @@ assert_file_has_content hello_out '^Hello world, from a runtime$' + + ok "setup" + +-! run --app-path="" --command=/app/bin/hello.sh org.test.Hello > /dev/null ++assert_fail run --app-path="" --command=/app/bin/hello.sh org.test.Hello > /dev/null + + run --app-path="" --command=/usr/bin/runtime_hello.sh org.test.Hello > hello_out + assert_file_has_content hello_out '^Hello world, from a runtime$' +@@ -59,7 +59,7 @@ assert_file_has_content hello_out '^Hello world, from a runtime$' + run --app-path="" --command=/run/parent/app/bin/hello.sh org.test.Hello > hello_out + assert_file_has_content hello_out '^Hello world, from a sandbox$' + +-! run --app-path="" --command=/run/parent/usr/bin/runtime_hello.sh org.test.Hello > /dev/null ++assert_fail run --app-path="" --command=/run/parent/usr/bin/runtime_hello.sh org.test.Hello > /dev/null + + ok "empty app path" + +@@ -72,11 +72,11 @@ assert_file_has_content hello_out '^Hello world, from a runtime$' + run --app-path=custom-app/files --command=/run/parent/app/bin/hello.sh org.test.Hello > hello_out + assert_file_has_content hello_out '^Hello world, from a sandbox$' + +-! run --app-path=custom-app/files --command=/run/parent/usr/bin/runtime_hello.sh org.test.Hello > /dev/null ++assert_fail run --app-path=custom-app/files --command=/run/parent/usr/bin/runtime_hello.sh org.test.Hello > /dev/null + + ok "custom app path" + +-! run --app-path=path-which-does-not-exist org.test.Hello > /dev/null ++assert_fail run --app-path=path-which-does-not-exist org.test.Hello > /dev/null + + ok "bad custom app path" + +@@ -85,7 +85,7 @@ run --app-fd=3 --command=/app/bin/hello.sh org.test.Hello > hello_out + assert_file_has_content hello_out '^Hello world, from a sandboxCUSTOM$' + exec 3>&- + +-! run --app-fd=3 --command=/app/bin/hello.sh org.test.Hello > /dev/null ++assert_fail run --app-fd=3 --command=/app/bin/hello.sh org.test.Hello > /dev/null + + ok "custom app fd" + +@@ -95,14 +95,14 @@ assert_file_has_content hello_out '^Hello world, from a sandbox$' + run --usr-path=custom-runtime/files --command=/usr/bin/runtime_hello.sh org.test.Hello > hello_out + assert_file_has_content hello_out '^Hello world, from a runtimeCUSTOM$' + +-! run --usr-path=custom-runtime/files --command=/run/parent/app/bin/hello.sh org.test.Hello > /dev/null ++assert_fail run --usr-path=custom-runtime/files --command=/run/parent/app/bin/hello.sh org.test.Hello > /dev/null + + run --usr-path=custom-runtime/files --command=/run/parent/usr/bin/runtime_hello.sh org.test.Hello > hello_out + assert_file_has_content hello_out '^Hello world, from a runtime$' + + ok "custom usr path" + +-! run --usr-path=path-which-does-not-exist org.test.Hello > /dev/null ++assert_fail run --usr-path=path-which-does-not-exist org.test.Hello > /dev/null + + ok "bad custom usr path" + +@@ -116,7 +116,7 @@ run --usr-fd=3 --command=/usr/bin/runtime_hello.sh org.test.Hello > hello_out + assert_file_has_content hello_out '^Hello world, from a runtimeCUSTOM$' + exec 3>&- + +-! run --usr-fd=3 --command=/app/bin/hello.sh org.test.Hello > /dev/null ++assert_fail run --usr-fd=3 --command=/app/bin/hello.sh org.test.Hello > /dev/null + + ok "custom usr fd" + +@@ -138,8 +138,8 @@ assert_file_has_content hello_out '^Hello world, from a runtime$' + + ok "custom usr and app path" + +-! run --usr-path=custom-runtime/files --app-path="" \ +- --command=/app/bin/hello.sh org.test.Hello > /dev/null ++assert_fail run --usr-path=custom-runtime/files --app-path="" \ ++ --command=/app/bin/hello.sh org.test.Hello > /dev/null + + run --usr-path=custom-runtime/files --app-path="" \ + --command=/usr/bin/runtime_hello.sh org.test.Hello > hello_out +@@ -173,7 +173,7 @@ assert_file_has_content hello_out '^baz$' + exec 3>&- + + exec 3< "${path}" +-! run --ro-bind-fd=3 --command=bash org.test.Hello -c "echo baz > ${path}" > /dev/null ++assert_fail run --ro-bind-fd=3 --command=bash org.test.Hello -c "echo baz > ${path}" > /dev/null + exec 3>&- + + ok "bind-fd and ro-bind-fd"