From e9eef6987384e31b625b6cadbad0fd7606d31a81 Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Mon, 2 Mar 2026 21:07:51 +0100 Subject: [PATCH 1/2] add bfromcstrrangealloc, improve bSecureInput, fix bfindreplace ported improvements from the parent bstrlib project, all code originally by Paul Hsieh and slightly modified for our fork - Add bfromcstrrangealloc() with retry-on-alloc-failure loop - Refactor bfromcstralloc() to delegate to bfromcstrrangealloc() - Improve bSecureInput for extreme memory situations - Fix redundant validation check in bfindreplace - Guard backward-compat macros with BSTRLIB_REDUCE_NAMESPACE_POLLUTION - Update bCatStatic to use bcatStatic --- bstring/bstraux.c | 51 ++++++++++++++++++++++--------------------- bstring/bstraux.h | 4 +++- bstring/bstrlib.c | 51 +++++++++++++++++++++++++++++++------------ bstring/bstrlib.h | 9 ++++++++ tests/bstest.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++ tests/testaux.c | 2 +- 6 files changed, 132 insertions(+), 40 deletions(-) diff --git a/bstring/bstraux.c b/bstring/bstraux.c index f331c47..18d33c9 100644 --- a/bstring/bstraux.c +++ b/bstring/bstraux.c @@ -989,8 +989,12 @@ bSetChar(bstring b, int pos, char c) bstring bSecureInput(int maxlen, int termchar, bNgetc vgetchar, void *vgcCtx) { - size_t i, m, c; - bstring b, t; + size_t i; + size_t m; + int c; + int done; + bstring b; + bstring t; if (!vgetchar) { return NULL; } @@ -998,31 +1002,29 @@ bSecureInput(int maxlen, int termchar, bNgetc vgetchar, void *vgcCtx) if (!b) { return NULL; } - if ((c = UCHAR_MAX + 1) == (size_t)termchar) { - c++; - } - for (i = 0; ; i++) { - if ((size_t)termchar == c || - (maxlen > 0 && i >= (size_t)maxlen)) { - c = (size_t)EOF; - } else { - c = vgetchar (vgcCtx); - } - if ((size_t)EOF == c) { - break; + i = 0; + done = 0; + while (!done && (maxlen <= 0 || i < (size_t)maxlen)) { + c = vgetchar(vgcCtx); + if (EOF == c) { + done = 1; + continue; } if (i + 1 >= (size_t)b->mlen) { - /* Double size, but deal with unusual case of numeric - * overflows - */ - if ((m = b->mlen << 1) <= (size_t)b->mlen && - (m = b->mlen + 1024) <= (size_t)b->mlen && - (m = b->mlen + 16) <= (size_t)b->mlen && - (m = b->mlen + 1) <= (size_t)b->mlen) { - t = NULL; + /* Double size, and deal with numeric overflows */ + if (b->mlen <= INT_MAX / 2) { + m = (size_t)b->mlen << 1; + } else if (b->mlen <= INT_MAX - 1024) { + m = (size_t)b->mlen + 1024; + } else if (b->mlen <= INT_MAX - 16) { + m = (size_t)b->mlen + 16; + } else if (b->mlen <= INT_MAX - 1) { + m = (size_t)b->mlen + 1; } else { - t = bfromcstralloc ((int)m, ""); + bSecureDestroy(b); /* Cleanse partial buffer */ + return NULL; } + t = bfromcstrrangealloc(b->mlen + 1, (int)m, ""); if (t) { memcpy(t->data, b->data, i); } @@ -1032,7 +1034,8 @@ bSecureInput(int maxlen, int termchar, bNgetc vgetchar, void *vgcCtx) return b; } } - b->data[i] = (unsigned char)c; + b->data[i++] = (unsigned char)c; + done = (termchar == c); } b->slen = (int)i; b->data[i] = (unsigned char)'\0'; diff --git a/bstring/bstraux.h b/bstring/bstraux.h index d5c649f..12e6125 100644 --- a/bstring/bstraux.h +++ b/bstring/bstraux.h @@ -66,6 +66,7 @@ do { \ } while (0) /* Backward compatibilty with previous versions of Bstrlib */ +#if !defined(BSTRLIB_REDUCE_NAMESPACE_POLLUTION) #define bAssign(a, b) \ ((bassign)((a), (b))) @@ -85,7 +86,7 @@ do { \ ((bcatblk)((b), (s), (len))) #define bCatStatic(b, s) \ - bCatBlk((b), ("" s ""), sizeof (s) - 1) + bcatStatic(b, s) #define bTrunc(b, n) \ ((btrunc)((b), (n))) @@ -110,6 +111,7 @@ do { \ #define bUuDecode(b) \ (bUuDecodeEx((b), NULL)) +#endif /* Unusual functions */ diff --git a/bstring/bstrlib.c b/bstring/bstrlib.c index 41e9ec6..2676304 100644 --- a/bstring/bstrlib.c +++ b/bstring/bstrlib.c @@ -214,37 +214,60 @@ bfromcstr(const char *str) } bstring -bfromcstralloc(int mlen, const char *str) +bfromcstrrangealloc(int minl, int maxl, const char *str) { bstring b; int i; size_t j; + + /* Bad parameters? */ if (str == NULL) { return NULL; } - j = strlen(str); - i = snapUpSize((int)(j + (2 - (j != 0)))); - if (i <= (int) j) { + if (maxl < minl || minl < 0) { return NULL; } + + /* Adjust lengths */ + j = strlen(str); + if ((size_t)minl < (j + 1)) { + minl = (int)(j + 1); + } + if (maxl < minl) { + maxl = minl; + } + i = maxl; + b = malloc(sizeof(struct tagbstring)); if (b == NULL) { return NULL; } b->slen = (int)j; - if (i < mlen) { - i = mlen; - } - b->mlen = i; - b->data = malloc(b->mlen); - if (!b->data) { - free(b); - return NULL; + + while (1) { + b->data = malloc(i); + if (b->data != NULL) { + b->mlen = i; + break; + } + int k = (i >> 1) + (minl >> 1); + if (i == k || i < minl) { + free(b); + return NULL; + } + i = k; } - memcpy(b->data, str, j + 1); + + bBlockCopy(b->data, str, j + 1); return b; } +bstring +bfromcstralloc(int mlen, const char *str) +{ + return bfromcstrrangealloc(mlen, mlen, str); +} + bstring blk2bstr(const void *blk, int len) { @@ -1642,7 +1665,7 @@ findreplaceengine(bstring b, const bstring find, const bstring repl, bstring auxr = (bstring) repl; if (!b || !b->data || !find || !find->data || !repl || !repl->data || - pos < 0 || find->slen <= 0 || b->mlen < 0 || + pos < 0 || find->slen <= 0 || b->slen > b->mlen || b->mlen <= 0 || b->slen < 0 || repl->slen < 0) { return BSTR_ERR; diff --git a/bstring/bstrlib.h b/bstring/bstrlib.h index f82f131..ce00808 100644 --- a/bstring/bstrlib.h +++ b/bstring/bstrlib.h @@ -134,6 +134,15 @@ bfromcstr(const char *str); BSTR_PUBLIC bstring bfromcstralloc(int mlen, const char *str); +/** + * Create a bstring which contains the contents of the '\\0' terminated + * char* buffer str. The memory buffer backing the string is at least + * minl characters in length, but an attempt is made to allocate up to + * maxl characters. + */ +BSTR_PUBLIC bstring +bfromcstrrangealloc(int minl, int maxl, const char *str); + /** * Create a bstring whose contents are described by the contiguous buffer * pointing to by blk with a length of len bytes. diff --git a/tests/bstest.c b/tests/bstest.c index 87bdaca..8fb38a2 100644 --- a/tests/bstest.c +++ b/tests/bstest.c @@ -103,6 +103,57 @@ test0_1(const char *s, int len, const char *res) "This is a bogus but reasonably long string. " \ "Just long enough to cause some mallocing." +static void +test0_2(const char *s, int l) +{ + int i_count = l * 2; + int j_count = l * 2; + int k_count = l + 1; + int is_valgrind = getenv("VALGRIND_OPTS") != NULL; + + /* + * The full matrix is O(l^3) and turns into millions of allocations for + * LONG_STRING. Keep exhaustive coverage for small inputs; sample evenly + * for large inputs and valgrind runs. + */ + if (l > 32) { + i_count = 32; + j_count = 32; + k_count = 33; + } + if (is_valgrind) { + if (i_count > 12) i_count = 12; + if (j_count > 12) j_count = 12; + if (k_count > 10) k_count = 10; + } + + for (int ii = 0; ii < i_count; ii++) { + int i = (i_count <= 1) ? 0 : (ii * ((l * 2) - 1)) / (i_count - 1); + for (int jj = 0; jj < j_count; jj++) { + int j = (j_count <= 1) ? 0 : (jj * ((l * 2) - 1)) / (j_count - 1); + for (int kk = 0; kk < k_count; kk++) { + int k = (k_count <= 1) ? 0 : (kk * l) / (k_count - 1); + const char *t = s ? (s + k) : NULL; + bstring b = bfromcstrrangealloc(i, j, t); + if (NULL == b) { + ck_assert(!(i < j && t != NULL)); + continue; + } + if (t == NULL || b->data == NULL) { + bdestroy(b); + ck_abort(); + return; /* Just a safeguard */ + } + ck_assert_int_eq(b->slen, l - k); + ck_assert_int_eq(b->data[l - k], '\0'); + ck_assert(b->mlen > b->slen); + ck_assert_int_eq(memcmp(t, b->data, l - k + 1), 0); + bdestroy(b); + } + } + } +} + START_TEST(core_000) { /* tests with NULL */ @@ -121,6 +172,10 @@ START_TEST(core_000) test0_1(SHORT_STRING, 30, SHORT_STRING); test0_1(LONG_STRING, 0, LONG_STRING); test0_1(LONG_STRING, 30, LONG_STRING); + /* bfromcstrrangealloc tests */ + test0_2(NULL, 2); + test0_2(EMPTY_STRING, sizeof(EMPTY_STRING) - 1); + test0_2(LONG_STRING, sizeof(LONG_STRING) - 1); } END_TEST diff --git a/tests/testaux.c b/tests/testaux.c index 0e28ffd..6cc6983 100644 --- a/tests/testaux.c +++ b/tests/testaux.c @@ -467,7 +467,7 @@ core13_fgetc(void *ctx) START_TEST(core_013) { - struct tagbstring t0 = bsStatic("Random String"); + struct tagbstring t0 = bsStatic("Random String, long enough to cause to reallocing"); struct vfgetc vctx; bstring b; int ret = 0; From 4dd0723c755f840a2df91f0ac7611e22ae507abf Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Mon, 2 Mar 2026 22:50:16 +0100 Subject: [PATCH 2/2] introduce meson option for controlling backwards compat macros --- meson.build | 4 ++++ meson_options.txt | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/meson.build b/meson.build index 926d2a4..3248ebf 100644 --- a/meson.build +++ b/meson.build @@ -34,6 +34,10 @@ if cc.get_id() == 'msvc' endforeach endif +if get_option('reduce-namespace-pollution') + add_project_arguments('-DBSTRLIB_REDUCE_NAMESPACE_POLLUTION', language: 'c') +endif + add_project_arguments(warning_flags, language: 'c') bstring_inc = include_directories(['.', 'bstring']) conf_data = configuration_data() diff --git a/meson_options.txt b/meson_options.txt index 5bf78df..95c0a3b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -22,3 +22,9 @@ option( value: true, description: 'Build bstring library with UTF-8 support', ) +option( + 'reduce-namespace-pollution', + type: 'boolean', + value: true, + description: 'Hide backward-compatibility macros in bstraux.h', +)