Skip to content
This repository has been archived by the owner on Aug 11, 2022. It is now read-only.

Global install file-ownership “nobody” - is this really sane ? #3849

Closed
sjorek opened this issue Aug 31, 2013 · 11 comments
Closed

Global install file-ownership “nobody” - is this really sane ? #3849

sjorek opened this issue Aug 31, 2013 · 11 comments

Comments

@sjorek
Copy link

sjorek commented Aug 31, 2013

I known this is a 💥-topic, as already seen in the now closed issue https://github.com/isaacs/npm/issues/601 . Nevertheless I'd like to add some thoughts I have missed in the former discussions.

But before I start to dig into this topic, I'd like to mention that I really appreciate the work done in npm as well as node.js. Keep up the good work! 🤘

I'll base my assumptions on a quotation from http://en.wikipedia.org/wiki/Nobody_(username) :

In many Unix variants, "nobody" is the conventional name of a user account which owns no files, is in no privileged groups, and has no abilities except those which every other user has.

The point I struggle with is: nobody … owns no files. This is true for every fresh installed Linux I've seen so far, as well as all BSDs including OS X. Please, prove me that I'm wrong ! … I'm talking here about real files, not file-descriptors of unix-domain-sockets or linux-devices.

But first let us continue with the quotation, without drawing any conclusions:

It is common to run daemons as nobody, especially servers, in order to limit the damage that could be done by a malicious user who gained control of them. However, the usefulness of this technique is reduced if more than one daemon is run like this, because then gaining control of one daemon would provide control of them all.

This is only partially true - on modern operating systems proper ACLs, sandboxes, chroots and other security enhancing techniques protect from such weaknesses.

The reason is that nobody-owned processes have the ability to send signals to each other and even debug each other, allowing them to read or even modify each other's memory.

This also is partially true - because every daemon (process) is able to use private/protected memory areas isolated from other processes, if security is really a concern and the proper OS provides such techniques.


So let me sum up the (to my mind) major points, and mark npm's compliance to these assumptions with:

  • ⭕ ok, npm's global install privileges do not weaken security or
    do not implicitly suffer from weaknesses
  • ❌ not ok, npm's global install privileges weaken security or
    suffer from weaknesses implicitly

Please, keep in mind, that I struggle with npm's global install behavior - especially with the nobody-ownership !

  1. ❌ nobody owns no files:
    • npm installs files globally owned by nobody
    • if one decides to run a service as nobody (not very uncommon)
      it can modify everything installed by npm globally
    • If one decides to run more than one service as nobody then filesystem ACLs,
      sandboxes or chroots may protect these services from each other, but
      avoiding nobody-ownership and still let the services run as nobody would be the
      “cheapest” approach
      .
  2. ⭕ nobody does not run more than one service
    • npm/node.js does not force any service to run as nobody, it's up to the
      implementation to gain proper privileges
    • Again, if one deliberately runs more than one service as nobody, then other
      ways of protecting these services from each other are available, without any
      negative effects but certainly with overhead
    • Changing global install ownership won't help here, it's up to the implementation
  3. ❌ services running as nobody, can not tweak other nobody-services' signals
    • obviously true. As long as there are no protections build into the services,
      every other service can connect to other services and send IPC signals
    • Changing global install ownership won't help here, it's up to the implementation
  4. ⭕ services running as nobody, can not tweak other nobody-services' memory
    • I don't know any way of accessing memory under node.js, that does not
      imply, that there is no way (maybe someone implements a native-module to
      do so)
    • Changing global install ownership won't help here, it's up to the implementation

My personal conclusion: npm is the only tool I know, installing files owned by nobody (globally). This scares me (:ghost:) a little bit - but I have to admit that I have no death-prove way to exploit this fact. Only thoughts …

My personal wish (:gift:) : I'd like to have the chance to configure/override the global install ownership, without touching the process uid/gid of globally running npm-scripts. (Please read my second comment to get a clue what changes I'd like to incorporate.)

