-
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
permission: add permission for udp #53398
base: main
Are you sure you want to change the base?
Conversation
Review requested:
|
de7f6ac
to
0fc2a52
Compare
8e44fed
to
9c645ef
Compare
I assume this also impacts the http module |
Yes. Or we can skip the permission check when using dns module in Node.js core. |
9c645ef
to
67f5b92
Compare
Its very important to document it as I think http is unusable without dns being granted |
9c54926
to
54facc9
Compare
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.
Hey, thanks for working on that.
Would you mind providing some context on why you believe this is useful for end users? In other words, what kind of security would this provide?
Requesting changes just to make sure we are aligned this is something we'd like to see on the Permission Model
I would say it's 90% unusable. AFAIK Anyway it's not only |
Thank you for reviewing the code. I tried to add permission verification to the network(TCP / UDP / DNS) module. The user can set the access to only certain domain names or IP addresses, for example, to avoid SSRF attacks. |
@theanarkh I think I'm losing some context. You said you blocked access but I don't see in the code. Are you saying that you blocked at DNS level, isn't it? If that's the case, I was instead talking to prevent |
I see a lot of value in this PR, as part of adding permission model to the network. |
@marco-ippolito I totally agree. If we agree this is only the first PR which will be shortly followed by others for complete coverage of But I get to choose I'd prefer a single and comprehensive PR that covers both Nonetheless, for clarity I'm not blocking this as it is an amazing piece of work ❤️ |
Thanks for all the suggestions, I convert this PR to draft and continue to work on it. |
I see that too, it's probably more useful to block all network communication instead of specific DNS operations. |
ea66a82
to
ed0b811
Compare
5cf5136
to
29e2adc
Compare
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.
Few nits but LGTM.
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 believe the checks you added to dgram
are way more assertive if placed into the native side (udp_wrap
). I think we must have to move it here.
at afterDns (node:dgram:337:12) | ||
at node:dgram:379:9 | ||
at process.processTicksAndRejections (node:internal/process/task_queues:77:11) { | ||
code: 'ERR_ACCESS_DENIED' |
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.
Instead of adding "Permission: bind to 127.0.0.1/9297"
, we should add a resource
field and a permission
field to the error object. See how --allow-fs-read works
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.
ping
src/permission/net_permission.cc
Outdated
&netmask, | ||
result->netmask, | ||
ip_version)) { // 127.0.0.1/255.255.255.0 | ||
std::cerr << "invalid netmask: " << result->netmask << std::endl; |
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 must be Node.js debug log
Good work! |
If add permission check in native side, i think we should pass a flags to tell native side if it needs to check the permission. Because the native side should not check the IP if the IP is from In addition. If we use UDP in cluster worker(use So i think we also need to check the permission in JS layer. |
Hey, I'll review it carefully over the week. Sorry for the delay. Since it's a security feature, I want to make sure we have covered all caveats as it would expose security vulnerabilities if landed. |
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.
We need to adjust a few things before landing it. Assuming a regular udpv4 socket server:
const server = udp.createSocket('udp4');
server.on('error', () => {}
server.bind(() => {})
- The error object should contain the
resource
property. In this case, resource will be the address + port. - The error object should contain the
permission
property. In this case,NetUDP
. - How would we handle IPV6 addresses when
--allow-net-udp=IPV4/22
? Will Octal IP addresses be covered? We must have tests for all those assertions regardless of whether we are supporting this first iteration or not. - You need to rebase on main, since the
ERR_ACCESS_DENIED
error changed a bit (b9289a6#diff-670bf55805b781d9a3579f8bca9104c04d94af87cc33220149fd7d37b095ca1cR1115). - We should not use
std::cerr
(iostream) for error messages. Instead, create proper error objects. - What if
fd
is passed to_createSocketHandle
? I don't think we could support it. But, we must document. - Last but not least, can't we move all those checks to the C++ side? I think it's less susceptible to changes that would bypass the UDP Check. For instance, if someone writes a new JS API to create UDP sockets, they might forget to include
permission.has
check leading to an exposure. If we use theudp_wrap.cc
it's more likely to be reused in any new JS API.
Overall, looks really good.
8b0d61d
to
457760e
Compare
@RafaelGSS 3: What does the const dgram = require('dgram')
const server = dgram.createSocket('udp4');
server.on('error', console.log)
server.bind(8888, "::1",() => {})
const dgram = require('dgram')
const server = dgram.createSocket('udp4');
server.on('error', console.log)
server.bind(8888, "localhost",() => {})
If we only check in C++ layer, the code above will throw an error because we do not allow bind to 127.0.0.1(the result of dns.lookup). Thanks for your review. I will continue to improve the code and add more tests. |
} | ||
} | ||
// Resolve address first | ||
state.handle.lookup(address, afterDns); |
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.
Why don't we throw the error in .lookup()
? This would reduce the duplicated code.
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.
If the address which is allowed is a domain, we should allow bind to all IPs this domain ponit to, so I think we need check before DNS lookup.
f555bd8
to
9f3b13c
Compare
@anonrig Thanks for your review. I have updated the code and added some replies. |
d34f32b
to
d389d9a
Compare
d389d9a
to
e4e3cc5
Compare
at afterDns (node:dgram:337:12) | ||
at node:dgram:379:9 | ||
at process.processTicksAndRejections (node:internal/process/task_queues:77:11) { | ||
code: 'ERR_ACCESS_DENIED' |
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.
ping
|
||
// The flag --experimental_permission maybe set after --allow-net-udp | ||
// So we just check allow_net_udp.size() here | ||
if (allow_net_udp.size() > 0) { |
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.
This logic must live on NETPermission::Apply
|
||
namespace permission { | ||
|
||
bool ConvertIPToBitset(std::bitset<IPV6_BIT_LEN>* bitset, |
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.
We should have a cpp test for this
bool checked = true; | ||
if (args.Length() > 3 && args[3]->IsBoolean()) { | ||
checked = args[3].As<Boolean>()->Value(); | ||
} |
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.
Why is this needed?
@@ -861,4 +861,59 @@ v8::Maybe<int> GetValidFileMode(Environment* env, | |||
return v8::Just(mode); | |||
} | |||
|
|||
bool IsDigit(const std::string_view 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.
All these functions needs a cpp test
Yeah, that's expected. In the future, we can check if |
But we can not know the IP of other domains. For example, when |
Why we simply don't resolve the domain in the C++ layer? Something like:
Also, what happens if I pass:
|
Refs: #44004
This PR add permission check or UDP module. Usage:
node --allow-net-udp=domain1,127.0.0.1/80 --allow-net-udp-in=domain2/9999
The format can be
*
,*/*
,host
,host:*
,host:port
,host/netmask:port
,*/port
...(host
can be domain or IP).make -j4 test
(UNIX), orvcbuild test
(Windows) passes