From 0a07565d7c0eaefc3912191c7ca673552479d533 Mon Sep 17 00:00:00 2001 From: Daniel Bevenius Date: Wed, 17 Mar 2021 13:48:51 +0100 Subject: [PATCH] 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. --- .github/workflows/build-tarball.yml | 4 +++ .github/workflows/coverage-linux.yml | 2 ++ .github/workflows/test-asan.yml | 2 ++ .github/workflows/test-linux.yml | 2 ++ common.gypi | 3 ++ src/node_credentials.cc | 42 +++++++++++++++++++++++++++- 6 files changed, 54 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-tarball.yml b/.github/workflows/build-tarball.yml index 226dc69f0e921b..faa596e10a5203 100644 --- a/.github/workflows/build-tarball.yml +++ b/.github/workflows/build-tarball.yml @@ -25,6 +25,8 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Environment Information run: npx envinfo + - name: Install libcap-dev + run: sudo apt-get install -y libcap-dev - name: Make tarball run: | export DISTTYPE=nightly @@ -51,6 +53,8 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Environment Information run: npx envinfo + - name: Install libcap-dev + run: sudo apt-get install -y libcap-dev - name: Download tarball uses: actions/download-artifact@v1 with: diff --git a/.github/workflows/coverage-linux.yml b/.github/workflows/coverage-linux.yml index 75f296230dfd37..235191b5f894d2 100644 --- a/.github/workflows/coverage-linux.yml +++ b/.github/workflows/coverage-linux.yml @@ -34,6 +34,8 @@ jobs: run: npx envinfo - name: Install gcovr run: pip install gcovr==4.2 + - name: Install libcap-dev + run: sudo apt-get install -y libcap-dev - name: Build run: make build-ci -j2 V=1 CONFIG_FLAGS="--error-on-warn --coverage" # TODO(bcoe): fix the couple tests that fail with the inspector enabled. diff --git a/.github/workflows/test-asan.yml b/.github/workflows/test-asan.yml index 9e6192c32a37e9..9c22a5eca72902 100644 --- a/.github/workflows/test-asan.yml +++ b/.github/workflows/test-asan.yml @@ -34,6 +34,8 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Environment Information run: npx envinfo + - name: Install libcap-dev + run: sudo apt-get install -y libcap-dev - name: Build run: make build-ci -j2 V=1 - name: Test diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index 41968139816c38..431b0119778523 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -25,6 +25,8 @@ jobs: python-version: ${{ env.PYTHON_VERSION }} - name: Environment Information run: npx envinfo + - name: Install libcap-dev + run: sudo apt-get install -y libcap-dev - name: Build run: make build-ci -j2 V=1 CONFIG_FLAGS="--error-on-warn" - name: Test diff --git a/common.gypi b/common.gypi index 79a22ac0faca37..1a3d13abb6977c 100644 --- a/common.gypi +++ b/common.gypi @@ -373,6 +373,9 @@ 'cflags': [ '-pthread' ], 'ldflags': [ '-pthread' ], }], + [ 'OS in "linux"', { + 'ldflags': [ '-Wl,-Bstatic -Wl,--whole-archive -lcap -Wl,--no-whole-archive -Wl,-Bdynamic' ], + }], [ 'OS in "linux freebsd openbsd solaris android aix cloudabi"', { 'cflags': [ '-Wall', '-Wextra', '-Wno-unused-parameter', ], 'cflags_cc': [ '-fno-rtti', '-fno-exceptions', '-std=gnu++1y' ], diff --git a/src/node_credentials.cc b/src/node_credentials.cc index fa3dfa48a3ceb2..a485eec6b57b36 100644 --- a/src/node_credentials.cc +++ b/src/node_credentials.cc @@ -11,6 +11,9 @@ #if !defined(_MSC_VER) #include // setuid, getuid #endif +#ifdef __linux__ +#include +#endif // __linux__ namespace node { @@ -33,11 +36,48 @@ bool linux_at_secure = false; namespace credentials { -// Look up environment variable unless running as setuid root. +#if defined(__linux__) +// Returns true if the current process only has the passed-in capability. +bool HasOnly(cap_value_t capability) { + DCHECK(cap_valid(capability)); + + cap_t cap = cap_get_proc(); + if (cap == nullptr) { + return false; + } + + // Create a cap_t and set the capability passed in. + cap_t cap_cmp = cap_init(); + if (cap_cmp == nullptr) { + cap_free(cap); + return false; + } + cap_value_t cap_list[] = { capability }; + cap_set_flag(cap_cmp, CAP_EFFECTIVE, 1, cap_list, CAP_SET); + cap_set_flag(cap_cmp, CAP_PERMITTED, 1, cap_list, CAP_SET); + // Compare this to the process's effective capabilities + bool ret = cap_compare(cap, cap_cmp) == 0; + + cap_free(cap); + cap_free(cap_cmp); + + return ret; +} +#endif + +// Look up the environment variable and allow the lookup if the current +// process only has the capability CAP_NET_BIND_SERVICE set. If the current +// process does not have any capabilities set and the process is running as +// setuid root then lookup will not be allowed. bool SafeGetenv(const char* key, std::string* text, Environment* env) { #if !defined(__CloudABI__) && !defined(_WIN32) +#if defined(__linux__) + if ((!HasOnly(CAP_NET_BIND_SERVICE) && per_process::linux_at_secure) || + getuid() != geteuid() || getgid() != getegid()) +#else if (per_process::linux_at_secure || getuid() != geteuid() || getgid() != getegid()) +#endif goto fail; #endif