Skip to content

process: add get/set resuid #40581

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions doc/api/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -1966,6 +1966,36 @@ if (process.getuid) {
This function is only available on POSIX platforms (i.e. not Windows or
Android).

## `process.getresuid()`

<!-- YAML
added: REPLACEME
-->

The `process.getresuid()` method returns an array with the real, effective,
and saved user IDs.

* Returns: {integer\[]}

```mjs
import process from 'process';

if (process.getresuid) {
console.log(process.getresuid()); // [ 0, 0, 0 ]
}
```

```cjs
const process = require('process');

if (process.getresuid) {
console.log(process.getresuid()); // [ 0, 0, 0 ]
}
```

This function is only available on POSIX platforms (i.e. not Windows or
Android).

## `process.hasUncaughtExceptionCaptureCallback()`

<!-- YAML
Expand Down Expand Up @@ -3333,6 +3363,51 @@ This function is only available on POSIX platforms (i.e. not Windows or
Android).
This feature is not available in [`Worker`][] threads.

## `process.setresuid(ruid, euid, suid)`

<!-- YAML
added: REPLACEME
-->

The `process.setresuid(ruid, euid, suid)` method sets the real, effective,
and saved user IDs.

* `ruid` {string|number} The real user ID
* `euid` {string|number} The effective user ID
* `suid` {string|number} The saved user ID

```mjs
import process from 'process';

if (process.getresuid && process.setresuid) {
console.log(`Current ids: ${process.getresuid()[0]}`);
try {
process.setresuid(501, 501, 501);
console.log(`New ids: ${process.getresuid()}`);
} catch (err) {
console.log(`Failed to set ids: ${err}`);
}
}
```

```cjs
const process = require('process');

if (process.getresuid && process.setresuid) {
console.log(`Current ids: ${process.getresuid()[0]}`);
try {
process.setresuid(501, 501, 501);
console.log(`New ids: ${process.getresuid()}`);
} catch (err) {
console.log(`Failed to set ids: ${err}`);
}
}
```

This function is only available on POSIX platforms (i.e. not Windows or
Android).
This feature is not available in [`Worker`][] threads.

## `process.setSourceMapsEnabled(val)`

<!-- YAML
Expand Down
7 changes: 7 additions & 0 deletions lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,13 @@ if (credentials.implementsPosixCredentials) {
process.getgid = credentials.getgid;
process.getegid = credentials.getegid;
process.getgroups = credentials.getgroups;
if (!credentials.isApple) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of apis that may or may not exist depending on what platform you're on. My preference would be for the method to always be defined but throw a reasonable exception on unsupported platforms.

Copy link

@ptrxyz ptrxyz Jan 2, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't think of a similar case out of my head, but probably there are some cases like this when it comes to compatibility with Windows? Maybe this could be handled similarily then?
In general, I also like the idea of a consistent API with reasonable exceptions. 👍

process.getresuid = credentials.getresuid;
} else {
process.getresuid = () => {
throw new Error('Not implemented in MacOSX');
}
}
}

// Setup the callbacks that node::AsyncWrap will call when there are hooks to
Expand Down
42 changes: 41 additions & 1 deletion lib/internal/bootstrap/switches/does_own_process_state.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ if (credentials.implementsPosixCredentials) {
process.seteuid = wrapped.seteuid;
process.setgid = wrapped.setgid;
process.setuid = wrapped.setuid;
if (!credentials.isApple) {
process.setresuid = wrapped.setresuid;
} else {
process.setresuid = () => {
throw new Error('Not implemented in MacOSX');
}
}
}

// ---- keep the attachment of the wrappers above so that it's easier to ----
Expand Down Expand Up @@ -45,7 +52,8 @@ function wrapPosixCredentialSetters(credentials) {
setegid: _setegid,
seteuid: _seteuid,
setgid: _setgid,
setuid: _setuid
setuid: _setuid,
setresuid: _setresuid
} = credentials;

function initgroups(user, extraGroup) {
Expand Down Expand Up @@ -73,6 +81,37 @@ function wrapPosixCredentialSetters(credentials) {
}
}

function setresuid(ruid, euid, suid) {
const ids = credentials.getresuid();
if (ruid === -1) ruid = ids[0];
else {
validateId(ruid, 'ruid');
if (typeof ruid === 'number') ruid |= 0;
}

if (euid === -1) euid = ids[1];
else {
validateId(euid, 'euid');
if (typeof euid === 'number') euid |= 0;
}

if (suid === -1) suid = ids[2];
else {
validateId(suid, 'suid');
if (typeof suid === 'number') suid |= 0;
}

// Result is 0 on success, 0b1xxx if credential is unknown.
const result = _setresuid(ruid, euid, suid);
if (result >= 0b1000) {
const failures = [];
if (result & 0b0001) failures.push(ruid);
if (result & 0b0010) failures.push(euid);
if (result & 0b0100) failures.push(suid);
throw new ERR_UNKNOWN_CREDENTIAL('User', failures);
}
}

