Android
OpenSSH
SECCOMP
BPF
CTS

SECCOMP と Androidの関係

More than 1 year has passed since last update.

前回 は、brilloでも使われているAndroidの新しいbuild systemに関するまとめでしたが、今回は、Mashmallowから導入されたsanbox mechanismであるSECCOMPがどんなものなのかに迫ります。

CTS for Mashmallow

https://android.googlesource.com/platform/cts/+/marshmallow-dr-dev/tests/tests/os/jni/android_os_cts_OSFeatures.cpp

android_os_cts_OSFeatures.cpp
jboolean android_os_cts_OSFeatures_needsSeccompSupport(JNIEnv*, jobject)
{
#if !defined(ARCH_SUPPORTS_SECCOMP)
    // Seccomp support is only available for ARM, x86, x86_64.
    // This define is controlled by the Android.mk.
    return false;
#endif
    int major;
    int minor;
    struct utsname uts;
    if (uname(&uts) == -1) {
        return false;
    }
    if (sscanf(uts.release, "%d.%d", &major, &minor) != 2) {
        return false;
    }
    // Kernels before 3.8 don't have seccomp
    if ((major < 3) || ((major == 3) && (minor < 8))) {
        return false;
    }
    return true;
}

このようにCTSでテストしているので、kernel version >= 3.8では、SECCOMPが必須になっています。

例えば、brilloでも必要のようです。
https://android.googlesource.com/device/generic/brillo/+/brillo-m7-dev/kconfig/common.config

kconfig/common.config
# Require seccomp-bpf.
CONFIG_SECCOMP=y
CONFIG_SECCOMP_FILTER=y

が必要のようです。

SECCOMPとは何か?

https://en.wikipedia.org/wiki/Seccomp

seccomp (short for secure computing mode) is a computer security facility that provides an application sandboxing mechanism in the Linux kernel; ..snip.. seccomp allows a process to make a one-way transition into a "secure" state where it cannot make any system calls except exit(), sigreturn(), read() and write() to already-open file descriptors. Should it attempt any other system calls, the kernel will terminate the process with SIGKILL. In this sense, it does not virtualize the system's resources but isolates the process from them entirely.
seccompとは短く言うと、secure computing modeで、Linux kernelにおけるapplication sandboxを提供するsecurity機構です。 

seccomp mode is enabled via the prctl(2) system call using the PR_SET_SECCOMP argument, or (since Linux kernel 3.17[2]) via the seccomp(2) system call.[3] seccomp mode used to be enabled by writing to a file, /proc/self/seccomp, but this method was removed in favor of prctl().[4] In some kernel versions, seccomp disables the RDTSC x86 instruction.
seccomp modeはPR_SET_SECOMPを引数を伴ったprctl system call経由で有効になります。なお、kernel 3.17からはseccomp system callでも有効にできます。かつて、seccomp modeは、/proc/self/seccompファイルに書き込むことで有効にできましたが、この方法は、prctl()によって置き換えられました。

Androidのどこでseccompを使っているか?

使っているのは、seccomp bpfのようです。bpfとは、下記のようにBerkeley Packet Filterのことのようです。

seccomp-bpf is an extension to seccomp[6] that allows filtering of system calls using a configurable policy implemented using Berkeley Packet Filter rules. It is used by OpenSSH and vsftpd as well as the Google Chrome/Chromium web browsers on Chrome OS and Linux.[7]
seccomp-bpfとはseccompのextensionで、Berkeley Packet Filter ruleと呼ぶconfig可能なpolicyにより、system callのfilteringを行うものです。OpenSSH、vsftpや、Chrome OSやLinux上でのChrome browserで用いられています。

最近、Androidのsource codeを眺めていて思うのは、@android.comよりも@google.comの人がcommitが多いですし、また、brilloを見て思うのは、Chrome由来のcomponentsが使われていることです。同じような背景から、Androidでもseccompが導入されたのでしょうか。

実際にopengrokしてみると、、、
* chromium-trace
* chrome由来のcrash report用のcomponentであるgoogle-breakpad
* openssh
* strace

で見つかります。

seccomp with google-breakpad

external/google-breakpad/src/build/common.gypi
# Set to 1 to turn on seccomp sandbox by default.
# (Note: this is ignored for official builds.)
'linux_use_seccomp_sandbox%': 0,

となっており、実際には使っていないみたいです。

