11
11
#include "table.h"
12
12
#include "cmark-gfm-core-extensions.h"
13
13
14
+ // Limit to prevent a malicious input from causing a denial of service.
15
+ #define MAX_AUTOCOMPLETED_CELLS 0x80000
16
+
14
17
// Custom node flag, initialized in `create_table_extension`.
15
18
static cmark_node_internal_flags CMARK_NODE__TABLE_VISITED ;
16
19
@@ -31,6 +34,8 @@ typedef struct {
31
34
typedef struct {
32
35
uint16_t n_columns ;
33
36
uint8_t * alignments ;
37
+ int n_rows ;
38
+ int n_nonempty_cells ;
34
39
} node_table ;
35
40
36
41
typedef struct {
@@ -83,6 +88,33 @@ static int set_n_table_columns(cmark_node *node, uint16_t n_columns) {
83
88
return 1 ;
84
89
}
85
90
91
+ // Increment the number of rows in the table. Also update n_nonempty_cells,
92
+ // which keeps track of the number of cells which were parsed from the
93
+ // input file. (If one of the rows is too short, then the trailing cells
94
+ // are autocompleted. Autocompleted cells are not counted in n_nonempty_cells.)
95
+ // The purpose of this is to prevent a malicious input from generating a very
96
+ // large number of autocompleted cells, which could cause a denial of service
97
+ // vulnerability.
98
+ static int incr_table_row_count (cmark_node * node , int i ) {
99
+ if (!node || node -> type != CMARK_NODE_TABLE ) {
100
+ return 0 ;
101
+ }
102
+
103
+ ((node_table * )node -> as .opaque )-> n_rows ++ ;
104
+ ((node_table * )node -> as .opaque )-> n_nonempty_cells += i ;
105
+ return 1 ;
106
+ }
107
+
108
+ // Calculate the number of autocompleted cells.
109
+ static int get_n_autocompleted_cells (cmark_node * node ) {
110
+ if (!node || node -> type != CMARK_NODE_TABLE ) {
111
+ return 0 ;
112
+ }
113
+
114
+ const node_table * nt = (node_table * )node -> as .opaque ;
115
+ return (nt -> n_columns * nt -> n_rows ) - nt -> n_nonempty_cells ;
116
+ }
117
+
86
118
static uint8_t * get_table_alignments (cmark_node * node ) {
87
119
if (!node || node -> type != CMARK_NODE_TABLE )
88
120
return 0 ;
@@ -98,6 +130,23 @@ static int set_table_alignments(cmark_node *node, uint8_t *alignments) {
98
130
return 1 ;
99
131
}
100
132
133
+ static uint8_t get_cell_alignment (cmark_node * node ) {
134
+ if (!node || node -> type != CMARK_NODE_TABLE_CELL )
135
+ return 0 ;
136
+
137
+ const uint8_t * alignments = get_table_alignments (node -> parent -> parent );
138
+ int i = node -> as .cell_index ;
139
+ return alignments [i ];
140
+ }
141
+
142
+ static int set_cell_index (cmark_node * node , int i ) {
143
+ if (!node || node -> type != CMARK_NODE_TABLE_CELL )
144
+ return 0 ;
145
+
146
+ node -> as .cell_index = i ;
147
+ return 1 ;
148
+ }
149
+
101
150
static cmark_strbuf * unescape_pipes (cmark_mem * mem , unsigned char * string , bufsize_t len )
102
151
{
103
152
cmark_strbuf * res = (cmark_strbuf * )mem -> calloc (1 , sizeof (cmark_strbuf ));
@@ -257,7 +306,7 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
257
306
unsigned char * input , int len ) {
258
307
cmark_node * table_header ;
259
308
table_row * header_row = NULL ;
260
- table_row * marker_row = NULL ;
309
+ table_row * delimiter_row = NULL ;
261
310
node_table_row * ntr ;
262
311
const char * parent_string ;
263
312
uint16_t i ;
@@ -270,16 +319,16 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
270
319
return parent_container ;
271
320
}
272
321
273
- // Since scan_table_start was successful, we must have a marker row.
274
- marker_row = row_from_string (self , parser ,
275
- input + cmark_parser_get_first_nonspace (parser ),
276
- len - cmark_parser_get_first_nonspace (parser ));
322
+ // Since scan_table_start was successful, we must have a delimiter row.
323
+ delimiter_row = row_from_string (
324
+ self , parser , input + cmark_parser_get_first_nonspace (parser ),
325
+ len - cmark_parser_get_first_nonspace (parser ));
277
326
// assert may be optimized out, don't rely on it for security boundaries
278
- if (!marker_row ) {
327
+ if (!delimiter_row ) {
279
328
return parent_container ;
280
329
}
281
-
282
- assert (marker_row );
330
+
331
+ assert (delimiter_row );
283
332
284
333
cmark_arena_push ();
285
334
@@ -289,31 +338,31 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
289
338
parent_string = cmark_node_get_string_content (parent_container );
290
339
header_row = row_from_string (self , parser , (unsigned char * )parent_string ,
291
340
(int )strlen (parent_string ));
292
- if (!header_row || header_row -> n_columns != marker_row -> n_columns ) {
293
- free_table_row (parser -> mem , marker_row );
341
+ if (!header_row || header_row -> n_columns != delimiter_row -> n_columns ) {
342
+ free_table_row (parser -> mem , delimiter_row );
294
343
free_table_row (parser -> mem , header_row );
295
344
cmark_arena_pop ();
296
345
parent_container -> flags |= CMARK_NODE__TABLE_VISITED ;
297
346
return parent_container ;
298
347
}
299
348
300
349
if (cmark_arena_pop ()) {
301
- marker_row = row_from_string (
350
+ delimiter_row = row_from_string (
302
351
self , parser , input + cmark_parser_get_first_nonspace (parser ),
303
352
len - cmark_parser_get_first_nonspace (parser ));
304
353
header_row = row_from_string (self , parser , (unsigned char * )parent_string ,
305
354
(int )strlen (parent_string ));
306
355
// row_from_string can return NULL, add additional check to ensure n_columns match
307
- if (!marker_row || !header_row || header_row -> n_columns != marker_row -> n_columns ) {
308
- free_table_row (parser -> mem , marker_row );
356
+ if (!delimiter_row || !header_row || header_row -> n_columns != delimiter_row -> n_columns ) {
357
+ free_table_row (parser -> mem , delimiter_row );
309
358
free_table_row (parser -> mem , header_row );
310
359
return parent_container ;
311
360
}
312
361
}
313
362
314
363
if (!cmark_node_set_type (parent_container , CMARK_NODE_TABLE )) {
315
364
free_table_row (parser -> mem , header_row );
316
- free_table_row (parser -> mem , marker_row );
365
+ free_table_row (parser -> mem , delimiter_row );
317
366
return parent_container ;
318
367
}
319
368
@@ -326,12 +375,12 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
326
375
parent_container -> as .opaque = parser -> mem -> calloc (1 , sizeof (node_table ));
327
376
set_n_table_columns (parent_container , header_row -> n_columns );
328
377
329
- // allocate alignments based on marker_row ->n_columns
330
- // since we populate the alignments array based on marker_row ->cells
378
+ // allocate alignments based on delimiter_row ->n_columns
379
+ // since we populate the alignments array based on delimiter_row ->cells
331
380
uint8_t * alignments =
332
- (uint8_t * )parser -> mem -> calloc (marker_row -> n_columns , sizeof (uint8_t ));
333
- for (i = 0 ; i < marker_row -> n_columns ; ++ i ) {
334
- node_cell * node = & marker_row -> cells [i ];
381
+ (uint8_t * )parser -> mem -> calloc (delimiter_row -> n_columns , sizeof (uint8_t ));
382
+ for (i = 0 ; i < delimiter_row -> n_columns ; ++ i ) {
383
+ node_cell * node = & delimiter_row -> cells [i ];
335
384
bool left = node -> buf -> ptr [0 ] == ':' , right = node -> buf -> ptr [node -> buf -> size - 1 ] == ':' ;
336
385
337
386
if (left && right )
@@ -353,25 +402,26 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
353
402
table_header -> as .opaque = ntr = (node_table_row * )parser -> mem -> calloc (1 , sizeof (node_table_row ));
354
403
ntr -> is_header = true;
355
404
356
- {
357
- for (i = 0 ; i < header_row -> n_columns ; ++ i ) {
358
- node_cell * cell = & header_row -> cells [i ];
359
- cmark_node * header_cell = cmark_parser_add_child (parser , table_header ,
360
- CMARK_NODE_TABLE_CELL , parent_container -> start_column + cell -> start_offset );
361
- header_cell -> start_line = header_cell -> end_line = parent_container -> start_line ;
362
- header_cell -> internal_offset = cell -> internal_offset ;
363
- header_cell -> end_column = parent_container -> start_column + cell -> end_offset ;
364
- cmark_node_set_string_content (header_cell , (char * ) cell -> buf -> ptr );
365
- cmark_node_set_syntax_extension (header_cell , self );
366
- }
405
+ for (i = 0 ; i < header_row -> n_columns ; ++ i ) {
406
+ node_cell * cell = & header_row -> cells [i ];
407
+ cmark_node * header_cell = cmark_parser_add_child (parser , table_header ,
408
+ CMARK_NODE_TABLE_CELL , parent_container -> start_column + cell -> start_offset );
409
+ header_cell -> start_line = header_cell -> end_line = parent_container -> start_line ;
410
+ header_cell -> internal_offset = cell -> internal_offset ;
411
+ header_cell -> end_column = parent_container -> start_column + cell -> end_offset ;
412
+ cmark_node_set_string_content (header_cell , (char * ) cell -> buf -> ptr );
413
+ cmark_node_set_syntax_extension (header_cell , self );
414
+ set_cell_index (header_cell , i );
367
415
}
368
416
417
+ incr_table_row_count (parent_container , i );
418
+
369
419
cmark_parser_advance_offset (
370
420
parser , (char * )input ,
371
421
(int )strlen ((char * )input ) - 1 - cmark_parser_get_offset (parser ), false);
372
422
373
423
free_table_row (parser -> mem , header_row );
374
- free_table_row (parser -> mem , marker_row );
424
+ free_table_row (parser -> mem , delimiter_row );
375
425
return parent_container ;
376
426
}
377
427
@@ -385,6 +435,10 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
385
435
if (cmark_parser_is_blank (parser ))
386
436
return NULL ;
387
437
438
+ if (get_n_autocompleted_cells (parent_container ) > MAX_AUTOCOMPLETED_CELLS ) {
439
+ return NULL ;
440
+ }
441
+
388
442
table_row_block =
389
443
cmark_parser_add_child (parser , parent_container , CMARK_NODE_TABLE_ROW ,
390
444
parent_container -> start_column );
@@ -412,12 +466,16 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
412
466
node -> end_column = parent_container -> start_column + cell -> end_offset ;
413
467
cmark_node_set_string_content (node , (char * ) cell -> buf -> ptr );
414
468
cmark_node_set_syntax_extension (node , self );
469
+ set_cell_index (node , i );
415
470
}
416
471
472
+ incr_table_row_count (parent_container , i );
473
+
417
474
for (; i < table_columns ; ++ i ) {
418
475
cmark_node * node = cmark_parser_add_child (
419
476
parser , table_row_block , CMARK_NODE_TABLE_CELL , 0 );
420
477
cmark_node_set_syntax_extension (node , self );
478
+ set_cell_index (node , i );
421
479
}
422
480
}
423
481
@@ -602,13 +660,7 @@ static const char *xml_attr(cmark_syntax_extension *extension,
602
660
cmark_node * node ) {
603
661
if (node -> type == CMARK_NODE_TABLE_CELL ) {
604
662
if (cmark_gfm_extensions_get_table_row_is_header (node -> parent )) {
605
- uint8_t * alignments = get_table_alignments (node -> parent -> parent );
606
- int i = 0 ;
607
- cmark_node * n ;
608
- for (n = node -> parent -> first_child ; n ; n = n -> next , ++ i )
609
- if (n == node )
610
- break ;
611
- switch (alignments [i ]) {
663
+ switch (get_cell_alignment (node )) {
612
664
case 'l' : return " align=\"left\"" ;
613
665
case 'c' : return " align=\"center\"" ;
614
666
case 'r' : return " align=\"right\"" ;
@@ -696,7 +748,6 @@ static void html_render(cmark_syntax_extension *extension,
696
748
cmark_event_type ev_type , int options ) {
697
749
bool entering = (ev_type == CMARK_EVENT_ENTER );
698
750
cmark_strbuf * html = renderer -> html ;
699
- cmark_node * n ;
700
751
701
752
// XXX: we just monopolise renderer->opaque.
702
753
struct html_table_state * table_state =
@@ -745,7 +796,6 @@ static void html_render(cmark_syntax_extension *extension,
745
796
}
746
797
}
747
798
} else if (node -> type == CMARK_NODE_TABLE_CELL ) {
748
- uint8_t * alignments = get_table_alignments (node -> parent -> parent );
749
799
if (entering ) {
750
800
cmark_html_render_cr (html );
751
801
if (table_state -> in_table_header ) {
@@ -754,12 +804,7 @@ static void html_render(cmark_syntax_extension *extension,
754
804
cmark_strbuf_puts (html , "<td" );
755
805
}
756
806
757
- int i = 0 ;
758
- for (n = node -> parent -> first_child ; n ; n = n -> next , ++ i )
759
- if (n == node )
760
- break ;
761
-
762
- switch (alignments [i ]) {
807
+ switch (get_cell_alignment (node )) {
763
808
case 'l' : html_table_add_align (html , "left" , options ); break ;
764
809
case 'c' : html_table_add_align (html , "center" , options ); break ;
765
810
case 'r' : html_table_add_align (html , "right" , options ); break ;
0 commit comments