Did I overlook something ? Is it already there ? Am I (too) paranoid ? (If yes, yes or yes then I'm really sorry to waste your time!)

If not I'd like to offer a branch to pull (:octocat:), with such an option implemented, but only if you're willing to incorporate such a feature and moreover if we find a common denominator in an upcoming discussion.

I really appreciate any feedback - even if so much has already been said and written !

Thanks a lot,
Stephan
🙊 🙉 🙈

@sjorek
Copy link
Author

sjorek commented Aug 31, 2013

After using the force and digging deeper into the source I found out that npm -g config user is yielding nobody as expected. (Mind the global flag -g). My observations regarding this value are:

  1. If installing globally via sudo npm install -g … npm itself runs as root (due to being called with sudo).
  2. The files installed (cached) get the uid and gid of whatever npm -g config user/group has been set to via chown
  3. The documentation of npm -g config user in https://github.com/isaacs/npm/blame/master/doc/misc/npm-config.md#L738 is ambigious. It states:

user

  • Default: "nobody"
  • Type: String or Number

The UID to set to when running package scripts as root.

This could mean several things (including those I observed):

  1. If one runs:
    sudo npm -g run-script … or
    sudo npm -g test … or
    sudo npm -g start … or
    sudo npm -g restart … or
    sudo npm -g stop …
    • (a) the npm-process changes its effective uid to the one given by npm -g config user prior to or
      immediately when starting the desired script.
      • This does not happen - at least I was not able to find a case where this happens
    • (b) the npm-process simply starts the desired script with the uid of npm -g config user
      • This does not happen - at least I was not able to find a case where this happens
  2. If one runs sudo npm -g install … then on the files being installed (or better: cached) fs.chown is invoked
    to set their uid and gid of whatever npm -g config user/group has been set to
    • As already mentioned above, this is what actually happens

So my final conclusions are now:

  1. We need to update the user-option's documentation to something like:

    user

    • Default: "?!?!?"
    • Type: String or Number

    The UID of globally installed files.

  2. We should find a way to change the default value of globally installed files from “nobody” to
    an OS-specific sane default. To my mind this is:

    • all Unix-derivates/-variants: “root”
    • Windows: keep it as it is … ?
  3. We should add a configuration option, possibly override-able per package, but at least override-able
    per npm -g … invocation defining which user-uid/-gid to escalate to when running npm's scripts
    (“run-script”, “test”, “start”, “stop” & “restart”) as “root”.

    • It's default value is, surprise, surprise “nobody” and it's behaviour should be inspired by
      the common privilege-escalation-model of unix-services, like this one:
    • To ease the implementation we could just use sudo -u user npm … or su user -c …
      or give a proper hint towards this direction.

I appreciate any feedback,
Stephan

@wrigleyster
Copy link

IMO it is a serious issue that files are installed as nobody. They should be compiled as nobody (or whomever), then installed as root, thus making runtime permissions those of nobody and file ownership root -- denying everyone but root to modify the files.

@othiym23
Copy link
Contributor

@zkat and I just spend a good 15 minutes talking through what we believe is going on here, and I ultimately realized that what the code says it's doing doesn't match the description here. npm shouldn't be writing files as nobody, even when running as a global install – the code that handles setting ownership and permissions on unpack should be using the permissions of the target directory, or perhaps the permissions of the invoking user. Unfortunately, the deeper we dug into this code, the gnarlier the situation got, and we had to call it after 20 minutes of investigation. However, I don't think this is a feature request now, but instead a security-sensitive issue (much as @sjorek does), and as such, I'm reclassifying this as a serious bug. That said, I'm still not sure how files are getting written as nobody, so if somebody could give me a repro case, that would be extremely useful.

@wrigleyster
Copy link

wrigleyster commented May 12, 2016

Yay, finally, someone takes this seriously!

The default user is set to 'nobody' in lib/config/defaults.js

    user: process.platform === 'win32' ? 0 : 'nobody',

and the files are extracted as the default user in lib/install/action/extract.js, unless the unsafe-perm is set

  var up = npm.config.get('unsafe-perm')
  var user = up ? null : npm.config.get('user')

However, the safe thing would be to write the files as root, and run the files as nobody or some custom npm-user, thus the extract file above should say something like:

if(npm.getCurrentUser() == 'nobody') {
    fail("running as 'nobody': but 'nobody' lacks permission to write");
} else {
  var user = npm.config.global ? root : npm.getCurrentUser()
}

basically if running as 'nobody' extract should fail, if running with '-g' it should install as root and if running without '-g' it is being installed locally and should be installed as whoever ran npm.

@othiym23
Copy link
Contributor

It's much more complicated than that, when you start tracing through all of the various code paths that touch permissions and the configuration around user and group. I would like npm to be simpler and safer about how it writes files to disk.

However, that's not the end of the story, because the lifecycle permissions affect child processes run by the CLI, which themselves sometimes write files (think phantomjs here). A complete solution to this is going to require some care and probably some breaking changes.

@wrigleyster
Copy link

wrigleyster commented May 12, 2016

I would like npm to be simpler and safer about how it writes files to disk.

I admire that.

As for the complexity of it, lifecycle.js does it correctly: run subsequent
commands as default user ('nobody'), unless the unsafe parameter is set.

Thus, so long as extract.js does it's job correctly and not extract files
with the default user as file owner, the subsequent commands will not be
permitted to write to the files -- precisely as intended.

The only time a user should run anything as root is during a global install,
because during a global install you want the files to be owned by root. At any
other time you're either running on a local install (in which case the
permission isn't of greater concern), or you will run as a different user, and
you won't have permission to change the files, nor change their ownership
anyway.

@78juli
Copy link

78juli commented Nov 15, 2016

It's getting quite confusing at this stage, I didn't even know about the nobody user before checking the write permissions of these folders.
Look at this mess, different users for various modules:

juli@ArchLinux` /usr/lib/node_modules % ls -la
total 244
drwxr-xr-x  22 juli   root   4096 Nov 15 23:09 .
drwxr-xr-x 286 root   root 159744 Nov 15 13:10 ..
drwxr-xr-x   6 root   root   4096 Sep 16 10:29 acorn
drwxr-xr-x   4 nobody juli   4096 Nov 15 22:57 ansi
drwxr-xr-x   7 root   root   4096 Nov 15 22:54 browserify
drwxr-xr-x   3 root   root   4096 Nov 15 23:09 cordova
drwxr-xr-x   7 nobody juli   4096 Nov 15 22:35 eslint
drwxr-xr-x   6 nobody juli   4096 Nov 13 00:57 generator-code
drwxr-xr-x   4 nobody juli   4096 Nov 13 01:06 generator-electron
drwxr-xr-x   6 nobody juli   4096 Oct 27 14:13 grunt-cli
drwxr-xr-x   6 nobody juli   4096 Nov 15 22:25 gulp
drwxr-xr-x   7 nobody juli   4096 Nov 12 20:46 jshint
drwxr-xr-x   6 nobody root   4096 Nov 15 01:17 nativefier
drwxr-xr-x   7 nobody juli   4096 Nov 15 22:37 node.js
drwxr-xr-x  12 nobody juli   4096 Nov 15 22:49 npm
drwxr-xr-x   6 nobody root   4096 Oct 27 14:12 nw-gyp
drwxr-xr-x   3 root   root   4096 Nov 15 23:09 phonegap
drwxr-xr-x   3 root   root   4096 Nov 15 23:09 pm2
drwxr-xr-x   3 root   root   4096 Jul 15 04:25 semver
drwxr-xr-x   6 root   root   4096 Oct 26 03:21 uglify-js
drwxr-xr-x   8 nobody juli   4096 Oct 27 14:13 webpack
drwxr-xr-x   4 nobody juli   4096 Nov 13 01:06 yo

This is what I get in the terminal when trying to run a global update:

npm ERR! addLocal Could not install /build/acorn/src/acorn-4.0.3.tgz
npm ERR! addLocal Could not install /build/browserify/src/browserify-13.1.1.tgz
npm ERR! addLocal Could not install /build/uglify-js/src/uglify-js-2.7.4.tgz
npm ERR! addLocal Could not install /build/npm/src/npm
npm WARN deprecated tough-cookie@2.2.2: ReDoS vulnerability parsing Set-Cookie https://nodesecurity.io/advisories/130
npm WARN deprecated minimatch@2.0.10: Please update to minimatch 3.0.2 or higher to avoid a RegExp DoS issue
/usr/lib
├── phonegap@6.3.5
└── pm2@2.1.5

npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@^1.0.0 (node_modules/pm2/node_modules/chokidar/node_modules/fsevents):
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.0.15: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})
npm ERR! Linux 4.8.7-1-ARCH
npm ERR! argv "/usr/bin/node" "/usr/bin/npm" "-g" "update"
npm ERR! node v7.1.0
npm ERR! npm  v3.10.9
npm ERR! path /usr/lib/node_modules/.staging/ansi-a242ced5
npm ERR! code ENOENT
npm ERR! errno -2
npm ERR! syscall rename

npm ERR! enoent ENOENT: no such file or directory, rename '/usr/lib/node_modules/.staging/ansi-a242ced5' -> '/usr/lib/node_modules/cordova/node_modules/npm/node_modules/ansi'
npm ERR! enoent ENOENT: no such file or directory, rename '/usr/lib/node_modules/.staging/ansi-a242ced5' -> '/usr/lib/node_modules/cordova/node_modules/npm/node_modules/ansi'
npm ERR! enoent This is most likely not a problem with npm itself
npm ERR! enoent and is related to npm not being able to find a file.
npm ERR! enoent

npm ERR! Please include the following file with any support request:
npm ERR!     /home/juli/npm-debug.log
sudo npm -g update  22.03s user 1.85s system 83% cpu 28.508 total

I can also attach the log file if it helps in any way...
I'm not really sure how to fix this problem, any suggestions are welcome!

@78juli
Copy link

78juli commented Nov 15, 2016

Update:

Actually the terminal output I posted previously didn't have an EACCES problem anymore because I changed the permissions partially. Before I did that I was getting both an EACCES and an ENOENT error too.

I found this video in the npm docs which really helped,
https://docs.npmjs.com/getting-started/fixing-npm-permissions

and the solution for me was this:

sudo chown -R your_user /usr/lib/node_modules

chown the folder and files recursively

Neverthless I hope it helps seeing that I had such a mix up of user ownerships and I am sure it happens often with other people too. Now I just have to figure out a solution to the ENOENT problem. I'm guessing there are some weird symlinks that need fixing. Correct me if I am wrong!
😸

@npm-robot
Copy link

We're closing this issue as it has gone thirty days without activity. In our experience if an issue has gone thirty days without any activity then it's unlikely to be addressed. In the case of bug reports, often the underlying issue will be addressed but finding related issues is quite difficult and often incomplete.

If this was a bug report and it is still relevant then we encourage you to open it again as a new issue. If this was a feature request then you should feel free to open it again, or even better open a PR.

For more information about our new issue aging policies and why we've instituted them please see our blog post.

@wrigleyster
Copy link

WTF!?! Issues labeled big-bug and/or security should not be auto-closed. That is a huge security bug in and of itself!

@vkotovv
Copy link

vkotovv commented Jun 23, 2017

@78juli you have invalid link in your latest post, it should be https://docs.npmjs.com/getting-started/fixing-npm-permissions

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

6 participants