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