From e8f14f328f06c328b2bffcf11e7f40f46c12ecaa Mon Sep 17 00:00:00 2001 From: Sunli Date: Fri, 28 Jan 2022 09:46:14 +0800 Subject: [PATCH] Fix possible stack overflow in validator. --- CHANGELOG.md | 4 ++ SECURITY.md | 6 +-- .../rules/overlapping_fields_can_be_merged.rs | 11 ++++- tests/simple_object.rs | 48 +++++++++++++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8776db49..3cd1000c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +# [3.0.27] 2022-1-28 + +- Fix possible stack overflow in validator, thanks @quapka. + # [3.0.26] 2022-1-26 - Add `skip_input` attribute to `InputObject` macro, `skip_output` attribute to `SimpleObject` macro. diff --git a/SECURITY.md b/SECURITY.md index 5ce6cb90..c06f3702 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,9 +2,9 @@ ## Supported Versions -| Version | Supported | -| ------- | ------------------ | -| >= 3.0.0 | :white_check_mark: | +| Version | Supported | +|----------|--------------------| +| >= 3.0.0 | :white_check_mark: | ## Reporting a Vulnerability diff --git a/src/validation/rules/overlapping_fields_can_be_merged.rs b/src/validation/rules/overlapping_fields_can_be_merged.rs index eccd4e0a..a8032a7e 100644 --- a/src/validation/rules/overlapping_fields_can_be_merged.rs +++ b/src/validation/rules/overlapping_fields_can_be_merged.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use crate::parser::types::{Field, Selection, SelectionSet}; use crate::validation::visitor::{Visitor, VisitorContext}; @@ -15,6 +15,7 @@ impl<'a> Visitor<'a> for OverlappingFieldsCanBeMerged { ) { let mut find_conflicts = FindConflicts { outputs: Default::default(), + visited: Default::default(), ctx, }; find_conflicts.find(selection_set); @@ -23,6 +24,7 @@ impl<'a> Visitor<'a> for OverlappingFieldsCanBeMerged { struct FindConflicts<'a, 'ctx> { outputs: HashMap<&'a str, &'a Positioned>, + visited: HashSet<&'a str>, ctx: &'a mut VisitorContext<'ctx>, } @@ -46,6 +48,13 @@ impl<'a, 'ctx> FindConflicts<'a, 'ctx> { if let Some(fragment) = self.ctx.fragment(&fragment_spread.node.fragment_name.node) { + if !self + .visited + .insert(fragment_spread.node.fragment_name.node.as_str()) + { + // To avoid recursing itself, this error is detected by the `NoFragmentCycles` validator. + continue; + } self.find(&fragment.node.selection_set); } } diff --git a/tests/simple_object.rs b/tests/simple_object.rs index bd33eb6d..540f44de 100644 --- a/tests/simple_object.rs +++ b/tests/simple_object.rs @@ -54,3 +54,51 @@ async fn test_flatten() { }) ); } + +#[tokio::test] +async fn recursive_fragment_definition() { + #[derive(SimpleObject)] + struct Hello { + world: String, + } + + struct Query; + + // this setup is actually completely irrelevant we just need to be able ot execute a query + #[Object] + impl Query { + async fn obj(&self) -> Hello { + Hello { + world: "Hello World".into(), + } + } + } + + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + let query = "fragment f on Query {...f} { __typename }"; + assert!(schema.execute(query).await.into_result().is_err()); +} + +#[tokio::test] +async fn recursive_fragment_definition_nested() { + #[derive(SimpleObject)] + struct Hello { + world: String, + } + + struct Query; + + // this setup is actually completely irrelevant we just need to be able ot execute a query + #[Object] + impl Query { + async fn obj(&self) -> Hello { + Hello { + world: "Hello World".into(), + } + } + } + + let schema = Schema::new(Query, EmptyMutation, EmptySubscription); + let query = "fragment f on Query { a { ...f a { ...f } } } { __typename }"; + assert!(schema.execute(query).await.into_result().is_err()); +}