Patch librdkafka cluster_id strlen() overrun in DescribeCluster parsing - #24437
Patch librdkafka cluster_id strlen() overrun in DescribeCluster parsing#24437piochelepiotr wants to merge 3 commits into
Conversation
…uster metadata parsing rd_kafka_parse_Metadata() copies the admin DescribeCluster cluster_id via rd_tmpabuf_write_str(), which determines length with strlen() on a wire string that is not nul-terminated. The bytes immediately following it on the wire are the next field (controller_id), so the copy silently overruns into that field whenever its big-endian encoding doesn't happen to contain an early nul byte (e.g. a large controller/broker id, as WarpStream assigns). The corrupted, non-UTF-8 result then makes PyUnicode_FromString() fail inside confluent-kafka-python's unchecked cfl_PyDict_SetString(), crashing the process with a NULL-pointer segfault in PyDict_SetItem(). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Found the identical bug filed upstream today: confluentinc/librdkafka#5545 ("Fix cluster id over-read past the controller id in Metadata response"). Its root-cause writeup matches this investigation exactly (down to identifying Verified locally: built librdkafka v2.13.2 from source with this exact patch applied, rebuilt
|
Validation ReportAll 21 validations passed. Show details
|
What does this PR do?
Applies a build-time patch to librdkafka fixing a buffer overrun in the admin
DescribeClustermetadata-response parsing path (rd_kafka_parse_Metadata()inrdkafka_metadata.c).The
cluster_idfield is read as a length-prefixed Kafka wire string (rd_kafkap_str_t) that points directly into the network receive buffer and is not nul-terminated — the wire format has no terminator, the next field (controller_id) simply follows immediately. The existing code copies it withrd_tmpabuf_write_str(), which determines the copy length viastrlen(). Sincecluster_id.strisn't nul-terminated at its real end,strlen()reads past it into the adjacentcontroller_idbytes (and beyond), stopping only when it happens to hit an accidental zero byte.For a typical Kafka cluster with small numeric broker/controller IDs (e.g.
0,1,2), the big-endian encoding starts with00 00 00 0N— an accidental null byte right after the string that "accidentally" terminates it correctly, masking the bug. Against a WarpStream-backed cluster, whose controller/broker ID is a large, non-sequential value (observed:1611705428, hex60 10 AC 54— no zero bytes),strlen()runs past the intended string into unrelated binary data. The corrupted, non-UTF-8-safe result is then passed toPyUnicode_FromString()inside confluent-kafka-python'scfl_PyDict_SetString(), which fails and returnsNULL— aNULLthatcfl_PyDict_SetString()never checks before handing it toPyDict_SetItem(), causing a segfault (exit code 139) inside the Datadog Agent'skafka_consumerintegration whenever it callsAdminClient.describe_cluster()against such a broker.The fix copies
cluster_idusing its wire-known length (viard_tmpabuf_write()) instead of relying onstrlen(), mirroring the existing, already-correctrd_kafka_buf_read_str_tmpabuf()pattern used elsewhere in the same function for broker host/rack strings.This mirrors the existing precedent in #23240: applying an upstream-style fix as a build-time patch via the
install-from-source.shPATCHESmechanism, since this fix is not yet available in a released librdkafka version.Changes
.builders/patches/librdkafka-fix-cluster-id-strlen.patch— the fix, verified to apply cleanly against librdkafka v2.13.2 (the version pinned viaconfluent-kafka==2.13.2inkafka_consumer).builders/images/linux-x86_64/build_script.sh— apply patch during librdkafka build.builders/images/linux-aarch64/build_script.sh— sameMotivation
Root-caused a reported segfault (exit code 139) when the
kafka_consumerintegration callsAdminClient.describe_cluster()against a WarpStream-backed Kafka cluster. Confirmed via:valgrind+ ARM64 register-levelgdbinspection proving a genuine NULL-pointer dereference inPyDict_SetItemcluster_idbytes on the wire are always valid and correctly encoded (ruling out server-side/WarpStream corruption)gdbbreakpoint atcfl_PyDict_SetString()'s entry, dumping the actualvalargument and observing the valid cluster_id string immediately followed — with no null terminator — by the raw bytes ofcontroller_idrd_tmpabuf_write_str()usesstrlen()on a non-nul-terminated wire string, while the sibling helperrd_kafka_buf_read_str_tmpabuf()(used for other fields in the same function) correctly uses the wire-known lengthReview checklist (to be filled by reviewers)
qa/requiredif this PR needs QA validation, orqa/skip-qaif it does not. Exactly one of the two is required.backport/<branch-name>label to the PR and it will automatically open a backport PR once this one is merged