Skip to content

fix bUuDecodeEx: prevent OOM and memory leak on malformed input#148

Open
rdmark wants to merge 2 commits intomainfrom
fix-bUuDecodeEx
Open

fix bUuDecodeEx: prevent OOM and memory leak on malformed input#148
rdmark wants to merge 2 commits intomainfrom
fix-bUuDecodeEx

Conversation

@rdmark
Copy link
Collaborator

@rdmark rdmark commented Mar 7, 2026

bsread was called with INT_MAX as the byte limit, allowing up to 2 GB to accumulate in the output bstring when the UU decode stream failed to signal EOF cleanly on input lacking line terminators. Cap the read at src->slen, which is always a safe upper bound since decoded output is smaller than its encoded input.

The same function also leaked the bsUuCtx and its two internal bstrings whenever bsUuDecodePart was not invoked before the stream was closed — in particular when src->slen is zero, causing bsread to return immediately without calling the read function. The self-freeing pattern in bsUuDecodePart was inherently fragile: it freed ctx inside the callback, leaving bsclose to return a dangling pointer that bUuDecodeEx silently discarded.

Fix by transferring ownership explicitly: bsUuDecodePart now only frees the stream buffers (ctx->io.dst, ctx->io.src) at EOF and nulls them; bUuDecodeEx always frees the ctx container via bsclose's return value. Since bdestroy(NULL) is a no-op, the exit path handles both the fully-consumed and early-close cases safely.

Both bugs were detected by libFuzzer fuzz testing.

bsread was called with INT_MAX as the byte limit, allowing up to 2 GB to
accumulate in the output bstring when the UU decode stream failed to signal
EOF cleanly on input lacking line terminators.  Cap the read at src->slen,
which is always a safe upper bound since decoded output is smaller than its
encoded input.

The same function also leaked the bsUuCtx and its two internal bstrings
whenever bsUuDecodePart was not invoked before the stream was closed — in
particular when src->slen is zero, causing bsread to return immediately
without calling the read function.  The self-freeing pattern in
bsUuDecodePart was inherently fragile: it freed ctx inside the callback,
leaving bsclose to return a dangling pointer that bUuDecodeEx silently
discarded.

Fix by transferring ownership explicitly: bsUuDecodePart now only frees the
stream buffers (ctx->io.dst, ctx->io.src) at EOF and nulls them; bUuDecodeEx
always frees the ctx container via bsclose's return value.  Since
bdestroy(NULL) is a no-op, the exit path handles both the fully-consumed and
early-close cases safely.

Both bugs were detected by libFuzzer fuzz testing.
@rdmark rdmark requested a review from msteinert as a code owner March 7, 2026 12:59
@github-actions
Copy link

github-actions bot commented Mar 7, 2026

File Coverage Lines Branches
All files 69% 74% 63%
bstring/bstraux.c 55% 64% 46%
bstring/bstrlib.c 74% 78% 71%
bstring/buniutil.c 80% 86% 73%
bstring/utf8util.c 60% 71% 50%

Minimum allowed coverage is 50%

Generated by 🐒 cobertura-action against 359515b

@sonarqubecloud
Copy link

sonarqubecloud bot commented Mar 7, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant