Skip to content

tarfile: IndexError instead of ReadError on a truncated GNU sparse member's extended header #158181

Description

@not-ekalabya

Bug report

TarInfo._proc_sparse() (Lib/tarfile.py:1496-1507) reads the extended
header block for a GNU GNUTYPE_SPARSE member with a plain
tarfile.fileobj.read(BLOCKSIZE) instead of _safe_read(), so a truncated
archive can hand it a short or empty buffer. The parse loop only guards
nti() with except ValueError, but nti() raises IndexError on an
empty buffer (s[0] at Lib/tarfile.py:177), which escapes that guard and
every handler in next()/TarFile.__init__.

The documented contract for malformed/truncated archives is
tarfile.ReadError (a subclass of tarfile.TarError, itself an OSError
in some versions). An application that catches that contract around
archive parsing gets an unexpected IndexError instead when the truncation
happens to land inside a GNU sparse member's extended header.

Reproduction

import io, tarfile

# Minimal GNU sparse ('S' typeflag) header with isextended=1, followed by
# a truncated (missing) extended-header block.
def build_truncated_sparse_tar():
    ...  # see attached repro_sparse.py
Traceback (most recent call last):
  ...
  File ".../Lib/tarfile.py", line 1500, in _proc_sparse
    offset = nti(buf[pos:pos + 12])
  File ".../Lib/tarfile.py", line 177, in nti
    if s[0] in (0o200, 0o377):
IndexError: index out of range

A second variant (300-byte truncated extended header instead of a fully
missing one) hits the same IndexError at a different nti() call site
(Lib/tarfile.py:1501).

Your environment

  • CPython versions tested: main (commit 64d315ac11a341fa5622718a679d0f6ef1a8dd6b)
  • Reproducible under 3.12 as well (the affected code is unchanged there);
    this is a long-standing issue, not a new regression.

Suggested fix

Read the extended header with _safe_read() (which raises ReadError on
EOF, matching the documented contract) instead of a plain read(), and/or
widen the guard to except (ValueError, IndexError). Separately, nti()
raising IndexError for empty input rather than InvalidHeaderError is
itself worth fixing, since the same except ValueError pattern appears at
other nti()/nts() call sites in the sparse-header parsing path.

I'll follow up with a PR.

Found via an automated code-review pass using GLM-5.3-flash paired with
scopegrep, a semantic code-retrieval tool; independently re-verified by
hand against the source above.

Linked PRs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions