Safer and more efficient custom syscall policies

Generate syscall jump table without using bpf_helper.
Check that any jump in the user provided policy is within the provided policy.

PiperOrigin-RevId: 409362089
Change-Id: I31493e52cf868e4b184ff79fcb26beeb75f49773
This commit is contained in:
Wiktor Garbacz 2021-11-12 02:44:07 -08:00 committed by Copybara-Service
parent c95837a6c1
commit 26da6e6b0a

View File

@ -25,6 +25,7 @@
#include <csignal>
#include <cstdint>
#include <deque>
#include <utility>
#include "absl/memory/memory.h"
@ -59,6 +60,16 @@ constexpr PolicyBuilder::SyscallInitializer kMmapSyscalls = {
#endif
};
bool CheckBpfBounds(const sock_filter& filter, size_t max_jmp) {
if (BPF_CLASS(filter.code) == BPF_JMP) {
if (BPF_OP(filter.code) == BPF_JA) {
return filter.k <= max_jmp;
}
return filter.jt <= max_jmp && filter.jf <= max_jmp;
}
return true;
}
} // namespace
PolicyBuilder& PolicyBuilder::AllowSyscall(unsigned int num) {
@ -691,36 +702,61 @@ PolicyBuilder& PolicyBuilder::AddPolicyOnSyscall(unsigned int num, BpfFunc f) {
PolicyBuilder& PolicyBuilder::AddPolicyOnSyscalls(
SyscallInitializer nums, const std::vector<sock_filter>& policy) {
auto resolved_policy =
ResolveBpfFunc(
[nums, policy](bpf_labels& labels) -> std::vector<sock_filter> {
std::vector<sock_filter> out;
out.reserve(nums.size() + policy.size());
for (auto num : nums) {
out.insert(out.end(), {SYSCALL(num, JUMP(&labels, do_policy_l))});
}
out.insert(out.end(), {JUMP(&labels, dont_do_policy_l),
LABEL(&labels, do_policy_l)});
for (const auto& filter : policy) {
// Syscall arch is expected as TRACE value
if (filter.code == (BPF_RET | BPF_K) &&
(filter.k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRACE &&
(filter.k & SECCOMP_RET_DATA) != Syscall::GetHostArch()) {
LOG(WARNING)
<< "SANDBOX2_TRACE should be used in policy instead of "
"TRACE(value)";
out.push_back(SANDBOX2_TRACE);
} else {
out.push_back(filter);
}
}
out.push_back(LOAD_SYSCALL_NR);
out.insert(out.end(), {LABEL(&labels, dont_do_policy_l)});
return out;
});
std::deque<sock_filter> out;
// Insert and verify the policy.
out.insert(out.end(), policy.begin(), policy.end());
for (size_t i = 0; i < out.size(); ++i) {
sock_filter& filter = out[i];
const size_t max_jump = out.size() - i - 1;
if (!CheckBpfBounds(filter, max_jump)) {
SetError(absl::InvalidArgumentError("bpf jump out of bounds"));
return *this;
}
// Syscall arch is expected as TRACE value
if (filter.code == (BPF_RET | BPF_K) &&
(filter.k & SECCOMP_RET_ACTION) == SECCOMP_RET_TRACE &&
(filter.k & SECCOMP_RET_DATA) != Syscall::GetHostArch()) {
LOG(WARNING) << "SANDBOX2_TRACE should be used in policy instead of "
"TRACE(value)";
filter = SANDBOX2_TRACE;
}
}
// Pre-/Postcondition: Syscall number loaded into A register
user_policy_.insert(user_policy_.end(), resolved_policy.begin(),
resolved_policy.end());
out.push_back(LOAD_SYSCALL_NR);
if (out.size() > std::numeric_limits<uint32_t>::max()) {
SetError(absl::InvalidArgumentError("syscall policy is too long"));
return *this;
}
// Create jumps for each syscall.
size_t do_policy_loc = out.size();
// Iterate in reverse order and prepend instruction, so that jumps can be
// calculated easily.
constexpr size_t kMaxShortJump = 255;
bool last = true;
for (auto it = std::rbegin(nums); it != std::rend(nums); ++it) {
// If syscall is not matched try with the next one.
uint8_t jf = 0;
// If last syscall on the list does not match skip the policy by jumping
// over it.
if (last) {
if (out.size() > kMaxShortJump) {
out.push_front(BPF_STMT(BPF_JMP + BPF_JA, out.size()));
} else {
jf = out.size();
}
last = false;
}
// Add a helper absolute jump if needed - the policy/last helper jump is
// out of reach of a short jump.
if ((out.size() - do_policy_loc) > kMaxShortJump) {
out.push_front(BPF_STMT(BPF_JMP + BPF_JA, out.size() - policy.size()));
do_policy_loc = out.size();
++jf;
}
uint8_t jt = out.size() - do_policy_loc;
out.push_front(BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, *it, jt, jf));
}
user_policy_.insert(user_policy_.end(), out.begin(), out.end());
return *this;
}