public inbox for git-commits@fedoraproject.org
help / color / mirror / Atom feed
From: Sumit Bose <sbose@redhat.com>
To: git-commits@fedoraproject.org
Subject: [rpms/sssd] f44: CVE fixes:
Date: Thu, 09 Jul 2026 15:26:14 GMT	[thread overview]
Message-ID: <178361077426.1.11757331729723042766.rpms-sssd-b2dadd5d343d@fedoraproject.org> (raw)

            A new commit has been pushed.

            Repo   : rpms/sssd
            Branch : f44
            Commit : b2dadd5d343d4b1b3c71562fe5641e74caea45ba
            Author : Sumit Bose <sbose@redhat.com>
            Date   : 2026-07-08T17:43:01+02:00
            Stats  : +368/-0 in 4 file(s)
            URL    : https://src.fedoraproject.org/rpms/sssd/c/b2dadd5d343d4b1b3c71562fe5641e74caea45ba?branch=f44

            Log:
            CVE fixes:

... CVE-2026-12610 CVE-2026-14474 CVE-2026-14476
- rhbz#2494777: CVE-2026-12610 sssd: Use-after-free crash in SSSD' 'sssd_pam' process
- rhbz#2497650: CVE-2026-14476 sssd: sssd: GPO cache path traversal via unsanitized
                gPCFileSysPath allows Kerberos authentication bypass
- rhbz#2497651: CVE-2026-14474 sssd: sssd: sudo LDAP provider searches entire directory
                tree for sudoRole objects by default, enabling privilege escalation

---
diff --git a/0001-PAM-fix-use-after-free-during-p11_child-processing.patch b/0001-PAM-fix-use-after-free-during-p11_child-processing.patch
new file mode 100644
index 0000000..3dbbf3a
--- /dev/null
+++ b/0001-PAM-fix-use-after-free-during-p11_child-processing.patch
@@ -0,0 +1,110 @@
+From fa7a55949a30fed064a28ea6f0c801fc5e8c5ba7 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>
+---
+ 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
+

diff --git a/0002-gpo-reject-path-traversal-in-gPCFileSysPath.patch b/0002-gpo-reject-path-traversal-in-gPCFileSysPath.patch
new file mode 100644
index 0000000..6206dcc
--- /dev/null
+++ b/0002-gpo-reject-path-traversal-in-gPCFileSysPath.patch
@@ -0,0 +1,175 @@
+From ba207eab76ff5253662a763b9b6e9ea42f03d31b 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>
+---
+ 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
+

diff --git a/0003-sudo-warn-when-ldap_sudo_search_base-falls-back-to-r.patch b/0003-sudo-warn-when-ldap_sudo_search_base-falls-back-to-r.patch
new file mode 100644
index 0000000..833c63d
--- /dev/null
+++ b/0003-sudo-warn-when-ldap_sudo_search_base-falls-back-to-r.patch
@@ -0,0 +1,80 @@
+From ff8c1b19bcdbf79b733b052a7d926bd920b1205d 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>
+---
+ 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
+

diff --git a/sssd.spec b/sssd.spec
index a268c6a..d39a6b3 100644
--- a/sssd.spec
+++ b/sssd.spec
@@ -38,6 +38,9 @@ Source3: pubkey.asc
 ### Patches ###
 # Place your patches here:
 # Patch0001:  0001-patch-file.patch
+Patch0001: 0001-PAM-fix-use-after-free-during-p11_child-processing.patch
+Patch0002: 0002-gpo-reject-path-traversal-in-gPCFileSysPath.patch
+Patch0003: 0003-sudo-warn-when-ldap_sudo_search_base-falls-back-to-r.patch
 
 ### Downstream only patches ###
 # Place your downstream only patches here:

                 reply	other threads:[~2026-07-09 15:26 UTC|newest]

Thread overview: [no followups] expand[flat|nested]  mbox.gz  Atom feed

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=178361077426.1.11757331729723042766.rpms-sssd-b2dadd5d343d@fedoraproject.org \
    --to=sbose@redhat.com \
    --cc=git-commits@fedoraproject.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox