1111#include "table.h"
1212#include "cmark-gfm-core-extensions.h"
1313
14+ // Limit to prevent a malicious input from causing a denial of service.
15+ #define MAX_AUTOCOMPLETED_CELLS 0x80000
16+
1417// Custom node flag, initialized in `create_table_extension`.
1518static cmark_node_internal_flags CMARK_NODE__TABLE_VISITED ;
1619
@@ -31,6 +34,8 @@ typedef struct {
3134typedef struct {
3235 uint16_t n_columns ;
3336 uint8_t * alignments ;
37+ int n_rows ;
38+ int n_nonempty_cells ;
3439} node_table ;
3540
3641typedef struct {
@@ -83,6 +88,33 @@ static int set_n_table_columns(cmark_node *node, uint16_t n_columns) {
8388 return 1 ;
8489}
8590
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+
86118static uint8_t * get_table_alignments (cmark_node * node ) {
87119 if (!node || node -> type != CMARK_NODE_TABLE )
88120 return 0 ;
@@ -98,6 +130,23 @@ static int set_table_alignments(cmark_node *node, uint8_t *alignments) {
98130 return 1 ;
99131}
100132
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+
101150static cmark_strbuf * unescape_pipes (cmark_mem * mem , unsigned char * string , bufsize_t len )
102151{
103152 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,
257306 unsigned char * input , int len ) {
258307 cmark_node * table_header ;
259308 table_row * header_row = NULL ;
260- table_row * marker_row = NULL ;
309+ table_row * delimiter_row = NULL ;
261310 node_table_row * ntr ;
262311 const char * parent_string ;
263312 uint16_t i ;
@@ -270,16 +319,16 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
270319 return parent_container ;
271320 }
272321
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 ));
277326 // assert may be optimized out, don't rely on it for security boundaries
278- if (!marker_row ) {
327+ if (!delimiter_row ) {
279328 return parent_container ;
280329 }
281-
282- assert (marker_row );
330+
331+ assert (delimiter_row );
283332
284333 cmark_arena_push ();
285334
@@ -289,31 +338,31 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
289338 parent_string = cmark_node_get_string_content (parent_container );
290339 header_row = row_from_string (self , parser , (unsigned char * )parent_string ,
291340 (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 );
294343 free_table_row (parser -> mem , header_row );
295344 cmark_arena_pop ();
296345 parent_container -> flags |= CMARK_NODE__TABLE_VISITED ;
297346 return parent_container ;
298347 }
299348
300349 if (cmark_arena_pop ()) {
301- marker_row = row_from_string (
350+ delimiter_row = row_from_string (
302351 self , parser , input + cmark_parser_get_first_nonspace (parser ),
303352 len - cmark_parser_get_first_nonspace (parser ));
304353 header_row = row_from_string (self , parser , (unsigned char * )parent_string ,
305354 (int )strlen (parent_string ));
306355 // 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 );
309358 free_table_row (parser -> mem , header_row );
310359 return parent_container ;
311360 }
312361 }
313362
314363 if (!cmark_node_set_type (parent_container , CMARK_NODE_TABLE )) {
315364 free_table_row (parser -> mem , header_row );
316- free_table_row (parser -> mem , marker_row );
365+ free_table_row (parser -> mem , delimiter_row );
317366 return parent_container ;
318367 }
319368
@@ -326,12 +375,12 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
326375 parent_container -> as .opaque = parser -> mem -> calloc (1 , sizeof (node_table ));
327376 set_n_table_columns (parent_container , header_row -> n_columns );
328377
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
331380 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 ];
335384 bool left = node -> buf -> ptr [0 ] == ':' , right = node -> buf -> ptr [node -> buf -> size - 1 ] == ':' ;
336385
337386 if (left && right )
@@ -353,25 +402,26 @@ static cmark_node *try_opening_table_header(cmark_syntax_extension *self,
353402 table_header -> as .opaque = ntr = (node_table_row * )parser -> mem -> calloc (1 , sizeof (node_table_row ));
354403 ntr -> is_header = true;
355404
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 );
367415 }
368416
417+ incr_table_row_count (parent_container , i );
418+
369419 cmark_parser_advance_offset (
370420 parser , (char * )input ,
371421 (int )strlen ((char * )input ) - 1 - cmark_parser_get_offset (parser ), false);
372422
373423 free_table_row (parser -> mem , header_row );
374- free_table_row (parser -> mem , marker_row );
424+ free_table_row (parser -> mem , delimiter_row );
375425 return parent_container ;
376426}
377427
@@ -385,6 +435,10 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
385435 if (cmark_parser_is_blank (parser ))
386436 return NULL ;
387437
438+ if (get_n_autocompleted_cells (parent_container ) > MAX_AUTOCOMPLETED_CELLS ) {
439+ return NULL ;
440+ }
441+
388442 table_row_block =
389443 cmark_parser_add_child (parser , parent_container , CMARK_NODE_TABLE_ROW ,
390444 parent_container -> start_column );
@@ -412,12 +466,16 @@ static cmark_node *try_opening_table_row(cmark_syntax_extension *self,
412466 node -> end_column = parent_container -> start_column + cell -> end_offset ;
413467 cmark_node_set_string_content (node , (char * ) cell -> buf -> ptr );
414468 cmark_node_set_syntax_extension (node , self );
469+ set_cell_index (node , i );
415470 }
416471
472+ incr_table_row_count (parent_container , i );
473+
417474 for (; i < table_columns ; ++ i ) {
418475 cmark_node * node = cmark_parser_add_child (
419476 parser , table_row_block , CMARK_NODE_TABLE_CELL , 0 );
420477 cmark_node_set_syntax_extension (node , self );
478+ set_cell_index (node , i );
421479 }
422480 }
423481
@@ -602,13 +660,7 @@ static const char *xml_attr(cmark_syntax_extension *extension,
602660 cmark_node * node ) {
603661 if (node -> type == CMARK_NODE_TABLE_CELL ) {
604662 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 )) {
612664 case 'l' : return " align=\"left\"" ;
613665 case 'c' : return " align=\"center\"" ;
614666 case 'r' : return " align=\"right\"" ;
@@ -696,7 +748,6 @@ static void html_render(cmark_syntax_extension *extension,
696748 cmark_event_type ev_type , int options ) {
697749 bool entering = (ev_type == CMARK_EVENT_ENTER );
698750 cmark_strbuf * html = renderer -> html ;
699- cmark_node * n ;
700751
701752 // XXX: we just monopolise renderer->opaque.
702753 struct html_table_state * table_state =
@@ -745,7 +796,6 @@ static void html_render(cmark_syntax_extension *extension,
745796 }
746797 }
747798 } else if (node -> type == CMARK_NODE_TABLE_CELL ) {
748- uint8_t * alignments = get_table_alignments (node -> parent -> parent );
749799 if (entering ) {
750800 cmark_html_render_cr (html );
751801 if (table_state -> in_table_header ) {
@@ -754,12 +804,7 @@ static void html_render(cmark_syntax_extension *extension,
754804 cmark_strbuf_puts (html , "<td" );
755805 }
756806
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 )) {
763808 case 'l' : html_table_add_align (html , "left" , options ); break ;
764809 case 'c' : html_table_add_align (html , "center" , options ); break ;
765810 case 'r' : html_table_add_align (html , "right" , options ); break ;
0 commit comments