Headline
CVE-2022-31173: Backport CVE-2022-31173 fix from GHSA-4rx6-g5vg-5f3j · graphql-rust/juniper@8d28cdb
Juniper is a GraphQL server library for Rust. Affected versions of Juniper are vulnerable to uncontrolled recursion resulting in a program crash. This issue has been addressed in version 0.15.10. Users are advised to upgrade. Users unable to upgrade should limit the recursion depth manually.
@@ -7,19 +7,6 @@ use crate::{ value::ScalarValue, };
pub struct NoFragmentCycles<’a> { current_fragment: Option<&’a str>, spreads: HashMap<&’a str, Vec<Spanning<&’a str>>>, fragment_order: Vec<&’a str>, }
struct CycleDetector<’a> { visited: HashSet<&’a str>, spreads: &’a HashMap<&’a str, Vec<Spanning<&’a str>>>, path_indices: HashMap<&’a str, usize>, errors: Vec<RuleError>, }
pub fn factory<’a>() -> NoFragmentCycles<’a> { NoFragmentCycles { current_fragment: None, @@ -28,6 +15,12 @@ pub fn factory<’a>() -> NoFragmentCycles<’a> { } }
pub struct NoFragmentCycles<’a> { current_fragment: Option<&’a str>, spreads: HashMap<&’a str, Vec<Spanning<&’a str>>>, fragment_order: Vec<&’a str>, }
impl<’a, S> Visitor<’a, S> for NoFragmentCycles<’a> where S: ScalarValue, @@ -38,14 +31,12 @@ where let mut detector = CycleDetector { visited: HashSet::new(), spreads: &self.spreads, path_indices: HashMap::new(), errors: Vec::new(), };
for frag in &self.fragment_order { if !detector.visited.contains(frag) { let mut path = Vec::new(); detector.detect_from(frag, &mut path); detector.detect_from(frag); } }
@@ -91,19 +82,46 @@ where } }
type CycleDetectorState<’a> = (&’a str, Vec<&’a Spanning<&’a str>>, HashMap<&’a str, usize>);
struct CycleDetector<’a> { visited: HashSet<&’a str>, spreads: &’a HashMap<&’a str, Vec<Spanning<&’a str>>>, errors: Vec<RuleError>, }
impl<’a> CycleDetector<’a> { fn detect_from(&mut self, from: &’a str, path: &mut Vec<&’a Spanning<&’a str>>) { fn detect_from(&mut self, from: &’a str) { let mut to_visit = Vec::new(); to_visit.push((from, Vec::new(), HashMap::new()));
while let Some((from, path, path_indices)) = to_visit.pop() { to_visit.extend(self.detect_from_inner(from, path, path_indices)); } }
/// This function should be called only inside [`Self::detect_from()`], as /// it’s a recursive function using heap instead of a stack. So, instead of /// the recursive call, we return a [`Vec`] that is visited inside /// [`Self::detect_from()`]. fn detect_from_inner( &mut self, from: &’a str, path: Vec<&’a Spanning<&’a str>>, mut path_indices: HashMap<&’a str, usize>, ) -> Vec<CycleDetectorState<’a>> { self.visited.insert(from);
if !self.spreads.contains_key(from) { return; return Vec::new(); }
self.path_indices.insert(from, path.len()); path_indices.insert(from, path.len());
let mut to_visit = Vec::new(); for node in &self.spreads[from] { let name = &node.item; let index = self.path_indices.get(name).cloned(); let name = node.item; let index = path_indices.get(name).cloned();
if let Some(index) = index { let err_pos = if index < path.len() { @@ -114,14 +132,14 @@ impl<’a> CycleDetector<’a> {
self.errors .push(RuleError::new(&error_message(name), &[err_pos.start])); } else if !self.visited.contains(name) { } else { let mut path = path.clone(); path.push(node); self.detect_from(name, path); path.pop(); to_visit.push((name, path, path_indices.clone())); } }
self.path_indices.remove(from); to_visit } }
Related news
### GraphQL behaviour Nested fragment in GraphQL might be quite hard to handle depending on the implementation language. Some language support natively a max recursion depth. However, on most compiled languages, you should add a threshold of recursion. ```graphql # Infinite loop example query { ...a } fragment a on Query { ...b } fragment b on Query { ...a } ``` ### POC TLDR With max_size being the number of nested fragment generated. At max_size=7500, it should instantly raise: ![](https://i.imgur.com/wXbUx8l.png) However, with a lower size, you will overflow the memory after some iterations. ### Reproduction steps (Juniper) ``` git clone https://github.com/graphql-rust/juniper.git cd juniper ``` Save this POC as poc.py ```python import requests import time import json from itertools import permutations print('=== Fragments POC ===') url = 'http://localhost:8080/graphql' max_size = 7500 perms = [''.join(p) for p in permutations('abcefghijk')] perms = perms[:m...