Fix panic on malformed queries with recursive fragments.

This is a potential denial-of-service attack vector. Thanks to
[@quapka](https://github.com/quapka) for the detailed vulnerability report
and reproduction steps.
This commit is contained in:
Christian Legnitto 2022-01-26 21:59:50 -08:00
parent 88b10fe009
commit c28c77e458
2 changed files with 39 additions and 1 deletions

View file

@ -1,6 +1,6 @@
# master # master
- No changes yet - Fix panic on malformed queries with recursive fragments. *This is a potential denial-of-service attack vector.* Thanks to [@quapka](https://github.com/quapka) for the detailed vulnerability report and reproduction steps.
# [[0.15.7] 2021-07-08](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.7) # [[0.15.7] 2021-07-08](https://github.com/graphql-rust/juniper/releases/tag/juniper-v0.15.7)

View file

@ -172,6 +172,13 @@ impl<'a, S: Debug> OverlappingFieldsCanBeMerged<'a, S> {
); );
for frag_name2 in &fragment_names[i + 1..] { for frag_name2 in &fragment_names[i + 1..] {
// Prevent infinite fragment recursion. This case is
// caught by fragment validators, but because the validation is
// done in parallel we can't rely on fragments being
// non-recursive here.
if frag_name1 == frag_name2 {
continue;
}
self.collect_conflicts_between_fragments( self.collect_conflicts_between_fragments(
&mut conflicts, &mut conflicts,
frag_name1, frag_name1,
@ -195,6 +202,10 @@ impl<'a, S: Debug> OverlappingFieldsCanBeMerged<'a, S> {
) where ) where
S: ScalarValue, S: ScalarValue,
{ {
// Prevent infinite fragment recursion. This case is
// caught by fragment validators, but because the validation is
// done in parallel we can't rely on fragments being
// non-recursive here.
if fragment_name1 == fragment_name2 { if fragment_name1 == fragment_name2 {
return; return;
} }
@ -282,6 +293,13 @@ impl<'a, S: Debug> OverlappingFieldsCanBeMerged<'a, S> {
self.collect_conflicts_between(conflicts, mutually_exclusive, field_map, &field_map2, ctx); self.collect_conflicts_between(conflicts, mutually_exclusive, field_map, &field_map2, ctx);
for fragment_name2 in fragment_names2 { for fragment_name2 in fragment_names2 {
// Prevent infinite fragment recursion. This case is
// caught by fragment validators, but because the validation is
// done in parallel we can't rely on fragments being
// non-recursive here.
if fragment_name == fragment_name2 {
return;
}
self.collect_conflicts_between_fields_and_fragment( self.collect_conflicts_between_fields_and_fragment(
conflicts, conflicts,
field_map, field_map,
@ -2261,6 +2279,26 @@ mod tests {
); );
} }
#[test]
fn handles_recursive_fragments() {
expect_passes_rule_with_schema::<
_,
EmptyMutation<()>,
EmptySubscription<()>,
_,
_,
DefaultScalarValue,
>(
QueryRoot,
EmptyMutation::new(),
EmptySubscription::new(),
factory,
r#"
fragment f on Query { ...f }
"#,
);
}
#[test] #[test]
fn error_message_contains_hint_for_alias_conflict() { fn error_message_contains_hint_for_alias_conflict() {
assert_eq!( assert_eq!(