Skip to content
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

security,unix: audit use of process.env in lib/ for setuid binary #18511

Closed
wants to merge 2 commits into from
Closed

security,unix: audit use of process.env in lib/ for setuid binary #18511

wants to merge 2 commits into from

Conversation

j0t3x
Copy link
Contributor

@j0t3x j0t3x commented Feb 1, 2018

Wrapped SafeGetenv() in util binding with the
purpose of protecting the cases when env vars are
accessed with the privileges of another user in jsland.

Fixes: #9160

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • commit message follows commit guidelines
Affected core subsystem(s)

os, module, util binding

@nodejs-github-bot nodejs-github-bot added c++ Issues and PRs that require attention from people who are familiar with C++. module Issues and PRs related to the module subsystem. os Issues and PRs related to the os subsystem. util Issues and PRs related to the built-in util module. labels Feb 1, 2018
src/node_util.cc Outdated
String::Utf8Value strenvtag(envtag);
std::string text;
bool isEnvSafe = false;
isEnvSafe = SafeGetenv(*strenvtag, &text);
Copy link
Member

Choose a reason for hiding this comment

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

Okay, doing it this way does make my TODO comment relevant – this is going to break on Windows if the environment variable is not pure ASCII.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think I'm not using it on any windows part of the code, right?

lib/os.js Outdated
@@ -123,13 +123,14 @@ function tmpdir() {
if (isWindows) {
path = process.env.TEMP ||
process.env.TMP ||
(process.env.SystemRoot || process.env.windir) + '\\temp';
(process.env.SystemRoot ||
process.env.windir) + '\\temp';
Copy link
Member

Choose a reason for hiding this comment

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

No unrelated stylistic changes, please.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤦🏽‍♂️ Will do.

lib/module.js Outdated
@@ -35,6 +35,7 @@ const {
internalModuleReadJSON,
internalModuleStat
} = process.binding('fs');
const { safeGetenvForJs } = process.binding('util');
Copy link
Member

Choose a reason for hiding this comment

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

Just safeGetenv? The ForJs part is self-evident.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Its alright if I let the SafeGetenvForJs name in c++, I think it's useful there.

Copy link
Member

Choose a reason for hiding this comment

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

It's also self-evident for C++ code because of the function prototype; if it takes a const FunctionCallbackInfo<Value>&, you know it's a JS -> C++ API method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

got it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

but here I have same name functions in the same namespace.

src/node_util.cc Outdated
@@ -174,6 +176,20 @@ void PromiseReject(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(ret.FromMaybe(false));
}

void SafeGetenvForJs(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
Local<String> envtag = args[0]->ToString();
Copy link
Member

Choose a reason for hiding this comment

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

Can you use the overload that takes a Local<Context>?

Or simply use args[0].As<String>() and CHECK(args[0]->IsString()) first. It's an internal API, it's reasonable to enforce strict types.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you point me where can I find different overload options?

Copy link
Member

Choose a reason for hiding this comment

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

Yep, search deps/v8/include/v8.h for ToString.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bnoordhuis as a side benefit, can you point me somewhere to read about v8(guides are out of date or poor), mostly to improve my contributions.

Copy link
Member

Choose a reason for hiding this comment

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

There is some documentation on the V8 wiki and a few samples in the V8 repo but that's about it. Working with V8 means reading its source (or at least its header files.)

At least the headers have pretty good doc comments now. It wasn't anything like that when I started working on node.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will have that in mind, thanks!

src/node_util.cc Outdated
Local<String> envtag = args[0]->ToString();
String::Utf8Value strenvtag(envtag);
std::string text;
bool isEnvSafe = false;
Copy link
Member

Choose a reason for hiding this comment

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

Locals should use snake_case, i.e., is_env_safe.

That said, you don't need a separate local, just do if (!SafeGetenv(...)) return;

src/node_util.cc Outdated
if (isEnvSafe) {
args.GetReturnValue()
.Set(String::NewFromUtf8(isolate, text.c_str()));
return;
Copy link
Member

Choose a reason for hiding this comment

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

Superfluous return statements.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

so no return statements are needed given the case i have 1 value to return?

Copy link
Member

Choose a reason for hiding this comment

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

Yep. You set the return value and then just fall through. If you use the guard style I mentioned above, the args.GetReturnValue().Set(...) will also be the last statement in this function.

} = process.binding('util');

for (const oneEnv in process.env) {
console.log(oneEnv, safeGetenvForJs(oneEnv));
Copy link
Member

Choose a reason for hiding this comment

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

Tests should, as a rule of thumb, be silent. Don't print the inputs, the exception from assert.strictEqual() takes care of that (it's in the error message.)

assert.strictEqual(
safeGetenvForJs(FAKEENVVAR),
undefined
);
Copy link
Member

Choose a reason for hiding this comment

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

I guess it's unlikely that anyone has this set in the environment but it's less robust than I'd like.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll remove it then.

@ChALkeR ChALkeR added the security Issues and PRs related to security. label Feb 1, 2018
@BridgeAR
Copy link
Member

BridgeAR commented Feb 2, 2018

lib/module.js Outdated
@@ -735,7 +736,7 @@ Module._initPaths = function() {
paths.unshift(path.resolve(homeDir, '.node_modules'));
}

var nodePath = process.env.NODE_PATH;
var nodePath = safeGetenv('NODE_PATH');
Copy link
Contributor Author

@j0t3x j0t3x Feb 2, 2018

Choose a reason for hiding this comment

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

@addaleax here I'm touching windows and indeed it's tests are failing. 👍
I see the value of changing NODE_PATH in a test,
Ill consider windows vs Unix on that too, Is it ok?

@jdalton
Copy link
Member

jdalton commented Feb 2, 2018

Is this something user code has to worry about? Should this be tackled in the accessors of the magic env object itself so that user code gets this too?

@addaleax
Copy link
Member

addaleax commented Feb 2, 2018

Is this something user code has to worry about?

Yes, but I don’t think Node can make a blanket decision for all user code to block out environment variables in this (already very rarely occurring) situation.

I would be okay with requiring an explicit opt-in by the userland code in some way, I guess.

Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

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

Looks good!

src/node_util.cc Outdated
std::string text;
if (!SafeGetenv(*strenvtag, &text)) return;
args.GetReturnValue()
.Set(String::NewFromUtf8(args.GetIsolate(), text.c_str()));
Copy link
Member

Choose a reason for hiding this comment

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

Can you indent this line by 4 spaces? We generally do that for statement continuations, see https://github.com/nodejs/node/blob/master/CPP_STYLE_GUIDE.md#4-spaces-of-indentation-for-statement-continuations

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure 👍

@bnoordhuis
Copy link
Member

bnoordhuis commented Feb 3, 2018

I don’t think Node can make a blanket decision for all user code to block out environment variables in this (already very rarely occurring) situation.

That's more of an argument for an opt-out. JS code can't even properly detect it's running as setuid root right now, let alone protect against it.

edit: nuance: process.geteuid() and process.getegid() have been back-ported but there's no way to detect AT_SECURE.

@j0t3x
Copy link
Contributor Author

j0t3x commented Feb 7, 2018

process.geteuid() and process.getegid() have been back-ported but there's no way to detect AT_SECURE.

@bnoordhuis This is something we can raise in another pr, right? I would love to do that too.

@BridgeAR
Copy link
Member

Ping @bnoordhuis

Copy link
Member

@bnoordhuis bnoordhuis left a comment

Choose a reason for hiding this comment

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

Is the ping w.r.t. @j0t3x's question? Yes, can be done in another PR.

src/node_util.cc Outdated
std::string text;
if (!SafeGetenv(*strenvtag, &text)) return;
args.GetReturnValue()
.Set(String::NewFromUtf8(args.GetIsolate(), text.c_str()));
Copy link
Member

Choose a reason for hiding this comment

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

Can you use the String::NewFromUtf8 overload that returns a MaybeLocal?

Apropos the function name, I remember discussing renaming it to just SafeGetenv?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done 👍

src/node_util.cc Outdated
@@ -8,12 +8,14 @@ using v8::Array;
using v8::Context;
using v8::FunctionCallbackInfo;
using v8::Integer;
using v8::Isolate;
Copy link
Member

Choose a reason for hiding this comment

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

Unused import.

src/node_util.cc Outdated
void SafeGetenvForJs(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsString());
Local<String> envtag = args[0].As<String>();
String::Utf8Value strenvtag(envtag);
Copy link
Member

Choose a reason for hiding this comment

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

Node has its own slightly more efficient Utf8Value implementation; just drop the String:: part and pass in args.GetIsolate() as the first argument. You can pass in args[0] as-is, no need to cast.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done, thanks for the tip!

Wrapped SafeGetenv() in util binding with the
purpose of protecting the cases when env vars are
accessed with the privileges of another user in jsland.

Fixes: #9160
Copy link
Member

@bnoordhuis bnoordhuis left a comment

Choose a reason for hiding this comment

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

@j0t3x
Copy link
Contributor Author

j0t3x commented Feb 13, 2018

@bnoordhuis is the node-test-linter failing for something out of this pr?

@bnoordhuis
Copy link
Member

Nope, it's something else. Can be ignored.

@bnoordhuis bnoordhuis added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Feb 15, 2018
BridgeAR pushed a commit to BridgeAR/node that referenced this pull request Feb 16, 2018
Wrap SafeGetenv() in util binding with the purpose of protecting
the cases when env vars are accessed with the privileges of another
user in jsland.

PR-URL: nodejs#18511
Fixes: nodejs#9160
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Ben Noordhuis <[email protected]>
@BridgeAR
Copy link
Member

Landed in e200db1b2d8e6b25bb273d5a058957b07fc0166b 🎉

I fixed the commit message while landing.

@BridgeAR BridgeAR closed this Feb 16, 2018
@j0t3x
Copy link
Contributor Author

j0t3x commented Feb 18, 2018

Thanks a lot :)

@richardlau
Copy link
Member

Landed in e200db1b2d8e6b25bb273d5a058957b07fc0166b 🎉

This looks to have actually landed in 916cfec.

@BridgeAR
Copy link
Member

Uh, thanks @richardlau. I sometimes post the commit id while pushing. If someone else has landed something in the meanwhile, it is rejected and I have to rebase. Seems like I forgot to update the commit it.

@richardlau
Copy link
Member

@BridgeAR easily done 😁. Just making sure the information is accurate just in case backports are needed to earlier release lines. And on that note, @bnoordhuis what's the semver-ness of this PR?

@MylesBorins
Copy link
Contributor

Should this be backported to v9.x-staging? If yes please follow the guide and raise a backport PR, if not let me know or add the dont-land-on label.

BridgeAR pushed a commit to BridgeAR/node that referenced this pull request May 1, 2018
Wrap SafeGetenv() in util binding with the purpose of protecting
the cases when env vars are accessed with the privileges of another
user in jsland.

PR-URL: nodejs#18511
Fixes: nodejs#9160
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Ben Noordhuis <[email protected]>
MayaLekova pushed a commit to MayaLekova/node that referenced this pull request May 8, 2018
Wrap SafeGetenv() in util binding with the purpose of protecting
the cases when env vars are accessed with the privileges of another
user in jsland.

PR-URL: nodejs#18511
Fixes: nodejs#9160
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: Ben Noordhuis <[email protected]>
@MylesBorins
Copy link
Contributor

Should this be backported to v8.x-staging? If yes please follow the guide and raise a backport PR, if not let me know or add the dont-land-on label.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
author ready PRs that have at least one approval, no pending requests for changes, and a CI started. c++ Issues and PRs that require attention from people who are familiar with C++. module Issues and PRs related to the module subsystem. os Issues and PRs related to the os subsystem. security Issues and PRs related to security. util Issues and PRs related to the built-in util module.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

security,unix: audit use of process.env in lib/ for setuid binary
10 participants