Opened 3 weeks ago
Last modified 2 weeks ago
#37390 assigned Bug
archive.extract() crashes with AttributeError on special tar members
| Reported by: | Dean Ruina | Owned by: | Dean Ruina |
|---|---|---|---|
| Component: | Core (Management commands) | Version: | dev |
| Severity: | Normal | Keywords: | startproject startapp archive hardening |
| Cc: | Triage Stage: | Accepted | |
| Has patch: | yes | 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.
Change History (3)
comment:1 by , 3 weeks ago
| Owner: | set to |
|---|---|
| Status: | new → assigned |
comment:2 by , 2 weeks ago
| Summary: | archive.extract() crashes with AttributeError on special tar members (and applies setuid/setgid bits) → archive.extract() crashes with AttributeError on special tar members |
|---|---|
| Triage Stage: | Unreviewed → Accepted |
Thank you for the report. I can confirm the FIFO crash. A tar member with type FIFOTYPE causes
archive.extract()to create an empty file and then raiseAttributeError:tests/utils_tests/test_archive.py
I agree that Django should reject unsupported tar members before creating a destination file. Raising
SuspiciousOperationmakes sense to meI'm less certain about including the mode change in this fix. Could you open a separate ticket for it and explain the practical concern it addresses? That would let us assess the permission change on its own merits.