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]

Reply via email to