[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1024745: bullseye-pu: package node-xmldom/0.5.0-1+deb11u2



Package: release.debian.org
Severity: normal
Tags: bullseye
User: release.debian.org@packages.debian.org
Usertags: pu

[ Reason ]
node-xmldom is vulnerable: it doesn't verify that root element is uniq
(#1024736, CVE-2022-39353)

[ Impact ]
Medium vulnerability

[ Tests ]
Test still pass

[ Risks ]
Moderate risk: test still pass and patch isn't too big

[ Checklist ]
  [X] *all* changes are documented in the d/changelog
  [X] I reviewed all changes and I approve them
  [X] attach debdiff against the package in (old)stable
  [X] the issue is verified as fixed in unstable

[ Changes ]
Verify XML document before change it

Cheers,
Yadd
diff --git a/debian/changelog b/debian/changelog
index e486812..50d0288 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,11 @@
+node-xmldom (0.5.0-1+deb11u2) bullseye; urgency=medium
+
+  * Team upload
+  * Prevent inserting DOM nodes when they are not well-formed
+    (Closes: #1024736, CVE-2022-39353)
+
+ -- Yadd <yadd@debian.org>  Thu, 24 Nov 2022 09:22:10 +0100
+
 node-xmldom (0.5.0-1+deb11u1) bullseye; urgency=medium
 
   * Team upload
diff --git a/debian/patches/CVE-2022-39353.patch b/debian/patches/CVE-2022-39353.patch
new file mode 100644
index 0000000..b15040a
--- /dev/null
+++ b/debian/patches/CVE-2022-39353.patch
@@ -0,0 +1,270 @@
+Description: Prevent inserting DOM nodes when they are not well-formed
+Author: Christian Bewernitz <coder@karfau.de>
+Origin: upstream, https://github.com/xmldom/xmldom/commit/7ff7c10a
+Bug: https://github.com/xmldom/xmldom/security/advisories/GHSA-crh6-fp67-6883
+Bug-Debian: https://bugs.debian.org/1024736
+Forwarded: not-needed
+Reviewed-By: Yadd <yadd@debian.org>
+Last-Update: 2022-11-24
+
+--- a/lib/dom.js
++++ b/lib/dom.js
+@@ -111,7 +111,31 @@
+ 			serializeToString(this[i],buf,isHTML,nodeFilter);
+ 		}
+ 		return buf.join('');
+-	}
++	},
++	/**
++	 * @private
++	 * @param {function (Node):boolean} predicate
++	 * @returns {Node | undefined}
++	 */
++	find: function (predicate) {
++		return Array.prototype.find.call(this, predicate);
++	},
++	/**
++	 * @private
++	 * @param {function (Node):boolean} predicate
++	 * @returns {Node[]}
++	 */
++	filter: function (predicate) {
++		return Array.prototype.filter.call(this, predicate);
++	},
++	/**
++	 * @private
++	 * @param {Node} item
++	 * @returns {number}
++	 */
++	indexOf: function (item) {
++		return Array.prototype.indexOf.call(this, item);
++	},
+ };
+ function LiveNodeList(node,refresh){
+ 	this._node = node;
+@@ -182,7 +206,7 @@
+ 			}
+ 		}
+ 	}else{
+-		throw DOMException(NOT_FOUND_ERR,new Error(el.tagName+'@'+attr))
++		throw new DOMException(NOT_FOUND_ERR,new Error(el.tagName+'@'+attr))
+ 	}
+ }
+ NamedNodeMap.prototype = {
+@@ -496,48 +520,177 @@
+ 	_onUpdateChild(parentNode.ownerDocument,parentNode);
+ 	return child;
+ }
++
+ /**
+- * preformance key(refChild == null)
++ * Returns `true` if `node` can be a parent for insertion.
++ * @param {Node} node
++ * @returns {boolean}
+  */
+-function _insertBefore(parentNode,newChild,nextChild){
+-	var cp = newChild.parentNode;
++function hasValidParentNodeType(node) {
++	return (
++		node &&
++		(node.nodeType === Node.DOCUMENT_NODE || node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.ELEMENT_NODE)
++	);
++}
++
++/**
++ * Returns `true` if `node` can be inserted according to it's `nodeType`.
++ * @param {Node} node
++ * @returns {boolean}
++ */
++function hasInsertableNodeType(node) {
++	return (
++		node &&
++		(isElementNode(node) ||
++			isTextNode(node) ||
++			isDocTypeNode(node) ||
++			node.nodeType === Node.DOCUMENT_FRAGMENT_NODE ||
++			node.nodeType === Node.COMMENT_NODE ||
++			node.nodeType === Node.PROCESSING_INSTRUCTION_NODE)
++	);
++}
++
++/**
++ * Returns true if `node` is a DOCTYPE node
++ * @param {Node} node
++ * @returns {boolean}
++ */
++function isDocTypeNode(node) {
++	return node && node.nodeType === Node.DOCUMENT_TYPE_NODE;
++}
++
++/**
++ * Returns true if the node is an element
++ * @param {Node} node
++ * @returns {boolean}
++ */
++function isElementNode(node) {
++	return node && node.nodeType === Node.ELEMENT_NODE;
++}
++/**
++ * Returns true if `node` is a text node
++ * @param {Node} node
++ * @returns {boolean}
++ */
++function isTextNode(node) {
++	return node && node.nodeType === Node.TEXT_NODE;
++}
++
++/**
++ * Check if en element node can be inserted before `child`, or at the end if child is falsy,
++ * according to the presence and position of a doctype node on the same level.
++ *
++ * @param {Document} doc The document node
++ * @param {Node} child the node that would become the nextSibling if the element would be inserted
++ * @returns {boolean} `true` if an element can be inserted before child
++ * @private
++ * https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
++ */
++function isElementInsertionPossible(doc, child) {
++	var parentChildNodes = doc.childNodes || [];
++	if (parentChildNodes.find(isElementNode) || isDocTypeNode(child)) {
++		return false;
++	}
++	var docTypeNode = parentChildNodes.find(isDocTypeNode);
++	return !(child && docTypeNode && parentChildNodes.indexOf(docTypeNode) > parentChildNodes.indexOf(child));
++}
++/**
++ * @private
++ * @param {Node} parent the parent node to insert `node` into
++ * @param {Node} node the node to insert
++ * @param {Node=} child the node that should become the `nextSibling` of `node`
++ * @returns {Node}
++ * @throws DOMException for several node combinations that would create a DOM that is not well-formed.
++ * @throws DOMException if `child` is provided but is not a child of `parent`.
++ * @see https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
++ */
++function _insertBefore(parent, node, child) {
++	if (!hasValidParentNodeType(parent)) {
++		throw new DOMException(HIERARCHY_REQUEST_ERR, 'Unexpected parent node type ' + parent.nodeType);
++	}
++	if (child && child.parentNode !== parent) {
++		throw new DOMException(NOT_FOUND_ERR, 'child not in parent');
++	}
++	if (
++		!hasInsertableNodeType(node) ||
++		// the sax parser currently adds top level text nodes, this will be fixed in 0.9.0
++		// || (node.nodeType === Node.TEXT_NODE && parent.nodeType === Node.DOCUMENT_NODE)
++		(isDocTypeNode(node) && parent.nodeType !== Node.DOCUMENT_NODE)
++	) {
++		throw new DOMException(
++			HIERARCHY_REQUEST_ERR,
++			'Unexpected node type ' + node.nodeType + ' for parent node type ' + parent.nodeType
++		);
++	}
++	var parentChildNodes = parent.childNodes || [];
++	var nodeChildNodes = node.childNodes || [];
++	if (parent.nodeType === Node.DOCUMENT_NODE) {
++		if (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
++			let nodeChildElements = nodeChildNodes.filter(isElementNode);
++			if (nodeChildElements.length > 1 || nodeChildNodes.find(isTextNode)) {
++				throw new DOMException(HIERARCHY_REQUEST_ERR, 'More than one element or text in fragment');
++			}
++			if (nodeChildElements.length === 1 && !isElementInsertionPossible(parent, child)) {
++				throw new DOMException(HIERARCHY_REQUEST_ERR, 'Element in fragment can not be inserted before doctype');
++			}
++		}
++		if (isElementNode(node)) {
++			if (parentChildNodes.find(isElementNode) || !isElementInsertionPossible(parent, child)) {
++				throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one element can be added and only after doctype');
++			}
++		}
++		if (isDocTypeNode(node)) {
++			if (parentChildNodes.find(isDocTypeNode)) {
++				throw new DOMException(HIERARCHY_REQUEST_ERR, 'Only one doctype is allowed');
++			}
++			let parentElementChild = parentChildNodes.find(isElementNode);
++			if (child && parentChildNodes.indexOf(parentElementChild) < parentChildNodes.indexOf(child)) {
++				throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can only be inserted before an element');
++			}
++			if (!child && parentElementChild) {
++				throw new DOMException(HIERARCHY_REQUEST_ERR, 'Doctype can not be appended since element is present');
++			}
++		}
++	}
++
++	var cp = node.parentNode;
+ 	if(cp){
+-		cp.removeChild(newChild);//remove and update
++		cp.removeChild(node);//remove and update
+ 	}
+-	if(newChild.nodeType === DOCUMENT_FRAGMENT_NODE){
+-		var newFirst = newChild.firstChild;
++	if(node.nodeType === DOCUMENT_FRAGMENT_NODE){
++		var newFirst = node.firstChild;
+ 		if (newFirst == null) {
+-			return newChild;
++			return node;
+ 		}
+-		var newLast = newChild.lastChild;
++		var newLast = node.lastChild;
+ 	}else{
+-		newFirst = newLast = newChild;
++		newFirst = newLast = node;
+ 	}
+-	var pre = nextChild ? nextChild.previousSibling : parentNode.lastChild;
++	var pre = child ? child.previousSibling : parent.lastChild;
+ 
+ 	newFirst.previousSibling = pre;
+-	newLast.nextSibling = nextChild;
+-	
+-	
++	newLast.nextSibling = child;
++
++
+ 	if(pre){
+ 		pre.nextSibling = newFirst;
+ 	}else{
+-		parentNode.firstChild = newFirst;
++		parent.firstChild = newFirst;
+ 	}
+-	if(nextChild == null){
+-		parentNode.lastChild = newLast;
++	if(child == null){
++		parent.lastChild = newLast;
+ 	}else{
+-		nextChild.previousSibling = newLast;
++		child.previousSibling = newLast;
+ 	}
+ 	do{
+-		newFirst.parentNode = parentNode;
++		newFirst.parentNode = parent;
+ 	}while(newFirst !== newLast && (newFirst= newFirst.nextSibling))
+-	_onUpdateChild(parentNode.ownerDocument||parentNode,parentNode);
+-	//console.log(parentNode.lastChild.nextSibling == null)
+-	if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) {
+-		newChild.firstChild = newChild.lastChild = null;
++	_onUpdateChild(parent.ownerDocument||parent, parent);
++	//console.log(parent.lastChild.nextSibling == null)
++	if (node.nodeType == DOCUMENT_FRAGMENT_NODE) {
++		node.firstChild = node.lastChild = null;
+ 	}
+-	return newChild;
++	return node;
+ }
+ function _appendSingleChild(parentNode,newChild){
+ 	var cp = newChild.parentNode;
+@@ -578,11 +731,13 @@
+ 			}
+ 			return newChild;
+ 		}
+-		if(this.documentElement == null && newChild.nodeType == ELEMENT_NODE){
++		_insertBefore(this, newChild, refChild);
++		newChild.ownerDocument = this;
++		if (this.documentElement === null && newChild.nodeType === ELEMENT_NODE) {
+ 			this.documentElement = newChild;
+ 		}
+ 		
+-		return _insertBefore(this,newChild,refChild),(newChild.ownerDocument = this),newChild;
++		return newChild;
+ 	},
+ 	removeChild :  function(oldChild){
+ 		if(this.documentElement == oldChild){
diff --git a/debian/patches/series b/debian/patches/series
index 8f56e74..d272af1 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -1 +1,2 @@
 CVE-2022-37616.patch
+CVE-2022-39353.patch

Reply to: