import UBI flatpak-1.16.0-9.el10_2.1

This commit is contained in:
AlmaLinux RelEng Bot 2026-05-28 13:05:39 -04:00
parent e37ada1959
commit 6dde1ae8d0
40 changed files with 7013 additions and 35 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
From d19f44408000b53b6d61d3e60a3c65104dc5a8c6 Mon Sep 17 00:00:00 2001
From: Xiangzhe <xiangzhedev@gmail.com>
Date: Wed, 8 Apr 2026 12:27:28 +0800
Subject: [PATCH] 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"
Helps: https://github.com/flatpak/flatpak/issues/6568
(cherry picked from commit 066babba75d355d077ea11091e5f65d3b0e0d818)
---
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 055c721d..f44c66ff 100644
--- a/common/flatpak-run.c
+++ b/common/flatpak-run.c
@@ -3203,7 +3203,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;
--
2.54.0

View File

@ -0,0 +1,34 @@
From ccd9c8a7fa8bee1b728e683c2d478f4408cd651c Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Wed, 8 Apr 2026 09:44:55 +0100
Subject: [PATCH] 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"
Resolves: https://github.com/flatpak/flatpak/issues/6568
Signed-off-by: Simon McVittie <smcv@collabora.com>
(cherry picked from commit fde4716f67b6620da57fd74481694eb58795d589)
---
common/flatpak-run.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/common/flatpak-run.c b/common/flatpak-run.c
index f44c66ff..b90af672 100644
--- a/common/flatpak-run.c
+++ b/common/flatpak-run.c
@@ -3335,6 +3335,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;
}
--
2.54.0

View File

@ -0,0 +1,31 @@
From 2e2f4a430b784163614403b333ab3990c9354db3 Mon Sep 17 00:00:00 2001
From: Alberto Garcia <berto@igalia.com>
Date: Wed, 8 Apr 2026 19:28:32 +0200
Subject: [PATCH] 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.
Closes: https://github.com/flatpak/flatpak/issues/6570
---
portal/flatpak-portal.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/portal/flatpak-portal.c b/portal/flatpak-portal.c
index 5c0935a1..a67ee330 100644
--- a/portal/flatpak-portal.c
+++ b/portal/flatpak-portal.c
@@ -1150,6 +1150,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)
--
2.54.0

View File

@ -0,0 +1,123 @@
From bd41305b98373d762cc02395d9fe7b9486da20d4 Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Wed, 8 Apr 2026 17:47:48 +0200
Subject: [PATCH] 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.
---
common/flatpak-run.c | 33 ++++++++++++++++-----------------
1 file changed, 16 insertions(+), 17 deletions(-)
diff --git a/common/flatpak-run.c b/common/flatpak-run.c
index b90af672..5882b2a9 100644
--- a/common/flatpak-run.c
+++ b/common/flatpak-run.c
@@ -1366,7 +1366,7 @@ 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,
char **instance_id_host_private_dir_out,
char **instance_id_out,
@@ -1374,7 +1374,11 @@ flatpak_run_add_app_info_args (FlatpakBwrap *bwrap,
{
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;
@@ -1523,8 +1527,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),
@@ -1535,7 +1539,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));
@@ -1544,9 +1547,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,
@@ -1563,8 +1566,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));
@@ -1587,10 +1588,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;
@@ -1600,13 +1597,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);
@@ -3556,7 +3554,8 @@ 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,
+ &app_info_path,
+ g_steal_fd (&instance_id_fd),
&instance_id_host_dir, &instance_id_host_private_dir,
&instance_id, error))
return FALSE;
--
2.54.0

View File

@ -0,0 +1,28 @@
From e2ef538e8d70f8ddaf1adf201c3bb1578d0444f1 Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Wed, 8 Apr 2026 18:15:42 +0200
Subject: [PATCH] utils: Improve error message when passing an FD numer which
is not a FD
---
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 dbc46951..7e69e8be 100644
--- a/common/flatpak-utils.c
+++ b/common/flatpak-utils.c
@@ -2520,8 +2520,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;
}
--
2.54.0

View File

@ -0,0 +1,32 @@
From e654ebade0b9154830563c55c0a5f426b41fd46d Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Wed, 8 Apr 2026 18:14:19 +0200
Subject: [PATCH] run: Do not close --bind/--ro-bind
---
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 0bfc311d..256f7828 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;
}
--
2.54.0

View File

@ -0,0 +1,95 @@
From c89a0c509ad315ccf5157e9faebb4e243bed165c Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Wed, 8 Apr 2026 18:19:20 +0200
Subject: [PATCH] run: Use the same FD validation for all FD options
---
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 256f7828..bca4d865 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 }
--
2.54.0

View File

