Opened 46 minutes ago
Last modified 39 minutes ago
#37390 assigned Bug
archive.extract() crashes with AttributeError on special tar members (and applies setuid/setgid bits)
| Reported by: | Dean Ruina | Owned by: | Dean Ruina |
|---|---|---|---|
| Component: | Core (Management commands) | Version: | dev |
| Severity: | Normal | Keywords: | startproject startapp archive hardening |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
django.utils.archive.extract() (used by startproject/startapp --template for tar/zip templates) crashes on a tar member that is a FIFO or a character/block device: TarFile.extractfile() returns None, and TarArchive.extract() then opens the target path for writing and calls shutil.copyfileobj(None, ...), which raises AttributeError - after an empty file has already been created at the destination. So a template archive containing a device/FIFO member aborts extraction uncleanly and leaves a partial tree behind, instead of failing with a clear error.
Steps to reproduce: build a tar whose members include a FIFO (e.g. a tarfile.TarInfo with type tarfile.FIFOTYPE), then run django-admin startproject proj --template that.tar. Expected: a clear error. Actual: AttributeError: 'NoneType' object has no attribute 'read' from shutil.copyfileobj in archive.py.
While fixing that, a related hardening gap in the same function: member modes are applied verbatim with os.chmod() (BaseArchive._copy_permissions), so a member with mode 0o4755 becomes a setuid file, and TemplateCommand.apply_umask() then copies stat.S_IMODE() -- including the setuid/setgid/sticky bits -- onto the generated project file.
Proposed fix, mirroring Python's own tarfile "data" filter (PEP 706, the default in Python 3.14):
- raise
SuspiciousOperationfor tar members that are not regular files, directories or links, before anything is written (consistent with the function's existing handling of invalid paths); - mask the applied mode to
0o777(the existing behavior of preserving0o775etc. is unchanged).
This hardening is in the spirit of CVE-2021-3281 and CVE-2025-59682, which both treated this function as a boundary against hostile templates. The documented "used as is" warning on --template is unchanged.
I have a patch ready with tests and a 6.2 release note, and will open a PR once the ticket is accepted.