-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
Conversation
src/node_util.cc
Outdated
String::Utf8Value strenvtag(envtag); | ||
std::string text; | ||
bool isEnvSafe = false; | ||
isEnvSafe = SafeGetenv(*strenvtag, &text); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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'; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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'); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
got it
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Superfluous return statements.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
test/parallel/test-util-internal.js
Outdated
} = process.binding('util'); | ||
|
||
for (const oneEnv in process.env) { | ||
console.log(oneEnv, safeGetenvForJs(oneEnv)); |
There was a problem hiding this comment.
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.)
test/parallel/test-util-internal.js
Outdated
assert.strictEqual( | ||
safeGetenvForJs(FAKEENVVAR), | ||
undefined | ||
); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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'); |
There was a problem hiding this comment.
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?
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? |
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. |
There was a problem hiding this 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())); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure 👍
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: |
@bnoordhuis This is something we can raise in another pr, right? I would love to do that too. |
Ping @bnoordhuis |
There was a problem hiding this 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())); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, LGTM. CI: https://ci.nodejs.org/job/node-test-pull-request/13150/
@bnoordhuis is the node-test-linter failing for something out of this pr? |
Nope, it's something else. Can be ignored. |
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]>
Landed in e200db1b2d8e6b25bb273d5a058957b07fc0166b 🎉 I fixed the commit message while landing. |
Thanks a lot :) |
This looks to have actually landed in 916cfec. |
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. |
@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? |
Should this be backported to |
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]>
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]>
Should this be backported to |
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), orvcbuild test
(Windows) passesAffected core subsystem(s)
os, module, util binding