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: