Skip to content

Commit 0a07565

Browse files
committed
src: allow CAP_NET_BIND_SERVICE in SafeGetenv
This commit updates SafeGetenv to check if the current process has the effective capability cap_net_bind_service set, and if so allows environment variables to be read. The motivation for this change is a use-case where Node is run in a container, and the is a requirement to be able to listen to ports below 1024. This is done by setting the capability of cap_net_bind_service. In addition there is a need to set the environment variable `NODE_EXTRA_CA_CERTS`. But currently this environment variable will not be read when the capability has been set on the executable.
1 parent 45cdc13 commit 0a07565

File tree

6 files changed

+54
-1
lines changed

6 files changed

+54
-1
lines changed

.github/workflows/build-tarball.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ jobs:
2525
python-version: ${{ env.PYTHON_VERSION }}
2626
- name: Environment Information
2727
run: npx envinfo
28+
- name: Install libcap-dev
29+
run: sudo apt-get install -y libcap-dev
2830
- name: Make tarball
2931
run: |
3032
export DISTTYPE=nightly
@@ -51,6 +53,8 @@ jobs:
5153
python-version: ${{ env.PYTHON_VERSION }}
5254
- name: Environment Information
5355
run: npx envinfo
56+
- name: Install libcap-dev
57+
run: sudo apt-get install -y libcap-dev
5458
- name: Download tarball
5559
uses: actions/download-artifact@v1
5660
with:

.github/workflows/coverage-linux.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ jobs:
3434
run: npx envinfo
3535
- name: Install gcovr
3636
run: pip install gcovr==4.2
37+
- name: Install libcap-dev
38+
run: sudo apt-get install -y libcap-dev
3739
- name: Build
3840
run: make build-ci -j2 V=1 CONFIG_FLAGS="--error-on-warn --coverage"
3941
# TODO(bcoe): fix the couple tests that fail with the inspector enabled.

.github/workflows/test-asan.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ jobs:
3434
python-version: ${{ env.PYTHON_VERSION }}
3535
- name: Environment Information
3636
run: npx envinfo
37+
- name: Install libcap-dev
38+
run: sudo apt-get install -y libcap-dev
3739
- name: Build
3840
run: make build-ci -j2 V=1
3941
- name: Test

.github/workflows/test-linux.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ jobs:
2525
python-version: ${{ env.PYTHON_VERSION }}
2626
- name: Environment Information
2727
run: npx envinfo
28+
- name: Install libcap-dev
29+
run: sudo apt-get install -y libcap-dev
2830
- name: Build
2931
run: make build-ci -j2 V=1 CONFIG_FLAGS="--error-on-warn"
3032
- name: Test

common.gypi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,9 @@
373373
'cflags': [ '-pthread' ],
374374
'ldflags': [ '-pthread' ],
375375
}],
376+
[ 'OS in "linux"', {
377+
'ldflags': [ '-Wl,-Bstatic -Wl,--whole-archive -lcap -Wl,--no-whole-archive -Wl,-Bdynamic' ],
378+
}],
376379
[ 'OS in "linux freebsd openbsd solaris android aix cloudabi"', {
377380
'cflags': [ '-Wall', '-Wextra', '-Wno-unused-parameter', ],
378381
'cflags_cc': [ '-fno-rtti', '-fno-exceptions', '-std=gnu++1y' ],

src/node_credentials.cc

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
#if !defined(_MSC_VER)
1212
#include <unistd.h> // setuid, getuid
1313
#endif
14+
#ifdef __linux__
15+
#include <sys/capability.h>
16+
#endif // __linux__
1417

1518
namespace node {
1619

@@ -33,11 +36,48 @@ bool linux_at_secure = false;
3336

3437
namespace credentials {
3538

36-
// Look up environment variable unless running as setuid root.
39+
#if defined(__linux__)
40+
// Returns true if the current process only has the passed-in capability.
41+
bool HasOnly(cap_value_t capability) {
42+
DCHECK(cap_valid(capability));
43+
44+
cap_t cap = cap_get_proc();
45+
if (cap == nullptr) {
46+
return false;
47+
}
48+
49+
// Create a cap_t and set the capability passed in.
50+
cap_t cap_cmp = cap_init();
51+
if (cap_cmp == nullptr) {
52+
cap_free(cap);
53+
return false;
54+
}
55+
cap_value_t cap_list[] = { capability };
56+
cap_set_flag(cap_cmp, CAP_EFFECTIVE, 1, cap_list, CAP_SET);
57+
cap_set_flag(cap_cmp, CAP_PERMITTED, 1, cap_list, CAP_SET);
58+
// Compare this to the process's effective capabilities
59+
bool ret = cap_compare(cap, cap_cmp) == 0;
60+
61+
cap_free(cap);
62+
cap_free(cap_cmp);
63+
64+
return ret;
65+
}
66+
#endif
67+
68+
// Look up the environment variable and allow the lookup if the current
69+
// process only has the capability CAP_NET_BIND_SERVICE set. If the current
70+
// process does not have any capabilities set and the process is running as
71+
// setuid root then lookup will not be allowed.
3772
bool SafeGetenv(const char* key, std::string* text, Environment* env) {
3873
#if !defined(__CloudABI__) && !defined(_WIN32)
74+
#if defined(__linux__)
75+
if ((!HasOnly(CAP_NET_BIND_SERVICE) && per_process::linux_at_secure) ||
76+
getuid() != geteuid() || getgid() != getegid())
77+
#else
3978
if (per_process::linux_at_secure || getuid() != geteuid() ||
4079
getgid() != getegid())
80+
#endif
4181
goto fail;
4282
#endif
4383

0 commit comments

Comments
 (0)