CVE-2022-21144: Improper Input Validation (CWE-20)
value: This affects all versions of package libxmljs. When invoking the libxmljs.parseXml function with a non-buffer argument the V8 code will attempt invoking the .toString method of the argument. If the argument's toString value is not a Function object V8 will crash.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 7.5
Published: 2022-05-01T16:15Z
Repository: https://github.com/libxmljs/libxmljs
Language: JavaScript
Files changed: package.json, src/xml_document.cc, test/html_parser.js, test/xml_parser.js
Commit date: 2022-03-30 11:48:41 -0400
Commit message: Ensure parseXml/parseHtml input is string or buffer (#594)


Diff: diff --git a/package.json b/package.json
index db5718e..da9c677 100644
--- a/package.json
+++ b/package.json
@@ -10,11 +10,11 @@
     "host": "https://github.com",
     "remote_path": "./libxmljs/libxmljs/releases/download/v{version}/",
     "package_name": "{node_abi}-{platform}-{arch}.tar.gz"
   },
   "description": "libxml bindings for v8 javascript engine",
-  "version": "0.19.7",
+  "version": "0.19.8",
   "scripts": {
     "install": "node-pre-gyp install --fallback-to-build --loglevel http",
     "test": "node --expose_gc ./node_modules/.bin/nodeunit test"
   },
   "repository": {
@@ -36,6 +36,6 @@
   },
   "devDependencies": {
     "nodeunit": "~0.11.2",
     "semver": "~5.5.0"
   }
-}
+}
\ No newline at end of file
diff --git a/src/xml_document.cc b/src/xml_document.cc
index cb281e2..8e4f825 100644
--- a/src/xml_document.cc
+++ b/src/xml_document.cc
@@ -373,20 +373,22 @@ NAN_METHOD(XmlDocument::FromHtml)
     int opts = (int)getParserOptions(options);
     if (Nan::To<v8::Boolean>(excludeImpliedElementsOpt).ToLocalChecked()->Value())
         opts |= HTML_PARSE_NOIMPLIED | HTML_PARSE_NODEFDTD;
 
     htmlDocPtr doc;
-    if (!node::Buffer::HasInstance(info[0])) {
+    if (info[0]->IsString()) {
         // Parse a string
         Nan::Utf8String str(Nan::To<v8::String>(info[0]).ToLocalChecked());
         doc = htmlReadMemory(*str, str.length(), baseUrl, encoding, opts);
     }
-    else {
+    else if (node::Buffer::HasInstance(info[0])) {
         // Parse a buffer
         v8::Local<v8::Object> buf = Nan::To<v8::Object>(info[0]).ToLocalChecked();
         doc = htmlReadMemory(node::Buffer::Data(buf), node::Buffer::Length(buf),
                             baseUrl, encoding, opts);
+    } else {
+        return Nan::ThrowError("XML must be a string or buffer");
     }
 
     xmlSetStructuredErrorFunc(NULL, NULL);
 
     if (!doc) {
@@ -437,20 +439,22 @@ NAN_METHOD(XmlDocument::FromXml)
         encoding = NULL;
     }
 
     int opts = (int) getParserOptions(options);
     xmlDocPtr doc;
-    if (!node::Buffer::HasInstance(info[0])) {
+    if (info[0]->IsString()) {
       // Parse a string
       Nan::Utf8String str(Nan::To<v8::String>(info[0]).ToLocalChecked());
       doc = xmlReadMemory(*str, str.length(), baseUrl, "UTF-8", opts);
     }
-    else {
+    else if (node::Buffer::HasInstance(info[0])) {
       // Parse a buffer
       v8::Local<v8::Object> buf = Nan::To<v8::Object>(info[0]).ToLocalChecked();
       doc = xmlReadMemory(node::Buffer::Data(buf), node::Buffer::Length(buf),
                           baseUrl, encoding, opts);
+    } else {
+        return Nan::ThrowError("XML must be a string or buffer");
     }
 
     xmlSetStructuredErrorFunc(NULL, NULL);
 
     if (!doc) {
diff --git a/test/html_parser.js b/test/html_parser.js
index 1526123..bde64ea 100644
--- a/test/html_parser.js
+++ b/test/html_parser.js
@@ -30,10 +30,21 @@ module.exports.parse = function(assert) {
     attempt_parse(null);
 
     assert.done();
 };
 
+module.exports.invalid_input = function(assert) {
+    try {
+        libxml.parseHtml({object: true});
+        assert.ok(false);
+    } catch(err) {
+        assert.ok(true)
+    }
+
+    assert.done();
+};
+
 // Although libxml defaults to a utf-8 encoding, if not specifically specified
 // it will guess the encoding based on meta http-equiv tags available
 // This test shows that the "guessed" encoding can be overridden
 module.exports.parse_force_encoding = function(assert) {
     var filename = __dirname + '/fixtures/parser.euc_jp.html';
diff --git a/test/xml_parser.js b/test/xml_parser.js
index e7ea831..ca4bb60 100644
--- a/test/xml_parser.js
+++ b/test/xml_parser.js
@@ -19,10 +19,21 @@ module.exports.parse = function(assert) {
     assert.equal('with content!', doc.get('sibling').text());
     assert.equal(str, doc.toString());
     assert.done();
 };
 
+module.exports.invalid_input = function(assert) {
+    try {
+        libxml.parseXml({object: true});
+        assert.ok(false);
+    } catch(err) {
+        assert.ok(true)
+    }
+
+    assert.done();
+};
+
 module.exports.parse_buffer = function(assert) {
     var filename = __dirname + '/fixtures/parser-utf16.xml';
     var buf = fs.readFileSync(filename);
 
     var doc = libxml.parseXml(buf);

Vulnerable code:   "version": "0.19.7",
}
    if (!node::Buffer::HasInstance(info[0])) {
    else {
    if (!node::Buffer::HasInstance(info[0])) {
    else {
Fixed code:   "version": "0.19.8",
}
    if (info[0]->IsString()) {
    else if (node::Buffer::HasInstance(info[0])) {
    } else {
        return Nan::ThrowError("XML must be a string or buffer");
    if (info[0]->IsString()) {
    else if (node::Buffer::HasInstance(info[0])) {
    } else {
        return Nan::ThrowError("XML must be a string or buffer");
module.exports.invalid_input = function(assert) {
    try {
        libxml.parseHtml({object: true});
        assert.ok(false);
    } catch(err) {
        assert.ok(true)
    }

    assert.done();
};

module.exports.invalid_input = function(assert) {
    try {
        libxml.parseXml({object: true});
        assert.ok(false);
    } catch(err) {
        assert.ok(true)
    }

    assert.done();
};