Rebuild SSSD 2.13.1-2 for RHEL10.3 - Update 2

- Resolves: RHEL-190529 CVE-2026-12610 sssd: Use-after-free crash in SSSD' 'sssd_pam' process
- Resolves: RHEL-185008 Regression in IPA + Idp authentication with keycloak
- Resolves: RHEL-111571 'sssctl config-check' does not work correctly for "Accessing AD with a MSA"
- Resolves: RHEL-192054 CVE-2026-14474 sssd: sudo ldap provider searches entire directory tree for sudorole objects by default, enabling privilege escalation
- Resolves: RHEL-192069 CVE-2026-14476 sssd: path traversal in SSSD AD GPO provider allows writing files outside GPO cache directory as root

Signed-off-by: Madhuri Upadhye <mupadhye@redhat.com>
This commit is contained in:
Madhuri Upadhye 2026-07-02 16:08:53 +05:30
parent bad5956bb7
commit 12e41ae937
6 changed files with 472 additions and 1 deletions

View File

@ -0,0 +1,111 @@
From f2c69b916f5fe53a930aa39c2078b248b83bc2b4 Mon Sep 17 00:00:00 2001
From: Alexey Tikhonov <atikhono@redhat.com>
Date: Mon, 29 Jun 2026 21:46:46 +0200
Subject: [PATCH] PAM: fix use-after-free during p11_child processing
`pam_check_cert_send()` stored `pctx->sss_certmap_ctx` in the
request state. If `p11_refresh_certmap_ctx()` ran while `p11_child`
was still executing (e.g. triggered by a domain refresh), it freed
and replaced the certmap context, leaving the request state holding
a dangling pointer. `p11_child_done()` could later use that pointer.
Fix this by passing the `pam_ctx` into `pam_check_cert_send()` and
dereferencing `pctx->sss_certmap_ctx` at the time it is actually needed
in `p11_child_done()`, so the current context is always used.
Resolves: https://github.com/SSSD/sssd/issues/8796
Fixes: CVE-2026-12610
Assisted-By: Claude Code (Opus 4.6)
Reviewed-by: Iker Pedrosa <ipedrosa@redhat.com>
Reviewed-by: Sumit Bose <sbose@redhat.com>
(cherry picked from commit fa7a55949a30fed064a28ea6f0c801fc5e8c5ba7)
---
src/responder/pam/pamsrv.h | 2 +-
src/responder/pam/pamsrv_cmd.c | 2 +-
src/responder/pam/pamsrv_p11.c | 11 ++++++-----
3 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/src/responder/pam/pamsrv.h b/src/responder/pam/pamsrv.h
index 694b391bb..162bfe1a4 100644
--- a/src/responder/pam/pamsrv.h
+++ b/src/responder/pam/pamsrv.h
@@ -153,7 +153,7 @@ struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx,
const char *ca_db,
time_t timeout,
const char *verify_opts,
- struct sss_certmap_ctx *sss_certmap_ctx,
+ struct pam_ctx *pctx,
const char *uri,
struct pam_data *pd);
errno_t pam_check_cert_recv(struct tevent_req *req, TALLOC_CTX *mem_ctx,
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index cd003ff46..7fadf647d 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -1945,7 +1945,7 @@ static errno_t check_cert(TALLOC_CTX *mctx,
req = pam_check_cert_send(mctx, ev,
pctx->ca_db, p11_child_timeout,
- cert_verification_opts, pctx->sss_certmap_ctx,
+ cert_verification_opts, pctx,
uri, pd);
if (req == NULL) {
DEBUG(SSSDBG_OP_FAILURE, "pam_check_cert_send failed.\n");
diff --git a/src/responder/pam/pamsrv_p11.c b/src/responder/pam/pamsrv_p11.c
index 191bf40c5..29a6edc05 100644
--- a/src/responder/pam/pamsrv_p11.c
+++ b/src/responder/pam/pamsrv_p11.c
@@ -749,7 +749,7 @@ done:
struct pam_check_cert_state {
struct tevent_context *ev;
- struct sss_certmap_ctx *sss_certmap_ctx;
+ struct pam_ctx *pctx;
struct child_io_fds *io;
struct cert_auth_info *cert_list;
struct pam_data *pam_data;
@@ -763,7 +763,7 @@ struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx,
const char *ca_db,
time_t timeout,
const char *verify_opts,
- struct sss_certmap_ctx *sss_certmap_ctx,
+ struct pam_ctx *pctx,
const char *uri,
struct pam_data *pd)
{
@@ -791,11 +791,12 @@ struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx,
goto done;
}
- if (sss_certmap_ctx == NULL) {
+ if (pctx == NULL || pctx->sss_certmap_ctx == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "Missing certificate matching context.\n");
ret = EINVAL;
goto done;
}
+ state->pctx = pctx;
state->pam_data = pd;
@@ -880,7 +881,6 @@ struct tevent_req *pam_check_cert_send(TALLOC_CTX *mem_ctx,
}
state->ev = ev;
- state->sss_certmap_ctx = sss_certmap_ctx;
ret = sss_child_start(state, ev,
P11_CHILD_PATH, extra_args, false,
@@ -985,7 +985,8 @@ static void p11_child_done(struct tevent_req *subreq)
FD_CLOSE(state->io->read_from_child_fd);
- ret = parse_p11_child_response(state, buf, buf_len, state->sss_certmap_ctx,
+ ret = parse_p11_child_response(state, buf, buf_len,
+ state->pctx->sss_certmap_ctx,
&state->cert_list);
if (ret != EOK) {
if (ret == ERR_P11_PIN_LOCKED) {
--
2.54.0

View File

@ -0,0 +1,54 @@
From 9884f33477b3e280f37c41465eb28260fea8522c Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose@redhat.com>
Date: Mon, 15 Jun 2026 17:42:29 +0200
Subject: [PATCH] oidc_child: change default with no auth method
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Before the new client-auth-method option was added oidc_child was
accepting a client or nothing in case the IdP client is a public one.
With the introduction of the client-auth-method option the default if
not option was given was set the 'secret' which cause a regression for
public clients.
With this patch the old default behavior, accepting a secret or nothing,
is restored.
Reviewed-by: Alexey Tikhonov <atikhono@redhat.com>
Reviewed-by: Pavel Březina <pbrezina@redhat.com>
(cherry picked from commit 10bad8b1d74b53a90e10f0a7a74565d4272a01bf)
---
src/oidc_child/oidc_child.c | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/oidc_child/oidc_child.c b/src/oidc_child/oidc_child.c
index 8aa7f7ea4..15e88d4da 100644
--- a/src/oidc_child/oidc_child.c
+++ b/src/oidc_child/oidc_child.c
@@ -410,7 +410,11 @@ static bool set_client_auth_method(const char *tmp_cam, struct cli_opts *opts)
return false;
}
} else {
- opts->client_auth_method = CAM_SECRET;
+ if (has_creds_client_secret(opts)) {
+ opts->client_auth_method = CAM_SECRET;
+ } else {
+ opts->client_auth_method = CAM_NONE;
+ }
}
switch (opts->client_auth_method) {
@@ -502,7 +506,8 @@ static int parse_cli(int argc, const char *argv[], struct cli_opts *opts)
{"pkcs12-client-creds", 0, POPT_ARG_STRING, &opts->pkcs12_client_creds, 0,
_("Client certificate and key in PKCS#12 format"), NULL},
{"client-auth-method", 0, POPT_ARG_STRING, &tmp_cam, 0,
- _("Authentication method against IdP [secret (default), mtls, jwt, none]"),
+ _("Authentication method against IdP [secret, mtls, jwt, none] "
+ "(default: secret if client secret given, none otherwise)"),
NULL},
{"ca-db", 0, POPT_ARG_STRING, &opts->ca_db, 0,
_("Path to PEM file with CA certificates"), NULL},
--
2.54.0

View File

@ -0,0 +1,37 @@
From f0a3dbe607079d3501061dc8b60822a478dddfa3 Mon Sep 17 00:00:00 2001
From: Alexey Tikhonov <atikhono@redhat.com>
Date: Tue, 30 Jun 2026 20:30:24 +0200
Subject: [PATCH] cfg_rules: allow
ldap_sasl_authid/ldap_krb5_keytab/krb5_keytab
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
in subdomain section.
Note that there is a bug: those values are overwritten by domain's
`krb5_keytab` option. But "by design" they are supposed to work.
Reviewed-by: Tomáš Halman <thalman@redhat.com>
(cherry picked from commit 27651161b625b9c2d748686893302b19df59750d)
---
src/config/cfg_rules.ini | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini
index 4ef0d0156..3d79f4374 100644
--- a/src/config/cfg_rules.ini
+++ b/src/config/cfg_rules.ini
@@ -831,6 +831,9 @@ option = ldap_group_search_base
option = ldap_netgroup_search_base
option = ldap_service_search_base
option = ldap_sasl_mech
+option = ldap_sasl_authid
+option = ldap_krb5_keytab
+option = krb5_keytab
option = ad_server
option = ad_backup_server
option = ad_site
--
2.54.0

View File

@ -0,0 +1,176 @@
From 25bacdb4ad096bb753192da776c1b7bb9900710c Mon Sep 17 00:00:00 2001
From: Alexey Tikhonov <atikhono@redhat.com>
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 <imurphy@redhat.com>
Amended by: Alexey Tikhonov <atikhono@redhat.com>
:fixes: CVE-2026-14476
Reviewed-by: Sumit Bose <sbose@redhat.com>
Reviewed-by: Tomáš Halman <thalman@redhat.com>
(cherry picked from commit ba207eab76ff5253662a763b9b6e9ea42f03d31b)
---
src/providers/ad/ad_gpo.c | 47 +++++++++++++++++++++++++++++++
src/providers/ad/ad_gpo_child.c | 49 +++++++++++++++++++++++++++++++++
2 files changed, 96 insertions(+)
diff --git a/src/providers/ad/ad_gpo.c b/src/providers/ad/ad_gpo.c
index 5c2f117a5..b8562edc5 100644
--- a/src/providers/ad/ad_gpo.c
+++ b/src/providers/ad/ad_gpo.c
@@ -3832,6 +3832,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.
@@ -3908,6 +3945,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 b8b47b09e..db3716fdf 100644
--- a/src/providers/ad/ad_gpo_child.c
+++ b/src/providers/ad/ad_gpo_child.c
@@ -322,6 +322,55 @@ 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 (when updating existing files). 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");
--
2.54.0

View File

@ -0,0 +1,81 @@
From c29afbf02acf9e235eb5283f0325ea0037c1574a Mon Sep 17 00:00:00 2001
From: Alexey Tikhonov <atikhono@redhat.com>
Date: Fri, 3 Jul 2026 13:25:08 +0200
Subject: [PATCH] sudo: warn when ldap_sudo_search_base falls back to root DN
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
When ldap_sudo_search_base is not explicitly configured, SSSD falls back
to the domain's naming context (root DN) and searches the entire LDAP
directory tree for sudoRole objects. Any LDAP principal with write access
to any subtree can inject a sudoRole granting arbitrary sudo privileges
on every enrolled host.
This patch adds a warning log when the fallback occurs, alerting
administrators that their configuration searches the entire directory
tree for sudo rules. A future hardening step would be to default to
ou=sudoers,<base_dn> instead of the root DN.
The warning approach preserves backwards compatibility while ensuring
administrators are aware of the security implications.
Based on the patch by: Ian Murphy <imurphy@redhat.com>
Amended by: Alexey Tikhonov <atikhono@redhat.com>
:fixes: CVE-2026-14474
Reviewed-by: Sumit Bose <sbose@redhat.com>
Reviewed-by: Tomáš Halman <thalman@redhat.com>
(cherry picked from commit ff8c1b19bcdbf79b733b052a7d926bd920b1205d)
---
src/providers/ldap/sdap.c | 19 +++++++++++++++++++
src/tests/system/tests/test_ldap.py | 2 +-
2 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/src/providers/ldap/sdap.c b/src/providers/ldap/sdap.c
index 1ce1d704b..7aa9244aa 100644
--- a/src/providers/ldap/sdap.c
+++ b/src/providers/ldap/sdap.c
@@ -1341,6 +1341,25 @@ errno_t sdap_set_config_options_with_rootdse(struct sysdb_attrs *rootdse,
/* Sudo */
if (!sdom->sudo_search_bases) {
+ /* At some point make this option mandatory,
+ * i.e. disable sudo rules lookup if 'sudo_search_bases' not set.
+ */
+ DEBUG(SSSDBG_IMPORTANT_INFO,
+ "`ldap_sudo_search_base` is not set. SSSD will search the entire "
+ "directory tree (%s) for sudoRole objects. This may allow any "
+ "LDAP principal with write access to any subtree to inject "
+ "sudo rules granting arbitrary privileges. Set "
+ "`ldap_sudo_search_base` to restrict the search scope "
+ "(e.g., 'ou=sudoers,dc=example,dc=com').\n",
+ sdom->naming_context);
+ sss_log(SSS_LOG_ALERT,
+ "`ldap_sudo_search_base` is not set. SSSD will search the entire "
+ "directory tree (%s) for sudoRole objects. This may allow any "
+ "LDAP principal with write access to any subtree to inject "
+ "sudo rules granting arbitrary privileges. Set "
+ "`ldap_sudo_search_base` to restrict the search scope "
+ "(e.g., 'ou=sudoers,dc=example,dc=com').",
+ sdom->naming_context);
ret = sdap_set_search_base(opts, sdom,
SDAP_SUDO_SEARCH_BASE,
sdom->naming_context);
diff --git a/src/tests/system/tests/test_ldap.py b/src/tests/system/tests/test_ldap.py
index 9e21d73f6..36d988475 100644
--- a/src/tests/system/tests/test_ldap.py
+++ b/src/tests/system/tests/test_ldap.py
@@ -245,7 +245,7 @@ def test_ldap__search_base_is_discovered_and_defaults_to_root_dse(client: Client
client.sssd.dom("test")["ldap_search_base"] = ldap.ldap.naming_context
client.sssd.stop()
- client.sssd.clear()
+ client.sssd.clear(logs=True)
client.sssd.start()
assert client.auth.ssh.password("puser1", "Secret123"), "User 'puser1' login failed!"
--
2.54.0

View File

@ -21,7 +21,7 @@
Name: sssd
Version: 2.13.1
Release: 1%{?dist}
Release: 2%{?dist}
Summary: System Security Services Daemon
License: GPL-3.0-or-later
URL: https://github.com/SSSD/sssd/
@ -29,6 +29,11 @@ Source0: https://github.com/SSSD/sssd/releases/download/%{version}/sssd-%{versio
Source1: sssd.sysusers
### Patches ###
Patch1: 0001-pam-fix-use-after-free-during-p11_child-processing.patch
Patch2: 0002-oidc_child-change-default-with-no-auth-method.patch
Patch3: 0003-cfg_rules-allow-ldap_sasl_authid-in-subdomain.patch
Patch4: 0004-gpo-reject-path-traversal-in-gPCFileSysPath.patch
Patch5: 0005-sudo-warn-when-ldap_sudo_search_base-falls-back-to-root-DN.patch
### Dependencies ###
@ -1087,6 +1092,13 @@ fi
%systemd_postun_with_restart sssd.service
%changelog
* Thu Jul 2 2026 Madhuri Upadhye <mupadhye@redhat.com> - 2.13.1-2
- Resolves: RHEL-190529 CVE-2026-12610 sssd: Use-after-free crash in SSSD' 'sssd_pam' process
- Resolves: RHEL-185008 Regression in IPA + Idp authentication with keycloak
- Resolves: RHEL-111571 'sssctl config-check' does not work correctly for "Accessing AD with a MSA"
- Resolves: RHEL-192054 CVE-2026-14474 sssd: sudo ldap provider searches entire directory tree for sudorole objects by default, enabling privilege escalation
- Resolves: RHEL-192069 CVE-2026-14476 sssd: path traversal in SSSD AD GPO provider allows writing files outside GPO cache directory as root
* Wed Jun 10 2026 Sumit Bose <sbose@redhat.com> - 2.13.1-1
- Resolves: RHEL-183331 Rebase SSSD for RHEL 10.3
- Resolves: RHEL-178180 'sssctl analyze --help' shows incorrect command in usage field