Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalid escaped character in string will crash the parser. #222

Closed
Coder666 opened this issue Mar 14, 2022 · 23 comments · Fixed by #231
Closed

Invalid escaped character in string will crash the parser. #222

Coder666 opened this issue Mar 14, 2022 · 23 comments · Fixed by #231
Assignees

Comments

@Coder666
Copy link

Hi,

It is possible to crash the parser (client/query/subscription) by having an invalid escaped character.

For example using a directive:

@something( s : "\." )

This looks like it causes an infinite recursion

@wravery
Copy link
Contributor

wravery commented Mar 14, 2022

Thanks, good catch!

@wravery wravery self-assigned this Mar 14, 2022
wravery added a commit to wravery/cppgraphqlgen that referenced this issue Mar 14, 2022
wravery added a commit to wravery/cppgraphqlgen that referenced this issue Mar 14, 2022
@wravery
Copy link
Contributor

wravery commented Mar 14, 2022

Hmm... I haven't been able to reproduce this. Maybe it was an older build of PEGTL that triggered it. I'm going to merge a new unit test to pin the fix.

@Coder666
Copy link
Author

This is occuring for me on 3.7.1

@Coder666
Copy link
Author

Coder666 commented Mar 14, 2022

I just upgraded to PEGTL 3.2.5 and this issue still occurs for me in cppgraphqlgen 3.7.1

I have a directive:

directive @deliverIfStringMatch ( str : String!, caseSensitive : Boolean! ) on FIELD

Then on a string field I put:

@deliverIfStringMatch ( str : "\.", caseSensitive : false )

Testing with clientgen I get the crash 100%. I also get the crash when subscribing with the same query.

@wravery
Copy link
Contributor

wravery commented Mar 14, 2022

Are you catching parse exceptions? For example, the unit test I'm adding looks like this:

	try
	{
		memory_input<> input(R"gql(query { foo @something(arg: "\.") })gql",
			"InvalidStringEscapeSequence");
		parsedQuery = parse<executable_document>(input);
	}
	catch (const peg::parse_error& e)
	{
		using namespace std::literals;

		ASSERT_NE(nullptr, e.what());
		EXPECT_TRUE(
			"InvalidStringEscapeSequence:1:31: parse error matching struct graphql::peg::string_escape_sequence_content"sv
			== e.what())
			<< e.what();
		caughtException = true;
	}

@wravery
Copy link
Contributor

wravery commented Mar 14, 2022

Testing with clientgen I get the crash 100%. I also get the crash when subscribing with the same query.

