Skip to content

Commit 4bf5715

Browse files
committed
fix: allow thrift 0.25.x and clear additional Apache Thrift CVEs
Widen the thrift constraint from ~=0.24.0 to >=0.24.0,<0.26.0 so 0.25.0 can be resolved. thrift 0.25.0 fixes 61 CVEs across language bindings, several affecting the Python binding this connector uses, including CVE-2026-66858 (skip() recursion-limit bypass in the Python accelerator) and CVE-2026-85494 (framed transport / binary protocol size a read buffer from a peer-declared length with no effective maximum). thrift 0.25.0 also changes TBinaryProtocol's default string_length_limit from unbounded (None) to ~15.6 MiB (DEFAULT_MAX_FRAME_SIZE) as part of that same CVE-2026-85494 fix. This connector already bounds its own result stream via the TLS-verified, server-negotiated buffer_size_bytes (default 100 MiB), and inline Arrow result batches in TFetchResultsResp routinely exceed thrift's new ~15.6 MiB cap, so a bare version bump would regress large-result-set reads. Construct TBinaryProtocol with explicit string_length_limit=None and container_length_limit=None to preserve pre-0.25 unbounded behavior for the connector's own already-bounded, trusted-server result stream. 0.25.0 ships the same prebuilt wheel matrix as 0.24.0 (manylinux2014/macOS/musl/Windows, cp310-cp314), so the DBR LTS build-time packaging risk that originally capped this dependency (#798, #840) does not apply; the DBR LTS Install CI gate remains the authoritative check. Closes #969 Signed-off-by: Paddy Hannon <pih@ehukai.com> AOS-Session: 01a10c87-3242-7517-a60e-279091b796c3 AOS-Session: pi-1791211537-21291-e08bda26 AOS-Commit-Time: 2026-10-05T15:03:26Z
1 parent 01564c7 commit 4bf5715

4 files changed

Lines changed: 145 additions & 46 deletions

File tree

‎poetry.lock‎

