-
Notifications
You must be signed in to change notification settings - Fork 29.8k
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
process: initial impl of feature access control #22112
Conversation
this looks pretty cool. I've been playing around with some stuff that can be applied on a per-script/per-module granularity. I don't think per-process is always good enough. |
@devsnek Would love to hear more details. In particular, i feel like it might end up being pretty hard to make sure nothing messes with node’s internals, if it’s a solution in JS land? |
This is really cool! Would it be possible to place restrictions on npm install scripts and third-party modules in the future? It could be really cool if the |
@addaleax basically I had to hack into v8 internal execution context tracking to see where calls from from. from there I keep a big map of applied permissions and check them in order of lowest to highest by doing a graph traversal from the node the rule applies to up to the entry. that way the highest request always overrides lower ones so permissions can change but not overridden. |
|
||
<!-- eslint-skip --> | ||
```js | ||
{ accessInspectorBindings: true, |
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.
Missing some stuff like http2 and dns? (dns is under net outgoing?)
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.
dns
currently falls under netOutgoing
, but I missed that in the doc here (updated with that).
HTTP/2 is a purely computational thing, so I don’t think there’s anything to restrict here (although that could of course be implemented)? For now, that’s also covered by the net
flags
@benjamingr I think what @devsnek is hacking around on would go into that direction. It’s a lot easier for npm install scripts, though; something I had in mind is that we could maybe refine this to allow for connections to whitelisted hosts/writing to whitelisted files/directories (these are not easy to implement, though). |
This is a very interesting start to something that will likely be a long running discussion. I've been working on details for a command line based approach that could easily parallel this. Essentially something like:
The are some tricky bits to all this and having a number of different options on the table would be great to work things out |
@jasnell Yeah, I thought about a CLI API too… totally open for suggestions and happy to implement them :) (I haven’t spent much all that much time on this, tho – this PR is literally hacked together within a single afternoon so far.) |
For example of how these could parallel with one another... Command-line: $ node --deny=fs.read,fs.write,workers --allow=setV8Flags Runtime API (with a slightly different syntax that could provide more flexibility): process.accessControl({
deny: ['fs.read', 'fs.write', 'workers'],
allow: ['setV8Flags']
}) FWIW, I would think that ability to spawn child processes and loading addons would need to be automatically denied by default if certain policies such as fs.read, fs.write, etc are denied, since a child process can be used as an easy workaround to those policies. We need to give some thought to sensible defaults. |
@jasnell One reason I didn’t look into using arrays/sets for this was that I’d like to keep the possibility open to use more complex settings than booleans, e.g. whitelists for specific directories or so. (Once again, hard to implement). |
Yeah, I definitely get that but I'd much rather keep the API and the policy definition as simple as possible. The more options we provide, the more we increase the complexity, cost, and likelihood of getting it wrong. |
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 some doc nits)
[`promise.catch()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch | ||
[`require()`]: globals.html#globals_require | ||
[`require.main`]: modules.html#modules_accessing_the_main_module | ||
[`require.resolve()`]: modules.html#modules_require_resolve_request_options | ||
[`tracing.disable()`]: tracing.html#tracing_tracing_disable | ||
[`tracing.enable()`]: tracing.html#tracing_tracing_enable |
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.
Nit: both after [`setTimeout(fn, 0)`]
?
doc/api/process.md
Outdated
Please report bugs at https://github.com/nodejs/node/issues. | ||
|
||
Operations scheduled before this call are not undone or stopped. | ||
Features that were previously disabled through this call can not be re-enabled. |
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.
Nit: can not
-> cannot
?
doc/api/process.md
Outdated
Features that were previously disabled through this call can not be re-enabled. | ||
Child processes do not inherit these restrictions, whereas [`Worker`][]s do. | ||
|
||
The following code removes some permissions of this Node.js instance: |
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.
Nit: maybe this
-> current
?
doc/api/process.md
Outdated
added: REPLACEME | ||
--> | ||
|
||
Returns an object representing a set of permissions for the current Node.js |
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.
Replace with the formal * Returns: {Object} Description...
signature?
doc/api/process.md
Outdated
|
||
Currently only boolean options are supported. | ||
|
||
In particular, thes settings affect the following features of the Node.js API: |
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.
Nit: thes
-> these
.
<!-- eslint-skip --> | ||
```js | ||
{ accessInspectorBindings: true, | ||
childProcesses: true, |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
doc/api/process.md
Outdated
- All other [`fs`][] operations | ||
- [`net`][] operations on UNIX domain sockets | ||
- `loadAddons`: | ||
- [`process.dlopen()`][] and [`require()`][] in the case of native addons. |
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.
Nit: inconsistent period at the end of an incomplete sentence?
doc/api/process.md
Outdated
- `setEnvironmentVariables`: | ||
- Setting/deleting keys on [`process.env`][] | ||
- `setProcessAttrs`: | ||
- [`process.title`][] setting |
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.
Nit: maybe it is worth to sort this sublist?
@vsemozhetbyt Done, but please keep in mind that this PR is still at its very beginning :) |
We had a long discussion about this sort of feature at the IRL @nodejs/security-wg meeting in SF last week. We came to the conclusion that without actual isolation, this can be done so long as user code in the Node process is assumed to be trusted (that is, there's no malicious code, but code could be buggy). @addaleax based on the docs you've added, it seems you've implemented this with the same assumption, which is great! Sadly, I don't think anyone took notes. Sorry! That being said, there's some more discussing here: nodejs/security-wg#327 Also FWIW, Intrinsic builds a commercial product around this kind of feature: https://intrinsic.com. Intrinsic also provides multiple policy sets per process (i.e. multiple actually-isolated environments), though by default they're organized by inbound HTTP routes, rather than by module. (Disclaimer: I work for Intrinsic.) |
@bengl I would personally love it if we could get actual sandboxing in a runtime that is written for a language which in turn was designed with sandboxing in mind and whose primary use case still is sandboxing. But yes, this PR doesn’t provide any protections against user code leading to crashes or resource exhaustion or whatever. |
@bengl ... this really is a critical point. We need to make sure that whatever we do here is additive to whatever additional experience commercial vendors may provide and does not compete or run counter to such efforts. Ideally, anything we do here should make it easier for companies like Intrinsic to either continue providing such services or easier to build new capabilities on top. On the sandboxing bit, the point was raised at the face to face that the only guaranteed way to ensure proper sandboxing here is to rely on the container within which the Node.js process is running. The goal for us should be to make it more difficult to use Node.js as an attack vector while retaining the current assumption that code running within the typical Node.js process is trusted. |
Could you elaborate on the intended use case for this? To me, it seems like the main value-add of "access control" is typically the improved security (since it restricts the level of impact that a compromised component could have). But if this can be bypassed by spawning a child process, then it's not clear how it's useful for security, because an attacker that would have originally called |
@jasnell Because Intrinsic assumes a malicious attacker model, there's not as much overlap as it may seem. |
I’d love it if we could get to a point where we can run npm install scripts without worrying too much about that, as an example.
This PR does provide the option to disable spawning child processes / loading addons through Node’s APIs for that. |
I would disagree slightly with avoiding features competitive with products built on Node.js in favor of only being additive. If a feature is useful to a significant portion of users for it to be built into core, I don't think we should prevent that. I would, however, be hesitant to support more niche features around it, which may not be useful to as many users. In this particular case, I would not want a desire to avoid competition to result in a less capable or less usable feature, potentially compromising the security advantage of the feature, given that is the main purpose of it. |
That makes sense, and to be clear, I'm definitely on board with allowing things like this to be done safely -- it's just not clear to me whether the security policy mechanism in this PR would be an improvement for that case. For example, regarding this note:
Is there a particular category of buggy code that this is intended to protect against? For example, it seems unlikely to me that there are common bugs which would cause a module to write to the filesystem if the module is not supposed to ever write to the filesystem. Either a module's source code has a call like But in cases where the attacker can execute arbitrary code, then it seems like the "trusted code assumption" is violated, so at that point there are probably other things they can exploit even without spawning a child process (such as issues like #9820 which previously were not considered to have security impact). |
For example, it seems unlikely to me that there are common bugs which
would cause a module to write to the filesystem if the module is not
supposed to ever write to the filesystem.
At the very least, having access control would mean that if, initially,
writing to the file system is disabled, then, when someone (inadvertently?)
adds a `fs.write()` call in some subsequent version of the package, it will
initially fail, until the policy is updated. This should make adding a
capability an explicit step, creating a process which hopefully results in
justified and explicit modifications of the policy, rather than implicit
access grant.
…On Fri, Aug 3, 2018 at 3:47 PM, Teddy Katz ***@***.***> wrote:
I’d love it if we could get to a point where we can run npm install
scripts without worrying too much about that, as an example.
That makes sense, and to be clear, I'm definitely on board with allowing
things like this to be done safely -- it's just not clear to me whether the
security policy mechanism in this PR would be an improvement for that case.
For example, regarding this note:
[The Security WG] came to the conclusion that without actual isolation,
this can be done so long as user code in the Node process is assumed to be
trusted (that is, there's no malicious code, but code could be buggy).
Is there a particular category of buggy code that this is intended to
protect against? For example, it seems unlikely to me that there are common
bugs which would cause a module to write to the filesystem if the module is
not supposed to ever write to the filesystem. Either a module's source code
has a call like fs.write() (in which case it's clearly supposed to write
to the filesystem and it wouldn't be possible to block its filesystem
access while preserving intended functionality), or it does not have any
calls like that (in which case it seems like an attacker would need to have
arbitrary code execution in order to create that call).
But in cases where the attacker can execute arbitrary code, then it seems
like the "trusted code assumption" is violated, so at that point there are
probably other things they can exploit even without spawning a child
process (such as issues like #9820
<#9820> which previously were not
considered to have security impact).
—
You are receiving this because you are on a team that was mentioned.
Reply to this email directly, view it on GitHub
<#22112 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AA7k0cd-GL_e-rcRAjD20vuMHgqIGHmrks5uNNL6gaJpZM4VumUO>
.
|
e2631eb
to
bf473c2
Compare
Full CI: https://ci.nodejs.org/job/node-test-pull-request/16283/
I don’t expect this to have a noticeable impact since the check compiles down to a single bit-test + a conditional jump (with a "not taken" hint when available), but in any case, here’s a benchmark CI for an affected subsystem (dgram): https://ci.nodejs.org/view/Node.js%20benchmark/job/benchmark-node-micro-benchmarks/222/ (edit: 4 significant results, which is about the number of false postives we’d expect to see – and all of the results are actually increases. 🙂)
Yes, definitely. 👍
A fork has advantages if we think that we want a separate place for discussion (in this case that might as well be the security WG’s repo?) and/or expect this to be implemented incrementally in more than a couple steps. I’m not sure these conditions are met, though. |
bf473c2
to
58f4334
Compare
|
9526757
to
1038a8f
Compare
This is something that I think would be super valuable. Third party modules are currently given full access to the machine, but they really shouldn't in 99% of the cases. It would make that much harder for the next eslint-scope to happen, and would have added benefits (if the package managers know that a given package will never access the filesystem, new optimizations become possible). If we had such an API available, I believe we would be super interested into implementing in Yarn an interface that would let the user review the permissions granted to their packages 🙂 |
Implement `process.accessControl`, a simple API for restricting usage of certain in-process APIs. Refs: https://github.com/nodejs/node/issues/22107
Similarly to the previous commit, this API allows prohibiting usage of certain features inside a worker thread. Refs: https://github.com/nodejs/node/issues/22107
Add `--disable=featureA,featureB,...` and `--experimental-access-control` flags that control (and enable) `process.accessControl` at startup.
1038a8f
to
b47f3eb
Compare
@addaleax is this abandoned? Would you like someone else to pick this up or sth? |
ping @addaleax Should we close this? Please re-open if it's still needed. |
Java has something similar where you can limit what's possible to do. Imagine a kind of sandbox where you deny any kind of network request, just like Of course you could use the |
Just playing around with something here … this doesn’t need to happen, or can happen in a completely different way or by somebody else. I’d love to get ideas and feedback on this, though.
/cc @nodejs/security @nodejs/collaborators
Implement
process.accessControl
, a simple API for restricting usage of certain in-process APIs,and similarly an
accessControl
option when setting up workers.Refs: #22107
Checklist
make -j4 test
(UNIX), orvcbuild test
(Windows) passes