Skip to content

Vulnerable tmpdir handling #13669

@orlitzky

Description

@orlitzky

Starting around https://github.com/pytest-dev/pytest/blob/main/src/_pytest/tmpdir.py#L156, there are some heuristics to make sure that the tmpdir is safe:

            temproot = Path(from_env or tempfile.gettempdir()).resolve()
            user = get_user() or "unknown"
            # use a sub-directory in the temproot to speed-up                                                                                                  
            # make_numbered_dir() call                                                                                                                         
            rootdir = temproot.joinpath(f"pytest-of-{user}")
            try:
                rootdir.mkdir(mode=0o700, exist_ok=True)
            except OSError:
                # getuser() likely returned illegal characters for the platform, use unknown back off mechanism                                                
                rootdir = temproot.joinpath("pytest-of-unknown")
                rootdir.mkdir(mode=0o700, exist_ok=True)
            # Because we use exist_ok=True with a predictable name, make sure                                                                                  
            # we are the owners, to prevent any funny business (on unix, where                                                                                 
            # temproot is usually shared).                                                                                                                     
            # Also, to keep things private, fixup any world-readable temp                                                                                      
            # rootdir's permissions. Historically 0o755 was used, so we can't                                                                                  
            # just error out on this, at least for a while.                                                                                                    
            uid = get_user_id()
            if uid is not None:
                rootdir_stat = rootdir.stat()
                if rootdir_stat.st_uid != uid:
                    raise OSError(
                        f"The temporary directory {rootdir} is not owned by the current user. "
                        "Fix this and try again."
                    )

This is vulnerable to symlink attacks, and probably TOCTOU attacks (see e.g. https://owasp.org/www-community/vulnerabilities/Insecure_Temporary_File). The linux kernel has protections for some of these in the fs.protected_hardlinks and fs.protected_symlinks sysctls, but they violate POSIX and are not enabled by default (though most distros enable them). In any case, they are linux-only.

For a proof of concept, create a symlink as some other user from /tmp/pytest-of-youruser to a directory you own (this will require fs.protected_symlinks to be disabled). Run pytest as yourself, and it will start writing junk to the directory that the attacker chose. It will also change the permissions on the directory he chose.

Obviously whoever wrote this was aware of these issues, but maybe not of how hard they can be to fix completely. This can be made a little bit better by telling stat() not to follow symlinks, but I would instead recommend getting a random base directory in a secure way (using the python tempdir functions) rather than using a predictable name.
There are just too many ways it can go wrong.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions