CVE-2020-7677 (published 2022-07-25T14:15Z)

This affects the package thenify before 3.3.1. The name argument provided to the package can be controlled by users without any sanitization, and this is provided to the eval function without any sanitization.

CVSS v3 base score: 9.8, CRITICAL (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-noinfo
cwe_name: Insufficient Information
description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/thenables/thenify
commit_details: repo_url: https://github.com/thenables/thenify
author: Yiyu He
author_date: 2020-06-18 01:26:11+08:00
msg: fix: remove eval (#30)
repository: repo_url: https://github.com/thenables/thenify
repo_name: thenables/thenify
description: Promisify a callback-based function
repo_language: JavaScript
owner: thenables
file_changes: filename: index.js
change_type: ModificationType.MODIFY
diff: @@ -7,33 +7,34 @@ module.exports = thenify
 /**
  * Turn async functions into promises
  *
- * @param {Function} $$__fn__$$
+ * @param {Function} fn
  * @return {Function}
  * @api public
  */
 
-function thenify($$__fn__$$, options) {
-  assert(typeof $$__fn__$$ === 'function')
-  return eval(createWrapper($$__fn__$$.name, options))
+function thenify(fn, options) {
+  assert(typeof fn === 'function')
+  return createWrapper(fn, options)
 }
 
 /**
  * Turn async functions into promises and backward compatible with callback
  *
- * @param {Function} $$__fn__$$
+ * @param {Function} fn
  * @return {Function}
  * @api public
  */
 
-thenify.withCallback = function ($$__fn__$$, options) {
-  assert(typeof $$__fn__$$ === 'function')
+thenify.withCallback = function (fn, options) {
+  assert(typeof fn === 'function')
   options = options || {}
   options.withCallback = true
-  if (options.multiArgs === undefined) options.multiArgs = true
-  return eval(createWrapper($$__fn__$$.name, options))
+  return createWrapper(fn, options)
 }
 
 function createCallback(resolve, reject, multiArgs) {
+  // default to true
+  if (multiArgs === undefined) multiArgs = true
   return function(err, value) {
     if (err) return reject(err)
     var length = arguments.length
@@ -52,29 +53,25 @@ function createCallback(resolve, reject, multiArgs) {
   }
 }
 
-function createWrapper(name, options) {
-  name = (name || '').replace(/\s|bound(?!$)/g, '')
+function createWrapper(fn, options) {
   options = options || {}
-  // default to true
-  var multiArgs = options.multiArgs !== undefined ? options.multiArgs : true
-  multiArgs = 'var multiArgs = ' + JSON.stringify(multiArgs) + '\n'
-
-  var withCallback = options.withCallback ?
-    'var lastType = typeof arguments[len - 1]\n'
-    + 'if (lastType === "function") return $$__fn__$$.apply(self, arguments)\n'
-   : ''
-
-  return '(function ' + name + '() {\n'
-    + 'var self = this\n'
-    + 'var len = arguments.length\n'
-    + multiArgs
-    + withCallback
-    + 'var args = new Array(len + 1)\n'
-    + 'for (var i = 0; i < len; ++i) args[i] = arguments[i]\n'
-    + 'var lastIndex = i\n'
-    + 'return new Promise(function (resolve, reject) {\n'
-      + 'args[lastIndex] = createCallback(resolve, reject, multiArgs)\n'
-      + '$$__fn__$$.apply(self, args)\n'
-    + '})\n'
-  + '})'
+  var name = fn.name;
+  name = (name || '').replace(/\s|bound(?!$)/g, '')
+  var newFn = function () {
+    var self = this
+    var len = arguments.length
+    if (options.withCallback) {
+      var lastType = typeof arguments[len - 1]
+      if (lastType === 'function') return fn.apply(self, arguments)
+    }
+    var args = new Array(len + 1)
+    for (var i = 0; i < len; ++i) args[i] = arguments[i]
+    var lastIndex = i
+    return new Promise(function (resolve, reject) {
+      args[lastIndex] = createCallback(resolve, reject, options.multiArgs)
+      fn.apply(self, args)
+    })
+  }
+  Object.defineProperty(newFn, 'name', { value: name })
+  return newFn
 }

programming_language: JavaScript
filename: test.js
change_type: ModificationType.MODIFY
diff: @@ -69,3 +69,27 @@ it('fn(..args, callback())', function () {
     assert.deepEqual(values, [1, 2, 3])
   })
 })
+
+it('unicode function name', function () {
+  function 你好$hello_123(a, b, c, cb) {
+    cb(null, a, b, c)
+  }
+  var wrapper = thenify(你好$hello_123)
+  assert.equal(wrapper.name, '你好$hello_123')
+  wrapper(1, 2, 3).then(function (values) {
+    assert.deepEqual(values, [1, 2, 3])
+  })
+})
+
+it('invalid function name', function () {
+  function fn(a, b, c, cb) {
+    cb(null, a, b, c)
+  }
+
+  Object.defineProperty(fn, 'name', { value: 'fake(){a.b;})();(function(){//' })
+  var wrapper = thenify(fn)
+  assert.equal(wrapper.name, fn.name)
+  wrapper(1, 2, 3).then(function (values) {
+    assert.deepEqual(values, [1, 2, 3])
+  })
+})

programming_language: JavaScript