@ -0,0 +1,97 @@
From 6118b415e4ed1e35852736c9a28585f4093bf246 Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Wed, 8 Apr 2026 21:59:19 +0200
Subject: [PATCH] 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).
---
common/flatpak-run.c | 64 ++++++++++++++++++++++----------------------
1 file changed, 32 insertions(+), 32 deletions(-)
diff --git a/common/flatpak-run.c b/common/flatpak-run.c
index 5882b2a9..a8088a92 100644
--- a/common/flatpak-run.c
+++ b/common/flatpak-run.c
@@ -3585,6 +3585,38 @@ flatpak_run_app (FlatpakDecomposed *app_ref,
flatpak_bwrap_add_arg_printf (bwrap, "/run/user/%d", getuid ());
}
+ if (!flatpak_run_add_dconf_args (bwrap, app_id, metakey, error))
+ return FALSE;
+
+ if (!sandboxed && !(flags & FLATPAK_RUN_FLAG_NO_DOCUMENTS_PORTAL))
+ add_document_portal_args (bwrap, app_id, &doc_mount_path);
+
+ if (!flatpak_run_add_environment_args (bwrap, app_info_path, flags,
+ app_id, app_context, app_id_dir, previous_app_id_dirs,
+ per_app_dir_lock_fd, instance_id,
+ &exports, cancellable, error))
+ return FALSE;
+
+ if (per_app_dir_lock_path != NULL)
+ {
+ static const char lock[] = "/run/flatpak/per-app-dirs-ref";
+
+ flatpak_bwrap_add_args (bwrap,
+ "--ro-bind", per_app_dir_lock_path, lock,
+ "--lock-file", lock,
+ NULL);
+ }
+
+ flatpak_run_add_socket_args_late (bwrap, app_context->shares);
+ add_font_path_args (bwrap);
+ add_icon_path_args (bwrap);
+
+ flatpak_bwrap_add_args (bwrap,
+ /* Not in base, because we don't want this for flatpak build */
+ "--symlink", "/app/lib/debug/source", "/run/build",
+ "--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);
@@ -3619,38 +3651,6 @@ flatpak_run_app (FlatpakDecomposed *app_ref,
return FALSE;
}
- if (!flatpak_run_add_dconf_args (bwrap, app_id, metakey, error))
- return FALSE;
-
- if (!sandboxed && !(flags & FLATPAK_RUN_FLAG_NO_DOCUMENTS_PORTAL))
- add_document_portal_args (bwrap, app_id, &doc_mount_path);
-
- if (!flatpak_run_add_environment_args (bwrap, app_info_path, flags,
- app_id, app_context, app_id_dir, previous_app_id_dirs,
- per_app_dir_lock_fd, instance_id,
- &exports, cancellable, error))
- return FALSE;
-
- if (per_app_dir_lock_path != NULL)
- {
- static const char lock[] = "/run/flatpak/per-app-dirs-ref";
-
- flatpak_bwrap_add_args (bwrap,
- "--ro-bind", per_app_dir_lock_path, lock,
- "--lock-file", lock,
- NULL);
- }
-
- flatpak_run_add_socket_args_late (bwrap, app_context->shares);
- add_font_path_args (bwrap);
- add_icon_path_args (bwrap);
-
- flatpak_bwrap_add_args (bwrap,
- /* Not in base, because we don't want this for flatpak build */
- "--symlink", "/app/lib/debug/source", "/run/build",
- "--symlink", "/usr/lib/debug/source", "/run/build-runtime",
- NULL);
-
if (cwd)
flatpak_bwrap_add_args (bwrap, "--chdir", cwd, NULL);
--
2.54.0

View File

@ -0,0 +1,38 @@
From 8e71548ab56a97b88c7d34d68a6b04f6d430b2aa Mon Sep 17 00:00:00 2001
From: Alberto Garcia <berto@igalia.com>
Date: Wed, 8 Apr 2026 19:44:29 +0200
Subject: [PATCH] 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.
---
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 a67ee330..0550a379 100644
--- a/portal/flatpak-portal.c
+++ b/portal/flatpak-portal.c
@@ -1294,7 +1294,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));
@@ -1304,7 +1304,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));
--
2.54.0

View File

@ -0,0 +1,28 @@
From ac540672bb6598285fd333e3863335d27236762b Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Thu, 9 Apr 2026 00:56:40 +0200
Subject: [PATCH] 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")
---
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 bca4d865..23b9f368 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 }
--
2.54.0

View File

