Skip to content

VMime uuencode decoder: an unsigned underflow of the read length drives an out-of-bounds write on the stack while decoding a crafted MIME body #362

Closed

Description

@Haind03

Hello, and thank you for maintaining this project.

I found a likely memory-safety issue by static source review (no runtime exploit was developed). Sharing it here so it can be fixed.

  • Class: CWE-191 Integer Underflow, CWE-787 Out-of-bounds Write, CWE-121 Stack-based Buffer Overflow (FINDING 1). CWE-908 Use of Uninitialized Resource (FINDING 2).
  • Honest severity: High
  • Full technical write-up (trace, PoC builder, suggested fix): https://hackmd.io/@haindpwn/vmime-uuencode-decoder-oob-write

The write-up gives the exact file and line of the sink, the reachability chain from the public entry point, an honest impact assessment, and a suggested fix. I am happy to send a patch or any further detail.

Reported by HaiND (Nguyen Dinh Hai), Post and Telecommunication Institute of Technology, github.com/Haind03.

Activity

  1. added a commit that references this issue on Oct 22, 2026
    ea5afc6
  2. vincent-richard commented on Oct 22, 2026

    @vincent-richard
    Member

    Hi @Haind03,

    Thank you for the report and for the detailed write-up, it made the issue easy to confirm and fix.

    I confirm both findings. I checked the trace against the source and it holds:

    • Finding 1: when a line starts with b but is not a begin line, the probe leaves inPos at 5 while inLength is 2, and inLength - inPos wraps around. The two stream adapters then pass the huge count through, because their bounds checks (pos + count >= end) overflow too.
    • Finding 2: with a line length above 48, out.write() emits up to 15 bytes of outBuffer that were never written.

    The fix is in #363:

    • the decoder stops when the marker probe has consumed more than the line holds, and the line length is clamped so that only decoded bytes are written out;
    • inputStreamStringAdapter and seekableInputStreamRegionAdapter now compare the count with the remaining bytes, so a wrapped count can no longer get through to the copy;
    • regression tests cover both cases, as well as the adapters.

    While I was in this code, I also fixed some decoding bugs: lines whose length is not a multiple of 3 were not read entirely, the filename from the begin line included the mode, and a begin line with no mode and no filename swallowed the next line.

    You are credited in the commit message. If you have time to look at the PR, feedback is welcome.

    Thanks again!

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions