Fix possible stack overflow in validator.

This commit is contained in:
Sunli 2022-01-28 09:46:14 +08:00
parent b233ea3e41
commit e8f14f328f
4 changed files with 65 additions and 4 deletions

View File

@ -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/), 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). 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 # [3.0.26] 2022-1-26
- Add `skip_input` attribute to `InputObject` macro, `skip_output` attribute to `SimpleObject` macro. - Add `skip_input` attribute to `InputObject` macro, `skip_output` attribute to `SimpleObject` macro.

View File

@ -2,9 +2,9 @@
## Supported Versions ## Supported Versions
| Version | Supported | | Version | Supported |
| ------- | ------------------ | |----------|--------------------|
| >= 3.0.0 | :white_check_mark: | | >= 3.0.0 | :white_check_mark: |
## Reporting a Vulnerability ## Reporting a Vulnerability

View File

@ -1,4 +1,4 @@
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use crate::parser::types::{Field, Selection, SelectionSet}; use crate::parser::types::{Field, Selection, SelectionSet};
use crate::validation::visitor::{Visitor, VisitorContext}; use crate::validation::visitor::{Visitor, VisitorContext};
@ -15,6 +15,7 @@ impl<'a> Visitor<'a> for OverlappingFieldsCanBeMerged {
) { ) {
let mut find_conflicts = FindConflicts { let mut find_conflicts = FindConflicts {
outputs: Default::default(), outputs: Default::default(),
visited: Default::default(),
ctx, ctx,
}; };
find_conflicts.find(selection_set); find_conflicts.find(selection_set);
@ -23,6 +24,7 @@ impl<'a> Visitor<'a> for OverlappingFieldsCanBeMerged {
struct FindConflicts<'a, 'ctx> { struct FindConflicts<'a, 'ctx> {
outputs: HashMap<&'a str, &'a Positioned<Field>>, outputs: HashMap<&'a str, &'a Positioned<Field>>,
visited: HashSet<&'a str>,
ctx: &'a mut VisitorContext<'ctx>, ctx: &'a mut VisitorContext<'ctx>,
} }
@ -46,6 +48,13 @@ impl<'a, 'ctx> FindConflicts<'a, 'ctx> {
if let Some(fragment) = if let Some(fragment) =
self.ctx.fragment(&fragment_spread.node.fragment_name.node) 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); self.find(&fragment.node.selection_set);
} }
} }

View File

@ -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());
}