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 2ce88c34e066bff2f97874b1db95a1fe1249c58b
Author: Andrew Stitcher <[email protected]>
AuthorDate: Tue Sep 15 14:07:33 2026 -0400

    PROTON-2950: More robustness for pn_buffer
    
    - Tightened up one possible buffer wraparound case
    - Test for more robust pn_string
---
 c/src/core/buffer.c     | 14 +++++++++-----
 c/tests/object_test.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 59 insertions(+), 5 deletions(-)

diff --git a/c/src/core/buffer.c b/c/src/core/buffer.c
index 103d57c21..61a4a2c2c 100644
--- a/c/src/core/buffer.c
+++ b/c/src/core/buffer.c
@@ -117,21 +117,25 @@ int pn_buffer_ensure(pn_buffer_t *buf, size_t size)
 {
   if (pn_buffer_available(buf) >= size) return 0;
 
-  size_t old_capacity = buf->capacity;
-  size_t old_head = pni_buffer_head(buf);
-  bool wrapped = pni_buffer_wrapped(buf);
-
+  // Reject the request before adding it to the size, so the sum can't wrap
+  if (size > 0x80000000ULL) {
+    return PN_OUT_OF_MEMORY;
+  }
   uint64_t needed = (uint64_t) buf->size + size;
   if (needed < 32) needed = 32;
   if (needed > 0x80000000ULL) {
     return PN_OUT_OF_MEMORY;
   }
   uint32_t new_capacity = pni_round_up_pow2((uint32_t) needed);
-
   char* new_bytes = (char *) pni_mem_subreallocate(PN_CLASSCLASS(pn_buffer), 
buf, buf->bytes, new_capacity);
   if (!new_bytes) {
     return PN_OUT_OF_MEMORY;
   }
+
+  size_t old_capacity = buf->capacity;
+  size_t old_head = pni_buffer_head(buf);
+  bool wrapped = pni_buffer_wrapped(buf);
+
   buf->bytes = new_bytes;
   buf->capacity = new_capacity;
 
diff --git a/c/tests/object_test.cpp b/c/tests/object_test.cpp
index af0d5d5e8..60c7ffb4f 100644
--- a/c/tests/object_test.cpp
+++ b/c/tests/object_test.cpp
@@ -679,6 +679,56 @@ TEST_CASE("string_addf") {
   pn_free(str);
 }
 
+TEST_CASE("string_setn_overflow") {
+  pn_string_t *str = pn_string("hello");
+  CHECK(str);
+
+  // A size that can't be represented is refused outright, without touching
+  // the string and without reading from bytes
+  for (size_t n : {(size_t)INT32_MAX + 1, SIZE_MAX - 1, SIZE_MAX}) {
+    CHECK(pn_string_setn(str, "world", n) != 0);
+    CHECK(pn_string_size(str) == 5);
+    CHECK_THAT("hello", Equals(pn_string_get(str)));
+  }
+
+  // The refused growth must not have recorded a capacity that was never
+  // allocated - if it had, appending here would run off the heap block
+  CHECK(pn_string_addf(str, "%s", " world") == 0);
+  CHECK_THAT("hello world", Equals(pn_string_get(str)));
+  CHECK(pn_string_size(str) == 11);
+
+  CHECK(pn_string_set(str, "goodbye") == 0);
+  CHECK_THAT("goodbye", Equals(pn_string_get(str)));
+  pn_free(str);
+}
+
+TEST_CASE("string_addf_repeated") {
+  // Exercises the grow-and-retry loop in pn_string_vaddf
+  pn_string_t *str = pn_string("");
+  CHECK(str);
+  for (int i = 0; i < 1000; i++) {
+    CHECK(pn_string_addf(str, "%s", "0123456789") == 0);
+  }
+  CHECK(pn_string_size(str) == 10000);
+  CHECK(strlen(pn_string_get(str)) == 10000);
+  pn_free(str);
+}
+
+TEST_CASE("string_setn_sizes") {
+  // Round trip a range of sizes across the power-of-two growth boundaries
+  pn_string_t *str = pn_string(NULL);
+  CHECK(str);
+  std::string value;
+  for (size_t n = 0; n < 1100; n++) {
+    value.push_back('a' + (char)(n % 26));
+    CHECK(pn_string_setn(str, value.data(), value.size()) == 0);
+    CHECK(pn_string_size(str) == value.size());
+    CHECK(memcmp(pn_string_get(str), value.data(), value.size()) == 0);
+    CHECK(pn_string_get(str)[value.size()] == '\0');
+  }
+  pn_free(str);
+}
+
 TEST_CASE("map_iteration") {
   int n = 5;
   pn_list_t *pairs = pn_list(PN_OBJECT, 2 * n);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to