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:
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)
Summary
Under UndefinedBehaviorSanitizer, parsing a crafted Markdown document aborts
with a pointer-overflow error in the autolink extension's protocol validation
(
validate_protocol, reached frompostprocess_text).The subscript expression
mixes signed and unsigned arithmetic.
rewindandlenaresize_t, so-((ptrdiff_t)rewind) - len - 1is evaluated in unsigned arithmetic and canwraparound 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 aboutundefined 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):The preceding checks guarantee
rewind + len + 1 <= max_rewind, so theintended 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,undefinedand run it on anattached PoC:
Sanitizer report
Variant A (top frame
validate_protocol):Variant B (same line, entered through
postprocess_text):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)