diff --git a/bstring/bstraux.c b/bstring/bstraux.c index 92d57fe..76bf22b 100644 --- a/bstring/bstraux.c +++ b/bstring/bstraux.c @@ -648,10 +648,16 @@ bUuDecLine(void *parm, int ofs, int len) return ret; } +struct bsUuCtx { + struct bUuInOut io; + struct bStream * sInp; +}; + bstring bUuDecodeEx(const bstring src, int *badlines) { struct bStream *s, *d; + struct bsUuCtx *ctx; struct tagbstring t; bstring b; @@ -668,11 +674,22 @@ bUuDecodeEx(const bstring src, int *badlines) if (NULL == b) { goto error; } - if (0 > bsread(b, d, INT_MAX)) { + if (src->slen > 0 && 0 > bsread(b, d, src->slen)) { goto error; } exit: - bsclose(d); + /* + * bsUuDecodePart frees ctx->io.dst and ctx->io.src when it signals + * EOF (nulling both pointers), but leaves ctx itself for us to free. + * If the stream was never read (e.g. empty input), all three are still + * live. Either way, bsclose returns the ctx pointer and we own it. + */ + ctx = (struct bsUuCtx *)bsclose(d); + if (ctx) { + bdestroy(ctx->io.dst); + bdestroy(ctx->io.src); + free(ctx); + } bsclose(s); return b; error: @@ -681,11 +698,6 @@ bUuDecodeEx(const bstring src, int *badlines) goto exit; } -struct bsUuCtx { - struct bUuInOut io; - struct bStream * sInp; -}; - static size_t bsUuDecodePart(void *buff, size_t elsize, size_t nelem, void *parm) { @@ -746,10 +758,11 @@ bsUuDecodePart(void *buff, size_t elsize, size_t nelem, void *parm) return tsz; } } - /* Deallocate once EOF becomes triggered */ + /* Release stream buffers on EOF; ctx itself is owned by bUuDecodeEx. */ bdestroy(ctx->io.dst); + ctx->io.dst = NULL; bdestroy(ctx->io.src); - free(ctx); + ctx->io.src = NULL; return 0; } diff --git a/tests/testaux.c b/tests/testaux.c index ff80d96..9fdde74 100644 --- a/tests/testaux.c +++ b/tests/testaux.c @@ -510,6 +510,22 @@ START_TEST(core_014) } END_TEST +START_TEST(core_016) +{ + /* Empty input: bUuDecodeEx must return an empty bstring (not NULL) + * and must not leak the internal decode context. Before the fix, + * bsread was called with INT_MAX on empty input, immediately returned + * BSTR_ERR, and the function took the error path — returning NULL and + * leaking ctx, ctx->io.dst and ctx->io.src. */ + struct tagbstring empty = bsStatic(""); + int err = 0; + bstring c = bUuDecodeEx(&empty, &err); + ck_assert_ptr_nonnull(c); + ck_assert_int_eq(c->slen, 0); + bdestroy(c); +} +END_TEST + int main(void) { @@ -532,6 +548,7 @@ main(void) tcase_add_test(core, core_012); tcase_add_test(core, core_013); tcase_add_test(core, core_014); + tcase_add_test(core, core_016); suite_add_tcase(suite, core); /* Run tests */ SRunner *runner = srunner_create(suite);