@ -0,0 +1,61 @@
From 36bd977772ea0df7159ff207f89564bfccd3b0ca Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Fri, 6 Feb 2026 17:56:21 +0100
Subject: [PATCH] flatpak-bwrap: Add dup-ing variant
flatpak_bwrap_add_args_data_fd_dup
---
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 9effed8a..d7a73304 100644
--- a/common/flatpak-bwrap-private.h
+++ b/common/flatpak-bwrap-private.h
@@ -63,6 +63,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 f7e8b10e..a3d29782 100644
--- a/common/flatpak-bwrap.c
+++ b/common/flatpak-bwrap.c
@@ -146,6 +146,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, ...)
{
--
2.54.0

View File

@ -0,0 +1,190 @@
From ab8ca4d6a02900974c08694f54903b2616875bf0 Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Wed, 8 Apr 2026 14:20:53 +0200
Subject: [PATCH] tests/test-run-custom: Test --usr-path, --usr-fd, --app-path,
--app-fd
---
tests/test-matrix/meson.build | 2 +
tests/test-run-custom.sh | 156 ++++++++++++++++++++++++++++++++++
2 files changed, 158 insertions(+)
create mode 100755 tests/test-run-custom.sh
diff --git a/tests/test-matrix/meson.build b/tests/test-matrix/meson.build
index fd0b5034..ef85b7ec 100644
--- a/tests/test-matrix/meson.build
+++ b/tests/test-matrix/meson.build
@@ -45,4 +45,6 @@ wrapped_tests += {'name' : 'test-unused.sh', 'script' : 'test-unused.sh'}
wrapped_tests += {'name' : 'test-prune.sh', 'script' : 'test-prune.sh'}
wrapped_tests += {'name' : 'test-seccomp.sh', 'script' : 'test-seccomp.sh'}
wrapped_tests += {'name' : 'test-repair.sh', 'script' : 'test-repair.sh'}
wrapped_tests += {'name' : 'test-preinstall.sh', 'script' : 'test-preinstall.sh'}
+wrapped_tests += {'name' : 'test-run-custom@user.wrap', 'script' : 'test-run-custom.sh'}
+wrapped_tests += {'name' : 'test-run-custom@system.wrap', 'script' : 'test-run-custom.sh'}
\ No newline at end of file
diff --git a/tests/test-run-custom.sh b/tests/test-run-custom.sh
new file mode 100755
index 00000000..f8a11f03
--- /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
--
2.54.0

View File

@ -0,0 +1,56 @@
From 8ebffc2ad37fb5f2c03641506e8091aee53defd8 Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Wed, 8 Apr 2026 21:55:22 +0200
Subject: [PATCH] tests/test-run-custom: Test --bind-fd and --ro-bind-fd
---
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 f8a11f03..94c32416 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
--
2.54.0

View File

@ -0,0 +1,110 @@
From b942152bde980cde51e933359e4ba3be527ae3ed Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Thu, 9 Apr 2026 18:45:04 +0100
Subject: [PATCH] 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.
Fixes: ac62ebe3 "run: Use O_PATH fds for the runtime and app deploy directories"
Helps: https://github.com/flatpak/flatpak/issues/6583
Signed-off-by: Simon McVittie <smcv@collabora.com>
(cherry picked from commit aa1a54c9dae25fd13ebc936e06996f8db39f4aa5)
---
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 a8088a92..f176e884 100644
--- a/common/flatpak-run.c
+++ b/common/flatpak-run.c
@@ -1618,6 +1618,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)
@@ -1630,6 +1634,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);
@@ -1638,10 +1644,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)
@@ -1652,7 +1659,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;
@@ -2163,6 +2170,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,
@@ -2226,6 +2237,10 @@ static const char *const sysfs_dirs[] =
"/sys/devices"
};
+/*
+ * @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,
@@ -2246,7 +2261,7 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap,
gboolean bwrap_unprivileged = flatpak_bwrap_is_unprivileged ();
gsize i;
- g_return_val_if_fail (runtime_fd >= 0, FALSE);
+ g_return_val_if_fail (runtime_fd >= -1, FALSE);
/* Disable recursive userns for all flatpak processes, as we need this
* to guarantee that the sandbox can't restructure the filesystem.
@@ -2355,7 +2370,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;
--
2.54.0

View File

@ -0,0 +1,44 @@
From a140dd16297df7dee73bfd6c99c278d7a3fa580c Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Thu, 9 Apr 2026 18:47:40 +0100
Subject: [PATCH] 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"
Resolves: https://github.com/flatpak/flatpak/issues/6583
Signed-off-by: Simon McVittie <smcv@collabora.com>
(cherry picked from commit c14ad3722940706730a76997c6925f9998106f90)
---
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 7e8fbf6b..e381b16d 100644
--- a/common/flatpak-dir.c
+++ b/common/flatpak-dir.c
@@ -8476,10 +8476,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))
--
2.54.0

View File

@ -0,0 +1,126 @@
From 2bf787c496c8ea0349cee668c90fd52824157a01 Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Thu, 21 Aug 2025 18:40:33 +0200
Subject: [PATCH] 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]
(cherry picked from commit e9e713fa0ddd0d1af4b8cdec6aa9124d471ed33e)
---
tests/meson.build | 1 +
tests/test-extra-data.sh | 63 +++++++++++++++++++++++++++++++++++
tests/test-matrix/meson.build | 2 ++
tests/update-test-matrix | 1 +
4 files changed, 67 insertions(+)
create mode 100755 tests/test-extra-data.sh
diff --git a/tests/meson.build b/tests/meson.build
index cb187d71..8f96e5f0 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -362,6 +362,7 @@ foreach testcase : wrapped_tests
'test-summaries.sh' : 60,
'test-unused.sh' : 90,
'test-update-portal.sh' : 90,
+ 'test-extra-data.sh' : 90,
}.get(script, 30)
is_parallel = {
diff --git a/tests/test-extra-data.sh b/tests/test-extra-data.sh
new file mode 100755
index 00000000..41c9b913
--- /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/test-matrix/meson.build b/tests/test-matrix/meson.build
index ef85b7ec..037500eb 100644
--- a/tests/test-matrix/meson.build
+++ b/tests/test-matrix/meson.build
@@ -29,6 +29,8 @@ wrapped_tests += {'name' : 'test-summaries@user.wrap', 'script' : 'test-summarie
wrapped_tests += {'name' : 'test-summaries@system.wrap', 'script' : 'test-summaries.sh'}
wrapped_tests += {'name' : 'test-subset@user.wrap', 'script' : 'test-subset.sh'}
wrapped_tests += {'name' : 'test-subset@system.wrap', 'script' : 'test-subset.sh'}
+wrapped_tests += {'name' : 'test-extra-data@user.wrap', 'script' : 'test-extra-data.sh'}
+wrapped_tests += {'name' : 'test-extra-data@system.wrap', 'script' : 'test-extra-data.sh'}
wrapped_tests += {'name' : 'test-basic.sh', 'script' : 'test-basic.sh'}
wrapped_tests += {'name' : 'test-completion.sh', 'script' : 'test-completion.sh'}
wrapped_tests += {'name' : 'test-config.sh', 'script' : 'test-config.sh'}
diff --git a/tests/update-test-matrix b/tests/update-test-matrix
index 3a51d0ba..2e26a6f2 100755
--- a/tests/update-test-matrix
+++ b/tests/update-test-matrix
@@ -34,6 +34,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" --meson "${TEST_MATRIX_SOURCE[*]}" > "${tests_srcdir}/test-matrix/meson.build"
--
2.54.0

View File

@ -0,0 +1,121 @@
From b9ec9cab240ffdfd8768dc1dd6272d40719ecdcd Mon Sep 17 00:00:00 2001
From: bbhtt <bbhtt.zn0i8@slmail.me>
Date: Fri, 10 Apr 2026 10:14:23 +0530
Subject: [PATCH] tests: Add test for NoRuntime extra-data app
(cherry picked from commit 79cb10e8803a3f13fe5bd8329dec37e58cfa2a84)
---
tests/apply-extra-static.c | 15 +++++++++++++
tests/meson.build | 15 +++++++++++++
tests/test-extra-data.sh | 43 +++++++++++++++++++++++++++++++++++++-
3 files changed, 72 insertions(+), 1 deletion(-)
create mode 100644 tests/apply-extra-static.c
diff --git a/tests/apply-extra-static.c b/tests/apply-extra-static.c
new file mode 100644
index 00000000..5be3993a
--- /dev/null
+++ b/tests/apply-extra-static.c
@@ -0,0 +1,15 @@
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+static const char msg[] = "noruntime-extra-data\n";
+
+int main(void) {
+ mkdir("/app/extra", 0755);
+ int fd = open("/app/extra/ran", O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ if (fd < 0)
+ return 1;
+ write(fd, msg, sizeof(msg) - 1);
+ close(fd);
+ return 0;
+}
diff --git a/tests/meson.build b/tests/meson.build
index 8f96e5f0..464ed0bb 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -291,6 +291,21 @@ executable(
install_dir : installed_testdir,
)
+if cc.links('#include <stdio.h>\nint main(void) { printf("test"); return 0; }',
+ args: ['-static'],
+ name: 'static libc')
+ executable(
+ 'apply-extra-static',
+ 'apply-extra-static.c',
+ install : get_option('installed_tests'),
+ install_dir : installed_testdir,
+ override_options : ['b_sanitize=none'],
+ link_args : ['-static'],
+ )
+else
+ warning('static libc not available, apply-extra-static will be skipped')
+endif
+
subdir('share/xdg-desktop-portal/portals')
subdir('services')
subdir('test-keyring')
diff --git a/tests/test-extra-data.sh b/tests/test-extra-data.sh
index 41c9b913..73fe3ce9 100755
--- a/tests/test-extra-data.sh
+++ b/tests/test-extra-data.sh
@@ -45,7 +45,7 @@ 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"
+echo "1..2"
# build the app with the extra data
EXTRA_DATA="--extra-data=test:${EXTRA_DATA_SHA256}:${EXTRA_DATA_SIZE}:${EXTRA_DATA_SIZE}:${EXTRA_DATA_URL}"
@@ -61,3 +61,44 @@ assert_file_has_content out "extra-data-test-content"
${FLATPAK} ${U} uninstall -y org.test.Hello >&2
ok "install extra data app with ostree"
+
+build_extra_data_noruntime_app() {
+ local repo="$1"
+ local branch="$2"
+
+ DIR="$(mktemp -d)"
+ ARCH="$(flatpak --default-arch)"
+
+ mkdir -p "${DIR}/files/bin"
+
+ cp "${G_TEST_BUILDDIR}/apply-extra-static" "${DIR}/files/bin/apply_extra"
+ chmod +x "${DIR}/files/bin/apply_extra"
+
+ cat > "${DIR}/metadata" <<EOF
+[Application]
+name=org.test.ExtraDataNoRuntime
+runtime=org.test.Platform/${ARCH}/${branch}
+
+[Extra Data]
+NoRuntime=true
+uri=${EXTRA_DATA_URL}
+size=${EXTRA_DATA_SIZE}
+checksum=${EXTRA_DATA_SHA256}
+EOF
+
+ flatpak build-finish "${DIR}" >&2
+ flatpak build-export ${FL_GPGARGS} "${repo}" "${DIR}" "${branch}" >&2
+ rm -rf "${DIR}"
+}
+
+if test -f "${G_TEST_BUILDDIR}/apply-extra-static"; then
+ build_extra_data_noruntime_app repos/test ${BRANCH}
+ update_repo ${REPONAME} ${COLLECTION_ID}
+ ${FLATPAK} ${U} install --or-update -y ${REPONAME}-repo org.test.ExtraDataNoRuntime ${BRANCH} >&2
+ DEPLOY_DIR="$(${FLATPAK} ${U} info --show-location org.test.ExtraDataNoRuntime)"
+ assert_file_has_content "${DEPLOY_DIR}/files/extra/ran" "^noruntime-extra-data$"
+ ${FLATPAK} ${U} uninstall -y org.test.ExtraDataNoRuntime >&2
+ ok "install+run extra data app with NoRuntime"
+else
+ ok "# SKIP install+run extra data app with NoRuntime apply-extra-static not found"
+fi
--
2.54.0

View File

@ -0,0 +1,52 @@
From 90625f0ef612364ded91a67327bdfe5b59e03b27 Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Fri, 10 Apr 2026 09:58:05 +0100
Subject: [PATCH] utils: Add flatpak_set_cloexec()
Helps: https://github.com/flatpak/flatpak/issues/6582
Signed-off-by: Simon McVittie <smcv@collabora.com>
(cherry picked from commit 8a989c790d9121f53ada88fd001a3997b9e40632)
---
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 8f970ea2..e7fa348c 100644
--- a/common/flatpak-utils-private.h
+++ b/common/flatpak-utils-private.h
@@ -353,4 +353,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 7e69e8be..706a9cb2 100644
--- a/common/flatpak-utils.c
+++ b/common/flatpak-utils.c
@@ -2525,3 +2525,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;
+}
--
2.54.0

View File

@ -0,0 +1,102 @@
From a58978e40e7650c9c2cda716dd6d387fd6b5ea8e Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Fri, 10 Apr 2026 10:07:14 +0100
Subject: [PATCH] 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"
Resolves: https://github.com/flatpak/flatpak/issues/6582
Signed-off-by: Simon McVittie <smcv@collabora.com>
(cherry picked from commit 0902090726c2e51b1c6f22c64d708a4895a196e7)
---
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 23b9f368..48968fbb 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 b5206aab..5c66a9ee 100644
--- a/common/flatpak-context.c
+++ b/common/flatpak-context.c
@@ -1391,6 +1391,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);
}
--
2.54.0

View File

@ -0,0 +1,179 @@
From ccaf86c854aeed47c2b346666b14e52d0f821b7f Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Thu, 9 Apr 2026 20:06:18 +0100
Subject: [PATCH] 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.
Helps: https://github.com/flatpak/flatpak/issues/6584
Co-authored-by: Sebastian Wick <sebastian.wick@redhat.com>
Signed-off-by: Simon McVittie <smcv@collabora.com>
(cherry picked from commit 15dc818874514ffbece4c080405353ed396b54a9)
---
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 f176e884..7d46dd6b 100644
--- a/common/flatpak-run.c
+++ b/common/flatpak-run.c
@@ -2939,42 +2939,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 "/<name> (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,
@@ -3203,7 +3167,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,
@@ -3331,7 +3295,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");
@@ -3640,7 +3604,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;
@@ -3657,7 +3621,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 e7fa348c..8cad6672 100644
--- a/common/flatpak-utils-private.h
+++ b/common/flatpak-utils-private.h
@@ -351,6 +351,9 @@ gboolean flatpak_is_debugging (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 706a9cb2..b8bfcfdb 100644
--- a/common/flatpak-utils.c
+++ b/common/flatpak-utils.c
@@ -2542,3 +2542,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 "/<name> (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);
+}
--
2.54.0

View File

@ -0,0 +1,66 @@
From 5f73d530a25a119222b42b0680766faba3875777 Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Thu, 9 Apr 2026 20:16:16 +0100
Subject: [PATCH] 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"
Helps: https://github.com/flatpak/flatpak/issues/6584
Co-authored-by: Sebastian Wick <sebastian.wick@redhat.com>
Signed-off-by: Simon McVittie <smcv@collabora.com>
(cherry picked from commit 4ef2421bd22d8fbf8f17cf9bf5da1dd95aedef8d)
---
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 0550a379..be4d40a8 100644
--- a/portal/flatpak-portal.c
+++ b/portal/flatpak-portal.c
@@ -1247,8 +1247,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]);
}
@@ -1273,8 +1282,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]);
}
--
2.54.0

View File

@ -0,0 +1,94 @@
From fbe5a2faa776e0f5bdd6160c1dd69b0b97c2d8eb Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Fri, 6 Feb 2026 17:14:49 +0100
Subject: [PATCH] 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.
---
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 646c5ec5..b5206aab 100644
--- a/common/flatpak-context.c
+++ b/common/flatpak-context.c
@@ -1382,21 +1382,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 7fe509f3..8f970ea2 100644
--- a/common/flatpak-utils-private.h
+++ b/common/flatpak-utils-private.h
@@ -348,6 +348,9 @@ gboolean running_under_sudo_root (void);
void flatpak_set_debugging (gboolean debugging);
gboolean flatpak_is_debugging (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 7bfa2ad1..7a30969c 100644
--- a/common/flatpak-utils.c
+++ b/common/flatpak-utils.c
@@ -2473,3 +2473,25 @@ flatpak_is_debugging (void)
return is_debugging;
}
+
+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;
+}
--
2.54.0

View File

@ -0,0 +1,65 @@
From 4b6ee5e5c9ca33638b238c15e865864ed1437fb8 Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Thu, 9 Apr 2026 20:24:48 +0100
Subject: [PATCH] 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"
Helps: https://github.com/flatpak/flatpak/issues/6584
Co-authored-by: Sebastian Wick <sebastian.wick@redhat.com>
Signed-off-by: Simon McVittie <smcv@collabora.com>
(cherry picked from commit 75ab6eebb857fd26172613b69e55f04830ad0d82)
---
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 be4d40a8..1803dad1 100644
--- a/portal/flatpak-portal.c
+++ b/portal/flatpak-portal.c
@@ -1263,12 +1263,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);
}
}
}
@@ -1298,12 +1295,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);
}
}
}
--
2.54.0

View File

@ -0,0 +1,69 @@
From 0ef48d4a4bd7479d35c2526104292e36d9ea1500 Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Thu, 9 Apr 2026 20:28:57 +0100
Subject: [PATCH] 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"
Resolves: https://github.com/flatpak/flatpak/issues/6584
Co-authored-by: Sebastian Wick <sebastian.wick@redhat.com>
Signed-off-by: Simon McVittie <smcv@collabora.com>
(cherry picked from commit 28634c7f52e57df7091007973d1bb5e1f87f1e9d)
---
portal/flatpak-portal.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/portal/flatpak-portal.c b/portal/flatpak-portal.c
index 1803dad1..e2f59b73 100644
--- a/portal/flatpak-portal.c
+++ b/portal/flatpak-portal.c
@@ -558,7 +558,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);
@@ -577,6 +579,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;
--
2.54.0

View File

@ -0,0 +1,37 @@
From b7806fe7838e6a9114163fbcd20c11e7636d695f Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Fri, 10 Apr 2026 11:38:12 +0100
Subject: [PATCH] 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.
Resolves: https://github.com/flatpak/flatpak/issues/6591
Signed-off-by: Simon McVittie <smcv@collabora.com>
(cherry picked from commit 50dda82eb054695b3d3758d0a88ef68c8dd79dc4)
---
tests/libtest.sh | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/libtest.sh b/tests/libtest.sh
index 823b1be2..7004f2d1 100644
--- a/tests/libtest.sh
+++ b/tests/libtest.sh
@@ -312,7 +312,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
--
2.54.0

View File

@ -0,0 +1,61 @@
From 4491406f509707feb755982fdda24b2fd9234903 Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Fri, 10 Apr 2026 15:44:44 +0200
Subject: [PATCH] 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)
---
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 94c32416..4868b8b5 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
--
2.54.0

View File

@ -0,0 +1,103 @@
From 7f5306876adc6f322f88c01801fd5aab2742c7b4 Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Fri, 10 Apr 2026 14:00:14 +0100
Subject: [PATCH] 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 <smcv@collabora.com>
(cherry picked from commit c4ab58cd2e66c4bcf193919ef9cbdce1dac042da)
---
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 48968fbb..f1827a13 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 5c66a9ee..af113946 100644
--- a/common/flatpak-context.c
+++ b/common/flatpak-context.c
@@ -1389,7 +1389,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
--
2.54.0

View File

@ -0,0 +1,235 @@
From 817888730861a21bf930f7fbadc28155c2f3de39 Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Fri, 10 Apr 2026 15:02:43 +0100
Subject: [PATCH] app, context: Factor out flatpak_accept_fd_argument()
Signed-off-by: Simon McVittie <smcv@collabora.com>
(cherry picked from commit d42037c5267ac7967ce285b9052b25fe7a968368)
---
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 f1827a13..b6185ca7 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 af113946..59e8d9d4 100644
--- a/common/flatpak-context.c
+++ b/common/flatpak-context.c
@@ -1384,25 +1384,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 8cad6672..75538399 100644
--- a/common/flatpak-utils-private.h
+++ b/common/flatpak-utils-private.h
@@ -358,4 +358,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 b8bfcfdb..09102fd8 100644
--- a/common/flatpak-utils.c
+++ b/common/flatpak-utils.c
@@ -2543,6 +2543,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.
*
--
2.54.0

View File

@ -0,0 +1,48 @@
From 5a7e677294c92c04ea8c7c09a2b1abd2988f03df Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Fri, 6 Feb 2026 16:32:50 +0100
Subject: [PATCH] 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.
---
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 a3d29782..a0eddfd4 100644
--- a/common/flatpak-bwrap.c
+++ b/common/flatpak-bwrap.c
@@ -42,15 +42,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 *
@@ -60,9 +51,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);
--
2.54.0

View File

@ -0,0 +1,915 @@
From 9c9fe3fdf3920428822cacbb249fd75e6d8dbe48 Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Fri, 6 Feb 2026 20:54:22 +0100
Subject: [PATCH] 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.
---
app/flatpak-builtins-build.c | 8 +-
app/flatpak-builtins-run.c | 37 ++-
common/flatpak-dir.c | 8 +-
common/flatpak-installation.c | 3 +-
common/flatpak-run-private.h | 11 +-
common/flatpak-run.c | 471 +++++++++++++++++++++++-----------
6 files changed, 382 insertions(+), 156 deletions(-)
diff --git a/app/flatpak-builtins-build.c b/app/flatpak-builtins-build.c
index 585f8f4..8625b36 100644
--- a/app/flatpak-builtins-build.c
+++ b/app/flatpak-builtins-build.c
@@ -458,7 +458,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 d0a1fb0..aebc345 100644
--- a/app/flatpak-builtins-run.c
+++ b/app/flatpak-builtins-run.c
@@ -111,6 +111,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;
run_environ = g_get_environ ();
@@ -309,14 +311,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 6936d45..a1d71a2 100644
--- a/common/flatpak-dir.c
+++ b/common/flatpak-dir.c
@@ -8468,7 +8468,13 @@ apply_extra_data (FlatpakDir *self,
* Disable /proc entirely in this context. */
run_flags |= FLATPAK_RUN_FLAG_NO_PROC;
- if (!flatpak_run_setup_base_argv (bwrap, runtime_files, NULL, runtime_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, NULL, runtime_arch,
run_flags, error))
return FALSE;
diff --git a/common/flatpak-installation.c b/common/flatpak-installation.c
index c7d2e1d..00c023f 100644
--- a/common/flatpak-installation.c
+++ b/common/flatpak-installation.c
@@ -703,9 +703,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 40899e2..4c6fe16 100644
--- a/common/flatpak-run-private.h
+++ b/common/flatpak-run-private.h
@@ -30,6 +30,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);
@@ -71,7 +76,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,
@@ -103,12 +108,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 8c92924..3a2028f 100644
--- a/common/flatpak-run.c
+++ b/common/flatpak-run.c
@@ -1609,40 +1609,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 = flatpak_get_tzdir ();
+
+ 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
{
- const char *tzdir = flatpak_get_tzdir ();
+ g_autofree char *runtime_zoneinfo = NULL;
+ glnx_autofd int runtime_zoneinfo_fd = -1;
- /* Check for host /usr/share/zoneinfo */
- if (g_file_test (tzdir, G_FILE_TEST_IS_DIR))
+ 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", tzdir, "/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",
@@ -2138,24 +2154,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;
@@ -2169,11 +2202,6 @@ flatpak_run_setup_usr_links (FlatpakBwrap *bwrap,
"--symlink", link, create,
NULL);
}
- else
- {
- g_info ("%s does not exist",
- flatpak_file_get_path_cached (runtime_subdir));
- }
}
}
@@ -2189,7 +2217,7 @@ static const char *const sysfs_dirs[] =
gboolean
flatpak_run_setup_base_argv (FlatpakBwrap *bwrap,
- GFile *runtime_files,
+ int runtime_fd,
GFile *app_id_dir,
const char *arch,
FlatpakRunFlags flags,
@@ -2202,12 +2230,13 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap,
struct group *g;
gulong pers;
gid_t gid = getgid ();
- g_autoptr(GFile) etc = NULL;
gboolean parent_expose_pids = (flags & FLATPAK_RUN_FLAG_PARENT_EXPOSE_PIDS) != 0;
gboolean parent_share_pids = (flags & FLATPAK_RUN_FLAG_PARENT_SHARE_PIDS) != 0;
gboolean bwrap_unprivileged = flatpak_bwrap_is_unprivileged ();
gsize i;
+ g_return_val_if_fail (runtime_fd >= 0, FALSE);
+
/* Disable recursive userns for all flatpak processes, as we need this
* to guarantee that the sandbox can't restructure the filesystem.
* Allowing to change e.g. /.flatpak-info would allow sandbox escape
@@ -2315,22 +2344,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;
@@ -2347,9 +2379,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;
@@ -2360,9 +2402,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);
}
}
}
@@ -2383,9 +2428,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;
@@ -2612,7 +2657,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)
@@ -2652,7 +2697,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)
{
@@ -2867,15 +2912,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 "/<name> (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,
@@ -2891,11 +2972,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;
@@ -2929,20 +3005,41 @@ 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_assert (run_environ != 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.
*/
@@ -3068,38 +3165,53 @@ flatpak_run_app (FlatpakDecomposed *app_ref,
flatpak_context_dump (app_context, "Final 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_usr_path != NULL)
+ if (custom_runtime_fd >= 0)
{
- 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";
+ 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);
+ }
+
+ 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)
+ {
+ 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);
@@ -3186,19 +3298,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");
+
+ 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 (custom_app_fd == FLATPAK_RUN_APP_DEPLOY_APP_EMPTY)
+ {
+ app_fd = -1;
+ app_files = NULL;
+ }
+ else
+ {
+ g_assert_not_reached ();
+ }
- /* Mount the original app below here */
- app_target_path = "/run/parent/app";
+ /* 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 if (original_app_files != NULL)
+ else
{
- app_files = g_object_ref (original_app_files);
+ 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);
@@ -3211,75 +3364,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
{
@@ -3288,7 +3452,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,
@@ -3301,26 +3465,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) */
@@ -3332,7 +3507,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)
@@ -3342,7 +3517,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)
--
2.54.0

