This is an automated email from the ASF dual-hosted git repository. asf-gitbox-commits pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/qpid-proton.git
commit 040c0993f3b85205607740f2c847ea7ef754693d Author: Andrew Stitcher <[email protected]> AuthorDate: Fri Sep 11 16:08:57 2026 -0400 PROTON-2968: pn_decoder_decode rejects too large compound counts Elements of a zero width type (null, true, ...) take up no input bytes at all, so the child count together with the input size in an array of these does not bound the size of the decoded array. A compound with more children than the allowed number of nodes in a pn_data_t couldn't fit in the node array, so reject the count outright with PN_OUT_OF_MEMORY, which is what a decode that ran out of nodes would report anyway. Assisted-By: Claude Opus 5 <[email protected]> --- c/src/core/codec.c | 7 +++---- c/src/core/data.h | 7 +++++++ c/src/core/decoder.c | 10 ++++++++++ c/tests/data_test.cpp | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/c/src/core/codec.c b/c/src/core/codec.c index e90449b9b..63d20d1fb 100644 --- a/c/src/core/codec.c +++ b/c/src/core/codec.c @@ -486,8 +486,7 @@ void pn_data_clear(pn_data_t *data) static int pni_data_grow(pn_data_t *data) { - /* Resolve the effective ceiling: the user-set limit, or the hard uint16 max. */ - pni_nid_t effective_max = data->max_nid ? data->max_nid : PNI_NID_MAX; + pni_nid_t effective_max = pni_data_max_nid(data); /* The limit is on logical nodes in use (size), not pre-allocated capacity. * pni_data_new() is the only caller and it only calls us when size >= capacity, @@ -1292,8 +1291,8 @@ static size_t pni_data_id(pn_data_t *data, pni_node_t *node) static pni_node_t *pni_data_new(pn_data_t *data) { - /* Enforce max_nid limit on logical node count, regardless of pre-allocated capacity. */ - if (data->max_nid > 0 && data->size >= data->max_nid) { + /* Enforce the node limit on logical node count, regardless of pre-allocated capacity. */ + if (data->size >= pni_data_max_nid(data)) { pn_error_set(pni_data_error(data), PN_OUT_OF_MEMORY, "pn_data node limit exceeded"); return NULL; } diff --git a/c/src/core/data.h b/c/src/core/data.h index 0189be0fe..37b70f306 100644 --- a/c/src/core/data.h +++ b/c/src/core/data.h @@ -155,6 +155,13 @@ struct pn_data_t { #define PNI_DATA_DEFAULT_MAX_NODES 1024 #define PNI_DATA_BODY_MAX_NODES 0 +/* The most nodes this pn_data_t is allowed to hold: the limit set with + * pn_data_set_decode_limits(), or the hard pni_nid_t ceiling if none is set. */ +static inline pni_nid_t pni_data_max_nid(pn_data_t *data) +{ + return data->max_nid ? data->max_nid : PNI_NID_MAX; +} + static inline pni_node_t * pn_data_node(pn_data_t *data, pni_nid_t nd) { return nd ? (data->nodes + nd - 1) : NULL; diff --git a/c/src/core/decoder.c b/c/src/core/decoder.c index a525ecb1c..59681dabf 100644 --- a/c/src/core/decoder.c +++ b/c/src/core/decoder.c @@ -551,6 +551,16 @@ static int pni_decoder_open_container(pn_decoder_t *decoder, pn_data_t *data, un if (pn_decoder_remaining(decoder) < size) return PN_UNDERFLOW; size_t count = (width == 1) ? pn_decoder_readf8(decoder) : pn_decoder_readf32(decoder); + // Array elements of a zero width type (null, true, ...) take no input bytes + // at all, so a count is not bounded by the size the way a list's is. Reject + // any count that could never fit in this pn_data_t - whose node budget may be + // well below the hard ceiling - rather than truncating it. + if (count > pni_data_max_nid(data)) { + return pn_error_format(pn_data_error(data), PN_OUT_OF_MEMORY, + "%s count %zu exceeds the pn_data node limit", + pn_type_name(type), count); + } + if (type == PN_ARRAY) return pni_decoder_open_array(decoder, data, depth, (pni_nid_t) count); int err = (type == PN_LIST) ? pn_data_put_list(data) : pn_data_put_map(data); diff --git a/c/tests/data_test.cpp b/c/tests/data_test.cpp index 4a6e04d20..4beccef74 100644 --- a/c/tests/data_test.cpp +++ b/c/tests/data_test.cpp @@ -392,6 +392,42 @@ TEST_CASE("data_decode_rejects_deeply_nested_values_on_node_limit") { CHECK(pn_data_errno(data) == PN_OUT_OF_MEMORY); } +TEST_CASE("data_decode_rejects_element_count_beyond_node_limit") { + auto_free<pn_data_t, pn_data_free> data(pn_data(0)); + + // array32 of null: null elements are zero width, so the declared count is not + // bounded by the declared size and can name more elements than the node array + // could ever hold. That must be rejected. + // 0xe0 size=0x00000005 count=0x00010000 0x40(null) + const uint8_t encoded[] = { + 0xf0, 0x00, 0x00, 0x00, 0x05, 0x00, 0x01, 0x00, 0x00, 0x40 + }; + + pn_data_set_decode_limits(data, 0, 0); // unlimited: only the hard node-id ceiling applies + ssize_t dec = pn_data_decode(data, (const char *) encoded, sizeof(encoded)); + CHECK(dec == PN_OUT_OF_MEMORY); + CHECK(pn_data_errno(data) == PN_OUT_OF_MEMORY); +} + +TEST_CASE("data_decode_rejects_element_count_beyond_configured_node_limit") { + auto_free<pn_data_t, pn_data_free> data(pn_data(0)); + + // Same shape, but with a count (100) that is under the hard node-id ceiling + // and only over the limit this pn_data_t was configured with. The count is + // measured against that limit, so the array is rejected from its header + // rather than after the budget has been spent decoding its elements. + // 0xf0 size=0x00000005 count=0x00000064 0x40(null) + const uint8_t encoded[] = { + 0xf0, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x64, 0x40 + }; + + pn_data_set_decode_limits(data, 10, 0); + ssize_t dec = pn_data_decode(data, (const char *) encoded, sizeof(encoded)); + CHECK(dec == PN_OUT_OF_MEMORY); + CHECK(pn_data_errno(data) == PN_OUT_OF_MEMORY); + CHECK(pn_data_size(data) == 0); // rejected from the header, before any node was put +} + TEST_CASE("data_decode_into_entered_container") { auto_free<pn_data_t, pn_data_free> data(pn_data(0)); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
