node:zlib: account Zstd native allocations against external memory#6210
node:zlib: account Zstd native allocations against external memory#6210
Conversation
There was a problem hiding this comment.
I'm Bonk, and I've done a quick review of your PR.
This PR wires Zstd encoder/decoder contexts through CompressionAllocator so native allocations are tracked against isolate external memory, preventing untrusted Workers from silently exhausting process memory.
Issues
-
KJ_DASSERTis debug-only — nullallocator_will crash in release (Medium severity)KJ_DASSERTcompiles to nothing in release builds. Ifallocator_is null in production (programming error / missed wiring), the code proceeds to build aZSTD_customMemwith.opaque = nullptr. The first allocation callback (AllocForBrotli) then doesstatic_cast<CompressionAllocator*>(opaque)and dereferences it — undefined behavior / segfault.This is a class invariant that should be enforced in all builds. Use
KJ_ASSERT(orKJ_REQUIRE) instead. See both locations below.
| : ZstdContext(_mode) {} | ||
|
|
||
| kj::Maybe<CompressionError> ZstdEncoderContext::initialize(uint64_t pledgedSrcSize) { | ||
| KJ_DASSERT(allocator_ != nullptr, "Zstd encoder allocator should not be null"); |
There was a problem hiding this comment.
KJ_DASSERT is stripped in release builds. If allocator_ is ever null here (e.g. a future caller forgets setAllocator), this will silently pass through and crash inside the ZSTD allocation callback when it dereferences the null opaque pointer. Use KJ_ASSERT so the invariant is enforced in all build modes:
| KJ_DASSERT(allocator_ != nullptr, "Zstd encoder allocator should not be null"); | |
| KJ_ASSERT(allocator_ != nullptr, "Zstd encoder allocator should not be null"); |
| kj::Maybe<CompressionError> ZstdDecoderContext::initialize() { | ||
| // dctx_ is created in the constructor. It can only be nullptr if ZSTD_createDCtx() | ||
| // failed due to memory allocation failure. | ||
| KJ_DASSERT(allocator_ != nullptr, "Zstd decoder allocator should not be null"); |
There was a problem hiding this comment.
Same issue as the encoder path — KJ_DASSERT is a no-op in release builds:
| KJ_DASSERT(allocator_ != nullptr, "Zstd decoder allocator should not be null"); | |
| KJ_ASSERT(allocator_ != nullptr, "Zstd decoder allocator should not be null"); |
|
Review posted successfully. Here's a summary of what I found: PR #6210 wires Zstd encoder/decoder contexts through One actionable issue found: Both |
|
The generated output of Full Type Diff |
|
Some compile errors here need to be looked at |
Motivation
Description
ZstdContextviavoid setAllocator(CompressionAllocator*)and aCompressionAllocator* allocator_field so Zstd code can use the existing external-memory accounting.ZSTD_createCCtx_advanced()/ZSTD_createDCtx_advanced()using aZSTD_customMemthat forwards toCompressionAllocator::AllocForBrotli/CompressionAllocator::FreeForZlibso native allocations are tracked.context()->setAllocator(&this->allocator)inZstdCompressionStream::initialize()and creating a temporaryCompressionAllocatorforzstdSync()and callingctx.setAllocator(&allocator)before initialization.src/workerd/api/node/zlib-util.handsrc/workerd/api/node/zlib-util.c++and preserve the original lifetime/behavior of Zstd contexts while enabling external-memory accounting.Testing
bazel test //src/workerd/api/node/tests:zlib-zstd-nodejs-test@, but the test run failed in this environment because Bazel could not be downloaded fromreleases.bazel.build(HTTP 403), so no build/test artifacts were produced.Codex Task