Fedora Account System
Red Hat Associate
Red Hat Customer
Disclaimer: Community trackers are created by Red Hat Product Security team on a best effort basis. Package maintainers are required to ascertain if the flaw indeed affects their package, before starting the update process. AI_ONLY_REPORT package: sssd-2.12.0-1.el10 ------ Summary: Unbounded NSS Negative Cache Growth Can Cause Local DoS (Memory Exhaustion): an unprivileged local user can trigger large numbers of distinct negative NSS lookups and drive application-level unbounded growth of the in-memory negative cache, potentially exhausting responder memory and disrupting local name-service availability. Requirements to exploit: Unprivileged local access on a system where the `sssd` NSS responder is active, the responder socket is reachable by local users, and `entry_negative_timeout` is non-zero (default `15`). The attacker must be able to trigger many distinct NSS misses through normal lookup paths. The available evidence does not establish remote exploitability by default. Component affected: `sssd-2.12.0-1.el10` NSS responder negative cache in `src/responder/common/negcache.c` (`sss_ncache_init()`, `sss_ncache_check_str()`, `sss_ncache_set_str()`), with local reachability through `src/responder/nss/nsssrv.c` Version affected: `sssd-2.12.0-1.el10` when the NSS responder is active and `entry_negative_timeout` is non-zero (default `15`) Patch available: no released package fix established; proposed patch included below Version fixed: unknown Upstream coordination: Not notified. CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H - 5.5 (MEDIUM) AV:L - The established attack path requires local access to issue NSS lookups on the host. AC:L - Repeated distinct nonexistent lookups are sufficient; no race or special conditions are required. PR:L - The demonstrated exploit requires an unprivileged local execution context or local account on the system. UI:N - No victim interaction is needed. S:U - The impact remains within the vulnerable component's security scope. C:N - No confidentiality impact is established by the available evidence. I:N - No integrity impact is established by the available evidence. A:H - Responder memory can grow until service becomes unstable or the process is OOM-killed. Impact: Moderate. The established outcome is a practical local denial of service against the NSS responder, not remote compromise, privilege escalation, or data exposure. Under Red Hat's severity guidance, that is more than Low because it can materially disrupt host functionality in active NSS deployments, but below Important because the supported attack is local-only and availability-focused. Embargo: no Reason: The available evidence supports a local denial of service with operational mitigations and no evidence of remote compromise, confidentiality loss, or privilege escalation. This class of issue can usually be fixed and disclosed without a prolonged embargo. Acknowledgement: Aisle Research Vulnerability Details: In `src/responder/common/negcache.c`, the negative cache is created as an in-memory TDB and expired entries are only removed lazily when the same key is checked again: ```c ctx->tdb = tdb_open("memcache", 0, TDB_INTERNAL, O_RDWR|O_CREAT, 0); ... if (expired) { /* expired, remove and return no entry */ tdb_delete(ctx->tdb, key); ret = ENOENT; } ``` New negative entries are inserted through `tdb_store()` with no application-level global size or entry bound shown in this path: ```c ret = tdb_store(ctx->tdb, key, data, TDB_REPLACE); ``` Non-permanent negative caching is enabled by default. In `src/responder/common/responder_common.c`, the responder reads `entry_negative_timeout` with a default of `15`: ```c ret = confdb_get_int(cdb, CONFDB_NSS_CONF_ENTRY, CONFDB_NSS_ENTRY_NEG_TIMEOUT, 15, &tmp_value); ``` Local reachability is realistic when the NSS responder is enabled, because the responder defines a public socket umask: ```c /* Public sockets must be readable and writable by anybody on the system. So we set umask to 0111. */ #define SCKT_RSP_UMASK 0111 ``` Based on the available code and reproduction, many distinct nonexistent lookups can accumulate in memory even though each entry has a timeout, because unique keys are not revisited and therefore do not trigger lazy expiry removal. The supported attack path is through successful "not found" handling in the normal cache-request flow, not through malformed packet delivery. There is an operational clear path for the negative cache, but that is a cleanup mechanism rather than a preventive limit. Steps to reproduce: 1. Ensure the system is using the `sssd` NSS responder and that `entry_negative_timeout` is non-zero. 2. Monitor responder memory usage, for example: ```bash watch -n1 'ps -o pid,rss,cmd -C sssd_nss' ``` 3. As an unprivileged local user, generate a large number of distinct negative NSS lookups: ```bash for i in $(seq 1 500000); do getent passwd no_such_user_$i >/dev/null; done ``` 4. Observe steady RSS growth in `sssd_nss` during and after the run. 5. As a control, clear the negative cache through the existing operational path or send SIGHUP to the monitor; this should remove the accumulated negative-cache state and typically relieve the observed memory pressure. Mitigation: Set `entry_negative_timeout = 0` to disable non-permanent negative-cache insertions. This reduces exposure but increases backend lookup traffic. Use the existing negative-cache clear path or restart the responder if memory growth is observed. Restrict untrusted local users on systems where the NSS responder is exposed and this behavior would be materially disruptive. Proposed Fix: A minimal hardening approach is to opportunistically prune expired entries before insertion and reject new inserts once a configured maximum entry count is reached. ```diff diff --git a/src/responder/common/negcache.c b/src/responder/common/negcache.c ... — a/src/responder/common/negcache.c +++ b/src/responder/common/negcache.c @@ -41,10 +41,15 @@ #define NC_DOMAIN_ACCT_LOCATE_PREFIX NC_ENTRY_PREFIX"DOM_LOCATE" #define NC_DOMAIN_ACCT_LOCATE_TYPE_PREFIX NC_ENTRY_PREFIX"DOM_LOCATE_TYPE" +#define NCACHE_DEFAULT_MAX_ENTRIES 20000 + struct sss_nc_ctx { struct tdb_context *tdb; uint32_t timeout; + uint32_t max_entries; }; +struct ncache_prune_state { time_t now; uint32_t alive; }; + typedef int (*ncache_set_byname_fn_t)(struct sss_nc_ctx *, bool, const char *, const char *); @@ -79,6 +84,7 @@ int sss_ncache_init(TALLOC_CTX *memctx, uint32_t timeout, if (!ctx->tdb) return errno; ctx->timeout = timeout; + ctx->max_entries = NCACHE_DEFAULT_MAX_ENTRIES; *_ctx = ctx; return EOK; @@ -146,6 +152,36 @@ done: return ret; } +static int ncache_prune_expired(struct tdb_context *tdb, + TDB_DATA key, TDB_DATA data, void *pvt) +{ + struct ncache_prune_state *st = talloc_get_type(pvt, struct ncache_prune_state); + unsigned long long ts; + char *ep = NULL; + + errno = 0; + ts = strtoull((const char *)data.dptr, &ep, 10); + if (errno != 0 || *ep != '\0') { + return tdb_delete(tdb, key); + } + + if (ts != 0 && ts < (unsigned long long)st->now) { + return tdb_delete(tdb, key); + } + + st->alive++; + return 0; +} + static int sss_ncache_set_str(struct sss_nc_ctx *ctx, char *str, bool permanent) { + struct ncache_prune_state st = { 0 }; TDB_DATA key; TDB_DATA data; @@ -167,6 +203,18 @@ static int sss_ncache_set_str(struct sss_nc_ctx *ctx, char *str, bool permanent) } if (!timest) return ENOMEM; + st.now = time(NULL); + if (tdb_traverse(ctx->tdb, ncache_prune_expired, &st) < 0) { + return EIO; + } + if (st.alive >= ctx->max_entries) { + DEBUG(SSSDBG_MINOR_FAILURE, + "Negative cache limit reached (%u), dropping [%s]\n", + ctx->max_entries, str); + talloc_free(timest); + return ENOSPC; + } + ret = string_to_tdb_data(timest, &data); if (ret != EOK) goto done; ``` ------ This report was generated using AI technology. Always review AI-generated content prior to use