Lines changed: 46 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎pyproject.toml‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,26 @@ python = "^3.10"
2121
# a wheel and never runs setup.py on DBR LTS -- the build-time break cannot
2222
# trigger. The `DBR LTS Install` CI check (.github/workflows/dbr-lts-install.yml)
2323
# installs the built artifact on real DBR LTS clusters and is the authoritative
24-
# gate for this. Cap at <0.25.0: thrift is pre-1.0, each 0.x minor can carry
25-
# breaking changes or packaging regressions (see 0.23.0), so bump this
26-
# deliberately once a new minor ships and the DBR-LTS gate proves it safe.
27-
thrift = "~=0.24.0"
24+
# gate for this.
25+
#
26+
# Ceiling raised to <0.26.0 for 0.25.0 (released 2026-09-30, THRIFT-6067 series
27+
# follow-up), which fixes 61 CVEs across language bindings, several affecting
28+
# the Python binding this connector uses (see #969), e.g. CVE-2026-66858
29+
# (skip() recursion-limit bypass in the Python accelerator) and CVE-2026-85494
30+
# (framed transport / binary protocol size a read buffer from a peer-declared
31+
# length with no effective maximum). 0.25.0 ships the same prebuilt wheel
32+
# matrix as 0.24.0 (manylinux2014/macOS/musl/Windows, cp310-cp314), so the
33+
# DBR LTS build-time risk above does not apply; the `DBR LTS Install` CI gate
34+
# remains the authoritative check. thrift is pre-1.0, so keep this ceiling one
35+
# minor ahead and bump deliberately once a newer minor ships and the DBR-LTS
36+
# gate proves it safe -- do not use an open-ended `^`/`>=` constraint.
37+
#
38+
# NOTE: 0.25.0 also changes TBinaryProtocol's default `string_length_limit`
39+
# from unbounded to ~15.6 MiB (see the explicit `string_length_limit=None` /
40+
# `container_length_limit=None` passed in thrift_backend.py's protocol
41+
# construction, which preserves the connector's pre-0.25 unbounded behavior
42+
# for its own already-bounded, trusted-server result stream).
43+
thrift = ">=0.24.0,<0.26.0"
2844
pandas = [
2945
{ version = ">=1.2.5,<4.0.0", python = ">=3.10,<3.13" },
3046
{ version = ">=2.2.3,<4.0.0", python = ">=3.13" }

‎src/databricks/sql/backend/thrift_backend.py‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@
7676
TIMESTAMP_AS_STRING_CONFIG = "spark.thriftserver.arrowBasedRowSet.timestampAsString"
7777
DEFAULT_SOCKET_TIMEOUT = float(900)
7878

79+
# thrift>=0.25.0 changed TBinaryProtocol's default string_length_limit from
80+
# unbounded (None) to DEFAULT_STRING_LENGTH_LIMIT (DEFAULT_MAX_FRAME_SIZE,
81+
# ~15.6 MiB) as part of its CVE-2026-85494 fix (peer-declared length with no
82+
# effective maximum). That default is sized for untrusted/unbounded peers;
83+
# this connector already bounds its own result stream via the TLS-verified,
84+
# server-negotiated `buffer_size_bytes` (default 100 MiB, see client.py's
85+
# DEFAULT_RESULT_BUFFER_SIZE_BYTES) and the inline Arrow batches in
86+
# TFetchResultsResp routinely exceed thrift's new ~15.6 MiB cap. Pass these
87+
# explicitly so upgrading thrift does not silently cap -- or regress -- the
88+
# connector's own, already-bounded result size behavior.
89+
THRIFT_BINARY_PROTOCOL_STRING_LENGTH_LIMIT = None
90+
THRIFT_BINARY_PROTOCOL_CONTAINER_LENGTH_LIMIT = None
91+
7992
# see Connection.__init__ for parameter descriptions.
8093
# - Min/Max avoids unsustainable configs (sane values are far more constrained)
8194
# - 900s attempts-duration lines up w ODBC/JDBC drivers (for cluster startup > 10 mins)
@@ -237,7 +250,11 @@ def __init__(
237250
self._transport.setTimeout(timeout and (float(timeout) * 1000.0))
238251

239252
self._transport.setCustomHeaders(dict(http_headers))
240-
protocol = thrift.protocol.TBinaryProtocol.TBinaryProtocol(self._transport)
253+
protocol = thrift.protocol.TBinaryProtocol.TBinaryProtocol(
254+
self._transport,
255+
string_length_limit=THRIFT_BINARY_PROTOCOL_STRING_LENGTH_LIMIT,
256+
container_length_limit=THRIFT_BINARY_PROTOCOL_CONTAINER_LENGTH_LIMIT,
257+
)
241258
self._client = TCLIService.Client(protocol)
242259

243260
try:

‎tests/unit/test_thrift_backend.py‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,67 @@ def test_headers_are_set(self, t_http_client_class):
204204
{"header": "value"}
205205
)
206206

207+
@patch("databricks.sql.auth.thrift_http_client.THttpClient")
208+
def test_binary_protocol_preserves_unbounded_string_and_container_limits(
209+
self, t_http_client_class
210+
):
211+
"""thrift>=0.25.0 changed TBinaryProtocol's default string_length_limit
212+
from unbounded (None) to ~15.6 MiB (DEFAULT_STRING_LENGTH_LIMIT) as part
213+
of its CVE-2026-85494 fix. The connector must pass these limits through
214+
explicitly so a thrift upgrade cannot silently cap -- or regress -- the
215+
size of result data (e.g. inline Arrow batches) it can read, since the
216+
connector already governs its own result size via buffer_size_bytes.
217+
"""
218+
import thrift.protocol.TBinaryProtocol
219+
220+
with patch(
221+
"thrift.protocol.TBinaryProtocol.TBinaryProtocol"
222+
) as mock_protocol_class:
223+
ThriftDatabricksClient(
224+
"foo",
225+
123,
226+
"bar",
227+
[],
228+
auth_provider=AuthProvider(),
229+
ssl_options=SSLOptions(),
230+
http_client=MagicMock(),
231+
)
232+
233+
_, kwargs = mock_protocol_class.call_args
234+
self.assertIsNone(kwargs.get("string_length_limit"))
235+
self.assertIsNone(kwargs.get("container_length_limit"))
236+
237+
def test_binary_protocol_reads_result_larger_than_thrift_default_limit(self):
238+
"""Guard against relying on thrift's new (>=0.25.0) default
239+
string_length_limit of ~15.6 MiB, which is smaller than the
240+
connector's own DEFAULT_RESULT_BUFFER_SIZE_BYTES (100 MiB). A field
241+
larger than thrift's default limit, but within the connector's own
242+
result-size bound, must still read successfully.
243+
"""
244+
from thrift.transport import TTransport
245+
from thrift.protocol import TBinaryProtocol
246+
247+
from databricks.sql.backend.thrift_backend import (
248+
THRIFT_BINARY_PROTOCOL_STRING_LENGTH_LIMIT,
249+
THRIFT_BINARY_PROTOCOL_CONTAINER_LENGTH_LIMIT,
250+
)
251+
252+
oversized_payload = b"x" * (
253+
20 * 1024 * 1024
254+
) # 20 MiB > thrift's ~15.6 MiB default
255+
256+
write_buffer = TTransport.TMemoryBuffer()
257+
TBinaryProtocol.TBinaryProtocol(write_buffer).writeBinary(oversized_payload)
258+
259+
read_buffer = TTransport.TMemoryBuffer(write_buffer.getvalue())
260+
protocol = TBinaryProtocol.TBinaryProtocol(
261+
read_buffer,
262+
string_length_limit=THRIFT_BINARY_PROTOCOL_STRING_LENGTH_LIMIT,
263+
container_length_limit=THRIFT_BINARY_PROTOCOL_CONTAINER_LENGTH_LIMIT,
264+
)
265+
266+
self.assertEqual(protocol.readBinary(), oversized_payload)
267+
207268
def test_proxy_headers_are_set(self):
208269

209270
from databricks.sql.common.http_utils import create_basic_proxy_auth_headers

0 commit comments

Comments
 (0)