summaryrefslogtreecommitdiffstats
path: root/sandbox
diff options
context:
space:
mode:
Diffstat (limited to 'sandbox')
-rw-r--r--sandbox/linux/seccomp-bpf/sandbox_bpf.h6
-rw-r--r--sandbox/linux/seccomp-bpf/syscall.cc83
-rw-r--r--sandbox/linux/seccomp-bpf/syscall.h104
3 files changed, 125 insertions, 68 deletions
diff --git a/sandbox/linux/seccomp-bpf/sandbox_bpf.h b/sandbox/linux/seccomp-bpf/sandbox_bpf.h
index 5497963..0e781ea 100644
--- a/sandbox/linux/seccomp-bpf/sandbox_bpf.h
+++ b/sandbox/linux/seccomp-bpf/sandbox_bpf.h
@@ -165,6 +165,12 @@
TypeName(); \
TypeName(const TypeName&); \
void operator=(const TypeName&)
+
+template <bool>
+struct CompileAssert {
+};
+#define COMPILE_ASSERT(expr, msg) \
+ typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1]
#endif
#include "sandbox/linux/seccomp-bpf/die.h"
diff --git a/sandbox/linux/seccomp-bpf/syscall.cc b/sandbox/linux/seccomp-bpf/syscall.cc
index 9471c86..fe056d4 100644
--- a/sandbox/linux/seccomp-bpf/syscall.cc
+++ b/sandbox/linux/seccomp-bpf/syscall.cc
@@ -5,7 +5,6 @@
#include <asm/unistd.h>
#include <bits/wordsize.h>
#include <errno.h>
-#include <stdarg.h>
#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
#include "sandbox/linux/seccomp-bpf/syscall.h"
@@ -173,70 +172,20 @@ namespace playground2 {
#endif
); // asm
-#if defined(ADDRESS_SANITIZER)
-// ASAN will complain because we look at 6 arguments on the stack no matter
-// what. This is probably ok for a debugging feature, see crbug.com/162925.
-__attribute__((no_address_safety_analysis))
-#endif
-intptr_t SandboxSyscall(int nr, ...) {
- // It is most convenient for the caller to pass a variadic list of arguments.
- // But this is difficult to handle in assembly code without making
- // assumptions about internal implementation details of "va_list". So, we
- // first use C code to copy all the arguments into an array, where they are
- // easily accessible to asm().
- // This is preferable over copying them into individual variables, which
- // can result in too much register pressure.
- if (sizeof(void *)*8 != __WORDSIZE) {
- SANDBOX_DIE("This can't happen! "
- "__WORDSIZE doesn't agree with actual size");
- }
- void *args[6];
- va_list ap;
+intptr_t SandboxSyscall(int nr,
+ intptr_t p0, intptr_t p1, intptr_t p2,
+ intptr_t p3, intptr_t p4, intptr_t p5) {
+ // We rely on "intptr_t" to be the exact size as a "void *". This is
+ // typically true, but just in case, we add a check. The language
+ // specification allows platforms some leeway in cases, where
+ // "sizeof(void *)" is not the same as "sizeof(void (*)())". We expect
+ // that this would only be an issue for IA64, which we are currently not
+ // planning on supporting. And it is even possible that this would work
+ // on IA64, but for lack of actual hardware, I cannot test.
+ COMPILE_ASSERT(sizeof(void *) == sizeof(intptr_t),
+ pointer_types_and_intptr_must_be_exactly_the_same_size);
- // System calls take a system call number (typically passed in %eax or
- // %rax) and up to six arguments (passed in general-purpose CPU registers).
- //
- // On 32bit systems, all variadic arguments are passed on the stack as 32bit
- // quantities. We can use an arbitrary 32bit type to retrieve them with
- // va_arg() and then forward them to the kernel in the appropriate CPU
- // register. We do not need to know whether this is an integer or a pointer
- // value.
- //
- // On 64bit systems, variadic arguments can be either 32bit or 64bit wide,
- // which would seem to make it more important that we pass the correct type
- // to va_arg(). And we really can't know what this type is unless we have a
- // table with function signatures for all system calls.
- //
- // Fortunately, on x86-64 this is less critical. The first six function
- // arguments will be passed in CPU registers, no matter whether they were
- // named or variadic. This only leaves us with a single argument (if present)
- // that could be passed on the stack. And since x86-64 is little endian,
- // it will have the correct value both for 32bit and 64bit quantities.
- //
- // N.B. Because of how the x86-64 ABI works, it is possible that 32bit
- // quantities will have undefined garbage bits in the upper 32 bits of a
- // 64bit register. This is relatively unlikely for the first five system
- // call arguments, as the processor does automatic sign extensions and zero
- // filling so frequently, there rarely is garbage in CPU registers. But it
- // is quite likely for the last argument, which is passed on the stack.
- // That's generally OK, because the kernel has the correct function
- // signatures and knows to only inspect the LSB of a 32bit value.
- // But callers must be careful in cases, where the compiler cannot tell
- // the difference (e.g. when passing NULL to any system call, it must
- // always be cast to a pointer type).
- // The glibc implementation of syscall() has the exact same issues.
- // In the unlikely event that this ever becomes a problem, we could add
- // code that handles six-argument system calls specially. The number of
- // system calls that take six arguments and expect a 32bit value in the
- // sixth argument is very limited.
- va_start(ap, nr);
- args[0] = va_arg(ap, void *);
- args[1] = va_arg(ap, void *);
- args[2] = va_arg(ap, void *);
- args[3] = va_arg(ap, void *);
- args[4] = va_arg(ap, void *);
- args[5] = va_arg(ap, void *);
- va_end(ap);
+ const intptr_t args[6] = { p0, p1, p2, p3, p4, p5 };
// Invoke our file-scope assembly code. The constraints have been picked
// carefully to match what the rest of the assembly code expects in input,
@@ -252,9 +201,11 @@ intptr_t SandboxSyscall(int nr, ...) {
#elif defined(__x86_64__)
intptr_t ret = nr;
{
- register void **data __asm__("r12") = args;
+ register const intptr_t *data __asm__("r12") = args;
asm volatile(
+ "lea -128(%%rsp), %%rsp\n" // Avoid red zone.
"call SyscallAsm\n"
+ "lea 128(%%rsp), %%rsp\n"
// N.B. These are not the calling conventions normally used by the ABI.
: "=a"(ret)
: "0"(ret), "r"(data)
@@ -265,7 +216,7 @@ intptr_t SandboxSyscall(int nr, ...) {
intptr_t ret;
{
register intptr_t inout __asm__("r0") = nr;
- register void **data __asm__("r6") = args;
+ register const intptr_t *data __asm__("r6") = args;
asm volatile(
"bl SyscallAsm\n"
// N.B. These are not the calling conventions normally used by the ABI.
diff --git a/sandbox/linux/seccomp-bpf/syscall.h b/sandbox/linux/seccomp-bpf/syscall.h
index 932e398..39b1bca 100644
--- a/sandbox/linux/seccomp-bpf/syscall.h
+++ b/sandbox/linux/seccomp-bpf/syscall.h
@@ -5,7 +5,6 @@
#ifndef SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__
#define SANDBOX_LINUX_SECCOMP_BPF_SYSCALL_H__
-#include <signal.h>
#include <stdint.h>
namespace playground2 {
@@ -16,7 +15,108 @@ namespace playground2 {
// that also b) can be invoked in a way that computes this return address.
// Passing "nr" as "-1" computes the "magic" return address. Passing any
// other value invokes the appropriate system call.
-intptr_t SandboxSyscall(int nr, ...);
+intptr_t SandboxSyscall(int nr,
+ intptr_t p0, intptr_t p1, intptr_t p2,
+ intptr_t p3, intptr_t p4, intptr_t p5);
+
+
+// System calls can take up to six parameters. Traditionally, glibc
+// implements this property by using variadic argument lists. This works, but
+// confuses modern tools such as valgrind, because we are nominally passing
+// uninitialized data whenever we call through this function and pass less
+// than the full six arguments.
+// So, instead, we use C++'s template system to achieve a very similar
+// effect. C++ automatically sets the unused parameters to zero for us, and
+// it also does the correct type expansion (e.g. from 32bit to 64bit) where
+// necessary.
+// We have to use C-style cast operators as we want to be able to accept both
+// integer and pointer types.
+// We explicitly mark all functions as inline. This is not necessary in
+// optimized builds, where the compiler automatically figures out that it
+// can inline everything. But it makes stack traces of unoptimized builds
+// easier to read as it hides implementation details.
+#if __cplusplus >= 201103 // C++11
+
+template<class T0 = intptr_t, class T1 = intptr_t, class T2 = intptr_t,
+ class T3 = intptr_t, class T4 = intptr_t, class T5 = intptr_t>
+inline intptr_t SandboxSyscall(int nr,
+ T0 p0 = 0, T1 p1 = 0, T2 p2 = 0,
+ T3 p3 = 0, T4 p4 = 0, T5 p5 = 0)
+ __attribute__((always_inline));
+
+template<class T0, class T1, class T2, class T3, class T4, class T5>
+inline intptr_t SandboxSyscall(int nr,
+ T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5) {
+ return SandboxSyscall(nr,
+ (intptr_t)p0, (intptr_t)p1, (intptr_t)p2,
+ (intptr_t)p3, (intptr_t)p4, (intptr_t)p5);
+}
+
+#else // Pre-C++11
+
+// TODO(markus): C++11 has a much more concise and readable solution for
+// expressing what we are doing here. Delete the fall-back code for older
+// compilers as soon as we have fully switched to C++11
+
+template<class T0, class T1, class T2, class T3, class T4, class T5>
+inline intptr_t SandboxSyscall(int nr,
+ T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5)
+ __attribute__((always_inline));
+template<class T0, class T1, class T2, class T3, class T4, class T5>
+inline intptr_t SandboxSyscall(int nr,
+ T0 p0, T1 p1, T2 p2, T3 p3, T4 p4, T5 p5) {
+ return SandboxSyscall(nr,
+ (intptr_t)p0, (intptr_t)p1, (intptr_t)p2,
+ (intptr_t)p3, (intptr_t)p4, (intptr_t)p5);
+}
+
+template<class T0, class T1, class T2, class T3, class T4>
+inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4)
+ __attribute__((always_inline));
+template<class T0, class T1, class T2, class T3, class T4>
+inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1, T2 p2, T3 p3, T4 p4) {
+ return SandboxSyscall(nr, p0, p1, p2, p3, p4, 0);
+}
+
+template<class T0, class T1, class T2, class T3>
+inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1, T2 p2, T3 p3)
+ __attribute__((always_inline));
+template<class T0, class T1, class T2, class T3>
+inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1, T2 p2, T3 p3) {
+ return SandboxSyscall(nr, p0, p1, p2, p3, 0, 0);
+}
+
+template<class T0, class T1, class T2>
+inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1, T2 p2)
+ __attribute__((always_inline));
+template<class T0, class T1, class T2>
+inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1, T2 p2) {
+ return SandboxSyscall(nr, p0, p1, p2, 0, 0, 0);
+}
+
+template<class T0, class T1>
+inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1)
+ __attribute__((always_inline));
+template<class T0, class T1>
+inline intptr_t SandboxSyscall(int nr, T0 p0, T1 p1) {
+ return SandboxSyscall(nr, p0, p1, 0, 0, 0, 0);
+}
+
+template<class T0>
+inline intptr_t SandboxSyscall(int nr, T0 p0)
+ __attribute__((always_inline));
+template<class T0>
+inline intptr_t SandboxSyscall(int nr, T0 p0) {
+ return SandboxSyscall(nr, p0, 0, 0, 0, 0, 0);
+}
+
+inline intptr_t SandboxSyscall(int nr)
+ __attribute__((always_inline));
+inline intptr_t SandboxSyscall(int nr) {
+ return SandboxSyscall(nr, 0, 0, 0, 0, 0, 0);
+}
+
+#endif // Pre-C++11
} // namespace