Package: release.debian.org
Severity: normal
Tags: bullseye
User: release.debian....@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 <y...@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 <co...@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 <y...@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 via email to