Skip to content

Keep negative-size reads within upload chunk boundaries - #423

Merged
alexgromero merged 2 commits into
boto:developfrom
rioyu123:codex/s3transfer-negative-read
Oct 19, 2026
Merged

alexgromero merged 2 commits into
boto:developfrom
rioyu123:codex/s3transfer-negative-read

Conversation

@rioyu123

@rioyu123 rioyu123 commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #422.

ReadFileChunk.read() passed negative sizes straight to the underlying file. read(-1) returned the rest of the file instead of the rest of the chunk. Both s3transfer.utils.ReadFileChunk and the legacy s3transfer.ReadFileChunk now treat a negative size like None and read only to the end of the chunk.

The legacy reader also clamps the remaining length at zero after seeking past the chunk boundary, matching utils.ReadFileChunk. Reads from that position return empty bytes without moving the position or reporting extra bytes read. Seeking back into the chunk still allows normal reads. The seek() implementation and callback mechanism are unchanged.

Added unit regressions for both classes with a nonzero offset, a partial first read, end-of-chunk reads and callback totals. The legacy seek-past-boundary regression covers None, negative, zero and positive read sizes, position preservation, callbacks and seeking back into the chunk. A functional multipart upload test checks the part contents when its body consumer uses read(-1).

Validation on the updated code:

  • Linux, Python 3.12 and 3.14: scripts/ci/run-tests passes on both versions. Each has 683 passed / 54 skipped with threaded execution, then 676 passed / 61 skipped with serial execution.
  • Windows, Python 3.12: affected unit/functional suites and changelog checks pass, 200 tests plus 16 subtests.
  • The new seek-past-boundary cases fail on the previous PR head and pass with this change.
  • Pre-commit checks pass for both changed files.

No tests were run against live S3. The change only affects read-all/negative-size reads and reads after seeking past the chunk. Normal in-bounds, positive-size reads behave the same as before.

@rioyu123
rioyu123 requested a review from a team as a code owner October 10, 2026 16:29
@codecov-commenter

codecov-commenter commented Oct 16, 2026 •

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 81.64%. Comparing base (8647a30) to head (22d10e9).
⚠️ Report is 55 commits behind head on develop.
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop     #423      +/-   ##
===========================================
+ Coverage    80.28%   81.64%   +1.36%     
===========================================
  Files           16       16              
  Lines         3013     3188     +175     
===========================================
+ Hits          2419     2603     +184     
+ Misses         594      585       -9     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@alexgromero alexgromero left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the report and the fix! This looks good overall and fixes the reported issue. I left one minor suggestion for a related legacy edge case, if you're open to including it in this PR.

Comment thread s3transfer/__init__.py

def read(self, amount=None):
if amount is None:
if amount is None or amount < 0:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After seeking beyond the chunk boundary, read() or read(-1) can still read from the underlying file because the remaining size is negative. Would you mind calculating amount_left = max(self._size - self._amount_read, 0) here matching utils.ReadFileChunk and adding a regression test for that case?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, good catch. I've added the same clamp to the legacy reader, matching utils.ReadFileChunk, plus a regression test that seeks past the chunk boundary but stays inside the underlying file. It covers read-all, negative, zero and positive sizes. It checks that nothing is read, the position doesn't move and the callback reports 0 bytes, and that seeking back still reads the chunk normally. The new cases fail on the previous head and pass now. The unit and functional suites pass in threaded and serial modes on Linux with Python 3.12 and 3.14.

@alexgromero alexgromero left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing the feedback and adding the additional regression coverage! This looks good to me :shipit:

@alexgromero
alexgromero merged commit cddafab into boto:develop Oct 19, 2026
68 of 69 checks passed
Alan4506 pushed a commit that referenced this pull request Oct 19, 2026
* release-0.19.3:
  Bumping version to 0.19.3
  Keep negative-size reads within upload chunk boundaries (#423)
  Add GitHub issue templates
  Bump https://github.com/astral-sh/ruff-pre-commit (#421)
  Bump https://github.com/astral-sh/ruff-pre-commit (#414)
  Bump the github-actions group across 1 directory with 6 updates (#420)
  Bump zizmorcore/zizmor-action in the github-actions group (#412)
  Bump https://github.com/astral-sh/ruff-pre-commit (#411)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ReadFileChunk.read() with a negative size reads past the end of the chunk

3 participants