Ah... you mean specifically clientgen is crashing here, is that right? Or do you mean when parsing it to subscribe at runtime (since I assume it's not generating the client for you if clientgen crashed)?

@wravery
Copy link
Contributor

wravery commented Mar 14, 2022

...although clientgen should not crash here either. It should output an error message and exit cleanly:

	catch (const graphql::peg::parse_error& pe)
	{
		std::cerr << "Invalid GraphQL: " << pe.what() << std::endl;

		for (const auto& position : pe.positions())
		{
			std::cerr << "\tline: " << position.line << " column: " << position.column << std::endl;
		}

		return 1;
	}

@Coder666
Copy link
Author

Testing with clientgen I get the crash 100%. I also get the crash when subscribing with the same query.

Ah... you mean specifically clientgen is crashing here, is that right? Or do you mean when parsing it to subscribe at runtime (since I assume it's not generating the client for you if clientgen crashed)?

Both client gen and the subscription service, when passed a query string.

Anyway I will try and make a repro today

@Coder666
Copy link
Author

Coder666 commented Mar 15, 2022

Also note that because this is a callstack overflow it is unable to further call into the exception handling routines and then unwind the stack, instead the callstack runs off into a guard page and crashes/produces a unrecoverable exception.

@Coder666
Copy link
Author

Coder666 commented Mar 15, 2022

OK so I have a 100% reproduction on a completly clean check out of the latest, followed with a git checkout of v3.7.1:

git checkout v3.7.1

schema file:


type NodeA
{
    _id     : ID!
    name    : String!
}

type NodeB
{
    _id     : ID!
    name    : String!
    nodeA   : NodeA!
}

type NodeC
{
    _id     : ID!
    name    : String!
    nodeB   : NodeB!
}

type NodeD
{
    _id     : ID!
    name    : String!
    nodeC   : NodeC!
}


directive @test( str : String! ) on FIELD

type Query
{
    nodeD : NodeD!
}

type Subscription
{
    onNodeA                         : NodeA!
    onNodeB                         : NodeB!
    onNodeC                         : NodeC!
    onNodeD                         : NodeD!

}

Query file:

subscription mysub
{
    onNodeD
    {
        nodeC
        {
            nodeB
            {
                nodeA
                {
                    name @test( str : "\." )
                
                }
           
            }
        }
   
    }
}

using clientgen with these files causes the issue.

@Coder666
Copy link
Author

Coder666 commented Mar 15, 2022

>	clientgen.exe!std::_Container_base12::_Orphan_all_locked_v3() Line 1096	C++	Non-user code. Symbols loaded.
 	clientgen.exe!std::_Container_base12::_Orphan_all() Line 1260	C++	Non-user code. Symbols loaded.
 	clientgen.exe!std::vector<std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>,std::allocator<std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>>>::_Tidy() Line 1763	C++	Non-user code. Symbols loaded.
 	clientgen.exe!std::vector<std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>,std::allocator<std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>>>::~vector<std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>,std::allocator<std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>>>() Line 714	C++	Non-user code. Symbols loaded.
 	clientgen.exe!graphql::peg::ast_node::~ast_node() Line 40	C++	Symbols loaded.
 	clientgen.exe!graphql::peg::ast_node::`scalar deleting destructor'(unsigned int)	C++	Non-user code. Symbols loaded.
 	clientgen.exe!std::default_delete<graphql::peg::ast_node>::operator()(graphql::peg::ast_node * _Ptr=0x000001a9c0e2b140) Line 3120	C++	Non-user code. Symbols loaded.
 	clientgen.exe!std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>::~unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>() Line 3232	C++	Non-user code. Symbols loaded.
 	clientgen.exe!std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>::`scalar deleting destructor'(unsigned int)	C++	Non-user code. Symbols loaded.
 	clientgen.exe!std::_Default_allocator_traits<std::allocator<std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>>>::destroy<std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>>(std::allocator<std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>> & __formal={...}, std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>> * const _Ptr=0x000001a9c0e2c0b8) Line 725	C++	Non-user code. Symbols loaded.
 	clientgen.exe!std::vector<std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>,std::allocator<std::unique_ptr<graphql::peg::ast_node,std::default_delete<graphql::peg::ast_node>>>>::pop_back() Line 1403	C++	Non-user code. Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node>::pop_back() Line 189	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::state_handler<graphql::peg::field,1,0>::unwind<tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>>(const tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & state={...}) Line 319	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::shuffle_states<tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::state_handler<graphql::peg::field,1,0>,tao::graphqlpeg::internal::rotate_right<1>>::unwind<tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(const tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & st={...}) Line 137	C++	Symbols loaded.
 	clientgen.exe!`tao::graphqlpeg::internal::match_control_unwind<graphql::peg::field,1,1,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>'::`1'::catch$0() Line 80	C++	Symbols loaded.
 	vcruntime140_1d.dll!_CallSettingFrame_LookupContinuationIndex() Line 98	Unknown	Non-user code. Symbols loaded.
 	vcruntime140_1d.dll!__FrameHandler4::CxxCallCatchBlock(_EXCEPTION_RECORD * pExcept=0x0000002e81e05190) Line 1393	C++	Non-user code. Symbols loaded.
 	ntdll.dll!RcConsolidateFrames�()	Unknown	Non-user code. Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::match_control_unwind<graphql::peg::field,1,1,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 76	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::match<graphql::peg::field,1,1,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 141	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::normal<graphql::peg::field>::match<1,1,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 94	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::sor<graphql::peg::field,graphql::peg::fragement_spread_or_inline_fragment>::match<1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,0,1,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(std::integer_sequence<unsigned __int64,0,1> __formal={...}, tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 45	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::sor<graphql::peg::field,graphql::peg::fragement_spread_or_inline_fragment>::match<1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 59	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::match_no_control<graphql::peg::selection,1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 45	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::match_control_unwind<graphql::peg::selection,1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 76	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::match<graphql::peg::selection,1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 141	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::normal<graphql::peg::selection>::match<1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 94	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::seq<graphql::peg::selection,tao::graphqlpeg::internal::star<tao::graphqlpeg::plus<graphql::peg::ignored>,graphql::peg::selection>>::match<1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 48	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::match_no_control<tao::graphqlpeg::list<graphql::peg::selection,tao::graphqlpeg::plus<graphql::peg::ignored>,void>,1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 45	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::match_control_unwind<tao::graphqlpeg::list<graphql::peg::selection,tao::graphqlpeg::plus<graphql::peg::ignored>,void>,1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 76	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::match<tao::graphqlpeg::list<graphql::peg::selection,tao::graphqlpeg::plus<graphql::peg::ignored>,void>,1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 141	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::normal<tao::graphqlpeg::list<graphql::peg::selection,tao::graphqlpeg::plus<graphql::peg::ignored>,void>>::match<1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 94	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::seq<tao::graphqlpeg::star<graphql::peg::ignored>,tao::graphqlpeg::list<graphql::peg::selection,tao::graphqlpeg::plus<graphql::peg::ignored>,void>,tao::graphqlpeg::star<graphql::peg::ignored>,tao::graphqlpeg::must<tao::graphqlpeg::ascii::one<125>>>::match<1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 48	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::match_no_control<graphql::peg::selection_set_content,1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 45	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::match_control_unwind<graphql::peg::selection_set_content,1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 76	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::match<graphql::peg::selection_set_content,1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 141	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::normal<graphql::peg::selection_set_content>::match<1,2,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 94	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::must<graphql::peg::selection_set_content>::match<1,0,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 56	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::match_no_control<tao::graphqlpeg::internal::must<graphql::peg::selection_set_content>,1,0,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 45	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::match_control_unwind<tao::graphqlpeg::internal::must<graphql::peg::selection_set_content>,1,0,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 76	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::match<tao::graphqlpeg::internal::must<graphql::peg::selection_set_content>,1,0,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 141	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::normal<tao::graphqlpeg::internal::must<graphql::peg::selection_set_content>>::match<1,0,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 94	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::if_must<0,tao::graphqlpeg::ascii::one<123>,graphql::peg::selection_set_content>::match<1,0,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 40	C++	Symbols loaded.
 	clientgen.exe!tao::graphqlpeg::internal::match_no_control<graphql::peg::selection_set,1,0,tao::graphqlpeg::nothing,tao::graphqlpeg::parse_tree::internal::make_control<graphql::peg::ast_node,graphql::peg::executable_selector,graphql::peg::ast_control>::type,tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf>,tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> &>(tao::graphqlpeg::file_input<0,tao::graphqlpeg::ascii::eol::lf_crlf> & in={...}, tao::graphqlpeg::parse_tree::internal::state<graphql::peg::ast_node> & <st_0>={...}) Line 45	C++	Symbols loaded.
etc...
etc...
etc...
etc...
etc...
etc...
etc...
etc...
etc...
etc...
a lot

@Coder666
Copy link
Author

Please also note that I am using VS2019, and this is a 64 bit Debug build

@Coder666
Copy link
Author

Further to this, using a query file like this

subscription mysub
{
    onNodeD
    {
        name @test( str : "\." )
    }
}

Produces the following output

Invalid GraphQL: .\query.graphql:5:29: Expected http://spec.graphql.org/June2018/#EscapedCharacter
        line: 5 column: 29

So this is to do with nesting of objects, which causes a much larger callstack.

@Coder666
Copy link
Author

Coder666 commented Mar 15, 2022

100% this is a stack exhaustion issue. If I change clientgen to use the linker option /STACK:4194304 i.e. 4MB stack, then the exception can be safely trapped even with several nested objects.

This will be a potential DOS issue where a query is able to be controlled by an attacker.

@Coder666
Copy link
Author

Is it possible to set a max nesting depth? Could this be a CMake build option so that it is customisable?

@Coder666
Copy link
Author

Here is a patch for 3.7.1 with my fix using PEGTL limit_depth on the selection_set rule, note that the depth of 18 I have used here is the maximum safe limit when using a 4MB stack, it is much smaller when using the default 1MB stack.

 .../include/graphqlservice/internal/Grammar.h              | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/include/graphqlservice/internal/Grammar.h b/include/graphqlservice/internal/Grammar.h
index 806891d2..50560966 100644
--- a/include/graphqlservice/internal/Grammar.h
+++ b/include/graphqlservice/internal/Grammar.h
@@ -15,7 +15,7 @@
 
 #include <tao/pegtl.hpp>
 #include <tao/pegtl/contrib/parse_tree.hpp>
-
+#include <tao/pegtl/contrib/limit_depth.hpp>
 #include <functional>
 
 namespace graphql::peg {
@@ -1192,6 +1192,18 @@ struct schema_document : must<schema_document_content>
 {
 };
 
+
+template <typename Rule>
+struct ast_action : nothing<Rule>
+{
+};
+
+template <>
+struct ast_action<selection_set> : limit_depth<18>
+{
+};
+
+
 } // namespace graphql::peg
 
 #endif // GRAPHQLGRAMMAR_H
 /src/SyntaxTree.cpp | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/src/SyntaxTree.cpp b/src/SyntaxTree.cpp
index 7304a446..bf5b7ca2 100644
--- a/src/SyntaxTree.cpp
+++ b/src/SyntaxTree.cpp
@@ -849,7 +849,7 @@ ast parseSchemaString(std::string_view input)
 	{
 		// Try a smaller grammar with only schema type definitions first.
 		result.root =
-			parse_tree::parse<schema_document, ast_node, schema_selector, nothing, ast_control>(
+			parse_tree::parse<schema_document, ast_node, schema_selector, ast_action, ast_control>(
 				memory_input<>(data.data(), data.size(), "GraphQL"));
 	}
 	catch (const peg::parse_error&)
@@ -857,7 +857,7 @@ ast parseSchemaString(std::string_view input)
 		// Try again with the full document grammar so validation can handle the unexepected
 		// executable definitions if this is a mixed document.
 		result.root =
-			parse_tree::parse<mixed_document, ast_node, schema_selector, nothing, ast_control>(
+			parse_tree::parse<mixed_document, ast_node, schema_selector, ast_action, ast_control>(
 				memory_input<>(data.data(), data.size(), "GraphQL"));
 	}
 
@@ -877,7 +877,7 @@ ast parseSchemaFile(std::string_view filename)
 
 		// Try a smaller grammar with only schema type definitions first.
 		result.root =
-			parse_tree::parse<schema_document, ast_node, schema_selector, nothing, ast_control>(
+			parse_tree::parse<schema_document, ast_node, schema_selector, ast_action, ast_control>(
 				std::move(in));
 	}
 	catch (const peg::parse_error&)
@@ -890,7 +890,7 @@ ast parseSchemaFile(std::string_view filename)
 		// Try again with the full document grammar so validation can handle the unexepected
 		// executable definitions if this is a mixed document.
 		result.root =
-			parse_tree::parse<mixed_document, ast_node, schema_selector, nothing, ast_control>(
+			parse_tree::parse<mixed_document, ast_node, schema_selector, ast_action, ast_control>(
 				std::move(in));
 	}
 
@@ -908,15 +908,15 @@ ast parseString(std::string_view input)
 	{
 		// Try a smaller grammar with only executable definitions first.
 		result.root = parse_tree::
-			parse<executable_document, ast_node, executable_selector, nothing, ast_control>(
+			parse<executable_document, ast_node, executable_selector, ast_action, ast_control>(
 				memory_input<>(data.data(), data.size(), "GraphQL"));
 	}
 	catch (const peg::parse_error&)
 	{
 		// Try again with the full document grammar so validation can handle the unexepected type
 		// definitions if this is a mixed document.
-		result.root =
-			parse_tree::parse<mixed_document, ast_node, executable_selector, nothing, ast_control>(
+		result.root = parse_tree::
+			parse<mixed_document, ast_node, executable_selector, ast_action, ast_control>(
 				memory_input<>(data.data(), data.size(), "GraphQL"));
 	}
 
@@ -936,7 +936,7 @@ ast parseFile(std::string_view filename)
 
 		// Try a smaller grammar with only executable definitions first.
 		result.root = parse_tree::
-			parse<executable_document, ast_node, executable_selector, nothing, ast_control>(
+			parse<executable_document, ast_node, executable_selector, ast_action, ast_control>(
 				std::move(in));
 	}
 	catch (const peg::parse_error&)
@@ -948,8 +948,8 @@ ast parseFile(std::string_view filename)
 
 		// Try again with the full document grammar so validation can handle the unexepected type
 		// definitions if this is a mixed document.
-		result.root =
-			parse_tree::parse<mixed_document, ast_node, executable_selector, nothing, ast_control>(
+		result.root = parse_tree::
+			parse<mixed_document, ast_node, executable_selector, ast_action, ast_control>(
 				std::move(in));
 	}
 

@wravery
Copy link
Contributor

wravery commented Mar 15, 2022

Thanks for the detailed repro steps, I can reproduce this with the sample app now. Interesting thing I'm noticing is that it's in the fallback case where it tries to parse an invalid mixed_document that it ends up overflowing. I need to try extending the depth to see if I can overflow the valid case, it's probably just a consequence of being completely unbounded. However, the mixed_document support, which is only there to produce better error messages from validation/schema generation, is very expensive. If there's a way to get rid of it without sacrificing too much clarity of error messages, then it would speed up the build quite a bit and increase the possible depth limit for selection_set.

@wravery
Copy link
Contributor

wravery commented Mar 15, 2022

I can trigger the stack overflow just by wrapping a few more levels of node fields around the selection set, so it does need to be bounded.

@wravery
Copy link
Contributor

wravery commented Mar 15, 2022

Or maybe not... it also depends on throwing an exception. If I fix the invalid escape sequence (replace "\." with "\\.") and add a type definition so the executable_document fails to parse but the mixed_document does not, then it doesn't overflow the stack. It seems like something about throwing the exception for the invalid escape sequence triggered this.

@wravery
Copy link
Contributor

wravery commented Mar 17, 2022

I think I'm starting to understand what's going on. I think the stack is being exhausted because the catch block in PEGTL's match_control_unwind catches ... and rethrows it with throw; when it finishes cleaning up its own parser stack. It does that for every layer of the stack. I would expect it to perform a sort of tail recursion, where the thrown exception reuses the previously allocated exception, but I think it's making a copy. Effectively, each throw/catch is adding another stack frame as part of the unwind.

@wravery
Copy link
Contributor

wravery commented Mar 17, 2022

I think I can replace the contrib/parse_tree.hpp implementation with a more limited version that doesn't catch/rethrow the exception at each frame. This version of ast_node doesn't require as many state callbacks as the parse_tree template supports, and the regular stack unwind should cleanup just fine through the destructors, so it can pass a single exception to the catch block in the top parser function.

That doesn't remove the need for some kind of depth limits on the parser, otherwise untrusted queries can DoS the application before it can perform any inspection on the AST. However, fixing the stack consumption in the unwind should more or less double the safe depth limit for any given target (assuming the target toolchain unwinds the stack in a similar way).

@wravery
Copy link
Contributor

wravery commented Mar 17, 2022

Relevant discussion: taocpp/PEGTL#256.

@wravery
Copy link
Contributor

wravery commented Mar 17, 2022

I edited my local copy of contrib/parse_tree.hpp to make the make_control::unwind implementation conditional on the underlying has_unwind<Control<Rule>>, and I confirmed that this prevents match_control_unwind from entering its if constexpr (has_unwind<Control<Rule>>) branch with the try/catch/rethrow per-state stack. It worked to prevent the stack overflow with the following query using the default MSVC stack size in sample.exe:

query {
 node1 {
  node2 {
   node3 {
    node4 {
     node5 {
      node6 {
       node7 {
        node8 {
         node9 {
          node10 {
            # ...
            node100 {
             name @test( str : "\." )
            }
            # ...
           }
          }
         }
        }
       }
      }
     }
    }
   }
  }
 }
}

type foo {
 bar: String
}

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 a pull request may close this issue.

2 participants