function wrapIdSetter(type, method) {
return function(id) {
validateId(id, 'id');
Expand All @@ -96,6 +135,7 @@ function wrapPosixCredentialSetters(credentials) {
return {
initgroups,
setgroups,
setresuid,
setegid: wrapIdSetter('Group', _setegid),
seteuid: wrapIdSetter('User', _seteuid),
setgid: wrapIdSetter('Group', _setgid),
Expand Down
58 changes: 58 additions & 0 deletions src/node_credentials.cc
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,47 @@ static void SetGroups(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(0);
}

// MACOS does not support it yet
#if !defined(__APPLE__)
static void GetRESUid(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(env->has_run_bootstrapping_code());
uid_t ruid, euid, suid;
CHECK_EQ(getresuid(&ruid, &euid, &suid), 0);
Local<Value> array;
if (ToV8Value(env->context(), std::vector<uid_t>{ruid, euid, suid}).ToLocal(&array))
args.GetReturnValue().Set(array);
}

static void SetRESUid(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(env->owns_process_state());

CHECK_EQ(args.Length(), 3);
for (int i = 0; i < 3; i++) {
CHECK(args[i]->IsUint32() || args[i]->IsString());
}

uid_t ruid = uid_by_name(env->isolate(), args[0]);
uid_t euid = uid_by_name(env->isolate(), args[1]);
uid_t suid = uid_by_name(env->isolate(), args[2]);

if (ruid == uid_not_found || euid == uid_not_found ||
suid == uid_not_found) {
// Tells JS to throw ERR_INVALID_CREDENTIAL
int flag = 0b1000;
if (ruid == uid_not_found) flag |= 0b0001;
if (euid == uid_not_found) flag |= 0b0010;
if (suid == uid_not_found) flag |= 0b0100;
args.GetReturnValue().Set(flag);
} else if (setresuid(ruid, euid, suid)) {
env->ThrowErrnoException(errno, "setresuid");
} else {
args.GetReturnValue().Set(0);
}
}
#endif

static void InitGroups(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);

Expand Down Expand Up @@ -429,6 +470,11 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
registry->Register(GetEGid);
registry->Register(GetGroups);

#if !defined(__APPLE__)
registry->Register(SetRESUid);
registry->Register(GetRESUid);
#endif

registry->Register(InitGroups);
registry->Register(SetEGid);
registry->Register(SetEUid);
Expand All @@ -447,6 +493,10 @@ static void Initialize(Local<Object> target,

env->SetMethod(target, "safeGetenv", SafeGetenv);

#if defined(__APPLE__)
READONLY_TRUE_PROPERTY(target, "isApple");
#endif

#ifdef NODE_IMPLEMENTS_POSIX_CREDENTIALS
READONLY_TRUE_PROPERTY(target, "implementsPosixCredentials");
env->SetMethodNoSideEffect(target, "getuid", GetUid);
Expand All @@ -455,13 +505,21 @@ static void Initialize(Local<Object> target,
env->SetMethodNoSideEffect(target, "getegid", GetEGid);
env->SetMethodNoSideEffect(target, "getgroups", GetGroups);

#if !defined(__APPLE__)
env->SetMethodNoSideEffect(target, "getresuid", GetRESUid);
#endif

if (env->owns_process_state()) {
env->SetMethod(target, "initgroups", InitGroups);
env->SetMethod(target, "setegid", SetEGid);
env->SetMethod(target, "seteuid", SetEUid);
env->SetMethod(target, "setgid", SetGid);
env->SetMethod(target, "setuid", SetUid);
env->SetMethod(target, "setgroups", SetGroups);

#if !defined(__APPLE__)
env->SetMethod(target, "setresuid", SetRESUid);
#endif
}
#endif // NODE_IMPLEMENTS_POSIX_CREDENTIALS
}
Expand Down
36 changes: 36 additions & 0 deletions test/parallel/test-process-uid-gid.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ if (common.isWindows) {
assert.strictEqual(process.getgid, undefined);
assert.strictEqual(process.setuid, undefined);
assert.strictEqual(process.setgid, undefined);
assert.strictEqual(process.getresuid, undefined);
assert.strictEqual(process.setresuid, undefined);
return;
}

Expand All @@ -51,6 +53,32 @@ assert.throws(() => {
message: 'User identifier does not exist: fhqwhgadshgnsdhjsdbkhsdabkfabkveyb'
});

if (!common.isOSX) {
assert.throws(() => {
process.setresuid({}, 0, 0);
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "ruid" argument must be one of type ' +
'number or string. Received an instance of Object'
});

assert.throws(() => {
process.setresuid(0, {}, 0);
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "euid" argument must be one of type ' +
'number or string. Received an instance of Object'
});

assert.throws(() => {
process.setresuid(0, 0, {});
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: 'The "suid" argument must be one of type ' +
'number or string. Received an instance of Object'
});
}

// Passing -0 shouldn't crash the process
// Refs: https://github.com/nodejs/node/issues/32750
try { process.setuid(-0); } catch {}
Expand All @@ -63,6 +91,7 @@ if (process.getuid() !== 0) {
// Should not throw.
process.getgid();
process.getuid();
if (!common.isOSX) process.getresuid();

assert.throws(
() => { process.setgid('nobody'); },
Expand All @@ -73,6 +102,13 @@ if (process.getuid() !== 0) {
() => { process.setuid('nobody'); },
/(?:EPERM, .+|User identifier does not exist: nobody)$/
);

if (!common.isOSX) {
assert.throws(
() => { process.setresuid('nobody', 1, 2); },
/(?:EPERM, .+|Group identifier does not exist: \[ 'nobody' \])$/
);
}
return;
}

Expand Down