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