Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions base/cvd/cuttlefish/host/commands/cvdalloc/privilege.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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 {}`",
Expand Down Expand Up @@ -157,4 +162,43 @@ int DropPrivileges(uid_t orig) {
return setuid(orig);
}

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)

} // namespace

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

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

// 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

// 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;

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

#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.

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
24 changes: 23 additions & 1 deletion base/cvd/cuttlefish/host/commands/cvdalloc/privilege.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <sys/types.h>
#ifndef CUTTLEFISH_HOST_COMMANDS_CVDALLOC_PRIVILEGE_H_
#define CUTTLEFISH_HOST_COMMANDS_CVDALLOC_PRIVILEGE_H_

#include <unistd.h>

#include <optional>
#include <string_view>

#include "cuttlefish/result/result_type.h"
Expand All @@ -25,4 +29,22 @@ int BeginElevatedPrivileges();
int DropPrivileges(uid_t orig);
Result<void> ValidateCvdallocBinary(std::string_view path);

class ScopedPrivileges {
public:
static Result<ScopedPrivileges> Elevate();

ScopedPrivileges(ScopedPrivileges&& other) noexcept;
ScopedPrivileges& operator=(ScopedPrivileges&& other) = delete;
ScopedPrivileges(const ScopedPrivileges&) = delete;
ScopedPrivileges& operator=(const ScopedPrivileges&) = delete;
~ScopedPrivileges();

private:
explicit ScopedPrivileges(uid_t orig);

std::optional<uid_t> orig_;
};

} // namespace cuttlefish

#endif // CUTTLEFISH_HOST_COMMANDS_CVDALLOC_PRIVILEGE_H_
Loading