From 76751dc569ceeebb9789c8c5694e546bf50b3b10 Mon Sep 17 00:00:00 2001 From: pkgagent Date: Wed, 5 Aug 2026 10:48:03 +0800 Subject: [PATCH] Fix CVE-2026-14476: reject path traversal in gPCFileSysPath in AD GPO provider --- sssd-2.9.4-CVE-2026-14476.patch | 170 ++++++++++++++++++++++++++++++++ sssd.spec | 8 +- 2 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 sssd-2.9.4-CVE-2026-14476.patch diff --git a/sssd-2.9.4-CVE-2026-14476.patch b/sssd-2.9.4-CVE-2026-14476.patch new file mode 100644 index 0000000..e3d14d2 --- /dev/null +++ b/sssd-2.9.4-CVE-2026-14476.patch @@ -0,0 +1,170 @@ +From 35e6dc38b0acc2112bac08ef4acadf6057b55df7 Mon Sep 17 00:00:00 2001 +From: Alexey Tikhonov +Date: Thu, 2 Jul 2026 17:29:51 +0200 +Subject: [PATCH] gpo: reject path traversal in gPCFileSysPath +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The gPCFileSysPath LDAP attribute from AD Group Policy Objects is parsed +by ad_gpo_extract_smb_components() which converts backslashes to forward +slashes but does not reject ".." path traversal sequences. The resulting +smb_path is used directly in gpo_cache_store_file() to construct a local +filesystem path under GPO_CACHE_PATH, allowing an attacker with GPO +write access to write files outside the cache directory. + +Due to differential path resolution between libsmbclient (which clamps +".." at the SMB share root) and the kernel (which resolves ".." fully), +the SMB download succeeds while the local file write escapes the cache. +On systems with SELinux enforcing, this enables Kerberos configuration +injection via /var/lib/sss/pubconf/krb5.include.d/ (sssd_public_t, +writable by sssd_t). On systems without SELinux, this enables arbitrary +file writes including cron job injection for root code execution. + +This patch adds two layers of defense: + +1. Reject ".." as a path component in smb_path at parse time in + ad_gpo_extract_smb_components(). Uses component-aware validation + that checks for "/..", "../", and exact ".." — not substring matching + which would false-positive on legitimate names containing "..". + +2. Validate the resolved cache path stays within GPO_CACHE_PATH in + gpo_cache_store_file() using realpath(), with a trailing-slash + prefix check to prevent prefix-collision attacks (e.g., + /var/lib/sss/gpo_cache_evil/ matching /var/lib/sss/gpo_cache). + +Based on the patch by: Ian Murphy +Amended by: Alexey Tikhonov + +Fixes: CVE-2026-14476 + + +--- + src/providers/ad/ad_gpo.c | 47 ++++++++++++++++++++++++++++++++++++++ + src/providers/ad/ad_gpo_child.c | 48 +++++++++++++++++++++++++++++++++++++++ + 2 files changed, 95 insertions(+) + +diff --git a/src/providers/ad/ad_gpo.c b/src/providers/ad/ad_gpo.c +index 94959c3..08b79dc 100644 +--- a/src/providers/ad/ad_gpo.c ++++ b/src/providers/ad/ad_gpo.c +@@ -3725,6 +3725,43 @@ ad_gpo_populate_candidate_gpos(TALLOC_CTX *mem_ctx, + return ret; + } + ++/* ++ * Check whether a path contains ".." as a path component. ++ * Returns true if traversal is detected, false if the path is safe. ++ * ++ * Checks for: ++ * - "/.." anywhere in the path (component starting with ..) ++ * - "../" at the start of the path ++ * - exact match ".." (path is just "..") ++ * - "/.." at the end of the path ++ * ++ * Does NOT match ".." as a substring of a longer component ++ * (e.g., "my..file" is allowed). ++ */ ++static bool gpo_path_has_traversal(const char *path) ++{ ++ const char *p; ++ ++ if (path == NULL) { ++ return false; ++ } ++ ++ /* Exact match */ ++ if (strcmp(path, "..") == 0) return true; ++ ++ /* Starts with ../ */ ++ if (strncmp(path, "../", 3) == 0) return true; ++ ++ /* Contains /../ or ends with /.. */ ++ p = path; ++ while ((p = strstr(p, "/..")) != NULL) { ++ if (p[3] == '/' || p[3] == '\0') return true; ++ p += 3; ++ } ++ ++ return false; ++} ++ + /* + * This function parses the input_path into its components, replaces each + * back slash ('\') with a forward slash ('/'), and populates the output params. +@@ -3801,6 +3838,16 @@ ad_gpo_extract_smb_components(TALLOC_CTX *mem_ctx, + goto done; + } + ++ /* Reject path traversal. See function comment for what is matched. */ ++ if (gpo_path_has_traversal(smb_path)) { ++ DEBUG(SSSDBG_CRIT_FAILURE, ++ "gPCFileSysPath contains path traversal component '..': " ++ "[%s]. Rejecting to prevent cache directory escape.\n", ++ smb_path); ++ ret = EINVAL; ++ goto done; ++ } ++ + *_smb_server = talloc_asprintf(mem_ctx, "%s%s", + SMB_STANDARD_URI, + server_hostname); +diff --git a/src/providers/ad/ad_gpo_child.c b/src/providers/ad/ad_gpo_child.c +index 2f2807b..445d977 100644 +--- a/src/providers/ad/ad_gpo_child.c ++++ b/src/providers/ad/ad_gpo_child.c +@@ -320,6 +320,54 @@ static errno_t gpo_cache_store_file(const char *smb_path, + goto done; + } + ++ /* Defense-in-depth: verify the resolved path stays within the cache ++ * directory. This catches any bypass of the ".." check in the parser, ++ * including encoding tricks, symlink attacks, or future regressions. ++ * ++ * The trailing-slash comparison prevents prefix-collision attacks: ++ * without it, a path resolving to "/var/lib/sss/gpo_cache_evil/" ++ * would incorrectly match the prefix "/var/lib/sss/gpo_cache". ++ */ ++ { ++ char *resolved = realpath(filename, NULL); ++ if (resolved != NULL) { ++ /* Resolve GPO_CACHE_PATH too so the comparison works ++ * even when the cache path contains symlinks. */ ++ char *resolved_cache = realpath(GPO_CACHE_PATH, NULL); ++ if (resolved_cache == NULL) { ++ ret = errno; ++ DEBUG(SSSDBG_CRIT_FAILURE, ++ "realpath(\"%s\") failed: [%d][%s]\n", ++ GPO_CACHE_PATH, ret, strerror(ret)); ++ free(resolved); ++ goto done; ++ } ++ ++ /* Check that resolved path starts with resolved cache + "/" */ ++ size_t cache_len = strlen(resolved_cache); ++ bool inside = ((strlen(resolved) >= cache_len) && ++ (strncmp(resolved, resolved_cache, cache_len) == 0) && ++ (resolved[cache_len] == '/' || resolved[cache_len] == '\0')); ++ if (!inside) { ++ DEBUG(SSSDBG_CRIT_FAILURE, ++ "GPO cache path escapes cache directory: [%s] " ++ "resolves to [%s] which is outside [%s]. " ++ "Rejecting.\n", ++ filename, resolved, resolved_cache); ++ free(resolved_cache); ++ free(resolved); ++ ret = EINVAL; ++ goto done; ++ } ++ free(resolved_cache); ++ free(resolved); ++ } ++ /* If realpath returns NULL, the path doesn't exist yet. ++ * prepare_gpo_cache() will create it — the mkdir calls ++ * are validated by SELinux MAC policy. ++ */ ++ } ++ + tmp_name = talloc_asprintf(tmp_ctx, "%sXXXXXX", filename); + if (tmp_name == NULL) { + DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n"); diff --git a/sssd.spec b/sssd.spec index 6f24b1c..824eb7c 100644 --- a/sssd.spec +++ b/sssd.spec @@ -8,7 +8,7 @@ Summary: System Security Services Daemon Name: sssd Version: 2.9.4 -Release: 7%{?dist} +Release: 8%{?dist} License: GPLv3+ URL: https://github.com/SSSD/sssd/ Source0: https://github.com/SSSD/sssd/releases/download/%{version}/%{name}-%{version}.tar.gz @@ -19,6 +19,8 @@ Patch0001: ad-gpo-use-hash-to-store-intermediate-results.patch Patch0002: krb5-disable-Kerberos-localauth-an2ln-plugin.patch # Fix OAuth2 code verification logic errors Patch0003: sssd-2.9.4-krb5-fix-logic-errors-in-oauth2-code-ver-3eaaefc.patch +# Fix CVE-2026-14476 +Patch0004: sssd-2.9.4-CVE-2026-14476.patch BuildRequires: libtool bind-utils c-ares-devel check-devel cifs-utils-devel BuildRequires: docbook-style-xsl doxygen gettext-devel dbus-devel krb5-devel @@ -875,6 +877,10 @@ fi %{_datadir}/sssd/krb5-snippets/sssd_enable_passkey %changelog +* Wed Aug 05 2026 PkgAgent Robot - 2.9.4-8 +- [Type] security +- [DESC] Fix CVE-2026-14476: reject path traversal in gPCFileSysPath in AD GPO provider + * Mon Jul 27 2026 PkgAgent Robot - 2.9.4-7 - [Type] security - [DESC] Fix logic errors in OAuth2 code verification (krb5) -- Gitee