CVE-2018-7408 (published 2018-02-22T18:29Z)

An issue was discovered in an npm 5.7.0 2018-02-21 pre-release (marked as "next: 5.7.0" and therefore automatically installed by an "npm upgrade -g npm" command, and also announced in the vendor\'s blog without mention of pre-release status). It might allow local users to bypass intended filesystem access restrictions because ownerships of /etc and /usr directories are being changed unexpectedly, related to a "correctMkdir" issue.

Severity: MEDIUM
CVSS v2 base score: 4.6 (AV:L/AC:L/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 7.8, HIGH (CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-732
cwe_name: Incorrect Permission Assignment for Critical Resource
description: The product specifies permissions for a security-critical resource in a way that allows that resource to be read or modified by unintended actors.
extended_description: When a resource is given a permission setting that provides access to a wider range of actors than required, it could lead to the exposure of sensitive information, or the modification of that resource by unintended parties. This is especially dangerous when the resource is related to program configuration, execution, or sensitive user data. For example, consider a misconfigured storage account for the cloud that can be read or written by a public or anonymous user.
         

Fix information:
repo_url: https://github.com/npm/npm
commit_details: repo_url: https://github.com/npm/npm
author: Kat Marchán
author_date: 2018-02-22 09:00:45-08:00
msg: Revert "*: Switch from mkdirp to correctMkdir to preserve perms and owners"

This reverts commit 94227e15eeced836b3d7b3d2b5e5cc41d4959cff.

Fixes: #19883
repository: repo_url: https://github.com/npm/npm
repo_name: npm/npm
description: This repository is moving to: https://github.com/npm/cli
owner: npm
file_changes: filename: core.js
change_type: ModificationType.MODIFY
diff: @@ -8,7 +8,7 @@ var path = require('path')
 var nopt = require('nopt')
 var ini = require('ini')
 var Umask = configDefs.Umask
-var correctMkdir = require('../utils/correct-mkdir.js')
+var mkdirp = require('mkdirp')
 var umask = require('../utils/umask')
 var isWindows = require('../utils/is-windows.js')
 
@@ -153,7 +153,7 @@ function load_ (builtin, rc, cli, cb) {
     // annoying humans and their expectations!
     if (conf.get('prefix')) {
       var etc = path.resolve(conf.get('prefix'), 'etc')
-      correctMkdir(etc, function () {
+      mkdirp(etc, function () {
         defaults.globalconfig = path.resolve(etc, 'npmrc')
         defaults.globalignorefile = path.resolve(etc, 'npmignore')
         afterUserContinuation()
@@ -235,7 +235,7 @@ Conf.prototype.loadExtras = function (cb) {
     this.loadUid(function (er) {
       if (er) return cb(er)
       // Without prefix, nothing will ever work
-      correctMkdir(this.prefix, cb)
+      mkdirp(this.prefix, cb)
     }.bind(this))
   }.bind(this))
 }
@@ -292,7 +292,7 @@ Conf.prototype.save = function (where, cb) {
       done(null)
     })
   } else {
-    correctMkdir(path.dirname(target.path), function (er) {
+    mkdirp(path.dirname(target.path), function (er) {
       if (er) return then(er)
       fs.writeFile(target.path, data, 'utf8', function (er) {
         if (er) return then(er)

programming_language: JavaScript
filename: set-user.js
change_type: ModificationType.MODIFY
diff: @@ -3,7 +3,7 @@ module.exports = setUser
 var assert = require('assert')
 var path = require('path')
 var fs = require('fs')
-var correctMkdir = require('../utils/correct-mkdir.js')
+var mkdirp = require('mkdirp')
 
 function setUser (cb) {
   var defaultConf = this.root
@@ -19,7 +19,7 @@ function setUser (cb) {
   }
 
   var prefix = path.resolve(this.get('prefix'))
-  correctMkdir(prefix, function (er) {
+  mkdirp(prefix, function (er) {
     if (er) return cb(er)
     fs.stat(prefix, function (er, st) {
       defaultConf.user = st && st.uid

programming_language: JavaScript
filename: install.js
change_type: ModificationType.MODIFY
diff: @@ -102,7 +102,7 @@ var readPackageJson = require('read-package-json')
 var chain = require('slide').chain
 var asyncMap = require('slide').asyncMap
 var archy = require('archy')
-var correctMkdir = require('./utils/correct-mkdir.js')
+var mkdirp = require('mkdirp')
 var rimraf = require('rimraf')
 var iferr = require('iferr')
 var validate = require('aproba')
@@ -648,7 +648,7 @@ Installer.prototype.readGlobalPackageData = function (cb) {
   log.silly('install', 'readGlobalPackageData')
   var self = this
   this.loadArgMetadata(iferr(cb, function () {
-    correctMkdir(self.where, iferr(cb, function () {
+    mkdirp(self.where, iferr(cb, function () {
       var pkgs = {}
       self.args.forEach(function (pkg) {
         pkgs[pkg.name] = true
@@ -665,7 +665,7 @@ Installer.prototype.readLocalPackageData = function (cb) {
   validate('F', arguments)
   log.silly('install', 'readLocalPackageData')
   var self = this
-  correctMkdir(this.where, iferr(cb, function () {
+  mkdirp(this.where, iferr(cb, function () {
     readPackageTree(self.where, iferr(cb, function (currentTree) {
       self.currentTree = currentTree
       self.currentTree.warnings = []

programming_language: JavaScript
filename: extract.js
change_type: ModificationType.MODIFY
diff: @@ -4,7 +4,7 @@ const BB = require('bluebird')
 
 const stat = BB.promisify(require('graceful-fs').stat)
 const gentlyRm = BB.promisify(require('../../utils/gently-rm.js'))
-const correctMkdir = BB.promisify(require('../../utils/correct-mkdir'))
+const mkdirp = BB.promisify(require('mkdirp'))
 const moduleStagingPath = require('../module-staging-path.js')
 const move = require('../../utils/move.js')
 const npa = require('npm-package-arg')
@@ -127,7 +127,7 @@ function stageBundledModule (bundler, child, staging, parentPath) {
 function finishModule (bundler, child, stageTo, stageFrom) {
   // If we were the one's who bundled this module…
   if (child.fromBundle === bundler) {
-    return correctMkdir(path.dirname(stageTo)).then(() => {
+    return mkdirp(path.dirname(stageTo)).then(() => {
       return move(stageFrom, stageTo)
     })
   } else {

programming_language: JavaScript
filename: finalize.js
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,7 @@ const path = require('path')
 const fs = require('graceful-fs')
 const Bluebird = require('bluebird')
 const rimraf = Bluebird.promisify(require('rimraf'))
+const mkdirp = Bluebird.promisify(require('mkdirp'))
 const lstat = Bluebird.promisify(fs.lstat)
 const readdir = Bluebird.promisify(fs.readdir)
 const symlink = Bluebird.promisify(fs.symlink)
@@ -13,7 +14,6 @@ const moveOpts = {fs: fs, Promise: Bluebird, maxConcurrency: 4}
 const getRequested = require('../get-requested.js')
 const log = require('npmlog')
 const packageId = require('../../utils/package-id.js')
-const correctMkdir = Bluebird.promisify(require('../../utils/correct-mkdir.js'))
 
 module.exports = function (staging, pkg, log) {
   log.silly('finalize', pkg.realpath)
@@ -48,7 +48,7 @@ module.exports = function (staging, pkg, log) {
   }
 
   function makeParentPath (dir) {
-    return correctMkdir(path.dirname(dir))
+    return mkdirp(path.dirname(dir))
   }
 
   function moveStagingToDestination () {
@@ -81,7 +81,7 @@ module.exports = function (staging, pkg, log) {
     if (!movedDestAway) return
     return readdir(path.join(delpath, 'node_modules')).catch(() => []).then((modules) => {
       if (!modules.length) return
-      return correctMkdir(path.join(pkg.realpath, 'node_modules')).then(() => Bluebird.map(modules, (file) => {
+      return mkdirp(path.join(pkg.realpath, 'node_modules')).then(() => Bluebird.map(modules, (file) => {
         const from = path.join(delpath, 'node_modules', file)
         const to = path.join(pkg.realpath, 'node_modules', file)
         return move(from, to, moveOpts)

programming_language: JavaScript
filename: move.js
change_type: ModificationType.MODIFY
diff: @@ -4,7 +4,7 @@ var path = require('path')
 var chain = require('slide').chain
 var iferr = require('iferr')
 var rimraf = require('rimraf')
-var correctMkdir = require('../../utils/correct-mkdir')
+var mkdirp = require('mkdirp')
 var rmStuff = require('../../unbuild.js').rmStuff
 var lifecycle = require('../../utils/lifecycle.js')
 var move = require('../../utils/move.js')
@@ -67,7 +67,7 @@ function moveModuleOnly (from, to, log, done) {
   function makeDestination (next) {
     return function () {
       log.silly('move', 'make sure destination parent exists', path.resolve(to, '..'))
-      correctMkdir(path.resolve(to, '..'), iferr(done, moveNodeModules(next)))
+      mkdirp(path.resolve(to, '..'), iferr(done, moveNodeModules(next)))
     }
   }
 
@@ -87,7 +87,7 @@ function moveModuleOnly (from, to, log, done) {
 
   function moveNodeModulesBack (next) {
     return function () {
-      correctMkdir(from, iferr(done, function () {
+      mkdirp(from, iferr(done, function () {
         log.silly('move', 'put source node_modules back', fromModules)
         move(tempFromModules, fromModules).then(next, done)
       }))

programming_language: JavaScript
filename: remove.js
change_type: ModificationType.MODIFY
diff: @@ -3,7 +3,7 @@ var path = require('path')
 var fs = require('graceful-fs')
 var rimraf = require('rimraf')
 var asyncMap = require('slide').asyncMap
-var correctMkdir = require('../../utils/correct-mkdir')
+var mkdirp = require('mkdirp')
 var npm = require('../../npm.js')
 var andIgnoreErrors = require('../and-ignore-errors.js')
 var move = require('../../utils/move.js')
@@ -52,7 +52,7 @@ function removeDir (pkg, log, next) {
   function makeTarget (readdirEr, files) {
     if (readdirEr) return cleanup()
     if (!files.length) return cleanup()
-    correctMkdir(path.join(pkg.path, 'node_modules'), function (mkdirEr) { moveModules(mkdirEr, files) })
+    mkdirp(path.join(pkg.path, 'node_modules'), function (mkdirEr) { moveModules(mkdirEr, files) })
   }
 
   function moveModules (mkdirEr, files) {

programming_language: JavaScript
filename: file-completion.js
change_type: ModificationType.MODIFY
diff: @@ -1,6 +1,6 @@
 module.exports = fileCompletion
 
-var correctMkdir = require('../correct-mkdir.js')
+var mkdir = require('mkdirp')
 var glob = require('glob')
 
 function fileCompletion (root, req, depth, cb) {
@@ -8,7 +8,7 @@ function fileCompletion (root, req, depth, cb) {
     cb = depth
     depth = Infinity
   }
-  correctMkdir(root, function (er) {
+  mkdir(root, function (er) {
     if (er) return cb(er)
 
     // can be either exactly the req, or a descendent

programming_language: JavaScript
filename: correct-mkdir.js
change_type: ModificationType.MODIFY
diff: @@ -6,21 +6,21 @@ var log = require('npmlog')
 var mkdirp = require('mkdirp')
 
 // memoize the directories created by this step
+var stats = {}
 var effectiveOwner
 module.exports = function correctMkdir (path, cb) {
   cb = dezalgo(cb)
   cb = inflight('correctMkdir:' + path, cb)
   if (!cb) {
-    return log.silly('correctMkdir', path, 'correctMkdir already in flight; waiting')
+    return log.verbose('correctMkdir', path, 'correctMkdir already in flight; waiting')
   } else {
-    log.silly('correctMkdir', path, 'correctMkdir not in flight; initializing')
+    log.verbose('correctMkdir', path, 'correctMkdir not in flight; initializing')
   }
-  var stats = {}
 
   if (stats[path]) return cb(null, stats[path])
 
   fs.stat(path, function (er, st) {
-    if (er) return makeDirectory(path, stats, cb)
+    if (er) return makeDirectory(path, cb)
 
     if (!st.isDirectory()) {
       log.error('correctMkdir', 'invalid dir %s', path)
@@ -60,12 +60,12 @@ function calculateOwner () {
   return effectiveOwner
 }
 
-function makeDirectory (path, stats, cb) {
+function makeDirectory (path, cb) {
   cb = inflight('makeDirectory:' + path, cb)
   if (!cb) {
-    return log.silly('makeDirectory', path, 'creation already in flight; waiting')
+    return log.verbose('makeDirectory', path, 'creation already in flight; waiting')
   } else {
-    log.silly('makeDirectory', path, 'creation not in flight; initializing')
+    log.verbose('makeDirectory', path, 'creation not in flight; initializing')
   }
 
   var owner = calculateOwner()

programming_language: JavaScript