From 368ca6cd41a9e0864785ade13b417c0e7b119d57cfb015537bab201dfbcf9f8c Mon Sep 17 00:00:00 2001 From: Samuel Cabrero Date: Fri, 19 Mar 2021 14:41:34 +0000 Subject: [PATCH 1/4] Accepting request 877747 from home:aaptel:cifs-utils-alternatives OBS-URL: https://build.opensuse.org/request/show/877747 OBS-URL: https://build.opensuse.org/package/show/network:samba:STABLE/cifs-utils?expand=0&rev=190 --- cifs-utils.changes | 7 +++++++ cifs-utils.spec | 41 +++++++++++++++++++++++++++++++++++------ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/cifs-utils.changes b/cifs-utils.changes index a112c90..c787277 100644 --- a/cifs-utils.changes +++ b/cifs-utils.changes @@ -53,6 +53,13 @@ Tue Nov 17 13:42:23 UTC 2020 - Ludwig Nussel - prepare usrmerge (boo#1029961) +------------------------------------------------------------------- +Tue Aug 11 12:45:15 UTC 2020 - Aurelien Aptel + +- Make cifs-idmap plugin (idmapwb.so) use update-alternatives + mechanism to be able to switch between cifs-utils and sssd; + (bsc#1182682). + ------------------------------------------------------------------- Mon Aug 10 06:56:11 UTC 2020 - Aurelien Aptel diff --git a/cifs-utils.spec b/cifs-utils.spec index 2c8199d..b7bcf82 100644 --- a/cifs-utils.spec +++ b/cifs-utils.spec @@ -38,6 +38,18 @@ Source1: cifs.init Patch1: fix-sbin-install-error.patch +# Both SSSD and cifs-utils provide an idmap plugin for cifs.ko +# /etc/cifs-utils/idmap-plugin should be a symlink to one of the 2 idmap plugins +# * cifs-utils one is the default (priority 20) +# * installing SSSD should NOT switch to SSSD plugin (priority 10) +%define cifs_idmap_plugin %{_sysconfdir}/cifs-utils/idmap-plugin +%define cifs_idmap_lib %{_libdir}/cifs-utils/idmapwb.so +%define cifs_idmap_name cifs-idmap-plugin +%define cifs_idmap_priority 20 +BuildRequires: update-alternatives +Requires(post): update-alternatives +Requires(preun): update-alternatives + # cifs-utils 6.8 switched to python for man page generation # we need to require either py2 or py3 package # some products do not have a py2/py3 versions @@ -139,8 +151,6 @@ mkdir -p %{buildroot}/%{_sysconfdir}/init.d %endif %make_install -mkdir -p %{buildroot}%{_sysconfdir}/%{name} -ln -s %{_libdir}/%{name}/idmapwb.so %{buildroot}%{_sysconfdir}/%{name}/idmap-plugin mkdir -p %{buildroot}%{_sysconfdir}/request-key.d install -m 644 -p contrib/request-key.d/cifs.idmap.conf %{buildroot}%{_sysconfdir}/request-key.d install -m 644 -p contrib/request-key.d/cifs.spnego.conf %{buildroot}%{_sysconfdir}/request-key.d @@ -156,6 +166,10 @@ install -m 0755 -p ${RPM_SOURCE_DIR}/cifs.init %{buildroot}/%{_sysconfdir}/init. ln -s service %{buildroot}/%{_sbindir}/rccifs %endif +# dummy target for cifs-idmap-plugin +mkdir -p %{buildroot}%{_sysconfdir}/alternatives %{buildroot}%{_sysconfdir}/cifs-utils +ln -s -f %{_sysconfdir}/alternatives/%{cifs_idmap_name} %{buildroot}%{cifs_idmap_plugin} + touch %{buildroot}/%{_sysconfdir}/sysconfig/network/if-{down,up}.d/${script} \ %{buildroot}%{_rundir}/cifs %endif @@ -164,6 +178,15 @@ touch %{buildroot}/%{_sysconfdir}/sysconfig/network/if-{down,up}.d/${script} \ %fdupes %{buildroot} %endif +%post +# install cifs-utils cifs-idmap plugin using alternatives system +update-alternatives --install %{cifs_idmap_plugin} %{cifs_idmap_name} %{cifs_idmap_lib} %{cifs_idmap_priority} + +%postun +if [ ! -f %{cifs_idmap_lib} ] ; then + update-alternatives --remove %{cifs_idmap_name} %{cifs_idmap_lib} +fi + %files %if 0%{?usrmerged} %{_sbindir}/mount.cifs @@ -188,14 +211,20 @@ touch %{buildroot}/%{_sysconfdir}/sysconfig/network/if-{down,up}.d/${script} \ %{_mandir}/man8/cifs.upcall.8%{ext_man} %{_mandir}/man8/mount.cifs.8%{ext_man} %{_mandir}/man8/mount.smb3.8%{ext_man} + +# request keys %dir %{_sysconfdir}/request-key.d %config(noreplace) %{_sysconfdir}/request-key.d/cifs.idmap.conf %config(noreplace) %{_sysconfdir}/request-key.d/cifs.spnego.conf -%dir %{_libdir}/cifs-utils -%dir %{_sysconfdir}/cifs-utils -%config(noreplace) %{_sysconfdir}/cifs-utils/idmap-plugin -%{_libdir}/%{name}/idmapwb.so + +# idmap plugin +%dir %_sysconfdir/cifs-utils +%{cifs_idmap_plugin} +%dir %_libdir/cifs-utils +%{cifs_idmap_lib} +%ghost %_sysconfdir/alternatives/%{cifs_idmap_name} %{_mandir}/man8/idmapwb.8%{ext_man} + %if 0%{?suse_version} > 1221 %if ! %{systemd} %attr(0754,root,root) %config %{_sysconfdir}/init.d/cifs From 54f8c7c464eaa3b9cbaa28018bad4e7264d004f93273f43bbf909c52146089aa Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 26 Apr 2021 15:29:09 +0000 Subject: [PATCH 2/4] Accepting request 888523 from home:aaptel:cifs-utils-cvefix Add CVE and its regression fix: - cifs.upcall: fix CVE regression in kerberos mount; (bsc#1184815). * add 0001-cifs.upcall-fix-CVE-regression-in-kerberos-mount.patch - CVE-2021-20208: cifs-utils: cifs.upcall kerberos auth leak in container; (bsc#1183239); CVE-2021-20208. OBS-URL: https://build.opensuse.org/request/show/888523 OBS-URL: https://build.opensuse.org/package/show/network:samba:STABLE/cifs-utils?expand=0&rev=191 --- ...fix-CVE-regression-in-kerberos-mount.patch | 485 ++++++++++++++++++ ...to-use-container-ipc-uts-net-pid-mnt.patch | 264 ++++++++++ cifs-utils.changes | 12 + cifs-utils.spec | 4 + 4 files changed, 765 insertions(+) create mode 100644 0001-cifs.upcall-fix-CVE-regression-in-kerberos-mount.patch create mode 100644 0001-cifs.upcall-try-to-use-container-ipc-uts-net-pid-mnt.patch diff --git a/0001-cifs.upcall-fix-CVE-regression-in-kerberos-mount.patch b/0001-cifs.upcall-fix-CVE-regression-in-kerberos-mount.patch new file mode 100644 index 0000000..9a917e5 --- /dev/null +++ b/0001-cifs.upcall-fix-CVE-regression-in-kerberos-mount.patch @@ -0,0 +1,485 @@ +From 5e885b485f08045e95e7f29418facf777e053fc3 Mon Sep 17 00:00:00 2001 +From: Aurelien Aptel +Date: Wed, 21 Apr 2021 16:22:15 +0200 +Subject: [PATCH v2] cifs.upcall: fix CVE regression in kerberos mount + +The last CVE fix introduced a regression for kerberos mounts when +cifs-utils is built with libcap-ng. + +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 + - 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 +Reviewed-by: Paulo Alcantara + +--- + cifs.upcall.c | 212 +++++++++++++++++++++++++++++++------------------- + 1 file changed, 132 insertions(+), 80 deletions(-) + +diff --git a/cifs.upcall.c b/cifs.upcall.c +index e413934..31d102b 100644 +--- a/cifs.upcall.c ++++ b/cifs.upcall.c +@@ -52,6 +52,9 @@ + #include + #include + #include ++#include ++#include ++#include + + #include "data_blob.h" + #include "spnego.h" +@@ -787,6 +790,25 @@ handle_krb5_mech(const char *oid, const char *host, DATA_BLOB * secblob, + 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 +@@ -796,23 +818,13 @@ handle_krb5_mech(const char *oid, const char *host, DATA_BLOB * secblob, + #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; + char *pos; + const char *tkn = desc; + +@@ -826,13 +838,9 @@ decode_key_description(const char *desc, struct decoded_args *arg) + 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"); +- 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) +@@ -841,13 +849,9 @@ decode_key_description(const char *desc, struct decoded_args *arg) + 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"); +- 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) +@@ -856,13 +860,9 @@ decode_key_description(const char *desc, struct decoded_args *arg) + 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"); +- 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; +@@ -873,13 +873,13 @@ decode_key_description(const char *desc, struct decoded_args *arg) + 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); +@@ -891,7 +891,7 @@ decode_key_description(const char *desc, struct decoded_args *arg) + 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; +@@ -901,7 +901,7 @@ decode_key_description(const char *desc, struct decoded_args *arg) + 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; +@@ -911,14 +911,56 @@ decode_key_description(const char *desc, struct decoded_args *arg) + 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) +@@ -1098,7 +1140,7 @@ int main(const int argc, char *const argv[]) + 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; +@@ -1109,7 +1151,6 @@ int main(const int argc, char *const argv[]) + const char *key_descr = NULL; + + hostbuf[0] = '\0'; +- memset(&arg, 0, sizeof(arg)); + + openlog(prog, 0, LOG_DAEMON); + +@@ -1150,9 +1191,6 @@ int main(const int argc, char *const argv[]) + } + } + +- if (trim_capabilities(env_probe)) +- goto out; +- + /* is there a key? */ + if (argc <= optind) { + usage(); +@@ -1178,6 +1216,10 @@ int main(const int argc, char *const argv[]) + + 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) +@@ -1187,33 +1229,42 @@ int main(const int argc, char *const argv[]) + 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"); +@@ -1221,6 +1272,21 @@ int main(const int argc, char *const argv[]) + goto out; + } + ++ /* ++ * 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 +@@ -1266,20 +1332,7 @@ int main(const int argc, char *const argv[]) + * 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) { +@@ -1301,18 +1354,18 @@ int main(const int argc, char *const argv[]) + + 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: + /* +@@ -1328,7 +1381,7 @@ int main(const int argc, char *const argv[]) + * 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; +@@ -1385,10 +1438,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; + +@@ -1396,7 +1449,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; + } +@@ -1414,7 +1467,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; +@@ -1440,11 +1493,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; + } +-- +2.30.0 + diff --git a/0001-cifs.upcall-try-to-use-container-ipc-uts-net-pid-mnt.patch b/0001-cifs.upcall-try-to-use-container-ipc-uts-net-pid-mnt.patch new file mode 100644 index 0000000..ce7b68d --- /dev/null +++ b/0001-cifs.upcall-try-to-use-container-ipc-uts-net-pid-mnt.patch @@ -0,0 +1,264 @@ +From e461afd8cfa6d0781ae0c5c10e89b6ef1ca6da32 Mon Sep 17 00:00:00 2001 +From: Alastair Houghton +Date: Tue, 29 Dec 2020 14:02:39 +0000 +Subject: [PATCH] cifs.upcall: try to use container ipc/uts/net/pid/mnt/user + namespaces + +In certain scenarios (e.g. kerberos multimount), when a process does +syscalls, the kernel sometimes has to query information or trigger +some actions in userspace. To do so it calls the cifs.upcall binary +with information on the process that triggered the syscall in the +first place. + +ls(pid=10) ====> open("foo") ====> kernel + + that user doesn't have an SMB + session, lets create one using his + kerberos credential cache + + call cifs.upcall and ask for krb info + for whoever owns pid=10 + | + cifs.upcall --pid 10 <=================+ + + ...gather info... + return binary blob used + when establishing SMB session + ===================> kernel + open SMB session, handle + open() syscall +ls <=================================== return open() result to ls + +On a system using containers, the kernel is still calling the host +cifs.upcall and using the host configuration (for network, pid, etc). + +This patch changes the behaviour of cifs.upcall so that it uses the +calling process namespaces (ls in the example) when doing its +job. + +Note that the kernel still calls the binary in the host, but the +binary will place itself the contexts of the calling process +namespaces. + +This code makes use of (but shouldn't require) the following kernel +config options and syscall flags: + +approx. year | +introduced | config/flags +---------------+---------------- +2008 | CONFIG_NAMESPACES=y +2007 | CONFIG_UTS_NS=y +2020 | CONFIG_TIME_NS=y +2006 | CONFIG_IPC_NS=y +2007 | CONFIG_USER_NS +2008 | CONFIG_PID_NS=y +2007 | CONFIG_NET_NS=y +2007 | CONFIG_CGROUPS +2016 | CLONE_NEWCGROUP setns() flag + +Signed-off-by: Aurelien Aptel +Signed-off-by: Alastair Houghton +--- + cifs.upcall.c | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 172 insertions(+) + +diff --git a/cifs.upcall.c b/cifs.upcall.c +index 400b42d..e413934 100644 +--- a/cifs.upcall.c ++++ b/cifs.upcall.c +@@ -51,6 +51,7 @@ + #include + #include + #include ++#include + + #include "data_blob.h" + #include "spnego.h" +@@ -240,6 +241,164 @@ err_cache: + return credtime; + } + ++static struct namespace_file { ++ int nstype; ++ const char *name; ++ int fd; ++} namespace_files[] = { ++ ++#ifdef CLONE_NEWCGROUP ++ { CLONE_NEWCGROUP, "cgroup", -1 }, ++#endif ++ ++#ifdef CLONE_NEWIPC ++ { CLONE_NEWIPC, "ipc", -1 }, ++#endif ++ ++#ifdef CLONE_NEWUTS ++ { CLONE_NEWUTS, "uts", -1 }, ++#endif ++ ++#ifdef CLONE_NEWNET ++ { CLONE_NEWNET, "net", -1 }, ++#endif ++ ++#ifdef CLONE_NEWPID ++ { CLONE_NEWPID, "pid", -1 }, ++#endif ++ ++#ifdef CLONE_NEWTIME ++ { CLONE_NEWTIME, "time", -1 }, ++#endif ++ ++#ifdef CLONE_NEWNS ++ { CLONE_NEWNS, "mnt", -1 }, ++#endif ++ ++#ifdef CLONE_NEWUSER ++ { CLONE_NEWUSER, "user", -1 }, ++#endif ++}; ++ ++#define NS_PATH_FMT "/proc/%d/ns/%s" ++#define NS_PATH_MAXLEN (6 + 10 + 4 + 6 + 1) ++ ++/** ++ * in_same_user_ns - return true if two processes are in the same user ++ * namespace. ++ * @pid_a: the pid of the first process ++ * @pid_b: the pid of the second process ++ * ++ * Works by comparing the inode numbers for /proc//user. ++ */ ++static int ++in_same_user_ns(pid_t pid_a, pid_t pid_b) ++{ ++ char path[NS_PATH_MAXLEN]; ++ ino_t a_ino, b_ino; ++ struct stat st; ++ ++ snprintf(path, sizeof(path), NS_PATH_FMT, pid_a, "user"); ++ if (stat(path, &st) != 0) ++ return 0; ++ a_ino = st.st_ino; ++ ++ snprintf(path, sizeof(path), NS_PATH_FMT, pid_b, "user"); ++ if (stat(path, &st) != 0) ++ return 0; ++ b_ino = st.st_ino; ++ ++ return a_ino == b_ino; ++} ++ ++/** ++ * switch_to_process_ns - change the namespace to the one for the specified ++ * process. ++ * @pid: initiating pid value from the upcall string ++ * ++ * Uses setns() to switch process namespace. ++ * This ensures that we have the same access and configuration as the ++ * process that triggered the lookup. ++ */ ++static int ++switch_to_process_ns(pid_t pid) ++{ ++ int count = sizeof(namespace_files) / sizeof(struct namespace_file); ++ int n, err = 0; ++ int rc = 0; ++ ++ /* First, open all the namespace fds. We do this first because ++ the namespace changes might prohibit us from opening them. */ ++ for (n = 0; n < count; ++n) { ++ char nspath[NS_PATH_MAXLEN]; ++ int ret, fd; ++ ++#ifdef CLONE_NEWUSER ++ if (namespace_files[n].nstype == CLONE_NEWUSER ++ && in_same_user_ns(getpid(), pid)) { ++ /* Switching to the same user namespace is forbidden, ++ because switching to a user namespace grants all ++ capabilities in that namespace regardless of uid. */ ++ namespace_files[n].fd = -1; ++ continue; ++ } ++#endif ++ ++ ret = snprintf(nspath, NS_PATH_MAXLEN, NS_PATH_FMT, ++ pid, namespace_files[n].name); ++ if (ret >= NS_PATH_MAXLEN) { ++ syslog(LOG_DEBUG, "%s: unterminated path!\n", __func__); ++ err = ENAMETOOLONG; ++ rc = -1; ++ goto out; ++ } ++ ++ fd = open(nspath, O_RDONLY); ++ if (fd < 0 && errno != ENOENT) { ++ /* ++ * don't stop on non-existing ns ++ * but stop for other errors ++ */ ++ err = errno; ++ rc = -1; ++ goto out; ++ } ++ ++ namespace_files[n].fd = fd; ++ } ++ ++ /* Next, call setns for each of them */ ++ for (n = 0; n < count; ++n) { ++ /* skip non-existing ns */ ++ if (namespace_files[n].fd < 0) ++ continue; ++ ++ rc = setns(namespace_files[n].fd, namespace_files[n].nstype); ++ ++ if (rc < 0) { ++ syslog(LOG_DEBUG, "%s: setns() failed for %s\n", ++ __func__, namespace_files[n].name); ++ err = errno; ++ goto out; ++ } ++ } ++ ++out: ++ /* Finally, close all the fds */ ++ for (n = 0; n < count; ++n) { ++ if (namespace_files[n].fd != -1) { ++ close(namespace_files[n].fd); ++ namespace_files[n].fd = -1; ++ } ++ } ++ ++ if (rc != 0) { ++ errno = err; ++ } ++ ++ return rc; ++} ++ + #define ENV_PATH_FMT "/proc/%d/environ" + #define ENV_PATH_MAXLEN (6 + 10 + 8 + 1) + +@@ -1109,6 +1268,19 @@ int main(const int argc, char *const argv[]) + 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; ++ } ++ + rc = setuid(uid); + if (rc == -1) { + syslog(LOG_ERR, "setuid: %s", strerror(errno)); +-- +2.30.0 + diff --git a/cifs-utils.changes b/cifs-utils.changes index c787277..84a721b 100644 --- a/cifs-utils.changes +++ b/cifs-utils.changes @@ -1,3 +1,15 @@ +------------------------------------------------------------------- +Fri Apr 23 10:41:59 UTC 2021 - Aurelien Aptel + +- cifs.upcall: fix CVE regression in kerberos mount; (bsc#1184815). + * add 0001-cifs.upcall-fix-CVE-regression-in-kerberos-mount.patch + +------------------------------------------------------------------- +Tue Mar 9 17:17:59 UTC 2021 - palcantara@suse.de + +- CVE-2021-20208: cifs-utils: cifs.upcall kerberos auth leak in + container; (bsc#1183239); CVE-2021-20208. + ------------------------------------------------------------------- Tue Feb 23 12:15:39 UTC 2021 - Aurelien Aptel diff --git a/cifs-utils.spec b/cifs-utils.spec index b7bcf82..024ac62 100644 --- a/cifs-utils.spec +++ b/cifs-utils.spec @@ -37,6 +37,8 @@ Source100: README.cifstab.migration Source1: cifs.init Patch1: fix-sbin-install-error.patch +Patch2: 0001-cifs.upcall-try-to-use-container-ipc-uts-net-pid-mnt.patch +Patch3: 0001-cifs.upcall-fix-CVE-regression-in-kerberos-mount.patch # Both SSSD and cifs-utils provide an idmap plugin for cifs.ko # /etc/cifs-utils/idmap-plugin should be a symlink to one of the 2 idmap plugins @@ -133,6 +135,8 @@ for i in $pyscripts; do done %patch1 -p1 +%patch2 -p1 +%patch3 -p1 %build export CFLAGS="%{optflags} -D_GNU_SOURCE -fpie" From c29e0dbf49422f1f9d050bfd3cb9f39b67f73ab8cf0d3b529b3b725c3ae60293 Mon Sep 17 00:00:00 2001 From: Paulo Alcantara Date: Mon, 26 Apr 2021 16:33:30 +0000 Subject: [PATCH 3/4] Accepting request 888546 from home:aaptel:cifs-utils-cvefix add patch name in .changes file to be able to pass Factory checks OBS-URL: https://build.opensuse.org/request/show/888546 OBS-URL: https://build.opensuse.org/package/show/network:samba:STABLE/cifs-utils?expand=0&rev=192 --- cifs-utils.changes | 1 + 1 file changed, 1 insertion(+) diff --git a/cifs-utils.changes b/cifs-utils.changes index 84a721b..b0529b1 100644 --- a/cifs-utils.changes +++ b/cifs-utils.changes @@ -9,6 +9,7 @@ Tue Mar 9 17:17:59 UTC 2021 - palcantara@suse.de - CVE-2021-20208: cifs-utils: cifs.upcall kerberos auth leak in container; (bsc#1183239); CVE-2021-20208. + * add 0001-cifs.upcall-try-to-use-container-ipc-uts-net-pid-mnt.patch ------------------------------------------------------------------- Tue Feb 23 12:15:39 UTC 2021 - Aurelien Aptel From aa38f0c77391dbd47a2ab95bba1d3285000ce938f5d9aafda09515d7c868d468 Mon Sep 17 00:00:00 2001 From: David Disseldorp Date: Tue, 27 Apr 2021 12:30:18 +0000 Subject: [PATCH 4/4] Accepting request 888768 from home:aaptel:cifs-utils-cvefix update patch and patch name with ddiss comments OBS-URL: https://build.opensuse.org/request/show/888768 OBS-URL: https://build.opensuse.org/package/show/network:samba:STABLE/cifs-utils?expand=0&rev=193 --- ...all-fix-regression-in-kerberos-mount.patch | 80 ++++++++++--------- cifs-utils.changes | 4 +- cifs-utils.spec | 2 +- 3 files changed, 47 insertions(+), 39 deletions(-) rename 0001-cifs.upcall-fix-CVE-regression-in-kerberos-mount.patch => 0001-cifs.upcall-fix-regression-in-kerberos-mount.patch (84%) diff --git a/0001-cifs.upcall-fix-CVE-regression-in-kerberos-mount.patch b/0001-cifs.upcall-fix-regression-in-kerberos-mount.patch similarity index 84% rename from 0001-cifs.upcall-fix-CVE-regression-in-kerberos-mount.patch rename to 0001-cifs.upcall-fix-regression-in-kerberos-mount.patch index 9a917e5..f20f688 100644 --- a/0001-cifs.upcall-fix-CVE-regression-in-kerberos-mount.patch +++ b/0001-cifs.upcall-fix-regression-in-kerberos-mount.patch @@ -1,10 +1,13 @@ -From 5e885b485f08045e95e7f29418facf777e053fc3 Mon Sep 17 00:00:00 2001 +From 4ca235223d948fe4f3392da28b1471bce36e88d4 Mon Sep 17 00:00:00 2001 From: Aurelien Aptel Date: Wed, 21 Apr 2021 16:22:15 +0200 -Subject: [PATCH v2] cifs.upcall: fix CVE regression in kerberos mount +Subject: [PATCH v4] cifs.upcall: fix regression in kerberos mount -The last CVE fix introduced a regression for kerberos mounts when -cifs-utils is built with libcap-ng. +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: @@ -52,14 +55,12 @@ This fix: Fixes: e461afd ("cifs.upcall: try to use container ipc/uts/net/pid/mnt/user namespaces") Signed-off-by: Aurelien Aptel -Reviewed-by: Paulo Alcantara - --- - cifs.upcall.c | 212 +++++++++++++++++++++++++++++++------------------- - 1 file changed, 132 insertions(+), 80 deletions(-) + cifs.upcall.c | 214 ++++++++++++++++++++++++++++++++------------------ + 1 file changed, 139 insertions(+), 75 deletions(-) diff --git a/cifs.upcall.c b/cifs.upcall.c -index e413934..31d102b 100644 +index e413934..ad04301 100644 --- a/cifs.upcall.c +++ b/cifs.upcall.c @@ -52,6 +52,9 @@ @@ -119,12 +120,13 @@ index e413934..31d102b 100644 -decode_key_description(const char *desc, struct decoded_args *arg) +__decode_key_description(const char *desc, struct decoded_args *arg) { - int len; +- int len; - int retval = 0; ++ size_t len; char *pos; const char *tkn = desc; -@@ -826,13 +838,9 @@ decode_key_description(const char *desc, struct decoded_args *arg) +@@ -826,13 +838,13 @@ decode_key_description(const char *desc, struct decoded_args *arg) len = pos - tkn; len -= 5; @@ -132,8 +134,10 @@ index e413934..31d102b 100644 - arg->hostname = strndup(tkn + 5, len); - if (arg->hostname == NULL) { - syslog(LOG_ERR, "Unable to allocate memory"); -- return 1; -- } ++ 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); @@ -141,7 +145,7 @@ index e413934..31d102b 100644 syslog(LOG_DEBUG, "host=%s", arg->hostname); } else if (!strncmp(tkn, "ip4=", 4) || !strncmp(tkn, "ip6=", 4)) { if (pos == NULL) -@@ -841,13 +849,9 @@ decode_key_description(const char *desc, struct decoded_args *arg) +@@ -841,13 +853,13 @@ decode_key_description(const char *desc, struct decoded_args *arg) len = pos - tkn; len -= 4; @@ -149,8 +153,10 @@ index e413934..31d102b 100644 - arg->ip = strndup(tkn + 4, len); - if (arg->ip == NULL) { - syslog(LOG_ERR, "Unable to allocate memory"); -- return 1; -- } ++ 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); @@ -158,7 +164,7 @@ index e413934..31d102b 100644 syslog(LOG_DEBUG, "ip=%s", arg->ip); } else if (strncmp(tkn, "user=", 5) == 0) { if (pos == NULL) -@@ -856,13 +860,9 @@ decode_key_description(const char *desc, struct decoded_args *arg) +@@ -856,13 +868,13 @@ decode_key_description(const char *desc, struct decoded_args *arg) len = pos - tkn; len -= 5; @@ -166,8 +172,10 @@ index e413934..31d102b 100644 - arg->username = strndup(tkn + 5, len); - if (arg->username == NULL) { - syslog(LOG_ERR, "Unable to allocate memory"); -- return 1; -- } ++ 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); @@ -175,7 +183,7 @@ index e413934..31d102b 100644 syslog(LOG_DEBUG, "user=%s", arg->username); } else if (strncmp(tkn, "pid=", 4) == 0) { errno = 0; -@@ -873,13 +873,13 @@ decode_key_description(const char *desc, struct decoded_args *arg) +@@ -873,13 +885,13 @@ decode_key_description(const char *desc, struct decoded_args *arg) return 1; } syslog(LOG_DEBUG, "pid=%u", arg->pid); @@ -192,7 +200,7 @@ index e413934..31d102b 100644 arg->sec = MS_KRB5; } syslog(LOG_DEBUG, "sec=%d", arg->sec); -@@ -891,7 +891,7 @@ decode_key_description(const char *desc, struct decoded_args *arg) +@@ -891,7 +903,7 @@ decode_key_description(const char *desc, struct decoded_args *arg) strerror(errno)); return 1; } @@ -201,7 +209,7 @@ index e413934..31d102b 100644 syslog(LOG_DEBUG, "uid=%u", arg->uid); } else if (strncmp(tkn, "creduid=", 8) == 0) { errno = 0; -@@ -901,7 +901,7 @@ decode_key_description(const char *desc, struct decoded_args *arg) +@@ -901,7 +913,7 @@ decode_key_description(const char *desc, struct decoded_args *arg) strerror(errno)); return 1; } @@ -210,7 +218,7 @@ index e413934..31d102b 100644 syslog(LOG_DEBUG, "creduid=%u", arg->creduid); } else if (strncmp(tkn, "ver=", 4) == 0) { /* if version */ errno = 0; -@@ -911,14 +911,56 @@ decode_key_description(const char *desc, struct decoded_args *arg) +@@ -911,14 +923,56 @@ decode_key_description(const char *desc, struct decoded_args *arg) strerror(errno)); return 1; } @@ -269,7 +277,7 @@ index e413934..31d102b 100644 } static int setup_key(const key_serial_t key, const void *data, size_t datalen) -@@ -1098,7 +1140,7 @@ int main(const int argc, char *const argv[]) +@@ -1098,7 +1152,7 @@ int main(const int argc, char *const argv[]) bool try_dns = false, legacy_uid = false , env_probe = true; char *buf; char hostbuf[NI_MAXHOST], *host; @@ -278,7 +286,7 @@ index e413934..31d102b 100644 const char *oid; uid_t uid; char *keytab_name = NULL; -@@ -1109,7 +1151,6 @@ int main(const int argc, char *const argv[]) +@@ -1109,7 +1163,6 @@ int main(const int argc, char *const argv[]) const char *key_descr = NULL; hostbuf[0] = '\0'; @@ -286,7 +294,7 @@ index e413934..31d102b 100644 openlog(prog, 0, LOG_DAEMON); -@@ -1150,9 +1191,6 @@ int main(const int argc, char *const argv[]) +@@ -1150,9 +1203,6 @@ int main(const int argc, char *const argv[]) } } @@ -296,7 +304,7 @@ index e413934..31d102b 100644 /* is there a key? */ if (argc <= optind) { usage(); -@@ -1178,6 +1216,10 @@ int main(const int argc, char *const argv[]) +@@ -1178,6 +1228,10 @@ int main(const int argc, char *const argv[]) syslog(LOG_DEBUG, "key description: %s", buf); @@ -307,7 +315,7 @@ index e413934..31d102b 100644 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) -@@ -1187,33 +1229,42 @@ int main(const int argc, char *const argv[]) +@@ -1187,33 +1241,42 @@ int main(const int argc, char *const argv[]) goto out; } @@ -359,7 +367,7 @@ index e413934..31d102b 100644 else { /* no uid= or creduid= parm -- something is wrong */ syslog(LOG_ERR, "No uid= or creduid= parm specified"); -@@ -1221,6 +1272,21 @@ int main(const int argc, char *const argv[]) +@@ -1221,6 +1284,21 @@ int main(const int argc, char *const argv[]) goto out; } @@ -381,7 +389,7 @@ index e413934..31d102b 100644 /* * 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 -@@ -1266,20 +1332,7 @@ int main(const int argc, char *const argv[]) +@@ -1266,20 +1344,7 @@ int main(const int argc, char *const argv[]) * look at the environ file. */ env_cachename = @@ -403,7 +411,7 @@ index e413934..31d102b 100644 rc = setuid(uid); if (rc == -1) { -@@ -1301,18 +1354,18 @@ int main(const int argc, char *const argv[]) +@@ -1301,18 +1366,18 @@ int main(const int argc, char *const argv[]) ccache = get_existing_cc(env_cachename); /* Couldn't find credcache? Try to use keytab */ @@ -426,7 +434,7 @@ index e413934..31d102b 100644 case MS_KRB5: case KRB5: /* -@@ -1328,7 +1381,7 @@ int main(const int argc, char *const argv[]) +@@ -1328,7 +1393,7 @@ int main(const int argc, char *const argv[]) * TRY only: * cifs/bar.example.com@REALM */ @@ -435,7 +443,7 @@ index e413934..31d102b 100644 oid = OID_KERBEROS5_OLD; else oid = OID_KERBEROS5; -@@ -1385,10 +1438,10 @@ retry_new_hostname: +@@ -1385,10 +1450,10 @@ retry_new_hostname: break; } @@ -448,7 +456,7 @@ index e413934..31d102b 100644 if (rc) break; -@@ -1396,7 +1449,7 @@ retry_new_hostname: +@@ -1396,7 +1461,7 @@ retry_new_hostname: host = hostbuf; goto retry_new_hostname; default: @@ -457,7 +465,7 @@ index e413934..31d102b 100644 rc = 1; break; } -@@ -1414,7 +1467,7 @@ retry_new_hostname: +@@ -1414,7 +1479,7 @@ retry_new_hostname: rc = 1; goto out; } @@ -466,7 +474,7 @@ index e413934..31d102b 100644 keydata->flags = 0; keydata->sesskey_len = sess_key.length; keydata->secblob_len = secblob.length; -@@ -1440,11 +1493,10 @@ out: +@@ -1440,11 +1505,10 @@ out: krb5_cc_close(context, ccache); if (context) krb5_free_context(context); diff --git a/cifs-utils.changes b/cifs-utils.changes index b0529b1..3deb669 100644 --- a/cifs-utils.changes +++ b/cifs-utils.changes @@ -1,8 +1,8 @@ ------------------------------------------------------------------- Fri Apr 23 10:41:59 UTC 2021 - Aurelien Aptel -- cifs.upcall: fix CVE regression in kerberos mount; (bsc#1184815). - * add 0001-cifs.upcall-fix-CVE-regression-in-kerberos-mount.patch +- cifs.upcall: fix regression in kerberos mount; (bsc#1184815). + * add 0001-cifs.upcall-fix-regression-in-kerberos-mount.patch ------------------------------------------------------------------- Tue Mar 9 17:17:59 UTC 2021 - palcantara@suse.de diff --git a/cifs-utils.spec b/cifs-utils.spec index 024ac62..685293d 100644 --- a/cifs-utils.spec +++ b/cifs-utils.spec @@ -38,7 +38,7 @@ Source1: cifs.init Patch1: fix-sbin-install-error.patch Patch2: 0001-cifs.upcall-try-to-use-container-ipc-uts-net-pid-mnt.patch -Patch3: 0001-cifs.upcall-fix-CVE-regression-in-kerberos-mount.patch +Patch3: 0001-cifs.upcall-fix-regression-in-kerberos-mount.patch # Both SSSD and cifs-utils provide an idmap plugin for cifs.ko # /etc/cifs-utils/idmap-plugin should be a symlink to one of the 2 idmap plugins