Title: Fix DecompressMemMapReader.read returning b'' before EOF (#120) by mvanhorn · Pull Request #135 · gitpython-developers/gitdb · GitHub
Open Graph Title: Fix DecompressMemMapReader.read returning b'' before EOF (#120) by mvanhorn · Pull Request #135 · gitpython-developers/gitdb
X Title: Fix DecompressMemMapReader.read returning b'' before EOF (#120) by mvanhorn · Pull Request #135 · gitpython-developers/gitdb
Description: Summary Closes #120 DecompressMemMapReader.read(N) could return b'' before _br == _s when N was small enough that the underlying zlib.decompress(indata, N) consumed input bytes without producing output (e.g. while ingesting the zlib header / dictionary frames). Callers using the standard while chunk := stream.read(N) idiom terminated at the first empty chunk -- before reaching the actual end of the uncompressed object. Reproduced with chunk sizes 1, 4, 16 against a 13 KB stream of compressible data. Why the original guard existed The previous if dcompdat and ... guard at the recursion site was added so that compressed_bytes_read() could drive read() in a scrub-loop that intentionally manipulates _br = 0 while the inner zlib object is already past EOF. Without that guard, the scrub loop would recurse forever because _br < _s stays true. Fix Convert the recursive "refill to size" branch into an iterative loop around the existing decompress block. The loop terminates when: len(dcompdat) >= size (caller's request satisfied), OR _br >= _s (uncompressed object fully read), OR Inner zlib produced an empty chunk AND (zlib has hit EOF, OR the input window has run off self._m, OR no compressed bytes were consumed on this turn). Condition 3 preserves the compressed_bytes_read() scrub safety: once the inner zip is at EOF, the loop breaks instead of looping forever. It also bounds the truncated-stream case (zip.eof never becomes true) by the input-exhaustion guard, and bounds the crafted "many empty deflate blocks" attack -- previously this could blow Python's recursion depth at ~1500+ stored-block headers because each recursion only consumed a handful of input bytes; the iterative form walks the stream forward without growing the stack. Tests New: test_decompress_reader_chunked_read_does_not_terminate_early reads a 13 KB highly-compressible stream with chunk sizes 1, 4, 16, 64 and asserts the full payload is returned and _br == _s. Existing: gitdb/test/ (24 tests) all pass, including test_decompress_reader_special_case and test_pack -- both exercise the compressed_bytes_read scrub path. $ pytest gitdb/test ======================== 24 passed, 1 skipped in 7.10s ======================== Manual smoke import zlib from gitdb import DecompressMemMapReader data = b"hello world! " * 1000 zdata = zlib.compress(data) for chunk_size in (1, 4, 16, 64, 4096): r = DecompressMemMapReader(zdata, close_on_deletion=False, size=len(data)) out = b"" while chunk := r.read(chunk_size): out += chunk assert out == data, chunk_size (Pre-fix this fails for chunk_size in (1, 4, 16).) Truncated input is handled the same as before: read() returns whatever was decoded so far, then b'', instead of looping or raising. The crafted recursion-depth attack (~5000 empty deflate blocks ahead of one valid block) now decodes correctly instead of raising RecursionError.
Open Graph Description: Summary Closes #120 DecompressMemMapReader.read(N) could return b'' before _br == _s when N was small enough that the underlying zlib.decompress(indata, N) consumed input bytes without prod...
X Description: Summary Closes #120 DecompressMemMapReader.read(N) could return b'' before _br == _s when N was small enough that the underlying zlib.decompress(indata, N) consumed input bytes with...
Opengraph URL: https://github.com/gitpython-developers/gitdb/pull/135
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:5604a281-cef5-2909-1fb6-0f6ad8de5087 |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | 81BC:2A9DD5:28B8FB:3A2E7C:6A59D813 |
| html-safe-nonce | 0ed4f2392656fc0dfefc6925b3d8978beacdba4d858900512c170ced1c2a152c |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI4MUJDOjJBOURENToyOEI4RkI6M0EyRTdDOjZBNTlEODEzIiwidmlzaXRvcl9pZCI6Ijg5NzU1MDk3MjkzODk5NTkxODciLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 05bbdb23b1dc52b7435be169b5d9a782d6321ec5cf71bf5af8dcb5e1d9c06e16 |
| hovercard-subject-tag | pull_request:3649338387 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/gitpython-developers/gitdb/pull/135/files |
| twitter:image | https://avatars.githubusercontent.com/u/455140?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/455140?s=400&v=4 |
| og:image:alt | Summary Closes #120 DecompressMemMapReader.read(N) could return b'' before _br == _s when N was small enough that the underlying zlib.decompress(indata, N) consumed input bytes without prod... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | ba3976babb66479b1c943a8edc0777d96157da48fadc0161f9ddb219deee8353 |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/gitpython-developers/gitdb git https://github.com/gitpython-developers/gitdb.git |
| octolytics-dimension-user_id | 503709 |
| octolytics-dimension-user_login | gitpython-developers |
| octolytics-dimension-repository_id | 1126093 |
| octolytics-dimension-repository_nwo | gitpython-developers/gitdb |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 1126093 |
| octolytics-dimension-repository_network_root_nwo | gitpython-developers/gitdb |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | bfacf98c3b7ad151665d6ddd216469389872b251 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width