-
Notifications
You must be signed in to change notification settings - Fork 245
Add ScopedPrivileges RAII and sanitize env on elevation #3138
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,20 +15,25 @@ | |
| */ | ||
| #include "cuttlefish/host/commands/cvdalloc/privilege.h" | ||
|
|
||
| #include <elf.h> | ||
| #include <errno.h> | ||
| #include <fcntl.h> | ||
| #include <stddef.h> | ||
| #include <stdlib.h> | ||
| #include <unistd.h> | ||
| #if defined(__linux__) | ||
| #include <linux/capability.h> | ||
| #include <linux/prctl.h> | ||
| #include <linux/xattr.h> | ||
| #include <sys/auxv.h> | ||
| #include <sys/prctl.h> | ||
| #include <sys/syscall.h> | ||
| #include <sys/types.h> | ||
| #include <sys/xattr.h> | ||
| #endif | ||
|
|
||
| #include <optional> | ||
| #include <string_view> | ||
| #include <utility> | ||
|
|
||
| #include "absl/log/log.h" | ||
|
|
||
|
|
@@ -95,10 +100,10 @@ Result<void> ValidateCvdallocBinary(std::string_view path) { | |
| #if defined(__linux__) | ||
| (void)st; | ||
| /* Try and determine if the cvdalloc binary has any capabilities. */ | ||
| struct vfs_cap_data cap; | ||
| struct vfs_cap_data cap = {}; | ||
| ssize_t s = getxattr(path.data(), XATTR_NAME_CAPS, &cap, sizeof(cap)); | ||
| CF_EXPECTF( | ||
| s != 1 && (cap.data[0].permitted & (1 << CAP_NET_ADMIN)) != 0, | ||
| s != -1 && (cap.data[0].permitted & (1 << CAP_NET_ADMIN)) != 0, | ||
| "cvdalloc binary does not have permissions to allocate resources.\n" | ||
| "As root, please\n\n setcap cap_net_admin,cap_net_bind_service," | ||
| "cap_net_raw=+ep `realpath {}`", | ||
|
|
@@ -157,4 +162,43 @@ int DropPrivileges(uid_t orig) { | |
| return setuid(orig); | ||
| } | ||
|
|
||
| namespace { | ||
| constexpr char kTrustedPath[] = "/usr/sbin:/usr/bin:/sbin:/bin"; | ||
| } // namespace | ||
|
|
||
| Result<ScopedPrivileges> ScopedPrivileges::Elevate() { | ||
| uid_t orig = getuid(); | ||
| // The child processes we exec run with elevated privilege (CAP_NET_ADMIN via | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we set this?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i'm not sure we can with ambient caps |
||
| // environment for us. Sanitize with an allowlist. | ||
| #if defined(__linux__) | ||
| // On Linux, only sanitize when this exec actually gained privilege (e.g. via | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ahhh yeah okay that makes sense |
||
| // file caps), as signalled by AT_SECURE. | ||
| 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; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fair enough |
||
| #endif | ||
| if (should_sanitize) { | ||
| CF_EXPECTF(clearenv() == 0, "Couldn't clear environment: {}", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| StrError(errno)); | ||
| CF_EXPECTF(setenv("PATH", kTrustedPath, /*overwrite=*/1) == 0, | ||
| "Couldn't set PATH: {}", StrError(errno)); | ||
| } | ||
| CF_EXPECTF(BeginElevatedPrivileges() != -1, | ||
| "Couldn't elevate permissions: {}", StrError(errno)); | ||
| return ScopedPrivileges(orig); | ||
| } | ||
|
|
||
| ScopedPrivileges::ScopedPrivileges(uid_t orig) : orig_(orig) {} | ||
|
|
||
| ScopedPrivileges::ScopedPrivileges(ScopedPrivileges&& other) noexcept | ||
| : orig_(std::exchange(other.orig_, std::nullopt)) {} | ||
|
|
||
| ScopedPrivileges::~ScopedPrivileges() { | ||
| if (orig_.has_value() && DropPrivileges(*orig_) == -1) { | ||
| LOG(ERROR) << "cvdalloc: couldn't drop privileges: " << StrError(errno); | ||
| } | ||
| } | ||
|
|
||
| } // namespace cuttlefish | ||
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 has a small possibility for future portability weirdness, but it's probably fine for now.)
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.
you mean if we try executing anything else from inside that binary?
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.
other platforms may not have the necessary binaries in these exact locations.
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.
oh i missed the most important word in that sentence (portability)