From 8a813204d23b0a92ef2ba6482fc4acaae9654ec8 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Thu, 25 Jun 2026 14:17:31 -0300 Subject: [PATCH] Fix local privilege escalation via cifs.spnego - resolves: RHEL-185759 - Fix CVE-2026-12505 Signed-off-by: Paulo Alcantara --- cifs-utils.spec | 11 +- ...pcall-fix-compiler-warning-with-Wvla.patch | 46 ++++ cifs.upcall-remove-getpwuid-dependency.patch | 251 ++++++++++++++++++ 3 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 cifs.upcall-fix-compiler-warning-with-Wvla.patch create mode 100644 cifs.upcall-remove-getpwuid-dependency.patch diff --git a/cifs-utils.spec b/cifs-utils.spec index 3480ae9..aa60e8c 100644 --- a/cifs-utils.spec +++ b/cifs-utils.spec @@ -3,7 +3,7 @@ Name: cifs-utils Version: 7.0 -Release: 3%{pre_release}%{?dist} +Release: 4%{pre_release}%{?dist} Summary: Utilities for mounting and managing CIFS mounts Group: System Environment/Daemons @@ -22,6 +22,8 @@ Patch1: 0001-Use-explicit-usr-bin-python3.patch Patch2: mount.cifs.rst-add-missing-reference-for-sssd.patch Patch3: mount.cifs.rst-update-section-about-xattr-acl-suppor.patch Patch4: docs-update-echo_interval-description.patch +Patch5: cifs.upcall-remove-getpwuid-dependency.patch +Patch6: cifs.upcall-fix-compiler-warning-with-Wvla.patch %description The SMB/CIFS protocol is a standard file sharing protocol widely deployed @@ -60,6 +62,8 @@ provide these credentials to the kernel automatically at login. %patch2 -p1 %patch3 -p1 %patch4 -p1 +%patch5 -p1 +%patch6 -p1 %build autoreconf -i @@ -119,6 +123,11 @@ fi %{_mandir}/man8/pam_cifscreds.8.gz %changelog +* Thu Jun 25 2026 Paulo Alcantara - 7.0-4 +- cifs.upcall: remove getpwuid dependency +- cifs.upcall: fix compiler warning with -Wvla +- Resolves: RHEL-185759 - Fix CVE-2026-12505 + * Thu Feb 19 2026 Paulo Alcantara - 7.0-3 - docs: update echo_interval description - Resolves: RHEL-80397 diff --git a/cifs.upcall-fix-compiler-warning-with-Wvla.patch b/cifs.upcall-fix-compiler-warning-with-Wvla.patch new file mode 100644 index 0000000..5ef0dc1 --- /dev/null +++ b/cifs.upcall-fix-compiler-warning-with-Wvla.patch @@ -0,0 +1,46 @@ +From d2f39a20d68aa55023b63e575f06941721e644c6 Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +Date: Tue, 23 Jun 2026 15:06:20 -0300 +Subject: [PATCH] cifs.upcall: fix compiler warning with -Wvla +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The length value for @path array in get_uidgid() needs to be evaluated +at compile time, so replace strlen() with sizeof() when defining +PROC_PID_PATH_MAXLEN and then fix the following warning: + + cifs.upcall.c: In function ‘get_uidgid’: + cifs.upcall.c:1400:9: warning: ISO C90 forbids array ‘path’ whose size + cannot be evaluated [-Wvla] + 1400 | char path[PROC_PID_PATH_MAXLEN] = {}, buf[256]; + | ^~~~ + +Fixes: 972c5b5ff95e ("cifs.upcall: remove getpwuid() dependency") +Signed-off-by: Paulo Alcantara (Red Hat) +Reviewed-by: David Howells +Cc: Enzo Matsumiya +Cc: linux-cifs@vger.kernel.org +Signed-off-by: Steve French +--- + cifs.upcall.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/cifs.upcall.c b/cifs.upcall.c +index 01690dfcade1..11dbc6186a74 100644 +--- a/cifs.upcall.c ++++ b/cifs.upcall.c +@@ -1375,8 +1375,8 @@ static int ip_to_fqdn(const char *addrstr, char *host, size_t hostlen) + return 0; + } + +-/* cover worst case/impossible scenarios, + 1 for NUL */ +-#define PROC_PID_PATH_MAXLEN ((int)strlen("/proc/2147483647/status") + 1) ++/* cover worst case/impossible scenarios */ ++#define PROC_PID_PATH_MAXLEN ((int)sizeof("/proc/2147483647/status")) + /* max valid UID/GID is (UINT_MAX - 1) */ + #define INVALID_UIDGID UINT_MAX + +-- +2.54.0 + diff --git a/cifs.upcall-remove-getpwuid-dependency.patch b/cifs.upcall-remove-getpwuid-dependency.patch new file mode 100644 index 0000000..6003d03 --- /dev/null +++ b/cifs.upcall-remove-getpwuid-dependency.patch @@ -0,0 +1,251 @@ +From 972c5b5ff95e3e812bc8daa72d0383654ab0dba7 Mon Sep 17 00:00:00 2001 +From: Enzo Matsumiya +Date: Mon, 15 Jun 2026 14:25:22 -0300 +Subject: [PATCH] cifs.upcall: remove getpwuid() dependency + +This patch removes getpwuid() and resort to getting UID/GID from + /proc/pid/status + +Changes: +- add get_uidgid() helper +- drop supplementary groups (i.e. move setgroups(0, NULL)) before even + decoding spnego key -- those are never needed in any case + +Signed-off-by: Enzo Matsumiya +Reported-by: Shaomin Chen +Acked-by: Paulo Alcantara (Red Hat) +Acked-by: Pavel Shilovskiy +Reviewed-by: Shyam Prasad N +Signed-off-by: Steve French +--- + cifs.upcall.c | 180 ++++++++++++++++++++++++++++++++++++++++---------- + 1 file changed, 145 insertions(+), 35 deletions(-) + +diff --git a/cifs.upcall.c b/cifs.upcall.c +index 52c03280dbe0..ae3294dd61f0 100644 +--- a/cifs.upcall.c ++++ b/cifs.upcall.c +@@ -51,7 +51,6 @@ + #include + #include + #include +-#include + #include + #include + #include +@@ -1265,6 +1264,121 @@ static int ip_to_fqdn(const char *addrstr, char *host, size_t hostlen) + return 0; + } + ++/* cover worst case/impossible scenarios, + 1 for NUL */ ++#define PROC_PID_PATH_MAXLEN ((int)strlen("/proc/2147483647/status") + 1) ++/* max valid UID/GID is (UINT_MAX - 1) */ ++#define INVALID_UIDGID UINT_MAX ++ ++/* ++ * get_uidgid - Get @pid's (real) UID and/or GID. ++ * @pid: process to get UID/GID from ++ * @uidp: pointer to store @pid's UID (can be NULL) ++ * @gidp: pointer to store @pid's GID (can be NULL) ++ * ++ * Extract "Uid:" and "Gid:" fields from /proc/@pid/status. ++ * Do so based on whether @uidp or @gidp are NULL. ++ * ++ * This function assumes we're on the same namespace as @pid. ++ * ++ * Return: 0 on success, -1 otherwise (errno set). ++ * ++ * On errors, *@uidp and *@gidp are set to INVALID_UIDGID. ++ */ ++static int get_uidgid(pid_t pid, uid_t *uidp, gid_t *gidp) ++{ ++ char path[PROC_PID_PATH_MAXLEN] = {}, buf[256]; ++ FILE *fp = NULL; ++ int ret; ++ ++ errno = 0; ++ if (pid < 0 || (!uidp && !gidp)) { ++ errno = EINVAL; ++ return -1; ++ } ++ ++ if (uidp) ++ *uidp = INVALID_UIDGID; ++ ++ if (gidp) ++ *gidp = INVALID_UIDGID; ++ ++ ret = snprintf(path, PROC_PID_PATH_MAXLEN, "/proc/%d/status", pid); ++ if (ret < 0 || ret >= PROC_PID_PATH_MAXLEN) { ++ if (!errno) ++ errno = ENAMETOOLONG; ++ return -1; ++ } ++ ++ fp = fopen(path, "r"); ++ if (!fp) { ++ ret = -1; ++ goto out; ++ } ++ ++ /* Parse /proc/pid/status fields */ ++ errno = 0; ++ ret = -1; ++ while (fgets(buf, 256, fp)) { ++ unsigned long long val; ++ ++ errno = ENODATA; ++ if ((!uidp || strncmp(buf, "Uid:", 4)) && (!gidp || strncmp(buf, "Gid:", 4))) ++ continue; ++ ++ errno = 0; ++ ++ /* ++ * Example line format (same for both Uid/Gid): ++ * "Uid:\t%u\t%u\%u\%u" ++ * ++ * Where the numbers represents: ++ * ++ * ++ * We're only interested in the value. ++ * ++ * (field names "Uid:"/"Gid:" parsed above, skip it) ++ */ ++ ret = sscanf(&buf[0] + 4, "%llu", &val); ++ if (ret != 1) { ++ ret = -1; ++ if (errno) ++ break; ++ continue; ++ } ++ ++ ret = -1; ++ if (val >= UINT_MAX) { ++ errno = EINVAL; ++ break; ++ } ++ ++ if (uidp && !strncmp(buf, "Uid:", 4)) ++ *uidp = (uid_t)val; ++ else ++ *gidp = (gid_t)val; ++ ++ if ((!uidp || *uidp != INVALID_UIDGID) && (!gidp || *gidp != INVALID_UIDGID)) { ++ errno = 0; ++ ret = 0; ++ break; ++ } ++ } ++out: ++ if (fp) ++ fclose(fp); ++ ++ if (ret) { ++ syslog(LOG_DEBUG, "%s(pid=%d): %s", __func__, pid, strerror(errno)); ++ if (uidp) ++ *uidp = INVALID_UIDGID; ++ ++ if (gidp) ++ *gidp = INVALID_UIDGID; ++ } ++ ++ return ret; ++} ++ + /* walk a string and lowercase it in-place */ + static void + lowercase_string(char *c) +@@ -1306,10 +1420,10 @@ int main(const int argc, char *const argv[]) + struct decoded_args *arg = NULL; + const char *oid; + uid_t uid; ++ gid_t gid; + char *keytab_name = NULL; + char *env_cachename = NULL; + krb5_ccache ccache = NULL; +- struct passwd *pw; + unsigned expire_time = DNS_RESOLVER_DEFAULT_TIMEOUT; + const char *key_descr = NULL; + +@@ -1396,6 +1510,17 @@ int main(const int argc, char *const argv[]) + * Otherwise, it's a spnego key request + */ + ++ /* ++ * We don't need supplementary groups, ever. ++ * Drop them ASAP. ++ */ ++ rc = setgroups(0, NULL); ++ if (rc == -1) { ++ syslog(LOG_ERR, "setgroups: %s", strerror(errno)); ++ rc = 1; ++ goto out; ++ } ++ + rc = decode_key_description(buf, &arg); + free(buf); + if (rc) { +@@ -1450,38 +1575,6 @@ int main(const int argc, char *const argv[]) + if (trim_capabilities(env_probe)) + goto out; + +- /* +- * The kernel doesn't pass down the gid, so we resort here to scraping +- * one out of the passwd nss db. Note that this might not reflect the +- * actual gid of the process that initiated the upcall. While we could +- * scrape that out of /proc, relying on that is a bit more risky. +- */ +- pw = getpwuid(uid); +- if (!pw) { +- syslog(LOG_ERR, "Unable to find pw entry for uid %d: %s\n", +- uid, strerror(errno)); +- rc = 1; +- goto out; +- } +- +- /* +- * The kernel should send down a zero-length grouplist already, but +- * just to be on the safe side... +- */ +- rc = setgroups(0, NULL); +- if (rc == -1) { +- syslog(LOG_ERR, "setgroups: %s", strerror(errno)); +- rc = 1; +- goto out; +- } +- +- rc = setgid(pw->pw_gid); +- if (rc == -1) { +- syslog(LOG_ERR, "setgid: %s", strerror(errno)); +- rc = 1; +- goto out; +- } +- + /* + * We can't reasonably do this for root. When mounting a DFS share, + * for instance we can end up with creds being overridden, but the env +@@ -1490,6 +1583,24 @@ int main(const int argc, char *const argv[]) + if (uid == 0) + env_probe = false; + ++ /* ++ * FIXME: this only works if we haven't switched PID namespaces. ++ * If we did, /proc/arg->pid/ might not exist, or worse, point to something else. ++ */ ++ rc = get_uidgid(arg->pid, &uid, &gid); ++ if (rc) { ++ syslog(LOG_ERR, "get_uidgid (NS): %s", strerror(errno)); ++ rc = 1; ++ goto out; ++ } ++ ++ rc = setgid(gid); ++ if (rc) { ++ syslog(LOG_ERR, "setgid: %s", strerror(errno)); ++ rc = 1; ++ goto out; ++ } ++ + /* + * Must do this before setuid, as we need elevated capabilities to + * look at the environ file.