// Suspends a thread by attaching to it.
static bool SuspendThread(pid_t pid) {
..snip..
#if defined(__i386) || defined(__x86_64)
  // On x86, the stack pointer is NULL or -1, when executing trusted code in
  // the seccomp sandbox. Not only does this cause difficulties down the line
  // when trying to dump the thread's stack, it also results in the minidumps
  // containing information about the trusted threads. This information is
  // generally completely meaningless and just pollutes the minidumps.
  // We thus test the stack pointer and exclude any threads that are part of
  // the seccomp sandbox's trusted code.
  user_regs_struct regs;
  if (sys_ptrace(PTRACE_GETREGS, pid, NULL, &regs) == -1 ||
#if defined(__i386)
      !regs.esp
#elif defined(__x86_64)
      !regs.rsp
#endif
      ) {
    sys_ptrace(PTRACE_DETACH, pid, NULL, NULL);
    return false;
  }
#endif
  return true;

seccompのsandboxで守らてた時には、上のようにstack pointerがNULL or -1にもなるみたいですね。(ARM不明)

openssh

external/openssh/sandbox-seccomp-filter.c
#ifdef SANDBOX_SECCOMP_FILTER
..snip..
#include <linux/filter.h>
#include <linux/seccomp.h>
..snip..
/* Simple helpers to avoid manual errors (but larger BPF programs). */
#define SC_DENY(_nr, _errno) \
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_ ## _nr, 0, 1), \
    BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ERRNO|(_errno))
#define SC_ALLOW(_nr) \
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_ ## _nr, 0, 1), \
    BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
/* Syscall filtering set for preauth. */
static const struct sock_filter preauth_insns[] = {
    /* Ensure the syscall arch convention is as expected. */
    BPF_STMT(BPF_LD+BPF_W+BPF_ABS,
        offsetof(struct seccomp_data, arch)),
    BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, SECCOMP_AUDIT_ARCH, 1, 0),
    BPF_STMT(BPF_RET+BPF_K, SECCOMP_FILTER_FAIL),
    /* Load the syscall number for checking. */
    BPF_STMT(BPF_LD+BPF_W+BPF_ABS,
        offsetof(struct seccomp_data, nr)),
    SC_DENY(open, EACCES),
..snip..
#ifdef __NR_shutdown /* not defined on archs that go via socketcall(2) */
    SC_ALLOW(shutdown),
#endif
..snip..
    BPF_STMT(BPF_RET+BPF_K, SECCOMP_FILTER_FAIL),
};
static const struct sock_fprog preauth_program = {
    .len = (unsigned short)(sizeof(preauth_insns)/sizeof(preauth_insns[0])),
    .filter = (struct sock_filter *)preauth_insns,
};
struct ssh_sandbox {
    pid_t child_pid;
};

..snip..

void
ssh_sandbox_child(struct ssh_sandbox *box)
{
..snip..
    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &preauth_program) == -1)
..snip..
}

こんな感じで使うみたいです。
system call単位なので、selinuxみたいな細やかな制御は難しそうですので、用途が限られそうですが。

strace

system call traceなので、その時の状況をdumpする機能として使っているだけで、strace自体を使っているわけではないです。

external/strace/seccomp.c
..snip..
# include <linux/seccomp.h>
..snip..
#include "xlat/seccomp_ops.h"
#include "xlat/seccomp_filter_flags.h"
..snip..

static void
decode_bpf_code(uint16_t code)
{
    uint16_t i = code & ~BPF_CLASS(code);
    printxval(bpf_class, BPF_CLASS(code), "BPF_???");
    switch (BPF_CLASS(code)) {
        case BPF_LD:
        case BPF_LDX:
            tprints(" | ");
            printxval(bpf_size, BPF_SIZE(code), "BPF_???");
            tprints(" | ");
            printxval(bpf_mode, BPF_MODE(code), "BPF_???");
            break;
..snip..
static void
decode_bpf_stmt(const struct bpf_filter *filter)
{
..snip..
    tprints("BPF_STMT(");
    decode_bpf_code(filter->code);
    tprints(", ");
    if (BPF_CLASS(filter->code) == BPF_RET) {
        unsigned int action = SECCOMP_RET_ACTION & filter->k;
        unsigned int data = filter->k & ~action;
        printxval(seccomp_ret_action, action, "SECCOMP_RET_???");
..snip..

つづく。