Skip to content

Add ScopedPrivileges RAII and sanitize env on elevation - #3138

Open
dxapd wants to merge 2 commits into
google:mainfrom
dxapd:nftables-privilege-raii
Open

Add ScopedPrivileges RAII and sanitize env on elevation#3138
dxapd wants to merge 2 commits into
google:mainfrom
dxapd:nftables-privilege-raii

Conversation

@dxapd

@dxapd dxapd commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

this pr does two things:

  1. create a RAII abstraction for holding elevated privileges
  2. within that abstraction, sanitize the entire provided environment, including the PATH, before elevating

not going to wire it up to cvdalloc until the final PR, though

@dxapd
dxapd marked this pull request as ready for review September 3, 2026 22:29
@dxapd
dxapd requested a review from 3405691582 September 3, 2026 22:30
@dxapd

dxapd commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator Author

@3405691582 we can continue the discussion about whether or not the sanitization is necessary here. imo it's not a huge deal, but there's no harm in doing it given that we are using the PATH variable and also transitively conferring elevated caps onto other binaries.

Result<ScopedPrivileges> ScopedPrivileges::Elevate() {
uid_t orig = getuid();
// The child processes we exec run with elevated privilege (CAP_NET_ADMIN via
// ambient caps) but with AT_SECURE=0, so the dynamic linker won't scrub their

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we set this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure we can with ambient caps


Result<ScopedPrivileges> ScopedPrivileges::Elevate() {
uid_t orig = getuid();
// The child processes we exec run with elevated privilege (CAP_NET_ADMIN via

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this comment talks about Linux capabilities, it should probably belong in the #if block below.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup

// ambient caps) but with AT_SECURE=0, so the dynamic linker won't scrub their
// environment for us. Sanitize with an allowlist.
#if defined(__linux__)
// On Linux, only sanitize when this exec actually gained privilege (e.g. via

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

per the other pr, when I hear "sanitize" I think about clang's sanitizers, for example. We could still use the term if we qualified it, e.g., "only sanitize the environment when..." and "should_sanitize_env" for example.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahhh yeah okay that makes sense

const bool should_sanitize = getauxval(AT_SECURE) != 0;
#else
// Elsewhere we can't rely on AT_SECURE, so sanitize unconditionally.
const bool should_sanitize = true;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think declaring in both branches of the #if will buy you much versus setting the default as non-const and then re-set it, then eliminate the #else branch.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fair enough

const bool should_sanitize = true;
#endif
if (should_sanitize) {
CF_EXPECTF(clearenv() == 0, "Couldn't clear environment: {}",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're hosing the environment here, but we're not saving and restoring it. Maybe it's sufficient to just force PATH?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i was thinking of it from the other way around, more or less "clear out everything we don't need, since we are conferring higher privileges on anyone running this code" rather than "clear out the thing that looks unsafe"

@3405691582 3405691582 Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage expectation semantically is that in the scoped block, we have elevated privileges, and then we drop them outside the block. This is not the case with the environment: we hose the environment and never restore them.

Maybe fixing the behavior is more trouble than it's worth, but we should at least signpost this.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, i see what you mean now. it could go either way; we could add some machinery to restore the environment that we cleared, though on the other hand i also do think it is defensible for elevating to not restore you to your old state if it was not clearly secure, for example. especially when we are aren't using any of that environment.

but yes 100% that behaviour should be documented in a comment, you're right

}

namespace {
constexpr char kTrustedPath[] = "/usr/sbin:/usr/bin:/sbin:/bin";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this has a small possibility for future portability weirdness, but it's probably fine for now.)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you mean if we try executing anything else from inside that binary?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other platforms may not have the necessary binaries in these exact locations.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh i missed the most important word in that sentence (portability)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants