From 76299370f82f18c03b6915bb4d262fa6857448a8 Mon Sep 17 00:00:00 2001 From: Demi Obenour Date: Thu, 3 Aug 2017 20:00:30 -0400 Subject: [PATCH] Harden against denial of service attacks This hardens the code against denial of service attacks by only going back a certain number of elements (set at parser construction time) in the list of active formatting elements and the stack of open elements. --- html5ever/src/tree_builder/mod.rs | 101 ++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/html5ever/src/tree_builder/mod.rs b/html5ever/src/tree_builder/mod.rs index d6480129..9574c3b8 100644 --- a/html5ever/src/tree_builder/mod.rs +++ b/html5ever/src/tree_builder/mod.rs @@ -40,6 +40,9 @@ use tree_builder::tag_sets::*; use util::str::to_escaped_string; pub use self::PushFlag::*; +use std::ops::{Deref, DerefMut}; +use std::cmp::max; +use std::slice::Iter; #[macro_use] mod tag_sets; @@ -48,6 +51,69 @@ mod types; include!(concat!(env!("OUT_DIR"), "/rules.rs")); +#[derive(Clone)] +pub struct LimitedVec { + vec: Vec, + limit: usize, +} + +impl LimitedVec { + pub fn new(limit: usize) -> Self { + LimitedVec { + vec: vec![], + limit: if limit == 0 { 10 } else { limit }, + } + } + + fn lower_bound(&self) -> usize { + let len = self.vec.len(); + // Watch out for overflow! + max(len, self.limit) - self.limit + } + + pub fn push(&mut self, other: T) { + self.vec.push(other) + } + + pub fn remove(&mut self, pos: usize) { + let lower_bound = self.lower_bound(); + self.vec.remove(pos + lower_bound); + } + + pub fn truncate(&mut self, pos: usize) { + let lower_bound = self.lower_bound(); + self.vec.truncate(pos + lower_bound); + } + + pub fn pop(&mut self) -> Option { + self.vec.pop() + } + + pub fn insert(&mut self, index: usize, element: T) { + let lower_bound = self.lower_bound(); + self.vec.insert(index + lower_bound, element) + } + + fn real_iter(&self) -> Iter { + self.vec.iter() + } +} + +impl Deref for LimitedVec { + type Target = [T]; + fn deref(&self) -> &[T] { + let bottom = self.lower_bound(); + &self.vec[bottom..] + } +} + +impl DerefMut for LimitedVec { + fn deref_mut(&mut self) -> &mut [T] { + let bottom = self.lower_bound(); + &mut self.vec[bottom..] + } +} + /// Tree builder options, with an impl for Default. #[derive(Copy, Clone)] pub struct TreeBuilderOpts { @@ -67,6 +133,13 @@ pub struct TreeBuilderOpts { /// Obsolete, ignored. pub ignore_missing_rules: bool, + /// The maximum amount that the parser will process through the list + /// of active formatting elements and the the stack of open elements. + /// This is set to a finite number to prevent denial-of-service security + /// vulnerabilities. 0 is treated as the default (currently 20); any other + /// value is used as-is. + pub max_stack_depth: u8, + /// Initial TreeBuilder quirks mode. Default: NoQuirks pub quirks_mode: QuirksMode, } @@ -79,6 +152,7 @@ impl Default for TreeBuilderOpts { iframe_srcdoc: false, drop_doctype: false, ignore_missing_rules: false, + max_stack_depth: 10, quirks_mode: NoQuirks, } } @@ -112,10 +186,10 @@ pub struct TreeBuilder { doc_handle: Handle, /// Stack of open elements, most recently added at end. - open_elems: Vec, + open_elems: LimitedVec, /// List of active formatting elements. - active_formatting: Vec>, + active_formatting: LimitedVec>, //§ the-element-pointers /// Head element pointer. @@ -165,8 +239,8 @@ impl TreeBuilder pending_table_text: vec!(), quirks_mode: opts.quirks_mode, doc_handle: doc_handle, - open_elems: vec!(), - active_formatting: vec!(), + open_elems: LimitedVec::new(opts.max_stack_depth as usize), + active_formatting: LimitedVec::new(opts.max_stack_depth as usize), head_elem: None, form_elem: None, frameset_ok: true, @@ -197,8 +271,8 @@ impl TreeBuilder pending_table_text: vec!(), quirks_mode: opts.quirks_mode, doc_handle: doc_handle, - open_elems: vec!(), - active_formatting: vec!(), + open_elems: LimitedVec::new(opts.max_stack_depth as usize), + active_formatting: LimitedVec::new(opts.max_stack_depth as usize), head_elem: None, form_elem: form_elem, frameset_ok: true, @@ -251,10 +325,10 @@ impl TreeBuilder /// internal state. This is intended to support garbage-collected DOMs. pub fn trace_handles(&self, tracer: &Tracer) { tracer.trace_handle(&self.doc_handle); - for e in &self.open_elems { + for e in self.open_elems.real_iter() { tracer.trace_handle(e); } - for e in &self.active_formatting { + for e in self.active_formatting.real_iter() { match e { &Element(ref h, _) => tracer.trace_handle(h), _ => (), @@ -269,7 +343,7 @@ impl TreeBuilder fn dump_state(&self, label: String) { println!("dump_state on {}", label); print!(" open_elems:"); - for node in self.open_elems.iter() { + for node in self.open_elems.real_iter() { let name = self.sink.elem_name(node); match *name.ns { ns!(html) => print!(" {}", name.local), @@ -278,7 +352,7 @@ impl TreeBuilder } println!(""); print!(" active_formatting:"); - for entry in self.active_formatting.iter() { + for entry in self.active_formatting.real_iter() { match entry { &Marker => print!(" Marker"), &Element(ref h, _) => { @@ -477,7 +551,7 @@ impl TokenSink } fn end(&mut self) { - for elem in self.open_elems.drain(..).rev() { + for elem in self.open_elems.into_iter().rev() { self.sink.pop(&elem); } } @@ -688,6 +762,11 @@ impl TreeBuilder } ); + if fmt_elem_stack_index == 0 { + self.sink.parse_error(Borrowed("Tree too complex to parse correctly – returned tree will be inaccurate")); + return + } + // 11. let common_ancestor = self.open_elems[fmt_elem_stack_index - 1].clone();