Skip to content

Commit 5557ccf

Browse files
Merge pull request #376 from sehe/pr-364
Fix security issue #364 and non-keyword subgraph parsing
2 parents 17acf25 + 05aa9fd commit 5557ccf

File tree

2 files changed

+71
-11
lines changed

2 files changed

+71
-11
lines changed

src/read_graphviz_new.cpp

+26-11
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ namespace boost
5353

5454
namespace read_graphviz_detail
5555
{
56+
static const long max_subgraph_nesting_level = 255;
5657
struct token
5758
{
5859
enum token_type
@@ -207,7 +208,7 @@ namespace read_graphviz_detail
207208

208209
tokenizer(const std::string& str) : begin(str.begin()), end(str.end())
209210
{
210-
std::string end_of_token = "(?=(?:\\W))";
211+
// std::string end_of_token = "(?=(?:\\W))"; // SEHE: unused?
211212
std::string whitespace = "(?:\\s+)";
212213
std::string slash_slash_comment = "(?://.*?$)";
213214
std::string slash_star_comment = "(?:/\\*.*?\\*/)";
@@ -527,6 +528,7 @@ namespace read_graphviz_detail
527528
std::map< subgraph_name, subgraph_info > subgraphs;
528529
std::string current_subgraph_name;
529530
int sgcounter; // Counter for anonymous subgraphs
531+
long sgnesting_level;
530532
std::set< std::pair< node_name, node_name > >
531533
existing_edges; // Used for checking in strict graphs
532534

@@ -538,7 +540,7 @@ namespace read_graphviz_detail
538540
subgraph_member_list& current_members() { return current().members; }
539541

540542
parser(const std::string& gr, parser_result& result)
541-
: the_tokenizer(gr), lookahead(), r(result), sgcounter(0)
543+
: the_tokenizer(gr), lookahead(), r(result), sgcounter(0), sgnesting_level(0)
542544
{
543545
current_subgraph_name = "___root___";
544546
current() = subgraph_info(); // Initialize root graph
@@ -773,10 +775,18 @@ namespace read_graphviz_detail
773775
bool is_anonymous = true;
774776
if (first_token.type == token::kw_subgraph)
775777
{
776-
if (peek().type == token::identifier)
778+
switch (peek().type)
777779
{
780+
case token::identifier:
778781
name = get().normalized_value;
779782
is_anonymous = false;
783+
break;
784+
case token::left_brace:
785+
is_anonymous = true;
786+
break;
787+
default:
788+
error("Subgraph reference needs a name");
789+
break;
780790
}
781791
}
782792
if (is_anonymous)
@@ -790,25 +800,30 @@ namespace read_graphviz_detail
790800
= current(); // Initialize properties and defaults
791801
subgraphs[name].members.clear(); // Except member list
792802
}
793-
if (first_token.type == token::kw_subgraph
794-
&& peek().type != token::left_brace)
803+
if (!is_anonymous && peek().type != token::left_brace)
795804
{
796-
if (is_anonymous)
797-
error("Subgraph reference needs a name");
798805
return name;
799806
}
800807
subgraph_name old_sg = current_subgraph_name;
808+
if (++sgnesting_level > max_subgraph_nesting_level)
809+
{
810+
error("Exceeded maximum subgraph nesting level");
811+
}
801812
current_subgraph_name = name;
802-
if (peek().type == token::left_brace)
803-
get();
804-
else
805-
error("Wanted left brace to start subgraph");
813+
if (first_token.type != token::left_brace)
814+
{
815+
if (peek().type == token::left_brace)
816+
get();
817+
else
818+
error("Wanted left brace to start subgraph");
819+
}
806820
parse_stmt_list();
807821
if (peek().type == token::right_brace)
808822
get();
809823
else
810824
error("Wanted right brace to end subgraph");
811825
current_subgraph_name = old_sg;
826+
sgnesting_level -= 1;
812827
return name;
813828
}
814829

test/graphviz_test.cpp

+45
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,49 @@ void test_basic_csr_directed_graph_ext_props()
440440
edge_weight);
441441
}
442442

443+
void test_subgraphs()
444+
{
445+
// on the BGL side, the new parser doesn't support subgraphs
446+
// however, the docs promise to support reading them on the input side as
447+
// "syntactic sugar".
448+
for (auto gv : {
449+
Fixture { "digraph {}" },
450+
Fixture { "digraph { 1 -> {} }", 1 },
451+
Fixture { "digraph { 1 -> {2} }", 2 },
452+
Fixture { "digraph { 1; { 2; 3; } }", 3 },
453+
Fixture { "digraph { { 2; 3; } 1; }", 3 },
454+
Fixture { "digraph { 1; subgraph { 2; 3; } }", 3 },
455+
Fixture { "digraph { 1 -> subgraph { 2; 3; } }", 3 },
456+
Fixture { "digraph { 1 -> subgraph hello { 2; 3; } }", 3 },
457+
Fixture { "digraph { 1 -> subgraph clust_Hello { 2; 3; } }", 3 },
458+
Fixture { "digraph { 1 -> subgraph \"hello\" { 2; 3; } }", 3 },
459+
Fixture {
460+
"digraph { {2} -> subgraph \"hello\" {{{{ 2; 3; }}}} }", 2 },
461+
})
462+
{
463+
TEST_GRAPH(Models::DiGraph, gv);
464+
}
465+
}
466+
467+
void test_subgraph_nesting_limit() // issue #364
468+
{
469+
auto independent_nests = [=](unsigned level)
470+
{
471+
auto sg = std::string(level, '{') + " 2; 3; " + std::string(level, '}');
472+
ComparisonDriver::test_graph< Models::DiGraph >(
473+
{ "digraph{1->" + sg + "}", 3 });
474+
ComparisonDriver::test_graph< Models::DiGraph >(
475+
{ "digraph{1->" + sg + ";4->" + sg + "}", 4 });
476+
};
477+
478+
constexpr unsigned limit = 255;
479+
independent_nests(1);
480+
independent_nests(limit / 2);
481+
independent_nests(limit - 1);
482+
independent_nests(limit); // edge-case
483+
BOOST_TEST_THROWS(independent_nests(limit + 1), boost::bad_graphviz_syntax);
484+
}
485+
443486
int main()
444487
{
445488
test_basic_directed_graph_1();
@@ -457,5 +500,7 @@ int main()
457500
test_comments_embedded_in_strings();
458501
test_basic_csr_directed_graph_ext_props();
459502
test_basic_csr_directed_graph();
503+
test_subgraphs();
504+
test_subgraph_nesting_limit();
460505
return boost::report_errors();
461506
}

0 commit comments

Comments
 (0)