View File

@ -0,0 +1,78 @@
From 494c5ea687be884487fa5dc8b943130d9df7bb5f Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Fri, 6 Feb 2026 20:55:46 +0100
Subject: [PATCH] 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.
---
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 aebc345c..a2644f89 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 }
};
@@ -311,7 +315,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)
{
@@ -330,7 +345,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);
--
2.54.0

View File

@ -0,0 +1,116 @@
From 4f1e7f8d1705488e501925255fbe061ac71405fc Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Fri, 6 Feb 2026 21:02:47 +0100
Subject: [PATCH] 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.
---
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 a2644f89..b14bf056 100644
--- a/app/flatpak-builtins-run.c
+++ b/app/flatpak-builtins-run.c
@@ -385,6 +385,8 @@ flatpak_builtin_run (int argc, char **argv, GCancellable *cancellable, GError **
opt_instance_id_fd,
(const char * const *) run_environ,
NULL,
+ NULL,
+ NULL,
cancellable,
error))
return FALSE;
diff --git a/common/flatpak-installation.c b/common/flatpak-installation.c
index 29250b90..fe5e5757 100644
--- a/common/flatpak-installation.c
+++ b/common/flatpak-installation.c
@@ -714,6 +714,7 @@ flatpak_installation_launch_full (FlatpakInstallation *self,
NULL, 0, -1,
(const char * const *) run_environ,
&instance_dir,
+ NULL, NULL,
cancellable, error))
return FALSE;
diff --git a/common/flatpak-run-private.h b/common/flatpak-run-private.h
index 0adbb8af..8c6cc049 100644
--- a/common/flatpak-run-private.h
+++ b/common/flatpak-run-private.h
@@ -124,6 +124,8 @@ gboolean flatpak_run_app (FlatpakDecomposed *app_ref,
int instance_id_fd,
const char * const *run_environ,
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 2813d150..055c721d 100644
--- a/common/flatpak-run.c
+++ b/common/flatpak-run.c
@@ -2979,6 +2979,8 @@ flatpak_run_app (FlatpakDecomposed *app_ref,
int instance_id_fd,
const char * const *run_environ,
char **instance_dir_out,
+ GArray *bind_fds,
+ GArray *ro_bind_fds,
GCancellable *cancellable,
GError **error)
{
@@ -3583,6 +3585,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;
--
2.54.0

View File

@ -0,0 +1,95 @@
From a971671f9546a20e2134afde31b317ff140e5bd9 Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Fri, 6 Feb 2026 21:03:34 +0100
Subject: [PATCH] run: Add --(ro-)bind-fd options
Exposes the functionality added to flatpak_run_app in the previous
commit with two new options.
---
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 b14bf056..0bfc311d 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 }
};
@@ -120,6 +162,9 @@ flatpak_builtin_run (int argc, char **argv, GCancellable *cancellable, GError **
run_environ = g_get_environ ();
+ 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);
@@ -385,8 +430,8 @@ flatpak_builtin_run (int argc, char **argv, GCancellable *cancellable, GError **
opt_instance_id_fd,
(const char * const *) run_environ,
NULL,
- NULL,
- NULL,
+ opt_bind_fds,
+ opt_ro_bind_fds,
cancellable,
error))
return FALSE;
--
2.54.0

