Skip to content

UBSan pointer-overflow in autolink postprocessing (extensions/autolink.c:315) #393

Description

@iceray00

Summary

Under UndefinedBehaviorSanitizer, parsing a crafted Markdown document aborts
with a pointer-overflow error in the autolink extension's protocol validation
(validate_protocol, reached from postprocess_text).

The subscript expression

char prev_char = data[-((ptrdiff_t)rewind) - len - 1];

mixes signed and unsigned arithmetic. rewind and len are size_t, so
-((ptrdiff_t)rewind) - len - 1 is evaluated in unsigned arithmetic and can
wraparound to a very large index before the pointer addition; UBSan then
reports the wrapped pointer addition. On this run the address happens to wrap
back to the intended location, so the guards above (rewind + len + 1 <= max_rewind) keep the access in range. This is therefore a report about
undefined behavior in an intermediate expression, not a demonstrated
out-of-bounds read: a compiler is entitled to assume the expression does not
wrap and may miscompile this path. Computing the offset in signed arithmetic
first (as is already done for rewind) avoids the wrap.

Reported by iceray-Li.

Details

extensions/autolink.c (cmark-gfm 0.29.0.gfm.12):

static bool validate_protocol(const char protocol[], uint8_t *data, size_t rewind, size_t max_rewind) {
  size_t len = strlen(protocol);

  if (len > (max_rewind - rewind)) {
    return false;
  }

  // Check that the protocol matches
  if (memcmp(data - rewind - len, protocol, len) != 0) {
    return false;
  }

  if (len == (max_rewind - rewind)) {
    return true;
  }

  char prev_char = data[-((ptrdiff_t)rewind) - len - 1];   // line 315

  // Make sure the character before the protocol is non-alphanumeric
  return !cmark_isalnum(prev_char);
}

The preceding checks guarantee rewind + len + 1 <= max_rewind, so the
intended index is valid. The problem is only the unsigned wraparound in the
intermediate expression.

Reproduction

  • Version: cmark-gfm 0.29.0.gfm.12.

  • OS / compiler: UNKNOWN (OSS-Fuzz-style build).

  • Harness: test/cmark-fuzz.c.

  • Build the harness with -fsanitize=address,undefined and run it on an
    attached PoC:

    ./cmark-fuzz <poc-path>
    

Sanitizer report

Variant A (top frame validate_protocol):

extensions/autolink.c:315:20: runtime error: addition of unsigned offset to 0x6080000009d5 overflowed to 0x6080000009c6
    #0 validate_protocol        extensions/autolink.c:315:20
    #1 postprocess_text         extensions/autolink.c:364:13
    #2 postprocess              extensions/autolink.c:486:7
    #3 cmark_parser_finish      src/blocks.c:1548:31
    #4 LLVMFuzzerTestOneInput   test/cmark-fuzz.c:46:23
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior extensions/autolink.c:315:20

Variant B (same line, entered through postprocess_text):

extensions/autolink.c:315:20: runtime error: addition of unsigned offset to 0x606000000210 overflowed to 0x606000000200
    #0 postprocess_text         extensions/autolink.c
    #1 postprocess              extensions/autolink.c:486:7
    #2 cmark_parser_finish      src/blocks.c:1548:31
    #3 LLVMFuzzerTestOneInput   test/cmark-fuzz.c:46:23
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior extensions/autolink.c:315:20

PoC

Two inputs reproduce the same site through different entry points; both are
attached.

  • cmark-gfm_autolink_validate_protocol.poc (variant A)
  • cmark-gfm_autolink_postprocess_text.poc (variant B)

Activity

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