Version in base suite: 7.4-1 Base version: cifs-utils_7.4-1 Target version: cifs-utils_7.4-1+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/c/cifs-utils/cifs-utils_7.4-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/c/cifs-utils/cifs-utils_7.4-1+deb13u1.dsc changelog | 12 patches/cifs.upcall-remove-getpwuid-dependency.patch | 252 ++++++++++ patches/mount.cifs-reject-option-delimiters-in-username-and-.patch | 95 +++ patches/mount.cifs-validate-username-derived-from-the-passwd.patch | 54 ++ patches/series | 3 5 files changed, 416 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpxllj2epf/cifs-utils_7.4-1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpxllj2epf/cifs-utils_7.4-1+deb13u1.dsc: no acceptable signature found diff -Nru cifs-utils-7.4/debian/changelog cifs-utils-7.4/debian/changelog --- cifs-utils-7.4/debian/changelog 2025-06-12 19:32:49.000000000 +0000 +++ cifs-utils-7.4/debian/changelog 2026-10-14 11:18:53.000000000 +0000 @@ -1,3 +1,15 @@ +cifs-utils (2:7.4-1+deb13u1) trixie-security; urgency=high + + * Non-maintainer upload by the Security Team. + * cifs.upcall: remove getpwuid() dependency (CVE-2026-12505) + (Closes: #1140422) + * mount.cifs: reject option delimiters in username and domain + (CVE-2026-104715) + * mount.cifs: validate username derived from the passwd database + (CVE-2026-104715) + + -- Salvatore Bonaccorso Wed, 14 Oct 2026 13:18:53 +0200 + cifs-utils (2:7.4-1) unstable; urgency=medium * new upstream release diff -Nru cifs-utils-7.4/debian/patches/cifs.upcall-remove-getpwuid-dependency.patch cifs-utils-7.4/debian/patches/cifs.upcall-remove-getpwuid-dependency.patch --- cifs-utils-7.4/debian/patches/cifs.upcall-remove-getpwuid-dependency.patch 1970-01-01 00:00:00.000000000 +0000 +++ cifs-utils-7.4/debian/patches/cifs.upcall-remove-getpwuid-dependency.patch 2026-10-14 10:22:25.000000000 +0000 @@ -0,0 +1,252 @@ +From: Enzo Matsumiya +Date: Mon, 15 Jun 2026 14:25:22 -0300 +Subject: cifs.upcall: remove getpwuid() dependency +Origin: https://git.samba.org/?p=cifs-utils.git;a=commit;h=972c5b5ff95e3e812bc8daa72d0383654ab0dba7 +Bug-Debian: https://bugs.debian.org/1140422 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2026-12505 + +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 | 168 +++++++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 139 insertions(+), 29 deletions(-) + +diff --git a/cifs.upcall.c b/cifs.upcall.c +index 9e05ac1596ae..01690dfcade1 100644 +--- a/cifs.upcall.c ++++ b/cifs.upcall.c +@@ -51,7 +51,6 @@ + #include + #include + #include +-#include + #include + #include + #include +@@ -1376,6 +1375,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) +@@ -1419,10 +1533,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; + +@@ -1516,6 +1630,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) { +@@ -1575,47 +1700,32 @@ int main(const int argc, char *const argv[]) + syslog(LOG_INFO, "upcall_target=mount, not switching namespaces to application thread"); + } + +- + /* +- * 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. ++ * 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 ++ * variable left intact. + */ +- 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; +- } ++ if (uid == 0) ++ env_probe = false; + + /* +- * The kernel should send down a zero-length grouplist already, but +- * just to be on the safe side... ++ * 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 = setgroups(0, NULL); +- if (rc == -1) { +- syslog(LOG_ERR, "setgroups: %s", strerror(errno)); ++ rc = get_uidgid(arg->pid, &uid, &gid); ++ if (rc) { ++ syslog(LOG_ERR, "get_uidgid (NS): %s", strerror(errno)); + rc = 1; + goto out; + } + +- rc = setgid(pw->pw_gid); +- if (rc == -1) { ++ rc = setgid(gid); ++ if (rc) { + 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 +- * variable left intact. +- */ +- if (uid == 0) +- env_probe = false; +- + /* + * Must do this before setuid, as we need elevated capabilities to + * look at the environ file. +-- +2.55.0 + diff -Nru cifs-utils-7.4/debian/patches/mount.cifs-reject-option-delimiters-in-username-and-.patch cifs-utils-7.4/debian/patches/mount.cifs-reject-option-delimiters-in-username-and-.patch --- cifs-utils-7.4/debian/patches/mount.cifs-reject-option-delimiters-in-username-and-.patch 1970-01-01 00:00:00.000000000 +0000 +++ cifs-utils-7.4/debian/patches/mount.cifs-reject-option-delimiters-in-username-and-.patch 2026-10-14 11:17:10.000000000 +0000 @@ -0,0 +1,95 @@ +From efaa327c69c7570cda1211de295190ce7a628dc3 Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +Date: Thu, 17 Sep 2026 14:08:50 -0300 +Subject: [PATCH 1/2] mount.cifs: reject option delimiters in username and + domain + +The username= and domain= values from a credentials file, and the USER +environment variable, are copied verbatim into the kernel mount option +string as user=/domain=. The kernel option parser is +comma separated and last-write-wins, so a value like + + username=smbuser,uid=0,forceuid,noperm + +injects further options past mount.cifs's uid=/gid= sanitizer, yielding +a root-owned mount with permission checks disabled. A caller that can +control the credentials file (e.g. pam_mount) thus gains local root. + +Reject username and domain values containing an option delimiter (comma, +equals, CR or LF) at the two untokenized inputs. Command-line +-o user=/domain= is already split on commas, and passwords are left +alone as they may legitimately contain these characters. + +Reported-by: Johnathan Benge +Fixes: c610039ef674 ("mount.cifs: add username and domain fields to parsed_mount_info") +Reviewed-by: Bharath SM +Reviewed-by: Shyam Prasad N +Signed-off-by: Paulo Alcantara +--- + mount.cifs.c | 25 +++++++++++++++++++++++-- + 1 file changed, 23 insertions(+), 2 deletions(-) + +diff --git a/mount.cifs.c b/mount.cifs.c +index f0e9bc7beae2..72beae874209 100644 +--- a/mount.cifs.c ++++ b/mount.cifs.c +@@ -588,6 +588,11 @@ parsing_err: + return CRED_UNPARSEABLE; + } + ++static int check_creds_field(const char *v) ++{ ++ return strpbrk(v, ",=\r\n") != NULL; ++} ++ + static int open_cred_file(char *file_name, + struct parsed_mount_info *parsed_info) + { +@@ -638,6 +643,11 @@ static int open_cred_file(char *file_name, + /* parse next token */ + switch (parse_cred_line(line_buf + i, &temp_val)) { + case CRED_USER: ++ if (check_creds_field(temp_val)) { ++ fprintf(stderr, "Invalid username in credentials file\n"); ++ i = EX_USAGE; ++ goto return_i; ++ } + strlcpy(parsed_info->username, temp_val, + sizeof(parsed_info->username)); + parsed_info->got_user = 1; +@@ -653,6 +663,11 @@ static int open_cred_file(char *file_name, + goto return_i; + break; + case CRED_DOM: ++ if (check_creds_field(temp_val)) { ++ fprintf(stderr, "Invalid domain in credentials file\n"); ++ i = EX_USAGE; ++ goto return_i; ++ } + strlcpy(parsed_info->domain, temp_val, + sizeof(parsed_info->domain)); + break; +@@ -2006,12 +2021,18 @@ assemble_mountinfo(struct parsed_mount_info *parsed_info, + * USER env variable (ie user%password form) as there is + * already a PASSWD environment varaible + */ +- if (getenv("USER")) ++ if (getenv("USER")) { ++ if (check_creds_field(getenv("USER"))) { ++ fprintf(stderr, "Invalid username in USER environment variable\n"); ++ rc = EX_USAGE; ++ goto assemble_exit; ++ } + strlcpy(parsed_info->username, getenv("USER"), + sizeof(parsed_info->username)); +- else ++ } else { + strlcpy(parsed_info->username, getusername(getuid()), + sizeof(parsed_info->username)); ++ } + parsed_info->got_user = 1; + } + +-- +2.55.0 + diff -Nru cifs-utils-7.4/debian/patches/mount.cifs-validate-username-derived-from-the-passwd.patch cifs-utils-7.4/debian/patches/mount.cifs-validate-username-derived-from-the-passwd.patch --- cifs-utils-7.4/debian/patches/mount.cifs-validate-username-derived-from-the-passwd.patch 1970-01-01 00:00:00.000000000 +0000 +++ cifs-utils-7.4/debian/patches/mount.cifs-validate-username-derived-from-the-passwd.patch 2026-10-14 11:17:57.000000000 +0000 @@ -0,0 +1,54 @@ +From 1bb7cb8af3bda83d220678455611b931c0085765 Mon Sep 17 00:00:00 2001 +From: Paulo Alcantara +Date: Thu, 17 Sep 2026 14:49:39 -0300 +Subject: [PATCH 2/2] mount.cifs: validate username derived from the passwd + database + +When neither the credentials file nor the USER environment variable +supplies a username, assemble_mountinfo() falls back to the caller's +passwd login name via getusername(getuid()) and copies it verbatim into +the "user=" mount option, just like the other two sources. Run it +through check_creds_field() as well so the invariant "the username never +contains an option delimiter" holds regardless of where the username +came from. Unlike the credentials file and USER env, this value is not +attacker-controlled, so this is defense in depth rather than a fix for +the injection itself. + +While here, guard against getusername() returning NULL (no passwd entry +for the uid), which was previously passed straight into strlcpy(). + +Reviewed-by: Bharath SM +Reviewed-by: Shyam Prasad N +Signed-off-by: Paulo Alcantara +--- + mount.cifs.c | 14 ++++++++++++-- + 1 file changed, 12 insertions(+), 2 deletions(-) + +diff --git a/mount.cifs.c b/mount.cifs.c +index 72beae874209..671d58fc919b 100644 +--- a/mount.cifs.c ++++ b/mount.cifs.c +@@ -2030,8 +2030,18 @@ assemble_mountinfo(struct parsed_mount_info *parsed_info, + strlcpy(parsed_info->username, getenv("USER"), + sizeof(parsed_info->username)); + } else { +- strlcpy(parsed_info->username, getusername(getuid()), +- sizeof(parsed_info->username)); ++ char *username = getusername(getuid()); ++ ++ if (username) { ++ if (check_creds_field(username)) { ++ fprintf(stderr, "Invalid username for uid %u\n", ++ (unsigned int)getuid()); ++ rc = EX_USAGE; ++ goto assemble_exit; ++ } ++ strlcpy(parsed_info->username, username, ++ sizeof(parsed_info->username)); ++ } + } + parsed_info->got_user = 1; + } +-- +2.55.0 + diff -Nru cifs-utils-7.4/debian/patches/series cifs-utils-7.4/debian/patches/series --- cifs-utils-7.4/debian/patches/series 2025-05-14 09:39:10.000000000 +0000 +++ cifs-utils-7.4/debian/patches/series 2026-10-14 11:17:57.000000000 +0000 @@ -1,2 +1,5 @@ 0001-Fix-fPIE-casing.patch spelling.patch +cifs.upcall-remove-getpwuid-dependency.patch +mount.cifs-reject-option-delimiters-in-username-and-.patch +mount.cifs-validate-username-derived-from-the-passwd.patch