From a9c53b6d4eb3366ad97ddd01d043c93daea2c5d7 Mon Sep 17 00:00:00 2001 From: Sebastian Wick Date: Thu, 18 Jun 2026 17:59:47 +0200 Subject: [PATCH] run: Harden regenerate_ld_cache against symlink attacks A sandboxed app can replace ~/.var/app/$appid/.ld.so with a symlink, causing regenerate_ld_cache to write files at an arbitrary location. A concurrent app instance makes this a TOCTOU even after the initial directory verification. Replace all path-based operations with fd-based equivalents using ld_so_dir_fd obtained via glnx_chase_and_mkdirat, and pass the directory to bwrap via --bind-fd instead of --bind. Assisted-by: Claude:opus-4.6 Resolves: https://github.com/flatpak/flatpak/security/advisories/GHSA-99wv-m8rp-g58x --- common/flatpak-run.c | 88 ++++++++++++++++++++++++++++++-------------- 1 file changed, 61 insertions(+), 27 deletions(-) diff --git a/common/flatpak-run.c b/common/flatpak-run.c index f9591751d..684500247 100644 --- a/common/flatpak-run.c +++ b/common/flatpak-run.c @@ -2826,33 +2826,72 @@ regenerate_ld_cache (GPtrArray *base_argv_array, { g_autoptr(FlatpakBwrap) bwrap = NULL; g_autoptr(GArray) combined_fd_array = NULL; - g_autoptr(GFile) ld_so_cache = NULL; - g_autoptr(GFile) ld_so_cache_tmp = NULL; g_autofree char *sandbox_cache_path = NULL; g_autofree char *tmp_basename = NULL; g_auto(GStrv) minimal_envp = NULL; g_autofree char *commandline = NULL; int exit_status; glnx_autofd int ld_so_fd = -1; - g_autoptr(GFile) ld_so_dir = NULL; + glnx_autofd int ld_so_dir_fd = -1; + g_autofree char *ld_so_dir_path = NULL; if (app_id_dir) - ld_so_dir = g_file_get_child (app_id_dir, ".ld.so"); + { + glnx_autofd int app_id_dir_fd = -1; + + app_id_dir_fd = glnx_chase_and_mkdirat (AT_FDCWD, + flatpak_file_get_path_cached (app_id_dir), + GLNX_CHASE_DEFAULT, + 0700, error); + if (app_id_dir_fd < 0) + { + g_prefix_error (error, "cannot open %s: ", + flatpak_file_get_path_cached (app_id_dir)); + return -1; + } + + ld_so_dir_fd = glnx_chase_and_mkdirat (app_id_dir_fd, ".ld.so", + GLNX_CHASE_RESOLVE_NO_SYMLINKS, + 0755, error); + + ld_so_dir_path = g_build_filename (flatpak_file_get_path_cached (app_id_dir), + ".ld.so", + NULL); + } else { - g_autoptr(GFile) base_dir = g_file_new_for_path (g_get_user_cache_dir ()); - ld_so_dir = g_file_resolve_relative_path (base_dir, "flatpak/ld.so"); + glnx_autofd int cache_dir_fd = -1; + + cache_dir_fd = glnx_chase_and_mkdirat (AT_FDCWD, + g_get_user_cache_dir (), + GLNX_CHASE_DEFAULT, + 0700, error); + if (cache_dir_fd < 0) + { + g_prefix_error (error, "cannot open %s: ", + g_get_user_cache_dir ()); + return -1; + } + + ld_so_dir_fd = glnx_chase_and_mkdirat (cache_dir_fd, "flatpak/ld.so", + GLNX_CHASE_RESOLVE_NO_SYMLINKS, + 0755, error); + + ld_so_dir_path = g_build_filename (g_get_user_cache_dir (), + "flatpak/ld.so", + NULL); } - ld_so_cache = g_file_get_child (ld_so_dir, checksum); - ld_so_fd = open (flatpak_file_get_path_cached (ld_so_cache), O_RDONLY); - if (ld_so_fd >= 0) + if (ld_so_dir_fd < 0) + { + g_prefix_error (error, "cannot open %s: ", ld_so_dir_path); + return -1; + } + + if (glnx_openat_rdonly (ld_so_dir_fd, checksum, FALSE, &ld_so_fd, NULL)) return g_steal_fd (&ld_so_fd); - g_info ("Regenerating ld.so.cache %s", flatpak_file_get_path_cached (ld_so_cache)); - - if (!flatpak_mkdir_p (ld_so_dir, cancellable, error)) - return FALSE; + g_info ("Regenerating ld.so.cache %s/%s", ld_so_dir_path, checksum); minimal_envp = flatpak_run_get_minimal_env (FALSE, FALSE); bwrap = flatpak_bwrap_new (minimal_envp); @@ -2875,7 +2914,6 @@ regenerate_ld_cache (GPtrArray *base_argv_array, glnx_gen_temp_name (tmp_basename); sandbox_cache_path = g_build_filename ("/run/ld-so-cache-dir", tmp_basename, NULL); - ld_so_cache_tmp = g_file_get_child (ld_so_dir, tmp_basename); flatpak_bwrap_add_args (bwrap, "--unshare-pid", @@ -2883,8 +2921,13 @@ regenerate_ld_cache (GPtrArray *base_argv_array, "--unshare-net", "--proc", "/proc", "--dev", "/dev", - "--bind", flatpak_file_get_path_cached (ld_so_dir), "/run/ld-so-cache-dir", NULL); + + if (!flatpak_bwrap_add_args_data_fd_dup (bwrap, "--bind-fd", + ld_so_dir_fd, "/run/ld-so-cache-dir", + error)) + return -1; + flatpak_bwrap_sort_envp (bwrap); flatpak_bwrap_envp_to_args (bwrap); @@ -2922,8 +2965,7 @@ regenerate_ld_cache (GPtrArray *base_argv_array, return -1; } - ld_so_fd = open (flatpak_file_get_path_cached (ld_so_cache_tmp), O_RDONLY); - if (ld_so_fd < 0) + if (!glnx_openat_rdonly (ld_so_dir_fd, tmp_basename, FALSE, &ld_so_fd, NULL)) { flatpak_fail_error (error, FLATPAK_ERROR_SETUP_FAILED, _("Can't open generated ld.so.cache")); return -1; @@ -2932,27 +2974,19 @@ regenerate_ld_cache (GPtrArray *base_argv_array, if (app_id_dir == NULL) { /* For runs without an app id dir we always regenerate the ld.so.cache */ - unlink (flatpak_file_get_path_cached (ld_so_cache_tmp)); + unlinkat (ld_so_dir_fd, tmp_basename, 0); } else { - glnx_autofd int ld_so_dir_fd = -1; - /* For app-dirs we keep one checksum alive, by pointing the active symlink to it */ /* Rename to known name, possibly overwriting existing ref if race */ - if (rename (flatpak_file_get_path_cached (ld_so_cache_tmp), flatpak_file_get_path_cached (ld_so_cache)) == -1) + if (renameat (ld_so_dir_fd, tmp_basename, ld_so_dir_fd, checksum) == -1) { glnx_set_error_from_errno (error); return -1; } - if (!glnx_opendirat (AT_FDCWD, - flatpak_file_get_path_cached (ld_so_dir), - FALSE, &ld_so_dir_fd, - error)) - return -1; - if (!flatpak_switch_symlink_and_remove (ld_so_dir_fd, "active", checksum, error)) return -1;