run: Harden ensure_data_dir and /var setup against symlink attacks

Replace path-based flatpak_mkdir_p calls in flatpak_ensure_data_dir
with glnx_chase_and_mkdirat(RESOLVE_NO_SYMLINKS) to prevent an app
from replacing subdirectories of its data dir with symlinks between
runs and having them followed during the next sandbox setup.

In flatpak_run_setup_base_argv, replace path-based --bind args for
the app cache/data/config/tmp directories with --bind-fd using fds
obtained via glnx_chaseat(RESOLVE_NO_SYMLINKS), preventing both
symlink following and TOCTOU races when setting up these bind mounts.

Assisted-by: Claude:opus-4.6
Resolves: https://github.com/flatpak/flatpak/security/advisories/GHSA-8688-9x26-hhxj
This commit is contained in:
Sebastian Wick committed 2026-08-10 23:06:17 +02:00
1 parent 2beb8890dc
commit 924bdc9b3d
1 file changed
+57 -31
+57 -31
View File
@@ -857,30 +857,35 @@ flatpak_ensure_data_dir (GFile *app_id_dir,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GFile) data_dir = g_file_get_child (app_id_dir, "data");
g_autoptr(GFile) cache_dir = g_file_get_child (app_id_dir, "cache");
g_autoptr(GFile) fontconfig_cache_dir = g_file_get_child (cache_dir, "fontconfig");
g_autoptr(GFile) tmp_dir = g_file_get_child (cache_dir, "tmp");
g_autoptr(GFile) config_dir = g_file_get_child (app_id_dir, "config");
g_autoptr(GFile) state_dir = g_file_get_child (app_id_dir, ".local/state");
glnx_autofd int app_id_dir_fd = -1;
static const char *const subdirs[] = {
"data",
"cache",
"cache/fontconfig",
"cache/tmp",
"config",
".local/state",
};
if (!flatpak_mkdir_p (data_dir, cancellable, error))
app_id_dir_fd = glnx_chase_and_mkdirat (AT_FDCWD,
flatpak_file_get_path_cached (app_id_dir),
GLNX_CHASE_DEFAULT,
0755,
error);
if (app_id_dir_fd < 0)
return FALSE;
if (!flatpak_mkdir_p (cache_dir, cancellable, error))
return FALSE;
for (size_t i = 0; i < G_N_ELEMENTS (subdirs); i++)
{
glnx_autofd int fd = -1;
if (!flatpak_mkdir_p (fontconfig_cache_dir, cancellable, error))
return FALSE;
if (!flatpak_mkdir_p (tmp_dir, cancellable, error))
return FALSE;
if (!flatpak_mkdir_p (config_dir, cancellable, error))
return FALSE;
if (!flatpak_mkdir_p (state_dir, cancellable, error))
return FALSE;
fd = glnx_chase_and_mkdirat (app_id_dir_fd, subdirs[i],
GLNX_CHASE_RESOLVE_BENEATH,
0755,
error);
if (fd < 0)
return FALSE;
}
return TRUE;
}
@@ -2526,18 +2531,39 @@ flatpak_run_setup_base_argv (FlatpakBwrap *bwrap,
if (app_id_dir != NULL)
{
g_autoptr(GFile) app_cache_dir = g_file_get_child (app_id_dir, "cache");
g_autoptr(GFile) app_tmp_dir = g_file_get_child (app_cache_dir, "tmp");
g_autoptr(GFile) app_data_dir = g_file_get_child (app_id_dir, "data");
g_autoptr(GFile) app_config_dir = g_file_get_child (app_id_dir, "config");
glnx_autofd int app_id_dir_fd = -1;
static const struct
{
const char *src;
const char *dst;
}
mounts[] = {
{ "cache", "/var/cache" },
{ "data", "/var/data" },
{ "config", "/var/config" },
{ "cache/tmp", "/var/tmp" },
};
flatpak_bwrap_add_args (bwrap,
/* These are nice to have as a fixed path */
"--bind", flatpak_file_get_path_cached (app_cache_dir), "/var/cache",
"--bind", flatpak_file_get_path_cached (app_data_dir), "/var/data",
"--bind", flatpak_file_get_path_cached (app_config_dir), "/var/config",
"--bind", flatpak_file_get_path_cached (app_tmp_dir), "/var/tmp",
NULL);
app_id_dir_fd = glnx_chaseat (AT_FDCWD,
flatpak_file_get_path_cached (app_id_dir),
GLNX_CHASE_MUST_BE_DIRECTORY,
error);
if (app_id_dir_fd < 0)
return FALSE;
for (i = 0; i < G_N_ELEMENTS (mounts); i++)
{
glnx_autofd int fd = -1;
fd = glnx_chase_and_mkdirat (app_id_dir_fd, mounts[i].src,
GLNX_CHASE_RESOLVE_BENEATH,
0755, error);
if (fd < 0)
return FALSE;
flatpak_bwrap_add_args_data_fd (bwrap, "--bind-fd",
g_steal_fd (&fd), mounts[i].dst);
}
}
flatpak_run_setup_usr_links (bwrap, runtime_fd, NULL);