mirror of
https://github.com/google/sandboxed-api.git
synced 2024-03-22 13:11:30 +08:00
78824353d1
This makes the class more ergonomic because * You don't have to heap allocate the builder. * You can create a policy builder "template" and re-use it across sandboxes to avoid repetitive work. PiperOrigin-RevId: 273555679 Change-Id: I4084ee9c74f95ebfde873eb0dc021b3b3cdc5ea2
546 lines
19 KiB
C++
546 lines
19 KiB
C++
// Copyright 2019 Google LLC. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#ifndef SANDBOXED_API_SANDBOX2_POLICYBUILDER_H_
|
|
#define SANDBOXED_API_SANDBOX2_POLICYBUILDER_H_
|
|
|
|
#include <linux/filter.h>
|
|
|
|
#include <cstddef>
|
|
#include <functional>
|
|
#include <initializer_list>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <string>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
#include <glog/logging.h>
|
|
#include "absl/base/macros.h"
|
|
#include "absl/memory/memory.h"
|
|
#include "absl/strings/string_view.h"
|
|
#include "sandboxed_api/sandbox2/mounts.h"
|
|
#include "sandboxed_api/sandbox2/policy.h"
|
|
#include "sandboxed_api/util/statusor.h"
|
|
|
|
struct bpf_labels;
|
|
|
|
namespace sandbox2 {
|
|
|
|
constexpr char kDefaultHostname[] = "sandbox2";
|
|
|
|
// PolicyBuilder is a helper class to simplify creation of policies. The builder
|
|
// uses fluent interface for convenience and increased readability of policies.
|
|
//
|
|
// To build a policy you simply create a new builder object, call methods on it
|
|
// specifying what you want and finally call BuildOrDie() to generate you
|
|
// policy.
|
|
//
|
|
// For instance this would generate a simple policy suitable for binaries doing
|
|
// only computations:
|
|
//
|
|
// std::unique_ptr<Policy> policy =
|
|
// PolicyBuilder()
|
|
// .AllowRead()
|
|
// .AllowWrite()
|
|
// .AllowExit()
|
|
// .AllowSystemMalloc()
|
|
// .BuildOrDie();
|
|
//
|
|
// Note that operations are executed in the order they are dictated, though in
|
|
// most cases this has no influence since the operations themselves commute.
|
|
//
|
|
// For instance these two policies are equivalent:
|
|
//
|
|
// auto policy = PolicyBuilder.AllowRead().AllowWrite().BuildOrDie();
|
|
// auto policy = PolicyBuilder.AllowWrite().AllowRead().BuildOrDie();
|
|
//
|
|
// While these two are not:
|
|
//
|
|
// auto policy = PolicyBuilder.AllowRead().BlockSyscallWithErrno(__NR_read, EIO)
|
|
// .BuildOrDie();
|
|
// auto policy = PolicyBuilder.BlockSyscallWithErrno(__NR_read, EIO).AllowRead()
|
|
// .BuildOrDie();
|
|
//
|
|
// In fact the first one is equivalent to:
|
|
//
|
|
// auto policy = PolicyBuilder.AllowRead().BuildOrDie();
|
|
//
|
|
// If you dislike the chained style, is is also possible to write the first
|
|
// example as this:
|
|
//
|
|
// PolicyBuilder builder;
|
|
// builder.AllowRead();
|
|
// builder.AllowWrite();
|
|
// builder.AllowExit();
|
|
// builder.AllowSystemMalloc();
|
|
// auto policy = builder.BuildOrDie();
|
|
//
|
|
// For a more complicated example, see examples/persistent/persistent_sandbox.cc
|
|
class PolicyBuilder final {
|
|
public:
|
|
using BpfInitializer = std::initializer_list<sock_filter>;
|
|
using BpfFunc = const std::function<std::vector<sock_filter>(bpf_labels&)>&;
|
|
using SyscallInitializer = std::initializer_list<unsigned int>;
|
|
|
|
// Appends code to allow a specific syscall
|
|
PolicyBuilder& AllowSyscall(unsigned int num);
|
|
|
|
// Appends code to allow a number of syscalls
|
|
PolicyBuilder& AllowSyscalls(const std::vector<uint32_t>& nums);
|
|
PolicyBuilder& AllowSyscalls(SyscallInitializer nums);
|
|
|
|
// Appends code to block a specific syscall while setting errno to the error
|
|
// given
|
|
PolicyBuilder& BlockSyscallWithErrno(unsigned int num, int error);
|
|
|
|
// Appends code to allow exiting.
|
|
// Allows these syscalls:
|
|
// - exit
|
|
// - exit_group
|
|
PolicyBuilder& AllowExit();
|
|
|
|
// Appends code to allow the scudo version of malloc, free and
|
|
// friends. This should be used in conjunction with namespaces. If scudo
|
|
// options are passed to the sandboxee through an environment variable, access
|
|
// to "/proc/self/environ" will have to be allowed by the policy.
|
|
//
|
|
// Note: This function is tuned towards the secure scudo allocator. If you are
|
|
// using another implementation, this function might not be the most
|
|
// suitable.
|
|
PolicyBuilder& AllowScudoMalloc();
|
|
|
|
// Appends code to allow the system-allocator version of malloc, free and
|
|
// friends.
|
|
//
|
|
// Note: This function is tuned towards the malloc implementation in glibc. If
|
|
// you are using another implementation, this function might not be the
|
|
// most suitable.
|
|
PolicyBuilder& AllowSystemMalloc();
|
|
|
|
// Appends code to allow the tcmalloc version of malloc, free and
|
|
// friends.
|
|
PolicyBuilder& AllowTcMalloc();
|
|
|
|
// Allows system calls typically used by the LLVM sanitizers (address
|
|
// sanitizer, memory sanitizer, and thread sanitizer). This method is
|
|
// intended as a best effort for adding system calls that are common to many
|
|
// binaries. It may not be fully inclusive of all potential system calls for
|
|
// all binaries.
|
|
PolicyBuilder& AllowLlvmSanitizers();
|
|
|
|
// Appends code to allow mmap. Specifically this allows the mmap2 syscall on
|
|
// architectures where this syscalls exist and the mmap syscall on all other
|
|
// architectures.
|
|
//
|
|
// Note: while this function allows the calls, the default policy is run first
|
|
// and it has checks for dangerous flags which can create a violation. See
|
|
// sandbox2/policy.cc for more details.
|
|
PolicyBuilder& AllowMmap();
|
|
|
|
// Appends code to allow calling futex with the given operation.
|
|
PolicyBuilder& AllowFutexOp(int op);
|
|
|
|
// Appends code to allow opening files or directories. Specifically it allows
|
|
// these sycalls:
|
|
//
|
|
// - open
|
|
// - openat
|
|
PolicyBuilder& AllowOpen();
|
|
|
|
// Appends code to allow calling stat, fstat and lstat.
|
|
// Allows these sycalls:
|
|
// - fstat
|
|
// - fstat64
|
|
// - fstatat
|
|
// - fstatat64
|
|
// - fstatfs
|
|
// - fstatfs64
|
|
// - lstat
|
|
// - lstat64
|
|
// - newfstatat
|
|
// - oldfstat
|
|
// - oldlstat
|
|
// - oldstat
|
|
// - stat
|
|
// - stat64
|
|
// - statfs
|
|
// - statfs64
|
|
// - ustat
|
|
PolicyBuilder& AllowStat();
|
|
|
|
// Appends code to the policy to allow reading from file descriptors.
|
|
// Allows these sycalls:
|
|
// - read
|
|
// - readv
|
|
// - preadv
|
|
// - pread64
|
|
PolicyBuilder& AllowRead();
|
|
|
|
// Appends code to the policy to allow writing to file descriptors.
|
|
// Allows these sycalls:
|
|
// - write
|
|
// - writev
|
|
// - pwritev
|
|
// - pwrite64
|
|
PolicyBuilder& AllowWrite();
|
|
|
|
// Appends code to allow reading directories.
|
|
// Allows these sycalls:
|
|
// - getdents
|
|
// - getdents64
|
|
PolicyBuilder& AllowReaddir();
|
|
|
|
// Appends code to allow safe calls to fcntl.
|
|
// Allows these sycalls:
|
|
// - fcntl
|
|
// - fcntl64 (on architectures where it exists)
|
|
//
|
|
// The above are only allowed when the cmd is one of:
|
|
// F_GETFD, F_SETFD, F_GETFL, F_SETFL, F_GETLK, F_SETLKW, F_SETLK,
|
|
// F_DUPFD, F_DUPFD_CLOEXEC
|
|
PolicyBuilder& AllowSafeFcntl();
|
|
|
|
// Appends code to allow creating new processes.
|
|
// Allows these sycalls:
|
|
// - fork
|
|
// - vfork
|
|
// - clone
|
|
//
|
|
// Note: while this function allows the calls, the default policy is run first
|
|
// and it has checks for dangerous flags which can create a violation. See
|
|
// sandbox2/policy.cc for more details.
|
|
PolicyBuilder& AllowFork();
|
|
|
|
// Appends code to allow waiting for processes.
|
|
// Allows these sycalls:
|
|
// - waitpid (on architectures where it exists)
|
|
// - wait4
|
|
PolicyBuilder& AllowWait();
|
|
|
|
// Appends code to allow setting up signal handlers, returning from them, etc.
|
|
// Allows these sycalls:
|
|
// - rt_sigaction
|
|
// - rt_sigreturn
|
|
// - rt_procmask
|
|
// - signal (on architectures where it exists)
|
|
// - sigaction (on architectures where it exists)
|
|
// - sigreturn (on architectures where it exists)
|
|
// - sigprocmask (on architectures where it exists)
|
|
PolicyBuilder& AllowHandleSignals();
|
|
|
|
// Appends code to allow doing the TCGETS ioctl.
|
|
// Allows these sycalls:
|
|
// - ioctl (when the first argument is TCGETS)
|
|
PolicyBuilder& AllowTCGETS();
|
|
|
|
// Appends code to allow to getting the current time.
|
|
// Allows these sycalls:
|
|
// - time
|
|
// - gettimeofday
|
|
// - clock_gettime
|
|
PolicyBuilder& AllowTime();
|
|
|
|
// Appends code to allow sleeping in the current thread.
|
|
// Allow these syscalls:
|
|
// - clock_nanosleep
|
|
// - nanosleep
|
|
PolicyBuilder& AllowSleep();
|
|
|
|
// Appends code to allow getting the uid, euid, gid, etc.
|
|
// - getuid + geteuid + getresuid
|
|
// - getgid + getegid + getresgid
|
|
// - getuid32 + geteuid32 + getresuid32 (on architectures where they exist)
|
|
// - getgid32 + getegid32 + getresgid32 (on architectures where they exist)
|
|
// - getgroups
|
|
PolicyBuilder& AllowGetIDs();
|
|
|
|
// Appends code to allow getting the pid, ppid and tid.
|
|
// Allows these syscalls:
|
|
// - getpid
|
|
// - getppid
|
|
// - gettid
|
|
PolicyBuilder& AllowGetPIDs();
|
|
|
|
// Appends code to allow getting the rlimits.
|
|
// Allows these sycalls:
|
|
// - getrlimit
|
|
// - ugetrlimit (on architectures where it exist)
|
|
PolicyBuilder& AllowGetRlimit();
|
|
|
|
// Appends code to allow setting the rlimits.
|
|
// Allows these sycalls:
|
|
// - setrlimit
|
|
// - usetrlimit (on architectures where it exist)
|
|
PolicyBuilder& AllowSetRlimit();
|
|
|
|
// Appends code to allow reading random bytes.
|
|
// Allows these sycalls:
|
|
// - getrandom (with no flags or GRND_NONBLOCK)
|
|
PolicyBuilder& AllowGetRandom();
|
|
|
|
// Enables syscalls required to use the logging support enabled via
|
|
// Client::SendLogsToSupervisor()
|
|
// Allows the following:
|
|
// - Writes
|
|
// - kill(0, SIGABRT) (for LOG(FATAL))
|
|
// - clock_gettime
|
|
// - gettid
|
|
// - close
|
|
//
|
|
// If you don't use namespaces you should also add this to your policy:
|
|
// - policy->GetFs()->EnableSyscall(__NR_open);
|
|
// - policy->GetFs()->AddRegexpToGreyList("/usr/share/zoneinfo/.*");
|
|
PolicyBuilder& AllowLogForwarding();
|
|
|
|
// Enables the syscalls necessary to start a statically linked binary
|
|
//
|
|
// NOTE: This will call BlockSyscallWithErrno(__NR_readlink, ENOENT). If you
|
|
// do not want readlink blocked, put a different call before this call.
|
|
//
|
|
// The current list of allowed syscalls are below. However you should *not*
|
|
// depend on the specifics, as these will change whenever the startup code
|
|
// changes.
|
|
//
|
|
// - uname,
|
|
// - brk,
|
|
// - set_tid_address,
|
|
// - set_robust_list,
|
|
// - futex(FUTEX_WAIT_BITSET, ...)
|
|
// - rt_sigaction(0x20, ...)
|
|
// - rt_sigaction(0x21, ...)
|
|
// - rt_sigprocmask(SIG_UNBLOCK, ...)
|
|
// - arch_prctl(ARCH_SET_FS)
|
|
//
|
|
// Additionally it will block calls to readlink.
|
|
PolicyBuilder& AllowStaticStartup();
|
|
|
|
// In addition to syscalls allowed by AllowStaticStartup, also allow reading,
|
|
// seeking, mmapping and closing files. It does not allow opening them, as
|
|
// the mechanism for doing so depends on whether GetFs-checks are used or not.
|
|
PolicyBuilder& AllowDynamicStartup();
|
|
|
|
// Appends a policy, which will be run on the specified syscall.
|
|
// This policy must be written without labels. If you need labels, use the
|
|
// next function.
|
|
PolicyBuilder& AddPolicyOnSyscall(unsigned int num, BpfInitializer policy);
|
|
PolicyBuilder& AddPolicyOnSyscall(unsigned int num,
|
|
const std::vector<sock_filter>& policy);
|
|
|
|
// Appends a policy, which will be run on the specified syscall.
|
|
// This policy may use labels.
|
|
// Example of how to use it:
|
|
// builder.AddPolicyOnSyscall(
|
|
// __NR_socket, [](bpf_labels& labels) -> std::vector<sock_filter> {
|
|
// return {
|
|
// ARG(0), // domain is first argument of socket
|
|
// JEQ(AF_UNIX, JUMP(&labels, af_unix)),
|
|
// JEQ(AF_NETLINK, JUMP(&labels, af_netlink)),
|
|
// KILL,
|
|
//
|
|
// LABEL(&labels, af_unix),
|
|
// ARG(1),
|
|
// JEQ(SOCK_STREAM | SOCK_NONBLOCK, ALLOW),
|
|
// KILL,
|
|
//
|
|
// LABEL(&labels, af_netlink),
|
|
// ARG(2),
|
|
// JEQ(NETLINK_ROUTE, ALLOW),
|
|
// };
|
|
// });
|
|
PolicyBuilder& AddPolicyOnSyscall(unsigned int num, BpfFunc f);
|
|
|
|
// Appends a policy, which will be run on the specified syscalls.
|
|
// This policy must be written without labels.
|
|
PolicyBuilder& AddPolicyOnSyscalls(SyscallInitializer nums,
|
|
BpfInitializer policy);
|
|
PolicyBuilder& AddPolicyOnSyscalls(SyscallInitializer nums,
|
|
const std::vector<sock_filter>& policy);
|
|
|
|
// Appends a policy, which will be run on the specified syscalls.
|
|
// This policy may use labels.
|
|
PolicyBuilder& AddPolicyOnSyscalls(SyscallInitializer nums, BpfFunc f);
|
|
|
|
// Equivalent to AddPolicyOnSyscall(mmap_syscall_no, policy), where
|
|
// mmap_syscall_no is either __NR_mmap or __NR_mmap2.
|
|
PolicyBuilder& AddPolicyOnMmap(BpfInitializer policy);
|
|
PolicyBuilder& AddPolicyOnMmap(const std::vector<sock_filter>& policy);
|
|
|
|
// Equivalent to AddPolicyOnSyscall(mmap_syscall_no, f), where
|
|
// mmap_syscall_no is either __NR_mmap or __NR_mmap2.
|
|
PolicyBuilder& AddPolicyOnMmap(BpfFunc f);
|
|
|
|
// Builds the policy returning a unique_ptr to it. This should only be called
|
|
// once.
|
|
sapi::StatusOr<std::unique_ptr<Policy>> TryBuild();
|
|
|
|
// Builds the policy returning a unique_ptr to it. This should only be called
|
|
// once.
|
|
// This function will abort if an error happened in any off the PolicyBuilder
|
|
// methods.
|
|
std::unique_ptr<Policy> BuildOrDie() { return TryBuild().ValueOrDie(); }
|
|
|
|
// Adds a bind-mount for a file from outside the namespace to inside. This
|
|
// will also create parent directories inside the namespace if needed.
|
|
//
|
|
// Calling these function will enable use of namespaces.
|
|
PolicyBuilder& AddFile(absl::string_view path, bool is_ro = true);
|
|
PolicyBuilder& AddFileAt(absl::string_view outside, absl::string_view inside,
|
|
bool is_ro = true);
|
|
|
|
// Best-effort function that adds the libraries and linker required by a
|
|
// binary.
|
|
//
|
|
// This does not add the binary itself, only the libraries it depends on.
|
|
//
|
|
// This function should work correctly for most binaries, but you might need
|
|
// to tweak it in some cases.
|
|
//
|
|
// This function is safe even for untrusted/potentially malicious binaries.
|
|
// It adds libraries only from standard library dirs and ld_library_path.
|
|
//
|
|
// run `ldd` yourself and use AddFile or AddDirectory.
|
|
PolicyBuilder& AddLibrariesForBinary(absl::string_view path,
|
|
absl::string_view ld_library_path = {});
|
|
|
|
// Similar to AddLibrariesForBinary, but binary is specified with an open fd.
|
|
PolicyBuilder& AddLibrariesForBinary(int fd,
|
|
absl::string_view ld_library_path = {});
|
|
|
|
// Adds a bind-mount for a directory from outside the namespace to
|
|
// inside. This will also create parent directories inside the namespace if
|
|
// needed.
|
|
//
|
|
// Calling these function will enable use of namespaces.
|
|
PolicyBuilder& AddDirectory(absl::string_view path, bool is_ro = true);
|
|
PolicyBuilder& AddDirectoryAt(absl::string_view outside,
|
|
absl::string_view inside, bool is_ro = true);
|
|
|
|
// Adds a tmpfs inside the namespace. This will also create parent
|
|
// directories inside the namespace if needed.
|
|
//
|
|
// Calling this function will enable use of namespaces.
|
|
PolicyBuilder& AddTmpfs(absl::string_view inside,
|
|
size_t sz = 4 << 20 /* 4MiB */);
|
|
|
|
// Allows unrestricted access to the network by *not* creating a network
|
|
// namespace. Note that this only disables the network namespace. To actually
|
|
// allow networking, you would also need to allow networking syscalls.
|
|
// Calling this function will enable use of namespaces.
|
|
PolicyBuilder& AllowUnrestrictedNetworking();
|
|
|
|
// Enables the use of namespaces.
|
|
//
|
|
// Namespaces are enabled by default.
|
|
// This is a no-op.
|
|
ABSL_DEPRECATED("Namespaces are enabled by default; no need to call this")
|
|
PolicyBuilder& EnableNamespaces() {
|
|
CHECK(use_namespaces_) << "Namespaces cannot be both disabled and enabled";
|
|
requires_namespaces_ = true;
|
|
return *this;
|
|
}
|
|
|
|
// Disables the use of namespaces.
|
|
//
|
|
// Call in order to use Sandbox2 without namespaces.
|
|
// This is not recommended.
|
|
PolicyBuilder& DisableNamespaces() {
|
|
CHECK(!requires_namespaces_)
|
|
<< "Namespaces cannot be both disabled and enabled. You're probably "
|
|
"using features that implicitly enable namespaces (SetHostname, "
|
|
"AddFile, AddDirectory, AddDataDependency, AddLibrariesForBinary or "
|
|
"similar)";
|
|
use_namespaces_ = false;
|
|
return *this;
|
|
}
|
|
|
|
// Set hostname in the network namespace instead of default "sandbox2".
|
|
//
|
|
// Calling this function will enable use of namespaces.
|
|
// It is an error to also call AllowUnrestrictedNetworking.
|
|
PolicyBuilder& SetHostname(absl::string_view hostname);
|
|
|
|
// Enables/disables stack trace collection on violations.
|
|
PolicyBuilder& CollectStacktracesOnViolation(bool enable);
|
|
|
|
// Enables/disables stack trace collection on signals (e.g. crashes / killed
|
|
// from a signal).
|
|
PolicyBuilder& CollectStacktracesOnSignal(bool enable);
|
|
|
|
// Enables/disables stack trace collection on hitting a timeout.
|
|
PolicyBuilder& CollectStacktracesOnTimeout(bool enable);
|
|
|
|
// Enables/disables stack trace collection on getting killed by the sandbox
|
|
// monitor / the user.
|
|
PolicyBuilder& CollectStacktracesOnKill(bool enable);
|
|
|
|
// Appends an unconditional ALLOW action for all syscalls.
|
|
// Do not use in environment with untrusted code and/or data, ask
|
|
// sandbox-team@ first if unsure.
|
|
PolicyBuilder& DangerDefaultAllowAll();
|
|
|
|
// Allows syscalls that are necessary for the NetworkProxyClient
|
|
PolicyBuilder& AddNetworkProxyPolicy();
|
|
|
|
// Allows syscalls that are necessary for the NetworkProxyClient and
|
|
// the NetworkProxyHandler
|
|
PolicyBuilder& AddNetworkProxyHandlerPolicy();
|
|
|
|
private:
|
|
friend class PolicyBuilderPeer; // For testing
|
|
friend class StackTracePeer;
|
|
|
|
// Allows a limited version of madvise
|
|
PolicyBuilder& AllowLimitedMadvise();
|
|
|
|
PolicyBuilder& SetMounts(Mounts mounts) {
|
|
mounts_ = std::move(mounts);
|
|
return *this;
|
|
}
|
|
|
|
std::vector<sock_filter> ResolveBpfFunc(BpfFunc f);
|
|
|
|
static sapi::StatusOr<std::string> ValidateAbsolutePath(
|
|
absl::string_view path);
|
|
static sapi::StatusOr<std::string> ValidatePath(absl::string_view path);
|
|
|
|
void StoreDescription(PolicyBuilderDescription* pb_description);
|
|
|
|
Mounts mounts_;
|
|
bool use_namespaces_ = true;
|
|
bool requires_namespaces_ = false;
|
|
bool allow_unrestricted_networking_ = false;
|
|
std::string hostname_ = kDefaultHostname;
|
|
|
|
bool collect_stacktrace_on_violation_ = true;
|
|
bool collect_stacktrace_on_signal_ = true;
|
|
bool collect_stacktrace_on_timeout_ = true;
|
|
bool collect_stacktrace_on_kill_ = false;
|
|
|
|
// Seccomp fields
|
|
std::vector<sock_filter> user_policy_;
|
|
std::set<unsigned int> handled_syscalls_;
|
|
|
|
// Error handling
|
|
sapi::Status last_status_;
|
|
bool already_built_ = false;
|
|
// This function returns a PolicyBuilder so that we can use it in the status
|
|
// macros
|
|
PolicyBuilder& SetError(const sapi::Status& status);
|
|
};
|
|
|
|
} // namespace sandbox2
|
|
|
|
#endif // SANDBOXED_API_SANDBOX2_POLICYBUILDER_H_
|