From 6f5d95de6df6dad23b908fb15ad1a823b9d9a4d1 Mon Sep 17 00:00:00 2001 From: isaacs Date: Wed, 22 Dec 2010 18:08:20 -0800 Subject: [PATCH] child_process: Add gid/uid flags to spawn config This is mostly working, but not completely ideal for two reasons. 1. Rather than emitting an error on the ChildProcess object when the setgid/setuid fails, it is simply printing the error to stderr and exiting. The same happens with the cwd, so that's not completely terrible. 2. I don't have a good test for this. It fails with an EPERM if you try to change the uid or gid as a non-root user. --- lib/child_process.js | 14 +++++-- src/node_child_process.cc | 28 +++++++++++++- src/node_child_process.h | 4 +- test/disabled/test-child-process-uid-gid.js | 41 +++++++++++++++++++++ 4 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 test/disabled/test-child-process-uid-gid.js diff --git a/lib/child_process.js b/lib/child_process.js index e20e80ef0c..520961fd1a 100644 --- a/lib/child_process.js +++ b/lib/child_process.js @@ -185,21 +185,27 @@ ChildProcess.prototype.kill = function(sig) { ChildProcess.prototype.spawn = function(path, args, options, customFds) { args = args || []; - var cwd, env, setsid; + var cwd, env, setsid, uid, gid; if (!options || options.cwd === undefined && options.env === undefined && - options.customFds === undefined) { + options.customFds === undefined && + options.gid === undefined && + options.uid === undefined) { // Deprecated API: (path, args, options, env, customFds) cwd = ''; env = options || process.env; customFds = customFds || [-1, -1, -1]; setsid = false; + uid = -1; + gid = -1; } else { // Recommended API: (path, args, options) cwd = options.cwd || ''; env = options.env || process.env; customFds = options.customFds || [-1, -1, -1]; setsid = options.setsid ? true : false; + uid = options.hasOwnProperty("uid") ? options.uid : -1; + gid = options.hasOwnProperty("gid") ? options.gid : -1; } var envPairs = []; @@ -214,7 +220,9 @@ ChildProcess.prototype.spawn = function(path, args, options, customFds) { cwd, envPairs, customFds, - setsid); + setsid, + uid, + gid); this.fds = fds; if (customFds[0] === -1 || customFds[0] === undefined) { diff --git a/src/node_child_process.cc b/src/node_child_process.cc index adc919f007..9ba7527b2a 100644 --- a/src/node_child_process.cc +++ b/src/node_child_process.cc @@ -158,7 +158,18 @@ Handle ChildProcess::Spawn(const Arguments& args) { int fds[3]; - int r = child->Spawn(argv[0], argv, cwd, env, fds, custom_fds, do_setsid); + int uid = args[6]->ToInteger()->Value(); + int gid = args[7]->ToInteger()->Value(); + + int r = child->Spawn(argv[0], + argv, + cwd, + env, + fds, + custom_fds, + do_setsid, + uid, + gid); for (i = 0; i < argv_length; i++) free(argv[i]); delete [] argv; @@ -233,7 +244,9 @@ int ChildProcess::Spawn(const char *file, char **env, int stdio_fds[3], int custom_fds[3], - bool do_setsid) { + bool do_setsid, + int custom_uid, + int custom_gid) { HandleScope scope; assert(pid_ == -1); assert(!ev_is_active(&child_watcher_)); @@ -276,6 +289,17 @@ int ChildProcess::Spawn(const char *file, case 0: // Child. if (do_setsid && setsid() < 0) { perror("setsid"); + _exit(127); + } + + if (custom_gid != -1 && setgid(custom_gid)) { + perror("setgid()"); + _exit(127); + } + + if (custom_uid != -1 && setuid(custom_uid)) { + perror("setuid()"); + _exit(127); } if (custom_fds[0] == -1) { diff --git a/src/node_child_process.h b/src/node_child_process.h index f9363195f2..eec27d5516 100644 --- a/src/node_child_process.h +++ b/src/node_child_process.h @@ -64,7 +64,9 @@ class ChildProcess : ObjectWrap { char **env, int stdio_fds[3], int custom_fds[3], - bool do_setsid); + bool do_setsid, + int uid, + int gid); // Simple syscall wrapper. Does not disable the watcher. onexit will be // called still. diff --git a/test/disabled/test-child-process-uid-gid.js b/test/disabled/test-child-process-uid-gid.js new file mode 100644 index 0000000000..71513c0e0e --- /dev/null +++ b/test/disabled/test-child-process-uid-gid.js @@ -0,0 +1,41 @@ +// must be run as sudo, otherwise the gid/uid setting will fail. +var child_process = require("child_process"), + constants = require("constants"), + passwd = require("fs").readFileSync("/etc/passwd", "utf8"), + myUid = process.getuid(), + myGid = process.getgid(); + +// get a different user. +// don't care who it is, as long as it's not root +passwd = passwd.trim().split(/\n/); +for (var i = 0, l = passwd.length; i < l; i ++) { + if (passwd[i].charAt(0) === "#") continue; + passwd[i] = passwd[i].split(":"); + var otherName = passwd[i][0]; + var otherUid = +passwd[i][2]; + var otherGid = +passwd[i][3]; + if (otherUid && otherUid !== myUid && + otherGid && otherGid !== myGid && + otherUid > 0) { + break; + } +} +if (!otherUid && !otherGid) throw new Error("failed getting passwd info."); + +console.error("name, id, gid = %j", [otherName, otherUid, otherGid]); + +var whoNumber = child_process.spawn("id",[], {uid:otherUid,gid:otherGid}), + assert = require("assert"); + +whoNumber.stdout.buf = "byNumber:"; +whoNumber.stdout.on("data", onData); +function onData (c) { this.buf += c; } + +whoNumber.on("exit", onExit); +function onExit (code) { + var buf = this.stdout.buf; + console.log(buf); + var expr = new RegExp("^byNumber:uid="+otherUid+"\\("+ + otherName+"\\) gid="+otherGid+"\\("); + assert.ok(buf.match(expr), "uid and gid should match "+otherName); +}