Skip to content

ZipFile.close() writes wrong offsets after a failed repack() #156434

Description

@fedonman

Bug description:

_ZipRepacker.repack() updates ZipInfo.header_offset for each member before moving that member's bytes, so a failing _copy_bytes() leaves the in-memory offsets describing a layout that was never written. remove() has already set _didModify, so a later normal close() commits a central directory built from those offsets: the caller handles the OSError, closes cleanly, and gets an archive that zipfile itself cannot read.

import io, zipfile

class Flaky(io.BytesIO):
    countdown = None
    def write(self, b):
        if self.countdown is not None:
            self.countdown -= 1
            if self.countdown < 0:
                raise OSError(28, 'No space left on device')
        return super().write(b)

buf = io.BytesIO()
with zipfile.ZipFile(buf, 'w') as zf:
    for c in 'abcd':
        zf.writestr(c + '.txt', c.upper().encode() * 5000)

flaky = Flaky(buf.getvalue())
with zipfile.ZipFile(flaky, 'a') as zf:
    flaky.countdown = 1
    try:
        zf.repack([zf.remove('b.txt')], chunk_size=4096)
    except OSError as exc:
        print('repack raised:', exc)
    flaky.countdown = None        # space freed; the caller closes normally

with zipfile.ZipFile(flaky) as zf:
    print('testzip:', zf.testzip())
repack raised: [Errno 28] No space left on device
testzip: c.txt

Expected: after the caller has handled the OSError, close() should either leave a readable archive or raise, rather than committing offsets that no write produced.

remove() and repack() are new in 3.16, so no released version is affected.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    stdlibStandard Library Python modules in the Lib/ directory
    on Aug 26, 2026
  2. added
    3.16new features, bugs and security fixes
    on Aug 27, 2026
  3. danny0838 commented on Oct 12, 2026

    @danny0838
    Contributor

    Do you have any real-world scenario of a failed repack in mind? I wonder if the No space left on device error is possible since the repack is shrinking a file.

    Strictly speaking, a repack could fail after some entries have been moved but before the remaining entries have been processed. In that case, restoring the original header offsets alone would not necessarily restore the archive to a consistent state.

    Also, repack sets _didModify after moving all entries, and it would not be set if an exception is raised during moving physical entries. (But may be set beforehand by remove)

    More generally, in-place archive modifications are not inherently atomic. This applies even to a simple ZipFile.write(). If we want to guarantee that a failed modification leaves the original archive intact, we would need a transactional approach, such as writing to a temporary file and replacing the original only after the operation succeeds. However, this would probably be out of scope for the zipfile library.

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

    3.16new features, bugs and security fixesstdlibStandard Library Python modules in the Lib/ directorytype-bugAn unexpected behavior, bug, or error

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions