summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Mena Quintero <federico@gnome.org>2023-03-13 17:13:52 -0600
committerFederico Mena Quintero <federico@gnome.org>2023-03-13 20:59:18 -0600
commit61053d9c32dbafe756c38af3fd0bd0005371e153 (patch)
tree3b166e799fa4d368df9a23d32f353fd4bf3fcbbb
parenta4fe5d9f3c1692d4d36af30947c506d9273fd44f (diff)
downloadlibrsvg-61053d9c32dbafe756c38af3fd0bd0005371e153.tar.gz
(#942): Fix crash when XML files get recursively included through XInclude
There is a new limit, MAX_XINCLUDE_DEPTH, which is a constant with the maximum level of nesting for XInclude. We keep a counter of the current nesting level in XmlStateInner, and check against the limit every time we need to xinclude another XML document. The sample file has <xi:include parse="xml" href=""/> which properly causes the *same* file to be included, per https://www.w3.org/TR/xinclude-11/#include_element The href attribute is optional; the absence of this attribute is the same as specifying href="", that is, the reference is to the same document. Fixes https://gitlab.gnome.org/GNOME/librsvg/-/issues/942
-rw-r--r--src/limits.rs10
-rw-r--r--src/xml/mod.rs34
-rw-r--r--tests/fixtures/crash/bug942-xinclude-recursion.svg3
3 files changed, 45 insertions, 2 deletions
diff --git a/src/limits.rs b/src/limits.rs
index 89e33da0..e287767b 100644
--- a/src/limits.rs
+++ b/src/limits.rs
@@ -40,3 +40,13 @@ pub const MAX_LOADED_ELEMENTS: usize = 1_000_000;
/// of attributes that the SVG standard ascribes meaning to are lower than
/// this limit.
pub const MAX_LOADED_ATTRIBUTES: usize = u16::MAX as usize;
+
+/// Maximum level of nesting for XInclude (XML Include) files.
+///
+/// See <https://gitlab.gnome.org/GNOME/librsvg/-/issues/942>. With
+/// the use of XML like `<xi:include parse="xml" href="foo.xml"/>`, an
+/// SVG document can recursively include other XML files. This value
+/// defines a maximum level of nesting for XInclude, to prevent cases
+/// where the base document is included within itself, or when two
+/// documents recursively include each other.
+pub const MAX_XINCLUDE_DEPTH: usize = 20;
diff --git a/src/xml/mod.rs b/src/xml/mod.rs
index a9ccffac..c7493455 100644
--- a/src/xml/mod.rs
+++ b/src/xml/mod.rs
@@ -25,7 +25,7 @@ use crate::document::{Document, DocumentBuilder};
use crate::error::{ImplementationLimit, LoadingError};
use crate::handle::LoadOptions;
use crate::io::{self, IoError};
-use crate::limits::MAX_LOADED_ELEMENTS;
+use crate::limits::{MAX_LOADED_ELEMENTS, MAX_XINCLUDE_DEPTH};
use crate::node::{Node, NodeBorrow};
use crate::style::StyleType;
use crate::url_resolver::AllowedUrl;
@@ -106,6 +106,7 @@ struct XmlStateInner {
weak: Option<Weak<XmlState>>,
document_builder: Option<DocumentBuilder>,
num_loaded_elements: usize,
+ xinclude_depth: usize,
context_stack: Vec<Context>,
current_node: Option<Node>,
@@ -144,6 +145,7 @@ impl XmlState {
weak: None,
document_builder: Some(document_builder),
num_loaded_elements: 0,
+ xinclude_depth: 0,
context_stack: vec![Context::Start],
current_node: None,
entities: HashMap::new(),
@@ -546,7 +548,7 @@ impl XmlState {
// the absence of a default value declaration). Values
// other than "xml" and "text" are a fatal error."
match parse {
- None | Some("xml") => self.acquire_xml(&aurl),
+ None | Some("xml") => self.include_xml(&aurl),
Some("text") => self.acquire_text(&aurl, encoding),
@@ -565,6 +567,34 @@ impl XmlState {
}
}
+ fn include_xml(&self, aurl: &AllowedUrl) -> Result<(), AcquireError> {
+ self.increase_xinclude_depth(aurl)?;
+
+ let result = self.acquire_xml(aurl);
+
+ self.decrease_xinclude_depth();
+
+ result
+ }
+
+ fn increase_xinclude_depth(&self, aurl: &AllowedUrl) -> Result<(), AcquireError> {
+ let mut inner = self.inner.borrow_mut();
+
+ if inner.xinclude_depth == MAX_XINCLUDE_DEPTH {
+ Err(AcquireError::FatalError(format!(
+ "exceeded maximum level of nested xinclude in {aurl}"
+ )))
+ } else {
+ inner.xinclude_depth += 1;
+ Ok(())
+ }
+ }
+
+ fn decrease_xinclude_depth(&self) {
+ let mut inner = self.inner.borrow_mut();
+ inner.xinclude_depth -= 1;
+ }
+
fn acquire_text(&self, aurl: &AllowedUrl, encoding: Option<&str>) -> Result<(), AcquireError> {
let session = self
.inner
diff --git a/tests/fixtures/crash/bug942-xinclude-recursion.svg b/tests/fixtures/crash/bug942-xinclude-recursion.svg
new file mode 100644
index 00000000..039639da
--- /dev/null
+++ b/tests/fixtures/crash/bug942-xinclude-recursion.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xi="http://www.w3.org/2001/XInclude">
+ <xi:include parse="xml" href=""/>
+</svg>