Dear maintainer,

I've prepared an NMU for cifs-utils (versioned as 2:6.11-3.1). The diff
is attached to this message.

Regards.
-- 
Sebastian Ramacher
diff -Nru cifs-utils-6.11/debian/changelog cifs-utils-6.11/debian/changelog
--- cifs-utils-6.11/debian/changelog	2021-05-06 21:24:29.000000000 +0200
+++ cifs-utils-6.11/debian/changelog	2021-07-26 23:16:25.000000000 +0200
@@ -1,3 +1,13 @@
+cifs-utils (2:6.11-3.1) unstable; urgency=medium
+
+  * Non-maintainer upload
+
+  [ Hideki Yamane ]
+  * debian/patches
+    - Add 0011-fix-regression-for-CVE-2021-20208.patch (Closes: #989080)
+
+ -- Sebastian Ramacher <sramac...@debian.org>  Mon, 26 Jul 2021 23:16:25 +0200
+
 cifs-utils (2:6.11-3) unstable; urgency=high
 
   * CVE-2021-20208: cifs.upcall kerberos auth leak in container
diff -Nru cifs-utils-6.11/debian/patches/0011-fix-regression-for-CVE-2021-20208.patch cifs-utils-6.11/debian/patches/0011-fix-regression-for-CVE-2021-20208.patch
--- cifs-utils-6.11/debian/patches/0011-fix-regression-for-CVE-2021-20208.patch	1970-01-01 01:00:00.000000000 +0100
+++ cifs-utils-6.11/debian/patches/0011-fix-regression-for-CVE-2021-20208.patch	2021-07-26 23:16:21.000000000 +0200
@@ -0,0 +1,490 @@
+From 4ca235223d948fe4f3392da28b1471bce36e88d4 Mon Sep 17 00:00:00 2001
+From: Aurelien Aptel <aap...@suse.com>
+Date: Wed, 21 Apr 2021 16:22:15 +0200
+Subject: [PATCH v4] cifs.upcall: fix regression in kerberos mount
+
+The fix for CVE-2021-20208 in commit e461afd ("cifs.upcall: try to use
+container ipc/uts/net/pid/mnt/user namespaces") introduced a
+regression for kerberos mounts when cifs-utils is built with
+libcap-ng. It makes mount fail with ENOKEY "Required key not
+available".
+
+Current state:
+
+mount.cifs
+ '---> mount() ---> kernel
+       	           negprot, session setup (need security blob for krb)
+                   request_key("cifs.spnego",  payload="pid=%d;username=...")
+                               upcall
+  /sbin/request-key <--------------'
+   reads /etc/request-keys.conf
+   dispatch cifs.spnego request
+   calls /usr/sbin/cifs.upcall <key id>
+   - drop privileges (capabilities)
+   - fetch keyid
+   - parse payload
+   - switch to mount.cifs namespaces
+   - call krb5_xxx() funcs
+   - generate security blob
+   - set key value to security blob
+      '-----------------------------------> kernel
+                                         put blob in session setup packet
+  				       continue auth
+  				       open tcon
+  				       get share root
+  				       setup superblock
+mount.cifs mount() returns    <-----------'
+
+By the time cifs.upcall tries to switch to namespaces, enough
+capabilities have dropped in trim_capabilities() that it makes setns()
+fail with EPERM.
+
+setns() requires CAP_SYS_ADMIN.
+
+With libcap trim_capabilities() is a no-op.
+
+This fix:
+
+- moves the namespace switch earlier so that operations like
+  setgroups(), setgid(), scanning of pid environment, ... happens in the
+  contained namespaces.
+- moves trim_capabilities() after the namespace switch
+- moves the string processing to decode the key request payload in a
+  child process with minimum capabilities. the decoded data is shared
+  with the parent process via shared memory obtained with mmap().
+
+Fixes: e461afd ("cifs.upcall: try to use container ipc/uts/net/pid/mnt/user namespaces")
+Signed-off-by: Aurelien Aptel <aap...@suse.com>
+---
+ cifs.upcall.c | 214 ++++++++++++++++++++++++++++++++------------------
+ 1 file changed, 139 insertions(+), 75 deletions(-)
+
+Index: cifs-utils/cifs.upcall.c
+===================================================================
+--- cifs-utils.orig/cifs.upcall.c
++++ cifs-utils/cifs.upcall.c
+@@ -52,6 +52,9 @@
+ #include <stdbool.h>
+ #include <errno.h>
+ #include <sched.h>
++#include <sys/mman.h>
++#include <sys/types.h>
++#include <sys/wait.h>
+ 
+ #include "data_blob.h"
+ #include "spnego.h"
+@@ -777,6 +780,25 @@ handle_krb5_mech(const char *oid, const
+ 	return retval;
+ }
+ 
++
++
++struct decoded_args {
++	int ver;
++	char hostname[NI_MAXHOST + 1];
++	char ip[NI_MAXHOST + 1];
++
++/* Max user name length. */
++#define MAX_USERNAME_SIZE 256
++	char username[MAX_USERNAME_SIZE + 1];
++
++	uid_t uid;
++	uid_t creduid;
++	pid_t pid;
++	sectype_t sec;
++
++/*
++ * Flags to keep track of what was provided
++ */
+ #define DKD_HAVE_HOSTNAME	0x1
+ #define DKD_HAVE_VERSION	0x2
+ #define DKD_HAVE_SEC		0x4
+@@ -786,23 +808,13 @@ handle_krb5_mech(const char *oid, const
+ #define DKD_HAVE_CREDUID	0x40
+ #define DKD_HAVE_USERNAME	0x80
+ #define DKD_MUSTHAVE_SET (DKD_HAVE_HOSTNAME|DKD_HAVE_VERSION|DKD_HAVE_SEC)
+-
+-struct decoded_args {
+-	int ver;
+-	char *hostname;
+-	char *ip;
+-	char *username;
+-	uid_t uid;
+-	uid_t creduid;
+-	pid_t pid;
+-	sectype_t sec;
++	int have;
+ };
+ 
+ static unsigned int
+-decode_key_description(const char *desc, struct decoded_args *arg)
++__decode_key_description(const char *desc, struct decoded_args *arg)
+ {
+-	int len;
+-	int retval = 0;
++	size_t len;
+ 	char *pos;
+ 	const char *tkn = desc;
+ 
+@@ -816,13 +828,13 @@ decode_key_description(const char *desc,
+ 				len = pos - tkn;
+ 
+ 			len -= 5;
+-			free(arg->hostname);
+-			arg->hostname = strndup(tkn + 5, len);
+-			if (arg->hostname == NULL) {
+-				syslog(LOG_ERR, "Unable to allocate memory");
++			if (len > sizeof(arg->hostname)-1) {
++				syslog(LOG_ERR, "host= value too long for buffer");
+ 				return 1;
+ 			}
+-			retval |= DKD_HAVE_HOSTNAME;
++			memset(arg->hostname, 0, sizeof(arg->hostname));
++			strncpy(arg->hostname, tkn + 5, len);
++			arg->have |= DKD_HAVE_HOSTNAME;
+ 			syslog(LOG_DEBUG, "host=%s", arg->hostname);
+ 		} else if (!strncmp(tkn, "ip4=", 4) || !strncmp(tkn, "ip6=", 4)) {
+ 			if (pos == NULL)
+@@ -831,13 +843,13 @@ decode_key_description(const char *desc,
+ 				len = pos - tkn;
+ 
+ 			len -= 4;
+-			free(arg->ip);
+-			arg->ip = strndup(tkn + 4, len);
+-			if (arg->ip == NULL) {
+-				syslog(LOG_ERR, "Unable to allocate memory");
++			if (len > sizeof(arg->ip)-1) {
++				syslog(LOG_ERR, "ip[46]= value too long for buffer");
+ 				return 1;
+ 			}
+-			retval |= DKD_HAVE_IP;
++			memset(arg->ip, 0, sizeof(arg->ip));
++			strncpy(arg->ip, tkn + 4, len);
++			arg->have |= DKD_HAVE_IP;
+ 			syslog(LOG_DEBUG, "ip=%s", arg->ip);
+ 		} else if (strncmp(tkn, "user=", 5) == 0) {
+ 			if (pos == NULL)
+@@ -846,13 +858,13 @@ decode_key_description(const char *desc,
+ 				len = pos - tkn;
+ 
+ 			len -= 5;
+-			free(arg->username);
+-			arg->username = strndup(tkn + 5, len);
+-			if (arg->username == NULL) {
+-				syslog(LOG_ERR, "Unable to allocate memory");
++			if (len > sizeof(arg->username)-1) {
++				syslog(LOG_ERR, "user= value too long for buffer");
+ 				return 1;
+ 			}
+-			retval |= DKD_HAVE_USERNAME;
++			memset(arg->username, 0, sizeof(arg->username));
++			strncpy(arg->username, tkn + 5, len);
++			arg->have |= DKD_HAVE_USERNAME;
+ 			syslog(LOG_DEBUG, "user=%s", arg->username);
+ 		} else if (strncmp(tkn, "pid=", 4) == 0) {
+ 			errno = 0;
+@@ -863,13 +875,13 @@ decode_key_description(const char *desc,
+ 				return 1;
+ 			}
+ 			syslog(LOG_DEBUG, "pid=%u", arg->pid);
+-			retval |= DKD_HAVE_PID;
++			arg->have |= DKD_HAVE_PID;
+ 		} else if (strncmp(tkn, "sec=", 4) == 0) {
+ 			if (strncmp(tkn + 4, "krb5", 4) == 0) {
+-				retval |= DKD_HAVE_SEC;
++				arg->have |= DKD_HAVE_SEC;
+ 				arg->sec = KRB5;
+ 			} else if (strncmp(tkn + 4, "mskrb5", 6) == 0) {
+-				retval |= DKD_HAVE_SEC;
++				arg->have |= DKD_HAVE_SEC;
+ 				arg->sec = MS_KRB5;
+ 			}
+ 			syslog(LOG_DEBUG, "sec=%d", arg->sec);
+@@ -881,7 +893,7 @@ decode_key_description(const char *desc,
+ 				       strerror(errno));
+ 				return 1;
+ 			}
+-			retval |= DKD_HAVE_UID;
++			arg->have |= DKD_HAVE_UID;
+ 			syslog(LOG_DEBUG, "uid=%u", arg->uid);
+ 		} else if (strncmp(tkn, "creduid=", 8) == 0) {
+ 			errno = 0;
+@@ -891,7 +903,7 @@ decode_key_description(const char *desc,
+ 				       strerror(errno));
+ 				return 1;
+ 			}
+-			retval |= DKD_HAVE_CREDUID;
++			arg->have |= DKD_HAVE_CREDUID;
+ 			syslog(LOG_DEBUG, "creduid=%u", arg->creduid);
+ 		} else if (strncmp(tkn, "ver=", 4) == 0) {	/* if version */
+ 			errno = 0;
+@@ -901,14 +913,56 @@ decode_key_description(const char *desc,
+ 				       strerror(errno));
+ 				return 1;
+ 			}
+-			retval |= DKD_HAVE_VERSION;
++			arg->have |= DKD_HAVE_VERSION;
+ 			syslog(LOG_DEBUG, "ver=%d", arg->ver);
+ 		}
+ 		if (pos == NULL)
+ 			break;
+ 		tkn = pos + 1;
+ 	} while (tkn);
+-	return retval;
++	return 0;
++}
++
++static unsigned int
++decode_key_description(const char *desc, struct decoded_args **arg)
++{
++	pid_t pid;
++	pid_t rc;
++	int status;
++
++	/*
++	 * Do all the decoding/string processing in a child process
++	 * with low privileges.
++	 */
++
++	*arg = mmap(NULL, sizeof(struct decoded_args), PROT_READ | PROT_WRITE,
++		    MAP_ANONYMOUS | MAP_SHARED, -1, 0);
++	if (*arg == MAP_FAILED) {
++		syslog(LOG_ERR, "%s: mmap failed: %s", __func__, strerror(errno));
++		return -1;
++	}
++
++	pid = fork();
++	if (pid < 0) {
++		syslog(LOG_ERR, "%s: fork failed: %s", __func__, strerror(errno));
++		munmap(*arg, sizeof(struct decoded_args));
++		*arg = NULL;
++		return -1;
++	}
++	if (pid == 0) {
++		/* do the parsing in child */
++		drop_all_capabilities();
++		exit(__decode_key_description(desc, *arg));
++	}
++
++	rc = waitpid(pid, &status, 0);
++	if (rc < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) {
++		munmap(*arg, sizeof(struct decoded_args));
++		*arg = NULL;
++		return 1;
++	}
++
++	return 0;
+ }
+ 
+ static int setup_key(const key_serial_t key, const void *data, size_t datalen)
+@@ -1088,7 +1142,7 @@ int main(const int argc, char *const arg
+ 	bool try_dns = false, legacy_uid = false , env_probe = true;
+ 	char *buf;
+ 	char hostbuf[NI_MAXHOST], *host;
+-	struct decoded_args arg;
++	struct decoded_args *arg = NULL;
+ 	const char *oid;
+ 	uid_t uid;
+ 	char *keytab_name = NULL;
+@@ -1099,7 +1153,6 @@ int main(const int argc, char *const arg
+ 	const char *key_descr = NULL;
+ 
+ 	hostbuf[0] = '\0';
+-	memset(&arg, 0, sizeof(arg));
+ 
+ 	openlog(prog, 0, LOG_DAEMON);
+ 
+@@ -1140,9 +1193,6 @@ int main(const int argc, char *const arg
+ 		}
+ 	}
+ 
+-	if (trim_capabilities(env_probe))
+-		goto out;
+-
+ 	/* is there a key? */
+ 	if (argc <= optind) {
+ 		usage();
+@@ -1168,6 +1218,10 @@ int main(const int argc, char *const arg
+ 
+ 	syslog(LOG_DEBUG, "key description: %s", buf);
+ 
++	/*
++	 * If we are requested a simple DNS query, do it and exit
++	 */
++
+ 	if (strncmp(buf, "cifs.resolver", sizeof("cifs.resolver") - 1) == 0)
+ 		key_descr = ".cifs.resolver";
+ 	else if (strncmp(buf, "dns_resolver", sizeof("dns_resolver") - 1) == 0)
+@@ -1177,33 +1231,42 @@ int main(const int argc, char *const arg
+ 		goto out;
+ 	}
+ 
+-	have = decode_key_description(buf, &arg);
++	/*
++	 * Otherwise, it's a spnego key request
++	 */
++
++	rc = decode_key_description(buf, &arg);
+ 	free(buf);
+-	if ((have & DKD_MUSTHAVE_SET) != DKD_MUSTHAVE_SET) {
++	if (rc) {
++		syslog(LOG_ERR, "failed to decode key description");
++		goto out;
++	}
++
++	if ((arg->have & DKD_MUSTHAVE_SET) != DKD_MUSTHAVE_SET) {
+ 		syslog(LOG_ERR, "unable to get necessary params from key "
+ 		       "description (0x%x)", have);
+ 		rc = 1;
+ 		goto out;
+ 	}
+ 
+-	if (arg.ver > CIFS_SPNEGO_UPCALL_VERSION) {
++	if (arg->ver > CIFS_SPNEGO_UPCALL_VERSION) {
+ 		syslog(LOG_ERR, "incompatible kernel upcall version: 0x%x",
+-		       arg.ver);
++		       arg->ver);
+ 		rc = 1;
+ 		goto out;
+ 	}
+ 
+-	if (strlen(arg.hostname) >= NI_MAXHOST) {
++	if (strlen(arg->hostname) >= NI_MAXHOST) {
+ 		syslog(LOG_ERR, "hostname provided by kernel is too long");
+ 		rc = 1;
+ 		goto out;
+ 
+ 	}
+ 
+-	if (!legacy_uid && (have & DKD_HAVE_CREDUID))
+-		uid = arg.creduid;
+-	else if (have & DKD_HAVE_UID)
+-		uid = arg.uid;
++	if (!legacy_uid && (arg->have & DKD_HAVE_CREDUID))
++		uid = arg->creduid;
++	else if (arg->have & DKD_HAVE_UID)
++		uid = arg->uid;
+ 	else {
+ 		/* no uid= or creduid= parm -- something is wrong */
+ 		syslog(LOG_ERR, "No uid= or creduid= parm specified");
+@@ -1212,6 +1275,21 @@ int main(const int argc, char *const arg
+ 	}
+ 
+ 	/*
++	 * Change to the process's namespace. This means that things will work
++	 * acceptably in containers, because we'll be looking at the correct
++	 * filesystem and have the correct network configuration.
++	 */
++	rc = switch_to_process_ns(arg->pid);
++	if (rc == -1) {
++		syslog(LOG_ERR, "unable to switch to process namespace: %s", strerror(errno));
++		rc = 1;
++		goto out;
++	}
++
++	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
+@@ -1256,20 +1334,7 @@ int main(const int argc, char *const arg
+ 	 * look at the environ file.
+ 	 */
+ 	env_cachename =
+-		get_cachename_from_process_env(env_probe ? arg.pid : 0);
+-
+-	/*
+-	 * Change to the process's namespace. This means that things will work
+-	 * acceptably in containers, because we'll be looking at the correct
+-	 * filesystem and have the correct network configuration.
+-	 */
+-	rc = switch_to_process_ns(arg.pid);
+-	if (rc == -1) {
+-		syslog(LOG_ERR, "unable to switch to process namespace: %s",
+-		       strerror(errno));
+-		rc = 1;
+-		goto out;
+-	}
++		get_cachename_from_process_env(env_probe ? arg->pid : 0);
+ 
+ 	rc = setuid(uid);
+ 	if (rc == -1) {
+@@ -1291,18 +1356,18 @@ int main(const int argc, char *const arg
+ 
+ 	ccache = get_existing_cc(env_cachename);
+ 	/* Couldn't find credcache? Try to use keytab */
+-	if (ccache == NULL && arg.username != NULL)
+-		ccache = init_cc_from_keytab(keytab_name, arg.username);
++	if (ccache == NULL && arg->username[0] != '\0')
++		ccache = init_cc_from_keytab(keytab_name, arg->username);
+ 
+ 	if (ccache == NULL) {
+ 		rc = 1;
+ 		goto out;
+ 	}
+ 
+-	host = arg.hostname;
++	host = arg->hostname;
+ 
+ 	// do mech specific authorization
+-	switch (arg.sec) {
++	switch (arg->sec) {
+ 	case MS_KRB5:
+ 	case KRB5:
+ 		/*
+@@ -1318,7 +1383,7 @@ int main(const int argc, char *const arg
+ 		 * TRY only:
+ 		 * cifs/bar.example.com@REALM
+ 		 */
+-		if (arg.sec == MS_KRB5)
++		if (arg->sec == MS_KRB5)
+ 			oid = OID_KERBEROS5_OLD;
+ 		else
+ 			oid = OID_KERBEROS5;
+@@ -1375,10 +1440,10 @@ retry_new_hostname:
+ 				break;
+ 		}
+ 
+-		if (!try_dns || !(have & DKD_HAVE_IP))
++		if (!try_dns || !(arg->have & DKD_HAVE_IP))
+ 			break;
+ 
+-		rc = ip_to_fqdn(arg.ip, hostbuf, sizeof(hostbuf));
++		rc = ip_to_fqdn(arg->ip, hostbuf, sizeof(hostbuf));
+ 		if (rc)
+ 			break;
+ 
+@@ -1386,7 +1451,7 @@ retry_new_hostname:
+ 		host = hostbuf;
+ 		goto retry_new_hostname;
+ 	default:
+-		syslog(LOG_ERR, "sectype: %d is not implemented", arg.sec);
++		syslog(LOG_ERR, "sectype: %d is not implemented", arg->sec);
+ 		rc = 1;
+ 		break;
+ 	}
+@@ -1404,7 +1469,7 @@ retry_new_hostname:
+ 		rc = 1;
+ 		goto out;
+ 	}
+-	keydata->version = arg.ver;
++	keydata->version = arg->ver;
+ 	keydata->flags = 0;
+ 	keydata->sesskey_len = sess_key.length;
+ 	keydata->secblob_len = secblob.length;
+@@ -1430,11 +1495,10 @@ out:
+ 		krb5_cc_close(context, ccache);
+ 	if (context)
+ 		krb5_free_context(context);
+-	free(arg.hostname);
+-	free(arg.ip);
+-	free(arg.username);
+ 	free(keydata);
+ 	free(env_cachename);
++	if (arg)
++		munmap(arg, sizeof(*arg));
+ 	syslog(LOG_DEBUG, "Exit status %ld", rc);
+ 	return rc;
+ }
diff -Nru cifs-utils-6.11/debian/patches/series cifs-utils-6.11/debian/patches/series
--- cifs-utils-6.11/debian/patches/series	2021-05-06 21:23:29.000000000 +0200
+++ cifs-utils-6.11/debian/patches/series	2021-07-26 23:16:21.000000000 +0200
@@ -2,3 +2,4 @@
 0002-Install-hook-relative-to-DESTDIR.patch
 0003-Change-script-shbangs-to-python3.patch
 0010-CVE-2021-20208.patch
+0011-fix-regression-for-CVE-2021-20208.patch

Attachment: signature.asc
Description: PGP signature

Reply via email to