Skip to content

zlib: reject reset while a zstd frame is incomplete - #66088

Open
xia-chao wants to merge 1 commit into
nodejs:mainfrom
xia-chao:zlib-flush-reset-corrupt-fix
Open

xia-chao wants to merge 1 commit into
nodejs:mainfrom
xia-chao:zlib-flush-reset-corrupt-fix

Conversation

@xia-chao

Copy link
Copy Markdown
Contributor

Calling reset() on a zstd compressor while a frame is still in progress left
the stream in a state where it produced output that could not be decompressed,
without reporting anything. This makes reset() throw instead.

Fixes #66087.

The problem

reset() drops the state of the frame currently being compressed. Any bytes
that were already written out — by flush(), or by an earlier write() that
filled the output buffer — cannot be taken back, so they stay at the start of
the output stream. The next frame is then appended to that fragment, and the
result decodes as corruption.

stream.write(Buffer.from('hello'));
await new Promise((r) => stream.flush(r));  // emits 14 bytes, frame unfinished
stream.reset();                             // frame state dropped here
stream.end(Buffer.from('world'));           // emits another 14 bytes, new frame
// 28 bytes total: fragment + complete frame -> ZSTD_error_corruption_detected

Without the reset() the same input produces 22 bytes and decodes to
helloworld, because end() continues the existing frame (8 bytes) instead of
starting a new one (14 bytes).

zstd requires buffers to be fully flushed before a new compression job starts —
see ZSTD_compressStream2 in deps/zstd/lib/zstd.h:

Before starting a new compression job, or changing compression parameters, it
is required to fully flush internal buffers.

ZSTD_compressStream2 returns non-zero while a frame is unfinished, which is
how a caller is meant to detect this. The compressor context was not keeping
track of that.

The fix

ZstdCompressContext now records whether the current frame has completed. A
frame is complete once ZSTD_compressStream2 has been called with ZSTD_e_end
and returned 0. ResetStream() refuses with ERR_ZLIB_INCOMPLETE_FRAME
otherwise:

Error [ERR_ZLIB_INCOMPLETE_FRAME]: Cannot reset a zstd stream with an incomplete
frame; end the frame or discard the output produced so far

This follows the existing behaviour for the other invalid reset case, where
reset() during a write already throws.

After the change the repro above reports the error instead of emitting a
corrupt stream.

What is not changed

  • reset() before any write still works.
  • flush() followed by end() still works and still produces a valid stream.
  • reset() on a finished frame is unaffected.

Tests

test/parallel/test-zlib-zstd-reset-incomplete-frame.js covers the reset
mid-frame case, the valid flush() + end() sequence, and the valid
reset() before writing.

Ran against a --debug-node --debug-symbols build on Linux x86_64:

$ ./out/Release/node --test test/parallel/test-zlib-zstd-reset-incomplete-frame.js
✔ ZstdCompress reset throws when a frame is incomplete
✔ ZstdCompress flush followed by end still produces a valid stream
✔ ZstdCompress reset before any write still works
ℹ pass 3
ℹ fail 0

Existing zlib tests:

$ ./out/Release/node --test test/parallel/test-zlib-zstd.js \
    test/parallel/test-zlib-zstd-reset.js \
    test/parallel/test-zlib-zstd-flush.js \
    test/parallel/test-zlib-reject-garbage-after-end.js \
    test/parallel/test-zlib-zstd-pledged-src-size.js \
    test/parallel/test-zlib-zstd-dictionary.js
ℹ pass 19
ℹ fail 0

Notes

gzip and brotli hit the same class of problem for the same call sequence (their
output is undecodable too). I kept this PR to zstd because that is where the
failure is completely silent, but I am happy to look at the other two codecs
separately if that is wanted.

@github-actions

Copy link
Copy Markdown
Contributor

Welcome to Node.js, and thank you for your first contribution!

Before review, please take a moment to read:

Please make sure every commit is signed off. For a first pull request, GitHub Actions require collaborator approval and Jenkins CI must be started by a collaborator or triager, so an initial wait is normal.

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. zlib Issues and PRs related to the zlib module and its compression dependencies. labels Sep 17, 2026
@xia-chao

xia-chao commented Sep 17, 2026

Copy link
Copy Markdown
Contributor Author

欢迎来到Node.js的世界,感谢您的首次贡献!

在进行审查之前,请花点时间阅读以下内容:

请确保每次提交都已签名确认。对于首次提交的拉取请求,GitHub Actions 需要协作者批准,Jenkins CI 也必须由协作者或问题处理人员启动,因此初始等待是正常的。

I'm not a beginner, do you know?

@MikeMcC399

Copy link
Copy Markdown
Contributor

I'm not a beginner, do you know?

You will get this message on any new PR until a commit from a PR that you have submitted has actually landed in the default main branch. Until then, GitHub classifies you as "First-time contributor" in this repo.

Your other PR #65521 is approved, but it seems to have stalled.

@MikeMcC399

This comment was marked as resolved.

@codecov

codecov Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.27%. Comparing base (99f0dde) to head (f6cd7c6).
⚠️ Report is 20 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #66088      +/-   ##
==========================================
- Coverage   90.28%   90.27%   -0.01%     
==========================================
  Files         789      789              
  Lines      271625   271633       +8     
  Branches    51849    51852       +3     
==========================================
- Hits       245228   245210      -18     
- Misses      16863    16903      +40     
+ Partials     9534     9520      -14     
Files with missing lines Coverage Δ
src/node_errors.h 86.95% <ø> (ø)
src/node_zlib.cc 80.55% <100.00%> (+0.76%) ⬆️

... and 28 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Resetting a ZstdCompress stream while a frame is still in
progress dropped the frame state, but any bytes already
written out stayed at the start of the output stream. The
next frame was then appended to that fragment, so the
resulting stream could not be decompressed. The failure was
silent: the compressor reported no error at all.

zstd requires internal buffers to be fully flushed before a
new compression job starts. Track whether the current frame
has completed and throw ERR_ZLIB_INCOMPLETE_FRAME from
reset() otherwise.

Signed-off-by: bun-unsafe <bun-unsafe@users.noreply.github.com>
@xia-chao
xia-chao force-pushed the zlib-flush-reset-corrupt-fix branch from 49d577d to f6cd7c6 Compare September 17, 2026 19:57
@xia-chao

Copy link
Copy Markdown
Contributor Author

@MikeMcC399
Thank you for pointing out the formatting issue!

@inoway46 inoway46 added the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Sep 18, 2026
@github-actions github-actions Bot removed the request-ci Add this label to start a Jenkins CI on a PR. Only starts once the PR has an approving review. label Sep 18, 2026
@nodejs-github-bot

Copy link
Copy Markdown
Collaborator

@MikeMcC399

Copy link
Copy Markdown
Contributor

I'm not a beginner, do you know?

Your other PR #65521 is approved, but it seems to have stalled.

PR #65521 has now landed, so congratulations on being upgraded to "Contributor" 🎉 !
You should not see the message for "First-time contributors" on any new PRs you open.

@xia-chao

Copy link
Copy Markdown
Contributor Author

@MikeMcC399
Thank you very much for your help. I really want to contribute my efforts to Node
But during the first PR, it hit me a bit and I haven't been merged for so long
Thank you, we will also meet frequently under the node repository in the future

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

Labels

c++ Issues and PRs that require attention from people who are familiar with C++. needs-ci PRs that need a full CI run. zlib Issues and PRs related to the zlib module and its compression dependencies.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

zlib: reset() after flush() silently produces an undecodable zstd stream

5 participants