View File

@ -0,0 +1,602 @@
From c82a3d7716bd02961d8c68b1d18256dfea19efc5 Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Fri, 6 Feb 2026 21:03:58 +0100
Subject: [PATCH] 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!
---
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 12f2729a..5c0935a1 100644
--- a/portal/flatpak-portal.c
+++ b/portal/flatpak-portal.c
@@ -552,195 +552,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 "/<name> (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
@@ -798,10 +663,13 @@ 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_autofree char *app_id_prefix = NULL;
+ 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;
@@ -925,29 +793,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_info ("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_info ("exposing %s", expose);
- if (!is_valid_expose (expose, &error))
- {
- g_dbus_method_invocation_return_gerror (invocation, error);
- return G_DBUS_METHOD_INVOCATION_HANDLED;
- }
- }
-
app_id_prefix = g_strdup_printf ("%s.", app_id);
for (i = 0; sandbox_a11y_own_names != NULL && sandbox_a11y_own_names[i] != NULL; i++)
{
@@ -1197,10 +1042,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,
@@ -1211,16 +1060,12 @@ handle_spawn (PortalFlatpak *object,
}
env_fd = g_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++)
@@ -1318,54 +1163,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_info ("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_info ("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;
}
@@ -1375,31 +1266,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_info ("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",
@@ -1409,20 +1289,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)
{
@@ -1434,18 +1334,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_info ("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)
{
@@ -1454,8 +1347,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)
{
@@ -1467,18 +1360,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_info ("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]));
--
2.54.0

View File

@ -0,0 +1,84 @@
From c97905c8188ddaad01ee146b57bba6c3fa294113 Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
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.
(cherry picked from commit FIXME)
diff --git a/common/flatpak-utils.c b/common/flatpak-utils.c
index 7a30969c..dbc46951 100644
--- a/common/flatpak-utils.c
+++ b/common/flatpak-utils.c
@@ -922,6 +922,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
@@ -931,6 +947,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,
@@ -974,10 +993,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)

View File

@ -0,0 +1,50 @@
From 4a678f463b455c585d38ac4cf4d994e7ce710f8e Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Mon, 12 Jan 2026 17:38:02 +0100
Subject: [PATCH] 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.
(cherry picked from commit FIXME)
---
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 f45cec5c..942bb47b 100644
--- a/common/flatpak-oci-registry.c
+++ b/common/flatpak-oci-registry.c
@@ -275,6 +275,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,
@@ -286,7 +289,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)
{
--
2.54.0

View File

@ -0,0 +1,150 @@
From a27ec46e8c0ab0ae162f2aa3142dccb6b79d9211 Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Sat, 7 Feb 2026 21:57:30 +0100
Subject: [PATCH] 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.
---
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 778b3c59..f709f948 100644
--- a/system-helper/flatpak-system-helper.c
+++ b/system-helper/flatpak-system-helper.c
@@ -358,23 +358,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);
@@ -426,6 +434,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,
@@ -433,30 +444,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,
@@ -738,31 +736,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);
--
2.54.0

View File

@ -1,4 +1,4 @@
From 5f5aeea8d8be071468fb8e9640554518fb65885e Mon Sep 17 00:00:00 2001
From 8c8d6faa9f0fc35581e95a9e771490feb94dd9ac Mon Sep 17 00:00:00 2001
From: Sebastian Wick <sebastian.wick@redhat.com>
Date: Tue, 16 Dec 2025 17:15:32 +0100
Subject: [PATCH] run: Enable FIPS crypto policy if it is enabled on the host
@ -6,27 +6,30 @@ Subject: [PATCH] run: Enable FIPS crypto policy if it is enabled on the host
This is a close copy of what podman/containers does to support FIPS. Any
other crypto policy is ignored for now.
---
common/flatpak-run.c | 87 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 87 insertions(+)
common/flatpak-run.c | 94 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 94 insertions(+)
diff --git ./common/flatpak-run.c ../common/flatpak-run.c
index 6c319231..b51cc637 100644
--- ./common/flatpak-run.c
+++ ../common/flatpak-run.c
@@ -2215,6 +2215,91 @@ flatpak_run_setup_usr_links (FlatpakBwrap *bwrap,
diff --git a/common/flatpak-run.c b/common/flatpak-run.c
index dfcef03..121bef3 100644
--- a/common/flatpak-run.c
+++ b/common/flatpak-run.c
@@ -2214,6 +2214,98 @@ flatpak_run_setup_usr_links (FlatpakBwrap *bwrap,
}
}
+static void
+flatpak_run_setup_fips (FlatpakBwrap *bwrap,
+ GFile *runtime_files)
+ int runtime_fd)
+{
+ g_autoptr(GFile) runtime_crypto_policies = NULL;
+ g_autoptr(GFile) runtime_fips_backend = NULL;
+ g_autoptr(GFile) runtime_fips_config = NULL;
+ glnx_autofd int crypto_policies_fd = -1;
+ glnx_autofd int fips_backend_fd = -1;
+ glnx_autofd int fips_config_fd = -1;
+ g_autofree char *fips_enabled = NULL;
+ g_autoptr(GError) error = NULL;
+
+ if (runtime_fd < 0)
+ return;
+
+ if (!g_file_get_contents ("/proc/sys/crypto/fips_enabled",
+ &fips_enabled,
+ NULL, &error))
@ -48,35 +51,39 @@ index 6c319231..b51cc637 100644
+ return;
+ }
+
+ runtime_crypto_policies =
+ g_file_resolve_relative_path (runtime_files, "etc/crypto-policies");
+ crypto_policies_fd = glnx_chaseat (runtime_fd, "etc/crypto-policies",
+ GLNX_CHASE_RESOLVE_BENEATH,
+ NULL);
+
+ if (!g_file_query_exists (runtime_crypto_policies, NULL))
+ if (crypto_policies_fd < 0)
+ {
+ g_info ("FIPS is enabled, but runtime does not support it");
+ return;
+ }
+
+ runtime_fips_backend =
+ g_file_resolve_relative_path (runtime_files,
+ "share/crypto-policies/back-ends/FIPS");
+ fips_backend_fd = glnx_chaseat (runtime_fd,
+ "share/crypto-policies/back-ends/FIPS",
+ GLNX_CHASE_RESOLVE_BENEATH,
+ NULL);
+
+ if (!g_file_query_exists (runtime_fips_backend, NULL))
+ if (fips_backend_fd < 0)
+ {
+ g_info ("FIPS is enabled, but runtime does not support it");
+ return;
+ }
+
+ runtime_fips_config =
+ g_file_resolve_relative_path (runtime_files,
+ "share/crypto-policies/default-fips-config");
+ fips_config_fd = glnx_chaseat (runtime_fd,
+ "share/crypto-policies/default-fips-config",
+ GLNX_CHASE_RESOLVE_BENEATH |
+ GLNX_CHASE_MUST_BE_REGULAR,
+ NULL);
+
+ if (g_file_query_exists (runtime_fips_config, NULL))
+ if (fips_config_fd >= 0)
+ {
+ flatpak_bwrap_add_args (bwrap, "--ro-bind",
+ flatpak_file_get_path_cached (runtime_fips_config),
+ "/etc/crypto-policies/config",
+ NULL);
+ flatpak_bwrap_add_args_data_fd (bwrap,
+ "--ro-bind-fd",
+ g_steal_fd (&fips_config_fd),
+ "/etc/crypto-policies/config");
+ }
+ else
+ {
@ -94,10 +101,10 @@ index 6c319231..b51cc637 100644
+ }
+ }
+
+ flatpak_bwrap_add_args (bwrap, "--ro-bind",
+ flatpak_file_get_path_cached (runtime_fips_backend),
+ "/etc/crypto-policies/back-ends",
+ NULL);
+ flatpak_bwrap_add_args_data_fd (bwrap,
+ "--ro-bind-fd",
+ g_steal_fd (&fips_backend_fd),
+ "/etc/crypto-policies/back-ends");
+
+ g_info ("Enabled FIPS configuration");
+}
@ -105,15 +112,15 @@ index 6c319231..b51cc637 100644
/* Directories in /sys to share with the sandbox if accessible. */
static const char *const sysfs_dirs[] =
{
@@ -2405,6 +2490,8 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap,
@@ -2426,6 +2518,8 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap,
}
}
+ flatpak_run_setup_fips (bwrap, runtime_files);
+ flatpak_run_setup_fips (bwrap, runtime_fd);
+
if (app_id_dir != NULL)
{
g_autoptr(GFile) app_cache_dir = g_file_get_child (app_id_dir, "cache");
--
2.51.0
2.54.0

View File

@ -12,7 +12,7 @@
Name: flatpak
Version: 1.16.0
Release: 9%{?dist}
Release: 9%{?dist}.1
Summary: Application deployment framework for desktop apps
License: LGPL-2.1-or-later
@ -52,6 +52,48 @@ Patch7: flatpak-run-Enable-FIPS-crypto-policy-if-it-is-enabled-on-th.pat
# Stop killing the session when stopping background apps
Patch9: flatpak-kill-Do-not-kill-pid-0-and-embrace-races.patch
# CVE-2026-34078
Patch100: CVE-2026-34078-1-update-subtree-libglnx-2026-04-07.patch
Patch101: CVE-2026-34078-2-flatpak-bwrap-add-dup-ing-variant-flatpak-bwrap-add-args-data-fd-dup.patch
Patch102: CVE-2026-34078-3-utils-add-flatpak-parse-fd.patch
Patch103: CVE-2026-34078-4-flatpak-bwrap-use-glnx-close-fd-as-clear-func.patch
Patch104: CVE-2026-34078-5-run-use-o-path-fds-for-the-runtime-and-app-deploy-directories.patch
Patch105: CVE-2026-34078-6-run-add-usr-fd-and-app-fd-options.patch
Patch106: CVE-2026-34078-7-run-add-ro-bind-fds-to-flatpak-run-app.patch
Patch107: CVE-2026-34078-8-run-add-ro-bind-fd-options.patch
Patch108: CVE-2026-34078-9-portal-use-bind-fd-app-fd-and-usr-fd-options-to-avoid-races.patch
Patch109: CVE-2026-34078-10-run-fix-checking-wrong-variable-in-runtime-fd-selection.patch
Patch110: CVE-2026-34078-11-run-mount-original-app-on-run-parent-app-when-using-app-path.patch
Patch111: CVE-2026-34078-12-portal-update-max-fd-after-creating-the-instance-id-pipe.patch
Patch112: CVE-2026-34078-13-run-fix-fd-tracking-in-flatpak-run-add-app-info-args.patch
Patch113: CVE-2026-34078-14-utils-improve-error-message-when-passing-an-fd-numer-which-is-not-a-fd.patch
Patch114: CVE-2026-34078-15-run-do-not-close-bind-ro-bind.patch
Patch115: CVE-2026-34078-16-run-use-the-same-fd-validation-for-all-fd-options.patch
Patch116: CVE-2026-34078-17-run-add-bind-fd-and-ro-bind-fd-binds-after-all-other-binds.patch
Patch117: CVE-2026-34078-18-portal-use-g-array-index-to-read-from-expose-fds-expose-fds-ro.patch
Patch118: CVE-2026-34078-19-run-fix-backport-mistake.patch
Patch119: CVE-2026-34078-20-tests-test-run-custom-test-usr-path-usr-fd-app-path-app-fd.patch
Patch120: CVE-2026-34078-21-tests-test-run-custom-test-bind-fd-and-ro-bind-fd.patch
Patch121: CVE-2026-34078-22-run-cope-with-an-empty-runtime.patch
Patch122: CVE-2026-34078-23-dir-in-apply-extra-data-don-t-assume-there-is-always-a-runtime.patch
Patch123: CVE-2026-34078-24-tests-add-test-extra-data-sh-to-test-extra-data-installation.patch
Patch124: CVE-2026-34078-25-tests-add-test-for-noruntime-extra-data-app.patch
Patch125: CVE-2026-34078-26-utils-add-flatpak-set-cloexec.patch
Patch126: CVE-2026-34078-27-run-context-mark-fd-arguments-as-close-on-exec.patch
Patch127: CVE-2026-34078-28-utils-move-flatpak-get-path-for-fd-to-here.patch
Patch128: CVE-2026-34078-29-portal-avoid-crash-if-sandbox-expose-ro-fd-is-out-of-range.patch
Patch129: CVE-2026-34078-30-portal-log-and-ignore-unusable-sandbox-expose-fds-instead-of-erroring.patch
Patch130: CVE-2026-34078-31-portal-reinstate-flatpak-get-path-for-fd-checks.patch
Patch131: CVE-2026-34078-32-libtest-allow-adding-a-new-ref-to-an-existing-temporary-ostree-repo.patch
Patch132: CVE-2026-34078-33-tests-check-that-flatpak-run-fd-arguments-do-not-leak-to-the-command.patch
Patch133: CVE-2026-34078-34-app-context-never-close-fds-0-1-or-2.patch
Patch134: CVE-2026-34078-35-app-context-factor-out-flatpak-accept-fd-argument.patch
# CVE-2026-34079
Patch135: CVE-2026-34079-1-utils-only-remove-cached-files-in-the-cache-directory.patch
Patch136: CVE-2026-34079-2-utils-do-not-follow-symlinks-in-local-open-file.patch
Patch137: CVE-2026-34079-3-system-helper-only-remove-an-ongoing-pull-if-users-match.patch
# ostree not on i686 for RHEL 10
# https://github.com/containers/composefs/pull/229#issuecomment-1838735764
%if 0%{?rhel} >= 10
@ -320,6 +362,12 @@ fi
%changelog
* Mon May 18 2026 Jan Grulich <jgrulich@redhat.com> - 1.16.0-9.1
- Fix arbitrary code execution via crafted symlinks in sandbox-expose options
Resolves: RHEL-165630
- Fix arbitrary file deletion on host via improper cache file path validation
Resolves: RHEL-170157
* Tue Jan 13 2026 Sebastian Wick <sebastian.wick@redhat.com> - 1.16.0-9
- kill: Do not kill pid 0 and embrace races
Resolves: RHEL-140924