Version in base suite: 1.14.10-1~deb12u1 Base version: flatpak_1.14.10-1~deb12u1 Target version: flatpak_1.14.10-1~deb12u2 Base file: /srv/ftp-master.debian.org/ftp/pool/main/f/flatpak/flatpak_1.14.10-1~deb12u1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/f/flatpak/flatpak_1.14.10-1~deb12u2.dsc changelog | 46 control | 2 patches/1.16.5/portal-update-max_fd-after-creating-the-instance-ID-pipe.patch | 29 patches/1.16.5/portal-use-g_array_index-to-read-from-expose_fds-expose_f.patch | 37 patches/1.16.5/run-Add-bind-fd-and-ro-bind-fd-binds-after-all-other-bind.patch | 103 + patches/1.16.5/run-Do-not-close-bind-ro-bind.patch | 31 patches/1.16.5/run-Fix-backport-mistake.patch | 27 patches/1.16.5/run-Fix-checking-wrong-variable-in-runtime-fd-selection.patch | 36 patches/1.16.5/run-Fix-fd-tracking-in-flatpak_run_add_app_info_args.patch | 124 + patches/1.16.5/run-Mount-original-app-on-run-parent-app-when-using-app-p.patch | 31 patches/1.16.5/run-Use-the-same-FD-validation-for-all-FD-options.patch | 94 + patches/1.16.5/tests-test-run-custom-Test-bind-fd-and-ro-bind-fd.patch | 55 patches/1.16.5/tests-test-run-custom-Test-usr-path-usr-fd-app-path-app-f.patch | 213 ++ patches/1.16.5/utils-Improve-error-message-when-passing-an-FD-numer-whic.patch | 27 patches/1.16.6/app-context-Factor-out-flatpak_accept_fd_argument.patch | 233 ++ patches/1.16.6/app-context-Never-close-fds-0-1-or-2.patch | 101 + patches/1.16.6/dir-In-apply_extra_data-don-t-assume-there-is-always-a-ru.patch | 40 patches/1.16.6/libtest-Allow-adding-a-new-ref-to-an-existing-temporary-o.patch | 33 patches/1.16.6/portal-Avoid-crash-if-sandbox-expose-ro-fd-is-out-of-rang.patch | 64 patches/1.16.6/portal-Log-and-ignore-unusable-sandbox-expose-fds-instead.patch | 63 patches/1.16.6/portal-Reinstate-flatpak_get_path_for_fd-checks.patch | 67 patches/1.16.6/run-Cope-with-an-empty-runtime.patch | 109 + patches/1.16.6/run-context-Mark-fd-arguments-as-close-on-exec.patch | 100 + patches/1.16.6/tests-Add-test-extra-data.sh-to-test-extra-data-installat.patch | 119 + patches/1.16.6/tests-Check-that-flatpak-run-fd-arguments-do-not-leak-to-.patch | 59 patches/1.16.6/utils-Add-flatpak_set_cloexec.patch | 50 patches/1.16.6/utils-Move-flatpak_get_path_for_fd-to-here.patch | 177 + patches/1.16.7/bwrap-Clarify-a-comment.patch | 30 patches/CVE-2026-34078-prep/backports-Add-g_clear_fd.patch | 140 + patches/CVE-2026-34078-prep/build-Add-glnx-chase.-ch-to-subprojects.patch | 31 patches/CVE-2026-34078-prep/build-Link-libglnx-into-libflatpak-common-not-just-into-l.patch | 35 patches/CVE-2026-34078-prep/chase-Add-glnx_chase_and_statxat.patch | 83 patches/CVE-2026-34078-prep/chase-Add-glnx_chaseat-which-functions-similar-to-openat2.patch | 824 ++++++++ patches/CVE-2026-34078-prep/chase-Don-t-leak-struct-glnx_statx-when-we-go-up-a-level.patch | 29 patches/CVE-2026-34078-prep/chase-Don-t-left-shift-signed-integer-1-by-31-places.patch | 31 patches/CVE-2026-34078-prep/chase-Factor-out-a-function-to-append-to-the-queue.patch | 61 patches/CVE-2026-34078-prep/fdio-Add-glnx_fd_reopen.patch | 109 + patches/CVE-2026-34078-prep/fdio-Add-glnx_statx.patch | 62 patches/CVE-2026-34078-prep/glnx-errors.h-add-glnx_fd_throw-_-variants.patch | 53 patches/CVE-2026-34078-prep/missing-Add-syscall-and-structs-for-statx.patch | 178 + patches/CVE-2026-34078-prep/missing-Add-syscall-number-for-openat2-and-open_tree.patch | 227 ++ patches/CVE-2026-34078/flatpak-bwrap-Add-dup-ing-variant-flatpak_bwrap_add_args_.patch | 61 patches/CVE-2026-34078/flatpak-bwrap-Use-glnx_close_fd-as-clear-func.patch | 49 patches/CVE-2026-34078/portal-Use-bind-fd-app-fd-and-usr-fd-options-to-avoid-rac.patch | 603 ++++++ patches/CVE-2026-34078/run-Add-ro-bind-fd-options.patch | 98 + patches/CVE-2026-34078/run-Add-ro-bind-fds-to-flatpak_run_app.patch | 119 + patches/CVE-2026-34078/run-Add-usr-fd-and-app-fd-options.patch | 79 patches/CVE-2026-34078/run-Use-O_PATH-fds-for-the-runtime-and-app-deploy-directo.patch | 931 ++++++++++ patches/CVE-2026-34078/utils-Add-flatpak_parse_fd.patch | 97 + patches/CVE-2026-34079/utils-Only-remove-cached-files-in-the-cache-directory.patch | 89 patches/GHSA-2fxp-43j9-pwvc/utils-Do-not-follow-symlinks-in-local_open_file.patch | 48 patches/GHSA-89xm-3m96-w3jg/system-helper-Only-remove-an-ongoing-pull-if-users-match.patch | 150 + patches/dir-Silence-a-spurious-warning-when-installing-extra-data.patch | 37 patches/portal-Don-t-run-method-invocations-in-a-thread.patch | 33 patches/portal-Use-G_LOCK_DEFINE_STATIC.patch | 25 patches/series | 53 56 files changed, 6435 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpi6kzgxep/flatpak_1.14.10-1~deb12u1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpi6kzgxep/flatpak_1.14.10-1~deb12u2.dsc: no acceptable signature found diff -Nru flatpak-1.14.10/debian/changelog flatpak-1.14.10/debian/changelog --- flatpak-1.14.10/debian/changelog 2024-08-14 14:49:20.000000000 +0000 +++ flatpak-1.14.10/debian/changelog 2026-04-15 19:27:40.000000000 +0000 @@ -1,3 +1,49 @@ +flatpak (1.14.10-1~deb12u2) bookworm-security; urgency=high + + * Security update + * d/p/CVE-2026-34078-prep/*.patch: + Backport libglnx changes required to address CVE-2026-34078 + * d/p/CVE-2026-34078/*.patch: + Fix a sandbox escape involving symlinks passed to flatpak-portal. + A malicious or compromised Flatpak app could exploit this to achieve + arbitrary code execution on the host. + (CVE-2026-34078, GHSA-cc2q-qc34-jprg) (Closes: #1132943) + * d/p/CVE-2026-34079/*.patch: + Prevent arbitrary file deletion outside the sandbox by a malicious or + compromised Flatpak app + (CVE-2026-34079, GHSA-p29x-r292-46pp) (Closes: #1132944) + * d/p/GHSA-2fxp-43j9-pwvc/*.patch: + Prevent a local user from reading any file that is readable by the + _flatpak system user. A mitigation is that it would be very unusual + for these files not to be readable by the original local user as well. + (No CVE ID, GHSA-2fxp-43j9-pwvc) (Closes: #1132946) + * d/p/GHSA-89xm-3m96-w3jg/*.patch: + Prevent a local user from making another local user unable to cancel + an ongoing download of apps or runtimes installed system-wide + via the system helper. + (No CVE ID, GHSA-89xm-3m96-w3jg) (Closes: #1132945) + * d/p/portal-Use-G_LOCK_DEFINE_STATIC.patch, + d/p/portal-Don-t-run-method-invocations-in-a-thread.patch: + Add patches from upstream flatpak-1.14.x branch (which never got into a + release before the branch was discontinued), originally from 1.16.1, + fixing a thread-safety issue in flatpak-portal + * d/p/1.16.5/*.patch: + Add regression fixes taken from the upstream 1.16.5 release, + fixing various regressions introduced by fixing CVE-2026-34078 + and improving test coverage + (Closes: #1132960) + * d/p/1.16.6/*.patch: + Add regression fixes taken from the upstream 1.16.6 release, + fixing additional regressions introduced by fixing CVE-2026-34078 + and improving test coverage + (Closes: #1132968) + - d/control: Add curl(1) to Build-Depends and flatpak-tests Depends + * d/p/1.16.7/bwrap-Clarify-a-comment.patch, + d/p/dir-Silence-a-spurious-warning-when-installing-extra-data.patch: + Silence a spurious warning seen while testing 1.16.6 + + -- Simon McVittie Wed, 15 Apr 2026 20:27:40 +0100 + flatpak (1.14.10-1~deb12u1) bookworm-security; urgency=high * Backport upstream stable release into Debian 12 (CVE-2024-42472) diff -Nru flatpak-1.14.10/debian/control flatpak-1.14.10/debian/control --- flatpak-1.14.10/debian/control 2024-08-14 14:49:20.000000000 +0000 +++ flatpak-1.14.10/debian/control 2026-04-15 19:27:40.000000000 +0000 @@ -12,6 +12,7 @@ bubblewrap (>= 0.8.0-2+deb12u1~), bubblewrap (<< 0.8.1~) | bubblewrap (>= 0.10.0~), ca-certificates , + curl , dbus-daemon, debhelper-compat (= 13), desktop-file-utils , @@ -121,6 +122,7 @@ Depends: attr, ca-certificates, + curl, dbus-daemon, desktop-file-utils, flatpak (= ${binary:Version}), diff -Nru flatpak-1.14.10/debian/patches/1.16.5/portal-update-max_fd-after-creating-the-instance-ID-pipe.patch flatpak-1.14.10/debian/patches/1.16.5/portal-update-max_fd-after-creating-the-instance-ID-pipe.patch --- flatpak-1.14.10/debian/patches/1.16.5/portal-update-max_fd-after-creating-the-instance-ID-pipe.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.5/portal-update-max_fd-after-creating-the-instance-ID-pipe.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,29 @@ +From: Alberto Garcia +Date: Wed, 8 Apr 2026 19:28:32 +0200 +Subject: portal: update max_fd after creating the instance ID pipe + +fd_map_remap_fd() is called several times after this, and without this +change it can allocate a target fd that collides with instance_id_fd. + +Only the write end of the pipe needs to be considered because that's +the one passed to the child. + +Origin: upstream, 1.16.5, commit:2e2f4a430b784163614403b333ab3990c9354db3 +Bug: https://github.com/flatpak/flatpak/issues/6570 +Bug-Debian: https://bugs.debian.org/1132968 +--- + portal/flatpak-portal.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/portal/flatpak-portal.c b/portal/flatpak-portal.c +index 6821831..3b9f0b6 100644 +--- a/portal/flatpak-portal.c ++++ b/portal/flatpak-portal.c +@@ -1078,6 +1078,7 @@ handle_spawn (PortalFlatpak *object, + + g_ptr_array_add (flatpak_argv, g_strdup_printf ("--instance-id-fd=%d", pipe_fds[1])); + child_setup_data.instance_id_fd = pipe_fds[1]; ++ max_fd = MAX(max_fd, pipe_fds[1]); + } + + if (devel) diff -Nru flatpak-1.14.10/debian/patches/1.16.5/portal-use-g_array_index-to-read-from-expose_fds-expose_f.patch flatpak-1.14.10/debian/patches/1.16.5/portal-use-g_array_index-to-read-from-expose_fds-expose_f.patch --- flatpak-1.14.10/debian/patches/1.16.5/portal-use-g_array_index-to-read-from-expose_fds-expose_f.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.5/portal-use-g_array_index-to-read-from-expose_fds-expose_f.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,37 @@ +From: Alberto Garcia +Date: Wed, 8 Apr 2026 19:44:29 +0200 +Subject: portal: use g_array_index() to read from expose_fds / expose_fds_ro + +The data field of a GArray is a gchar* but we're storing integers +here, so use the proper method to ensure that we're getting the +element at the right offset and with the correct type. + +Origin: upstream, 1.16.5, commit:8e71548ab56a97b88c7d34d68a6b04f6d430b2aa +Bug: https://github.com/flatpak/flatpak/issues/6570 +Bug-Debian: https://bugs.debian.org/1132968 +--- + portal/flatpak-portal.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/portal/flatpak-portal.c b/portal/flatpak-portal.c +index 3b9f0b6..a82cfdf 100644 +--- a/portal/flatpak-portal.c ++++ b/portal/flatpak-portal.c +@@ -1222,7 +1222,7 @@ handle_spawn (PortalFlatpak *object, + { + int remapped_fd; + +- remapped_fd = fd_map_remap_fd (fd_map, &max_fd, expose_fds->data[i]); ++ remapped_fd = fd_map_remap_fd (fd_map, &max_fd, g_array_index (expose_fds, int, i)); + + g_ptr_array_add (flatpak_argv, g_strdup_printf ("--bind-fd=%d", + remapped_fd)); +@@ -1232,7 +1232,7 @@ handle_spawn (PortalFlatpak *object, + { + int remapped_fd; + +- remapped_fd = fd_map_remap_fd (fd_map, &max_fd, expose_fds_ro->data[i]); ++ remapped_fd = fd_map_remap_fd (fd_map, &max_fd, g_array_index (expose_fds_ro, int, i)); + + g_ptr_array_add (flatpak_argv, g_strdup_printf ("--ro-bind-fd=%d", + remapped_fd)); diff -Nru flatpak-1.14.10/debian/patches/1.16.5/run-Add-bind-fd-and-ro-bind-fd-binds-after-all-other-bind.patch flatpak-1.14.10/debian/patches/1.16.5/run-Add-bind-fd-and-ro-bind-fd-binds-after-all-other-bind.patch --- flatpak-1.14.10/debian/patches/1.16.5/run-Add-bind-fd-and-ro-bind-fd-binds-after-all-other-bind.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.5/run-Add-bind-fd-and-ro-bind-fd-binds-after-all-other-bind.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,103 @@ +From: Sebastian Wick +Date: Wed, 8 Apr 2026 21:59:19 +0200 +Subject: run: Add bind-fd and ro-bind-fd binds after all other binds + +This is only moving it a bit down because +flatpak_run_add_environment_args still adds a whole bunch of binds which +then can over-mount the user requested binds (bind-fd, ro-bind-fd). + +[smcv: Resolve conflicts for 1.14.x] + +Origin: backport, 1.16.5, commit:6118b415e4ed1e35852736c9a28585f4093bf246 +Bug: https://github.com/flatpak/flatpak/issues/6570 +Bug-Debian: https://bugs.debian.org/1132968 +--- + common/flatpak-run.c | 68 ++++++++++++++++++++++++++-------------------------- + 1 file changed, 34 insertions(+), 34 deletions(-) + +diff --git a/common/flatpak-run.c b/common/flatpak-run.c +index 415c6fa..0f3d122 100644 +--- a/common/flatpak-run.c ++++ b/common/flatpak-run.c +@@ -4803,40 +4803,6 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + flatpak_bwrap_add_arg_printf (bwrap, "/run/user/%d", getuid ()); + } + +- for (i = 0; bind_fds && i < bind_fds->len; i++) +- { +- int fd = g_array_index (bind_fds, int, i); +- g_autofree char *path = NULL; +- +- /* We get the path the fd refers to, to determine to mount point +- * destination inside the sandbox */ +- path = get_path_for_fd (fd, error); +- if (!path) +- return FALSE; +- +- if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, +- "--bind-fd", fd, path, +- error)) +- return FALSE; +- } +- +- for (i = 0; ro_bind_fds && i < ro_bind_fds->len; i++) +- { +- int fd = g_array_index (ro_bind_fds, int, i); +- g_autofree char *path = NULL; +- +- /* We get the path the fd refers to, to determine to mount point +- * destination inside the sandbox */ +- path = get_path_for_fd (fd, error); +- if (!path) +- return FALSE; +- +- if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, +- "--ro-bind-fd", fd, path, +- error)) +- return FALSE; +- } +- + if (!flatpak_run_add_dconf_args (bwrap, app_id, metakey, error)) + return FALSE; + +@@ -4872,6 +4838,40 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + "--symlink", "/usr/lib/debug/source", "/run/build-runtime", + NULL); + ++ for (i = 0; bind_fds && i < bind_fds->len; i++) ++ { ++ int fd = g_array_index (bind_fds, int, i); ++ g_autofree char *path = NULL; ++ ++ /* We get the path the fd refers to, to determine to mount point ++ * destination inside the sandbox */ ++ path = get_path_for_fd (fd, error); ++ if (!path) ++ return FALSE; ++ ++ if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, ++ "--bind-fd", fd, path, ++ error)) ++ return FALSE; ++ } ++ ++ for (i = 0; ro_bind_fds && i < ro_bind_fds->len; i++) ++ { ++ int fd = g_array_index (ro_bind_fds, int, i); ++ g_autofree char *path = NULL; ++ ++ /* We get the path the fd refers to, to determine to mount point ++ * destination inside the sandbox */ ++ path = get_path_for_fd (fd, error); ++ if (!path) ++ return FALSE; ++ ++ if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, ++ "--ro-bind-fd", fd, path, ++ error)) ++ return FALSE; ++ } ++ + if (cwd) + flatpak_bwrap_add_args (bwrap, "--chdir", cwd, NULL); + diff -Nru flatpak-1.14.10/debian/patches/1.16.5/run-Do-not-close-bind-ro-bind.patch flatpak-1.14.10/debian/patches/1.16.5/run-Do-not-close-bind-ro-bind.patch --- flatpak-1.14.10/debian/patches/1.16.5/run-Do-not-close-bind-ro-bind.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.5/run-Do-not-close-bind-ro-bind.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,31 @@ +From: Sebastian Wick +Date: Wed, 8 Apr 2026 18:14:19 +0200 +Subject: run: Do not close --bind/--ro-bind + +Origin: upstream, 1.16.5, commit:e654ebade0b9154830563c55c0a5f426b41fd46d +Bug: https://github.com/flatpak/flatpak/issues/6570 +Bug-Debian: https://bugs.debian.org/1132968 +--- + app/flatpak-builtins-run.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/app/flatpak-builtins-run.c b/app/flatpak-builtins-run.c +index 40ab8fc..2b53a9e 100644 +--- a/app/flatpak-builtins-run.c ++++ b/app/flatpak-builtins-run.c +@@ -82,6 +82,7 @@ option_bind_fd_cb (const char *option_name, + return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); + + g_array_append_val (opt_bind_fds, fd); ++ fd = -1; /* ownership transferred to GArray */ + return TRUE; + } + +@@ -101,6 +102,7 @@ option_ro_bind_fd_cb (const char *option_name, + return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); + + g_array_append_val (opt_ro_bind_fds, fd); ++ fd = -1; /* ownership transferred to GArray */ + return TRUE; + } + diff -Nru flatpak-1.14.10/debian/patches/1.16.5/run-Fix-backport-mistake.patch flatpak-1.14.10/debian/patches/1.16.5/run-Fix-backport-mistake.patch --- flatpak-1.14.10/debian/patches/1.16.5/run-Fix-backport-mistake.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.5/run-Fix-backport-mistake.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,27 @@ +From: Sebastian Wick +Date: Thu, 9 Apr 2026 00:56:40 +0200 +Subject: run: Fix backport mistake + +Not even sure how this happened. Whoops. It's time to get some sleep. + +Fixes: c89a0c50 ("run: Use the same FD validation for all FD options") +Origin: upstream, 1.16.5, commit:ac540672bb6598285fd333e3863335d27236762b +Bug: https://github.com/flatpak/flatpak/issues/6570 +Bug-Debian: https://bugs.debian.org/1132968 +--- + app/flatpak-builtins-run.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/app/flatpak-builtins-run.c b/app/flatpak-builtins-run.c +index f8ee4e0..5c0cda2 100644 +--- a/app/flatpak-builtins-run.c ++++ b/app/flatpak-builtins-run.c +@@ -191,7 +191,7 @@ static GOptionEntry options[] = { + { "app-path", 0, 0, G_OPTION_ARG_FILENAME, &opt_app_path, N_("Use PATH instead of the app's /app"), N_("PATH") }, + { "app-fd", 0, 0, G_OPTION_ARG_CALLBACK, &opt_app_fd_cb, N_("Use FD instead of the app's /app"), N_("FD") }, + { "usr-path", 0, 0, G_OPTION_ARG_FILENAME, &opt_usr_path, N_("Use PATH instead of the runtime's /usr"), N_("PATH") }, +- { "usr-fd", 0, 0, G_OPTION_ARG_INT, &opt_usr_fd_cb, N_("Use FD instead of the runtime's /usr"), N_("FD") }, ++ { "usr-fd", 0, 0, G_OPTION_ARG_CALLBACK, &opt_usr_fd_cb, N_("Use FD instead of the runtime's /usr"), N_("FD") }, + { "bind-fd", 0, 0, G_OPTION_ARG_CALLBACK | G_OPTION_FLAG_HIDDEN, &option_bind_fd_cb, N_("Bind mount the file or directory referred to by FD to its canonicalized path"), N_("FD") }, + { "ro-bind-fd", 0, 0, G_OPTION_ARG_CALLBACK | G_OPTION_FLAG_HIDDEN, &option_ro_bind_fd_cb, N_("Bind mount the file or directory referred to by FD read-only to its canonicalized path"), N_("FD") }, + { NULL } diff -Nru flatpak-1.14.10/debian/patches/1.16.5/run-Fix-checking-wrong-variable-in-runtime-fd-selection.patch flatpak-1.14.10/debian/patches/1.16.5/run-Fix-checking-wrong-variable-in-runtime-fd-selection.patch --- flatpak-1.14.10/debian/patches/1.16.5/run-Fix-checking-wrong-variable-in-runtime-fd-selection.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.5/run-Fix-checking-wrong-variable-in-runtime-fd-selection.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,36 @@ +From: Xiangzhe +Date: Wed, 8 Apr 2026 12:27:28 +0800 +Subject: run: Fix checking wrong variable in runtime fd selection + +In flatpak_run_app(), the else-if branch that handles +FLATPAK_RUN_APP_DEPLOY_USR_ORIGINAL was checking custom_app_fd instead +of custom_runtime_fd. When custom_app_fd is APP_EMPTY (-3) and +custom_runtime_fd is USR_ORIGINAL (-2), the condition would not match +and fall through to g_assert_not_reached(), aborting the process. + +This broke sub-sandbox spawning with --app-path="" (empty app), which +is used by steam-runtime-check-requirements to verify that Flatpak's +sub-sandbox mechanism works. + +Fixes: ac62ebe3 "run: Use O_PATH fds for the runtime and app deploy directories" +(cherry picked from commit 066babba75d355d077ea11091e5f65d3b0e0d818) +Origin: upstream, 1.16.5, commit:d19f44408000b53b6d61d3e60a3c65104dc5a8c6 +Bug: https://github.com/flatpak/flatpak/issues/6568 +Bug-Debian: https://bugs.debian.org/1132960 +--- + common/flatpak-run.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/common/flatpak-run.c b/common/flatpak-run.c +index f12b1f8..e0e98de 100644 +--- a/common/flatpak-run.c ++++ b/common/flatpak-run.c +@@ -4427,7 +4427,7 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + runtime_fd = custom_runtime_fd; + runtime_files = custom_runtime_files; + } +- else if (custom_app_fd == FLATPAK_RUN_APP_DEPLOY_USR_ORIGINAL) ++ else if (custom_runtime_fd == FLATPAK_RUN_APP_DEPLOY_USR_ORIGINAL) + { + original_runtime_target_path = "/usr"; + runtime_fd = original_runtime_fd; diff -Nru flatpak-1.14.10/debian/patches/1.16.5/run-Fix-fd-tracking-in-flatpak_run_add_app_info_args.patch flatpak-1.14.10/debian/patches/1.16.5/run-Fix-fd-tracking-in-flatpak_run_add_app_info_args.patch --- flatpak-1.14.10/debian/patches/1.16.5/run-Fix-fd-tracking-in-flatpak_run_add_app_info_args.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.5/run-Fix-fd-tracking-in-flatpak_run_add_app_info_args.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,124 @@ +From: Sebastian Wick +Date: Wed, 8 Apr 2026 17:47:48 +0200 +Subject: run: Fix fd tracking in flatpak_run_add_app_info_args + +Calls to flatpak_bwrap_add_args_data_fd take ownership over the fd they +take. Closing them while they are still in the bwrap struct will abort +later when the bwrap struct gets freed and it tries to close the already +closed fd. + +Fix this by using glnx_autofd and g_steal_fd. + +[smcv: Resolve conflicts in 1.14.x] + +Origin: backport, 1.16.5, commit:bd41305b98373d762cc02395d9fe7b9486da20d4 +Bug: https://github.com/flatpak/flatpak/issues/6570 +Bug-Debian: https://bugs.debian.org/1132968 +--- + common/flatpak-run.c | 34 +++++++++++++++++----------------- + 1 file changed, 17 insertions(+), 17 deletions(-) + +diff --git a/common/flatpak-run.c b/common/flatpak-run.c +index 2e47f28..415c6fa 100644 +--- a/common/flatpak-run.c ++++ b/common/flatpak-run.c +@@ -2676,13 +2676,17 @@ flatpak_run_add_app_info_args (FlatpakBwrap *bwrap, + gboolean build, + gboolean devel, + char **app_info_path_out, +- int instance_id_fd, ++ int instance_id_fd_arg, + char **instance_id_host_dir_out, + GError **error) + { + g_autofree char *info_path = NULL; + g_autofree char *bwrapinfo_path = NULL; +- int fd, fd2, fd3; ++ glnx_autofd int fd1 = -1; ++ glnx_autofd int fd2 = -1; ++ glnx_autofd int fd3 = -1; ++ int info_fd; ++ glnx_autofd int instance_id_fd = instance_id_fd_arg; + g_autoptr(GKeyFile) keyfile = NULL; + g_autofree char *runtime_path = NULL; + const char *group; +@@ -2828,8 +2832,8 @@ flatpak_run_add_app_info_args (FlatpakBwrap *bwrap, + This way even if the bind-mount is unmounted we can find the real data. + */ + +- fd = open (info_path, O_RDONLY); +- if (fd == -1) ++ fd1 = info_fd = open (info_path, O_RDONLY); ++ if (fd1 == -1) + { + int errsv = errno; + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), +@@ -2840,7 +2844,6 @@ flatpak_run_add_app_info_args (FlatpakBwrap *bwrap, + fd2 = open (info_path, O_RDONLY); + if (fd2 == -1) + { +- close (fd); + int errsv = errno; + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), + _("Failed to open flatpak-info file: %s"), g_strerror (errsv)); +@@ -2849,9 +2852,9 @@ flatpak_run_add_app_info_args (FlatpakBwrap *bwrap, + + flatpak_bwrap_add_args (bwrap, "--perms", "0600", NULL); + flatpak_bwrap_add_args_data_fd (bwrap, +- "--file", fd, "/.flatpak-info"); ++ "--file", g_steal_fd (&fd1), "/.flatpak-info"); + flatpak_bwrap_add_args_data_fd (bwrap, +- "--ro-bind-data", fd2, "/.flatpak-info"); ++ "--ro-bind-data", g_steal_fd (&fd2), "/.flatpak-info"); + + /* Tell the application that it's running under Flatpak in a generic way. */ + flatpak_bwrap_add_args (bwrap, +@@ -2868,8 +2871,6 @@ flatpak_run_add_app_info_args (FlatpakBwrap *bwrap, + fd3 = open (bwrapinfo_path, O_RDWR | O_CREAT, 0644); + if (fd3 == -1) + { +- close (fd); +- close (fd2); + int errsv = errno; + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), + _("Failed to open bwrapinfo.json file: %s"), g_strerror (errsv)); +@@ -2892,10 +2893,6 @@ flatpak_run_add_app_info_args (FlatpakBwrap *bwrap, + if (errsv == EINTR) + continue; + +- close (fd); +- close (fd2); +- close (fd3); +- + g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errsv), + _("Failed to write to instance id fd: %s"), g_strerror (errsv)); + return FALSE; +@@ -2905,13 +2902,14 @@ flatpak_run_add_app_info_args (FlatpakBwrap *bwrap, + instance_id_size -= bytes_written; + } + +- close (instance_id_fd); ++ /* explicitly close this as soon as we're done to notify the other side */ ++ g_clear_fd (&instance_id_fd, NULL); + } + +- flatpak_bwrap_add_args_data_fd (bwrap, "--info-fd", fd3, NULL); ++ flatpak_bwrap_add_args_data_fd (bwrap, "--info-fd", g_steal_fd (&fd3), NULL); + + if (app_info_path_out != NULL) +- *app_info_path_out = g_strdup_printf ("/proc/self/fd/%d", fd); ++ *app_info_path_out = g_strdup_printf ("/proc/self/fd/%d", info_fd); + + if (instance_id_host_dir_out != NULL) + *instance_id_host_dir_out = g_steal_pointer (&instance_id_host_dir); +@@ -4780,7 +4778,9 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + app_id, flatpak_decomposed_get_branch (app_ref), + runtime_ref, app_id_dir, app_context, extra_context, + sandboxed, FALSE, flags & FLATPAK_RUN_FLAG_DEVEL, +- &app_info_path, instance_id_fd, &instance_id_host_dir, ++ &app_info_path, ++ g_steal_fd (&instance_id_fd), ++ &instance_id_host_dir, + error)) + return FALSE; + diff -Nru flatpak-1.14.10/debian/patches/1.16.5/run-Mount-original-app-on-run-parent-app-when-using-app-p.patch flatpak-1.14.10/debian/patches/1.16.5/run-Mount-original-app-on-run-parent-app-when-using-app-p.patch --- flatpak-1.14.10/debian/patches/1.16.5/run-Mount-original-app-on-run-parent-app-when-using-app-p.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.5/run-Mount-original-app-on-run-parent-app-when-using-app-p.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,31 @@ +From: Simon McVittie +Date: Wed, 8 Apr 2026 09:44:55 +0100 +Subject: run: Mount original app on /run/parent/app when using --app-path="" + +Before addressing CVE-2026-34078, we would always mount the original app +*somewhere*, either /app (in the normal case) or /run/parent/app (when +using a custom or empty /app for the subsandbox). The empty-app case +regressed during the fix for CVE-2026-34078; bring back previous behaviour. + +Fixes: ac62ebe3 "run: Use O_PATH fds for the runtime and app deploy directories" +Signed-off-by: Simon McVittie +(cherry picked from commit fde4716f67b6620da57fd74481694eb58795d589) +Origin: upstream, 1.16.5, commit:ccd9c8a7fa8bee1b728e683c2d478f4408cd651c +Bug: https://github.com/flatpak/flatpak/issues/6568 +Bug-Debian: https://bugs.debian.org/1132960 +--- + common/flatpak-run.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/common/flatpak-run.c b/common/flatpak-run.c +index e0e98de..2e47f28 100644 +--- a/common/flatpak-run.c ++++ b/common/flatpak-run.c +@@ -4559,6 +4559,7 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + } + else if (custom_app_fd == FLATPAK_RUN_APP_DEPLOY_APP_EMPTY) + { ++ original_app_target_path = "/run/parent/app"; + app_fd = -1; + app_files = NULL; + } diff -Nru flatpak-1.14.10/debian/patches/1.16.5/run-Use-the-same-FD-validation-for-all-FD-options.patch flatpak-1.14.10/debian/patches/1.16.5/run-Use-the-same-FD-validation-for-all-FD-options.patch --- flatpak-1.14.10/debian/patches/1.16.5/run-Use-the-same-FD-validation-for-all-FD-options.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.5/run-Use-the-same-FD-validation-for-all-FD-options.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,94 @@ +From: Sebastian Wick +Date: Wed, 8 Apr 2026 18:19:20 +0200 +Subject: run: Use the same FD validation for all FD options + +Origin: upstream, 1.16.5, commit:c89a0c509ad315ccf5157e9faebb4e243bed165c +Bug: https://github.com/flatpak/flatpak/issues/6570 +Bug-Debian: https://bugs.debian.org/1132968 +--- + app/flatpak-builtins-run.c | 63 +++++++++++++++++++++++++++++++++++++++++++--- + 1 file changed, 60 insertions(+), 3 deletions(-) + +diff --git a/app/flatpak-builtins-run.c b/app/flatpak-builtins-run.c +index 2b53a9e..f8ee4e0 100644 +--- a/app/flatpak-builtins-run.c ++++ b/app/flatpak-builtins-run.c +@@ -106,6 +106,63 @@ option_ro_bind_fd_cb (const char *option_name, + return TRUE; + } + ++static gboolean ++opt_instance_id_fd_cb (const char *option_name, ++ const char *value, ++ gpointer data, ++ GError **error) ++{ ++ glnx_autofd int fd = -1; ++ ++ fd = flatpak_parse_fd (value, error); ++ if (fd < 0) ++ return FALSE; ++ ++ if (fd < 3) ++ return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ ++ opt_instance_id_fd = g_steal_fd (&fd); ++ return TRUE; ++} ++ ++static gboolean ++opt_app_fd_cb (const char *option_name, ++ const char *value, ++ gpointer data, ++ GError **error) ++{ ++ glnx_autofd int fd = -1; ++ ++ fd = flatpak_parse_fd (value, error); ++ if (fd < 0) ++ return FALSE; ++ ++ if (fd < 3) ++ return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ ++ opt_app_fd = g_steal_fd (&fd); ++ return TRUE; ++} ++ ++static gboolean ++opt_usr_fd_cb (const char *option_name, ++ const char *value, ++ gpointer data, ++ GError **error) ++{ ++ glnx_autofd int fd = -1; ++ ++ fd = flatpak_parse_fd (value, error); ++ if (fd < 0) ++ return FALSE; ++ ++ if (fd < 3) ++ return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ ++ opt_usr_fd = g_steal_fd (&fd); ++ return TRUE; ++} ++ + static GOptionEntry options[] = { + { "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, N_("Arch to use"), N_("ARCH") }, + { "command", 0, 0, G_OPTION_ARG_STRING, &opt_command, N_("Command to run"), N_("COMMAND") }, +@@ -130,11 +187,11 @@ static GOptionEntry options[] = { + { "parent-pid", 0, 0, G_OPTION_ARG_INT, &opt_parent_pid, N_("Use PID as parent pid for sharing namespaces"), N_("PID") }, + { "parent-expose-pids", 0, 0, G_OPTION_ARG_NONE, &opt_parent_expose_pids, N_("Make processes visible in parent namespace"), NULL }, + { "parent-share-pids", 0, 0, G_OPTION_ARG_NONE, &opt_parent_share_pids, N_("Share process ID namespace with parent"), NULL }, +- { "instance-id-fd", 0, 0, G_OPTION_ARG_INT, &opt_instance_id_fd, N_("Write the instance ID to the given file descriptor"), NULL }, ++ { "instance-id-fd", 0, 0, G_OPTION_ARG_CALLBACK, &opt_instance_id_fd_cb, N_("Write the instance ID to the given file descriptor"), NULL }, + { "app-path", 0, 0, G_OPTION_ARG_FILENAME, &opt_app_path, N_("Use PATH instead of the app's /app"), N_("PATH") }, +- { "app-fd", 0, 0, G_OPTION_ARG_INT, &opt_app_fd, N_("Use FD instead of the app's /app"), N_("FD") }, ++ { "app-fd", 0, 0, G_OPTION_ARG_CALLBACK, &opt_app_fd_cb, N_("Use FD instead of the app's /app"), N_("FD") }, + { "usr-path", 0, 0, G_OPTION_ARG_FILENAME, &opt_usr_path, N_("Use PATH instead of the runtime's /usr"), N_("PATH") }, +- { "usr-fd", 0, 0, G_OPTION_ARG_INT, &opt_usr_fd, N_("Use FD instead of the runtime's /usr"), N_("FD") }, ++ { "usr-fd", 0, 0, G_OPTION_ARG_INT, &opt_usr_fd_cb, N_("Use FD instead of the runtime's /usr"), N_("FD") }, + { "bind-fd", 0, 0, G_OPTION_ARG_CALLBACK | G_OPTION_FLAG_HIDDEN, &option_bind_fd_cb, N_("Bind mount the file or directory referred to by FD to its canonicalized path"), N_("FD") }, + { "ro-bind-fd", 0, 0, G_OPTION_ARG_CALLBACK | G_OPTION_FLAG_HIDDEN, &option_ro_bind_fd_cb, N_("Bind mount the file or directory referred to by FD read-only to its canonicalized path"), N_("FD") }, + { NULL } diff -Nru flatpak-1.14.10/debian/patches/1.16.5/tests-test-run-custom-Test-bind-fd-and-ro-bind-fd.patch flatpak-1.14.10/debian/patches/1.16.5/tests-test-run-custom-Test-bind-fd-and-ro-bind-fd.patch --- flatpak-1.14.10/debian/patches/1.16.5/tests-test-run-custom-Test-bind-fd-and-ro-bind-fd.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.5/tests-test-run-custom-Test-bind-fd-and-ro-bind-fd.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,55 @@ +From: Sebastian Wick +Date: Wed, 8 Apr 2026 21:55:22 +0200 +Subject: tests/test-run-custom: Test --bind-fd and --ro-bind-fd + +Origin: upstream, 1.16.5, commit:8ebffc2ad37fb5f2c03641506e8091aee53defd8 +Bug: https://github.com/flatpak/flatpak/issues/6570 +Bug-Debian: https://bugs.debian.org/1132968 +--- + tests/test-run-custom.sh | 27 +++++++++++++++++++++++++-- + 1 file changed, 25 insertions(+), 2 deletions(-) + +diff --git a/tests/test-run-custom.sh b/tests/test-run-custom.sh +index f8a11f0..94c3241 100755 +--- a/tests/test-run-custom.sh ++++ b/tests/test-run-custom.sh +@@ -24,7 +24,7 @@ set -euo pipefail + skip_without_bwrap + skip_revokefs_without_fuse + +-echo "1..10" ++echo "1..11" + + # Use stable rather than master as the branch so we can test that the run + # command automatically finds the branch correctly +@@ -153,4 +153,27 @@ run --usr-path=custom-runtime/files --app-path="" \ + --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 and empty app path" +\ No newline at end of file ++ok "custom usr and empty app path" ++ ++path="$(readlink -f .)/foo" ++echo "bar" > "${path}" ++ ++exec 3< "${path}" ++run --bind-fd=3 --command=cat org.test.Hello "${path}" > hello_out ++assert_file_has_content hello_out '^bar$' ++ ++exec 3< "${path}" ++run --bind-fd=3 --command=bash org.test.Hello -c "echo baz > ${path}" > /dev/null ++assert_file_has_content "${path}" '^baz$' ++exec 3>&- ++ ++exec 3< "${path}" ++run --ro-bind-fd=3 --command=cat org.test.Hello "${path}" > hello_out ++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 ++exec 3>&- ++ ++ok "bind-fd and ro-bind-fd" +\ No newline at end of file diff -Nru flatpak-1.14.10/debian/patches/1.16.5/tests-test-run-custom-Test-usr-path-usr-fd-app-path-app-f.patch flatpak-1.14.10/debian/patches/1.16.5/tests-test-run-custom-Test-usr-path-usr-fd-app-path-app-f.patch --- flatpak-1.14.10/debian/patches/1.16.5/tests-test-run-custom-Test-usr-path-usr-fd-app-path-app-f.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.5/tests-test-run-custom-Test-usr-path-usr-fd-app-path-app-f.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,213 @@ +From: Sebastian Wick +Date: Wed, 8 Apr 2026 14:20:53 +0200 +Subject: tests/test-run-custom: Test --usr-path, --usr-fd, --app-path, + --app-fd + +[smcv: Add the new test to the old build system] + +Origin: backport, 1.16.5, commit:ab8ca4d6a02900974c08694f54903b2616875bf0 +Bug: https://github.com/flatpak/flatpak/issues/6568 +Bug-Debian: https://bugs.debian.org/1132960 +--- + tests/Makefile-test-matrix.am.inc | 3 + + tests/test-run-custom.sh | 156 ++++++++++++++++++++++++++++++++++++++ + tests/update-test-matrix | 1 + + 3 files changed, 160 insertions(+) + create mode 100755 tests/test-run-custom.sh + +diff --git a/tests/Makefile-test-matrix.am.inc b/tests/Makefile-test-matrix.am.inc +index f3bcd9a..a2e797f 100644 +--- a/tests/Makefile-test-matrix.am.inc ++++ b/tests/Makefile-test-matrix.am.inc +@@ -6,6 +6,8 @@ TEST_MATRIX= \ + tests/test-run@system,deltas.wrap \ + tests/test-run@system-norevokefs,nodeltas.wrap \ + tests/test-run@system-norevokefs,deltas.wrap \ ++ tests/test-run-custom@user.wrap \ ++ tests/test-run-custom@system.wrap \ + tests/test-info@user.wrap \ + tests/test-info@system.wrap \ + tests/test-repo@user.wrap \ +@@ -49,6 +51,7 @@ TEST_MATRIX_DIST= \ + $(NULL) + TEST_MATRIX_EXTRA_DIST= \ + tests/test-run.sh \ ++ tests/test-run-custom.sh \ + tests/test-info.sh \ + tests/test-repo.sh \ + tests/test-sideload.sh \ +diff --git a/tests/test-run-custom.sh b/tests/test-run-custom.sh +new file mode 100755 +index 0000000..f8a11f0 +--- /dev/null ++++ b/tests/test-run-custom.sh +@@ -0,0 +1,156 @@ ++#!/bin/bash ++# ++# Copyright (C) 2026 Red Hat, Inc. ++# ++# This library is free software; you can redistribute it and/or ++# modify it under the terms of the GNU Lesser General Public ++# License as published by the Free Software Foundation; either ++# version 2 of the License, or (at your option) any later version. ++# ++# This library is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# Lesser General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public ++# License along with this library; if not, write to the ++# Free Software Foundation, Inc., 59 Temple Place - Suite 330, ++# Boston, MA 02111-1307, USA. ++ ++set -euo pipefail ++ ++. "$(dirname "$0")/libtest.sh" ++ ++skip_without_bwrap ++skip_revokefs_without_fuse ++ ++echo "1..10" ++ ++# Use stable rather than master as the branch so we can test that the run ++# command automatically finds the branch correctly ++setup_repo "" "" stable ++install_repo "" stable ++ ++setup_repo_no_add custom org.test.Collection.Custom master ++make_updated_runtime custom org.test.Collection.Custom master CUSTOM ++make_updated_app custom org.test.Collection.Custom master CUSTOM ++ ++ostree checkout -U --repo=repos/custom runtime/org.test.Platform/${ARCH}/master custom-runtime >&2 ++ostree checkout -U --repo=repos/custom app/org.test.Hello/$ARCH/master custom-app >&2 ++ ++cat custom-runtime/files/bin/runtime_hello.sh > runtime_hello ++assert_file_has_content runtime_hello "runtimeCUSTOM" ++cat custom-app/files/bin/hello.sh > app_hello ++assert_file_has_content app_hello "sandboxCUSTOM" ++ ++run org.test.Hello > hello_out ++assert_file_has_content hello_out '^Hello world, from a sandbox$' ++ ++run --command=/usr/bin/runtime_hello.sh org.test.Hello > hello_out ++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 ++ ++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$' ++ ++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 ++ ++ok "empty app path" ++ ++run --app-path=custom-app/files --command=/app/bin/hello.sh org.test.Hello > hello_out ++assert_file_has_content hello_out '^Hello world, from a sandboxCUSTOM$' ++ ++run --app-path=custom-app/files --command=/usr/bin/runtime_hello.sh org.test.Hello > hello_out ++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 ++ ++ok "custom app path" ++ ++! run --app-path=path-which-does-not-exist org.test.Hello > /dev/null ++ ++ok "bad custom app path" ++ ++exec 3< custom-app/files ++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 ++ ++ok "custom app fd" ++ ++run --usr-path=custom-runtime/files --command=/app/bin/hello.sh org.test.Hello > hello_out ++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 ++ ++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 ++ ++ok "bad custom usr path" ++ ++exec 3< custom-runtime/files ++run --usr-fd=3 --command=/app/bin/hello.sh org.test.Hello > hello_out ++assert_file_has_content hello_out '^Hello world, from a sandbox$' ++exec 3>&- ++ ++exec 3< custom-runtime/files ++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 ++ ++ok "custom usr fd" ++ ++run --usr-path=custom-runtime/files --app-path=custom-app/files \ ++ --command=/app/bin/hello.sh org.test.Hello > hello_out ++assert_file_has_content hello_out '^Hello world, from a sandboxCUSTOM$' ++ ++run --usr-path=custom-runtime/files --app-path=custom-app/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 --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 --usr-path=custom-runtime/files --app-path=custom-app/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 and app path" ++ ++! 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 ++assert_file_has_content hello_out '^Hello world, from a runtimeCUSTOM$' ++ ++run --usr-path=custom-runtime/files --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 --usr-path=custom-runtime/files --app-path="" \ ++ --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 and empty app path" +\ No newline at end of file +diff --git a/tests/update-test-matrix b/tests/update-test-matrix +index 04e6531..cf76c87 100755 +--- a/tests/update-test-matrix ++++ b/tests/update-test-matrix +@@ -14,6 +14,7 @@ TEST_MATRIX_SOURCE=( + 'tests/test-build-update-repo.sh' \ + 'tests/test-http-utils.sh' \ + 'tests/test-run.sh{{user+system+system-norevokefs},{nodeltas+deltas}}' \ ++ 'tests/test-run-custom.sh{user+system}' \ + 'tests/test-info.sh{user+system}' \ + 'tests/test-repo.sh{{user+system+system-norevokefs}+{{user+system},oldsummary}}' \ + 'tests/test-history.sh' \ diff -Nru flatpak-1.14.10/debian/patches/1.16.5/utils-Improve-error-message-when-passing-an-FD-numer-whic.patch flatpak-1.14.10/debian/patches/1.16.5/utils-Improve-error-message-when-passing-an-FD-numer-whic.patch --- flatpak-1.14.10/debian/patches/1.16.5/utils-Improve-error-message-when-passing-an-FD-numer-whic.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.5/utils-Improve-error-message-when-passing-an-FD-numer-whic.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,27 @@ +From: Sebastian Wick +Date: Wed, 8 Apr 2026 18:15:42 +0200 +Subject: utils: Improve error message when passing an FD numer which is not a + FD + +Origin: upstream, 1.16.5, commit:e2ef538e8d70f8ddaf1adf201c3bb1578d0444f1 +Bug: https://github.com/flatpak/flatpak/issues/6570 +Bug-Debian: https://bugs.debian.org/1132968 +--- + common/flatpak-utils.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c +index 2778304..6706aaa 100644 +--- a/common/flatpak-utils.c ++++ b/common/flatpak-utils.c +@@ -9466,8 +9466,8 @@ flatpak_parse_fd (const char *fd_string, + + fd = (int) parsed; + +- if (!glnx_fstat (fd, &stbuf, error)) +- return -1; ++ if (!glnx_fstat (fd, &stbuf, NULL)) ++ return glnx_fd_throw (error, "Not an open file descriptor: %d", fd); + + return fd; + } diff -Nru flatpak-1.14.10/debian/patches/1.16.6/app-context-Factor-out-flatpak_accept_fd_argument.patch flatpak-1.14.10/debian/patches/1.16.6/app-context-Factor-out-flatpak_accept_fd_argument.patch --- flatpak-1.14.10/debian/patches/1.16.6/app-context-Factor-out-flatpak_accept_fd_argument.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.6/app-context-Factor-out-flatpak_accept_fd_argument.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,233 @@ +From: Simon McVittie +Date: Fri, 10 Apr 2026 15:02:43 +0100 +Subject: app, context: Factor out flatpak_accept_fd_argument() + +Signed-off-by: Simon McVittie +(cherry picked from commit d42037c5267ac7967ce285b9052b25fe7a968368) + +Origin: upstream, 1.16.6, commit:817888730861a21bf930f7fbadc28155c2f3de39 +--- + app/flatpak-builtins-run.c | 61 +++++++----------------------------------- + common/flatpak-context.c | 18 ++----------- + common/flatpak-utils-private.h | 4 +++ + common/flatpak-utils.c | 47 ++++++++++++++++++++++++++++++++ + 4 files changed, 63 insertions(+), 67 deletions(-) + +diff --git a/app/flatpak-builtins-run.c b/app/flatpak-builtins-run.c +index 574a2d5..d44cc92 100644 +--- a/app/flatpak-builtins-run.c ++++ b/app/flatpak-builtins-run.c +@@ -74,20 +74,11 @@ option_bind_fd_cb (const char *option_name, + { + glnx_autofd int fd = -1; + +- fd = flatpak_parse_fd (value, error); ++ fd = flatpak_accept_fd_argument (option_name, value, error); ++ + if (fd < 0) + return FALSE; + +- if (fd < 3) +- { +- /* Don't close these fds! */ +- fd = -1; +- return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); +- } +- +- if (!flatpak_set_cloexec (fd)) +- return glnx_throw_errno_prefix (error, "--bind-fd"); +- + g_array_append_val (opt_bind_fds, fd); + fd = -1; /* ownership transferred to GArray */ + return TRUE; +@@ -101,19 +92,11 @@ option_ro_bind_fd_cb (const char *option_name, + { + glnx_autofd int fd = -1; + +- fd = flatpak_parse_fd (value, error); ++ fd = flatpak_accept_fd_argument (option_name, value, error); ++ + if (fd < 0) + return FALSE; + +- if (fd < 3) +- { +- fd = -1; +- return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); +- } +- +- if (!flatpak_set_cloexec (fd)) +- return glnx_throw_errno_prefix (error, "--ro-bind-fd"); +- + g_array_append_val (opt_ro_bind_fds, fd); + fd = -1; /* ownership transferred to GArray */ + return TRUE; +@@ -127,19 +110,11 @@ opt_instance_id_fd_cb (const char *option_name, + { + glnx_autofd int fd = -1; + +- fd = flatpak_parse_fd (value, error); ++ fd = flatpak_accept_fd_argument (option_name, value, error); ++ + if (fd < 0) + return FALSE; + +- if (fd < 3) +- { +- fd = -1; +- return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); +- } +- +- if (!flatpak_set_cloexec (fd)) +- return glnx_throw_errno_prefix (error, "--instance-id-fd"); +- + opt_instance_id_fd = g_steal_fd (&fd); + return TRUE; + } +@@ -152,19 +127,11 @@ opt_app_fd_cb (const char *option_name, + { + glnx_autofd int fd = -1; + +- fd = flatpak_parse_fd (value, error); ++ fd = flatpak_accept_fd_argument (option_name, value, error); ++ + if (fd < 0) + return FALSE; + +- if (fd < 3) +- { +- fd = -1; +- return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); +- } +- +- if (!flatpak_set_cloexec (fd)) +- return glnx_throw_errno_prefix (error, "--app-fd"); +- + opt_app_fd = g_steal_fd (&fd); + return TRUE; + } +@@ -177,19 +144,11 @@ opt_usr_fd_cb (const char *option_name, + { + glnx_autofd int fd = -1; + +- fd = flatpak_parse_fd (value, error); ++ fd = flatpak_accept_fd_argument (option_name, value, error); ++ + if (fd < 0) + return FALSE; + +- if (fd < 3) +- { +- fd = -1; +- return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); +- } +- +- if (!flatpak_set_cloexec (fd)) +- return glnx_throw_errno_prefix (error, "--usr-fd"); +- + opt_usr_fd = g_steal_fd (&fd); + return TRUE; + } +diff --git a/common/flatpak-context.c b/common/flatpak-context.c +index a3e58ea..567bd11 100644 +--- a/common/flatpak-context.c ++++ b/common/flatpak-context.c +@@ -1306,25 +1306,11 @@ option_env_fd_cb (const gchar *option_name, + FlatpakContext *context = data; + glnx_autofd int fd = -1; + +- fd = flatpak_parse_fd (value, error); ++ fd = flatpak_accept_fd_argument (option_name, value, error); ++ + if (fd < 0) + return FALSE; + +- if (fd < 3) +- { +- /* Don't close these fds! */ +- fd = -1; +- return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); +- } +- +- /* This is not strictly necessary, because we're going to close it after +- * parsing the environment block, but let's be consistent with other fd +- * arguments that we need to avoid being inherited by the "payload" +- * command. This is also a convenient place to validate that it's an +- * open fd. */ +- if (!flatpak_set_cloexec (fd)) +- return glnx_throw_errno_prefix (error, "--env-fd"); +- + return flatpak_context_parse_env_fd (context, fd, error); + } + +diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h +index 8199151..56afd9d 100644 +--- a/common/flatpak-utils-private.h ++++ b/common/flatpak-utils-private.h +@@ -954,4 +954,8 @@ char * flatpak_get_path_for_fd (int fd, + + gboolean flatpak_set_cloexec (int fd); + ++int flatpak_accept_fd_argument (const char *option_name, ++ const char *value, ++ GError **error); ++ + #endif /* __FLATPAK_UTILS_H__ */ +diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c +index ac3a8d8..2d0f085 100644 +--- a/common/flatpak-utils.c ++++ b/common/flatpak-utils.c +@@ -9489,6 +9489,53 @@ flatpak_set_cloexec (int fd) + return TRUE; + } + ++/* ++ * flatpak_accept_fd_argument: ++ * @option_name: Name of a command-line option such as `--env-fd` ++ * @value: Value of the command-line option ++ * ++ * Parse a command-line argument whose value is a file descriptor to be ++ * used internally by Flatpak. ++ * ++ * The file descriptor must be 3 or higher (cannot be stdin, stdout ++ * or stderr). ++ * ++ * The file descriptor is set to be close-on-execute (CLOEXEC). ++ * If child processes are meant to inherit it, the caller must clear the ++ * close-on-execute flag, or duplicate the fd. ++ * ++ * Returns: A file descriptor to be closed by the caller, or -1 on error ++ */ ++int ++flatpak_accept_fd_argument (const char *option_name, ++ const char *value, ++ GError **error) ++{ ++ glnx_autofd int fd = -1; ++ ++ fd = flatpak_parse_fd (value, error); ++ ++ if (fd < 0) ++ { ++ g_prefix_error (error, "%s: ", option_name); ++ return -1; ++ } ++ ++ if (fd < 3) ++ { ++ /* We don't want to close stdin, stdout or stderr */ ++ fd = -1; ++ return glnx_fd_throw (error, ++ "%s: Cannot use reserved file descriptor 0, 1 or 2", ++ option_name); ++ } ++ ++ if (!flatpak_set_cloexec (fd)) ++ return glnx_fd_throw_errno_prefix (error, "%s", option_name); ++ ++ return g_steal_fd (&fd); ++} ++ + /* + * Attempt to discover the filesystem path corresponding to @fd. + * diff -Nru flatpak-1.14.10/debian/patches/1.16.6/app-context-Never-close-fds-0-1-or-2.patch flatpak-1.14.10/debian/patches/1.16.6/app-context-Never-close-fds-0-1-or-2.patch --- flatpak-1.14.10/debian/patches/1.16.6/app-context-Never-close-fds-0-1-or-2.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.6/app-context-Never-close-fds-0-1-or-2.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,101 @@ +From: Simon McVittie +Date: Fri, 10 Apr 2026 14:00:14 +0100 +Subject: app, context: Never close fds 0, 1 or 2 + +These fds are stdin, stdout and stderr respectively, and are expected +to remain open at all times (if they are not needed then they can point +to /dev/null, but they should always be open). If the user gives us +`--env-fd=2` or similar, we don't want to close fd 2 before exiting +unsuccessfully: that would give us nowhere to display the error message. + +Signed-off-by: Simon McVittie +(cherry picked from commit c4ab58cd2e66c4bcf193919ef9cbdce1dac042da) + +Origin: upstream, 1.16.6, commit:7f5306876adc6f322f88c01801fd5aab2742c7b4 +--- + app/flatpak-builtins-run.c | 26 +++++++++++++++++++++----- + common/flatpak-context.c | 6 +++++- + 2 files changed, 26 insertions(+), 6 deletions(-) + +diff --git a/app/flatpak-builtins-run.c b/app/flatpak-builtins-run.c +index 2b5eccc..574a2d5 100644 +--- a/app/flatpak-builtins-run.c ++++ b/app/flatpak-builtins-run.c +@@ -79,7 +79,11 @@ option_bind_fd_cb (const char *option_name, + return FALSE; + + if (fd < 3) +- return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ { ++ /* Don't close these fds! */ ++ fd = -1; ++ return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ } + + if (!flatpak_set_cloexec (fd)) + return glnx_throw_errno_prefix (error, "--bind-fd"); +@@ -102,7 +106,10 @@ option_ro_bind_fd_cb (const char *option_name, + return FALSE; + + if (fd < 3) +- return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ { ++ fd = -1; ++ return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ } + + if (!flatpak_set_cloexec (fd)) + return glnx_throw_errno_prefix (error, "--ro-bind-fd"); +@@ -125,7 +132,10 @@ opt_instance_id_fd_cb (const char *option_name, + return FALSE; + + if (fd < 3) +- return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ { ++ fd = -1; ++ return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ } + + if (!flatpak_set_cloexec (fd)) + return glnx_throw_errno_prefix (error, "--instance-id-fd"); +@@ -147,7 +157,10 @@ opt_app_fd_cb (const char *option_name, + return FALSE; + + if (fd < 3) +- return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ { ++ fd = -1; ++ return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ } + + if (!flatpak_set_cloexec (fd)) + return glnx_throw_errno_prefix (error, "--app-fd"); +@@ -169,7 +182,10 @@ opt_usr_fd_cb (const char *option_name, + return FALSE; + + if (fd < 3) +- return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ { ++ fd = -1; ++ return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ } + + if (!flatpak_set_cloexec (fd)) + return glnx_throw_errno_prefix (error, "--usr-fd"); +diff --git a/common/flatpak-context.c b/common/flatpak-context.c +index 014ed0b..a3e58ea 100644 +--- a/common/flatpak-context.c ++++ b/common/flatpak-context.c +@@ -1311,7 +1311,11 @@ option_env_fd_cb (const gchar *option_name, + return FALSE; + + if (fd < 3) +- return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ { ++ /* Don't close these fds! */ ++ fd = -1; ++ return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ } + + /* This is not strictly necessary, because we're going to close it after + * parsing the environment block, but let's be consistent with other fd diff -Nru flatpak-1.14.10/debian/patches/1.16.6/dir-In-apply_extra_data-don-t-assume-there-is-always-a-ru.patch flatpak-1.14.10/debian/patches/1.16.6/dir-In-apply_extra_data-don-t-assume-there-is-always-a-ru.patch --- flatpak-1.14.10/debian/patches/1.16.6/dir-In-apply_extra_data-don-t-assume-there-is-always-a-ru.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.6/dir-In-apply_extra_data-don-t-assume-there-is-always-a-ru.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,40 @@ +From: Simon McVittie +Date: Thu, 9 Apr 2026 18:47:40 +0100 +Subject: dir: In apply_extra_data(), don't assume there is always a runtime + +org.freedesktop.Platform.openh264 is one example of an extension that +runs a statically-linked extra-data helper, with no runtime. Only open +the runtime if there is one. + +Fixes: ac62ebe3 "run: Use O_PATH fds for the runtime and app deploy directories" +Signed-off-by: Simon McVittie +(cherry picked from commit c14ad3722940706730a76997c6925f9998106f90) +Origin: upstream, 1.16.6, commit:a140dd16297df7dee73bfd6c99c278d7a3fa580c +Bug: https://github.com/flatpak/flatpak/issues/6583 +--- + common/flatpak-dir.c | 12 ++++++++---- + 1 file changed, 8 insertions(+), 4 deletions(-) + +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index dafbb49..44180d0 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -8305,10 +8305,14 @@ apply_extra_data (FlatpakDir *self, + run_flags |= FLATPAK_RUN_FLAG_NO_PROC; + + glnx_autofd int usr_fd = -1; +- 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 (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)) diff -Nru flatpak-1.14.10/debian/patches/1.16.6/libtest-Allow-adding-a-new-ref-to-an-existing-temporary-o.patch flatpak-1.14.10/debian/patches/1.16.6/libtest-Allow-adding-a-new-ref-to-an-existing-temporary-o.patch --- flatpak-1.14.10/debian/patches/1.16.6/libtest-Allow-adding-a-new-ref-to-an-existing-temporary-o.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.6/libtest-Allow-adding-a-new-ref-to-an-existing-temporary-o.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,33 @@ +From: Simon McVittie +Date: Fri, 10 Apr 2026 11:38:12 +0100 +Subject: libtest: Allow adding a new ref to an existing temporary ostree repo + +When we run `tests/test-run-custom.sh` as a build-time test, +we expect to already have the necessary runtimes, apps, etc. in +`${builddir}/tests/runtime-repo`. However, when running "as-installed" +tests, we're using a fresh temporary ostree repo for each test. +Merely having the repo exist is not enough: for some tests, and in +particular `tests/test-run-custom.sh`, it needs to have more than one +runtime available. + +Signed-off-by: Simon McVittie +(cherry picked from commit 50dda82eb054695b3d3758d0a88ef68c8dd79dc4) +Origin: upstream, 1.16.6, commit:b7806fe7838e6a9114163fbcd20c11e7636d695f +Bug: https://github.com/flatpak/flatpak/issues/6591 +--- + tests/libtest.sh | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tests/libtest.sh b/tests/libtest.sh +index 36d39ac..8d47057 100644 +--- a/tests/libtest.sh ++++ b/tests/libtest.sh +@@ -308,7 +308,7 @@ make_runtime () { + RUNTIME_REPO=${TEST_DATA_DIR}/runtime-repo + ( + flock -s 200 +- if [ ! -d ${RUNTIME_REPO} ]; then ++ if [ ! -f "${RUNTIME_REPO}/refs/heads/${RUNTIME_REF}" ]; then + $(dirname $0)/make-test-runtime.sh ${RUNTIME_REPO} org.test.Platform ${BRANCH} "" "" > /dev/null + fi + ) 200>${TEST_DATA_DIR}/runtime-repo-lock diff -Nru flatpak-1.14.10/debian/patches/1.16.6/portal-Avoid-crash-if-sandbox-expose-ro-fd-is-out-of-rang.patch flatpak-1.14.10/debian/patches/1.16.6/portal-Avoid-crash-if-sandbox-expose-ro-fd-is-out-of-rang.patch --- flatpak-1.14.10/debian/patches/1.16.6/portal-Avoid-crash-if-sandbox-expose-ro-fd-is-out-of-rang.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.6/portal-Avoid-crash-if-sandbox-expose-ro-fd-is-out-of-rang.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,64 @@ +From: Simon McVittie +Date: Thu, 9 Apr 2026 20:16:16 +0100 +Subject: portal: Avoid crash if sandbox-expose-[ro-]fd is out of range + +If the handle is not in the range `0 <= handle < fds_len`, but no +GError is set, we'd have crashed when we dereferenced error->message. +Instead, log an error and early-return, matching what we do for +app-fd, usr-fd and the array of inheritable fds. + +Fixes: 3c500145 "portal: Use --bind-fd, --app-fd and --usr-fd options to avoid races" +Co-authored-by: Sebastian Wick +Signed-off-by: Simon McVittie +(cherry picked from commit 4ef2421bd22d8fbf8f17cf9bf5da1dd95aedef8d) +Origin: upstream, 1.16.6, commit:5f73d530a25a119222b42b0680766faba3875777 +Bug: https://github.com/flatpak/flatpak/issues/6584 +Bug-Debian: https://bugs.debian.org/1132968 +--- + portal/flatpak-portal.c | 26 ++++++++++++++++++++++---- + 1 file changed, 22 insertions(+), 4 deletions(-) + +diff --git a/portal/flatpak-portal.c b/portal/flatpak-portal.c +index a82cfdf..cf459e7 100644 +--- a/portal/flatpak-portal.c ++++ b/portal/flatpak-portal.c +@@ -1175,8 +1175,17 @@ handle_spawn (PortalFlatpak *object, + gint32 handle; + + g_variant_get_child (sandbox_expose_fd, i, "h", &handle); +- if (handle >= 0 && handle < fds_len && +- validate_opath_fd (fds[handle], TRUE, &error)) ++ if (handle >= fds_len || handle < 0) ++ { ++ g_debug ("Invalid sandbox-expose-fd handle %d", handle); ++ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, ++ G_DBUS_ERROR_INVALID_ARGS, ++ "No file descriptor for handle %d", ++ handle); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ if (validate_opath_fd (fds[handle], TRUE, &error)) + { + g_array_append_val (expose_fds, fds[handle]); + } +@@ -1201,8 +1210,17 @@ handle_spawn (PortalFlatpak *object, + gint32 handle; + + g_variant_get_child (sandbox_expose_fd_ro, i, "h", &handle); +- if (handle >= 0 && handle < fds_len && +- validate_opath_fd (fds[handle], FALSE, &error)) ++ if (handle >= fds_len || handle < 0) ++ { ++ g_debug ("Invalid sandbox-expose-ro-fd handle %d", handle); ++ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, ++ G_DBUS_ERROR_INVALID_ARGS, ++ "No file descriptor for handle %d", ++ handle); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ if (validate_opath_fd (fds[handle], FALSE, &error)) + { + g_array_append_val (expose_fds_ro, fds[handle]); + } diff -Nru flatpak-1.14.10/debian/patches/1.16.6/portal-Log-and-ignore-unusable-sandbox-expose-fds-instead.patch flatpak-1.14.10/debian/patches/1.16.6/portal-Log-and-ignore-unusable-sandbox-expose-fds-instead.patch --- flatpak-1.14.10/debian/patches/1.16.6/portal-Log-and-ignore-unusable-sandbox-expose-fds-instead.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.6/portal-Log-and-ignore-unusable-sandbox-expose-fds-instead.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,63 @@ +From: Simon McVittie +Date: Thu, 9 Apr 2026 20:24:48 +0100 +Subject: portal: Log and ignore unusable sandbox-expose fds instead of + erroring + +For the sandbox expose fds, a historical quirk of this code is that if +the checks in get_path_for_fd() failed, we would merely log at g_info() +level (usually only shown when debugging the portal), and otherwise +silently ignore the request to expose the fd in the sandbox. + +With hindsight this was probably not the right thing to do, but apps +could well be relying on it now. For example, there are indications +that Epiphany might send a memfd from the main instance to a subsandbox, +which never actually worked, but will break that subsandbox process +if that's treated as a fatal error. + +Fixes: 3c500145 "portal: Use --bind-fd, --app-fd and --usr-fd options to avoid races" +Co-authored-by: Sebastian Wick +Signed-off-by: Simon McVittie +(cherry picked from commit 75ab6eebb857fd26172613b69e55f04830ad0d82) +Origin: upstream, 1.16.6, commit:4b6ee5e5c9ca33638b238c15e865864ed1437fb8 +Bug: https://github.com/flatpak/flatpak/issues/6584 +Bug-Debian: https://bugs.debian.org/1132968 +--- + portal/flatpak-portal.c | 18 ++++++------------ + 1 file changed, 6 insertions(+), 12 deletions(-) + +diff --git a/portal/flatpak-portal.c b/portal/flatpak-portal.c +index cf459e7..d97f917 100644 +--- a/portal/flatpak-portal.c ++++ b/portal/flatpak-portal.c +@@ -1191,12 +1191,9 @@ handle_spawn (PortalFlatpak *object, + } + else + { +- g_debug ("Invalid sandbox expose fd: %s", error->message); +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, +- G_DBUS_ERROR_INVALID_ARGS, +- "No valid file descriptor for handle %d", +- handle); +- return G_DBUS_METHOD_INVOCATION_HANDLED; ++ g_info ("unable to validate sandbox-expose-fd %d, ignoring: %s", ++ fds[handle], error->message); ++ g_clear_error (&error); + } + } + } +@@ -1226,12 +1223,9 @@ handle_spawn (PortalFlatpak *object, + } + else + { +- g_debug ("Invalid sandbox expose ro fd: %s", error->message); +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, +- G_DBUS_ERROR_INVALID_ARGS, +- "No file descriptor for handle %d", +- handle); +- return G_DBUS_METHOD_INVOCATION_HANDLED; ++ g_info ("unable to validate sandbox-expose-ro-fd %d, ignoring: %s", ++ fds[handle], error->message); ++ g_clear_error (&error); + } + } + } diff -Nru flatpak-1.14.10/debian/patches/1.16.6/portal-Reinstate-flatpak_get_path_for_fd-checks.patch flatpak-1.14.10/debian/patches/1.16.6/portal-Reinstate-flatpak_get_path_for_fd-checks.patch --- flatpak-1.14.10/debian/patches/1.16.6/portal-Reinstate-flatpak_get_path_for_fd-checks.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.6/portal-Reinstate-flatpak_get_path_for_fd-checks.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,67 @@ +From: Simon McVittie +Date: Thu, 9 Apr 2026 20:28:57 +0100 +Subject: portal: Reinstate flatpak_get_path_for_fd() checks + +As with the previous commit, historically we would debug-log but +otherwise silently ignore attempts to expose a file in a sandboxed +subsandbox that doesn't have a suitable path. + +For example, org.gnome.Epiphany (or possibly WebKitGTK) asks to expose +files from /app and /usr in the subsandbox. When we ignored those +requests (because /app and /usr have a different meaning on the host +system), the app worked as intended anyway, because the subsandbox has +access to the app's /app and the runtime's /usr whether they're +explicitly added or not, so it all worked out OK. However, treating +this as a fatal error (as it arguably should have been) broke +Epiphany's subsandboxes. + +Fixes: 3c500145 "portal: Use --bind-fd, --app-fd and --usr-fd options to avoid races" +Co-authored-by: Sebastian Wick +Signed-off-by: Simon McVittie +(cherry picked from commit 28634c7f52e57df7091007973d1bb5e1f87f1e9d) +Origin: backport, 1.16.6, commit:0ef48d4a4bd7479d35c2526104292e36d9ea1500 +Bug: https://github.com/flatpak/flatpak/issues/6584 +Bug-Debian: https://bugs.debian.org/1132968 +--- + portal/flatpak-portal.c | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/portal/flatpak-portal.c b/portal/flatpak-portal.c +index d97f917..b0ba1f4 100644 +--- a/portal/flatpak-portal.c ++++ b/portal/flatpak-portal.c +@@ -557,7 +557,9 @@ validate_opath_fd (int fd, + { + int fd_flags; + struct stat st_buf; ++ struct stat real_st_buf; + int access_mode; ++ g_autofree char *path = NULL; + + /* Must be able to get fd flags */ + fd_flags = fcntl (fd, F_GETFL); +@@ -576,6 +578,24 @@ validate_opath_fd (int fd, + if (fstat (fd, &st_buf) < 0) + return glnx_throw_errno_prefix (error, "Failed to fstat"); + ++ path = flatpak_get_path_for_fd (fd, error); ++ if (path == NULL) ++ return FALSE; ++ ++ /* Verify that this is the same file as the app opened. ++ * Note that this is not security relevant because flatpak-run/bwrap will ++ * check things and abort if something is off. We do this only for backwards ++ * compatibility reasons: we need to be able to ignore the issue instead of ++ * aborting the entire sandbox setup later. */ ++ if (stat (path, &real_st_buf) < 0 || ++ st_buf.st_dev != real_st_buf.st_dev || ++ st_buf.st_ino != real_st_buf.st_ino) ++ { ++ /* Different files on the inside and the outside, reject the request */ ++ return glnx_throw (error, ++ "different file inside and outside sandbox"); ++ } ++ + access_mode = R_OK; + if (S_ISDIR (st_buf.st_mode)) + access_mode |= X_OK; diff -Nru flatpak-1.14.10/debian/patches/1.16.6/run-Cope-with-an-empty-runtime.patch flatpak-1.14.10/debian/patches/1.16.6/run-Cope-with-an-empty-runtime.patch --- flatpak-1.14.10/debian/patches/1.16.6/run-Cope-with-an-empty-runtime.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.6/run-Cope-with-an-empty-runtime.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,109 @@ +From: Simon McVittie +Date: Thu, 9 Apr 2026 18:45:04 +0100 +Subject: run: Cope with an empty runtime + +When FlatpakDir runs extra-data helpers in apply_extra_data(), +if the helper is statically linked, it might not need a runtime at all. +For example the helper for openh264 falls into this category. + +[smcv: Resolve conflicts with 1.14.x] + +Fixes: ac62ebe3 "run: Use O_PATH fds for the runtime and app deploy directories" +Signed-off-by: Simon McVittie +(cherry picked from commit aa1a54c9dae25fd13ebc936e06996f8db39f4aa5) +Origin: backport, 1.16.6, commit:b942152bde980cde51e933359e4ba3be527ae3ed +Bug: https://github.com/flatpak/flatpak/issues/6583 +--- + common/flatpak-run.c | 30 +++++++++++++++++++++++------- + 1 file changed, 23 insertions(+), 7 deletions(-) + +diff --git a/common/flatpak-run.c b/common/flatpak-run.c +index 0f3d122..0a83d9b 100644 +--- a/common/flatpak-run.c ++++ b/common/flatpak-run.c +@@ -2917,6 +2917,10 @@ flatpak_run_add_app_info_args (FlatpakBwrap *bwrap, + return TRUE; + } + ++/* ++ * @runtime_fd: the /usr for the runtime, or -1 if running with no runtime, ++ * perhaps to unpack extra-data ++ */ + static void + add_tzdata_args (FlatpakBwrap *bwrap, + int runtime_fd) +@@ -2929,6 +2933,8 @@ add_tzdata_args (FlatpakBwrap *bwrap, + glnx_autofd int zoneinfo_fd = -1; + g_autoptr(GError) error = NULL; + ++ g_return_if_fail (runtime_fd >= -1); ++ + raw_timezone = flatpak_get_timezone (); + timezone_content = g_strdup_printf ("%s\n", raw_timezone); + localtime_content = g_strconcat ("../usr/share/zoneinfo/", raw_timezone, NULL); +@@ -2937,10 +2943,11 @@ add_tzdata_args (FlatpakBwrap *bwrap, + + tzdir_fd = glnx_chaseat (AT_FDCWD, tzdir, GLNX_CHASE_MUST_BE_DIRECTORY, NULL); + +- zoneinfo_fd = glnx_chaseat (runtime_fd, "share/zoneinfo", +- GLNX_CHASE_RESOLVE_BENEATH | +- GLNX_CHASE_MUST_BE_DIRECTORY, +- NULL); ++ if (runtime_fd >= 0) ++ zoneinfo_fd = glnx_chaseat (runtime_fd, "share/zoneinfo", ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_DIRECTORY, ++ NULL); + + /* Check for host /usr/share/zoneinfo */ + if (tzdir_fd >= 0 && zoneinfo_fd >= 0) +@@ -2951,7 +2958,7 @@ add_tzdata_args (FlatpakBwrap *bwrap, + "--symlink", localtime_content, "/etc/localtime", + NULL); + } +- else ++ else if (runtime_fd >= 0) + { + g_autofree char *runtime_zoneinfo = NULL; + glnx_autofd int runtime_zoneinfo_fd = -1; +@@ -3448,6 +3455,10 @@ setup_seccomp (FlatpakBwrap *bwrap, + } + #endif + ++/* ++ * @runtime_fd: the /usr for the runtime, or -1 if running with no runtime, ++ * perhaps to unpack extra-data ++ */ + static void + flatpak_run_setup_usr_links (FlatpakBwrap *bwrap, + int runtime_fd, +@@ -3501,6 +3512,10 @@ flatpak_run_setup_usr_links (FlatpakBwrap *bwrap, + } + } + ++/* ++ * @runtime_fd: the /usr for the runtime, or -1 if running with no runtime, ++ * perhaps to unpack extra-data ++ */ + gboolean + flatpak_run_setup_base_argv (FlatpakBwrap *bwrap, + int runtime_fd, +@@ -3517,7 +3532,7 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap, + gulong pers; + gid_t gid = getgid (); + +- g_return_val_if_fail (runtime_fd >= 0, FALSE); ++ g_return_val_if_fail (runtime_fd >= -1, FALSE); + + run_dir = g_strdup_printf ("/run/user/%d", getuid ()); + +@@ -3592,7 +3607,8 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap, + else if (g_file_test ("/var/lib/dbus/machine-id", G_FILE_TEST_EXISTS)) + flatpak_bwrap_add_args (bwrap, "--ro-bind", "/var/lib/dbus/machine-id", "/etc/machine-id", NULL); + +- if ((flags & FLATPAK_RUN_FLAG_WRITABLE_ETC) == 0) ++ if (runtime_fd >= 0 ++ && (flags & FLATPAK_RUN_FLAG_WRITABLE_ETC) == 0) + { + g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; + struct dirent *dent; diff -Nru flatpak-1.14.10/debian/patches/1.16.6/run-context-Mark-fd-arguments-as-close-on-exec.patch flatpak-1.14.10/debian/patches/1.16.6/run-context-Mark-fd-arguments-as-close-on-exec.patch --- flatpak-1.14.10/debian/patches/1.16.6/run-context-Mark-fd-arguments-as-close-on-exec.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.6/run-context-Mark-fd-arguments-as-close-on-exec.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,100 @@ +From: Simon McVittie +Date: Fri, 10 Apr 2026 10:07:14 +0100 +Subject: run, context: Mark fd arguments as close-on-exec + +On entry to `flatpak run`, these fds have been inheritable (not +FD_CLOEXEC), otherwise they would not have been inherited; but we don't +want the "payload" command to inherit them, so set them as +non-close-on-exec as soon as we receive them. In the cases where we pass +them down to the underlying bwrap command, we'll either dup them, or +set them to be inheritable again (in practice we dup them). + +In particular, Chromium-derived web browsers get very upset when their +subsandbox processes inherit unexpected fds, which has been causing crashes +with no useful diagnostic information since CVE-2026-34078 was fixed. + +Fixes: 1b5e886d "run: Add --usr-fd and --app-fd options" +Fixes: b5ae89ed "run: Add --(ro-)bind-fd options" +Signed-off-by: Simon McVittie +(cherry picked from commit 0902090726c2e51b1c6f22c64d708a4895a196e7) +Origin: upstream, 1.16.6, commit:a58978e40e7650c9c2cda716dd6d387fd6b5ea8e +Bug: https://github.com/flatpak/flatpak/issues/6582 +Bug-Debian: https://bugs.debian.org/1132968 +--- + app/flatpak-builtins-run.c | 15 +++++++++++++++ + common/flatpak-context.c | 8 ++++++++ + 2 files changed, 23 insertions(+) + +diff --git a/app/flatpak-builtins-run.c b/app/flatpak-builtins-run.c +index 5c0cda2..2b5eccc 100644 +--- a/app/flatpak-builtins-run.c ++++ b/app/flatpak-builtins-run.c +@@ -81,6 +81,9 @@ option_bind_fd_cb (const char *option_name, + if (fd < 3) + return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); + ++ if (!flatpak_set_cloexec (fd)) ++ return glnx_throw_errno_prefix (error, "--bind-fd"); ++ + g_array_append_val (opt_bind_fds, fd); + fd = -1; /* ownership transferred to GArray */ + return TRUE; +@@ -101,6 +104,9 @@ option_ro_bind_fd_cb (const char *option_name, + if (fd < 3) + return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); + ++ if (!flatpak_set_cloexec (fd)) ++ return glnx_throw_errno_prefix (error, "--ro-bind-fd"); ++ + g_array_append_val (opt_ro_bind_fds, fd); + fd = -1; /* ownership transferred to GArray */ + return TRUE; +@@ -121,6 +127,9 @@ opt_instance_id_fd_cb (const char *option_name, + if (fd < 3) + return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); + ++ if (!flatpak_set_cloexec (fd)) ++ return glnx_throw_errno_prefix (error, "--instance-id-fd"); ++ + opt_instance_id_fd = g_steal_fd (&fd); + return TRUE; + } +@@ -140,6 +149,9 @@ opt_app_fd_cb (const char *option_name, + if (fd < 3) + return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); + ++ if (!flatpak_set_cloexec (fd)) ++ return glnx_throw_errno_prefix (error, "--app-fd"); ++ + opt_app_fd = g_steal_fd (&fd); + return TRUE; + } +@@ -159,6 +171,9 @@ opt_usr_fd_cb (const char *option_name, + if (fd < 3) + return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); + ++ if (!flatpak_set_cloexec (fd)) ++ return glnx_throw_errno_prefix (error, "--usr-fd"); ++ + opt_usr_fd = g_steal_fd (&fd); + return TRUE; + } +diff --git a/common/flatpak-context.c b/common/flatpak-context.c +index 7fe1bff..014ed0b 100644 +--- a/common/flatpak-context.c ++++ b/common/flatpak-context.c +@@ -1313,6 +1313,14 @@ option_env_fd_cb (const gchar *option_name, + if (fd < 3) + return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); + ++ /* This is not strictly necessary, because we're going to close it after ++ * parsing the environment block, but let's be consistent with other fd ++ * arguments that we need to avoid being inherited by the "payload" ++ * command. This is also a convenient place to validate that it's an ++ * open fd. */ ++ if (!flatpak_set_cloexec (fd)) ++ return glnx_throw_errno_prefix (error, "--env-fd"); ++ + return flatpak_context_parse_env_fd (context, fd, error); + } + diff -Nru flatpak-1.14.10/debian/patches/1.16.6/tests-Add-test-extra-data.sh-to-test-extra-data-installat.patch flatpak-1.14.10/debian/patches/1.16.6/tests-Add-test-extra-data.sh-to-test-extra-data-installat.patch --- flatpak-1.14.10/debian/patches/1.16.6/tests-Add-test-extra-data.sh-to-test-extra-data-installat.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.6/tests-Add-test-extra-data.sh-to-test-extra-data-installat.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,119 @@ +From: Sebastian Wick +Date: Thu, 21 Aug 2025 18:40:33 +0200 +Subject: tests: Add test-extra-data.sh to test extra-data installation + +[smcv: Remove OCI test because #3790 was only solved in 1.17.x, +only keep libostree test] + +[smcv: Add to the old Autotools build system] + +(cherry picked from commit e9e713fa0ddd0d1af4b8cdec6aa9124d471ed33e) +Origin: backport, 1.16.6, commit:b942152bde980cde51e933359e4ba3be527ae3ed +Bug: https://github.com/flatpak/flatpak/issues/6583 +--- + tests/Makefile-test-matrix.am.inc | 3 ++ + tests/test-extra-data.sh | 63 +++++++++++++++++++++++++++++++++++++++ + tests/update-test-matrix | 1 + + 3 files changed, 67 insertions(+) + create mode 100755 tests/test-extra-data.sh + +diff --git a/tests/Makefile-test-matrix.am.inc b/tests/Makefile-test-matrix.am.inc +index a2e797f..98cd229 100644 +--- a/tests/Makefile-test-matrix.am.inc ++++ b/tests/Makefile-test-matrix.am.inc +@@ -30,6 +30,8 @@ TEST_MATRIX= \ + tests/test-summaries@system.wrap \ + tests/test-subset@user.wrap \ + tests/test-subset@system.wrap \ ++ tests/test-extra-data@user.wrap \ ++ tests/test-extra-data@system.wrap \ + $(NULL) + TEST_MATRIX_DIST= \ + tests/test-basic.sh \ +@@ -61,4 +63,5 @@ TEST_MATRIX_EXTRA_DIST= \ + tests/test-update-portal.sh \ + tests/test-summaries.sh \ + tests/test-subset.sh \ ++ tests/test-extra-data.sh \ + $(NULL) +diff --git a/tests/test-extra-data.sh b/tests/test-extra-data.sh +new file mode 100755 +index 0000000..41c9b91 +--- /dev/null ++++ b/tests/test-extra-data.sh +@@ -0,0 +1,63 @@ ++#!/bin/bash ++# ++# Copyright (C) 2025 Red Hat, Inc ++# ++# This library is free software; you can redistribute it and/or ++# modify it under the terms of the GNU Lesser General Public ++# License as published by the Free Software Foundation; either ++# version 2 of the License, or (at your option) any later version. ++# ++# This library is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# Lesser General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public ++# License along with this library; if not, write to the ++# Free Software Foundation, Inc., 59 Temple Place - Suite 330, ++# Boston, MA 02111-1307, USA. ++ ++set -euo pipefail ++ ++. "$(dirname $0)/libtest.sh" ++ ++skip_without_bwrap ++ ++REPONAME="test" ++BRANCH="master" ++COLLECTION_ID="org.test.Collection.${REPONAME}" ++ ++setup_repo ${REPONAME} ${COLLECTION_ID} ++ ++# create the extra data ++EXTRA_DATA_FILE="extra-data-test" ++EXTRA_DATA_DIR="${TEST_DATA_DIR}/extra-data-server/" ++mkdir -p "${EXTRA_DATA_DIR}" ++echo "extra-data-test-content" > "${EXTRA_DATA_DIR}/${EXTRA_DATA_FILE}" ++ ++# serve the extra data ++httpd web-server.py "${EXTRA_DATA_DIR}" ++EXTRA_DATA_URL="http://127.0.0.1:$(cat httpd-port)/${EXTRA_DATA_FILE}" ++ ++# download to get the size and sha256 sum ++DOWNLOADED_EXTRA_DATA="${TEST_DATA_DIR}/downloaded-extra-data" ++curl "${EXTRA_DATA_URL}" -o "${DOWNLOADED_EXTRA_DATA}" ++EXTRA_DATA_SIZE=$(stat --printf="%s" "${DOWNLOADED_EXTRA_DATA}") ++EXTRA_DATA_SHA256=$(sha256sum "${DOWNLOADED_EXTRA_DATA}" | cut -f1 -d' ') ++ ++echo "1..1" ++ ++# build the app with the extra data ++EXTRA_DATA="--extra-data=test:${EXTRA_DATA_SHA256}:${EXTRA_DATA_SIZE}:${EXTRA_DATA_SIZE}:${EXTRA_DATA_URL}" ++BUILD_FINISH_ARGS=${EXTRA_DATA} make_updated_app ${REPONAME} ${COLLECTION_ID} ${BRANCH} UPDATE1 ++ ++# ensure it installs correctly ++install_repo ${REPONAME} ${BRANCH} ++ ++# ensure the right extra-data got downloaded ++${FLATPAK} run --command=sh org.test.Hello -c "cat /app/extra/test" > out ++assert_file_has_content out "extra-data-test-content" ++ ++${FLATPAK} ${U} uninstall -y org.test.Hello >&2 ++ ++ok "install extra data app with ostree" +diff --git a/tests/update-test-matrix b/tests/update-test-matrix +index cf76c87..f167f61 100755 +--- a/tests/update-test-matrix ++++ b/tests/update-test-matrix +@@ -35,6 +35,7 @@ TEST_MATRIX_SOURCE=( + 'tests/test-prune.sh' \ + 'tests/test-seccomp.sh' \ + 'tests/test-repair.sh' \ ++ 'tests/test-extra-data.sh{user+system}' \ + ) + + "${tests_srcdir}/expand-test-matrix.sh" --automake "${TEST_MATRIX_SOURCE[*]}" > "${tests_srcdir}/Makefile-test-matrix.am.inc" diff -Nru flatpak-1.14.10/debian/patches/1.16.6/tests-Check-that-flatpak-run-fd-arguments-do-not-leak-to-.patch flatpak-1.14.10/debian/patches/1.16.6/tests-Check-that-flatpak-run-fd-arguments-do-not-leak-to-.patch --- flatpak-1.14.10/debian/patches/1.16.6/tests-Check-that-flatpak-run-fd-arguments-do-not-leak-to-.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.6/tests-Check-that-flatpak-run-fd-arguments-do-not-leak-to-.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,59 @@ +From: Sebastian Wick +Date: Fri, 10 Apr 2026 15:44:44 +0200 +Subject: tests: Check that flatpak-run fd-arguments do not leak to the + command + +flatpak-run takes a number of arguments which are file descriptor +numbers. Those file descriptors are supposed to set something up in the +way the instance gets spawned, but should never make it to the wrapper +command. + +(cherry picked from commit 136452768368613f3712c2f7cba79a7c3e7bee96) + +Origin: upstream, 1.16.6, commit:4491406f509707feb755982fdda24b2fd9234903 +--- + tests/test-run-custom.sh | 25 +++++++++++++++++++++++-- + 1 file changed, 23 insertions(+), 2 deletions(-) + +diff --git a/tests/test-run-custom.sh b/tests/test-run-custom.sh +index 94c3241..4868b8b 100755 +--- a/tests/test-run-custom.sh ++++ b/tests/test-run-custom.sh +@@ -24,7 +24,7 @@ set -euo pipefail + skip_without_bwrap + skip_revokefs_without_fuse + +-echo "1..11" ++echo "1..12" + + # Use stable rather than master as the branch so we can test that the run + # command automatically finds the branch correctly +@@ -176,4 +176,25 @@ exec 3< "${path}" + ! 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" +\ No newline at end of file ++ok "bind-fd and ro-bind-fd" ++ ++exec 3< custom-app/files ++exec 4< custom-runtime/files ++exec 5< "${path}" ++exec 6< "${path}" ++run --app-fd=3 --usr-fd=4 --bind-fd=5 --ro-bind-fd=6 \ ++ --command=sh org.test.Hello \ ++ -c 'for fd in $(ls /proc/self/fd); do readlink -f /proc/self/fd/$fd; done' > hello_out ++exec 6>&- ++exec 5>&- ++exec 4>&- ++exec 3>&- ++ ++wd="$(readlink -f .)" ++while read fdpath; do ++ if [[ "$fdpath" == "$wd"* && "$fdpath" != "$wd/hello_out" ]]; then ++ assert_not_reached "A fd for '$fdpath' unexpectedly made it to the app" ++ fi ++done < hello_out ++ ++ok "check no fd leak" +\ No newline at end of file diff -Nru flatpak-1.14.10/debian/patches/1.16.6/utils-Add-flatpak_set_cloexec.patch flatpak-1.14.10/debian/patches/1.16.6/utils-Add-flatpak_set_cloexec.patch --- flatpak-1.14.10/debian/patches/1.16.6/utils-Add-flatpak_set_cloexec.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.6/utils-Add-flatpak_set_cloexec.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,50 @@ +From: Simon McVittie +Date: Fri, 10 Apr 2026 09:58:05 +0100 +Subject: utils: Add flatpak_set_cloexec() + +Signed-off-by: Simon McVittie +(cherry picked from commit 8a989c790d9121f53ada88fd001a3997b9e40632) +Origin: upstream, 1.16.6, commit:90625f0ef612364ded91a67327bdfe5b59e03b27 +Bug: https://github.com/flatpak/flatpak/issues/6582 +Bug-Debian: https://bugs.debian.org/1132968 +--- + common/flatpak-utils-private.h | 2 ++ + common/flatpak-utils.c | 17 +++++++++++++++++ + 2 files changed, 19 insertions(+) + +diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h +index 800ce5e..0a29bd5 100644 +--- a/common/flatpak-utils-private.h ++++ b/common/flatpak-utils-private.h +@@ -949,4 +949,6 @@ int flatpak_parse_fd (const char *fd_string, + + #define FLATPAK_MESSAGE_ID "c7b39b1e006b464599465e105b361485" + ++gboolean flatpak_set_cloexec (int fd); ++ + #endif /* __FLATPAK_UTILS_H__ */ +diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c +index 6706aaa..46a2b4c 100644 +--- a/common/flatpak-utils.c ++++ b/common/flatpak-utils.c +@@ -9471,3 +9471,20 @@ flatpak_parse_fd (const char *fd_string, + + return fd; + } ++ ++/* Sets errno on failure. */ ++gboolean ++flatpak_set_cloexec (int fd) ++{ ++ int flags = fcntl (fd, F_GETFD); ++ ++ if (flags == -1) ++ return FALSE; ++ ++ flags |= FD_CLOEXEC; ++ ++ if (fcntl (fd, F_SETFD, flags) < 0) ++ return FALSE; ++ ++ return TRUE; ++} diff -Nru flatpak-1.14.10/debian/patches/1.16.6/utils-Move-flatpak_get_path_for_fd-to-here.patch flatpak-1.14.10/debian/patches/1.16.6/utils-Move-flatpak_get_path_for_fd-to-here.patch --- flatpak-1.14.10/debian/patches/1.16.6/utils-Move-flatpak_get_path_for_fd-to-here.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.6/utils-Move-flatpak_get_path_for_fd-to-here.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,177 @@ +From: Simon McVittie +Date: Thu, 9 Apr 2026 20:06:18 +0100 +Subject: utils: Move flatpak_get_path_for_fd to here + +This was originally in flatpak-portal, then was duplicated into +flatpak-run in commit ac62ebe3 "run: Use O_PATH fds for the runtime and +app deploy directories", and subsequently removed from the portal in +commit 3c500145 "portal: Use --bind-fd, --app-fd and --usr-fd options to +avoid races". Now we want to use it in the portal again. + +Co-authored-by: Sebastian Wick +Signed-off-by: Simon McVittie +(cherry picked from commit 15dc818874514ffbece4c080405353ed396b54a9) +Origin: upstream, 1.16.6, commit:ccaf86c854aeed47c2b346666b14e52d0f821b7f +Bug: https://github.com/flatpak/flatpak/issues/6584 +Bug-Debian: https://bugs.debian.org/1132968 +--- + common/flatpak-run.c | 44 ++++--------------------------------- + common/flatpak-utils-private.h | 3 +++ + common/flatpak-utils.c | 50 ++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 57 insertions(+), 40 deletions(-) + +diff --git a/common/flatpak-run.c b/common/flatpak-run.c +index 0a83d9b..19196f5 100644 +--- a/common/flatpak-run.c ++++ b/common/flatpak-run.c +@@ -4175,42 +4175,6 @@ open_namespace_fd_if_needed (const char *path, + return -1; + } + +-static char * +-get_path_for_fd (int fd, +- GError **error) +-{ +- g_autofree char *proc_path = NULL; +- g_autofree char *path = NULL; +- +- proc_path = g_strdup_printf ("/proc/self/fd/%d", fd); +- path = glnx_readlinkat_malloc (AT_FDCWD, proc_path, NULL, error); +- if (path == NULL) +- return NULL; +- +- /* All normal paths start with /, but some weird things +- don't, such as socket:[27345] or anon_inode:[eventfd]. +- We don't support any of these */ +- if (path[0] != '/') +- { +- return glnx_null_throw (error, "%s resolves to non-absolute path %s", +- proc_path, path); +- } +- +- /* File descriptors to actually deleted files have " (deleted)" +- appended to them. This also happens to some fake fd types +- like shmem which are "/ (deleted)". All such +- files are considered invalid. Unfortunately this also +- matches files with filenames that actually end in " (deleted)", +- but there is not much to do about this. */ +- if (g_str_has_suffix (path, " (deleted)")) +- { +- return glnx_null_throw (error, "%s resolves to deleted path %s", +- proc_path, path); +- } +- +- return g_steal_pointer (&path); +-} +- + gboolean + flatpak_run_app (FlatpakDecomposed *app_ref, + FlatpakDeploy *app_deploy, +@@ -4427,7 +4391,7 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + { + g_autofree char *path = NULL; + +- path = get_path_for_fd (custom_runtime_fd, &my_error); ++ path = flatpak_get_path_for_fd (custom_runtime_fd, &my_error); + if (path == NULL) + { + return flatpak_fail_error (error, FLATPAK_ERROR, +@@ -4555,7 +4519,7 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + { + g_autofree char *path = NULL; + +- path = get_path_for_fd (custom_app_fd, error); ++ path = flatpak_get_path_for_fd (custom_app_fd, error); + if (path == NULL) + return glnx_prefix_error (error, "Cannot convert custom app fd to path"); + +@@ -4861,7 +4825,7 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + + /* We get the path the fd refers to, to determine to mount point + * destination inside the sandbox */ +- path = get_path_for_fd (fd, error); ++ path = flatpak_get_path_for_fd (fd, error); + if (!path) + return FALSE; + +@@ -4878,7 +4842,7 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + + /* We get the path the fd refers to, to determine to mount point + * destination inside the sandbox */ +- path = get_path_for_fd (fd, error); ++ path = flatpak_get_path_for_fd (fd, error); + if (!path) + return FALSE; + +diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h +index 0a29bd5..8199151 100644 +--- a/common/flatpak-utils-private.h ++++ b/common/flatpak-utils-private.h +@@ -947,6 +947,9 @@ gboolean running_under_sudo (void); + int flatpak_parse_fd (const char *fd_string, + GError **error); + ++char * flatpak_get_path_for_fd (int fd, ++ GError **error); ++ + #define FLATPAK_MESSAGE_ID "c7b39b1e006b464599465e105b361485" + + gboolean flatpak_set_cloexec (int fd); +diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c +index 46a2b4c..ac3a8d8 100644 +--- a/common/flatpak-utils.c ++++ b/common/flatpak-utils.c +@@ -9488,3 +9488,53 @@ flatpak_set_cloexec (int fd) + + return TRUE; + } ++ ++/* ++ * Attempt to discover the filesystem path corresponding to @fd. ++ * ++ * If @fd points to an existing file, return the absolute path of that ++ * file in the environment where it was opened. Note that this is not ++ * necessarily a valid path in the current namespace, if it was ++ * transferred via fd-passing from a process in a different filesystem ++ * namespace. ++ * ++ * If @fd points to a deleted file, or to a socket, fifo, memfd or similar ++ * non-filesystem object, set an error and return %NULL. ++ * ++ * Returns: (type filename) (transfer full) (nullable): ++ */ ++char * ++flatpak_get_path_for_fd (int fd, ++ GError **error) ++{ ++ g_autofree char *proc_path = NULL; ++ g_autofree char *path = NULL; ++ ++ proc_path = g_strdup_printf ("/proc/self/fd/%d", fd); ++ path = glnx_readlinkat_malloc (AT_FDCWD, proc_path, NULL, error); ++ if (path == NULL) ++ return NULL; ++ ++ /* All normal paths start with /, but some weird things ++ don't, such as socket:[27345] or anon_inode:[eventfd]. ++ We don't support any of these */ ++ if (path[0] != '/') ++ { ++ return glnx_null_throw (error, "%s resolves to non-absolute path %s", ++ proc_path, path); ++ } ++ ++ /* File descriptors to actually deleted files have " (deleted)" ++ appended to them. This also happens to some fake fd types ++ like shmem which are "/ (deleted)". All such ++ files are considered invalid. Unfortunately this also ++ matches files with filenames that actually end in " (deleted)", ++ but there is not much to do about this. */ ++ if (g_str_has_suffix (path, " (deleted)")) ++ { ++ return glnx_null_throw (error, "%s resolves to deleted path %s", ++ proc_path, path); ++ } ++ ++ return g_steal_pointer (&path); ++} diff -Nru flatpak-1.14.10/debian/patches/1.16.7/bwrap-Clarify-a-comment.patch flatpak-1.14.10/debian/patches/1.16.7/bwrap-Clarify-a-comment.patch --- flatpak-1.14.10/debian/patches/1.16.7/bwrap-Clarify-a-comment.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/1.16.7/bwrap-Clarify-a-comment.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,30 @@ +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 7cb1be5..6bfb089 100644 +--- a/common/flatpak-bwrap.c ++++ b/common/flatpak-bwrap.c +@@ -522,7 +522,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.14.10/debian/patches/CVE-2026-34078/flatpak-bwrap-Add-dup-ing-variant-flatpak_bwrap_add_args_.patch flatpak-1.14.10/debian/patches/CVE-2026-34078/flatpak-bwrap-Add-dup-ing-variant-flatpak_bwrap_add_args_.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078/flatpak-bwrap-Add-dup-ing-variant-flatpak_bwrap_add_args_.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078/flatpak-bwrap-Add-dup-ing-variant-flatpak_bwrap_add_args_.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,61 @@ +From: Sebastian Wick +Date: Fri, 6 Feb 2026 17:56:21 +0100 +Subject: flatpak-bwrap: Add dup-ing variant + flatpak_bwrap_add_args_data_fd_dup + +Origin: upstream, 1.16.4, commit:36bd977772ea0df7159ff207f89564bfccd3b0ca +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + common/flatpak-bwrap-private.h | 5 +++++ + common/flatpak-bwrap.c | 20 ++++++++++++++++++++ + 2 files changed, 25 insertions(+) + +diff --git a/common/flatpak-bwrap-private.h b/common/flatpak-bwrap-private.h +index 207d23a..64bd26b 100644 +--- a/common/flatpak-bwrap-private.h ++++ b/common/flatpak-bwrap-private.h +@@ -62,6 +62,11 @@ void flatpak_bwrap_append_bwrap (FlatpakBwrap *bwrap, + FlatpakBwrap *other); /* Steals the fds */ + void flatpak_bwrap_append_args (FlatpakBwrap *bwrap, + GPtrArray *other_array); ++gboolean flatpak_bwrap_add_args_data_fd_dup (FlatpakBwrap *bwrap, ++ const char *op, ++ int fd, ++ const char *path_optional, ++ GError **error); + void flatpak_bwrap_add_args_data_fd (FlatpakBwrap *bwrap, + const char *op, + int fd, +diff --git a/common/flatpak-bwrap.c b/common/flatpak-bwrap.c +index 348b645..13c6856 100644 +--- a/common/flatpak-bwrap.c ++++ b/common/flatpak-bwrap.c +@@ -141,6 +141,26 @@ flatpak_bwrap_add_fd (FlatpakBwrap *bwrap, + g_array_append_val (bwrap->fds, fd); + } + ++gboolean ++flatpak_bwrap_add_args_data_fd_dup (FlatpakBwrap *bwrap, ++ const char *op, ++ int fd, ++ const char *path_optional, ++ GError **error) ++{ ++ glnx_autofd int fd_dup = -1; ++ ++ fd_dup = fcntl (fd, F_DUPFD_CLOEXEC, 3); ++ if (fd_dup < 0) ++ return glnx_throw_errno_prefix (error, "Failed to dup fd %d", fd); ++ ++ flatpak_bwrap_add_args_data_fd (bwrap, ++ op, ++ g_steal_fd (&fd_dup), ++ path_optional); ++ return TRUE; ++} ++ + void + flatpak_bwrap_add_arg_printf (FlatpakBwrap *bwrap, const char *format, ...) + { diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078/flatpak-bwrap-Use-glnx_close_fd-as-clear-func.patch flatpak-1.14.10/debian/patches/CVE-2026-34078/flatpak-bwrap-Use-glnx_close_fd-as-clear-func.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078/flatpak-bwrap-Use-glnx_close_fd-as-clear-func.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078/flatpak-bwrap-Use-glnx_close_fd-as-clear-func.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,49 @@ +From: Sebastian Wick +Date: Fri, 6 Feb 2026 16:32:50 +0100 +Subject: flatpak-bwrap: Use glnx_close_fd as clear func + +We already have a function which clears a fd that a pointer points to, +so let's use it instead of duplicating the code. + +Will become useful in a later commit as well. + +Origin: upstream, 1.16.4, commit:5a7e677294c92c04ea8c7c09a2b1abd2988f03df +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + common/flatpak-bwrap.c | 13 ++----------- + 1 file changed, 2 insertions(+), 11 deletions(-) + +diff --git a/common/flatpak-bwrap.c b/common/flatpak-bwrap.c +index 13c6856..7cb1be5 100644 +--- a/common/flatpak-bwrap.c ++++ b/common/flatpak-bwrap.c +@@ -41,15 +41,6 @@ + #include "flatpak-utils-private.h" + #include "flatpak-utils-base-private.h" + +-static void +-clear_fd (gpointer data) +-{ +- int *fd_p = data; +- +- if (fd_p != NULL && *fd_p != -1) +- close (*fd_p); +-} +- + char *flatpak_bwrap_empty_env[] = { NULL }; + + FlatpakBwrap * +@@ -59,9 +50,9 @@ flatpak_bwrap_new (char **env) + + bwrap->argv = g_ptr_array_new_with_free_func (g_free); + bwrap->noinherit_fds = g_array_new (FALSE, TRUE, sizeof (int)); +- g_array_set_clear_func (bwrap->noinherit_fds, clear_fd); ++ g_array_set_clear_func (bwrap->noinherit_fds, (GDestroyNotify) glnx_close_fd); + bwrap->fds = g_array_new (FALSE, TRUE, sizeof (int)); +- g_array_set_clear_func (bwrap->fds, clear_fd); ++ g_array_set_clear_func (bwrap->fds, (GDestroyNotify) glnx_close_fd); + + if (env) + bwrap->envp = g_strdupv (env); diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078/portal-Use-bind-fd-app-fd-and-usr-fd-options-to-avoid-rac.patch flatpak-1.14.10/debian/patches/CVE-2026-34078/portal-Use-bind-fd-app-fd-and-usr-fd-options-to-avoid-rac.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078/portal-Use-bind-fd-app-fd-and-usr-fd-options-to-avoid-rac.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078/portal-Use-bind-fd-app-fd-and-usr-fd-options-to-avoid-rac.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,603 @@ +From: Sebastian Wick +Date: Fri, 6 Feb 2026 21:03:58 +0100 +Subject: portal: Use --bind-fd, --app-fd and --usr-fd options to avoid races + +Now that flatpak_run_app accepts fds for app and runtime deploy, as well +as bind and ro-bind fds, and flatpak-run exposes the functionality, we +can finally hook this all up to the flatpak portal! + +[smcv: Resolve conflicts with 1.14.x] + +Origin: backport, 1.16.4, commit:c82a3d7716bd02961d8c68b1d18256dfea19efc5 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + portal/flatpak-portal.c | 438 ++++++++++++++++++------------------------------ + 1 file changed, 162 insertions(+), 276 deletions(-) + +diff --git a/portal/flatpak-portal.c b/portal/flatpak-portal.c +index b32e5e4..6821831 100644 +--- a/portal/flatpak-portal.c ++++ b/portal/flatpak-portal.c +@@ -551,195 +551,60 @@ child_setup_func (gpointer user_data) + } + + static gboolean +-is_valid_expose (const char *expose, +- GError **error) ++validate_opath_fd (int fd, ++ gboolean needs_writable, ++ GError **error) + { +- /* No subdirs or absolute paths */ +- if (expose[0] == '/') +- { +- g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, +- "Invalid sandbox expose: absolute paths not allowed"); +- return FALSE; +- } +- else if (strchr (expose, '/')) +- { +- g_set_error (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, +- "Invalid sandbox expose: subdirectories not allowed"); +- return FALSE; +- } +- +- return TRUE; +-} +- +-static char * +-filesystem_arg (const char *path, +- gboolean readonly) +-{ +- g_autoptr(GString) s = g_string_new ("--filesystem="); +- const char *p; +- +- for (p = path; *p != 0; p++) +- { +- if (*p == ':') +- g_string_append (s, "\\:"); +- else +- g_string_append_c (s, *p); +- } +- +- if (readonly) +- g_string_append (s, ":ro"); +- +- return g_string_free (g_steal_pointer (&s), FALSE); +-} +- +- +-static char * +-filesystem_sandbox_arg (const char *path, +- const char *sandbox, +- gboolean readonly) +-{ +- g_autoptr(GString) s = g_string_new ("--filesystem="); +- const char *p; +- +- for (p = path; *p != 0; p++) +- { +- if (*p == ':') +- g_string_append (s, "\\:"); +- else +- g_string_append_c (s, *p); +- } +- +- g_string_append (s, "/sandbox/"); +- +- for (p = sandbox; *p != 0; p++) +- { +- if (*p == ':') +- g_string_append (s, "\\:"); +- else +- g_string_append_c (s, *p); +- } +- +- if (readonly) +- g_string_append (s, ":ro"); +- +- return g_string_free (g_steal_pointer (&s), FALSE); +-} +- +-static char * +-bubblewrap_remap_path (const char *path) +-{ +- if (g_str_has_prefix (path, "/newroot/")) +- path = path + strlen ("/newroot"); +- return g_strdup (path); +-} +- +-static char * +-verify_proc_self_fd (const char *proc_path, +- GError **error) +-{ +- char path_buffer[PATH_MAX + 1]; +- ssize_t symlink_size; +- +- symlink_size = readlink (proc_path, path_buffer, PATH_MAX); +- if (symlink_size < 0) +- return glnx_null_throw_errno_prefix (error, "readlink"); +- +- path_buffer[symlink_size] = 0; +- +- /* All normal paths start with /, but some weird things +- don't, such as socket:[27345] or anon_inode:[eventfd]. +- We don't support any of these */ +- if (path_buffer[0] != '/') +- return glnx_null_throw (error, "%s resolves to non-absolute path %s", +- proc_path, path_buffer); +- +- /* File descriptors to actually deleted files have " (deleted)" +- appended to them. This also happens to some fake fd types +- like shmem which are "/ (deleted)". All such +- files are considered invalid. Unfortunatelly this also +- matches files with filenames that actually end in " (deleted)", +- but there is not much to do about this. */ +- if (g_str_has_suffix (path_buffer, " (deleted)")) +- return glnx_null_throw (error, "%s resolves to deleted path %s", +- proc_path, path_buffer); +- +- /* remap from sandbox to host if needed */ +- return bubblewrap_remap_path (path_buffer); +-} +- +-static char * +-get_path_for_fd (int fd, +- gboolean *writable_out, +- GError **error) +-{ +- g_autofree char *proc_path = NULL; + int fd_flags; + struct stat st_buf; +- struct stat real_st_buf; +- g_autofree char *path = NULL; +- gboolean writable = FALSE; +- int read_access_mode; ++ int access_mode; + + /* Must be able to get fd flags */ + fd_flags = fcntl (fd, F_GETFL); +- if (fd_flags == -1) +- return glnx_null_throw_errno_prefix (error, "fcntl F_GETFL"); ++ if (fd_flags < 0) ++ return glnx_throw_errno_prefix (error, "Failed to get fd flags"); + + /* Must be O_PATH */ + if ((fd_flags & O_PATH) != O_PATH) +- return glnx_null_throw (error, "not opened with O_PATH"); +- +- /* We don't want to allow exposing symlinks, because if they are +- * under the callers control they could be changed between now and +- * starting the child allowing it to point anywhere, so enforce NOFOLLOW. +- * and verify that stat is not a link. +- */ +- if ((fd_flags & O_NOFOLLOW) != O_NOFOLLOW) +- return glnx_null_throw (error, "not opened with O_NOFOLLOW"); ++ { ++ g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "File descriptor is not O_PATH"); ++ return FALSE; ++ } + + /* Must be able to fstat */ + if (fstat (fd, &st_buf) < 0) +- return glnx_null_throw_errno_prefix (error, "fstat"); +- +- /* As per above, no symlinks */ +- if (S_ISLNK (st_buf.st_mode)) +- return glnx_null_throw (error, "is a symbolic link"); ++ return glnx_throw_errno_prefix (error, "Failed to fstat"); + +- proc_path = g_strdup_printf ("/proc/self/fd/%d", fd); +- +- /* Must be able to read valid path from /proc/self/fd */ +- /* This is an absolute and (at least at open time) symlink-expanded path */ +- path = verify_proc_self_fd (proc_path, error); +- if (path == NULL) +- return NULL; ++ access_mode = R_OK; ++ if (S_ISDIR (st_buf.st_mode)) ++ access_mode |= X_OK; + +- /* Verify that this is the same file as the app opened */ +- if (stat (path, &real_st_buf) < 0 || +- st_buf.st_dev != real_st_buf.st_dev || +- st_buf.st_ino != real_st_buf.st_ino) +- { +- /* Different files on the inside and the outside, reject the request */ +- return glnx_null_throw (error, +- "different file inside and outside sandbox"); +- } ++ if (needs_writable) ++ access_mode |= W_OK; + +- read_access_mode = R_OK; +- if (S_ISDIR (st_buf.st_mode)) +- read_access_mode |= X_OK; ++ /* Must be able to access readable and potentially writable */ ++ if (faccessat (fd, "", access_mode, AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW) != 0) ++ return glnx_throw_errno_prefix (error, "Bad access mode"); + +- /* Must be able to access the path via the sandbox supplied O_PATH fd, +- which applies the sandbox side mount options (like readonly). */ +- if (access (proc_path, read_access_mode) != 0) +- return glnx_null_throw (error, "not %s in sandbox", +- read_access_mode & X_OK ? "accessible" : "readable"); ++ return TRUE; ++} + +- if (access (proc_path, W_OK) == 0) +- writable = TRUE; ++static int ++fd_map_remap_fd (GArray *fd_map, ++ int *max_fd_in_out, ++ int fd) ++{ ++ FdMapEntry fd_map_entry; + +- if (writable_out != NULL) +- *writable_out = writable; ++ /* Use a fd that hasn't been used yet. We might have to reshuffle ++ * fd_map_entry.to, a bit later. */ ++ fd_map_entry.from = fd; ++ fd_map_entry.to = ++(*max_fd_in_out); ++ fd_map_entry.final = fd_map_entry.to; ++ g_array_append_val (fd_map, fd_map_entry); + +- return g_steal_pointer (&path); ++ return fd_map_entry.final; + } + + static gboolean +@@ -794,9 +659,12 @@ handle_spawn (PortalFlatpak *object, + gboolean devel; + gboolean empty_app; + g_autoptr(GString) env_string = g_string_new (""); +- glnx_autofd int env_fd = -1; + const char *flatpak; + gboolean testing = FALSE; ++ g_autoptr(GArray) owned_fds = NULL; ++ g_autoptr(GArray) expose_fds = NULL; ++ g_autoptr(GArray) expose_fds_ro = NULL; ++ glnx_autofd int instance_sandbox_fd = -1; + + child_setup_data.instance_id_fd = -1; + child_setup_data.env_fd = -1; +@@ -919,29 +787,6 @@ handle_spawn (PortalFlatpak *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- for (i = 0; sandbox_expose != NULL && sandbox_expose[i] != NULL; i++) +- { +- const char *expose = sandbox_expose[i]; +- +- g_debug ("exposing %s", expose); +- if (!is_valid_expose (expose, &error)) +- { +- g_dbus_method_invocation_return_gerror (invocation, error); +- return G_DBUS_METHOD_INVOCATION_HANDLED; +- } +- } +- +- for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++) +- { +- const char *expose = sandbox_expose_ro[i]; +- g_debug ("exposing %s", expose); +- if (!is_valid_expose (expose, &error)) +- { +- g_dbus_method_invocation_return_gerror (invocation, error); +- return G_DBUS_METHOD_INVOCATION_HANDLED; +- } +- } +- + g_debug ("Running spawn command %s", arg_argv[0]); + + n_fds = 0; +@@ -1115,10 +960,14 @@ handle_spawn (PortalFlatpak *object, + g_string_append_c (env_string, '\0'); + } + ++ owned_fds = g_array_new (FALSE, FALSE, sizeof (int)); ++ g_array_set_clear_func (owned_fds, (GDestroyNotify) glnx_close_fd); ++ + if (env_string->len > 0) + { +- FdMapEntry fd_map_entry; + g_auto(GLnxTmpfile) env_tmpf = { 0, }; ++ int env_fd = -1; ++ int remapped_fd; + + if (!flatpak_buffer_to_sealed_memfd_or_tmpfile (&env_tmpf, "environ", + env_string->str, +@@ -1129,16 +978,12 @@ handle_spawn (PortalFlatpak *object, + } + + env_fd = glnx_steal_fd (&env_tmpf.fd); ++ g_array_append_val (owned_fds, env_fd); + +- /* Use a fd that hasn't been used yet. We might have to reshuffle +- * fd_map_entry.to, a bit later. */ +- fd_map_entry.from = env_fd; +- fd_map_entry.to = ++max_fd; +- fd_map_entry.final = fd_map_entry.to; +- g_array_append_val (fd_map, fd_map_entry); ++ remapped_fd = fd_map_remap_fd (fd_map, &max_fd, env_fd); + + g_ptr_array_add (flatpak_argv, +- g_strdup_printf ("--env-fd=%d", fd_map_entry.final)); ++ g_strdup_printf ("--env-fd=%d", remapped_fd)); + } + + for (i = 0; unset_env != NULL && unset_env[i] != NULL; i++) +@@ -1246,54 +1091,100 @@ handle_spawn (PortalFlatpak *object, + else + g_ptr_array_add (flatpak_argv, g_strdup ("--unshare=network")); + ++ expose_fds = g_array_new (FALSE, FALSE, sizeof (int)); ++ expose_fds_ro = g_array_new (FALSE, FALSE, sizeof (int)); ++ ++ if (instance_path != NULL) ++ { ++ glnx_autofd int instance_fd = -1; ++ ++ instance_fd = glnx_chaseat (AT_FDCWD, instance_path, ++ GLNX_CHASE_DEFAULT, ++ &error); ++ if (instance_fd < 0) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ if (!glnx_ensure_dir (instance_fd, "sandbox", 0700, &error)) ++ { ++ g_warning ("Unable to create %s/sandbox: %s", instance_path, error->message); ++ g_clear_error (&error); ++ } ++ ++ instance_sandbox_fd = glnx_chaseat (instance_fd, "sandbox", ++ GLNX_CHASE_RESOLVE_NO_SYMLINKS, ++ &error); ++ if (instance_sandbox_fd < 0) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ } + +- if (instance_path) ++ for (i = 0; sandbox_expose != NULL && sandbox_expose[i] != NULL; i++) + { +- for (i = 0; sandbox_expose != NULL && sandbox_expose[i] != NULL; i++) +- g_ptr_array_add (flatpak_argv, +- filesystem_sandbox_arg (instance_path, sandbox_expose[i], FALSE)); +- for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++) +- g_ptr_array_add (flatpak_argv, +- filesystem_sandbox_arg (instance_path, sandbox_expose_ro[i], TRUE)); ++ int expose_fd; ++ ++ g_assert (instance_sandbox_fd >= 0); ++ ++ expose_fd = glnx_chaseat (instance_sandbox_fd, sandbox_expose[i], ++ GLNX_CHASE_RESOLVE_NO_SYMLINKS | ++ GLNX_CHASE_RESOLVE_BENEATH, ++ &error); ++ if (expose_fd < 0) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ g_array_append_val (expose_fds, expose_fd); ++ /* transfers ownership, can't g_steal_fd with g_array_append_val */ ++ g_array_append_val (owned_fds, expose_fd); + } + + for (i = 0; sandbox_expose_ro != NULL && sandbox_expose_ro[i] != NULL; i++) + { +- const char *expose = sandbox_expose_ro[i]; +- g_debug ("exposing %s", expose); ++ int expose_fd; ++ ++ g_assert (instance_sandbox_fd >= 0); ++ ++ expose_fd = glnx_chaseat (instance_sandbox_fd, sandbox_expose_ro[i], ++ GLNX_CHASE_RESOLVE_NO_SYMLINKS | ++ GLNX_CHASE_RESOLVE_BENEATH, ++ &error); ++ if (expose_fd < 0) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ ++ g_array_append_val (expose_fds_ro, expose_fd); ++ /* transfers ownership, can't g_steal_fd with g_array_append_val */ ++ g_array_append_val (owned_fds, expose_fd); + } + + if (sandbox_expose_fd != NULL) + { + gsize len = g_variant_n_children (sandbox_expose_fd); ++ + for (i = 0; i < len; i++) + { + gint32 handle; ++ + g_variant_get_child (sandbox_expose_fd, i, "h", &handle); +- if (handle >= 0 && handle < fds_len) ++ if (handle >= 0 && handle < fds_len && ++ validate_opath_fd (fds[handle], TRUE, &error)) + { +- int handle_fd = fds[handle]; +- g_autofree char *path = NULL; +- gboolean writable = FALSE; +- +- path = get_path_for_fd (handle_fd, &writable, &error); +- +- if (path) +- { +- g_ptr_array_add (flatpak_argv, filesystem_arg (path, !writable)); +- } +- else +- { +- g_debug ("unable to get path for sandbox-exposed fd %d, ignoring: %s", +- handle_fd, error->message); +- g_clear_error (&error); +- } ++ g_array_append_val (expose_fds, fds[handle]); + } + else + { ++ g_debug ("Invalid sandbox expose fd: %s", error->message); + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, +- "No file descriptor for handle %d", ++ "No valid file descriptor for handle %d", + handle); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } +@@ -1303,31 +1194,20 @@ handle_spawn (PortalFlatpak *object, + if (sandbox_expose_fd_ro != NULL) + { + gsize len = g_variant_n_children (sandbox_expose_fd_ro); ++ + for (i = 0; i < len; i++) + { + gint32 handle; ++ + g_variant_get_child (sandbox_expose_fd_ro, i, "h", &handle); +- if (handle >= 0 && handle < fds_len) ++ if (handle >= 0 && handle < fds_len && ++ validate_opath_fd (fds[handle], FALSE, &error)) + { +- int handle_fd = fds[handle]; +- g_autofree char *path = NULL; +- gboolean writable = FALSE; +- +- path = get_path_for_fd (handle_fd, &writable, &error); +- +- if (path) +- { +- g_ptr_array_add (flatpak_argv, filesystem_arg (path, TRUE)); +- } +- else +- { +- g_debug ("unable to get path for sandbox-exposed fd %d, ignoring: %s", +- handle_fd, error->message); +- g_clear_error (&error); +- } ++ g_array_append_val (expose_fds_ro, fds[handle]); + } + else + { ++ g_debug ("Invalid sandbox expose ro fd: %s", error->message); + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, + G_DBUS_ERROR_INVALID_ARGS, + "No file descriptor for handle %d", +@@ -1337,20 +1217,40 @@ handle_spawn (PortalFlatpak *object, + } + } + ++ for (i = 0; i < expose_fds->len; i++) ++ { ++ int remapped_fd; ++ ++ remapped_fd = fd_map_remap_fd (fd_map, &max_fd, expose_fds->data[i]); ++ ++ g_ptr_array_add (flatpak_argv, g_strdup_printf ("--bind-fd=%d", ++ remapped_fd)); ++ } ++ ++ for (i = 0; i < expose_fds_ro->len; i++) ++ { ++ int remapped_fd; ++ ++ remapped_fd = fd_map_remap_fd (fd_map, &max_fd, expose_fds_ro->data[i]); ++ ++ g_ptr_array_add (flatpak_argv, g_strdup_printf ("--ro-bind-fd=%d", ++ remapped_fd)); ++ } ++ + empty_app = (arg_flags & FLATPAK_SPAWN_FLAGS_EMPTY_APP) != 0; + ++ if (empty_app && app_fd != NULL) ++ { ++ g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, ++ G_DBUS_ERROR_INVALID_ARGS, ++ "app-fd and EMPTY_APP cannot both be used"); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ + if (app_fd != NULL) + { ++ int remapped_fd; + gint32 handle = g_variant_get_handle (app_fd); +- g_autofree char *path = NULL; +- +- if (empty_app) +- { +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, +- G_DBUS_ERROR_INVALID_ARGS, +- "app-fd and EMPTY_APP cannot both be used"); +- return G_DBUS_METHOD_INVOCATION_HANDLED; +- } + + if (handle >= fds_len || handle < 0) + { +@@ -1362,18 +1262,11 @@ handle_spawn (PortalFlatpak *object, + } + + g_assert (fds != NULL); /* otherwise fds_len would be 0 */ +- path = get_path_for_fd (fds[handle], NULL, &error); + +- if (path == NULL) +- { +- g_prefix_error (&error, "Unable to convert /app fd %d into path: ", +- fds[handle]); +- g_dbus_method_invocation_return_gerror (invocation, error); +- return G_DBUS_METHOD_INVOCATION_HANDLED; +- } ++ remapped_fd = fd_map_remap_fd (fd_map, &max_fd, fds[handle]); + +- g_debug ("Using %s as /app instead of app", path); +- g_ptr_array_add (flatpak_argv, g_strdup_printf ("--app-path=%s", path)); ++ g_ptr_array_add (flatpak_argv, g_strdup_printf ("--app-fd=%d", ++ remapped_fd)); + } + else if (empty_app) + { +@@ -1382,8 +1275,8 @@ handle_spawn (PortalFlatpak *object, + + if (usr_fd != NULL) + { ++ int remapped_fd; + gint32 handle = g_variant_get_handle (usr_fd); +- g_autofree char *path = NULL; + + if (handle >= fds_len || handle < 0) + { +@@ -1395,18 +1288,11 @@ handle_spawn (PortalFlatpak *object, + } + + g_assert (fds != NULL); /* otherwise fds_len would be 0 */ +- path = get_path_for_fd (fds[handle], NULL, &error); + +- if (path == NULL) +- { +- g_prefix_error (&error, "Unable to convert /usr fd %d into path: ", +- fds[handle]); +- g_dbus_method_invocation_return_gerror (invocation, error); +- return G_DBUS_METHOD_INVOCATION_HANDLED; +- } ++ remapped_fd = fd_map_remap_fd (fd_map, &max_fd, fds[handle]); + +- g_debug ("Using %s as /usr instead of runtime", path); +- g_ptr_array_add (flatpak_argv, g_strdup_printf ("--usr-path=%s", path)); ++ g_ptr_array_add (flatpak_argv, g_strdup_printf ("--usr-fd=%d", ++ remapped_fd)); + } + + g_ptr_array_add (flatpak_argv, g_strdup_printf ("--runtime=%s", runtime_parts[1])); diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Add-ro-bind-fd-options.patch flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Add-ro-bind-fd-options.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Add-ro-bind-fd-options.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Add-ro-bind-fd-options.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,98 @@ +From: Sebastian Wick +Date: Fri, 6 Feb 2026 21:03:34 +0100 +Subject: run: Add --(ro-)bind-fd options + +Exposes the functionality added to flatpak_run_app in the previous +commit with two new options. + +[smcv: Resolve conflicts with 1.14.x] + +Origin: backport, 1.16.4, commit:a971671f9546a20e2134afde31b317ff140e5bd9 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + app/flatpak-builtins-run.c | 49 ++++++++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 47 insertions(+), 2 deletions(-) + +diff --git a/app/flatpak-builtins-run.c b/app/flatpak-builtins-run.c +index c22650d..40ab8fc 100644 +--- a/app/flatpak-builtins-run.c ++++ b/app/flatpak-builtins-run.c +@@ -63,6 +63,46 @@ static char *opt_app_path; + static int opt_app_fd = -1; + static char *opt_usr_path; + static int opt_usr_fd = -1; ++static GArray *opt_bind_fds = NULL; ++static GArray *opt_ro_bind_fds = NULL; ++ ++static gboolean ++option_bind_fd_cb (const char *option_name, ++ const char *value, ++ gpointer data, ++ GError **error) ++{ ++ glnx_autofd int fd = -1; ++ ++ fd = flatpak_parse_fd (value, error); ++ if (fd < 0) ++ return FALSE; ++ ++ if (fd < 3) ++ return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ ++ g_array_append_val (opt_bind_fds, fd); ++ return TRUE; ++} ++ ++static gboolean ++option_ro_bind_fd_cb (const char *option_name, ++ const char *value, ++ gpointer data, ++ GError **error) ++{ ++ glnx_autofd int fd = -1; ++ ++ fd = flatpak_parse_fd (value, error); ++ if (fd < 0) ++ return FALSE; ++ ++ if (fd < 3) ++ return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); ++ ++ g_array_append_val (opt_ro_bind_fds, fd); ++ return TRUE; ++} + + static GOptionEntry options[] = { + { "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, N_("Arch to use"), N_("ARCH") }, +@@ -93,6 +133,8 @@ static GOptionEntry options[] = { + { "app-fd", 0, 0, G_OPTION_ARG_INT, &opt_app_fd, N_("Use FD instead of the app's /app"), N_("FD") }, + { "usr-path", 0, 0, G_OPTION_ARG_FILENAME, &opt_usr_path, N_("Use PATH instead of the runtime's /usr"), N_("PATH") }, + { "usr-fd", 0, 0, G_OPTION_ARG_INT, &opt_usr_fd, N_("Use FD instead of the runtime's /usr"), N_("FD") }, ++ { "bind-fd", 0, 0, G_OPTION_ARG_CALLBACK | G_OPTION_FLAG_HIDDEN, &option_bind_fd_cb, N_("Bind mount the file or directory referred to by FD to its canonicalized path"), N_("FD") }, ++ { "ro-bind-fd", 0, 0, G_OPTION_ARG_CALLBACK | G_OPTION_FLAG_HIDDEN, &option_ro_bind_fd_cb, N_("Bind mount the file or directory referred to by FD read-only to its canonicalized path"), N_("FD") }, + { NULL } + }; + +@@ -117,6 +159,9 @@ flatpak_builtin_run (int argc, char **argv, GCancellable *cancellable, GError ** + glnx_autofd int app_fd = -1; + glnx_autofd int usr_fd = -1; + ++ opt_bind_fds = g_array_new (FALSE, FALSE, sizeof (int)); ++ opt_ro_bind_fds = g_array_new (FALSE, FALSE, sizeof (int)); ++ + context = g_option_context_new (_("APP [ARGUMENT…] - Run an app")); + g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); + +@@ -381,8 +426,8 @@ flatpak_builtin_run (int argc, char **argv, GCancellable *cancellable, GError ** + rest_argc - 1, + opt_instance_id_fd, + NULL, +- NULL, +- NULL, ++ opt_bind_fds, ++ opt_ro_bind_fds, + cancellable, + error)) + return FALSE; diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Add-ro-bind-fds-to-flatpak_run_app.patch flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Add-ro-bind-fds-to-flatpak_run_app.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Add-ro-bind-fds-to-flatpak_run_app.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Add-ro-bind-fds-to-flatpak_run_app.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,119 @@ +From: Sebastian Wick +Date: Fri, 6 Feb 2026 21:02:47 +0100 +Subject: run: Add (ro-)bind fds to flatpak_run_app + +The flatpak portal allows apps to expose files and folders from within +the sandbox to a side-sandbox using flatpak-spawn. So far it has used +the --filesystem option to mount those files and folders, but it takes a +path. Paths are inherently racy and they allow the app to swap out any +component of the path with a symlink after handing it off. If they win +the race, flatpak will mount a completely different directory. + +This adds a new way to mount files and directories based on O_PATH +file descriptor that needs to provided when execing the flatpak binary. + +[smcv: Resolve conflicts with 1.14.x] + +Origin: backport, 1.16.4, commit:4f1e7f8d1705488e501925255fbe061ac71405fc +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + app/flatpak-builtins-run.c | 2 ++ + common/flatpak-installation.c | 1 + + common/flatpak-run-private.h | 2 ++ + common/flatpak-run.c | 36 ++++++++++++++++++++++++++++++++++++ + 4 files changed, 41 insertions(+) + +diff --git a/app/flatpak-builtins-run.c b/app/flatpak-builtins-run.c +index 5c4dca0..c22650d 100644 +--- a/app/flatpak-builtins-run.c ++++ b/app/flatpak-builtins-run.c +@@ -381,6 +381,8 @@ flatpak_builtin_run (int argc, char **argv, GCancellable *cancellable, GError ** + rest_argc - 1, + opt_instance_id_fd, + NULL, ++ NULL, ++ NULL, + cancellable, + error)) + return FALSE; +diff --git a/common/flatpak-installation.c b/common/flatpak-installation.c +index 6dd79e1..893f03d 100644 +--- a/common/flatpak-installation.c ++++ b/common/flatpak-installation.c +@@ -710,6 +710,7 @@ flatpak_installation_launch_full (FlatpakInstallation *self, + NULL, + NULL, 0, -1, + &instance_dir, ++ NULL, NULL, + cancellable, error)) + return FALSE; + +diff --git a/common/flatpak-run-private.h b/common/flatpak-run-private.h +index e58d9aa..2d8b462 100644 +--- a/common/flatpak-run-private.h ++++ b/common/flatpak-run-private.h +@@ -200,6 +200,8 @@ gboolean flatpak_run_app (FlatpakDecomposed *app_ref, + int n_args, + int instance_id_fd, + char **instance_dir_out, ++ GArray *bind_fds, ++ GArray *ro_bind_fds, + GCancellable *cancellable, + GError **error); + +diff --git a/common/flatpak-run.c b/common/flatpak-run.c +index aa3fb41..f12b1f8 100644 +--- a/common/flatpak-run.c ++++ b/common/flatpak-run.c +@@ -4214,6 +4214,8 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + int n_args, + int instance_id_fd, + char **instance_dir_out, ++ GArray *bind_fds, ++ GArray *ro_bind_fds, + GCancellable *cancellable, + GError **error) + { +@@ -4800,6 +4802,40 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + flatpak_bwrap_add_arg_printf (bwrap, "/run/user/%d", getuid ()); + } + ++ for (i = 0; bind_fds && i < bind_fds->len; i++) ++ { ++ int fd = g_array_index (bind_fds, int, i); ++ g_autofree char *path = NULL; ++ ++ /* We get the path the fd refers to, to determine to mount point ++ * destination inside the sandbox */ ++ path = get_path_for_fd (fd, error); ++ if (!path) ++ return FALSE; ++ ++ if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, ++ "--bind-fd", fd, path, ++ error)) ++ return FALSE; ++ } ++ ++ for (i = 0; ro_bind_fds && i < ro_bind_fds->len; i++) ++ { ++ int fd = g_array_index (ro_bind_fds, int, i); ++ g_autofree char *path = NULL; ++ ++ /* We get the path the fd refers to, to determine to mount point ++ * destination inside the sandbox */ ++ path = get_path_for_fd (fd, error); ++ if (!path) ++ return FALSE; ++ ++ if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, ++ "--ro-bind-fd", fd, path, ++ error)) ++ return FALSE; ++ } ++ + if (!flatpak_run_add_dconf_args (bwrap, app_id, metakey, error)) + return FALSE; + diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Add-usr-fd-and-app-fd-options.patch flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Add-usr-fd-and-app-fd-options.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Add-usr-fd-and-app-fd-options.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Add-usr-fd-and-app-fd-options.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,79 @@ +From: Sebastian Wick +Date: Fri, 6 Feb 2026 20:55:46 +0100 +Subject: run: Add --usr-fd and --app-fd options + +Exposes options to pass in a fd for the runtime and app deploy. The +flatpak portal will make use of this in a following commit. + +Origin: upstream, 1.16.4, commit:494c5ea687be884487fa5dc8b943130d9df7bb5f +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + app/flatpak-builtins-run.c | 30 ++++++++++++++++++++++++++++-- + 1 file changed, 28 insertions(+), 2 deletions(-) + +diff --git a/app/flatpak-builtins-run.c b/app/flatpak-builtins-run.c +index 11a2d09..5c4dca0 100644 +--- a/app/flatpak-builtins-run.c ++++ b/app/flatpak-builtins-run.c +@@ -60,7 +60,9 @@ static gboolean opt_parent_expose_pids; + static gboolean opt_parent_share_pids; + static int opt_instance_id_fd = -1; + static char *opt_app_path; ++static int opt_app_fd = -1; + static char *opt_usr_path; ++static int opt_usr_fd = -1; + + static GOptionEntry options[] = { + { "arch", 0, 0, G_OPTION_ARG_STRING, &opt_arch, N_("Arch to use"), N_("ARCH") }, +@@ -88,7 +90,9 @@ static GOptionEntry options[] = { + { "parent-share-pids", 0, 0, G_OPTION_ARG_NONE, &opt_parent_share_pids, N_("Share process ID namespace with parent"), NULL }, + { "instance-id-fd", 0, 0, G_OPTION_ARG_INT, &opt_instance_id_fd, N_("Write the instance ID to the given file descriptor"), NULL }, + { "app-path", 0, 0, G_OPTION_ARG_FILENAME, &opt_app_path, N_("Use PATH instead of the app's /app"), N_("PATH") }, ++ { "app-fd", 0, 0, G_OPTION_ARG_INT, &opt_app_fd, N_("Use FD instead of the app's /app"), N_("FD") }, + { "usr-path", 0, 0, G_OPTION_ARG_FILENAME, &opt_usr_path, N_("Use PATH instead of the runtime's /usr"), N_("PATH") }, ++ { "usr-fd", 0, 0, G_OPTION_ARG_INT, &opt_usr_fd, N_("Use FD instead of the runtime's /usr"), N_("FD") }, + { NULL } + }; + +@@ -308,7 +312,18 @@ flatpak_builtin_run (int argc, char **argv, GCancellable *cancellable, GError ** + if (!opt_session_bus) + flags |= FLATPAK_RUN_FLAG_NO_SESSION_BUS_PROXY; + +- if (opt_app_path != NULL) ++ if (opt_app_fd >= 0 && opt_app_path != NULL) ++ { ++ flatpak_fail_error (error, FLATPAK_ERROR, ++ _("app-fd and app-path cannot both be used")); ++ return FALSE; ++ } ++ ++ if (opt_app_fd >= 0) ++ { ++ app_fd = opt_app_fd; ++ } ++ else if (opt_app_path != NULL) + { + if (g_strcmp0 (opt_app_path, "") == 0) + { +@@ -327,7 +342,18 @@ flatpak_builtin_run (int argc, char **argv, GCancellable *cancellable, GError ** + app_fd = FLATPAK_RUN_APP_DEPLOY_APP_ORIGINAL; + } + +- if (opt_usr_path != NULL) ++ if (opt_usr_fd >= 0 && opt_usr_path != NULL) ++ { ++ flatpak_fail_error (error, FLATPAK_ERROR, ++ _("usr-fd and usr-path cannot both be used")); ++ return FALSE; ++ } ++ ++ if (opt_usr_fd >= 0) ++ { ++ usr_fd = opt_usr_fd; ++ } ++ else if (opt_usr_path != NULL) + { + usr_fd = open (opt_usr_path, O_PATH | O_CLOEXEC | O_NOFOLLOW); + diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Use-O_PATH-fds-for-the-runtime-and-app-deploy-directo.patch flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Use-O_PATH-fds-for-the-runtime-and-app-deploy-directo.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Use-O_PATH-fds-for-the-runtime-and-app-deploy-directo.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078/run-Use-O_PATH-fds-for-the-runtime-and-app-deploy-directo.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,931 @@ +From: Sebastian Wick +Date: Fri, 6 Feb 2026 20:54:22 +0100 +Subject: run: Use O_PATH fds for the runtime and app deploy directories + +This also allows us to use glnx_chaseat, and other at-functions to +traverse the filesystem tree in a safe way. + +This is important because the app and runtime deploy directories can be +under an attackers control. The flatpak portal for example allows +sandboxed apps to provide them. + +In particular, attacks where the deploy dirs get replaced by a symlink +pointing into the host system will be stopped by this. + +Note that this change alone is not enough to avoid the attack, and the +portal has to be changed as well. + +[smcv: Resolve conflicts with 1.14.x] + +Origin: backport, 1.16.4, commit:873ed8b3718bd1a77343091ad7838f120729cdb3 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + app/flatpak-builtins-build.c | 8 +- + app/flatpak-builtins-run.c | 37 +++- + common/flatpak-dir.c | 25 ++- + common/flatpak-installation.c | 3 +- + common/flatpak-run-private.h | 11 +- + common/flatpak-run.c | 471 +++++++++++++++++++++++++++++------------- + 6 files changed, 396 insertions(+), 159 deletions(-) + +diff --git a/app/flatpak-builtins-build.c b/app/flatpak-builtins-build.c +index ed7f92b..f4eca60 100644 +--- a/app/flatpak-builtins-build.c ++++ b/app/flatpak-builtins-build.c +@@ -457,7 +457,13 @@ flatpak_builtin_build (int argc, char **argv, GCancellable *cancellable, GError + /* Never set up an a11y bus for builds */ + run_flags |= FLATPAK_RUN_FLAG_NO_A11Y_BUS_PROXY; + +- if (!flatpak_run_setup_base_argv (bwrap, runtime_files, app_id_dir, arch, ++ glnx_autofd int usr_fd = -1; ++ 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, app_id_dir, arch, + run_flags, error)) + return FALSE; + +diff --git a/app/flatpak-builtins-run.c b/app/flatpak-builtins-run.c +index 4948e0b..11a2d09 100644 +--- a/app/flatpak-builtins-run.c ++++ b/app/flatpak-builtins-run.c +@@ -110,6 +110,8 @@ flatpak_builtin_run (int argc, char **argv, GCancellable *cancellable, GError ** + g_autoptr(GError) local_error = NULL; + g_autoptr(GPtrArray) dirs = NULL; + FlatpakRunFlags flags = 0; ++ glnx_autofd int app_fd = -1; ++ glnx_autofd int usr_fd = -1; + + context = g_option_context_new (_("APP [ARGUMENT…] - Run an app")); + g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); +@@ -306,14 +308,45 @@ flatpak_builtin_run (int argc, char **argv, GCancellable *cancellable, GError ** + if (!opt_session_bus) + flags |= FLATPAK_RUN_FLAG_NO_SESSION_BUS_PROXY; + ++ if (opt_app_path != NULL) ++ { ++ if (g_strcmp0 (opt_app_path, "") == 0) ++ { ++ app_fd = FLATPAK_RUN_APP_DEPLOY_APP_EMPTY; ++ } ++ else ++ { ++ app_fd = open (opt_app_path, O_PATH | O_CLOEXEC | O_NOFOLLOW); ++ ++ if (app_fd < 0) ++ return glnx_throw_errno_prefix (error, "Failed to open app-path"); ++ } ++ } ++ else ++ { ++ app_fd = FLATPAK_RUN_APP_DEPLOY_APP_ORIGINAL; ++ } ++ ++ if (opt_usr_path != NULL) ++ { ++ usr_fd = open (opt_usr_path, O_PATH | O_CLOEXEC | O_NOFOLLOW); ++ ++ if (usr_fd < 0) ++ return glnx_throw_errno_prefix (error, "Failed to open usr-path"); ++ } ++ else ++ { ++ usr_fd = FLATPAK_RUN_APP_DEPLOY_USR_ORIGINAL; ++ } ++ + if (!flatpak_run_app (app_deploy ? app_ref : runtime_ref, + app_deploy, +- opt_app_path, ++ app_fd, + arg_context, + opt_runtime, + opt_runtime_version, + opt_runtime_commit, +- opt_usr_path, ++ usr_fd, + opt_parent_pid, + flags, + opt_cwd, +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index e20bec8..dafbb49 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -8216,6 +8216,7 @@ apply_extra_data (FlatpakDir *self, + 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)) +@@ -8292,11 +8293,25 @@ apply_extra_data (FlatpakDir *self, + "--cap-drop", "ALL", + NULL); + +- if (!flatpak_run_setup_base_argv (bwrap, runtime_files, NULL, runtime_arch, +- /* Might need multiarch in apply_extra (see e.g. #3742). Should be pretty safe in this limited context */ +- FLATPAK_RUN_FLAG_MULTIARCH | +- FLATPAK_RUN_FLAG_NO_SESSION_HELPER | FLATPAK_RUN_FLAG_NO_PROC, +- error)) ++ run_flags = FLATPAK_RUN_FLAG_NO_SESSION_HELPER; ++ ++ /* Might need multiarch in apply_extra (see e.g. #3742). ++ * Should be pretty safe in this limited context. */ ++ run_flags |= FLATPAK_RUN_FLAG_MULTIARCH; ++ ++ /* This sandbox is run as root and /proc/self/exe can sometimes be used to ++ * access outside files (see cd21428). ++ * Disable /proc entirely in this context. */ ++ run_flags |= FLATPAK_RUN_FLAG_NO_PROC; ++ ++ glnx_autofd int usr_fd = -1; ++ 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; + + app_context = flatpak_context_new (); +diff --git a/common/flatpak-installation.c b/common/flatpak-installation.c +index 4742332..6dd79e1 100644 +--- a/common/flatpak-installation.c ++++ b/common/flatpak-installation.c +@@ -700,9 +700,10 @@ flatpak_installation_launch_full (FlatpakInstallation *self, + + if (!flatpak_run_app (app_ref, + app_deploy, ++ FLATPAK_RUN_APP_DEPLOY_APP_ORIGINAL, + NULL, +- NULL, NULL, + NULL, NULL, NULL, ++ FLATPAK_RUN_APP_DEPLOY_USR_ORIGINAL, + 0, + run_flags, + NULL, +diff --git a/common/flatpak-run-private.h b/common/flatpak-run-private.h +index 72b45c8..e58d9aa 100644 +--- a/common/flatpak-run-private.h ++++ b/common/flatpak-run-private.h +@@ -28,6 +28,11 @@ + #include "flatpak-utils-private.h" + #include "flatpak-exports-private.h" + ++#define FLATPAK_RUN_APP_DEPLOY_APP_ORIGINAL (-2) ++#define FLATPAK_RUN_APP_DEPLOY_APP_EMPTY (-3) ++ ++#define FLATPAK_RUN_APP_DEPLOY_USR_ORIGINAL (-2) ++ + gboolean flatpak_run_in_transient_unit (const char *app_id, + GError **error); + +@@ -151,7 +156,7 @@ gboolean flatpak_ensure_data_dir (GFile *app_id_dir, + GError **error); + + gboolean flatpak_run_setup_base_argv (FlatpakBwrap *bwrap, +- GFile *runtime_files, ++ int runtime_fd, + GFile *app_id_dir, + const char *arch, + FlatpakRunFlags flags, +@@ -181,12 +186,12 @@ gboolean flatpak_run_add_app_info_args (FlatpakBwrap *bwrap, + + gboolean flatpak_run_app (FlatpakDecomposed *app_ref, + FlatpakDeploy *app_deploy, +- const char *custom_app_path, ++ int custom_app_fd, + FlatpakContext *extra_context, + const char *custom_runtime, + const char *custom_runtime_version, + const char *custom_runtime_commit, +- const char *custom_usr_path, ++ int custom_usr_fd, + int parent_pid, + FlatpakRunFlags flags, + const char *cwd, +diff --git a/common/flatpak-run.c b/common/flatpak-run.c +index 31a5374..aa3fb41 100644 +--- a/common/flatpak-run.c ++++ b/common/flatpak-run.c +@@ -2921,38 +2921,56 @@ flatpak_run_add_app_info_args (FlatpakBwrap *bwrap, + + static void + add_tzdata_args (FlatpakBwrap *bwrap, +- GFile *runtime_files) ++ int runtime_fd) + { +- g_autofree char *raw_timezone = flatpak_get_timezone (); +- g_autofree char *timezone_content = g_strdup_printf ("%s\n", raw_timezone); +- g_autofree char *localtime_content = g_strconcat ("../usr/share/zoneinfo/", raw_timezone, NULL); +- g_autoptr(GFile) runtime_zoneinfo = NULL; ++ g_autofree char *raw_timezone = NULL; ++ g_autofree char *timezone_content = NULL; ++ g_autofree char *localtime_content = NULL; ++ const char *tzdir; ++ glnx_autofd int tzdir_fd = -1; ++ glnx_autofd int zoneinfo_fd = -1; ++ g_autoptr(GError) error = NULL; + +- if (runtime_files) +- runtime_zoneinfo = g_file_resolve_relative_path (runtime_files, "share/zoneinfo"); ++ raw_timezone = flatpak_get_timezone (); ++ timezone_content = g_strdup_printf ("%s\n", raw_timezone); ++ localtime_content = g_strconcat ("../usr/share/zoneinfo/", raw_timezone, NULL); + +- /* Check for runtime /usr/share/zoneinfo */ +- if (runtime_zoneinfo != NULL && g_file_query_exists (runtime_zoneinfo, NULL)) ++ tzdir = "/usr/share/zoneinfo"; ++ ++ tzdir_fd = glnx_chaseat (AT_FDCWD, tzdir, GLNX_CHASE_MUST_BE_DIRECTORY, NULL); ++ ++ zoneinfo_fd = glnx_chaseat (runtime_fd, "share/zoneinfo", ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_DIRECTORY, ++ NULL); ++ ++ /* Check for host /usr/share/zoneinfo */ ++ if (tzdir_fd >= 0 && zoneinfo_fd >= 0) ++ { ++ /* Here we assume the host timezone file exist in the host data */ ++ flatpak_bwrap_add_args (bwrap, ++ "--ro-bind", tzdir, "/usr/share/zoneinfo", ++ "--symlink", localtime_content, "/etc/localtime", ++ NULL); ++ } ++ else + { +- /* Check for host /usr/share/zoneinfo */ +- if (g_file_test ("/usr/share/zoneinfo", G_FILE_TEST_IS_DIR)) ++ g_autofree char *runtime_zoneinfo = NULL; ++ glnx_autofd int runtime_zoneinfo_fd = -1; ++ ++ runtime_zoneinfo = g_strconcat ("share/zoneinfo/", raw_timezone, NULL); ++ ++ /* Check for runtime /usr/share/zoneinfo */ ++ runtime_zoneinfo_fd = glnx_chaseat (runtime_fd, runtime_zoneinfo, ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_REGULAR, ++ NULL); ++ if (runtime_zoneinfo_fd >= 0) + { +- /* Here we assume the host timezone file exist in the host data */ + flatpak_bwrap_add_args (bwrap, +- "--ro-bind", "/usr/share/zoneinfo", "/usr/share/zoneinfo", + "--symlink", localtime_content, "/etc/localtime", + NULL); + } +- else +- { +- g_autoptr(GFile) runtime_tzfile = g_file_resolve_relative_path (runtime_zoneinfo, raw_timezone); +- +- /* Check if host timezone file exist in the runtime tzdata */ +- if (g_file_query_exists (runtime_tzfile, NULL)) +- flatpak_bwrap_add_args (bwrap, +- "--symlink", localtime_content, "/etc/localtime", +- NULL); +- } + } + + flatpak_bwrap_add_args_data (bwrap, "timezone", +@@ -3434,24 +3452,41 @@ setup_seccomp (FlatpakBwrap *bwrap, + + static void + flatpak_run_setup_usr_links (FlatpakBwrap *bwrap, +- GFile *runtime_files, ++ int runtime_fd, + const char *sysroot) + { + int i; + +- if (runtime_files == NULL) ++ g_return_if_fail (runtime_fd >= -1); ++ ++ if (runtime_fd < 0) + return; + + for (i = 0; flatpak_abs_usrmerged_dirs[i] != NULL; i++) + { + const char *subdir = flatpak_abs_usrmerged_dirs[i]; +- g_autoptr(GFile) runtime_subdir = NULL; ++ glnx_autofd int runtime_subdir_fd = -1; ++ g_autoptr(GError) local_error = NULL; + + g_assert (subdir[0] == '/'); ++ + /* Skip the '/' when using as a subdirectory of the runtime */ +- runtime_subdir = g_file_get_child (runtime_files, subdir + 1); ++ runtime_subdir_fd = glnx_chaseat (runtime_fd, subdir + 1, ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_NOFOLLOW, ++ &local_error); + +- if (g_file_query_exists (runtime_subdir, NULL)) ++ if (runtime_subdir_fd < 0 && ++ !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) ++ { ++ g_warning ("Checking for usrmerged dir %s failed: %s", ++ subdir, local_error->message); ++ } ++ else if (runtime_subdir_fd < 0) ++ { ++ g_info ("%s does not exist in runtime", subdir); ++ } ++ else + { + g_autofree char *link = g_strconcat ("usr", subdir, NULL); + g_autofree char *create = NULL; +@@ -3465,17 +3500,12 @@ flatpak_run_setup_usr_links (FlatpakBwrap *bwrap, + "--symlink", link, create, + NULL); + } +- else +- { +- g_debug ("%s does not exist", +- flatpak_file_get_path_cached (runtime_subdir)); +- } + } + } + + gboolean + flatpak_run_setup_base_argv (FlatpakBwrap *bwrap, +- GFile *runtime_files, ++ int runtime_fd, + GFile *app_id_dir, + const char *arch, + FlatpakRunFlags flags, +@@ -3488,7 +3518,8 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap, + struct group *g; + gulong pers; + gid_t gid = getgid (); +- g_autoptr(GFile) etc = NULL; ++ ++ g_return_val_if_fail (runtime_fd >= 0, FALSE); + + run_dir = g_strdup_printf ("/run/user/%d", getuid ()); + +@@ -3563,22 +3594,25 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap, + else if (g_file_test ("/var/lib/dbus/machine-id", G_FILE_TEST_EXISTS)) + flatpak_bwrap_add_args (bwrap, "--ro-bind", "/var/lib/dbus/machine-id", "/etc/machine-id", NULL); + +- if (runtime_files) +- etc = g_file_get_child (runtime_files, "etc"); +- if (etc != NULL && +- (flags & FLATPAK_RUN_FLAG_WRITABLE_ETC) == 0 && +- g_file_query_exists (etc, NULL)) ++ if ((flags & FLATPAK_RUN_FLAG_WRITABLE_ETC) == 0) + { + g_auto(GLnxDirFdIterator) dfd_iter = { 0, }; + struct dirent *dent; + gboolean inited; ++ g_autoptr(GError) local_error = NULL; + +- inited = glnx_dirfd_iterator_init_at (AT_FDCWD, flatpak_file_get_path_cached (etc), FALSE, &dfd_iter, NULL); ++ inited = glnx_dirfd_iterator_init_at (runtime_fd, "etc", FALSE, &dfd_iter, &local_error); ++ if (!inited && !g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) ++ { ++ g_propagate_error (error, g_steal_pointer (&local_error)); ++ return FALSE; ++ } + + while (inited) + { +- g_autofree char *src = NULL; + g_autofree char *dest = NULL; ++ glnx_autofd int src_fd = -1; ++ struct stat statbuf; + + if (!glnx_dirfd_iterator_next_dent_ensure_dtype (&dfd_iter, &dent, NULL, NULL) || dent == NULL) + break; +@@ -3595,9 +3629,19 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap, + strcmp (dent->d_name, "pkcs11") == 0) + continue; + +- src = g_build_filename (flatpak_file_get_path_cached (etc), dent->d_name, NULL); + dest = g_build_filename ("/etc", dent->d_name, NULL); +- if (dent->d_type == DT_LNK) ++ ++ src_fd = glnx_chaseat (dfd_iter.fd, dent->d_name, ++ GLNX_CHASE_NOFOLLOW | ++ GLNX_CHASE_RESOLVE_BENEATH, ++ error); ++ if (src_fd < 0) ++ return FALSE; ++ ++ if (!glnx_fstat (src_fd, &statbuf, error)) ++ return FALSE; ++ ++ if (S_ISLNK (statbuf.st_mode)) + { + g_autofree char *target = NULL; + +@@ -3608,9 +3652,12 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap, + + flatpak_bwrap_add_args (bwrap, "--symlink", target, dest, NULL); + } +- else ++ else if (src_fd >= 0) + { +- flatpak_bwrap_add_args (bwrap, "--ro-bind", src, dest, NULL); ++ flatpak_bwrap_add_args_data_fd (bwrap, ++ "--ro-bind-fd", ++ g_steal_fd (&src_fd), ++ dest); + } + } + } +@@ -3631,9 +3678,9 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap, + NULL); + } + +- flatpak_run_setup_usr_links (bwrap, runtime_files, NULL); ++ flatpak_run_setup_usr_links (bwrap, runtime_fd, NULL); + +- add_tzdata_args (bwrap, runtime_files); ++ add_tzdata_args (bwrap, runtime_fd); + + pers = PER_LINUX; + +@@ -3860,7 +3907,7 @@ regenerate_ld_cache (GPtrArray *base_argv_array, + GArray *base_fd_array, + GFile *app_id_dir, + const char *checksum, +- GFile *runtime_files, ++ int runtime_fd, + gboolean generate_ld_so_conf, + GCancellable *cancellable, + GError **error) +@@ -3900,7 +3947,7 @@ regenerate_ld_cache (GPtrArray *base_argv_array, + + flatpak_bwrap_append_args (bwrap, base_argv_array); + +- flatpak_run_setup_usr_links (bwrap, runtime_files, NULL); ++ flatpak_run_setup_usr_links (bwrap, runtime_fd, NULL); + + if (generate_ld_so_conf) + { +@@ -4114,15 +4161,51 @@ open_namespace_fd_if_needed (const char *path, + return -1; + } + ++static char * ++get_path_for_fd (int fd, ++ GError **error) ++{ ++ g_autofree char *proc_path = NULL; ++ g_autofree char *path = NULL; ++ ++ proc_path = g_strdup_printf ("/proc/self/fd/%d", fd); ++ path = glnx_readlinkat_malloc (AT_FDCWD, proc_path, NULL, error); ++ if (path == NULL) ++ return NULL; ++ ++ /* All normal paths start with /, but some weird things ++ don't, such as socket:[27345] or anon_inode:[eventfd]. ++ We don't support any of these */ ++ if (path[0] != '/') ++ { ++ return glnx_null_throw (error, "%s resolves to non-absolute path %s", ++ proc_path, path); ++ } ++ ++ /* File descriptors to actually deleted files have " (deleted)" ++ appended to them. This also happens to some fake fd types ++ like shmem which are "/ (deleted)". All such ++ files are considered invalid. Unfortunately this also ++ matches files with filenames that actually end in " (deleted)", ++ but there is not much to do about this. */ ++ if (g_str_has_suffix (path, " (deleted)")) ++ { ++ return glnx_null_throw (error, "%s resolves to deleted path %s", ++ proc_path, path); ++ } ++ ++ return g_steal_pointer (&path); ++} ++ + gboolean + flatpak_run_app (FlatpakDecomposed *app_ref, + FlatpakDeploy *app_deploy, +- const char *custom_app_path, ++ int custom_app_fd, + FlatpakContext *extra_context, + const char *custom_runtime, + const char *custom_runtime_version, + const char *custom_runtime_commit, +- const char *custom_usr_path, ++ int custom_runtime_fd, + int parent_pid, + FlatpakRunFlags flags, + const char *cwd, +@@ -4137,11 +4220,6 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + g_autoptr(FlatpakDeploy) runtime_deploy = NULL; + g_autoptr(GBytes) runtime_deploy_data = NULL; + g_autoptr(GBytes) app_deploy_data = NULL; +- g_autoptr(GFile) app_files = NULL; +- g_autoptr(GFile) original_app_files = NULL; +- g_autoptr(GFile) runtime_files = NULL; +- g_autoptr(GFile) original_runtime_files = NULL; +- g_autoptr(GFile) bin_ldconfig = NULL; + g_autoptr(GFile) app_id_dir = NULL; + g_autoptr(GFile) real_app_id_dir = NULL; + g_autofree char *default_runtime_pref = NULL; +@@ -4173,18 +4251,39 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + g_autofree char *per_app_dir_lock_path = NULL; + g_autofree char *shared_xdg_runtime_dir = NULL; + int ld_so_fd = -1; +- g_autoptr(GFile) runtime_ld_so_conf = NULL; + gboolean generate_ld_so_conf = TRUE; + gboolean use_ld_so_cache = TRUE; + gboolean sandboxed = (flags & FLATPAK_RUN_FLAG_SANDBOX) != 0; + gboolean parent_expose_pids = (flags & FLATPAK_RUN_FLAG_PARENT_EXPOSE_PIDS) != 0; + gboolean parent_share_pids = (flags & FLATPAK_RUN_FLAG_PARENT_SHARE_PIDS) != 0; +- const char *app_target_path = "/app"; +- const char *runtime_target_path = "/usr"; +- struct stat s; ++ glnx_autofd int original_runtime_fd = -1; ++ g_autoptr(GFile) original_runtime_files = NULL; ++ g_autoptr(GFile) custom_runtime_files = NULL; ++ /* borrows from either original_runtime_fd or custom_runtime_fd */ ++ int runtime_fd = -1; ++ /* borrows from either original_runtime_files or custom_runtime_files */ ++ GFile *runtime_files = NULL; ++ const char *original_runtime_target_path = NULL; ++ glnx_autofd int original_app_fd = -1; ++ g_autoptr(GFile) original_app_files = NULL; ++ g_autoptr(GFile) custom_app_files = NULL; ++ /* borrows from either original_app_fd or custom_app_fd */ ++ int app_fd = -1; ++ /* borrows from either original_app_files or custom_app_files */ ++ GFile *app_files = NULL; ++ const char *original_app_target_path = NULL; + + g_return_val_if_fail (app_ref != NULL, FALSE); + ++ g_return_val_if_fail (custom_app_fd == FLATPAK_RUN_APP_DEPLOY_APP_ORIGINAL || ++ custom_app_fd == FLATPAK_RUN_APP_DEPLOY_APP_EMPTY || ++ custom_app_fd >= 0, ++ FALSE); ++ ++ g_return_val_if_fail (custom_runtime_fd == FLATPAK_RUN_APP_DEPLOY_USR_ORIGINAL || ++ custom_runtime_fd >= 0, ++ FALSE); ++ + /* This check exists to stop accidental usage of `sudo flatpak run` + and is not to prevent running as root. + */ +@@ -4303,38 +4402,53 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + flatpak_context_merge (app_context, extra_context); + + original_runtime_files = flatpak_deploy_get_files (runtime_deploy); ++ original_runtime_fd = open (flatpak_file_get_path_cached (original_runtime_files), ++ O_PATH | O_CLOEXEC); ++ if (original_runtime_fd < 0) ++ return glnx_throw_errno_prefix (error, "Failed to open original runtime"); ++ ++ if (custom_runtime_fd >= 0) ++ { ++ g_autofree char *path = NULL; ++ ++ path = get_path_for_fd (custom_runtime_fd, &my_error); ++ if (path == NULL) ++ { ++ return flatpak_fail_error (error, FLATPAK_ERROR, ++ "Cannot convert custom usr fd to path: %s", ++ my_error->message); ++ } + +- if (custom_usr_path != NULL) ++ custom_runtime_files = g_file_new_for_path (path); ++ ++ original_runtime_target_path = "/run/parent/usr"; ++ runtime_fd = custom_runtime_fd; ++ runtime_files = custom_runtime_files; ++ } ++ else if (custom_app_fd == FLATPAK_RUN_APP_DEPLOY_USR_ORIGINAL) + { +- runtime_files = g_file_new_for_path (custom_usr_path); +- /* Mount the original runtime below here instead of /usr */ +- runtime_target_path = "/run/parent/usr"; ++ original_runtime_target_path = "/usr"; ++ runtime_fd = original_runtime_fd; ++ runtime_files = original_runtime_files; + } + else + { +- runtime_files = g_object_ref (original_runtime_files); ++ g_assert_not_reached (); + } + +- bin_ldconfig = g_file_resolve_relative_path (runtime_files, "bin/ldconfig"); +- if (!g_file_query_exists (bin_ldconfig, NULL)) +- use_ld_so_cache = FALSE; +- +- /* We can't use the ld.so cache if we are using a custom /usr or /app, +- * because we don't have a unique ID for the /usr or /app, so we can't +- * do cache-invalidation correctly. The caller can either build their +- * own ld.so.cache before supplying us with the runtime, or supply +- * their own LD_LIBRARY_PATH. */ +- if (custom_usr_path != NULL || custom_app_path != NULL) +- use_ld_so_cache = FALSE; +- + if (app_deploy != NULL) + { + g_autofree const char **previous_ids = NULL; + gsize len = 0; + gboolean do_migrate; + +- real_app_id_dir = flatpak_get_data_dir (app_id); + original_app_files = flatpak_deploy_get_files (app_deploy); ++ original_app_fd = open (flatpak_file_get_path_cached (original_app_files), ++ O_PATH | O_CLOEXEC | O_NOFOLLOW); ++ if (original_app_fd < 0) ++ return glnx_throw_errno_prefix (error, "Failed to open original runtime"); ++ ++ real_app_id_dir = flatpak_get_data_dir (app_id); + + previous_app_id_dirs = g_ptr_array_new_with_free_func (g_object_unref); + previous_ids = flatpak_deploy_data_get_previous_ids (app_deploy_data, &len); +@@ -4421,19 +4535,60 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + app_id_dir = g_object_ref (real_app_id_dir); + } + +- if (custom_app_path != NULL) ++ if (custom_app_fd >= 0) + { +- if (strcmp (custom_app_path, "") == 0) +- app_files = NULL; +- else +- app_files = g_file_new_for_path (custom_app_path); ++ g_autofree char *path = NULL; ++ ++ path = get_path_for_fd (custom_app_fd, error); ++ if (path == NULL) ++ return glnx_prefix_error (error, "Cannot convert custom app fd to path"); + +- /* Mount the original app below here */ +- app_target_path = "/run/parent/app"; ++ custom_app_files = g_file_new_for_path (path); ++ ++ original_app_target_path = "/run/parent/app"; ++ app_fd = custom_app_fd; ++ app_files = custom_app_files; ++ } ++ else if (custom_app_fd == FLATPAK_RUN_APP_DEPLOY_APP_ORIGINAL) ++ { ++ original_app_target_path = "/app"; ++ app_fd = original_app_fd; ++ app_files = original_app_files; + } +- else if (original_app_files != NULL) ++ else if (custom_app_fd == FLATPAK_RUN_APP_DEPLOY_APP_EMPTY) + { +- app_files = g_object_ref (original_app_files); ++ app_fd = -1; ++ app_files = NULL; ++ } ++ else ++ { ++ g_assert_not_reached (); ++ } ++ ++ /* We can't use the ld.so cache if we are using a custom /usr or /app, ++ * because we don't have a unique ID for the /usr or /app, so we can't ++ * do cache-invalidation correctly. The caller can either build their ++ * own ld.so.cache before supplying us with the runtime, or supply ++ * their own LD_LIBRARY_PATH. */ ++ if (runtime_fd == custom_runtime_fd || app_fd == custom_app_fd) ++ { ++ use_ld_so_cache = FALSE; ++ } ++ else ++ { ++ glnx_autofd int ldconfig_fd = -1; ++ ++ ldconfig_fd = glnx_chaseat (runtime_fd, "bin/ldconfig", ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_REGULAR, ++ &my_error); ++ if (ldconfig_fd < 0) ++ { ++ use_ld_so_cache = FALSE; ++ g_debug ("bin/ldconfig not found in runtime: %s", my_error->message); ++ } ++ ++ g_clear_error (&my_error); + } + + flatpak_run_apply_env_default (bwrap, use_ld_so_cache); +@@ -4446,75 +4601,86 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + flatpak_bwrap_set_env (bwrap, "FLATPAK_SANDBOX_DIR", flatpak_file_get_path_cached (sandbox_dir), TRUE); + } + +- flatpak_bwrap_add_args (bwrap, +- "--ro-bind", flatpak_file_get_path_cached (runtime_files), "/usr", +- NULL); +- +- if (runtime_files == original_runtime_files) +- { +- /* All true Flatpak runtimes have files/.ref */ +- flatpak_bwrap_add_args (bwrap, +- "--lock-file", "/usr/.ref", +- NULL); +- } +- else +- { +- g_autoptr(GFile) runtime_child = NULL; ++ if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, ++ "--ro-bind-fd", runtime_fd, "/usr", ++ error)) ++ return FALSE; + +- runtime_child = g_file_get_child (runtime_files, ".ref"); ++ { ++ glnx_autofd int runtime_ref_fd = -1; + +- /* Lock ${usr}/.ref if it exists */ +- if (g_file_query_exists (runtime_child, NULL)) ++ runtime_ref_fd = glnx_chaseat (runtime_fd, ".ref", ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_REGULAR, ++ NULL); ++ if (runtime_ref_fd >= 0) ++ { + flatpak_bwrap_add_args (bwrap, + "--lock-file", "/usr/.ref", + NULL); ++ } ++ } ++ ++ if (runtime_fd == custom_runtime_fd) ++ { ++ glnx_autofd int original_runtime_ref_fd = -1; ++ glnx_autofd int original_runtime_etc_fd = -1; + + /* Put the real Flatpak runtime in /run/parent, so that the + * replacement /usr can have symlinks into /run/parent in order + * to use the Flatpak runtime's graphics drivers etc. if desired */ +- flatpak_bwrap_add_args (bwrap, +- "--ro-bind", +- flatpak_file_get_path_cached (original_runtime_files), +- "/run/parent/usr", +- "--lock-file", "/run/parent/usr/.ref", +- NULL); +- flatpak_run_setup_usr_links (bwrap, original_runtime_files, +- "/run/parent"); ++ if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, ++ "--ro-bind-fd", ++ original_runtime_fd, ++ "/run/parent/usr", ++ error)) ++ return FALSE; + +- g_clear_object (&runtime_child); +- runtime_child = g_file_get_child (original_runtime_files, "etc"); ++ original_runtime_ref_fd = glnx_chaseat (original_runtime_fd, ".ref", ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_REGULAR, ++ NULL); ++ if (original_runtime_ref_fd >= 0) ++ { ++ flatpak_bwrap_add_args (bwrap, ++ "--lock-file", "/run/parent/usr/.ref", ++ NULL); ++ } + +- if (g_file_query_exists (runtime_child, NULL)) +- flatpak_bwrap_add_args (bwrap, +- "--symlink", "usr/etc", "/run/parent/etc", +- NULL); ++ original_runtime_etc_fd = glnx_chaseat (original_runtime_fd, "etc", ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_REGULAR, ++ NULL); ++ if (original_runtime_etc_fd >= 0) ++ { ++ flatpak_bwrap_add_args (bwrap, ++ "--symlink", "usr/etc", "/run/parent/etc", ++ NULL); ++ } ++ ++ flatpak_run_setup_usr_links (bwrap, original_runtime_fd, ++ "/run/parent"); + } + +- if (app_files != NULL) ++ if (app_fd >= 0) + { +- flatpak_bwrap_add_args (bwrap, +- "--ro-bind", flatpak_file_get_path_cached (app_files), "/app", +- NULL); ++ glnx_autofd int app_ref_fd = -1; + +- if (app_files == original_app_files) ++ if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, ++ "--ro-bind-fd", app_fd, "/app", ++ error)) ++ return FALSE; ++ ++ app_ref_fd = glnx_chaseat (app_fd, ".ref", ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_REGULAR, ++ NULL); ++ if (app_ref_fd >= 0) + { +- /* All true Flatpak apps have files/.ref */ + flatpak_bwrap_add_args (bwrap, + "--lock-file", "/app/.ref", + NULL); + } +- else +- { +- g_autoptr(GFile) app_child = NULL; +- +- app_child = g_file_get_child (app_files, ".ref"); +- +- /* Lock ${app}/.ref if it exists */ +- if (g_file_query_exists (app_child, NULL)) +- flatpak_bwrap_add_args (bwrap, +- "--lock-file", "/app/.ref", +- NULL); +- } + } + else + { +@@ -4523,7 +4689,7 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + NULL); + } + +- if (original_app_files != NULL && app_files != original_app_files) ++ if (original_app_fd >= 0 && original_app_fd != app_fd) + { + /* Put the real Flatpak app in /run/parent/app */ + flatpak_bwrap_add_args (bwrap, +@@ -4536,26 +4702,37 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + + if (metakey != NULL && + !flatpak_run_add_extension_args (bwrap, metakey, app_ref, +- use_ld_so_cache, app_target_path, ++ use_ld_so_cache, original_app_target_path, + &app_extensions, &app_ld_path, + cancellable, error)) + return FALSE; + + if (!flatpak_run_add_extension_args (bwrap, runtime_metakey, runtime_ref, +- use_ld_so_cache, runtime_target_path, ++ use_ld_so_cache, original_runtime_target_path, + &runtime_extensions, &runtime_ld_path, + cancellable, error)) + return FALSE; + +- if (custom_usr_path == NULL) ++ if (runtime_fd == original_runtime_fd) + flatpak_run_extend_ld_path (bwrap, NULL, runtime_ld_path); + +- if (custom_app_path == NULL) ++ if (app_fd == original_app_fd) + flatpak_run_extend_ld_path (bwrap, app_ld_path, NULL); + +- runtime_ld_so_conf = g_file_resolve_relative_path (runtime_files, "etc/ld.so.conf"); +- if (lstat (flatpak_file_get_path_cached (runtime_ld_so_conf), &s) == 0) +- generate_ld_so_conf = S_ISREG (s.st_mode) && s.st_size == 0; ++ { ++ glnx_autofd int ld_so_conf_fd = -1; ++ struct glnx_statx stx; ++ ++ ld_so_conf_fd = glnx_chase_and_statxat (runtime_fd, "etc/ld.so.conf", ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_MUST_BE_REGULAR, ++ GLNX_STATX_SIZE, ++ &stx, NULL); ++ if (ld_so_conf_fd < 0 || ++ !(stx.stx_mask & GLNX_STATX_SIZE) || ++ stx.stx_size != 0) ++ generate_ld_so_conf = FALSE; ++ } + + /* At this point we have the minimal argv set up, with just the app, runtime and extensions. + We can reuse this to generate the ld.so.cache (if needed) */ +@@ -4567,7 +4744,7 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + bwrap->fds, + app_id_dir, + checksum, +- runtime_files, ++ runtime_fd, + generate_ld_so_conf, + cancellable, error); + if (ld_so_fd == -1) +@@ -4577,7 +4754,7 @@ flatpak_run_app (FlatpakDecomposed *app_ref, + + flags |= flatpak_context_get_run_flags (app_context); + +- if (!flatpak_run_setup_base_argv (bwrap, runtime_files, app_id_dir, app_arch, flags, error)) ++ if (!flatpak_run_setup_base_argv (bwrap, runtime_fd, app_id_dir, app_arch, flags, error)) + return FALSE; + + if (generate_ld_so_conf) diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078/utils-Add-flatpak_parse_fd.patch flatpak-1.14.10/debian/patches/CVE-2026-34078/utils-Add-flatpak_parse_fd.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078/utils-Add-flatpak_parse_fd.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078/utils-Add-flatpak_parse_fd.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,97 @@ +From: Sebastian Wick +Date: Fri, 6 Feb 2026 17:14:49 +0100 +Subject: utils: Add flatpak_parse_fd + +This is meant to parse file descriptor strings passed via the command +line. It is not a security mechanism and will happily accept fds 0-3 as +well. + +[smcv: Resolve conflicts with 1.14.x] + +Origin: backport, 1.16.4, commit:fbe5a2faa776e0f5bdd6160c1dd69b0b97c2d8eb +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + common/flatpak-context.c | 19 +++++++------------ + common/flatpak-utils-private.h | 3 +++ + common/flatpak-utils.c | 22 ++++++++++++++++++++++ + 3 files changed, 32 insertions(+), 12 deletions(-) + +diff --git a/common/flatpak-context.c b/common/flatpak-context.c +index 920fd40..7fe1bff 100644 +--- a/common/flatpak-context.c ++++ b/common/flatpak-context.c +@@ -1304,21 +1304,16 @@ option_env_fd_cb (const gchar *option_name, + GError **error) + { + FlatpakContext *context = data; +- guint64 fd; +- gchar *endptr; +- gboolean ret; ++ glnx_autofd int fd = -1; + +- fd = g_ascii_strtoull (value, &endptr, 10); +- +- if (endptr == NULL || *endptr != '\0' || fd > G_MAXINT) +- return glnx_throw (error, "Not a valid file descriptor: %s", value); +- +- ret = flatpak_context_parse_env_fd (context, (int) fd, error); ++ fd = flatpak_parse_fd (value, error); ++ if (fd < 0) ++ return FALSE; + +- if (fd >= 3) +- close (fd); ++ if (fd < 3) ++ return glnx_throw (error, "File descriptors 0, 1, 2 are reserved"); + +- return ret; ++ return flatpak_context_parse_env_fd (context, fd, error); + } + + static gboolean +diff --git a/common/flatpak-utils-private.h b/common/flatpak-utils-private.h +index eb97a8d..800ce5e 100644 +--- a/common/flatpak-utils-private.h ++++ b/common/flatpak-utils-private.h +@@ -944,6 +944,9 @@ gboolean flatpak_validate_path_characters (const char *path, + + gboolean running_under_sudo (void); + ++int flatpak_parse_fd (const char *fd_string, ++ GError **error); ++ + #define FLATPAK_MESSAGE_ID "c7b39b1e006b464599465e105b361485" + + #endif /* __FLATPAK_UTILS_H__ */ +diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c +index f8ac758..6a49bb1 100644 +--- a/common/flatpak-utils.c ++++ b/common/flatpak-utils.c +@@ -9419,3 +9419,25 @@ g_string_replace (GString *string, + } + + #endif /* GLIB_CHECK_VERSION (2, 68, 0) */ ++ ++int ++flatpak_parse_fd (const char *fd_string, ++ GError **error) ++{ ++ guint64 parsed; ++ char *endptr; ++ int fd; ++ struct stat stbuf; ++ ++ parsed = g_ascii_strtoull (fd_string, &endptr, 10); ++ ++ if (endptr == NULL || *endptr != '\0' || parsed > G_MAXINT) ++ return glnx_fd_throw (error, "Not a valid file descriptor: %s", fd_string); ++ ++ fd = (int) parsed; ++ ++ if (!glnx_fstat (fd, &stbuf, error)) ++ return -1; ++ ++ return fd; ++} diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/backports-Add-g_clear_fd.patch flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/backports-Add-g_clear_fd.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/backports-Add-g_clear_fd.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/backports-Add-g_clear_fd.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,140 @@ +From: Sebastian Wick +Date: Sat, 24 Jan 2026 15:55:15 +0100 +Subject: backports: Add g_clear_fd + +Origin: libglnx, commit:cdce8957b2b53065b9936e6f6ed7a973234f1692 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + subprojects/libglnx/glnx-backports.c | 72 ++++++++++++++++++++++++++++++++++++ + subprojects/libglnx/glnx-backports.h | 29 +++++++++++++++ + 2 files changed, 101 insertions(+) + +diff --git a/subprojects/libglnx/glnx-backports.c b/subprojects/libglnx/glnx-backports.c +index f9aa7ce..f924394 100644 +--- a/subprojects/libglnx/glnx-backports.c ++++ b/subprojects/libglnx/glnx-backports.c +@@ -60,3 +60,75 @@ glnx_set_object (GObject **object_ptr, + return TRUE; + } + #endif ++ ++#if !GLIB_CHECK_VERSION(2, 76, 0) ++gboolean ++_glnx_close (gint fd, ++ GError **error) ++{ ++ int res; ++ ++ /* Important: if @error is NULL, we must not do anything that is ++ * not async-signal-safe. ++ */ ++ res = close (fd); ++ ++ if (res == -1) ++ { ++ int errsv = errno; ++ ++ if (errsv == EINTR) ++ { ++ /* Just ignore EINTR for now; a retry loop is the wrong thing to do ++ * on Linux at least. Anyone who wants to add a conditional check ++ * for e.g. HP-UX is welcome to do so later... ++ * ++ * close_func_with_invalid_fds() in gspawn.c has similar logic. ++ * ++ * https://lwn.net/Articles/576478/ ++ * http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html ++ * https://bugzilla.gnome.org/show_bug.cgi?id=682819 ++ * http://utcc.utoronto.ca/~cks/space/blog/unix/CloseEINTR ++ * https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain ++ * ++ * `close$NOCANCEL()` in gstdioprivate.h, on macOS, ensures that the fd is ++ * closed even if it did return EINTR. ++ */ ++ return TRUE; ++ } ++ ++ if (error) ++ { ++ g_set_error_literal (error, G_FILE_ERROR, ++ g_file_error_from_errno (errsv), ++ g_strerror (errsv)); ++ } ++ ++ if (errsv == EBADF) ++ { ++ /* There is a bug. Fail an assertion. Note that this function is supposed to be ++ * async-signal-safe, but in case an assertion fails, all bets are already off. */ ++ if (fd >= 0) ++ { ++ /* Closing an non-negative, invalid file descriptor is a bug. The bug is ++ * not necessarily in the caller of _glnx_close(), but somebody else ++ * might have wrongly closed fd. In any case, there is a serious bug ++ * somewhere. */ ++ g_critical ("_glnx_close(fd:%d) failed with EBADF. The tracking of file descriptors got messed up", fd); ++ } ++ else ++ { ++ /* Closing a negative "file descriptor" is less problematic. It's still a nonsensical action ++ * from the caller. Assert against that too. */ ++ g_critical ("_glnx_close(fd:%d) failed with EBADF. This is not a valid file descriptor", fd); ++ } ++ } ++ ++ errno = errsv; ++ ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++#endif +diff --git a/subprojects/libglnx/glnx-backports.h b/subprojects/libglnx/glnx-backports.h +index 0c5fabe..72b68cf 100644 +--- a/subprojects/libglnx/glnx-backports.h ++++ b/subprojects/libglnx/glnx-backports.h +@@ -24,6 +24,7 @@ + + #pragma once + ++#include + #include + + G_BEGIN_DECLS +@@ -48,6 +49,34 @@ G_BEGIN_DECLS + } G_STMT_END + #endif + ++#if !GLIB_CHECK_VERSION(2, 76, 0) ++gboolean _glnx_close (gint fd, ++ GError **error); ++#else ++#define _glnx_close g_close ++#endif ++ ++#if !GLIB_CHECK_VERSION(2, 76, 0) ++static inline gboolean ++g_clear_fd (int *fd_ptr, ++ GError **error) ++{ ++ int fd = *fd_ptr; ++ ++ *fd_ptr = -1; ++ ++ if (fd < 0) ++ return TRUE; ++ ++ /* Suppress "Not available before" warning */ ++ G_GNUC_BEGIN_IGNORE_DEPRECATIONS ++ /* This importantly calls _glnx_close to always get async-signal-safe if ++ * error == NULL */ ++ return _glnx_close (fd, error); ++ G_GNUC_END_IGNORE_DEPRECATIONS ++} ++#endif ++ + #if !GLIB_CHECK_VERSION(2, 44, 0) + + #define g_strv_contains glnx_strv_contains diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/build-Add-glnx-chase.-ch-to-subprojects.patch flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/build-Add-glnx-chase.-ch-to-subprojects.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/build-Add-glnx-chase.-ch-to-subprojects.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/build-Add-glnx-chase.-ch-to-subprojects.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,31 @@ +From: Simon McVittie +Date: Mon, 6 Apr 2026 20:19:06 +0100 +Subject: build: Add glnx-chase.[ch] to subprojects + +This was unnecessary in Flatpak 1.16.x, which use Meson, but is +necessary in the 1.14.x backport. Similar to +https://gitlab.gnome.org/GNOME/libglnx/-/commit/e3006ead94a7d2c89701f011de651bce9fe6539d +upstream. + +Signed-off-by: Simon McVittie +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +Forwarded: not-needed, upstream no longer supports 1.14.x +--- + subprojects/Makefile-libglnx.am.inc | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/subprojects/Makefile-libglnx.am.inc b/subprojects/Makefile-libglnx.am.inc +index 6115863..c947ed1 100644 +--- a/subprojects/Makefile-libglnx.am.inc ++++ b/subprojects/Makefile-libglnx.am.inc +@@ -35,6 +35,8 @@ libglnx_la_SOURCES = \ + subprojects/libglnx/glnx-backport-autoptr.h \ + subprojects/libglnx/glnx-backports.h \ + subprojects/libglnx/glnx-backports.c \ ++ subprojects/libglnx/glnx-chase.h \ ++ subprojects/libglnx/glnx-chase.c \ + subprojects/libglnx/glnx-local-alloc.h \ + subprojects/libglnx/glnx-local-alloc.c \ + subprojects/libglnx/glnx-errors.h \ diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/build-Link-libglnx-into-libflatpak-common-not-just-into-l.patch flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/build-Link-libglnx-into-libflatpak-common-not-just-into-l.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/build-Link-libglnx-into-libflatpak-common-not-just-into-l.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/build-Link-libglnx-into-libflatpak-common-not-just-into-l.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,35 @@ +From: Simon McVittie +Date: Mon, 6 Apr 2026 21:41:27 +0100 +Subject: build: Link libglnx into libflatpak-common, not just into libflatpak + +We'll need this for glnx_chaseat(). + +Signed-off-by: Simon McVittie +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +Forwarded: not-needed, upstream no longer supports 1.14.x +--- + common/Makefile.am.inc | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/common/Makefile.am.inc b/common/Makefile.am.inc +index 7d7bf28..da2e42e 100644 +--- a/common/Makefile.am.inc ++++ b/common/Makefile.am.inc +@@ -222,6 +222,7 @@ libflatpak_common_la_LIBADD = \ + $(SYSTEMD_LIBS) \ + $(XAUTH_LIBS) \ + $(XML_LIBS) \ ++ libglnx.la \ + $(NULL) + + +@@ -253,7 +254,6 @@ libflatpak_la_LIBADD = \ + $(AM_LIBADD) \ + libflatpak-common.la \ + libflatpak-common-base.la \ +- libglnx.la \ + $(BASE_LIBS) \ + $(OSTREE_LIBS) \ + $(CURL_LIBS) \ diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Add-glnx_chase_and_statxat.patch flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Add-glnx_chase_and_statxat.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Add-glnx_chase_and_statxat.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Add-glnx_chase_and_statxat.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,83 @@ +From: Sebastian Wick +Date: Sat, 7 Feb 2026 18:12:43 +0100 +Subject: chase: Add glnx_chase_and_statxat + +This combines glnx_chaseat with a call to glnx_statx for convenience. + +Origin: libglnx, commit:b4b9951901ddb8d15374fcc24bd78d2dcce98fc9 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + subprojects/libglnx/glnx-chase.c | 44 ++++++++++++++++++++++++++++++++++++++++ + subprojects/libglnx/glnx-chase.h | 7 +++++++ + 2 files changed, 51 insertions(+) + +diff --git a/subprojects/libglnx/glnx-chase.c b/subprojects/libglnx/glnx-chase.c +index ccf6242..70eb760 100644 +--- a/subprojects/libglnx/glnx-chase.c ++++ b/subprojects/libglnx/glnx-chase.c +@@ -732,3 +732,47 @@ glnx_chaseat (int dirfd, + + return g_steal_fd (&fd); + } ++ ++/** ++ * glnx_chase_and_statxat: ++ * @dirfd: a directory file descriptor ++ * @path: a path ++ * @flags: combination of GlnxChaseFlags flags ++ * @mask: combination of GLNX_STATX_ flags ++ * @statbuf: a pointer to a struct glnx_statx which will be filled out ++ * @error: a #GError ++ * ++ * Stats the file at @path relative to @dirfd and fills out @statbuf with the ++ * result according to the interest mask @mask. ++ * ++ * See glnx_chaseat for the meaning of @dirfd, @path, and @flags. ++ * ++ * Returns: the chased file, or -1 with @error set on error ++ */ ++int ++glnx_chase_and_statxat (int dirfd, ++ const char *path, ++ GlnxChaseFlags flags, ++ unsigned int mask, ++ struct glnx_statx *statbuf, ++ GError **error) ++{ ++ glnx_autofd int fd = -1; ++ ++ /* other args are checked by glnx_chaseat */ ++ g_return_val_if_fail (statbuf != NULL, FALSE); ++ ++ fd = glnx_chaseat (dirfd, path, flags, error); ++ if (fd < 0) ++ return -1; ++ ++ if (!glnx_statx (fd, "", ++ AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW | ++ ((flags & GLNX_CHASE_NO_AUTOMOUNT) ? AT_NO_AUTOMOUNT : 0), ++ mask, ++ statbuf, ++ error)) ++ return -1; ++ ++ return g_steal_fd (&fd); ++} +diff --git a/subprojects/libglnx/glnx-chase.h b/subprojects/libglnx/glnx-chase.h +index 06ead81..05dac12 100644 +--- a/subprojects/libglnx/glnx-chase.h ++++ b/subprojects/libglnx/glnx-chase.h +@@ -41,4 +41,11 @@ int glnx_chaseat (int dirfd, + GlnxChaseFlags flags, + GError **error); + ++int glnx_chase_and_statxat (int dirfd, ++ const char *path, ++ GlnxChaseFlags flags, ++ unsigned int mask, ++ struct glnx_statx *statbuf, ++ GError **error); ++ + G_END_DECLS diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Add-glnx_chaseat-which-functions-similar-to-openat2.patch flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Add-glnx_chaseat-which-functions-similar-to-openat2.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Add-glnx_chaseat-which-functions-similar-to-openat2.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Add-glnx_chaseat-which-functions-similar-to-openat2.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,824 @@ +From: Sebastian Wick +Date: Tue, 17 Feb 2026 23:00:36 +0100 +Subject: chase: Add glnx_chaseat which functions similar to openat2 + +The selling features are: + +* Support for RESOLVE_BENEATH, RESOLVE_IN_ROOT and RESOLVE_NO_SYMLINKS +* Fallback from openat2 to open_tree to openat for compatibility +* Triggering of automounts + +Origin: libglnx, commit:a973baad083aeee61d57245b0b3dd208ce42d930 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + subprojects/libglnx/glnx-chase.c | 734 +++++++++++++++++++++++++++++++++++++++ + subprojects/libglnx/glnx-chase.h | 44 +++ + subprojects/libglnx/libglnx.h | 1 + + 3 files changed, 779 insertions(+) + create mode 100644 subprojects/libglnx/glnx-chase.c + create mode 100644 subprojects/libglnx/glnx-chase.h + +diff --git a/subprojects/libglnx/glnx-chase.c b/subprojects/libglnx/glnx-chase.c +new file mode 100644 +index 0000000..ccf6242 +--- /dev/null ++++ b/subprojects/libglnx/glnx-chase.c +@@ -0,0 +1,734 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- ++ * ++ * Copyright (C) 2026 Red Hat, Inc. ++ * SPDX-License-Identifier: LGPL-2.1-or-later ++ * ++ * glnx_chaseat was inspired by systemd's chase ++ */ ++ ++#include "libglnx-config.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#define AUTOFS_SUPER_MAGIC 0x0187 /* man fstatfs */ ++ ++#define GLNX_CHASE_DEBUG_NO_OPENAT2 (1 << 31) ++#define GLNX_CHASE_DEBUG_NO_OPEN_TREE (1 << 30) ++ ++#define GLNX_CHASE_ALL_DEBUG_FLAGS \ ++ (GLNX_CHASE_DEBUG_NO_OPENAT2 | \ ++ GLNX_CHASE_DEBUG_NO_OPEN_TREE) ++ ++#define GLNX_CHASE_ALL_REGULAR_FLAGS \ ++ (GLNX_CHASE_NO_AUTOMOUNT | \ ++ GLNX_CHASE_NOFOLLOW | \ ++ GLNX_CHASE_RESOLVE_BENEATH | \ ++ GLNX_CHASE_RESOLVE_IN_ROOT | \ ++ GLNX_CHASE_RESOLVE_NO_SYMLINKS | \ ++ GLNX_CHASE_MUST_BE_REGULAR | \ ++ GLNX_CHASE_MUST_BE_DIRECTORY | \ ++ GLNX_CHASE_MUST_BE_SOCKET) ++ ++#define GLNX_CHASE_ALL_FLAGS \ ++ (GLNX_CHASE_ALL_DEBUG_FLAGS | GLNX_CHASE_ALL_REGULAR_FLAGS) ++ ++typedef GQueue GlnxStatxQueue; ++ ++static void ++glnx_statx_queue_free_element (gpointer element, ++ G_GNUC_UNUSED gpointer userdata) ++{ ++ g_free (element); ++} ++ ++static void ++glnx_statx_queue_free (GlnxStatxQueue *squeue) ++{ ++ GQueue *queue = (GQueue *) squeue; ++ ++ /* Same as g_queue_clear_full (queue, g_free), but works for <2.60 */ ++ g_queue_foreach (queue, glnx_statx_queue_free_element, NULL); ++ g_queue_clear (queue); ++} ++ ++G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(GlnxStatxQueue, glnx_statx_queue_free) ++ ++static gboolean ++glnx_statx_inode_same (const struct glnx_statx *a, ++ const struct glnx_statx *b) ++{ ++ g_assert ((a->stx_mask & (GLNX_STATX_TYPE | GLNX_STATX_INO)) == ++ (GLNX_STATX_TYPE | GLNX_STATX_INO)); ++ g_assert ((b->stx_mask & (GLNX_STATX_TYPE | GLNX_STATX_INO)) == ++ (GLNX_STATX_TYPE | GLNX_STATX_INO)); ++ ++ return ((a->stx_mode ^ b->stx_mode) & S_IFMT) == 0 && ++ a->stx_dev_major == b->stx_dev_major && ++ a->stx_dev_minor == b->stx_dev_minor && ++ a->stx_ino == b->stx_ino; ++} ++ ++static gboolean ++glnx_statx_mount_same (const struct glnx_statx *a, ++ const struct glnx_statx *b) ++{ ++ g_assert ((a->stx_mask & (GLNX_STATX_MNT_ID | GLNX_STATX_MNT_ID_UNIQUE)) != 0); ++ g_assert ((b->stx_mask & (GLNX_STATX_MNT_ID | GLNX_STATX_MNT_ID_UNIQUE)) != 0); ++ ++ return a->stx_mnt_id == b->stx_mnt_id; ++} ++ ++static gboolean ++glnx_chase_statx (int dfd, ++ int additional_flags, ++ struct glnx_statx *buf, ++ GError **error) ++{ ++ if (!glnx_statx (dfd, "", ++ AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW | additional_flags, ++ GLNX_STATX_TYPE | GLNX_STATX_INO | ++ GLNX_STATX_MNT_ID | GLNX_STATX_MNT_ID_UNIQUE, ++ buf, ++ error)) ++ return FALSE; ++ ++ if ((buf->stx_mask & (GLNX_STATX_TYPE | GLNX_STATX_INO)) != ++ (GLNX_STATX_TYPE | GLNX_STATX_INO) || ++ (buf->stx_mask & (GLNX_STATX_MNT_ID | GLNX_STATX_MNT_ID_UNIQUE)) == 0) ++ { ++ errno = ENODATA; ++ return glnx_throw_errno_prefix (error, ++ "statx didn't return all required fields"); ++ } ++ ++ return TRUE; ++} ++ ++/* TODO: procfs magiclinks handling */ ++ ++/* open_tree subset which transparently falls back to openat. ++ * ++ * Returned fd is always OPATH and CLOEXEC. ++ * ++ * With NO_AUTOMOUNT this function never triggers automounts. Otherwise, it only ++ * guarantees to trigger an automount which is on last segment of the path! ++ * ++ * flags can be a combinations of: ++ * - GLNX_CHASE_NO_AUTOMOUNT ++ * - GLNX_CHASE_NOFOLLOW ++ */ ++static int ++chase_open_tree (int dirfd, ++ const char *path, ++ GlnxChaseFlags flags, ++ GError **error) ++{ ++ glnx_autofd int fd = -1; ++ static gboolean can_open_tree = TRUE; ++ unsigned int openat_flags = 0; ++ ++ g_assert ((flags & ~(GLNX_CHASE_NO_AUTOMOUNT | ++ GLNX_CHASE_NOFOLLOW | ++ GLNX_CHASE_ALL_DEBUG_FLAGS)) == 0); ++ ++ /* First we try to actually use open_tree, and then fall back to the impl ++ * using openat. ++ * Technically racy (static, not synced), but both paths work fine so it ++ * doesn't matter. */ ++ if (can_open_tree && (flags & GLNX_CHASE_DEBUG_NO_OPEN_TREE) == 0) ++ { ++ unsigned int open_tree_flags = 0; ++ ++ open_tree_flags = OPEN_TREE_CLOEXEC; ++ if ((flags & GLNX_CHASE_NOFOLLOW) != 0) ++ open_tree_flags |= AT_SYMLINK_NOFOLLOW; ++ if ((flags & GLNX_CHASE_NO_AUTOMOUNT) != 0) ++ open_tree_flags |= AT_NO_AUTOMOUNT; ++ ++ fd = open_tree (dirfd, path, open_tree_flags); ++ ++ /* If open_tree is not supported, or blocked (EPERM), we fall back to ++ * openat */ ++ if (fd < 0 && G_IN_SET (errno, ++ EOPNOTSUPP, ++ ENOTTY, ++ ENOSYS, ++ EAFNOSUPPORT, ++ EPFNOSUPPORT, ++ EPROTONOSUPPORT, ++ ESOCKTNOSUPPORT, ++ ENOPROTOOPT, ++ EPERM)) ++ can_open_tree = FALSE; ++ else if (fd < 0) ++ return glnx_fd_throw_errno_prefix (error, "open_tree"); ++ else ++ return g_steal_fd (&fd); ++ } ++ ++ openat_flags = O_CLOEXEC | O_PATH; ++ if ((flags & GLNX_CHASE_NOFOLLOW) != 0) ++ openat_flags |= O_NOFOLLOW; ++ ++ fd = openat (dirfd, path, openat_flags); ++ if (fd < 0) ++ return glnx_fd_throw_errno_prefix (error, "openat in open_tree fallback"); ++ ++ /* openat does not trigger automounts, so we have to manually do so ++ * unless NO_AUTOMOUNT was specified */ ++ if ((flags & GLNX_CHASE_NO_AUTOMOUNT) == 0) ++ { ++ struct statfs stfs; ++ ++ if (fstatfs (fd, &stfs) < 0) ++ return glnx_fd_throw_errno_prefix (error, "fstatfs in open_tree fallback"); ++ ++ /* fstatfs(2) can then be used to determine if it is, in fact, an ++ * untriggered automount point (.f_type == AUTOFS_SUPER_MAGIC). */ ++ if (stfs.f_type == AUTOFS_SUPER_MAGIC) ++ { ++ glnx_autofd int new_fd = -1; ++ ++ new_fd = openat (fd, ".", openat_flags | O_DIRECTORY); ++ /* For some reason, openat with O_PATH | O_DIRECTORY does trigger ++ * automounts, without us having to actually open the file, so let's ++ * use this here. It only works for directories though. */ ++ if (new_fd >= 0) ++ return g_steal_fd (&new_fd); ++ ++ if (errno != ENOTDIR) ++ return glnx_fd_throw_errno_prefix (error, "openat(O_DIRECTORY) in autofs mount open_tree fallback"); ++ ++ /* The automount is a directory, so let's try to open the file, ++ * which can fail because we are missing permissions, but that's ++ * okay, we only need to trigger automount. */ ++ new_fd = openat (fd, ".", (openat_flags & ~O_PATH) | ++ O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY); ++ glnx_close_fd (&new_fd); ++ ++ /* And try again with O_PATH */ ++ new_fd = openat (dirfd, path, openat_flags); ++ if (new_fd < 0) ++ return glnx_fd_throw_errno_prefix (error, "reopening in autofs mount open_tree fallback"); ++ ++ if (fstatfs (new_fd, &stfs) < 0) ++ return glnx_fd_throw_errno_prefix (error, "fstatfs in autofs mount open_tree fallback"); ++ ++ /* bail if we didn't manage to trigger the automount */ ++ if (stfs.f_type == AUTOFS_SUPER_MAGIC) ++ { ++ errno = EOPNOTSUPP; ++ return glnx_fd_throw_errno_prefix (error, "unable to trigger automount"); ++ } ++ ++ return g_steal_fd (&new_fd); ++ } ++ } ++ ++ return g_steal_fd (&fd); ++} ++ ++static int ++open_cwd (GlnxChaseFlags flags, ++ GError **error) ++{ ++ GLNX_AUTO_PREFIX_ERROR ("cannot open working directory", error); ++ ++ /* NO_AUTOMOUNT should be fine here because automount must have been ++ * triggered already for the CWD */ ++ return chase_open_tree (AT_FDCWD, ".", ++ (flags & GLNX_CHASE_ALL_DEBUG_FLAGS) | ++ GLNX_CHASE_NO_AUTOMOUNT | ++ GLNX_CHASE_NOFOLLOW, ++ error); ++} ++ ++static int ++open_root (GlnxChaseFlags flags, ++ GError **error) ++{ ++ GLNX_AUTO_PREFIX_ERROR ("cannot open root directory", error); ++ ++ /* NO_AUTOMOUNT should be fine here because automount must have been ++ * triggered already for the root */ ++ return chase_open_tree (AT_FDCWD, "/", ++ (flags & GLNX_CHASE_ALL_DEBUG_FLAGS) | ++ GLNX_CHASE_NO_AUTOMOUNT | ++ GLNX_CHASE_NOFOLLOW, ++ error); ++} ++ ++/* This returns the next segment of a path and tells us if it is the last ++ * segment. ++ * ++ * Importantly, a segment is anything after a "/", even if it is empty or ".". ++ * ++ * For example: ++ * "" -> "" ++ * "/" -> "" ++ * "////" -> "" ++ * "foo/bar" -> "foo", "bar" ++ * "foo//bar" -> "foo", "bar" ++ * "///foo//bar" -> "foo", "bar" ++ * "///foo//bar/" -> "foo", "bar", "" ++ * "///foo//bar/." -> "foo", "bar", "." ++ */ ++static char * ++extract_next_segment (const char **remaining, ++ gboolean *is_last) ++{ ++ const char *r = *remaining; ++ const char *s; ++ size_t len = 0; ++ ++ while (r[0] != '\0' && G_IS_DIR_SEPARATOR (r[0])) ++ r++; ++ ++ s = r; ++ ++ while (r[0] != '\0' && !G_IS_DIR_SEPARATOR (r[0])) ++ { ++ r++; ++ len++; ++ } ++ ++ *is_last = (r[0] == '\0'); ++ *remaining = r; ++ return g_strndup (s, len); ++} ++ ++/* This iterates over the segments of path and opens the corresponding ++ * directories or files. This gives us the opportunity to implement openat2 ++ * like RESOLVE_ semantics, without actually needing openat2. ++ * It also allows us to implement features which openat2 does not have because ++ * we're in full control over the resolving. ++ */ ++static int ++chase_manual (int dirfd, ++ const char *path, ++ GlnxChaseFlags flags, ++ GError **error) ++{ ++ gboolean is_absolute; ++ g_autofree char *buffer = NULL; ++ const char *remaining; ++ glnx_autofd int owned_root_fd = -1; ++ int root_fd; ++ glnx_autofd int owned_fd = -1; ++ int fd; ++ int remaining_follows = GLNX_CHASE_MAX; ++ struct glnx_statx st; ++ g_auto(GlnxStatxQueue) path_st = G_QUEUE_INIT; ++ int no_automount; ++ ++ /* Take a shortcut if ++ * - 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) ++ * ++ * TODO: if we have a guarantee that the open_tree syscall works, we can ++ * shortcut even without GLNX_CHASE_NO_AUTOMOUNT ++ */ ++ if ((flags & (GLNX_CHASE_NO_AUTOMOUNT | ++ GLNX_CHASE_RESOLVE_BENEATH | ++ GLNX_CHASE_RESOLVE_IN_ROOT | ++ GLNX_CHASE_RESOLVE_NO_SYMLINKS)) == GLNX_CHASE_NO_AUTOMOUNT) ++ { ++ GlnxChaseFlags open_tree_flags = ++ (flags & (GLNX_CHASE_NOFOLLOW | GLNX_CHASE_ALL_DEBUG_FLAGS)); ++ ++ return chase_open_tree (dirfd, path, open_tree_flags, error); ++ } ++ ++ no_automount = (flags & GLNX_CHASE_NO_AUTOMOUNT) != 0 ? AT_NO_AUTOMOUNT : 0; ++ ++ is_absolute = g_path_is_absolute (path); ++ ++ if (is_absolute && (flags & GLNX_CHASE_RESOLVE_BENEATH) != 0) ++ { ++ /* Absolute paths always get rejected with RESOLVE_BENEATH with errno ++ * EXDEV */ ++ ++ errno = EXDEV; ++ return glnx_fd_throw_errno_prefix (error, "absolute path not allowed for RESOLVE_BENEATH"); ++ } ++ else if (!is_absolute || ++ (is_absolute && (flags & GLNX_CHASE_RESOLVE_IN_ROOT) != 0)) ++ { ++ /* The absolute path is relative to dirfd with GLNX_CHASE_RESOLVE_IN_ROOT, ++ * and a relative path is always relative. */ ++ ++ /* In both cases we use dirfd as our chase root */ ++ if (dirfd == AT_FDCWD) ++ { ++ owned_root_fd = root_fd = open_cwd (flags, error); ++ if (root_fd < 0) ++ return -1; ++ } ++ else ++ { ++ root_fd = dirfd; ++ } ++ } ++ else ++ { ++ /* For absolute paths, we ignore dirfd, we use the actual root / for our ++ * chase root */ ++ g_assert (is_absolute); ++ ++ owned_root_fd = root_fd = open_root (flags, error); ++ if (root_fd < 0) ++ return -1; ++ } ++ ++ /* At this point, we always have (a relative) path, relative to root_fd */ ++ is_absolute = FALSE; ++ g_assert (root_fd >= 0); ++ ++ /* Add root to path_st, so we can verify if we get back to it */ ++ if (!glnx_chase_statx (root_fd, no_automount, &st, error)) ++ return -1; ++ ++ g_queue_push_tail (&path_st, g_memdup2 (&st, sizeof (st))); ++ ++ /* Let's start walking the path! */ ++ buffer = g_strdup (path); ++ remaining = buffer; ++ fd = root_fd; ++ ++ for (;;) ++ { ++ g_autofree char *segment = NULL; ++ gboolean is_last; ++ glnx_autofd int next_fd = -1; ++ ++ segment = extract_next_segment (&remaining, &is_last); ++ ++ /* If we encounter an empty segment ("", "."), we stay where we are and ++ * ignore the segment, or just exit if it is the last segment. */ ++ if (g_strcmp0 (segment, "") == 0 || g_strcmp0 (segment, ".") == 0) ++ { ++ if (is_last) ++ break; ++ continue; ++ } ++ ++ /* Special handling for going down the tree with RESOLVE_ flags */ ++ if (g_strcmp0 (segment, "..") == 0) ++ { ++ /* path_st contains the stat of the root if we're at root, so the ++ * length is 1 in that case, and going lower than the root is not ++ * allowed here! */ ++ ++ if (path_st.length <= 1 && (flags & GLNX_CHASE_RESOLVE_BENEATH) != 0) ++ { ++ /* With RESOLVE_BENEATH, error out if we would end up above the ++ * root fd */ ++ errno = EXDEV; ++ return glnx_fd_throw_errno_prefix (error, "attempted to traverse above root path via \"..\""); ++ } ++ else if (path_st.length <= 1 && (flags & GLNX_CHASE_RESOLVE_IN_ROOT) != 0) ++ { ++ /* With RESOLVE_IN_ROOT, we pretend that we hit the real root, ++ * and stay there, just like the kernel does. */ ++ continue; ++ } ++ } ++ ++ { ++ /* Open the next segment. We always use GLNX_CHASE_NOFOLLOW here to be ++ * able to ensure the RESOLVE flags, and automount behavior. */ ++ ++ GlnxChaseFlags open_tree_flags = ++ GLNX_CHASE_NOFOLLOW | ++ (flags & (GLNX_CHASE_NO_AUTOMOUNT | GLNX_CHASE_ALL_DEBUG_FLAGS)); ++ ++ next_fd = chase_open_tree (fd, segment, open_tree_flags, error); ++ if (next_fd < 0) ++ return -1; ++ } ++ ++ if (!glnx_chase_statx (next_fd, no_automount, &st, error)) ++ return -1; ++ ++ /* We resolve links if: they are not in the last component, or if they ++ * are the last component and NOFOLLOW is not set. */ ++ if (S_ISLNK (st.stx_mode) && ++ (!is_last || (flags & GLNX_CHASE_NOFOLLOW) == 0)) ++ { ++ g_autofree char *link = NULL; ++ g_autofree char *new_buffer = NULL; ++ ++ /* ...however, we do not resolve symlinks with NO_SYMLINKS, and use ++ * remaining_follows to ensure we don't loop forever. */ ++ if ((flags & GLNX_CHASE_RESOLVE_NO_SYMLINKS) != 0 || ++ --remaining_follows <= 0) ++ { ++ errno = ELOOP; ++ return glnx_fd_throw_errno_prefix (error, "followed too many symlinks"); ++ } ++ ++ /* AT_EMPTY_PATH is implied for readlinkat */ ++ link = glnx_readlinkat_malloc (next_fd, "", NULL, error); ++ if (!link) ++ return -1; ++ ++ if (g_path_is_absolute (link) && ++ (flags & GLNX_CHASE_RESOLVE_BENEATH) != 0) ++ { ++ errno = EXDEV; ++ return glnx_fd_throw_errno_prefix (error, "absolute symlink not allowed for RESOLVE_BENEATH"); ++ } ++ ++ /* The link can be absolute, and we handle that below, by changing the ++ * dirfd. The path *remains* and absolute path internally, but that is ++ * okay because we always interpret any path (even absolute ones) as ++ * being relative to the dirfd */ ++ new_buffer = g_strdup_printf ("%s/%s", link, remaining); ++ g_clear_pointer (&buffer, g_free); ++ buffer = g_steal_pointer (&new_buffer); ++ remaining = buffer; ++ ++ if (g_path_is_absolute (link)) ++ { ++ if ((flags & GLNX_CHASE_RESOLVE_IN_ROOT) != 0) ++ { ++ /* If the path was absolute, and RESOLVE_IN_ROOT is set, we ++ * will resolve the remaining path relative to root_fd */ ++ ++ g_clear_fd (&owned_fd, NULL); ++ fd = root_fd; ++ } ++ else ++ { ++ /* If the path was absolute, we will resolve the remaining ++ * path relative to the real root */ ++ ++ g_clear_fd (&owned_fd, NULL); ++ fd = owned_fd = open_root (flags, error); ++ if (fd < 0) ++ return -1; ++ } ++ ++ /* path_st must only contain the new root at this point */ ++ if (!glnx_chase_statx (fd, no_automount, &st, error)) ++ return -1; ++ ++ glnx_statx_queue_free (&path_st); ++ g_queue_init (&path_st); ++ g_queue_push_tail (&path_st, g_memdup2 (&st, sizeof (st))); ++ } ++ ++ continue; ++ } ++ ++ /* Either adds an element to path_st or removes one if we got down the ++ * tree. This also checks that going down the tree ends up at the inode ++ * we saw before (if we saw it before). */ ++ if (g_strcmp0 (segment, "..") == 0) ++ { ++ struct glnx_statx *lower_st; ++ ++ g_queue_pop_tail (&path_st); ++ ++ lower_st = g_queue_peek_tail (&path_st); ++ if (lower_st && ++ (!glnx_statx_mount_same (&st, lower_st) || ++ !glnx_statx_inode_same (&st, lower_st))) ++ { ++ errno = EXDEV; ++ return glnx_fd_throw_errno_prefix (error, "a parent directory changed while traversing"); ++ } ++ } ++ else ++ { ++ g_queue_push_tail (&path_st, g_memdup2 (&st, sizeof (st))); ++ } ++ ++ /* There is still another path component, but the next fd is not a ++ * a directory. We need the fd to be a directory though, to open the next ++ * segment from. So bail with the appropriate error. */ ++ if (!is_last && !S_ISDIR (st.stx_mode)) ++ { ++ errno = ENOTDIR; ++ return glnx_fd_throw_errno_prefix (error, "a non-final path segment is not a directory"); ++ } ++ ++ g_clear_fd (&owned_fd, NULL); ++ fd = owned_fd = g_steal_fd (&next_fd); ++ ++ if (is_last) ++ break; ++ } ++ ++ /* We need an owned fd to return. Only having fd and not owned_fd can happen ++ * if we never finished a single iteration, or if an absolute path with ++ * RESOLVE_IN_ROOT makes us point at root_fd. ++ * We just re-open fd to always get an owned fd. ++ * Note that this only works because in all cases where owned_fd does not ++ * exists, fd is a directory. */ ++ if (owned_fd < 0) ++ { ++ owned_fd = openat (fd, ".", O_PATH | O_CLOEXEC | O_NOFOLLOW); ++ if (owned_fd < 0) ++ return glnx_fd_throw_errno_prefix (error, "reopening failed"); ++ } ++ ++ 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 gboolean can_openat2 = TRUE; ++ glnx_autofd int fd = -1; ++ ++ g_return_val_if_fail (dirfd >= 0 || dirfd == AT_FDCWD, -1); ++ g_return_val_if_fail (path != NULL, -1); ++ g_return_val_if_fail ((flags & ~(GLNX_CHASE_ALL_FLAGS)) == 0, -1); ++ g_return_val_if_fail (error == NULL || *error == NULL, -1); ++ ++ { ++ int must_flags = flags & (GLNX_CHASE_MUST_BE_REGULAR | ++ GLNX_CHASE_MUST_BE_DIRECTORY | ++ GLNX_CHASE_MUST_BE_SOCKET); ++ /* check that no more than one bit is set (= power of two) */ ++ 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) ++ { ++ uint64_t openat2_flags = 0; ++ uint64_t openat2_resolve = 0; ++ struct open_how how; ++ ++ openat2_flags = O_PATH | O_CLOEXEC; ++ if ((flags & GLNX_CHASE_NOFOLLOW) != 0) ++ openat2_flags |= O_NOFOLLOW; ++ ++ openat2_resolve |= RESOLVE_NO_MAGICLINKS; ++ if ((flags & GLNX_CHASE_RESOLVE_BENEATH) != 0) ++ openat2_resolve |= RESOLVE_BENEATH; ++ if ((flags & GLNX_CHASE_RESOLVE_IN_ROOT) != 0) ++ openat2_resolve |= RESOLVE_IN_ROOT; ++ if ((flags & GLNX_CHASE_RESOLVE_NO_SYMLINKS) != 0) ++ openat2_resolve |= RESOLVE_NO_SYMLINKS; ++ ++ how = (struct open_how) { ++ .flags = openat2_flags, ++ .mode = 0, ++ .resolve = openat2_resolve, ++ }; ++ ++ fd = openat2 (dirfd, path, &how, sizeof (how)); ++ if (fd < 0) ++ { ++ /* If the syscall is not implemented (ENOSYS) or blocked by ++ * seccomp (EPERM), we need to fall back to the manual path chasing ++ * via open_tree. */ ++ if (G_IN_SET (errno, ENOSYS, EPERM)) ++ can_openat2 = FALSE; ++ else ++ return glnx_fd_throw_errno (error); ++ } ++ } ++ ++ if (fd < 0) ++ { ++ fd = chase_manual (dirfd, path, flags, error); ++ if (fd < 0) ++ return -1; ++ } ++ ++ if ((flags & (GLNX_CHASE_MUST_BE_REGULAR | ++ GLNX_CHASE_MUST_BE_DIRECTORY | ++ GLNX_CHASE_MUST_BE_SOCKET)) != 0) ++ { ++ struct glnx_statx st; ++ ++ if (!glnx_statx (fd, "", ++ AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW | ++ ((flags & GLNX_CHASE_NO_AUTOMOUNT) ? AT_NO_AUTOMOUNT : 0), ++ GLNX_STATX_TYPE, ++ &st, ++ error)) ++ return -1; ++ ++ if ((st.stx_mask & GLNX_STATX_TYPE) == 0) ++ { ++ errno = ENODATA; ++ return glnx_fd_throw_errno_prefix (error, "unable to get file type"); ++ } ++ ++ if ((flags & GLNX_CHASE_MUST_BE_REGULAR) != 0 && ++ !S_ISREG (st.stx_mode)) ++ { ++ if (S_ISDIR (st.stx_mode)) ++ errno = EISDIR; ++ else ++ errno = EBADFD; ++ ++ return glnx_fd_throw_errno_prefix (error, "not a regular file"); ++ } ++ ++ if ((flags & GLNX_CHASE_MUST_BE_DIRECTORY) != 0 && ++ !S_ISDIR (st.stx_mode)) ++ { ++ errno = ENOTDIR; ++ return glnx_fd_throw_errno_prefix (error, "not a directory"); ++ } ++ ++ if ((flags & GLNX_CHASE_MUST_BE_SOCKET) != 0 && ++ !S_ISSOCK (st.stx_mode)) ++ { ++ errno = ENOTSOCK; ++ return glnx_fd_throw_errno_prefix (error, "not a socket"); ++ } ++ } ++ ++ return g_steal_fd (&fd); ++} +diff --git a/subprojects/libglnx/glnx-chase.h b/subprojects/libglnx/glnx-chase.h +new file mode 100644 +index 0000000..06ead81 +--- /dev/null ++++ b/subprojects/libglnx/glnx-chase.h +@@ -0,0 +1,44 @@ ++/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- ++ * ++ * Copyright (C) 2026 Red Hat, Inc. ++ * SPDX-License-Identifier: LGPL-2.1-or-later ++ */ ++ ++#pragma once ++ ++#include ++ ++typedef enum _GlnxChaseFlags { ++ /* Default */ ++ GLNX_CHASE_DEFAULT = 0, ++ /* Disable triggering of automounts */ ++ GLNX_CHASE_NO_AUTOMOUNT = 1 << 1, ++ /* Do not follow the path's right-most component. When the path's right-most ++ * component refers to symlink, return O_PATH fd of the symlink. */ ++ GLNX_CHASE_NOFOLLOW = 1 << 2, ++ /* Do not permit the path resolution to succeed if any component of the ++ * resolution is not a descendant of the directory indicated by dirfd. */ ++ GLNX_CHASE_RESOLVE_BENEATH = 1 << 3, ++ /* Symlinks are resolved relative to the given dirfd instead of root. */ ++ GLNX_CHASE_RESOLVE_IN_ROOT = 1 << 4, ++ /* Fail if any symlink is encountered. */ ++ GLNX_CHASE_RESOLVE_NO_SYMLINKS = 1 << 5, ++ /* Fail if the path's right-most component is not a regular file */ ++ GLNX_CHASE_MUST_BE_REGULAR = 1 << 6, ++ /* Fail if the path's right-most component is not a directory */ ++ GLNX_CHASE_MUST_BE_DIRECTORY = 1 << 7, ++ /* Fail if the path's right-most component is not a socket */ ++ GLNX_CHASE_MUST_BE_SOCKET = 1 << 8, ++} GlnxChaseFlags; ++ ++/* How many iterations to execute before returning ELOOP */ ++#define GLNX_CHASE_MAX 32 ++ ++G_BEGIN_DECLS ++ ++int glnx_chaseat (int dirfd, ++ const char *path, ++ GlnxChaseFlags flags, ++ GError **error); ++ ++G_END_DECLS +diff --git a/subprojects/libglnx/libglnx.h b/subprojects/libglnx/libglnx.h +index ca82ba5..3e28b50 100644 +--- a/subprojects/libglnx/libglnx.h ++++ b/subprojects/libglnx/libglnx.h +@@ -30,6 +30,7 @@ G_BEGIN_DECLS + #include + #include + #include ++#include + #include + #include + #include diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Don-t-leak-struct-glnx_statx-when-we-go-up-a-level.patch flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Don-t-leak-struct-glnx_statx-when-we-go-up-a-level.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Don-t-leak-struct-glnx_statx-when-we-go-up-a-level.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Don-t-leak-struct-glnx_statx-when-we-go-up-a-level.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,29 @@ +From: Simon McVittie +Date: Tue, 24 Mar 2026 19:38:17 +0000 +Subject: chase: Don't leak struct glnx_statx when we go up a level + +Signed-off-by: Simon McVittie +Origin: libglnx, commit:46205a62d2c6f96ecdb9eba6f2217a5745def134 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + subprojects/libglnx/glnx-chase.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/subprojects/libglnx/glnx-chase.c b/subprojects/libglnx/glnx-chase.c +index 261cc24..d4c2b73 100644 +--- a/subprojects/libglnx/glnx-chase.c ++++ b/subprojects/libglnx/glnx-chase.c +@@ -541,9 +541,10 @@ chase_manual (int dirfd, + * we saw before (if we saw it before). */ + if (g_strcmp0 (segment, "..") == 0) + { ++ g_autofree struct glnx_statx *old_tail = NULL; + struct glnx_statx *lower_st; + +- g_queue_pop_tail (&path_st); ++ old_tail = g_queue_pop_tail (&path_st); + + lower_st = g_queue_peek_tail (&path_st); + if (lower_st && diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Don-t-left-shift-signed-integer-1-by-31-places.patch flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Don-t-left-shift-signed-integer-1-by-31-places.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Don-t-left-shift-signed-integer-1-by-31-places.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Don-t-left-shift-signed-integer-1-by-31-places.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,31 @@ +From: Simon McVittie +Date: Tue, 24 Mar 2026 19:21:21 +0000 +Subject: chase: Don't left-shift signed integer 1 by 31 places + +This overflows, which is undefined behaviour (in practice it will usually +wrap around into unsigned space, but this can't be guaranteed). + +Signed-off-by: Simon McVittie +Origin: libglnx, commit:916b70619c44cd7736b2d4f9d2e8ae0ee5ddab28 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + subprojects/libglnx/glnx-chase.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/subprojects/libglnx/glnx-chase.c b/subprojects/libglnx/glnx-chase.c +index 70eb760..261cc24 100644 +--- a/subprojects/libglnx/glnx-chase.c ++++ b/subprojects/libglnx/glnx-chase.c +@@ -26,8 +26,8 @@ + + #define AUTOFS_SUPER_MAGIC 0x0187 /* man fstatfs */ + +-#define GLNX_CHASE_DEBUG_NO_OPENAT2 (1 << 31) +-#define GLNX_CHASE_DEBUG_NO_OPEN_TREE (1 << 30) ++#define GLNX_CHASE_DEBUG_NO_OPENAT2 (1U << 31) ++#define GLNX_CHASE_DEBUG_NO_OPEN_TREE (1U << 30) + + #define GLNX_CHASE_ALL_DEBUG_FLAGS \ + (GLNX_CHASE_DEBUG_NO_OPENAT2 | \ diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Factor-out-a-function-to-append-to-the-queue.patch flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Factor-out-a-function-to-append-to-the-queue.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Factor-out-a-function-to-append-to-the-queue.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/chase-Factor-out-a-function-to-append-to-the-queue.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,61 @@ +From: Simon McVittie +Date: Tue, 24 Mar 2026 19:38:59 +0000 +Subject: chase: Factor out a function to append to the queue + +Signed-off-by: Simon McVittie +Origin: libglnx, commit:6dbe18bcda03228f377f088a3a4f374b64d06865 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + subprojects/libglnx/glnx-chase.c | 16 +++++++++++++--- + 1 file changed, 13 insertions(+), 3 deletions(-) + +diff --git a/subprojects/libglnx/glnx-chase.c b/subprojects/libglnx/glnx-chase.c +index d4c2b73..9ad2fe3 100644 +--- a/subprojects/libglnx/glnx-chase.c ++++ b/subprojects/libglnx/glnx-chase.c +@@ -48,6 +48,16 @@ + + typedef GQueue GlnxStatxQueue; + ++static void ++glnx_statx_queue_push (GlnxStatxQueue *queue, ++ const struct glnx_statx *st) ++{ ++ struct glnx_statx *copy; ++ ++ copy = g_memdup2 (st, sizeof (*st)); ++ g_queue_push_tail (queue, copy); ++} ++ + static void + glnx_statx_queue_free_element (gpointer element, + G_GNUC_UNUSED gpointer userdata) +@@ -403,7 +413,7 @@ chase_manual (int dirfd, + if (!glnx_chase_statx (root_fd, no_automount, &st, error)) + return -1; + +- g_queue_push_tail (&path_st, g_memdup2 (&st, sizeof (st))); ++ glnx_statx_queue_push (&path_st, &st); + + /* Let's start walking the path! */ + buffer = g_strdup (path); +@@ -530,7 +540,7 @@ chase_manual (int dirfd, + + glnx_statx_queue_free (&path_st); + g_queue_init (&path_st); +- g_queue_push_tail (&path_st, g_memdup2 (&st, sizeof (st))); ++ glnx_statx_queue_push (&path_st, &st); + } + + continue; +@@ -557,7 +567,7 @@ chase_manual (int dirfd, + } + else + { +- g_queue_push_tail (&path_st, g_memdup2 (&st, sizeof (st))); ++ glnx_statx_queue_push (&path_st, &st); + } + + /* There is still another path component, but the next fd is not a diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/fdio-Add-glnx_fd_reopen.patch flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/fdio-Add-glnx_fd_reopen.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/fdio-Add-glnx_fd_reopen.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/fdio-Add-glnx_fd_reopen.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,109 @@ +From: Sebastian Wick +Date: Tue, 17 Feb 2026 23:01:25 +0100 +Subject: fdio: Add glnx_fd_reopen + +Reopens the specified fd with new flags. This is useful for convert an +O_PATH fd into a regular one, or to turn O_RDWR fds into O_RDONLY fds. + +Origin: libglnx, commit:1ec49dfc9b7a1b54cbcc5f6b6c816c1f49c0d84e +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + subprojects/libglnx/glnx-fdio.c | 72 +++++++++++++++++++++++++++++++++++++++++ + subprojects/libglnx/glnx-fdio.h | 4 +++ + 2 files changed, 76 insertions(+) + +diff --git a/subprojects/libglnx/glnx-fdio.c b/subprojects/libglnx/glnx-fdio.c +index ee69aa1..f52ca8f 100644 +--- a/subprojects/libglnx/glnx-fdio.c ++++ b/subprojects/libglnx/glnx-fdio.c +@@ -1194,3 +1194,75 @@ glnx_file_replace_contents_with_perms_at (int dfd, + + return TRUE; + } ++ ++/** ++ * glnx_fd_reopen: ++ * @fd: a file descriptor ++ * @flags: combination of openat flags ++ * @error: a #GError ++ * ++ * Reopens the specified fd with new flags. This is useful for converting an ++ * O_PATH fd into a regular one, or to turn O_RDWR fds into O_RDONLY fds. ++ * ++ * This implicitly sets `O_CLOEXEC | O_NOCTTY` in @flags. ++ * ++ * `O_CREAT` isn't allowed in @flags. ++ * ++ * This doesn't work on sockets (since they cannot be open()ed, ever). ++ * ++ * This implicitly resets the file read index to 0. ++ * ++ * If AT_FDCWD is specified as file descriptor, the function returns an fd to ++ * the current working directory. ++ * ++ * If the specified file descriptor refers to a symlink via O_PATH, then this ++ * function cannot be used to follow that symlink. Because we cannot have ++ * non-O_PATH fds to symlinks reopening it without O_PATH will always result in ++ * ELOOP. Or in other words: if you have an O_PATH fd to a symlink you can ++ * reopen it only if you pass O_PATH again. ++ */ ++int ++glnx_fd_reopen (int fd, ++ int flags, ++ GError **error) ++{ ++ glnx_autofd int new_fd = -1; ++ ++ g_return_val_if_fail (fd >= 0 || fd == AT_FDCWD, -1); ++ g_return_val_if_fail ((flags & O_CREAT) == 0, -1); ++ ++ /* */ ++ flags |= O_CLOEXEC | O_NOCTTY; ++ ++ /* O_NOFOLLOW is not allowed in fd_reopen(), because after all this is ++ * primarily implemented via a symlink-based interface in /proc/self/fd. Let's ++ * refuse this here early. Note that the kernel would generate ELOOP here too, ++ * hence this manual check is mostly redundant – the only reason we add it ++ * here is so that the O_DIRECTORY special case (see below) behaves the same ++ * way as the non-O_DIRECTORY case. */ ++ if ((flags & O_NOFOLLOW) != 0) ++ { ++ errno = ELOOP; ++ return glnx_fd_throw_errno (error); ++ } ++ ++ if ((flags & O_DIRECTORY) != 0 || fd == AT_FDCWD) ++ { ++ /* If we shall reopen the fd as directory we can just go via "." and thus ++ * bypass the whole magic /proc/ directory, and make ourselves independent ++ * of that being mounted. */ ++ new_fd = openat (fd, ".", flags | O_DIRECTORY); ++ } ++ else ++ { ++ g_autofree char *proc_fd_path = NULL; ++ ++ proc_fd_path = g_strdup_printf ("/proc/self/fd/%d", fd); ++ new_fd = open (proc_fd_path, flags); ++ } ++ ++ if (new_fd < 0) ++ return glnx_fd_throw_errno (error); ++ ++ return g_steal_fd (&new_fd); ++} +diff --git a/subprojects/libglnx/glnx-fdio.h b/subprojects/libglnx/glnx-fdio.h +index af53479..c2facef 100644 +--- a/subprojects/libglnx/glnx-fdio.h ++++ b/subprojects/libglnx/glnx-fdio.h +@@ -383,4 +383,8 @@ glnx_unlinkat (int dfd, + return TRUE; + } + ++int glnx_fd_reopen (int fd, ++ int flags, ++ GError **error); ++ + G_END_DECLS diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/fdio-Add-glnx_statx.patch flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/fdio-Add-glnx_statx.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/fdio-Add-glnx_statx.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/fdio-Add-glnx_statx.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,62 @@ +From: Sebastian Wick +Date: Wed, 25 Feb 2026 19:37:41 +0100 +Subject: fdio: Add glnx_statx + +Origin: libglnx, commit:01508367b486f0ac37b196994760200464c0116d +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + subprojects/libglnx/glnx-fdio.h | 32 ++++++++++++++++++++++++++++++++ + 1 file changed, 32 insertions(+) + +diff --git a/subprojects/libglnx/glnx-fdio.h b/subprojects/libglnx/glnx-fdio.h +index c2facef..acd546d 100644 +--- a/subprojects/libglnx/glnx-fdio.h ++++ b/subprojects/libglnx/glnx-fdio.h +@@ -22,6 +22,7 @@ + #pragma once + + #include ++#include + #include + #include + #include +@@ -313,6 +314,37 @@ glnx_fstatat (int dfd, + return TRUE; + } + ++/** ++ * glnx_statx: ++ * @dfd: Directory FD to stat beneath ++ * @path: Path to stat beneath @dfd ++ * @flags: Flags to pass to statx() ++ * @mask: Mask to pass to statx() ++ * @buf: (out caller-allocates): Return location for statx details ++ * @error: Return location for a #GError, or %NULL ++ * ++ * Wrapper around statx() which adds #GError support and ensures that it ++ * retries on %EINTR. ++ * ++ * The mask to pass must be a combination of GLNX_STATX_* flags which are ++ * defined by glnx, which map up with the struct glnx_statx. ++ * ++ * Returns: %TRUE on success, or %FALSE setting both @error and `errno` ++ * Since: UNRELEASED ++ */ ++static inline gboolean ++glnx_statx (int dfd, ++ const char *path, ++ unsigned flags, ++ unsigned int mask, ++ struct glnx_statx *buf, ++ GError **error) ++{ ++ if (TEMP_FAILURE_RETRY (glnx_statx_syscall (dfd, path, flags, mask, buf)) != 0) ++ return glnx_throw_errno_prefix (error, "statx(%s)", path); ++ return TRUE; ++} ++ + /** + * glnx_fstatat_allow_noent: + * @dfd: Directory FD to stat beneath diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/glnx-errors.h-add-glnx_fd_throw-_-variants.patch flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/glnx-errors.h-add-glnx_fd_throw-_-variants.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/glnx-errors.h-add-glnx_fd_throw-_-variants.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/glnx-errors.h-add-glnx_fd_throw-_-variants.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,53 @@ +From: Sebastian Wick +Date: Sat, 24 Jan 2026 15:55:36 +0100 +Subject: glnx-errors.h: add glnx_fd_throw[_*] variants + +Similar to the glnx_null_throw variants, this one returns -1 instead of +FALSE or NULL. This is helpful when dealing with functions which return +a fd. + +Origin: libglnx, commit:c33f0ed05e15f15bbf68cd2989b08903d9d00044 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + subprojects/libglnx/glnx-errors.h | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/subprojects/libglnx/glnx-errors.h b/subprojects/libglnx/glnx-errors.h +index e1dc3a5..6bb7be1 100644 +--- a/subprojects/libglnx/glnx-errors.h ++++ b/subprojects/libglnx/glnx-errors.h +@@ -32,6 +32,10 @@ gboolean glnx_throw (GError **error, const char *fmt, ...) G_GNUC_PRINTF (2,3); + #define glnx_null_throw(error, args...) \ + ({glnx_throw (error, args); NULL;}) + ++/* Like glnx_throw(), but yields -1 (invalid fd). */ ++#define glnx_fd_throw(error, args...) \ ++ ({glnx_throw (error, args); -1;}) ++ + /* Implementation detail of glnx_throw_prefix() */ + void glnx_real_set_prefix_error_va (GError *error, + const char *format, +@@ -108,6 +112,10 @@ glnx_throw_errno (GError **error) + #define glnx_null_throw_errno(error) \ + ({glnx_throw_errno (error); NULL;}) + ++/* Like glnx_throw_errno(), but yields -1 (invalid fd). */ ++#define glnx_fd_throw_errno(error) \ ++ ({glnx_throw_errno (error); -1;}) ++ + /* Implementation detail of glnx_throw_errno_prefix() */ + void glnx_real_set_prefix_error_from_errno_va (GError **error, + gint errsv, +@@ -120,6 +128,10 @@ gboolean glnx_throw_errno_prefix (GError **error, const char *fmt, ...) G_GNUC_P + #define glnx_null_throw_errno_prefix(error, args...) \ + ({glnx_throw_errno_prefix (error, args); NULL;}) + ++/* Like glnx_throw_errno_prefix(), but yields -1 (invalid fd). */ ++#define glnx_fd_throw_errno_prefix(error, args...) \ ++ ({glnx_throw_errno_prefix (error, args); -1;}) ++ + /* BEGIN LEGACY APIS */ + + #define glnx_set_error_from_errno(error) \ diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/missing-Add-syscall-and-structs-for-statx.patch flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/missing-Add-syscall-and-structs-for-statx.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/missing-Add-syscall-and-structs-for-statx.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/missing-Add-syscall-and-structs-for-statx.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,178 @@ +From: Sebastian Wick +Date: Wed, 25 Feb 2026 20:54:19 +0100 +Subject: missing: Add syscall and structs for statx() + +They are taken from an older revision of systemd with minimal changes. + +Origin: libglnx, commit:14578d856e5ede9ca82ea46cf481b1e950e7855d +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + subprojects/libglnx/glnx-missing-syscall.h | 149 +++++++++++++++++++++++++++++ + 1 file changed, 149 insertions(+) + +diff --git a/subprojects/libglnx/glnx-missing-syscall.h b/subprojects/libglnx/glnx-missing-syscall.h +index 3ee9dd5..b696a1c 100644 +--- a/subprojects/libglnx/glnx-missing-syscall.h ++++ b/subprojects/libglnx/glnx-missing-syscall.h +@@ -32,6 +32,8 @@ + */ + + #include "libglnx-config.h" ++#include ++#include + + #if !HAVE_DECL_RENAMEAT2 + # ifndef __NR_renameat2 +@@ -155,3 +157,150 @@ static inline ssize_t missing_copy_file_range(int fd_in, loff_t *off_in, + + # define copy_file_range missing_copy_file_range + #endif ++ ++#ifndef __IGNORE_statx ++# if defined(__aarch64__) ++# define systemd_NR_statx 291 ++# elif defined(__alpha__) ++# define systemd_NR_statx 522 ++# elif defined(__arc__) || defined(__tilegx__) ++# define systemd_NR_statx 291 ++# elif defined(__arm__) ++# define systemd_NR_statx 397 ++# elif defined(__i386__) ++# define systemd_NR_statx 383 ++# elif defined(__ia64__) ++# define systemd_NR_statx 1350 ++# elif defined(__loongarch_lp64) ++# define systemd_NR_statx 291 ++# elif defined(__m68k__) ++# define systemd_NR_statx 379 ++# elif defined(_MIPS_SIM) ++# if _MIPS_SIM == _MIPS_SIM_ABI32 ++# define systemd_NR_statx 4366 ++# elif _MIPS_SIM == _MIPS_SIM_NABI32 ++# define systemd_NR_statx 6330 ++# elif _MIPS_SIM == _MIPS_SIM_ABI64 ++# define systemd_NR_statx 5326 ++# else ++# error "Unknown MIPS ABI" ++# endif ++# elif defined(__hppa__) ++# define systemd_NR_statx 349 ++# elif defined(__powerpc__) ++# define systemd_NR_statx 383 ++# elif defined(__riscv) ++# if __riscv_xlen == 32 ++# define systemd_NR_statx 291 ++# elif __riscv_xlen == 64 ++# define systemd_NR_statx 291 ++# else ++# error "Unknown RISC-V ABI" ++# endif ++# elif defined(__s390__) ++# define systemd_NR_statx 379 ++# elif defined(__sparc__) ++# define systemd_NR_statx 360 ++# elif defined(__x86_64__) ++# if defined(__ILP32__) ++# define systemd_NR_statx (332 | /* __X32_SYSCALL_BIT */ 0x40000000) ++# else ++# define systemd_NR_statx 332 ++# endif ++# elif !defined(missing_arch_template) ++# warning "statx() syscall number is unknown for your architecture" ++# endif ++ ++/* may be an (invalid) negative number due to libseccomp, see PR 13319 */ ++# if defined __NR_statx && __NR_statx >= 0 ++# if defined systemd_NR_statx ++G_STATIC_ASSERT (__NR_statx == systemd_NR_statx); ++# endif ++# else ++# if defined __NR_statx ++# undef __NR_statx ++# endif ++# if defined systemd_NR_statx && systemd_NR_statx >= 0 ++# define __NR_statx systemd_NR_statx ++# endif ++# endif ++#endif ++ ++#if !defined(HAVE_GLNX_STATX) && defined(__NR_statx) ++#define GLNX_STATX_TYPE 0x00000001U /* Want/got stx_mode & S_IFMT */ ++#define GLNX_STATX_MODE 0x00000002U /* Want/got stx_mode & ~S_IFMT */ ++#define GLNX_STATX_NLINK 0x00000004U /* Want/got stx_nlink */ ++#define GLNX_STATX_UID 0x00000008U /* Want/got stx_uid */ ++#define GLNX_STATX_GID 0x00000010U /* Want/got stx_gid */ ++#define GLNX_STATX_ATIME 0x00000020U /* Want/got stx_atime */ ++#define GLNX_STATX_MTIME 0x00000040U /* Want/got stx_mtime */ ++#define GLNX_STATX_CTIME 0x00000080U /* Want/got stx_ctime */ ++#define GLNX_STATX_INO 0x00000100U /* Want/got stx_ino */ ++#define GLNX_STATX_SIZE 0x00000200U /* Want/got stx_size */ ++#define GLNX_STATX_BLOCKS 0x00000400U /* Want/got stx_blocks */ ++#define GLNX_STATX_BASIC_STATS 0x000007ffU /* The stuff in the normal stat struct */ ++#define GLNX_STATX_BTIME 0x00000800U /* Want/got stx_btime */ ++#define GLNX_STATX_MNT_ID 0x00001000U /* Got stx_mnt_id */ ++#define GLNX_STATX_DIOALIGN 0x00002000U /* Want/got direct I/O alignment info */ ++#define GLNX_STATX_MNT_ID_UNIQUE 0x00004000U /* Want/got extended stx_mount_id */ ++#define GLNX_STATX_SUBVOL 0x00008000U /* Want/got stx_subvol */ ++#define GLNX_STATX_WRITE_ATOMIC 0x00010000U /* Want/got atomic_write_* fields */ ++#define GLNX_STATX_DIO_READ_ALIGN 0x00020000U /* Want/got dio read alignment info */ ++#define GLNX_STATX__RESERVED 0x80000000U /* Reserved for future struct statx expansion */ ++ ++struct glnx_statx_timestamp ++{ ++ int64_t tv_sec; ++ uint32_t tv_nsec; ++ int32_t __reserved; ++}; ++ ++struct glnx_statx ++{ ++ uint32_t stx_mask; ++ uint32_t stx_blksize; ++ uint64_t stx_attributes; ++ uint32_t stx_nlink; ++ uint32_t stx_uid; ++ uint32_t stx_gid; ++ uint16_t stx_mode; ++ uint16_t __spare0[1]; ++ uint64_t stx_ino; ++ uint64_t stx_size; ++ uint64_t stx_blocks; ++ uint64_t stx_attributes_mask; ++ struct glnx_statx_timestamp stx_atime; ++ struct glnx_statx_timestamp stx_btime; ++ struct glnx_statx_timestamp stx_ctime; ++ struct glnx_statx_timestamp stx_mtime; ++ uint32_t stx_rdev_major; ++ uint32_t stx_rdev_minor; ++ uint32_t stx_dev_major; ++ uint32_t stx_dev_minor; ++ uint64_t stx_mnt_id; ++ uint32_t stx_dio_mem_align; ++ uint32_t stx_dio_offset_align; ++ uint64_t stx_subvol; ++ uint32_t stx_atomic_write_unit_min; ++ uint32_t stx_atomic_write_unit_max; ++ uint32_t stx_atomic_write_segments_max; ++ uint32_t stx_dio_read_offset_align; ++ uint32_t stx_atomic_write_unit_max_opt; ++ uint32_t __spare2[1]; ++ uint64_t __spare3[8]; ++}; ++ ++static inline int ++glnx_statx_syscall (int dfd, ++ const char *filename, ++ unsigned flags, ++ unsigned int mask, ++ struct glnx_statx *buf) ++{ ++ memset (buf, 0xbf, sizeof (*buf)); ++ return syscall (__NR_statx, dfd, filename, flags, mask, buf); ++ return 0; ++} ++ ++#define HAVE_GLNX_STATX ++#endif diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/missing-Add-syscall-number-for-openat2-and-open_tree.patch flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/missing-Add-syscall-number-for-openat2-and-open_tree.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/missing-Add-syscall-number-for-openat2-and-open_tree.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34078-prep/missing-Add-syscall-number-for-openat2-and-open_tree.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,227 @@ +From: Sebastian Wick +Date: Sun, 25 Jan 2026 15:08:03 +0100 +Subject: missing: Add syscall number for openat2() and open_tree() + +They are taken from an older revision of systemd with minimal changes. + +Origin: libglnx, commit:2a75ac86e95f01a429f10ab4084b7f7e7217b986 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-cc2q-qc34-jprg +Bug-Debian: https://bugs.debian.org/1132943 +Bug-CVE: CVE-2026-34078 +--- + subprojects/libglnx/glnx-missing-syscall.h | 205 +++++++++++++++++++++++++++++ + 1 file changed, 205 insertions(+) + +diff --git a/subprojects/libglnx/glnx-missing-syscall.h b/subprojects/libglnx/glnx-missing-syscall.h +index b696a1c..820d95e 100644 +--- a/subprojects/libglnx/glnx-missing-syscall.h ++++ b/subprojects/libglnx/glnx-missing-syscall.h +@@ -304,3 +304,208 @@ glnx_statx_syscall (int dfd, + + #define HAVE_GLNX_STATX + #endif ++ ++/* Copied from systemd git: ff83795469 ("boot: Improve log message") ++ * - open_tree ++ * - openat2 ++ */ ++ ++#ifndef __IGNORE_open_tree ++# if defined(__aarch64__) ++# define systemd_NR_open_tree 428 ++# elif defined(__alpha__) ++# define systemd_NR_open_tree 538 ++# elif defined(__arc__) || defined(__tilegx__) ++# define systemd_NR_open_tree 428 ++# elif defined(__arm__) ++# define systemd_NR_open_tree 428 ++# elif defined(__i386__) ++# define systemd_NR_open_tree 428 ++# elif defined(__ia64__) ++# define systemd_NR_open_tree 1452 ++# elif defined(__loongarch_lp64) ++# define systemd_NR_open_tree 428 ++# elif defined(__m68k__) ++# define systemd_NR_open_tree 428 ++# elif defined(_MIPS_SIM) ++# if _MIPS_SIM == _MIPS_SIM_ABI32 ++# define systemd_NR_open_tree 4428 ++# elif _MIPS_SIM == _MIPS_SIM_NABI32 ++# define systemd_NR_open_tree 6428 ++# elif _MIPS_SIM == _MIPS_SIM_ABI64 ++# define systemd_NR_open_tree 5428 ++# else ++# error "Unknown MIPS ABI" ++# endif ++# elif defined(__hppa__) ++# define systemd_NR_open_tree 428 ++# elif defined(__powerpc__) ++# define systemd_NR_open_tree 428 ++# elif defined(__riscv) ++# if __riscv_xlen == 32 ++# define systemd_NR_open_tree 428 ++# elif __riscv_xlen == 64 ++# define systemd_NR_open_tree 428 ++# else ++# error "Unknown RISC-V ABI" ++# endif ++# elif defined(__s390__) ++# define systemd_NR_open_tree 428 ++# elif defined(__sparc__) ++# define systemd_NR_open_tree 428 ++# elif defined(__x86_64__) ++# if defined(__ILP32__) ++# define systemd_NR_open_tree (428 | /* __X32_SYSCALL_BIT */ 0x40000000) ++# else ++# define systemd_NR_open_tree 428 ++# endif ++# elif !defined(missing_arch_template) ++# warning "open_tree() syscall number is unknown for your architecture" ++# endif ++ ++/* may be an (invalid) negative number due to libseccomp, see PR 13319 */ ++# if defined __NR_open_tree && __NR_open_tree >= 0 ++# if defined systemd_NR_open_tree ++G_STATIC_ASSERT (__NR_open_tree == systemd_NR_open_tree); ++# endif ++# else ++# if defined __NR_open_tree ++# undef __NR_open_tree ++# endif ++# if defined systemd_NR_open_tree && systemd_NR_open_tree >= 0 ++# define __NR_open_tree systemd_NR_open_tree ++# endif ++# endif ++#endif ++ ++#if !defined(HAVE_OPEN_TREE) && defined(__NR_open_tree) ++#ifndef OPEN_TREE_CLONE ++#define OPEN_TREE_CLONE 1 ++#endif ++ ++#ifndef OPEN_TREE_CLOEXEC ++#define OPEN_TREE_CLOEXEC O_CLOEXEC ++#endif ++ ++static inline int ++inline_open_tree (int dfd, ++ const char *filename, ++ unsigned flags) ++{ ++ return syscall(__NR_open_tree, dfd, filename, flags); ++} ++#define open_tree inline_open_tree ++#define HAVE_OPEN_TREE ++#endif ++ ++#ifndef __IGNORE_openat2 ++# if defined(__aarch64__) ++# define systemd_NR_openat2 437 ++# elif defined(__alpha__) ++# define systemd_NR_openat2 547 ++# elif defined(__arc__) || defined(__tilegx__) ++# define systemd_NR_openat2 437 ++# elif defined(__arm__) ++# define systemd_NR_openat2 437 ++# elif defined(__i386__) ++# define systemd_NR_openat2 437 ++# elif defined(__ia64__) ++# define systemd_NR_openat2 1461 ++# elif defined(__loongarch_lp64) ++# define systemd_NR_openat2 437 ++# elif defined(__m68k__) ++# define systemd_NR_openat2 437 ++# elif defined(_MIPS_SIM) ++# if _MIPS_SIM == _MIPS_SIM_ABI32 ++# define systemd_NR_openat2 4437 ++# elif _MIPS_SIM == _MIPS_SIM_NABI32 ++# define systemd_NR_openat2 6437 ++# elif _MIPS_SIM == _MIPS_SIM_ABI64 ++# define systemd_NR_openat2 5437 ++# else ++# error "Unknown MIPS ABI" ++# endif ++# elif defined(__hppa__) ++# define systemd_NR_openat2 437 ++# elif defined(__powerpc__) ++# define systemd_NR_openat2 437 ++# elif defined(__riscv) ++# if __riscv_xlen == 32 ++# define systemd_NR_openat2 437 ++# elif __riscv_xlen == 64 ++# define systemd_NR_openat2 437 ++# else ++# error "Unknown RISC-V ABI" ++# endif ++# elif defined(__s390__) ++# define systemd_NR_openat2 437 ++# elif defined(__sparc__) ++# define systemd_NR_openat2 437 ++# elif defined(__x86_64__) ++# if defined(__ILP32__) ++# define systemd_NR_openat2 (437 | /* __X32_SYSCALL_BIT */ 0x40000000) ++# else ++# define systemd_NR_openat2 437 ++# endif ++# elif !defined(missing_arch_template) ++# warning "openat2() syscall number is unknown for your architecture" ++# endif ++ ++/* may be an (invalid) negative number due to libseccomp, see PR 13319 */ ++# if defined __NR_openat2 && __NR_openat2 >= 0 ++# if defined systemd_NR_openat2 ++G_STATIC_ASSERT (__NR_openat2 == systemd_NR_openat2); ++# endif ++# else ++# if defined __NR_openat2 ++# undef __NR_openat2 ++# endif ++# if defined systemd_NR_openat2 && systemd_NR_openat2 >= 0 ++# define __NR_openat2 systemd_NR_openat2 ++# endif ++# endif ++#endif ++ ++#if !defined(HAVE_OPENAT2) && defined(__NR_openat2) ++#ifndef RESOLVE_NO_XDEV ++#define RESOLVE_NO_XDEV 0x01 ++#endif ++ ++#ifndef RESOLVE_NO_MAGICLINKS ++#define RESOLVE_NO_MAGICLINKS 0x02 ++#endif ++ ++#ifndef RESOLVE_NO_SYMLINKS ++#define RESOLVE_NO_SYMLINKS 0x04 ++#endif ++ ++#ifndef RESOLVE_BENEATH ++#define RESOLVE_BENEATH 0x08 ++#endif ++ ++#ifndef RESOLVE_IN_ROOT ++#define RESOLVE_IN_ROOT 0x10 ++#endif ++ ++#ifndef RESOLVE_CACHED ++#define RESOLVE_CACHED 0x20 ++#endif ++ ++struct inline_open_how { ++ uint64_t flags; ++ uint64_t mode; ++ uint64_t resolve; ++}; ++#define open_how inline_open_how ++ ++static inline int ++inline_openat2 (int dfd, ++ const char *filename, ++ void *buffer, ++ size_t size) ++{ ++ return syscall(__NR_openat2, dfd, filename, buffer, size); ++} ++#define openat2 inline_openat2 ++#define HAVE_OPENAT2 ++#endif diff -Nru flatpak-1.14.10/debian/patches/CVE-2026-34079/utils-Only-remove-cached-files-in-the-cache-directory.patch flatpak-1.14.10/debian/patches/CVE-2026-34079/utils-Only-remove-cached-files-in-the-cache-directory.patch --- flatpak-1.14.10/debian/patches/CVE-2026-34079/utils-Only-remove-cached-files-in-the-cache-directory.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/CVE-2026-34079/utils-Only-remove-cached-files-in-the-cache-directory.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,89 @@ +From: Sebastian Wick +Date: Fri, 9 Jan 2026 19:24:44 +0100 +Subject: utils: Only remove cached files in the cache directory + +The function flatpak_switch_symlink_and_remove is used to implement a +cache for ld.so (regenerate_ld_cache). If the active symlink changes to +a new cache file, the old cache file is supposed to get removed. + +The symlink still points to the old cache file, so we would remove the +file that it points to and then point at the new file. + +Because the symlink is under the app's control, the symlink can point +anywhere, and the removal happens in the host context, which allows an +app to remove arbitrary files on the host. + +The filename of the cache files are checksums, which means that we can +ensure that the link is a file in the same directory of the link by +checking that it only contains the chars a-zA-Z0-9. + +Origin: upstream, 1.16.4, commit:c97905c8188ddaad01ee146b57bba6c3fa294113 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-p29x-r292-46pp +Bug-Debian: https://bugs.debian.org/1132944 +Bug-CVE: CVE-2026-34079 +--- + common/flatpak-utils.c | 36 +++++++++++++++++++++++++++++++++--- + 1 file changed, 33 insertions(+), 3 deletions(-) + +diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c +index 6a49bb1..2778304 100644 +--- a/common/flatpak-utils.c ++++ b/common/flatpak-utils.c +@@ -1407,6 +1407,22 @@ out: + return ret; + } + ++static gboolean ++flatpak_str_is_alphanumeric (const char *arg) ++{ ++ while (*arg != '\0') ++ { ++ char c = *arg; ++ ++ if (!g_ascii_isalnum (c)) ++ return FALSE; ++ ++ arg++; ++ } ++ ++ return TRUE; ++} ++ + /* This atomically replaces a symlink with a new value, removing the + * existing symlink target, if it exstis and is different from + * @target. This is atomic in the sense that we're guaranteed to +@@ -1416,6 +1432,9 @@ out: + * 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. + */ + gboolean + flatpak_switch_symlink_and_remove (const char *symlink_path, +@@ -1459,10 +1478,21 @@ flatpak_switch_symlink_and_remove (const char *symlink_path, + g_autofree char *old_target = flatpak_readlink (tmp_path, error); + if (old_target == NULL) + return FALSE; +- if (strcmp (old_target, target) != 0) /* Don't remove old file if its the same as the new one */ ++ ++ /* Don't remove old file if its the same as the new one */ ++ if (strcmp (old_target, target) != 0) + { +- g_autofree char *old_target_path = g_build_filename (symlink_dir, old_target, NULL); +- unlink (old_target_path); ++ 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); ++ } ++ else ++ { ++ g_warning ("Refusing to delete old link target %s", old_target); ++ } + } + } + else if (errno != ENOENT) diff -Nru flatpak-1.14.10/debian/patches/GHSA-2fxp-43j9-pwvc/utils-Do-not-follow-symlinks-in-local_open_file.patch flatpak-1.14.10/debian/patches/GHSA-2fxp-43j9-pwvc/utils-Do-not-follow-symlinks-in-local_open_file.patch --- flatpak-1.14.10/debian/patches/GHSA-2fxp-43j9-pwvc/utils-Do-not-follow-symlinks-in-local_open_file.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/GHSA-2fxp-43j9-pwvc/utils-Do-not-follow-symlinks-in-local_open_file.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,48 @@ +From: Sebastian Wick +Date: Mon, 12 Jan 2026 17:38:02 +0100 +Subject: utils: Do not follow symlinks in local_open_file + +We use local_open_file in the context of the system helper to open +files written by a user. This means that we want to prevent DOS and +exposing files which only the system helper has access to. + +To prevent DOS and avoid side-effects, the file is opened with +O_NONBLOCK and O_NOCTTY. + +To prevent leaking files, the file is supposed to not open symlinks. +This part, we failed at. We check if the opened file is a regular file, +but what we actually checked is, if the file a symlink might point at is +a regular file. + +Fix this by also specifying O_NOFOLLOW in openat. + +Origin: upstream, 1.16.4, commit:a27ec46e8c0ab0ae162f2aa3142dccb6b79d9211 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-2fxp-43j9-pwvc +Bug-Debian: https://bugs.debian.org/1132946 +--- + common/flatpak-oci-registry.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/common/flatpak-oci-registry.c b/common/flatpak-oci-registry.c +index 01dcd26..6ad9882 100644 +--- a/common/flatpak-oci-registry.c ++++ b/common/flatpak-oci-registry.c +@@ -265,6 +265,9 @@ flatpak_oci_registry_new (const char *uri, + return oci_registry; + } + ++/* Carefully opens a file from a base directory and subpath, ++ * making sure that its not a symlink, pipe, etc. ++ */ + static int + local_open_file (int dfd, + const char *subpath, +@@ -276,7 +279,7 @@ local_open_file (int dfd, + struct stat tmp_st_buf; + + do +- fd = openat (dfd, subpath, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY); ++ fd = openat (dfd, subpath, O_NOFOLLOW | O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOCTTY); + while (G_UNLIKELY (fd == -1 && errno == EINTR)); + if (fd == -1) + { diff -Nru flatpak-1.14.10/debian/patches/GHSA-89xm-3m96-w3jg/system-helper-Only-remove-an-ongoing-pull-if-users-match.patch flatpak-1.14.10/debian/patches/GHSA-89xm-3m96-w3jg/system-helper-Only-remove-an-ongoing-pull-if-users-match.patch --- flatpak-1.14.10/debian/patches/GHSA-89xm-3m96-w3jg/system-helper-Only-remove-an-ongoing-pull-if-users-match.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/GHSA-89xm-3m96-w3jg/system-helper-Only-remove-an-ongoing-pull-if-users-match.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,150 @@ +From: Sebastian Wick +Date: Sat, 7 Feb 2026 21:57:30 +0100 +Subject: system-helper: Only remove an ongoing pull if users match + +The code would always remove a pull from the hashtable, and then check if the +users match and abort if they don't. Either way, the pull gets dropped. + +Fix this by only removing the pull if the dir and the user match. + +Origin: upstream, 1.16.4, commit:a27ec46e8c0ab0ae162f2aa3142dccb6b79d9211 +Bug: https://github.com/flatpak/flatpak/security/advisories/GHSA-89xm-3m96-w3jg +Bug-Debian: https://bugs.debian.org/1132945 +--- + system-helper/flatpak-system-helper.c | 85 +++++++++++++++-------------------- + 1 file changed, 36 insertions(+), 49 deletions(-) + +diff --git a/system-helper/flatpak-system-helper.c b/system-helper/flatpak-system-helper.c +index d637b1a..1fc1470 100644 +--- a/system-helper/flatpak-system-helper.c ++++ b/system-helper/flatpak-system-helper.c +@@ -355,23 +355,31 @@ get_connection_uid (GDBusMethodInvocation *invocation, uid_t *out_uid, GError ** + } + + static OngoingPull * +-take_ongoing_pull_by_dir (const gchar *src_dir) ++take_ongoing_pull_by_dir (const char *src_dir, ++ uid_t uid) + { + OngoingPull *pull = NULL; +- gpointer key, value; ++ char *cache_dir_name = NULL; + + G_LOCK (cache_dirs_in_use); +- /* Keep src_dir key inside hashtable but remove its OngoingPull +- * value and set it to NULL. This way src_dir is still marked +- * as in-use (as Deploy or CancelPull might be executing on it, +- * whereas OngoingPull ownership is transferred to respective +- * callers. */ +- if (g_hash_table_steal_extended (cache_dirs_in_use, src_dir, &key, &value)) +- { +- if (value) ++ if (g_hash_table_steal_extended (cache_dirs_in_use, src_dir, ++ (gpointer) &cache_dir_name, ++ (gpointer) &pull)) ++ { ++ if (pull && pull->uid == uid) + { +- g_hash_table_insert (cache_dirs_in_use, key, NULL); +- pull = value; ++ /* Keep src_dir key inside hashtable but remove its OngoingPull ++ * value and set it to NULL. This way src_dir is still marked ++ * as in-use (as Deploy or CancelPull might be executing on it, ++ * whereas OngoingPull ownership is transferred to respective ++ * callers. */ ++ g_hash_table_insert (cache_dirs_in_use, cache_dir_name, NULL); ++ } ++ else ++ { ++ /* Otherwise, re-insert what is currently there and return NULL */ ++ g_hash_table_insert (cache_dirs_in_use, cache_dir_name, pull); ++ pull = NULL; + } + } + G_UNLOCK (cache_dirs_in_use); +@@ -423,6 +431,9 @@ handle_deploy (FlatpakSystemHelper *object, + + if (strlen (arg_repo_path) > 0) + { ++ g_autoptr(GError) local_error = NULL; ++ uid_t uid; ++ + if (!g_file_query_exists (repo_file, NULL)) + { + g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS, +@@ -430,30 +441,17 @@ handle_deploy (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + ++ /* Ensure that pull's uid is same as the caller's uid */ ++ if (!get_connection_uid (invocation, &uid, &local_error)) ++ { ++ g_dbus_method_invocation_return_gerror (invocation, local_error); ++ return G_DBUS_METHOD_INVOCATION_HANDLED; ++ } ++ + src_dir = g_path_get_dirname (arg_repo_path); +- ongoing_pull = take_ongoing_pull_by_dir (src_dir); ++ ongoing_pull = take_ongoing_pull_by_dir (src_dir, uid); + if (ongoing_pull != NULL) + { +- g_autoptr(GError) local_error = NULL; +- uid_t uid; +- +- /* Ensure that pull's uid is same as the caller's uid */ +- if (!get_connection_uid (invocation, &uid, &local_error)) +- { +- g_dbus_method_invocation_return_gerror (invocation, local_error); +- return G_DBUS_METHOD_INVOCATION_HANDLED; +- } +- else +- { +- if (ongoing_pull->uid != uid) +- { +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, +- "Ongoing pull's uid(%d) does not match with peer uid(%d)", +- ongoing_pull->uid, uid); +- return G_DBUS_METHOD_INVOCATION_HANDLED; +- } +- } +- + terminate_revokefs_backend (ongoing_pull); + + if (!flatpak_canonicalize_permissions (AT_FDCWD, +@@ -735,31 +733,20 @@ handle_cancel_pull (FlatpakSystemHelper *object, + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- ongoing_pull = take_ongoing_pull_by_dir (arg_src_dir); +- if (ongoing_pull == NULL) ++ if (!get_connection_uid (invocation, &uid, &error)) + { +- g_set_error (&error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, +- "Cannot find ongoing pull to cancel at %s", arg_src_dir); + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } + +- /* Ensure that pull's uid is same as the caller's uid */ +- if (!get_connection_uid (invocation, &uid, &error)) ++ ongoing_pull = take_ongoing_pull_by_dir (arg_src_dir, uid); ++ if (ongoing_pull == NULL) + { ++ g_set_error (&error, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, ++ "Cannot find ongoing pull to cancel at %s", arg_src_dir); + g_dbus_method_invocation_return_gerror (invocation, error); + return G_DBUS_METHOD_INVOCATION_HANDLED; + } +- else +- { +- if (ongoing_pull->uid != uid) +- { +- g_dbus_method_invocation_return_error (invocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED, +- "Ongoing pull's uid(%d) does not match with peer uid(%d)", +- ongoing_pull->uid, uid); +- return G_DBUS_METHOD_INVOCATION_HANDLED; +- } +- } + + ongoing_pull->preserve_pull = (arg_flags & FLATPAK_HELPER_CANCEL_PULL_FLAGS_PRESERVE_PULL) != 0; + ongoing_pull_free (ongoing_pull); diff -Nru flatpak-1.14.10/debian/patches/dir-Silence-a-spurious-warning-when-installing-extra-data.patch flatpak-1.14.10/debian/patches/dir-Silence-a-spurious-warning-when-installing-extra-data.patch --- flatpak-1.14.10/debian/patches/dir-Silence-a-spurious-warning-when-installing-extra-data.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/dir-Silence-a-spurious-warning-when-installing-extra-data.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,37 @@ +From: Simon McVittie +Date: Tue, 14 Apr 2026 10:45:12 +0100 +Subject: dir: Silence a spurious warning when installing extra-data + +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. Instead of +logging a (non-async-signal-safe!) warning, ignore the failure to seek, +like flatpak_bwrap_child_setup() already did. + +Functionally equivalent to commit 333459c8 "dir: Use +flatpak_bwrap_child_setup_inherit_fds_cb() to apply extra-data" upstream, +but that function didn't exist yet in the 1.14.x branch. + +Signed-off-by: Simon McVittie +Bug: https://github.com/flatpak/flatpak/issues/6608 +Forwarded: not-needed, upstream no longer supports 1.14.x +--- + common/flatpak-dir.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/common/flatpak-dir.c b/common/flatpak-dir.c +index 44180d0..6262ad9 100644 +--- a/common/flatpak-dir.c ++++ b/common/flatpak-dir.c +@@ -8183,7 +8183,10 @@ child_setup (gpointer user_data) + /* 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"); ++ { ++ /* 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.14.10/debian/patches/portal-Don-t-run-method-invocations-in-a-thread.patch flatpak-1.14.10/debian/patches/portal-Don-t-run-method-invocations-in-a-thread.patch --- flatpak-1.14.10/debian/patches/portal-Don-t-run-method-invocations-in-a-thread.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/portal-Don-t-run-method-invocations-in-a-thread.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,33 @@ +From: Georges Basile Stavracas Neto +Date: Tue, 11 Mar 2025 10:25:16 -0300 +Subject: portal: Don't run method invocations in a thread + +Most access to the `client_pid_data_hash` hash table are unsafe due to +threading. + +One approach to solve this would be to protect the hash table with a +mutex, but as per a deeper analysis, nothing in these callbacks is +slow or heavy enough to justify the need for separate threads. + +Make method invocations run in the main thread. + +Origin: upstream, flatpak-1.14.x branch, commit:c11cbbfce1b8c469e7802ea81a1ccfa1337562fa +Bug: https://github.com/flatpak/flatpak/issues/5605 +--- + portal/flatpak-portal.c | 3 --- + 1 file changed, 3 deletions(-) + +diff --git a/portal/flatpak-portal.c b/portal/flatpak-portal.c +index c0f1fb2..b32e5e4 100644 +--- a/portal/flatpak-portal.c ++++ b/portal/flatpak-portal.c +@@ -2905,9 +2905,6 @@ on_bus_acquired (GDBusConnection *connection, + + g_object_set_data_full (G_OBJECT (portal), "track-alive", GINT_TO_POINTER (42), skeleton_died_cb); + +- g_dbus_interface_skeleton_set_flags (G_DBUS_INTERFACE_SKELETON (portal), +- G_DBUS_INTERFACE_SKELETON_FLAGS_HANDLE_METHOD_INVOCATIONS_IN_THREAD); +- + portal_flatpak_set_version (PORTAL_FLATPAK (portal), 6); + portal_flatpak_set_supports (PORTAL_FLATPAK (portal), supports); + diff -Nru flatpak-1.14.10/debian/patches/portal-Use-G_LOCK_DEFINE_STATIC.patch flatpak-1.14.10/debian/patches/portal-Use-G_LOCK_DEFINE_STATIC.patch --- flatpak-1.14.10/debian/patches/portal-Use-G_LOCK_DEFINE_STATIC.patch 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/portal-Use-G_LOCK_DEFINE_STATIC.patch 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,25 @@ +From: Georges Basile Stavracas Neto +Date: Tue, 11 Mar 2025 10:28:03 -0300 +Subject: portal: Use G_LOCK_DEFINE_STATIC + +The `update_monitors` lock lives in the global namespace and is not +used by other compile units, so make it static. + +Origin: upstream, flatpak-1.14.x branch, commit:1e43d5dc9ec1c73efe13420f1eff36c65b7ee870 +--- + portal/flatpak-portal.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/portal/flatpak-portal.c b/portal/flatpak-portal.c +index b3f82ff..c0f1fb2 100644 +--- a/portal/flatpak-portal.c ++++ b/portal/flatpak-portal.c +@@ -76,7 +76,7 @@ static int opt_poll_timeout; + static gboolean opt_poll_when_metered; + static FlatpakSpawnSupportFlags supports = 0; + +-G_LOCK_DEFINE (update_monitors); /* This protects the three variables below */ ++G_LOCK_DEFINE_STATIC (update_monitors); /* This protects the three variables below */ + static GHashTable *update_monitors; + static guint update_monitors_timeout = 0; + static gboolean update_monitors_timeout_running_thread = FALSE; diff -Nru flatpak-1.14.10/debian/patches/series flatpak-1.14.10/debian/patches/series --- flatpak-1.14.10/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ flatpak-1.14.10/debian/patches/series 2026-04-15 19:27:40.000000000 +0000 @@ -0,0 +1,53 @@ +portal-Use-G_LOCK_DEFINE_STATIC.patch +portal-Don-t-run-method-invocations-in-a-thread.patch +CVE-2026-34078-prep/backports-Add-g_clear_fd.patch +CVE-2026-34078-prep/glnx-errors.h-add-glnx_fd_throw-_-variants.patch +CVE-2026-34078-prep/fdio-Add-glnx_fd_reopen.patch +CVE-2026-34078-prep/missing-Add-syscall-and-structs-for-statx.patch +CVE-2026-34078-prep/fdio-Add-glnx_statx.patch +CVE-2026-34078-prep/missing-Add-syscall-number-for-openat2-and-open_tree.patch +CVE-2026-34078-prep/chase-Add-glnx_chaseat-which-functions-similar-to-openat2.patch +CVE-2026-34078-prep/chase-Add-glnx_chase_and_statxat.patch +CVE-2026-34078-prep/chase-Don-t-left-shift-signed-integer-1-by-31-places.patch +CVE-2026-34078-prep/chase-Don-t-leak-struct-glnx_statx-when-we-go-up-a-level.patch +CVE-2026-34078-prep/chase-Factor-out-a-function-to-append-to-the-queue.patch +CVE-2026-34078-prep/build-Add-glnx-chase.-ch-to-subprojects.patch +CVE-2026-34078-prep/build-Link-libglnx-into-libflatpak-common-not-just-into-l.patch +CVE-2026-34078/flatpak-bwrap-Add-dup-ing-variant-flatpak_bwrap_add_args_.patch +CVE-2026-34078/utils-Add-flatpak_parse_fd.patch +CVE-2026-34078/flatpak-bwrap-Use-glnx_close_fd-as-clear-func.patch +CVE-2026-34078/run-Use-O_PATH-fds-for-the-runtime-and-app-deploy-directo.patch +CVE-2026-34078/run-Add-usr-fd-and-app-fd-options.patch +CVE-2026-34078/run-Add-ro-bind-fds-to-flatpak_run_app.patch +CVE-2026-34078/run-Add-ro-bind-fd-options.patch +CVE-2026-34078/portal-Use-bind-fd-app-fd-and-usr-fd-options-to-avoid-rac.patch +CVE-2026-34079/utils-Only-remove-cached-files-in-the-cache-directory.patch +GHSA-2fxp-43j9-pwvc/utils-Do-not-follow-symlinks-in-local_open_file.patch +GHSA-89xm-3m96-w3jg/system-helper-Only-remove-an-ongoing-pull-if-users-match.patch +1.16.5/run-Fix-checking-wrong-variable-in-runtime-fd-selection.patch +1.16.5/run-Mount-original-app-on-run-parent-app-when-using-app-p.patch +1.16.5/portal-update-max_fd-after-creating-the-instance-ID-pipe.patch +1.16.5/run-Fix-fd-tracking-in-flatpak_run_add_app_info_args.patch +1.16.5/utils-Improve-error-message-when-passing-an-FD-numer-whic.patch +1.16.5/run-Do-not-close-bind-ro-bind.patch +1.16.5/run-Use-the-same-FD-validation-for-all-FD-options.patch +1.16.5/run-Add-bind-fd-and-ro-bind-fd-binds-after-all-other-bind.patch +1.16.5/portal-use-g_array_index-to-read-from-expose_fds-expose_f.patch +1.16.5/run-Fix-backport-mistake.patch +1.16.5/tests-test-run-custom-Test-usr-path-usr-fd-app-path-app-f.patch +1.16.5/tests-test-run-custom-Test-bind-fd-and-ro-bind-fd.patch +1.16.6/run-Cope-with-an-empty-runtime.patch +1.16.6/dir-In-apply_extra_data-don-t-assume-there-is-always-a-ru.patch +1.16.6/tests-Add-test-extra-data.sh-to-test-extra-data-installat.patch +1.16.6/utils-Add-flatpak_set_cloexec.patch +1.16.6/run-context-Mark-fd-arguments-as-close-on-exec.patch +1.16.6/utils-Move-flatpak_get_path_for_fd-to-here.patch +1.16.6/portal-Avoid-crash-if-sandbox-expose-ro-fd-is-out-of-rang.patch +1.16.6/portal-Log-and-ignore-unusable-sandbox-expose-fds-instead.patch +1.16.6/portal-Reinstate-flatpak_get_path_for_fd-checks.patch +1.16.6/libtest-Allow-adding-a-new-ref-to-an-existing-temporary-o.patch +1.16.6/tests-Check-that-flatpak-run-fd-arguments-do-not-leak-to-.patch +1.16.6/app-context-Never-close-fds-0-1-or-2.patch +1.16.6/app-context-Factor-out-flatpak_accept_fd_argument.patch +1.16.7/bwrap-Clarify-a-comment.patch +dir-Silence-a-spurious-warning-when-installing-extra-data.patch