SQLite zipfile returned more bytes than decompression produced
A malformed ZIP entry could expose uninitialized heap bytes through SQLite's optional zipfile extension. The confirmed fix uses the decompressor's actual output length when returning the SQL blob.
An allocation's capacity is not the length of the data written into it. A 6 December report to the SQLite forum shows that distinction being lost in the optional zipfile extension. A malformed ZIP entry can advertise more uncompressed data than its compressed stream actually produces. The extension allocated space for the advertised size, then returned that entire space as an SQL blob.
The unused tail could contain uninitialized heap bytes. On 7 December, Stephan Beal pointed the reporter to a corrective commit. The reporter confirmed on 8 December that it resolved the issue. The public record therefore supports an account of both the mechanism and the confirmed source fix, without needing to infer a packaged release or a broader exploitation history.
I value SQLite's small integration model, but extensions still add their own parsers and data boundaries. A program using the engine for local tables is different from a program asking an optional module to decode an archive supplied by somebody else. The relevant exposure here depends on the second operation being available and reachable.
The archive supplies an expected size
The report identifies szUncompressed in the ZIP central directory as the source of the expected output length. The column-reading code passes that length to zipfileInflate(). That function allocates an output buffer with sqlite3_malloc() and gives the buffer to zlib's inflation code.
Allocating the claimed capacity does not establish that decompression will fill it. A malformed archive can disagree with itself. Its metadata can claim a large uncompressed length while the actual compressed stream reaches its end after producing fewer bytes. Reaching the end of a stream is therefore insufficient to validate the claimed result size.
In the code shown by the reporter, a successful inflate() call leads to sqlite3_result_blob() with the original expected length. The function exposes that many bytes from the allocation. If the decompressor initialized only a prefix, the remaining bytes come from memory that does not belong to the archive's decoded contents.
This is a length-accounting defect with a confidentiality consequence. The output buffer need not overflow for information to escape. The allocation can be large enough for every returned byte, while some of those bytes have never been initialized as part of the current result. Bounds and initialization are separate properties.
The fix returns the actual decoded length
The corrective check-in is dated 6 December at 23:58 UTC. Its original comment says the change prevents returning too many bytes for a malformed ZIP file. The zipfile source at that check-in makes the changed result contract concrete.
The function still allocates according to the expected size. After successful decompression, it uses str.total_out for the blob length. That value describes what the decompressor actually produced. The returned SQL value therefore excludes the unwritten tail even when the metadata requested more space.
The relevant result call is small enough to show directly. This is the corrected call in the source, with its existing ownership callback.
sqlite3_result_blob(pCtx, aRes, (int)str.total_out, zipfileFree);
Changing the result length addresses the disclosure without needing to zero the entire spare capacity first. Zeroing would make spare bytes predictable, but would still return bytes that were not part of the decompressed stream. Using the produced length preserves the actual data boundary as well as excluding uninitialized memory.
This is also why the expected length and produced length should remain distinct in an explanation. The former is an input claim used to size an allocation. The latter is an execution result used to describe valid output. Reusing the input claim at the output boundary was the mistake.
Availability of the extension determines exposure
The zipfile module documentation describes a table-valued function that accepts either a filesystem path or a blob containing an archive. Its data column returns decompressed entry contents for supported compression methods. That is the result path relevant to the report.
The module is included in most command-line-shell builds. It can also be compiled as a loadable extension or linked into an application and registered with a connection. Those integration options do not imply that every program linking the standard SQLite library automatically has zipfile available.
The reporter observed the issue in a development checkout and in an official compiled Linux shell. The version string shown for the checkout is evidence about that test binary, not proof that a release with that version number had shipped. An exposure statement should identify the extension and the decoding path before making assumptions from the engine's general version label.
Similarly, a read-only SQL query is not evidence that no confidentiality risk exists. Reading the extension's data column invokes decompression of attacker-controlled input. The query need not update an ordinary table to return bytes the caller should not receive. The security boundary lies in the parser's output, not in whether SQL modifies the database.
The demonstrated leak has a specific scope
The report shows a heap address among bytes returned after the actual decoded contents. It suggests that addresses could assist attacks against other memory-corruption defects. That is a possible use of the disclosed information, not evidence that this issue alone supplies arbitrary code execution or arbitrary memory selection.
The actual bytes depend on the allocation and the process's heap state. A particular pointer in a demonstration should not be treated as a stable output contract. The supported finding is that the module can expose an uninitialized tail when an archive's advertised size exceeds the decompressed output.
I prefer that precise scope to assigning a universal severity from the word SQLite. A tool that imports untrusted archives has a relevant parsing boundary. An application without this extension does not acquire that boundary simply because it uses SQLite for its own data. The same distinction determines which source component needs the fix.
As of 8 December, the report includes a source correction and the original reporter's confirmation. It does not identify the first packaged SQLite release containing that correction. The concrete repair is available in check-in 9766b47beb, where the SQL blob length comes from the decompressor's produced-byte count. That leaves archive metadata responsible for capacity while actual decoded output determines which bytes the caller receives.