CVE-2021-30027: Use of Uninitialized Resource (CWE-908)
value: md_analyze_line in md4c.c in md4c 0.4.7 allows attackers to trigger use of uninitialized memory, and cause a denial of service via a malformed Markdown document.
CWE description: The product uses or accesses a resource that has not been initialized.
CVSS v3 base score: 5.5
Published: 2021-04-29T15:15Z
Repository: https://github.com/mity/md4c
Language: C
Files changed: CHANGELOG.md, src/md4c.c
Commit date: 2021-03-29 12:51:48 +0200
Commit message: md_analyze_line: Avoid reading 1 byte beyond the input size.

Fixes #155.

Diff: diff --git a/CHANGELOG.md b/CHANGELOG.md
index 649cf1b..1067a67 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -19,10 +19,13 @@ Fixes:
    [#154](https://github.com/mity/md4c/issues/154):
    Set `MD_BLOCK_UL_DETAIL::mark` and `MD_BLOCK_OL_DETAIL::mark_delimiter`
    correctly, even when the blocks are nested at the same line in a complicated
    ways.
 
+ * [#155](https://github.com/mity/md4c/issues/155):
+   Avoid reading 1 character beyond the input size in some complex cases.
+
 
 ## Version 0.4.7
 
 Changes:
 
diff --git a/src/md4c.c b/src/md4c.c
index 9e720be..2858592 100644
--- a/src/md4c.c
+++ b/src/md4c.c
@@ -5867,11 +5867,11 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end,
     #endif
         }
 
         /* Check whether we are Setext underline. */
         if(line->indent < ctx->code_indent_offset  &&  pivot_line->type == MD_LINE_TEXT
-            &&  (CH(off) == _T('=') || CH(off) == _T('-'))
+            &&  off < ctx->size  &&  ISANYOF2(off, _T('='), _T('-'))
             &&  (n_parents == ctx->n_containers))
         {
             unsigned level;
 
             if(md_is_setext_underline(ctx, off, &off, &level)) {
@@ -5880,11 +5880,14 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end,
                 break;
             }
         }
 
         /* Check for thematic break line. */
-        if(line->indent < ctx->code_indent_offset  &&  ISANYOF(off, _T("-_*"))  &&  off >= hr_killer) {
+        if(line->indent < ctx->code_indent_offset
+            &&  off < ctx->size  &&  off >= hr_killer
+            &&  ISANYOF(off, _T("-_*")))
+        {
             if(md_is_hr_line(ctx, off, &off, &hr_killer)) {
                 line->type = MD_LINE_HR;
                 break;
             }
         }
@@ -5944,11 +5947,11 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end,
             if(pivot_line->type == MD_LINE_TEXT  &&  n_parents == ctx->n_containers  &&
                         (off >= ctx->size || ISNEWLINE(off))  &&  container.ch != _T('>'))
             {
                 /* Noop. List mark followed by a blank line cannot interrupt a paragraph. */
             } else if(pivot_line->type == MD_LINE_TEXT  &&  n_parents == ctx->n_containers  &&
-                        (container.ch == _T('.') || container.ch == _T(')'))  &&  container.start != 1)
+                        ISANYOF2_(container.ch, _T('.'), _T(')'))  &&  container.start != 1)
             {
                 /* Noop. Ordered list cannot interrupt a paragraph unless the start index is 1. */
             } else {
                 total_indent += container.contents_indent - container.mark_indent;
                 line->indent = md_line_indentation(ctx, total_indent, off, &off);
@@ -5985,31 +5988,34 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end,
             line->type = MD_LINE_TABLE;
             break;
         }
 
         /* Check for ATX header. */
-        if(line->indent < ctx->code_indent_offset  &&  CH(off) == _T('#')) {
+        if(line->indent < ctx->code_indent_offset  &&
+                off < ctx->size  &&  CH(off) == _T('#'))
+        {
             unsigned level;
 
             if(md_is_atxheader_line(ctx, off, &line->beg, &off, &level)) {
                 line->type = MD_LINE_ATXHEADER;
                 line->data = level;
                 break;
             }
         }
 
         /* Check whether we are starting code fence. */
-        if(CH(off) == _T('`') || CH(off) == _T('~')) {
+        if(off < ctx->size  &&  ISANYOF2(off, _T('`'), _T('~'))) {
             if(md_is_opening_code_fence(ctx, off, &off)) {
                 line->type = MD_LINE_FENCEDCODE;
                 line->data = 1;
                 break;
             }
         }
 
         /* Check for start of raw HTML block. */
-        if(CH(off) == _T('<')  &&  !(ctx->parser.flags & MD_FLAG_NOHTMLBLOCKS))
+        if(off < ctx->size  &&  CH(off) == _T('<')
+            &&  !(ctx->parser.flags & MD_FLAG_NOHTMLBLOCKS))
         {
             ctx->html_block_type = md_is_html_block_start_condition(ctx, off);
 
             /* HTML block type 7 cannot interrupt paragraph. */
             if(ctx->html_block_type == 7  &&  pivot_line->type == MD_LINE_TEXT)
@@ -6026,13 +6032,13 @@ md_analyze_line(MD_CTX* ctx, OFF beg, OFF* p_end,
                 break;
             }
         }
 
         /* Check for table underline. */
-        if((ctx->parser.flags & MD_FLAG_TABLES)  &&  pivot_line->type == MD_LINE_TEXT  &&
-           (CH(off) == _T('|') || CH(off) == _T('-') || CH(off) == _T(':'))  &&
-           n_parents == ctx->n_containers)
+        if((ctx->parser.flags & MD_FLAG_TABLES)  &&  pivot_line->type == MD_LINE_TEXT
+            &&  off < ctx->size  &&  ISANYOF3(off, _T('|'), _T('-'), _T(':'))
+            &&  n_parents == ctx->n_containers)
         {
             unsigned col_count;
 
             if(ctx->current_block != NULL  &&  ctx->current_block->n_lines == 1  &&
                 md_is_table_underline(ctx, off, &off, &col_count))

Vulnerable code:             &&  (CH(off) == _T('=') || CH(off) == _T('-'))
        if(line->indent < ctx->code_indent_offset  &&  ISANYOF(off, _T("-_*"))  &&  off >= hr_killer) {
                        (container.ch == _T('.') || container.ch == _T(')'))  &&  container.start != 1)
        if(line->indent < ctx->code_indent_offset  &&  CH(off) == _T('#')) {
        if(CH(off) == _T('`') || CH(off) == _T('~')) {
        if(CH(off) == _T('<')  &&  !(ctx->parser.flags & MD_FLAG_NOHTMLBLOCKS))
        if((ctx->parser.flags & MD_FLAG_TABLES)  &&  pivot_line->type == MD_LINE_TEXT  &&
           (CH(off) == _T('|') || CH(off) == _T('-') || CH(off) == _T(':'))  &&
           n_parents == ctx->n_containers)
Fixed code:  * [#155](https://github.com/mity/md4c/issues/155):
   Avoid reading 1 character beyond the input size in some complex cases.

            &&  off < ctx->size  &&  ISANYOF2(off, _T('='), _T('-'))
        if(line->indent < ctx->code_indent_offset
            &&  off < ctx->size  &&  off >= hr_killer
            &&  ISANYOF(off, _T("-_*")))
        {
                        ISANYOF2_(container.ch, _T('.'), _T(')'))  &&  container.start != 1)
        if(line->indent < ctx->code_indent_offset  &&
                off < ctx->size  &&  CH(off) == _T('#'))
        {
        if(off < ctx->size  &&  ISANYOF2(off, _T('`'), _T('~'))) {
        if(off < ctx->size  &&  CH(off) == _T('<')
            &&  !(ctx->parser.flags & MD_FLAG_NOHTMLBLOCKS))
        if((ctx->parser.flags & MD_FLAG_TABLES)  &&  pivot_line->type == MD_LINE_TEXT
            &&  off < ctx->size  &&  ISANYOF3(off, _T('|'), _T('-'), _T(':'))
            &&  n_parents == ctx->n_containers)