blob: f980ee33b619dfe214e444630cc193ed49419e4a [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; c-basic-offset: 2; indent-tabs-mode: nil; -*- */
#include "SeccompFilterRewriter.h"
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <algorithm>
#include "AddressSpace.h"
#include "AutoRemoteSyscalls.h"
#include "RecordTask.h"
#include "Registers.h"
#include "ThreadGroup.h"
#include "kernel_abi.h"
#include "log.h"
#include "seccomp-bpf.h"
using namespace std;
namespace rr {
static void set_syscall_result(RecordTask* t, long ret) {
Registers r = t->regs();
r.set_syscall_result(ret);
t->set_regs(r);
}
static void pass_through_seccomp_filter(RecordTask* t) {
long ret;
{
AutoRemoteSyscalls remote(t);
ret = remote.syscall(t->regs().original_syscallno(), t->regs().orig_arg1(),
t->regs().arg2(), t->regs().arg3());
}
set_syscall_result(t, ret);
ASSERT(t, t->regs().syscall_failed());
}
template <typename Arch>
static void install_patched_seccomp_filter_arch(
RecordTask* t, unordered_map<uint32_t, uint16_t>& result_to_index,
vector<uint32_t>& index_to_result) {
// Take advantage of the fact that the filter program is arg3() in both
// prctl and seccomp syscalls.
bool ok = true;
auto prog =
t->read_mem(remote_ptr<typename Arch::sock_fprog>(t->regs().arg3()), &ok);
if (!ok) {
// We'll probably return EFAULT but a kernel that doesn't support
// seccomp(2) should return ENOSYS instead, so just run the original
// system call to get the correct error.
pass_through_seccomp_filter(t);
return;
}
auto code = t->read_mem(prog.filter.rptr(), prog.len, &ok);
if (!ok) {
pass_through_seccomp_filter(t);
return;
}
// Convert all returns to TRACE returns so that rr can handle them.
// See handle_ptrace_event in RecordSession.
for (auto& u : code) {
if (BPF_CLASS(u.code) == BPF_RET) {
ASSERT(t, BPF_RVAL(u.code) == BPF_K)
<< "seccomp-bpf program uses BPF_RET with A/X register, not "
"supported";
if (u.k != SECCOMP_RET_ALLOW) {
if (result_to_index.find(u.k) == result_to_index.end()) {
ASSERT(t,
SeccompFilterRewriter::BASE_CUSTOM_DATA +
index_to_result.size() <
SECCOMP_RET_DATA)
<< "Too many distinct constants used in seccomp-bpf programs";
result_to_index[u.k] = index_to_result.size();
index_to_result.push_back(u.k);
}
u.k = (SeccompFilterRewriter::BASE_CUSTOM_DATA + result_to_index[u.k]) |
SECCOMP_RET_TRACE;
}
}
}
SeccompFilter<typename Arch::sock_filter> f;
for (auto& e : AddressSpace::rr_page_syscalls()) {
if (e.privileged == AddressSpace::PRIVILEGED) {
auto ip = AddressSpace::rr_page_syscall_exit_point(e.traced, e.privileged,
e.enabled,
Arch::arch());
f.allow_syscalls_from_callsite(ip);
}
}
f.filters.insert(f.filters.end(), code.begin(), code.end());
long ret;
{
AutoRemoteSyscalls remote(t);
AutoRestoreMem mem(
remote, nullptr,
sizeof(prog) + f.filters.size() * sizeof(typename Arch::sock_filter));
auto code_ptr = mem.get().cast<typename Arch::sock_filter>();
t->write_mem(code_ptr, f.filters.data(), f.filters.size());
prog.len = f.filters.size();
prog.filter = code_ptr;
auto prog_ptr = remote_ptr<void>(code_ptr + f.filters.size())
.cast<typename Arch::sock_fprog>();
t->write_mem(prog_ptr, prog);
ret = remote.syscall(t->regs().original_syscallno(), t->regs().orig_arg1(),
t->regs().arg2(), prog_ptr);
}
set_syscall_result(t, ret);
if (!t->regs().syscall_failed()) {
if (is_seccomp_syscall(t->regs().original_syscallno(), t->arch()) &&
(t->regs().arg2() & SECCOMP_FILTER_FLAG_TSYNC)) {
for (Task* tt : t->thread_group()->task_set()) {
static_cast<RecordTask*>(tt)->prctl_seccomp_status = 2;
}
} else {
t->prctl_seccomp_status = 2;
}
}
}
void SeccompFilterRewriter::install_patched_seccomp_filter(RecordTask* t) {
RR_ARCH_FUNCTION(install_patched_seccomp_filter_arch, t->arch(), t,
result_to_index, index_to_result);
}
bool SeccompFilterRewriter::map_filter_data_to_real_result(RecordTask* t,
uint16_t value,
uint32_t* result) {
if (value < BASE_CUSTOM_DATA) {
return false;
}
ASSERT(t, value < BASE_CUSTOM_DATA + index_to_result.size());
*result = index_to_result[value - BASE_CUSTOM_DATA];
return true;
}
} // namespace rr