Windows: creating ~/.config and ~/.cache with mode=0o700 while elevated locks the user out of both directories (Python >= 3.12.4)

Open Beginner friendly
#5,190 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
python

Research direction

Start in scapy/main.py at _probe_xdg_folder and reproduce with repro_scapy_xdg.py using elevated and non-elevated Windows prompts. Check how path.mkdir(mode=0o700, exist_ok=True) behaves with Python 3.12.4 or newer. Done means Scapy can initialize its XDG directories while the interactive user and other applications retain access.

Written by the indexing model from the issue text.

Description

Brief description

On Windows with Python ≥ 3.12.4, running Scapy elevated (as it usually is, for Npcap) creates ~/.config and ~/.cache with an ACL that excludes the interactive user. From then on every application on the machine that uses those XDG base directories fails with PermissionError / EPERM, and the user cannot even read the ACL (icacls → Access is denied) until an elevated takeown + icacls /reset.

Found while debugging an unrelated app's startup crash on a user's machine: ~/.cache/scapy was the only thing in ~/.cache, created in the same minute as ~/.cache and ~/.config themselves.

flowchart LR
  A["elevated: import scapy"] --> B["_probe_xdg_folder:<br/>~/.config missing → mkdir(mode=0o700)"]
  B --> C["CPython ≥ 3.12.4 (CVE-2024-4030 fix):<br/>mode==0o700 → protected DACL SY, BA, OW"]
  C --> D["elevated token ⇒ owner = Administrators<br/>⇒ OW ≠ user"]
  D --> E["~/.config, ~/.cache:<br/>no ACE for the user, inheritance off"]
  E --> F["every other app:<br/>mkdir ~/.config/&lt;app&gt; → WinError 5 / EPERM"]

Environment

  • Scapy version: 2.7.0 (pip)
  • Python version: 3.13.7
  • Operating System: Windows 11 Pro 10.0.26200, x64, NTFS

How to reproduce

repro_scapy_xdg.py (calls Scapy's own probe functions; XDG_* pointed at fresh dirs so it doesn't touch your real ~/.config):

"""Scapy creates the XDG base dirs (~/.config, ~/.cache) with mode=0o700; on Windows + Python >= 3.12.4,
run elevated, this locks the interactive user out of both directories for every application.

Usage (Windows, Python >= 3.12.4, scapy installed):
  python repro_scapy_xdg.py create   # run from an ELEVATED prompt (as Scapy usually is, for Npcap)
  python repro_scapy_xdg.py check    # run from a NON-elevated prompt, same user
"""
import ctypes, os, pathlib, subprocess, sys

home = pathlib.Path(os.environ["USERPROFILE"])
os.environ["XDG_CONFIG_HOME"] = str(home / ".repro-scapy-config")
os.environ["XDG_CACHE_HOME"] = str(home / ".repro-scapy-cache")
elevated = bool(ctypes.windll.shell32.IsUserAnAdmin())
print(f"python {sys.version.split()[0]}  user={os.getlogin()}  elevated={elevated}")

if sys.argv[1] == "create":
    import scapy, scapy.main as m
    print(f"scapy {scapy.__version__}")
    print("created:", m._probe_config_folder("scapy"), m._probe_cache_folder("scapy"))
    for d in ("XDG_CONFIG_HOME", "XDG_CACHE_HOME"):
        print(subprocess.run(["icacls", os.environ[d]], capture_output=True, text=True).stdout)

if sys.argv[1] == "check":
    for d in ("XDG_CONFIG_HOME", "XDG_CACHE_HOME"):
        print(subprocess.run(["icacls", os.environ[d]], capture_output=True, text=True).stdout.strip())
        try:
            (pathlib.Path(os.environ[d]) / "someotherapp").mkdir(parents=True, exist_ok=True)
            print(f"mkdir {d}/someotherapp: ok")
        except OSError as e:
            print(f"mkdir {d}/someotherapp: {type(e).__name__}: {e}")

Actual result

=== create (elevated)
python 3.13.7  user=Lukem  elevated=True
scapy 2.7.0
created: C:\Users\Lukem\.repro-scapy-config\scapy C:\Users\Lukem\.repro-scapy-cache\scapy
C:\Users\Lukem\.repro-scapy-config NT AUTHORITY\SYSTEM:(OI)(CI)(F)
                                   BUILTIN\Administrators:(OI)(CI)(F)
                                   OWNER RIGHTS:(OI)(CI)(F)

C:\Users\Lukem\.repro-scapy-cache NT AUTHORITY\SYSTEM:(OI)(CI)(F)
                                  BUILTIN\Administrators:(OI)(CI)(F)
                                  OWNER RIGHTS:(OI)(CI)(F)

=== check (non-elevated)
python 3.13.7  user=Lukem  elevated=False
Successfully processed 0 files; Failed processing 1 files
mkdir XDG_CONFIG_HOME/someotherapp: PermissionError: [WinError 5] Access is denied: 'C:\\Users\\Lukem\\.repro-scapy-config\\someotherapp'
Successfully processed 0 files; Failed processing 1 files
mkdir XDG_CACHE_HOME/someotherapp: PermissionError: [WinError 5] Access is denied: 'C:\\Users\\Lukem\\.repro-scapy-cache\\someotherapp'

Owner of both directories is BUILTIN\Administrators; AreAccessRulesProtected = True. Non‑elevated Scapy produces the same DACL but with owner = user, so it is harmless — the damage needs elevation, which Scapy on Windows effectively requires.

Expected result

Other applications can still use ~/.config and ~/.cache after Scapy has run.

Where

scapy/main.py _probe_xdg_folder @ 5166573:

if not path.exists():
    # ~ folder doesn't exist. Create according to spec
    # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
    # "If, when attempting to write a file, the destination directory is
    # non-existent an attempt should be made to create it with permission 0700."
    path.mkdir(mode=0o700, exist_ok=True)

path here is the XDG base directory (~/.config, ~/.cache, ~/.local/share), not …/scapy.

The interaction is with CPython's CVE‑2024‑4030 change (posixmodule.c os_mkdir_impl): on Windows, exactly mode == 0o700 is now special‑cased into D:P(A;OICI;FA;;;SY)(A;OICI;FA;;;BA)(A;OICI;FA;;;OW). OW is the object owner, which for an elevated token is BUILTIN\Administrators. Reported to CPython separately; Scapy is the concrete trigger and can avoid it independently:

  • Don't pass mode=0o700 on Windows (mode is otherwise ignored there; only this exact value has an effect). E.g. path.mkdir(mode=0o700 if os.name != "nt" else 0o777, exist_ok=True).
  • Or create only the scapy leaf (path.joinpath(*cf).mkdir(parents=True, ...)) and leave the base directory's permissions to the OS defaults — the XDG 0700 recommendation is about POSIX modes and has no sane mapping to a shared Windows profile folder.

Related resources

Dominant language
Python
Stars
12.6k
Forks
2.2k
Avg merge
1d 9h
Merged PRs (30d)
60

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from secdev/scapy

All issues in secdev/scapy

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.