Skip to content

Guard to_base64 against empty input (UB on back())#39

Open
achamayou wants to merge 2 commits into
mainfrom
fix_to_base64_empty
Open

Guard to_base64 against empty input (UB on back())#39
achamayou wants to merge 2 commits into
mainfrom
fix_to_base64_empty

Conversation

@achamayou

@achamayou achamayou commented Jun 12, 2026

Copy link
Copy Markdown
Member

Summary

to_base64 trims trailing = padding with:

while (r.back() == '=') { r.pop_back(); }

For an empty input vector, r_sz is 0, r is an empty string, and r.back() on an empty std::string is undefined behaviour.

Fix

  • Return early for empty input (EVP_EncodeBlock produces nothing anyway).
  • Defensively guard the trim loop with !r.empty().

Currently unreachable — to_base64 is only ever called on fixed-size hashes and non-empty EC/RSA key components — so this is a hardening fix.

Testing

  • cmake --build build — clean.
  • ctest — all tests pass, including a new regression test covering to_base64({}) and to_base64url({}).
  • clang-tidy ../didx509cpp.h — exit 0, no findings (mirrors CI).

Found during a security review of the codebase.

For an empty byte vector, r is an empty string and 'while (r.back() == "=")'
calls back() on it, which is undefined behaviour. Return early for empty input
and also make the padding-trim loop check for emptiness defensively.

Currently unreachable (to_base64 is only called on fixed-size hashes and
non-empty key coordinates), so this is a hardening fix.
@achamayou achamayou requested a review from a team as a code owner June 12, 2026 22:33
@achamayou achamayou requested a review from Copilot June 13, 2026 06:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Hardens the internal to_base64 helper against undefined behavior when called with an empty input vector, preventing std::string::back() from being invoked on an empty string during padding trimming.

Changes:

  • Return early from to_base64 when the input byte vector is empty.
  • Defensively guard the padding-trim loop with !r.empty().
Comments suppressed due to low confidence (1)

didx509cpp.h:110

  • EVP_EncodeBlock takes an int length, but this code passes bytes.size() (size_t) and also stores the computed output size in an int without bounds checks. For sufficiently large inputs this can overflow/narrow and lead to incorrect sizing and undefined behavior. Consider explicitly checking the input/output sizes fit in int and casting intentionally before calling OpenSSL.
      const int r_sz = 4 * ((bytes.size() + 2) / 3);
      std::string r(r_sz, 0);
      auto out_sz =
        EVP_EncodeBlock((unsigned char*)r.data(), bytes.data(), bytes.size());

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread didx509cpp.h

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

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.

3 participants