Repository navigation
deps: run DuckDB 1.5.5 with the Brotli CVE-2026-111497 fix - #158
fuziontech wants to merge 1 commit into
Conversation
Upstream duckdb 1.5.5 (PyPI wheel and the upstream CLI release we installed) still vendors the pre-fix Brotli decoder: DecodeBlockTypeAndLength returns a BROTLI_BOOL that conflates a malformed stream with needs-more-input, so the fast path carries on with stale state. A crafted brotli stream writes past the decoder's literal buffer with an attacker-chosen offset. millpond reads parquet through this wheel (INSERT ... SELECT over ducklake tables), so a brotli-compressed data file is attacker-influenced input into memory-unsafe C++ in the ingestion process. Pin the PostHog build of the same version (v1.5.5-posthog.6), which carries the fix, for the two platforms this image ships; everything else keeps resolving duckdb from PyPI. Dockerfile installs the matching CLI from the same release. Verified: installed _duckdb...so has the fixed decoder's error string (strings | grep -c BLOCK_SWITCH == 1, 0 with PyPI 1.5.5); the ducklake extension-pin canary still passes (same extension build SHA); tests/unit 1445 passed, 1 xfailed. Co-authored-by: Shelley <shelley@exe.dev>
There was a problem hiding this comment.
Reviewed the PR description, diff, and existing discussion at 4205307. Checked platform source selection, lockfile consistency, release asset names and wheel hashes, Docker installation, and the existing DuckLake extension canary. Considered correctness, security, compatibility, missing coverage, overengineering, and scope creep. Tests, builds, linters, and dependency installs were not run, as requested.
No issues found.
— Robo Bill v2 (gpt-6.1-sol, high reasoning)
|
CI says this is not ready: Reproduced locally in this branch's venv ( What that rules out and what it leaves:
Keeping this in draft until we know why. Two ways forward:
@jghoman flagging you since this one is assigned to you: the security side is fine, the packaging side is not. Narrowed downI think I have the mechanism. for (auto &entry : db_config.GetExtensionSettings()) { // name -> ConfigOption
auto setting_index = option.setting_index.GetIndex(); // build-specific index
Value value;
if (!from.TryGetCurrentSetting(entry.first, value)) continue; // read by NAME -> VARCHAR 'Etc/UTC'
to.config.user_settings.SetUserSetting(setting_index, value); // write by INDEX -> lands on UINT64
}Read by name, write by index. That is only sound when both contexts share the same setting table. With the fork wheel the index for If that reading is right, the fix is either (a) make the copy name-based ( |
Draft — the wheel this points at is a fork build, so I'd like a second pair of eyes on the pin (and the canary) before this ships.
Why
Upstream DuckDB 1.5.5 still vendors the pre-fix Brotli decoder (
third_party/brotli1.1.0; upstream fix is duckdb/duckdb#27791, unmerged). CVE-2026-111497 makes that decoder memory-unsafe on crafted streams:DecodeBlockTypeAndLengthreturns aBROTLI_BOOLthat conflates malformed stream with needs more input, so theBROTLI_SAFEfast path drops the error and continues with stale state. Reproduced against DuckDB 1.1.0 — theNEEDS_MORE_INPUTtail copy writes past the 8-byte literal buffer, the overflow index lands onbuffer_length, and the attacker picks the offset, reaching ~18 controlled bytes across the decoder state and a controlledfree()(PostHog/duckdb#8).millpond reads parquet through this decoder on every
INSERT ... SELECTover a ducklake table, so a brotli-compressed data file is attacker-influenced input into memory-unsafe C++ in the ingestion process. Nothing here writes brotli (pyhoglake.parquet_write_optionsallows onlynone/snappy/zstd), so the exposure is ingested data, not our own output.What
PostHog/duckdbreleased v1.5.5-posthog.6 (same base version, plus the fix — ported byte-for-byte to DuckDB's C++ transliteration, plus the state-leak fix incolumn_reader.cpp). Point the two platforms this image ships at its wheels:macOS/Windows developers keep the PyPI wheel via the fallback entry (source markers must be disjoint, hence the odd-looking complement). The
[[tool.uv.index]] pypiblock exists only so the fallback source can name it.Dockerfileinstalls the CLI from the same release (ARG DUCKDB_RELEASE=v1.5.5-posthog.6) instead ofduckdb/duckdb, so the binary in the image and the Python module are the same decoder — otherwise anything shelling out toduckdbkeeps the bug.Verification
strings .venv/.../_duckdb.cpython-312-x86_64-linux-gnu.so | grep -c BLOCK_SWITCH→ 1 (0 on PyPI 1.5.5 — that's the new error string the fixed decoder returns).strings duckdb | grep -c BLOCK_SWITCH→ 1, and it runs (select version()).tests/unit/test_ducklake_pin.pypasses — the ducklake extension loaded under the fork wheel reports the same build SHA we validated against, somillpond/ducklake.py/schema.pybehaviour is unchanged.tests/unit: 1445 passed, 1 xfailed.uv syncfails loudly on a platform/ABI with no matching source instead of silently falling back, e.g. CPython 3.14 here: "duckdb … binary distribution is incompatible with the current platform". The image is 3.12, which matches the wheels.Rollback
Drop the three
duckdbentries from[tool.uv.sources](and the now-unused index block), setDUCKDB_RELEASEback / restore the upstream URL, re-lock. That returns to the vulnerable decoder, so it's a "fork build misbehaves" escape hatch, not a security-equivalent one.Refs #157, PostHog/duckgres#1267.