Skip to content

autolink: avoid unsigned wrap in the rewind scans - #823

Open
1820893135-pixel wants to merge 1 commit into
vmg:masterfrom
1820893135-pixel:fix/autolink-rewind-underflow
Open

1820893135-pixel wants to merge 1 commit into
vmg:masterfrom
1820893135-pixel:fix/autolink-rewind-underflow

Conversation

@1820893135-pixel

Copy link
Copy Markdown

Fixes #820 and #821.

Root cause

Both rewind scans form a negative index in size_t:

/* sd_autolink__email(), autolink.c:216 */
uint8_t c = data[-rewind - 1];

/* sd_autolink__url(), autolink.c:272 */
while (rewind < max_rewind && isalpha(data[-rewind - 1]))

-rewind - 1 is unsigned, so on the first iteration it is SIZE_MAX, and
data + SIZE_MAX is undefined pointer arithmetic even though the resulting
address (the byte before data) is inside the buffer. UBSan reports it for
both functions:

autolink.c:272:32: runtime error: addition of unsigned offset to 0x606000000145 overflowed to 0x606000000144
autolink.c:216:15: runtime error: addition of unsigned offset to 0x606000000141 overflowed to 0x606000000140

and with -fno-sanitize-recover=all, rendering the 9-byte document https://a
or the 2-byte document a@ aborts the process.

Fix

Walk with a pointer that is decremented once per iteration:

const uint8_t *p = data;
...
uint8_t c = *--p;                            /* email */
while (rewind < max_rewind && isalpha(*--p)) /* url   */

No index below zero is ever formed; the loop bound and the bytes read are
unchanged.

Verification

Build (no Ruby needed):

D=ext/redcarpet
clang -g -O1 -D_GNU_SOURCE -std=gnu11 -fsanitize=address,undefined \
  -fno-sanitize-recover=all -I$D driver.c \
  $D/markdown.c $D/html.c $D/html_smartypants.c $D/autolink.c \
  $D/buffer.c $D/stack.c $D/houdini_href_e.c $D/houdini_html_e.c -o repro

with the driver.c from the reports.

input bytes before after
https://a 9 exit 1, UBSan autolink.c:272 exit 0
a@ 2 exit 1, UBSan autolink.c:216 exit 0

No behaviour change: rendered 71 documents (every
test/MarkdownTest_1.0*/Tests/*.text plus 30 autolink/table/blockquote cases)
with the pristine and the patched build (plain -O2); HTML output, stderr and
exit status are byte-identical.

Note

For the callers in markdown.c, max_rewind is the distance to the last
consumed character, so the byte reached is inside the document; the defect is
the undefined index arithmetic itself.

sd_autolink__email() and sd_autolink__url() walk backwards with an index
expression computed in size_t:

    data[-rewind - 1]

With rewind == 0 this is data + (size_t)-1, which is undefined pointer
arithmetic even though the resulting address is the previous byte. UBSan
reports "addition of unsigned offset ... overflowed" for both functions,
and with -fno-sanitize-recover=all rendering a 2-byte or a 9-byte
document aborts.

Walk with a pointer that is decremented once per iteration instead, so no
index is ever formed below zero. The bytes read are the same.
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.

[Bug]sd_autolink__url() reads out of bounds: unsigned-offset underflow in the URL rewind scan (data[-rewind - 1]) (CWE-125)

1 participant