Project import
diff --git a/minijail/.clang-format b/minijail/.clang-format new file mode 100644 index 0000000..a3ef233 --- /dev/null +++ b/minijail/.clang-format
@@ -0,0 +1,6 @@ +BasedOnStyle: LLVM +IndentWidth: 8 +UseTab: Always +BreakBeforeBraces: Linux +AllowShortIfStatementsOnASingleLine: false +IndentCaseLabels: false
diff --git a/minijail/Android.mk b/minijail/Android.mk new file mode 100644 index 0000000..d3fadcc --- /dev/null +++ b/minijail/Android.mk
@@ -0,0 +1,315 @@ +# Copyright (C) 2015 The Android Open Source Project +# +# 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. + +LOCAL_PATH := $(call my-dir) + + +# Common variables. +# ========================================================= +libminijailSrcFiles := \ + bpf.c \ + libminijail.c \ + signal_handler.c \ + syscall_filter.c \ + util.c + +hostUnittestSrcFiles := \ + linux-x86/libconstants.gen.c \ + linux-x86/libsyscalls.gen.c + +minijailCommonCFlags := -DHAVE_SECUREBITS_H -Wall -Werror +minijailCommonLibraries := libcap + +# Android devices running kernel version < 3.8 are not required to +# support seccomp. Brillo devices must support seccomp regardless of +# kernel version. +# TODO: remove when no longer supporting kernel versions < 3.8. +ifndef BRILLO +minijailCommonCFlags += -DUSE_SECCOMP_SOFTFAIL +endif + + +# Static library for generated code. +# ========================================================= +include $(CLEAR_VARS) +LOCAL_MODULE := libminijail_generated + +LOCAL_MODULE_CLASS := STATIC_LIBRARIES +generated_sources_dir := $(local-generated-sources-dir) + +my_gen := $(generated_sources_dir)/$(TARGET_ARCH)/libsyscalls.c +# We need the quotes so the shell script treats the following as one argument. +my_cc := "$(lastword $(CLANG)) \ + $(addprefix -I ,$(TARGET_C_INCLUDES)) \ + $(addprefix -isystem ,$(TARGET_C_SYSTEM_INCLUDES)) \ + $(CLANG_TARGET_GLOBAL_CFLAGS)" +$(my_gen): PRIVATE_CC := $(my_cc) +$(my_gen): PRIVATE_CUSTOM_TOOL = $< $(PRIVATE_CC) $@ +$(my_gen): $(LOCAL_PATH)/gen_syscalls.sh + $(transform-generated-source) +$(call include-depfile,$(my_gen).d,$(my_gen)) +LOCAL_GENERATED_SOURCES_$(TARGET_ARCH) += $(my_gen) + +my_gen := $(generated_sources_dir)/$(TARGET_ARCH)/libconstants.c +$(my_gen): PRIVATE_CC := $(my_cc) +$(my_gen): PRIVATE_CUSTOM_TOOL = $< $(PRIVATE_CC) $@ +$(my_gen): $(LOCAL_PATH)/gen_constants.sh + $(transform-generated-source) +$(call include-depfile,$(my_gen).d,$(my_gen)) +LOCAL_GENERATED_SOURCES_$(TARGET_ARCH) += $(my_gen) + +# For processes running in 32-bit compat mode on 64-bit processors. +ifdef TARGET_2ND_ARCH +my_gen := $(generated_sources_dir)/$(TARGET_2ND_ARCH)/libsyscalls.c +my_cc := "$(lastword $(CLANG)) \ + $(addprefix -I ,$($(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_C_INCLUDES)) \ + $(addprefix -isystem ,$($(TARGET_2ND_ARCH_VAR_PREFIX)TARGET_C_SYSTEM_INCLUDES)) \ + $($(TARGET_2ND_ARCH_VAR_PREFIX)CLANG_TARGET_GLOBAL_CFLAGS)" +$(my_gen): PRIVATE_CC := $(my_cc) +$(my_gen): PRIVATE_CUSTOM_TOOL = $< $(PRIVATE_CC) $@ +$(my_gen): $(LOCAL_PATH)/gen_syscalls.sh + $(transform-generated-source) +LOCAL_GENERATED_SOURCES_$(TARGET_2ND_ARCH) += $(my_gen) + +my_gen := $(generated_sources_dir)/$(TARGET_2ND_ARCH)/libconstants.c +$(my_gen): PRIVATE_CC := $(my_cc) +$(my_gen): PRIVATE_CUSTOM_TOOL = $< $(PRIVATE_CC) $@ +$(my_gen): $(LOCAL_PATH)/gen_constants.sh + $(transform-generated-source) +LOCAL_GENERATED_SOURCES_$(TARGET_2ND_ARCH) += $(my_gen) +endif + +LOCAL_CFLAGS := $(minijailCommonCFlags) +LOCAL_CLANG := true +include $(BUILD_STATIC_LIBRARY) + + +# libminijail shared library for target. +# ========================================================= +include $(CLEAR_VARS) +LOCAL_MODULE := libminijail + +LOCAL_CFLAGS := $(minijailCommonCFlags) +LOCAL_CLANG := true +LOCAL_SRC_FILES := $(libminijailSrcFiles) + +LOCAL_STATIC_LIBRARIES := libminijail_generated +LOCAL_SHARED_LIBRARIES := $(minijailCommonLibraries) +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH) +include $(BUILD_SHARED_LIBRARY) + + +# Example ASan-ified libminijail shared library for target. +# Commented out since it's only needed for local debugging. +# ========================================================= +# include $(CLEAR_VARS) +# LOCAL_MODULE := libminijail_asan +# LOCAL_MODULE_TAGS := optional +# +# LOCAL_CFLAGS := $(minijailCommonCFlags) +# LOCAL_CLANG := true +# LOCAL_SANITIZE := address +# LOCAL_MODULE_RELATIVE_PATH := asan +# LOCAL_SRC_FILES := $(libminijailSrcFiles) +# +# LOCAL_STATIC_LIBRARIES := libminijail_generated +# LOCAL_SHARED_LIBRARIES := $(minijailCommonLibraries) +# LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH) +# include $(BUILD_SHARED_LIBRARY) + + +# libminijail static library for target. +# ========================================================= +include $(CLEAR_VARS) +LOCAL_MODULE := libminijail + +LOCAL_CFLAGS := $(minijailCommonCFlags) +LOCAL_CLANG := true +LOCAL_SRC_FILES := $(libminijailSrcFiles) + +LOCAL_WHOLE_STATIC_LIBRARIES := libminijail_generated $(minijailCommonLibraries) +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH) +include $(BUILD_STATIC_LIBRARY) + + +# libminijail native unit tests. Run with: +# adb shell /data/nativetest/libminijail_unittest/libminijail_unittest +# ========================================================= +include $(CLEAR_VARS) +LOCAL_MODULE := libminijail_unittest + +LOCAL_CFLAGS := $(minijailCommonCFlags) +LOCAL_CLANG := true +LOCAL_SRC_FILES := \ + bpf.c \ + libminijail.c \ + libminijail_unittest.c \ + signal_handler.c \ + syscall_filter.c \ + util.c \ + +LOCAL_STATIC_LIBRARIES := libminijail_generated +LOCAL_SHARED_LIBRARIES := $(minijailCommonLibraries) +include $(BUILD_NATIVE_TEST) + + +# libminijail native unit tests using gtest. Run with: +# adb shell /data/nativetest/libminijail_unittest_gtest/libminijail_unittest_gtest +# ========================================================= +include $(CLEAR_VARS) +LOCAL_MODULE := libminijail_unittest_gtest + +LOCAL_CFLAGS := $(minijailCommonCFlags) -Wno-writable-strings +LOCAL_CLANG := true +LOCAL_SRC_FILES := \ + bpf.c \ + libminijail.c \ + signal_handler.c \ + syscall_filter.c \ + util.c \ + libminijail_unittest.cpp \ + +LOCAL_STATIC_LIBRARIES := libminijail_generated +LOCAL_SHARED_LIBRARIES := $(minijailCommonLibraries) +include $(BUILD_NATIVE_TEST) + + +# libminijail native unit tests for the host. Run with: +# out/host/linux-x86/nativetest(64)/libminijail_unittest/libminijail_unittest +# ========================================================= +include $(CLEAR_VARS) +LOCAL_MODULE := libminijail_unittest +LOCAL_MODULE_HOST_OS := linux + +LOCAL_CFLAGS := $(minijailCommonCFlags) -DPRELOADPATH=\"/invalid\" +LOCAL_CLANG := true +LOCAL_SRC_FILES := \ + bpf.c \ + libminijail.c \ + libminijail_unittest.c \ + signal_handler.c \ + syscall_filter.c \ + util.c \ + $(hostUnittestSrcFiles) + +LOCAL_SHARED_LIBRARIES := $(minijailCommonLibraries) +include $(BUILD_HOST_NATIVE_TEST) + + +# Syscall filtering native unit tests. Run with: +# adb shell /data/nativetest/syscall_filter_unittest/syscall_filter_unittest +# ========================================================= +include $(CLEAR_VARS) +LOCAL_MODULE := syscall_filter_unittest + +LOCAL_CFLAGS := $(minijailCommonCFlags) +LOCAL_CLANG := true +LOCAL_SRC_FILES := \ + bpf.c \ + syscall_filter.c \ + syscall_filter_unittest.c \ + util.c \ + +LOCAL_STATIC_LIBRARIES := libminijail_generated +LOCAL_SHARED_LIBRARIES := $(minijailCommonLibraries) +include $(BUILD_NATIVE_TEST) + + +# Syscall filtering native unit tests using gtest. Run with: +# adb shell /data/nativetest/syscall_filter_unittest_gtest/syscall_filter_unittest_gtest +# ========================================================= +include $(CLEAR_VARS) +LOCAL_MODULE := syscall_filter_unittest_gtest + +LOCAL_CFLAGS := $(minijailCommonCFlags) +LOCAL_CLANG := true +LOCAL_SRC_FILES := \ + bpf.c \ + syscall_filter.c \ + util.c \ + syscall_filter_unittest.cpp \ + +LOCAL_STATIC_LIBRARIES := libminijail_generated +LOCAL_SHARED_LIBRARIES := $(minijailCommonLibraries) +include $(BUILD_NATIVE_TEST) + + +# Syscall filtering native unit tests for the host. Run with: +# out/host/linux-x86/nativetest(64)/syscall_filter_unittest/syscall_filter_unittest +# ========================================================= +include $(CLEAR_VARS) +LOCAL_MODULE := syscall_filter_unittest +LOCAL_MODULE_HOST_OS := linux + +LOCAL_CFLAGS := $(minijailCommonCFlags) +LOCAL_CLANG := true +LOCAL_SRC_FILES := \ + bpf.c \ + syscall_filter.c \ + syscall_filter_unittest.c \ + util.c \ + $(hostUnittestSrcFiles) + +LOCAL_SHARED_LIBRARIES := $(minijailCommonLibraries) +include $(BUILD_HOST_NATIVE_TEST) + + +# libminijail_test executable for brillo_Minijail test. +# ========================================================= +include $(CLEAR_VARS) +LOCAL_MODULE := libminijail_test + +LOCAL_CFLAGS := $(minijailCommonCFlags) +LOCAL_CLANG := true +LOCAL_SRC_FILES := \ + test/libminijail_test.cpp + +LOCAL_SHARED_LIBRARIES := libbase libminijail +include $(BUILD_EXECUTABLE) + + +# libminijail usage example. +# ========================================================= +include $(CLEAR_VARS) +LOCAL_MODULE := drop_privs +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS := $(minijailCommonCFlags) +LOCAL_CLANG := true +# Don't build with ASan, but leave commented out for easy local debugging. +# LOCAL_SANITIZE := address +LOCAL_SRC_FILES := \ + examples/drop_privs.cpp + +LOCAL_SHARED_LIBRARIES := libbase libminijail +include $(BUILD_EXECUTABLE) + + +# minijail0 executable. +# This is not currently used on Brillo/Android, +# but it's convenient to be able to build it. +# ========================================================= +include $(CLEAR_VARS) +LOCAL_MODULE := minijail0 +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS := \ + $(minijailCommonCFlags) -Wno-missing-field-initializers \ + -DPRELOADPATH=\"/invalidminijailpreload.so\" +LOCAL_CLANG := true +LOCAL_SRC_FILES := \ + elfparse.c \ + minijail0.c \ + +LOCAL_STATIC_LIBRARIES := libminijail_generated +LOCAL_SHARED_LIBRARIES := $(minijailCommonLibraries) libminijail +include $(BUILD_EXECUTABLE)
diff --git a/minijail/CPPLINT.cfg b/minijail/CPPLINT.cfg new file mode 100644 index 0000000..51ff339 --- /dev/null +++ b/minijail/CPPLINT.cfg
@@ -0,0 +1 @@ +exclude_files=.*
diff --git a/minijail/MODULE_LICENSE_BSD b/minijail/MODULE_LICENSE_BSD new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/minijail/MODULE_LICENSE_BSD
diff --git a/minijail/Makefile b/minijail/Makefile new file mode 100644 index 0000000..25e6494 --- /dev/null +++ b/minijail/Makefile
@@ -0,0 +1,79 @@ +# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +include common.mk + +LIBDIR ?= lib +PRELOADNAME = libminijailpreload.so +PRELOADPATH = \"/$(LIBDIR)/$(PRELOADNAME)\" +CPPFLAGS += -DPRELOADPATH="$(PRELOADPATH)" + +ifneq ($(HAVE_SECUREBITS_H),no) +CPPFLAGS += -DHAVE_SECUREBITS_H +endif +ifneq ($(USE_seccomp),yes) +CPPFLAGS += -DUSE_SECCOMP_SOFTFAIL +endif + +all: CC_BINARY(minijail0) CC_LIBRARY(libminijail.so) \ + CC_LIBRARY(libminijailpreload.so) + +# TODO(jorgelo): convert to TEST(). +tests: CC_BINARY(libminijail_unittest) CC_BINARY(syscall_filter_unittest) + +CC_BINARY(minijail0): LDLIBS += -lcap -ldl +CC_BINARY(minijail0): libconstants.gen.o libsyscalls.gen.o libminijail.o \ + syscall_filter.o signal_handler.o bpf.o util.o elfparse.o minijail0.o +clean: CLEAN(minijail0) + +CC_LIBRARY(libminijail.so): LDLIBS += -lcap +CC_LIBRARY(libminijail.so): libminijail.o syscall_filter.o signal_handler.o \ + bpf.o util.o libconstants.gen.o libsyscalls.gen.o +clean: CLEAN(libminijail.so) + +CC_BINARY(libminijail_unittest): LDLIBS += -lcap +CC_BINARY(libminijail_unittest): libminijail_unittest.o libminijail.o \ + syscall_filter.o signal_handler.o bpf.o util.o libconstants.gen.o \ + libsyscalls.gen.o +clean: CLEAN(libminijail_unittest) + +CC_LIBRARY(libminijailpreload.so): LDLIBS += -lcap -ldl +CC_LIBRARY(libminijailpreload.so): libminijailpreload.o libminijail.o \ + libconstants.gen.o libsyscalls.gen.o syscall_filter.o signal_handler.o \ + bpf.o util.o +clean: CLEAN(libminijailpreload.so) + +CC_BINARY(syscall_filter_unittest): syscall_filter_unittest.o syscall_filter.o \ + bpf.o util.o libconstants.gen.o libsyscalls.gen.o +clean: CLEAN(syscall_filter_unittest) + +libsyscalls.gen.o: CPPFLAGS += -I$(SRC) + +libsyscalls.gen.o.depends: libsyscalls.gen.c + +# Only regenerate libsyscalls.gen.c if the Makefile or header changes. +# NOTE! This will not detect if the file is not appropriate for the target. +# TODO(jorgelo): fix generation when 'CC' env variable is not set. +libsyscalls.gen.c: $(SRC)/Makefile $(SRC)/libsyscalls.h + @printf "Generating target-arch specific $@... " + $(QUIET)$(SRC)/gen_syscalls.sh $@ + @printf "done.\n" +clean: CLEAN(libsyscalls.gen.c) + +$(eval $(call add_object_rules,libsyscalls.gen.o,CC,c,CFLAGS)) + +libconstants.gen.o: CPPFLAGS += -I$(SRC) + +libconstants.gen.o.depends: libconstants.gen.c + +# Only regenerate libconstants.gen.c if the Makefile or header changes. +# NOTE! This will not detect if the file is not appropriate for the target. +# TODO(jorgelo): fix generation when 'CC' env variable is not set. +libconstants.gen.c: $(SRC)/Makefile $(SRC)/libconstants.h + @printf "Generating target-arch specific $@... " + $(QUIET)$(SRC)/gen_constants.sh $@ + @printf "done.\n" +clean: CLEAN(libconstants.gen.c) + +$(eval $(call add_object_rules,libconstants.gen.o,CC,c,CFLAGS))
diff --git a/minijail/NOTICE b/minijail/NOTICE new file mode 100644 index 0000000..b9e779f --- /dev/null +++ b/minijail/NOTICE
@@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/minijail/OWNERS b/minijail/OWNERS new file mode 100644 index 0000000..1b8effe --- /dev/null +++ b/minijail/OWNERS
@@ -0,0 +1,3 @@ +set noparent +jorgelo@chromium.org +wad@chromium.org
diff --git a/minijail/PRESUBMIT.cfg b/minijail/PRESUBMIT.cfg new file mode 100644 index 0000000..17f9b03 --- /dev/null +++ b/minijail/PRESUBMIT.cfg
@@ -0,0 +1,4 @@ +[Hook Overrides] + +# We are using Linux-style indentation with tabs +tab_check: false
diff --git a/minijail/arch.h b/minijail/arch.h new file mode 100644 index 0000000..652f072 --- /dev/null +++ b/minijail/arch.h
@@ -0,0 +1,65 @@ +/* arch.h + * Copyright 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * ARCH_NR #define's. + */ + +#ifndef ARCH_H +#define ARCH_H + +#include <linux/audit.h> + +#if defined(__i386__) +# define ARCH_NR AUDIT_ARCH_I386 +#elif defined(__x86_64__) +# define ARCH_NR AUDIT_ARCH_X86_64 +#elif defined(__arm__) +/* + * <linux/audit.h> includes <linux/elf-em.h>, which does not define EM_ARM. + * <linux/elf.h> only includes <asm/elf.h> if we're in the kernel. + */ +# ifndef EM_ARM +# define EM_ARM 40 +# endif +# define ARCH_NR AUDIT_ARCH_ARM +#elif defined(__aarch64__) +# define ARCH_NR AUDIT_ARCH_AARCH64 +#elif defined(__hppa__) +# define ARCH_NR AUDIT_ARCH_PARISC +#elif defined(__ia64__) +# define ARCH_NR AUDIT_ARCH_IA64 +#elif defined(__mips__) +# if defined(__mips64) +# if defined(__MIPSEB__) +# define ARCH_NR AUDIT_ARCH_MIPS64 +# else +# define ARCH_NR AUDIT_ARCH_MIPSEL64 +# endif +# else +# if defined(__MIPSEB__) +# define ARCH_NR AUDIT_ARCH_MIPS +# else +# define ARCH_NR AUDIT_ARCH_MIPSEL +# endif +# endif +#elif defined(__powerpc64__) +# define ARCH_NR AUDIT_ARCH_PPC64 +#elif defined(__powerpc__) +# define ARCH_NR AUDIT_ARCH_PPC +#elif defined(__s390x__) +# define ARCH_NR AUDIT_ARCH_S390X +#elif defined(__s390__) +# define ARCH_NR AUDIT_ARCH_S390 +#elif defined(__sparc__) +# if defined(__arch64__) +# define AUDIT_ARCH_SPARC64 +# else +# define AUDIT_ARCH_SPARC +# endif +#else +# error "AUDIT_ARCH value unavailable" +#endif + +#endif /* ARCH_H */
diff --git a/minijail/bpf.c b/minijail/bpf.c new file mode 100644 index 0000000..20f2ed0 --- /dev/null +++ b/minijail/bpf.c
@@ -0,0 +1,273 @@ +/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "bpf.h" + +/* Architecture validation. */ +size_t bpf_validate_arch(struct sock_filter *filter) +{ + struct sock_filter *curr_block = filter; + set_bpf_stmt(curr_block++, BPF_LD+BPF_W+BPF_ABS, arch_nr); + set_bpf_jump(curr_block++, + BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, SKIP, NEXT); + set_bpf_ret_kill(curr_block++); + return curr_block - filter; +} + +/* Syscall number eval functions. */ +size_t bpf_allow_syscall(struct sock_filter *filter, int nr) +{ + struct sock_filter *curr_block = filter; + set_bpf_jump(curr_block++, BPF_JMP+BPF_JEQ+BPF_K, nr, NEXT, SKIP); + set_bpf_stmt(curr_block++, BPF_RET+BPF_K, SECCOMP_RET_ALLOW); + return curr_block - filter; +} + +size_t bpf_allow_syscall_args(struct sock_filter *filter, + int nr, unsigned int id) +{ + struct sock_filter *curr_block = filter; + set_bpf_jump(curr_block++, BPF_JMP+BPF_JEQ+BPF_K, nr, NEXT, SKIP); + set_bpf_jump_lbl(curr_block++, id); + return curr_block - filter; +} + +/* Size-aware arg loaders. */ +#if defined(BITS32) +size_t bpf_load_arg(struct sock_filter *filter, int argidx) +{ + set_bpf_stmt(filter, BPF_LD+BPF_W+BPF_ABS, LO_ARG(argidx)); + return 1U; +} +#elif defined(BITS64) +size_t bpf_load_arg(struct sock_filter *filter, int argidx) +{ + struct sock_filter *curr_block = filter; + set_bpf_stmt(curr_block++, BPF_LD+BPF_W+BPF_ABS, LO_ARG(argidx)); + set_bpf_stmt(curr_block++, BPF_ST, 0); /* lo -> M[0] */ + set_bpf_stmt(curr_block++, BPF_LD+BPF_W+BPF_ABS, HI_ARG(argidx)); + set_bpf_stmt(curr_block++, BPF_ST, 1); /* hi -> M[1] */ + return curr_block - filter; +} +#endif + +/* Size-aware equality comparison. */ +size_t bpf_comp_jeq32(struct sock_filter *filter, unsigned long c, + unsigned char jt, unsigned char jf) +{ + unsigned int lo = (unsigned int)(c & 0xFFFFFFFF); + set_bpf_jump(filter, BPF_JMP+BPF_JEQ+BPF_K, lo, jt, jf); + return 1U; +} + +/* + * On 64 bits, we have to do two 32-bit comparisons. + * We jump true when *both* comparisons are true. + */ +#if defined(BITS64) +size_t bpf_comp_jeq64(struct sock_filter *filter, uint64_t c, + unsigned char jt, unsigned char jf) +{ + unsigned int lo = (unsigned int)(c & 0xFFFFFFFF); + unsigned int hi = (unsigned int)(c >> 32); + + struct sock_filter *curr_block = filter; + + /* bpf_load_arg leaves |hi| in A */ + curr_block += bpf_comp_jeq32(curr_block, hi, NEXT, SKIPN(2) + jf); + set_bpf_stmt(curr_block++, BPF_LD+BPF_MEM, 0); /* swap in |lo| */ + curr_block += bpf_comp_jeq32(curr_block, lo, jt, jf); + + return curr_block - filter; +} +#endif + +/* Size-aware bitwise AND. */ +size_t bpf_comp_jset32(struct sock_filter *filter, unsigned long mask, + unsigned char jt, unsigned char jf) +{ + unsigned int mask_lo = (unsigned int)(mask & 0xFFFFFFFF); + set_bpf_jump(filter, BPF_JMP+BPF_JSET+BPF_K, mask_lo, jt, jf); + return 1U; +} + +/* + * On 64 bits, we have to do two 32-bit bitwise ANDs. + * We jump true when *either* bitwise AND is true (non-zero). + */ +#if defined(BITS64) +size_t bpf_comp_jset64(struct sock_filter *filter, uint64_t mask, + unsigned char jt, unsigned char jf) +{ + unsigned int mask_lo = (unsigned int)(mask & 0xFFFFFFFF); + unsigned int mask_hi = (unsigned int)(mask >> 32); + + struct sock_filter *curr_block = filter; + + /* bpf_load_arg leaves |hi| in A */ + curr_block += bpf_comp_jset32(curr_block, mask_hi, SKIPN(2) + jt, NEXT); + set_bpf_stmt(curr_block++, BPF_LD+BPF_MEM, 0); /* swap in |lo| */ + curr_block += bpf_comp_jset32(curr_block, mask_lo, jt, jf); + + return curr_block - filter; +} +#endif + +size_t bpf_arg_comp(struct sock_filter **pfilter, + int op, int argidx, unsigned long c, unsigned int label_id) +{ + struct sock_filter *filter = calloc(BPF_ARG_COMP_LEN + 1, + sizeof(struct sock_filter)); + struct sock_filter *curr_block = filter; + size_t (*comp_function)(struct sock_filter *filter, unsigned long k, + unsigned char jt, unsigned char jf); + int flip = 0; + + /* Load arg */ + curr_block += bpf_load_arg(curr_block, argidx); + + /* Jump type */ + switch (op) { + case EQ: + comp_function = bpf_comp_jeq; + flip = 0; + break; + case NE: + comp_function = bpf_comp_jeq; + flip = 1; + break; + case SET: + comp_function = bpf_comp_jset; + flip = 0; + break; + default: + *pfilter = NULL; + return 0; + } + + /* + * It's easier for the rest of the code to have the true branch + * skip and the false branch fall through. + */ + unsigned char jt = flip ? NEXT : SKIP; + unsigned char jf = flip ? SKIP : NEXT; + curr_block += comp_function(curr_block, c, jt, jf); + curr_block += set_bpf_jump_lbl(curr_block, label_id); + + *pfilter = filter; + return curr_block - filter; +} + +void dump_bpf_filter(struct sock_filter *filter, unsigned short len) +{ + int i = 0; + + printf("len == %d\n", len); + printf("filter:\n"); + for (i = 0; i < len; i++) { + printf("%d: \t{ code=%#x, jt=%u, jf=%u, k=%#x \t}\n", + i, filter[i].code, filter[i].jt, filter[i].jf, filter[i].k); + } +} + +void dump_bpf_prog(struct sock_fprog *fprog) +{ + struct sock_filter *filter = fprog->filter; + unsigned short len = fprog->len; + dump_bpf_filter(filter, len); +} + +int bpf_resolve_jumps(struct bpf_labels *labels, + struct sock_filter *filter, size_t count) +{ + struct sock_filter *begin = filter; + __u8 insn = count - 1; + + if (count < 1) + return -1; + /* + * Walk it once, backwards, to build the label table and do fixups. + * Since backward jumps are disallowed by BPF, this is easy. + */ + for (filter += insn; filter >= begin; --insn, --filter) { + if (filter->code != (BPF_JMP+BPF_JA)) + continue; + switch ((filter->jt<<8)|filter->jf) { + case (JUMP_JT<<8)|JUMP_JF: + if (labels->labels[filter->k].location == 0xffffffff) { + fprintf(stderr, "Unresolved label: '%s'\n", + labels->labels[filter->k].label); + return 1; + } + filter->k = labels->labels[filter->k].location - + (insn + 1); + filter->jt = 0; + filter->jf = 0; + continue; + case (LABEL_JT<<8)|LABEL_JF: + if (labels->labels[filter->k].location != 0xffffffff) { + fprintf(stderr, "Duplicate label use: '%s'\n", + labels->labels[filter->k].label); + return 1; + } + labels->labels[filter->k].location = insn; + filter->k = 0; /* fall through */ + filter->jt = 0; + filter->jf = 0; + continue; + } + } + return 0; +} + +/* Simple lookup table for labels. */ +int bpf_label_id(struct bpf_labels *labels, const char *label) +{ + struct __bpf_label *begin = labels->labels, *end; + int id; + if (labels->count == 0) { + begin->label = strndup(label, MAX_BPF_LABEL_LEN); + if (!begin->label) { + return -1; + } + begin->location = 0xffffffff; + labels->count++; + return 0; + } + end = begin + labels->count; + for (id = 0; begin < end; ++begin, ++id) { + if (!strcmp(label, begin->label)) + return id; + } + begin->label = strndup(label, MAX_BPF_LABEL_LEN); + if (!begin->label) { + return -1; + } + begin->location = 0xffffffff; + labels->count++; + return id; +} + +/* Free label strings. */ +void free_label_strings(struct bpf_labels *labels) +{ + if (labels->count == 0) + return; + + struct __bpf_label *begin = labels->labels, *end; + + end = begin + labels->count; + for (; begin < end; ++begin) { + if (begin->label) + free((void*)(begin->label)); + } + + labels->count = 0; +}
diff --git a/minijail/bpf.h b/minijail/bpf.h new file mode 100644 index 0000000..e4897e2 --- /dev/null +++ b/minijail/bpf.h
@@ -0,0 +1,200 @@ +/* bpf.h + * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Berkeley Packet Filter functions. + */ + +#ifndef BPF_H +#define BPF_H + +#include <asm/bitsperlong.h> /* for __BITS_PER_LONG */ +#include <endian.h> +#include <linux/audit.h> +#include <linux/filter.h> +#include <stddef.h> +#include <sys/user.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#include "arch.h" + +#if __BITS_PER_LONG == 32 || defined(__ILP32__) +#define BITS32 +#elif __BITS_PER_LONG == 64 +#define BITS64 +#endif + +/* Constants for comparison operators. */ +#define MIN_OPERATOR 128 +enum { + EQ = MIN_OPERATOR, + NE, + LT, + LE, + GT, + GE, + SET +}; + +/* + * BPF return values and data structures, + * since they're not yet in the kernel. + */ +#define SECCOMP_RET_KILL 0x00000000U /* kill the task immediately */ +#define SECCOMP_RET_TRAP 0x00030000U /* return SIGSYS */ +#define SECCOMP_RET_ERRNO 0x00050000U /* return -1 and set errno */ +#define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */ + +#define SECCOMP_RET_DATA 0x0000ffffU /* mask for return value */ + +struct seccomp_data { + int nr; + __u32 arch; + __u64 instruction_pointer; + __u64 args[6]; +}; + +#define syscall_nr (offsetof(struct seccomp_data, nr)) +#define arch_nr (offsetof(struct seccomp_data, arch)) + +/* Size-dependent defines. */ +#if defined(BITS32) +/* + * On 32 bits, comparisons take 2 instructions: 1 for loading the argument, + * 1 for the actual comparison. + */ +#define BPF_LOAD_ARG_LEN 1U +#define BPF_COMP_LEN 1U +#define BPF_ARG_COMP_LEN (BPF_LOAD_ARG_LEN + BPF_COMP_LEN) + +#define bpf_comp_jeq bpf_comp_jeq32 +#define bpf_comp_jset bpf_comp_jset32 + +#define LO_ARG(idx) offsetof(struct seccomp_data, args[(idx)]) + +#elif defined(BITS64) +/* + * On 64 bits, comparisons take 7 instructions: 4 for loading the argument, + * and 3 for the actual comparison. + */ +#define BPF_LOAD_ARG_LEN 4U +#define BPF_COMP_LEN 3U +#define BPF_ARG_COMP_LEN (BPF_LOAD_ARG_LEN + BPF_COMP_LEN) + +#define bpf_comp_jeq bpf_comp_jeq64 +#define bpf_comp_jset bpf_comp_jset64 + +/* Ensure that we load the logically correct offset. */ +#if defined(__LITTLE_ENDIAN__) || __BYTE_ORDER == __LITTLE_ENDIAN +#define LO_ARG(idx) offsetof(struct seccomp_data, args[(idx)]) +#define HI_ARG(idx) offsetof(struct seccomp_data, args[(idx)]) + sizeof(__u32) +#else +#error "Unsupported endianness" +#endif + +#else +#error "Unknown bit width" + +#endif + +/* Common jump targets. */ +#define NEXT 0 +#define SKIP 1 +#define SKIPN(_n) (_n) + +/* Support for labels in BPF programs. */ +#define JUMP_JT 0xff +#define JUMP_JF 0xff +#define LABEL_JT 0xfe +#define LABEL_JF 0xfe + +#define MAX_BPF_LABEL_LEN 32 + +#define BPF_LABELS_MAX 256 +struct bpf_labels { + int count; + struct __bpf_label { + const char *label; + unsigned int location; + } labels[BPF_LABELS_MAX]; +}; + +/* BPF instruction manipulation functions and macros. */ +static inline size_t set_bpf_instr(struct sock_filter *instr, + unsigned short code, unsigned int k, + unsigned char jt, unsigned char jf) +{ + instr->code = code; + instr->k = k; + instr->jt = jt; + instr->jf = jf; + return 1U; +} + +#define set_bpf_stmt(_block, _code, _k) \ + set_bpf_instr((_block), (_code), (_k), 0, 0) + +#define set_bpf_jump(_block, _code, _k, _jt, _jf) \ + set_bpf_instr((_block), (_code), (_k), (_jt), (_jf)) + +#define set_bpf_lbl(_block, _lbl_id) \ + set_bpf_jump((_block), BPF_JMP+BPF_JA, (_lbl_id), \ + LABEL_JT, LABEL_JF) + +#define set_bpf_jump_lbl(_block, _lbl_id) \ + set_bpf_jump((_block), BPF_JMP+BPF_JA, (_lbl_id), \ + JUMP_JT, JUMP_JF) + +#define set_bpf_ret_kill(_block) \ + set_bpf_stmt((_block), BPF_RET+BPF_K, SECCOMP_RET_KILL) + +#define set_bpf_ret_trap(_block) \ + set_bpf_stmt((_block), BPF_RET+BPF_K, SECCOMP_RET_TRAP) + +#define set_bpf_ret_errno(_block, _errno) \ + set_bpf_stmt((_block), BPF_RET+BPF_K, \ + SECCOMP_RET_ERRNO | ((_errno) & SECCOMP_RET_DATA)) + +#define set_bpf_ret_allow(_block) \ + set_bpf_stmt((_block), BPF_RET+BPF_K, SECCOMP_RET_ALLOW) + +#define bpf_load_syscall_nr(_filter) \ + set_bpf_stmt((_filter), BPF_LD+BPF_W+BPF_ABS, syscall_nr) + +/* BPF label functions. */ +int bpf_resolve_jumps(struct bpf_labels *labels, + struct sock_filter *filter, size_t count); +int bpf_label_id(struct bpf_labels *labels, const char *label); +void free_label_strings(struct bpf_labels *labels); + +/* BPF helper functions. */ +size_t bpf_load_arg(struct sock_filter *filter, int argidx); +size_t bpf_comp_jeq(struct sock_filter *filter, unsigned long c, + unsigned char jt, unsigned char jf); +size_t bpf_comp_jset(struct sock_filter *filter, unsigned long mask, + unsigned char jt, unsigned char jf); + +/* Functions called by syscall_filter.c */ +#define ARCH_VALIDATION_LEN 3U +#define ALLOW_SYSCALL_LEN 2U + +size_t bpf_arg_comp(struct sock_filter **pfilter, + int op, int argidx, unsigned long c, unsigned int label_id); +size_t bpf_validate_arch(struct sock_filter *filter); +size_t bpf_allow_syscall(struct sock_filter *filter, int nr); +size_t bpf_allow_syscall_args(struct sock_filter *filter, + int nr, unsigned int id); + +/* Debug functions. */ +void dump_bpf_prog(struct sock_fprog *fprog); +void dump_bpf_filter(struct sock_filter *filter, unsigned short len); + +#ifdef __cplusplus +}; /* extern "C" */ +#endif + +#endif /* BPF_H */
diff --git a/minijail/common.mk b/minijail/common.mk new file mode 100644 index 0000000..4e47f2a --- /dev/null +++ b/minijail/common.mk
@@ -0,0 +1,941 @@ +# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. +# +# If this file is part of another source distribution, it's license may be +# stored in LICENSE.makefile or LICENSE.common.mk. +# +# NOTE NOTE NOTE +# The authoritative common.mk is located in: +# https://chromium.googlesource.com/chromiumos/platform2/+/master/common-mk +# Please make all changes there, then copy into place in other repos. +# NOTE NOTE NOTE +# +# This file provides a common architecture for building C/C++ source trees. +# It uses recursive makefile inclusion to create a single make process which +# can be built in the source tree or with the build artifacts placed elsewhere. +# +# It is fully parallelizable for all targets, including static archives. +# +# To use: +# 1. Place common.mk in your top source level +# 2. In your top-level Makefile, place "include common.mk" at the top +# 3. In all subdirectories, create a 'module.mk' file that starts with: +# include common.mk +# And then contains the remainder of your targets. +# 4. All build targets should look like: +# relative/path/target: relative/path/obj.o +# +# See existing makefiles for rule examples. +# +# Exported macros: +# - cc_binary, cxx_binary provide standard compilation steps for binaries +# - cxx_library, cc_library provide standard compilation steps for +# shared objects. +# All of the above optionally take an argument for extra flags. +# - update_archive creates/updates a given .a target +# +# Instead of using the build macros, most users can just use wrapped targets: +# - CXX_BINARY, CC_BINARY, CC_STATIC_BINARY, CXX_STATIC_BINARY +# - CXX_LIBRARY, CC_LIBRARY, CC_STATIC_LIBRARY, CXX_STATIC_LIBRARY +# - E.g., CXX_BINARY(mahbinary): foo.o +# - object.depends targets may be used when a prerequisite is required for an +# object file. Because object files result in multiple build artifacts to +# handle PIC and PIE weirdness. E.g. +# foo.o.depends: generated/dbus.h +# - TEST(binary) or TEST(CXX_BINARY(binary)) may be used as a prerequisite +# for the tests target to trigger an automated test run. +# - CLEAN(file_or_dir) dependency can be added to 'clean'. +# +# If source code is being generated, rules will need to be registered for +# compiling the objects. This can be done by adding one of the following +# to the Makefile: +# - For C source files +# $(eval $(call add_object_rules,sub/dir/gen_a.o sub/dir/b.o,CC,c,CFLAGS)) +# - For C++ source files +# $(eval $(call add_object_rules,sub/dir/gen_a.o sub/dir/b.o,CXX,cc,CXXFLAGS)) +# +# Exported targets meant to have prerequisites added to: +# - all - Your desired targets should be given +# - tests - Any TEST(test_binary) targets should be given +# - FORCE - force the given target to run regardless of changes +# In most cases, using .PHONY is preferred. +# +# Possible command line variables: +# - COLOR=[0|1] to set ANSI color output (default: 1) +# - VERBOSE=[0|1] to hide/show commands (default: 0) +# - MODE=[opt|dbg|profiling] (default: opt) +# opt - Enable optimizations for release builds +# dbg - Turn down optimization for debugging +# profiling - Turn off optimization and turn on profiling/coverage +# support. +# - ARCH=[x86|arm|supported qemu name] (default: from portage or uname -m) +# - SPLITDEBUG=[0|1] splits debug info in target.debug (default: 0) +# If NOSTRIP=1, SPLITDEBUG will never strip the final emitted objects. +# - NOSTRIP=[0|1] determines if binaries are stripped. (default: 1) +# NOSTRIP=0 and MODE=opt will also drop -g from the CFLAGS. +# - VALGRIND=[0|1] runs tests under valgrind (default: 0) +# - OUT=/path/to/builddir puts all output in given path (default: $PWD) +# - VALGRIND_ARGS="" supplies extra memcheck arguments +# +# Per-target(-ish) variable: +# - NEEDS_ROOT=[0|1] allows a TEST() target to run with root. +# Default is 0 unless it is running under QEmu. +# - NEEDS_MOUNTS=[0|1] allows a TEST() target running on QEmu to get +# setup mounts in the $(SYSROOT) +# +# Caveats: +# - Directories or files with spaces in them DO NOT get along with GNU Make. +# If you need them, all uses of dir/notdir/etc will need to have magic +# wrappers. Proceed at risk to your own sanity. +# - External CXXFLAGS and CFLAGS should be passed via the environment since +# this file does not use 'override' to control them. +# - Our version of GNU Make doesn't seem to support the 'private' variable +# annotation, so you can't tag a variable private on a wrapping target. + +# Behavior configuration variables +SPLITDEBUG ?= 0 +NOSTRIP ?= 1 +VALGRIND ?= 0 +COLOR ?= 1 +VERBOSE ?= 0 +MODE ?= opt +CXXEXCEPTIONS ?= 0 +ARCH ?= $(shell uname -m) + +# Put objects in a separate tree based on makefile locations +# This means you can build a tree without touching it: +# make -C $SRCDIR # will create ./build-$(MODE) +# Or +# make -C $SRCDIR OUT=$PWD +# This variable is extended on subdir calls and doesn't need to be re-called. +OUT ?= $(PWD)/ + +# Make OUT now so we can use realpath. +$(shell mkdir -p "$(OUT)") + +# TODO(wad) Relative paths are resolved against SRC and not the calling dir. +# Ensure a command-line supplied OUT has a slash +override OUT := $(realpath $(OUT))/ + +# SRC is not meant to be set by the end user, but during make call relocation. +# $(PWD) != $(CURDIR) all the time. +export SRC ?= $(CURDIR) + +# If BASE_VER is not set, read the libchrome revision number from +# common-mk/BASE_VER file. +ifeq ($(strip $(BASE_VER)),) +BASE_VER := $(shell cat $(SRC)/../common-mk/BASE_VER) +endif +$(info Using BASE_VER=$(BASE_VER)) + +# Re-start in the $(OUT) directory if we're not there. +# We may be invoked using -C or bare and we need to ensure behavior +# is consistent so we check both PWD vs OUT and PWD vs CURDIR. +override RELOCATE_BUILD := 0 +ifneq (${PWD}/,${OUT}) +override RELOCATE_BUILD := 1 +endif +# Make sure we're running with no builtin targets. They cause +# leakage and mayhem! +ifneq (${PWD},${CURDIR}) +override RELOCATE_BUILD := 1 +# If we're run from the build dir, don't let it get cleaned up later. +ifeq (${PWD}/,${OUT}) +$(shell touch "$(PWD)/.dont_delete_on_clean") +endif +endif # ifneq (${PWD},${CURDIR} + +# "Relocate" if we need to restart without implicit rules. +ifeq ($(subst r,,$(MAKEFLAGS)),$(MAKEFLAGS)) +override RELOCATE_BUILD := 1 +endif + +ifeq (${RELOCATE_BUILD},1) +# By default, silence build output. Reused below as well. +QUIET = @ +ifeq ($(VERBOSE),1) + QUIET= +endif + +# This target will override all targets, including prerequisites. To avoid +# calling $(MAKE) once per prereq on the given CMDGOAL, we guard it with a local +# variable. +RUN_ONCE := 0 +MAKECMDGOALS ?= all +# Keep the rules split as newer make does not allow them to be declared +# on the same line. But the way :: rules work, the _all here will also +# invoke the %:: rule while retaining "_all" as the default. +_all:: +%:: + $(if $(filter 0,$(RUN_ONCE)), \ + cd "$(OUT)" && \ + $(MAKE) -r -I "$(SRC)" -f "$(CURDIR)/Makefile" \ + SRC="$(CURDIR)" OUT="$(OUT)" $(foreach g,$(MAKECMDGOALS),"$(g)"),) + $(eval RUN_ONCE := 1) +pass-to-subcall := 1 +endif + +ifeq ($(pass-to-subcall),) + +# Only call MODULE if we're in a submodule +MODULES_LIST := $(filter-out Makefile %.d,$(MAKEFILE_LIST)) +ifeq ($(words $(filter-out Makefile common.mk %.d $(SRC)/Makefile \ + $(SRC)/common.mk,$(MAKEFILE_LIST))),0) + +# All the top-level defines outside of module.mk. + +# +# Helper macros +# + +# Create the directory if it doesn't yet exist. +define auto_mkdir + $(if $(wildcard $(dir $1)),$2,$(QUIET)mkdir -p "$(dir $1)") +endef + +# Creates the actual archive with an index. +# The target $@ must end with .pic.a or .pie.a. +define update_archive + $(call auto_mkdir,$(TARGET_OR_MEMBER)) + $(QUIET)# Create the archive in one step to avoid parallel use accessing it + $(QUIET)# before all the symbols are present. + @$(ECHO) "AR $(subst \ +$(SRC)/,,$(^:.o=$(suffix $(basename $(TARGET_OR_MEMBER))).o)) \ +-> $(subst $(SRC)/,,$(TARGET_OR_MEMBER))" + $(QUIET)$(AR) rcs $(TARGET_OR_MEMBER) \ + $(subst $(SRC)/,,$(^:.o=$(suffix $(basename $(TARGET_OR_MEMBER))).o)) +endef + +# Default compile from objects using pre-requisites but filters out +# subdirs and .d files. +define cc_binary + $(call COMPILE_BINARY_implementation,CC,$(CFLAGS) $(1),$(EXTRA_FLAGS)) +endef + +define cxx_binary + $(call COMPILE_BINARY_implementation,CXX,$(CXXFLAGS) $(1),$(EXTRA_FLAGS)) +endef + +# Default compile from objects using pre-requisites but filters out +# subdirs and .d files. +define cc_library + $(call COMPILE_LIBRARY_implementation,CC,$(CFLAGS) $(1),$(EXTRA_FLAGS)) +endef +define cxx_library + $(call COMPILE_LIBRARY_implementation,CXX,$(CXXFLAGS) $(1),$(EXTRA_FLAGS)) +endef + +# Deletes files silently if they exist. Meant for use in any local +# clean targets. +define silent_rm + $(if $(wildcard $(1)), + $(QUIET)($(ECHO) -n '$(COLOR_RED)CLEANFILE$(COLOR_RESET) ' && \ + $(ECHO) '$(subst $(OUT)/,,$(wildcard $(1)))' && \ + $(RM) $(1) 2>/dev/null) || true,) +endef +define silent_rmdir + $(if $(wildcard $(1)), + $(if $(wildcard $(1)/*), + $(QUIET)# $(1) not empty [$(wildcard $(1)/*)]. Not deleting., + $(QUIET)($(ECHO) -n '$(COLOR_RED)CLEANDIR$(COLOR_RESET) ' && \ + $(ECHO) '$(subst $(OUT)/,,$(wildcard $(1)))' && \ + $(RMDIR) $(1) 2>/dev/null) || true),) +endef + +# +# Default variable values +# + +# Only override toolchain vars if they are from make. +CROSS_COMPILE ?= +define override_var +ifneq ($(filter undefined default,$(origin $1)),) +$1 = $(CROSS_COMPILE)$2 +endif +endef +$(eval $(call override_var,AR,ar)) +$(eval $(call override_var,CC,gcc)) +$(eval $(call override_var,CXX,g++)) +$(eval $(call override_var,OBJCOPY,objcopy)) +$(eval $(call override_var,PKG_CONFIG,pkg-config)) +$(eval $(call override_var,RANLIB,ranlib)) +$(eval $(call override_var,STRIP,strip)) + +RMDIR ?= rmdir +ECHO = /bin/echo -e + +ifeq ($(lastword $(subst /, ,$(CC))),clang) +CDRIVER = clang +else +CDRIVER = gcc +endif + +ifeq ($(lastword $(subst /, ,$(CXX))),clang++) +CXXDRIVER = clang +else +CXXDRIVER = gcc +endif + +# Internal macro to support check_XXX macros below. +# Usage: $(call check_compile, [code], [compiler], [code_type], [c_flags], +# [extra_c_flags], [library_flags], [success_ret], [fail_ret]) +# Return: [success_ret] if compile succeeded, otherwise [fail_ret] +check_compile = $(shell printf '%b\n' $(1) | \ + $($(2)) $($(4)) -x $(3) $(LDFLAGS) $(5) - $(6) -o /dev/null > /dev/null 2>&1 \ + && echo "$(7)" || echo "$(8)") + +# Helper macro to check whether a test program will compile with the specified +# compiler flags. +# Usage: $(call check_compile_cc, [code], [flags], [alternate_flags]) +# Return: [flags] if compile succeeded, otherwise [alternate_flags] +check_compile_cc = $(call check_compile,$(1),CC,c,CFLAGS,$(2),,$(2),$(3)) +check_compile_cxx = $(call check_compile,$(1),CXX,c++,CXXFLAGS,$(2),,$(2),$(3)) + +# Helper macro to check whether a test program will compile with the specified +# libraries. +# Usage: $(call check_compile_cc, [code], [library_flags], [alternate_flags]) +# Return: [library_flags] if compile succeeded, otherwise [alternate_flags] +check_libs_cc = $(call check_compile,$(1),CC,c,CFLAGS,,$(2),$(2),$(3)) +check_libs_cxx = $(call check_compile,$(1),CXX,c++,CXXFLAGS,,$(2),$(2),$(3)) + +# Helper macro to check whether the compiler accepts the specified flags. +# Usage: $(call check_compile_cc, [flags], [alternate_flags]) +# Return: [flags] if compile succeeded, otherwise [alternate_flags] +check_cc = $(call check_compile_cc,'int main() { return 0; }',$(1),$(2)) +check_cxx = $(call check_compile_cxx,'int main() { return 0; }',$(1),$(2)) + +# Choose the stack protector flags based on whats supported by the compiler. +SSP_CFLAGS := $(call check_cc,-fstack-protector-strong) +ifeq ($(SSP_CFLAGS),) + SSP_CFLAGS := $(call check_cc,-fstack-protector-all) +endif + +# To update these from an including Makefile: +# CXXFLAGS += -mahflag # Append to the list +# CXXFLAGS := -mahflag $(CXXFLAGS) # Prepend to the list +# CXXFLAGS := $(filter-out badflag,$(CXXFLAGS)) # Filter out a value +# The same goes for CFLAGS. +COMMON_CFLAGS-gcc := -fvisibility=internal -ggdb3 -Wa,--noexecstack +COMMON_CFLAGS-clang := -fvisibility=hidden -ggdb +COMMON_CFLAGS := -Wall -Werror -fno-strict-aliasing $(SSP_CFLAGS) -O1 -Wformat=2 +CXXFLAGS += $(COMMON_CFLAGS) $(COMMON_CFLAGS-$(CXXDRIVER)) +CFLAGS += $(COMMON_CFLAGS) $(COMMON_CFLAGS-$(CDRIVER)) +CPPFLAGS += -D_FORTIFY_SOURCE=2 + +# Enable large file support. +CPPFLAGS += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE + +# Disable exceptions based on the CXXEXCEPTIONS setting. +ifeq ($(CXXEXCEPTIONS),0) + CXXFLAGS := $(CXXFLAGS) -fno-exceptions -fno-unwind-tables \ + -fno-asynchronous-unwind-tables +endif + +ifeq ($(MODE),opt) + # Up the optimizations. + CFLAGS := $(filter-out -O1,$(CFLAGS)) -O2 + CXXFLAGS := $(filter-out -O1,$(CXXFLAGS)) -O2 + # Only drop -g* if symbols aren't desired. + ifeq ($(NOSTRIP),0) + # TODO: do we want -fomit-frame-pointer on x86? + CFLAGS := $(filter-out -ggdb3,$(CFLAGS)) + CXXFLAGS := $(filter-out -ggdb3,$(CXXFLAGS)) + endif +endif + +ifeq ($(MODE),profiling) + CFLAGS := $(CFLAGS) -O0 -g --coverage + CXXFLAGS := $(CXXFLAGS) -O0 -g --coverage + LDFLAGS := $(LDFLAGS) --coverage +endif + +LDFLAGS := $(LDFLAGS) -Wl,-z,relro -Wl,-z,noexecstack -Wl,-z,now + +# Fancy helpers for color if a prompt is defined +ifeq ($(COLOR),1) +COLOR_RESET = \x1b[0m +COLOR_GREEN = \x1b[32;01m +COLOR_RED = \x1b[31;01m +COLOR_YELLOW = \x1b[33;01m +endif + +# By default, silence build output. +QUIET = @ +ifeq ($(VERBOSE),1) + QUIET= +endif + +# +# Implementation macros for compile helpers above +# + +# Useful for dealing with pie-broken toolchains. +# Call make with PIE=0 to disable default PIE use. +OBJ_PIE_FLAG = -fPIE +COMPILE_PIE_FLAG = -pie +ifeq ($(PIE),0) + OBJ_PIE_FLAG = + COMPILE_PIE_FLAG = +endif + +# Favor member targets first for CXX_BINARY(%) magic. +# And strip out nested members if possible. +LP := ( +RP := ) +TARGET_OR_MEMBER = $(lastword $(subst $(LP), ,$(subst $(RP),,$(or $%,$@)))) + +# Default compile from objects using pre-requisites but filters out +# all non-.o files. +define COMPILE_BINARY_implementation + @$(ECHO) "LD$(1) $(subst $(PWD)/,,$(TARGET_OR_MEMBER))" + $(call auto_mkdir,$(TARGET_OR_MEMBER)) + $(QUIET)$($(1)) $(COMPILE_PIE_FLAGS) -o $(TARGET_OR_MEMBER) \ + $(2) $(LDFLAGS) \ + $(filter %.o %.a,$(^:.o=.pie.o)) \ + $(foreach so,$(filter %.so,$^),-L$(dir $(so)) \ + -l$(patsubst lib%,%,$(basename $(notdir $(so))))) \ + $(LDLIBS) + $(call conditional_strip) + @$(ECHO) -n "BIN " + @$(ECHO) "$(COLOR_GREEN)$(subst $(PWD)/,,$(TARGET_OR_MEMBER))$(COLOR_RESET)" + @$(ECHO) " $(COLOR_YELLOW)-----$(COLOR_RESET)" +endef + +# TODO: add version support extracted from PV environment variable +#ifeq ($(PV),9999) +#$(warning PV=$(PV). If shared object versions matter, please force PV=.) +#endif +# Then add -Wl,-soname,$@.$(PV) ? + +# Default compile from objects using pre-requisites but filters out +# all non-.o values. (Remember to add -L$(OUT) -llib) +COMMA := , +define COMPILE_LIBRARY_implementation + @$(ECHO) "SHARED$(1) $(subst $(PWD)/,,$(TARGET_OR_MEMBER))" + $(call auto_mkdir,$(TARGET_OR_MEMBER)) + $(QUIET)$($(1)) -shared -Wl,-E -o $(TARGET_OR_MEMBER) \ + $(2) $(LDFLAGS) \ + $(if $(filter %.a,$^),-Wl$(COMMA)--whole-archive,) \ + $(filter %.o ,$(^:.o=.pic.o)) \ + $(foreach a,$(filter %.a,$^),-L$(dir $(a)) \ + -l$(patsubst lib%,%,$(basename $(notdir $(a))))) \ + $(foreach so,$(filter %.so,$^),-L$(dir $(so)) \ + -l$(patsubst lib%,%,$(basename $(notdir $(so))))) \ + $(LDLIBS) + $(call conditional_strip) + @$(ECHO) -n "LIB $(COLOR_GREEN)" + @$(ECHO) "$(subst $(PWD)/,,$(TARGET_OR_MEMBER))$(COLOR_RESET)" + @$(ECHO) " $(COLOR_YELLOW)-----$(COLOR_RESET)" +endef + +define conditional_strip + $(if $(filter 0,$(NOSTRIP)),$(call strip_artifact)) +endef + +define strip_artifact + @$(ECHO) "STRIP $(subst $(OUT)/,,$(TARGET_OR_MEMBER))" + $(if $(filter 1,$(SPLITDEBUG)), @$(ECHO) -n "DEBUG "; \ + $(ECHO) "$(COLOR_YELLOW)\ +$(subst $(OUT)/,,$(TARGET_OR_MEMBER)).debug$(COLOR_RESET)") + $(if $(filter 1,$(SPLITDEBUG)), \ + $(QUIET)$(OBJCOPY) --only-keep-debug "$(TARGET_OR_MEMBER)" \ + "$(TARGET_OR_MEMBER).debug") + $(if $(filter-out dbg,$(MODE)),$(QUIET)$(STRIP) --strip-unneeded \ + "$(TARGET_OR_MEMBER)",) +endef + +# +# Global pattern rules +# + +# Below, the archive member syntax is abused to create fancier +# syntactic sugar for recipe authors that avoids needed to know +# subcall options. The downside is that make attempts to look +# into the phony archives for timestamps. This will cause the final +# target to be rebuilt/linked on _every_ call to make even when nothing +# has changed. Until a better way presents itself, we have helpers that +# do the stat check on make's behalf. Dodgy but simple. +define old_or_no_timestamp + $(if $(realpath $%),,$(1)) + $(if $(shell find $^ -cnewer "$%" 2>/dev/null),$(1)) +endef + +define check_deps + $(if $(filter 0,$(words $^)),\ + $(error Missing dependencies or declaration of $@($%)),) +endef + +# Build a cxx target magically +CXX_BINARY(%): + $(call check_deps) + $(call old_or_no_timestamp,$(call cxx_binary)) +clean: CLEAN(CXX_BINARY*) + +CC_BINARY(%): + $(call check_deps) + $(call old_or_no_timestamp,$(call cc_binary)) +clean: CLEAN(CC_BINARY*) + +CXX_STATIC_BINARY(%): + $(call check_deps) + $(call old_or_no_timestamp,$(call cxx_binary,-static)) +clean: CLEAN(CXX_STATIC_BINARY*) + +CC_STATIC_BINARY(%): + $(call check_deps) + $(call old_or_no_timestamp,$(call cc_binary,-static)) +clean: CLEAN(CC_STATIC_BINARY*) + +CXX_LIBRARY(%): + $(call check_deps) + $(call old_or_no_timestamp,$(call cxx_library)) +clean: CLEAN(CXX_LIBRARY*) + +CXX_LIBARY(%): + $(error Typo alert! LIBARY != LIBRARY) + +CC_LIBRARY(%): + $(call check_deps) + $(call old_or_no_timestamp,$(call cc_library)) +clean: CLEAN(CC_LIBRARY*) + +CC_LIBARY(%): + $(error Typo alert! LIBARY != LIBRARY) + +CXX_STATIC_LIBRARY(%): + $(call check_deps) + $(call old_or_no_timestamp,$(call update_archive)) +clean: CLEAN(CXX_STATIC_LIBRARY*) + +CXX_STATIC_LIBARY(%): + $(error Typo alert! LIBARY != LIBRARY) + +CC_STATIC_LIBRARY(%): + $(call check_deps) + $(call old_or_no_timestamp,$(call update_archive)) +clean: CLEAN(CC_STATIC_LIBRARY*) + +CC_STATIC_LIBARY(%): + $(error Typo alert! LIBARY != LIBRARY) + + +TEST(%): % qemu_chroot_install + $(call TEST_implementation) +.PHONY: TEST + +# multiple targets with a wildcard need to share an directory. +# Don't use this directly it just makes sure the directory is removed _after_ +# the files are. +CLEANFILE(%): + $(call silent_rm,$(TARGET_OR_MEMBER)) +.PHONY: CLEANFILE + +CLEAN(%): CLEANFILE(%) + $(QUIET)# CLEAN($%) meta-target called + $(if $(filter-out $(PWD)/,$(dir $(abspath $(TARGET_OR_MEMBER)))), \ + $(call silent_rmdir,$(dir $(abspath $(TARGET_OR_MEMBER)))),\ + $(QUIET)# Not deleting $(dir $(abspath $(TARGET_OR_MEMBER))) yet.) +.PHONY: CLEAN + +# +# Top-level objects and pattern rules +# + +# All objects for .c files at the top level +C_OBJECTS = $(patsubst $(SRC)/%.c,%.o,$(wildcard $(SRC)/*.c)) + + +# All objects for .cxx files at the top level +CXX_OBJECTS = $(patsubst $(SRC)/%.cc,%.o,$(wildcard $(SRC)/*.cc)) + +# Note, the catch-all pattern rules don't work in subdirectories because +# we're building from the $(OUT) directory. At the top-level (here) they will +# work, but we go ahead and match using the module form. Then we can place a +# generic pattern rule to capture leakage from the main Makefile. (Later in the +# file.) +# +# The reason target specific pattern rules work well for modules, +# MODULE_C_OBJECTS, is because it scopes the behavior to the given target which +# ensures we get a relative directory offset from $(OUT) which otherwise would +# not match without further magic on a per-subdirectory basis. + +# Creates object file rules. Call with eval. +# $(1) list of .o files +# $(2) source type (CC or CXX) +# $(3) source suffix (cc or c) +# $(4) compiler flag name (CFLAGS or CXXFLAGS) +# $(5) source dir: _only_ if $(SRC). Leave blank for obj tree. +define add_object_rules +$(patsubst %.o,%.pie.o,$(1)): %.pie.o: $(5)%.$(3) %.o.depends + $$(call auto_mkdir,$$@) + $$(call OBJECT_PATTERN_implementation,$(2),\ + $$(basename $$@),$$($(4)) $$(CPPFLAGS) $$(OBJ_PIE_FLAG)) + +$(patsubst %.o,%.pic.o,$(1)): %.pic.o: $(5)%.$(3) %.o.depends + $$(call auto_mkdir,$$@) + $$(call OBJECT_PATTERN_implementation,$(2),\ + $$(basename $$@),$$($(4)) $$(CPPFLAGS) -fPIC) + +# Placeholder for depends +$(patsubst %.o,%.o.depends,$(1)): + $$(call auto_mkdir,$$@) + $$(QUIET)touch "$$@" + +$(1): %.o: %.pic.o %.pie.o + $$(call auto_mkdir,$$@) + $$(QUIET)touch "$$@" +endef + +define OBJECT_PATTERN_implementation + @$(ECHO) "$(1) $(subst $(SRC)/,,$<) -> $(2).o" + $(call auto_mkdir,$@) + $(QUIET)$($(1)) -c -MD -MF $(2).d $(3) -o $(2).o $< + $(QUIET)# Wrap all the deps in $$(wildcard) so a missing header + $(QUIET)# won't cause weirdness. First we remove newlines and \, + $(QUIET)# then wrap it. + $(QUIET)sed -i -e :j -e '$$!N;s|\\\s*\n| |;tj' \ + -e 's|^\(.*\s*:\s*\)\(.*\)$$|\1 $$\(wildcard \2\)|' $(2).d +endef + +# Now actually register handlers for C(XX)_OBJECTS. +$(eval $(call add_object_rules,$(C_OBJECTS),CC,c,CFLAGS,$(SRC)/)) +$(eval $(call add_object_rules,$(CXX_OBJECTS),CXX,cc,CXXFLAGS,$(SRC)/)) + +# Disable default pattern rules to help avoid leakage. +# These may already be handled by '-r', but let's keep it to be safe. +%: %.o ; +%.a: %.o ; +%.o: %.c ; +%.o: %.cc ; + +# NOTE: A specific rule for archive objects is avoided because parallel +# update of the archive causes build flakiness. +# Instead, just make the objects the prerequisites and use update_archive +# To use the foo.a(obj.o) functionality, targets would need to specify the +# explicit object they expect on the prerequisite line. + +# +# Architecture detection and QEMU wrapping +# + +HOST_ARCH ?= $(shell uname -m) +override ARCH := $(strip $(ARCH)) +override HOST_ARCH := $(strip $(HOST_ARCH)) +# emake will supply "x86" or "arm" for ARCH, but +# if uname -m runs and you get x86_64, then this subst +# will break. +ifeq ($(subst x86,i386,$(ARCH)),i386) + QEMU_ARCH := $(subst x86,i386,$(ARCH)) # x86 -> i386 +else ifeq ($(subst amd64,x86_64,$(ARCH)),x86_64) + QEMU_ARCH := $(subst amd64,x86_64,$(ARCH)) # amd64 -> x86_64 +else + QEMU_ARCH = $(ARCH) +endif +override QEMU_ARCH := $(strip $(QEMU_ARCH)) + +# If we're cross-compiling, try to use qemu for running the tests. +ifneq ($(QEMU_ARCH),$(HOST_ARCH)) + ifeq ($(SYSROOT),) + $(info SYSROOT not defined. qemu-based testing disabled) + else + # A SYSROOT is assumed for QEmu use. + USE_QEMU ?= 1 + + # Allow 64-bit hosts to run 32-bit without qemu. + ifeq ($(HOST_ARCH),x86_64) + ifeq ($(QEMU_ARCH),i386) + USE_QEMU = 0 + endif + endif + endif +else + USE_QEMU ?= 0 +endif + +# Normally we don't need to run as root or do bind mounts, so only +# enable it by default when we're using QEMU. +NEEDS_ROOT ?= $(USE_QEMU) +NEEDS_MOUNTS ?= $(USE_QEMU) + +SYSROOT_OUT = $(OUT) +ifneq ($(SYSROOT),) + SYSROOT_OUT = $(subst $(SYSROOT),,$(OUT)) +else + # Default to / when all the empty-sysroot logic is done. + SYSROOT = / +endif + +QEMU_NAME = qemu-$(QEMU_ARCH) +QEMU_PATH = /build/bin/$(QEMU_NAME) +QEMU_SYSROOT_PATH = $(SYSROOT)$(QEMU_PATH) +QEMU_SRC_PATH = /usr/bin/$(QEMU_NAME) +QEMU_BINFMT_PATH = /proc/sys/fs/binfmt_misc/$(QEMU_NAME) +QEMU_REGISTER_PATH = /proc/sys/fs/binfmt_misc/register + +QEMU_MAGIC_arm = ":$(QEMU_NAME):M::\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/build/bin/qemu-arm:" + + +# +# Output full configuration at top level +# + +# Don't show on clean +ifneq ($(MAKECMDGOALS),clean) + $(info build configuration:) + $(info - OUT=$(OUT)) + $(info - SRC=$(SRC)) + $(info - MODE=$(MODE)) + $(info - SPLITDEBUG=$(SPLITDEBUG)) + $(info - NOSTRIP=$(NOSTRIP)) + $(info - VALGRIND=$(VALGRIND)) + $(info - COLOR=$(COLOR)) + $(info - CXXEXCEPTIONS=$(CXXEXCEPTIONS)) + $(info - ARCH=$(ARCH)) + $(info - QEMU_ARCH=$(QEMU_ARCH)) + $(info - USE_QEMU=$(USE_QEMU)) + $(info - NEEDS_ROOT=$(NEEDS_ROOT)) + $(info - NEEDS_MOUNTS=$(NEEDS_MOUNTS)) + $(info - SYSROOT=$(SYSROOT)) + $(info ) +endif + +# +# Standard targets with detection for when they are improperly configured. +# + +# all does not include tests by default +all: + $(QUIET)(test -z "$^" && \ + $(ECHO) "You must add your targets as 'all' prerequisites") || true + $(QUIET)test -n "$^" + +# Builds and runs tests for the target arch +# Run them in parallel +# After the test have completed, if profiling, run coverage analysis +tests: +ifeq ($(MODE),profiling) + @$(ECHO) "COVERAGE [$(COLOR_YELLOW)STARTED$(COLOR_RESET)]" + $(QUIET)FILES=""; \ + for GCNO in `find . -name "*.gcno"`; do \ + GCDA="$${GCNO%.gcno}.gcda"; \ + if [ -e $${GCDA} ]; then \ + FILES="$${FILES} $${GCDA}"; \ + fi \ + done; \ + if [ -n "$${FILES}" ]; then \ + gcov -l $${FILES}; \ + lcov --capture --directory . \ + --output-file=lcov-coverage.info; \ + genhtml lcov-coverage.info \ + --output-directory lcov-html; \ + fi + @$(ECHO) "COVERAGE [$(COLOR_YELLOW)FINISHED$(COLOR_RESET)]" +endif +.PHONY: tests + +qemu_chroot_install: +ifeq ($(USE_QEMU),1) + $(QUIET)$(ECHO) "QEMU Preparing $(QEMU_NAME)" + @# Copying strategy + @# Compare /usr/bin/qemu inode to /build/$board/build/bin/qemu, if different + @# hard link to a temporary file, then rename temp to target. This should + @# ensure that once $QEMU_SYSROOT_PATH exists it will always exist, regardless + @# of simultaneous test setups. + $(QUIET)if [[ ! -e $(QEMU_SYSROOT_PATH) || \ + `stat -c %i $(QEMU_SRC_PATH)` != `stat -c %i $(QEMU_SYSROOT_PATH)` \ + ]]; then \ + $(ROOT_CMD) ln -Tf $(QEMU_SRC_PATH) $(QEMU_SYSROOT_PATH).$$$$; \ + $(ROOT_CMD) mv -Tf $(QEMU_SYSROOT_PATH).$$$$ $(QEMU_SYSROOT_PATH); \ + fi + + @# Prep the binfmt handler. First mount if needed, then unregister any bad + @# mappings and then register our mapping. + @# There may still be some race conditions here where one script de-registers + @# and another script starts executing before it gets re-registered, however + @# it should be rare. + -$(QUIET)[[ -e $(QEMU_REGISTER_PATH) ]] || \ + $(ROOT_CMD) mount binfmt_misc -t binfmt_misc \ + /proc/sys/fs/binfmt_misc + + -$(QUIET)if [[ -e $(QEMU_BINFMT_PATH) && \ + `awk '$$1 == "interpreter" {print $$NF}' $(QEMU_BINFMT_PATH)` != \ + "$(QEMU_PATH)" ]]; then \ + echo -1 | $(ROOT_CMD) tee $(QEMU_BINFMT_PATH) >/dev/null; \ + fi + + -$(if $(QEMU_MAGIC_$(ARCH)),$(QUIET)[[ -e $(QEMU_BINFMT_PATH) ]] || \ + echo $(QEMU_MAGIC_$(ARCH)) | $(ROOT_CMD) tee $(QEMU_REGISTER_PATH) \ + >/dev/null) +endif +.PHONY: qemu_clean qemu_chroot_install + +# TODO(wad) Move to -L $(SYSROOT) and fakechroot when qemu-user +# doesn't hang traversing /proc from SYSROOT. +SUDO_CMD = sudo +UNSHARE_CMD = unshare +QEMU_CMD = +ROOT_CMD = $(if $(filter 1,$(NEEDS_ROOT)),$(SUDO_CMD) , ) +MOUNT_CMD = $(if $(filter 1,$(NEEDS_MOUNTS)),$(ROOT_CMD) mount, \#) +UMOUNT_CMD = $(if $(filter 1,$(NEEDS_MOUNTS)),$(ROOT_CMD) umount, \#) +QEMU_LDPATH = $(SYSROOT_LDPATH):/lib64:/lib:/usr/lib64:/usr/lib +ROOT_CMD_LDPATH = $(SYSROOT_LDPATH):$(SYSROOT)/lib64: +ROOT_CMD_LDPATH := $(ROOT_CMD_LDPATH):$(SYSROOT)/lib:$(SYSROOT)/usr/lib64: +ROOT_CMD_LDPATH := $(ROOT_CMD_LDPATH):$(SYSROOT)/usr/lib +ifeq ($(USE_QEMU),1) + export QEMU_CMD = \ + $(SUDO_CMD) chroot $(SYSROOT) $(QEMU_PATH) \ + -drop-ld-preload \ + -E LD_LIBRARY_PATH="$(QEMU_LDPATH):$(patsubst $(OUT),,$(LD_DIRS))" \ + -E HOME="$(HOME)" -E SRC="$(SRC)" -- + # USE_QEMU conditional function + define if_qemu + $(1) + endef +else + ROOT_CMD = $(if $(filter 1,$(NEEDS_ROOT)),sudo, ) \ + LD_LIBRARY_PATH="$(ROOT_CMD_LDPATH):$(LD_DIRS)" + define if_qemu + $(2) + endef +endif + +VALGRIND_CMD = +ifeq ($(VALGRIND),1) + VALGRIND_CMD = /usr/bin/valgrind --tool=memcheck $(VALGRIND_ARGS) -- +endif + +define TEST_implementation + $(QUIET)$(call TEST_setup) + $(QUIET)$(call TEST_run) + $(QUIET)$(call TEST_teardown) + $(QUIET)exit $$(cat $(OUT)$(TARGET_OR_MEMBER).status.test) +endef + +define TEST_setup + @$(ECHO) -n "TEST $(TARGET_OR_MEMBER) " + @$(ECHO) "[$(COLOR_YELLOW)SETUP$(COLOR_RESET)]" + $(QUIET)# Setup a target-specific results file + $(QUIET)(echo > $(OUT)$(TARGET_OR_MEMBER).setup.test) + $(QUIET)(echo 1 > $(OUT)$(TARGET_OR_MEMBER).status.test) + $(QUIET)(echo > $(OUT)$(TARGET_OR_MEMBER).cleanup.test) + $(QUIET)# No setup if we are not using QEMU + $(QUIET)# TODO(wad) this is racy until we use a vfs namespace + $(call if_qemu,\ + $(QUIET)(echo "mkdir -p '$(SYSROOT)/proc' '$(SYSROOT)/dev' \ + '$(SYSROOT)/mnt/host/source'" \ + >> "$(OUT)$(TARGET_OR_MEMBER).setup.test")) + $(call if_qemu,\ + $(QUIET)(echo "$(MOUNT_CMD) --bind /mnt/host/source \ + '$(SYSROOT)/mnt/host/source'" \ + >> "$(OUT)$(TARGET_OR_MEMBER).setup.test")) + $(call if_qemu,\ + $(QUIET)(echo "$(MOUNT_CMD) --bind /proc '$(SYSROOT)/proc'" \ + >> "$(OUT)$(TARGET_OR_MEMBER).setup.test")) + $(call if_qemu,\ + $(QUIET)(echo "$(MOUNT_CMD) --bind /dev '$(SYSROOT)/dev'" \ + >> "$(OUT)$(TARGET_OR_MEMBER).setup.test")) +endef + +define TEST_teardown + @$(ECHO) -n "TEST $(TARGET_OR_MEMBER) " + @$(ECHO) "[$(COLOR_YELLOW)TEARDOWN$(COLOR_RESET)]" + $(call if_qemu, $(QUIET)$(SHELL) "$(OUT)$(TARGET_OR_MEMBER).cleanup.test") +endef + +# Use GTEST_ARGS.[arch] if defined. +override GTEST_ARGS.real = \ + $(call if_qemu,$(GTEST_ARGS.qemu.$(QEMU_ARCH)),$(GTEST_ARGS.host.$(HOST_ARCH))) + +define TEST_run + @$(ECHO) -n "TEST $(TARGET_OR_MEMBER) " + @$(ECHO) "[$(COLOR_GREEN)RUN$(COLOR_RESET)]" + $(QUIET)(echo 1 > "$(OUT)$(TARGET_OR_MEMBER).status.test") + $(QUIET)(echo $(ROOT_CMD) SRC="$(SRC)" $(QEMU_CMD) $(VALGRIND_CMD) \ + "$(strip $(call if_qemu, $(SYSROOT_OUT),$(OUT))$(TARGET_OR_MEMBER))" \ + $(if $(filter-out 0,$(words $(GTEST_ARGS.real))),$(GTEST_ARGS.real),\ + $(GTEST_ARGS)) >> "$(OUT)$(TARGET_OR_MEMBER).setup.test") + -$(QUIET)$(call if_qemu,$(SUDO_CMD) $(UNSHARE_CMD) -m) $(SHELL) \ + $(OUT)$(TARGET_OR_MEMBER).setup.test \ + && echo 0 > "$(OUT)$(TARGET_OR_MEMBER).status.test" +endef + +# Recursive list reversal so that we get RMDIR_ON_CLEAN in reverse order. +define reverse +$(if $(1),$(call reverse,$(wordlist 2,$(words $(1)),$(1)))) $(firstword $(1)) +endef + +clean: qemu_clean +clean: CLEAN($(OUT)*.d) CLEAN($(OUT)*.o) CLEAN($(OUT)*.debug) +clean: CLEAN($(OUT)*.test) CLEAN($(OUT)*.depends) +clean: CLEAN($(OUT)*.gcno) CLEAN($(OUT)*.gcda) CLEAN($(OUT)*.gcov) +clean: CLEAN($(OUT)lcov-coverage.info) CLEAN($(OUT)lcov-html) + +clean: + $(QUIET)# Always delete the containing directory last. + $(call silent_rmdir,$(OUT)) + +FORCE: ; +# Empty rule for use when no special targets are needed, like large_tests +NONE: + +.PHONY: clean NONE valgrind NONE +.DEFAULT_GOAL := all +# Don't let make blow away "intermediates" +.PRECIOUS: %.pic.o %.pie.o %.a %.pic.a %.pie.a %.test + +# Start accruing build info +OUT_DIRS = $(OUT) +LD_DIRS = $(OUT) +SRC_DIRS = $(SRC) + +include $(wildcard $(OUT)*.d) +SUBMODULE_DIRS = $(wildcard $(SRC)/*/module.mk) +include $(SUBMODULE_DIRS) + + +else ## In duplicate inclusions of common.mk + +# Get the current inclusion directory without a trailing slash +MODULE := $(patsubst %/,%, \ + $(dir $(lastword $(filter-out %common.mk,$(MAKEFILE_LIST))))) +MODULE := $(subst $(SRC)/,,$(MODULE)) +MODULE_NAME := $(subst /,_,$(MODULE)) +#VPATH := $(MODULE):$(VPATH) + + +# Depth first +$(eval OUT_DIRS += $(OUT)$(MODULE)) +$(eval SRC_DIRS += $(OUT)$(MODULE)) +$(eval LD_DIRS := $(LD_DIRS):$(OUT)$(MODULE)) + +# Add the defaults from this dir to rm_clean +clean: CLEAN($(OUT)$(MODULE)/*.d) CLEAN($(OUT)$(MODULE)/*.o) +clean: CLEAN($(OUT)$(MODULE)/*.debug) CLEAN($(OUT)$(MODULE)/*.test) +clean: CLEAN($(OUT)$(MODULE)/*.depends) +clean: CLEAN($(OUT)$(MODULE)/*.gcno) CLEAN($(OUT)$(MODULE)/*.gcda) +clean: CLEAN($(OUT)$(MODULE)/*.gcov) CLEAN($(OUT)lcov-coverage.info) +clean: CLEAN($(OUT)lcov-html) + +$(info + submodule: $(MODULE_NAME)) +# We must eval otherwise they may be dropped. +MODULE_C_OBJECTS = $(patsubst $(SRC)/$(MODULE)/%.c,$(MODULE)/%.o,\ + $(wildcard $(SRC)/$(MODULE)/*.c)) +$(eval $(MODULE_NAME)_C_OBJECTS ?= $(MODULE_C_OBJECTS)) +MODULE_CXX_OBJECTS = $(patsubst $(SRC)/$(MODULE)/%.cc,$(MODULE)/%.o,\ + $(wildcard $(SRC)/$(MODULE)/*.cc)) +$(eval $(MODULE_NAME)_CXX_OBJECTS ?= $(MODULE_CXX_OBJECTS)) + +# Note, $(MODULE) is implicit in the path to the %.c. +# See $(C_OBJECTS) for more details. +# Register rules for the module objects. +$(eval $(call add_object_rules,$(MODULE_C_OBJECTS),CC,c,CFLAGS,$(SRC)/)) +$(eval $(call add_object_rules,$(MODULE_CXX_OBJECTS),CXX,cc,CXXFLAGS,$(SRC)/)) + +# Continue recursive inclusion of module.mk files +SUBMODULE_DIRS = $(wildcard $(SRC)/$(MODULE)/*/module.mk) +include $(wildcard $(OUT)$(MODULE)/*.d) +include $(SUBMODULE_DIRS) + +endif +endif ## pass-to-subcall wrapper for relocating the call directory
diff --git a/minijail/elfparse.c b/minijail/elfparse.c new file mode 100644 index 0000000..cd032af --- /dev/null +++ b/minijail/elfparse.c
@@ -0,0 +1,111 @@ +/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "elfparse.h" + +int is_elf_magic (const uint8_t *buf) +{ + return (buf[EI_MAG0] == ELFMAG0) && + (buf[EI_MAG1] == ELFMAG1) && + (buf[EI_MAG2] == ELFMAG2) && + (buf[EI_MAG3] == ELFMAG3); +} + +#define parseElftemplate(bit) \ +ElfType parseElf ## bit(FILE *elf_file, uint8_t *pHead, int little_endian) \ +{ \ + ElfType ret = ELFSTATIC; \ + Minijail_Elf ## bit ## _Ehdr *pHeader = NULL; \ + Minijail_Elf ## bit ## _Phdr pheader = { 0 }; \ + uint32_t i = 0; \ + \ + if (!elf_file || !pHead) \ + return ELFERROR; \ + \ + pHeader = (Minijail_Elf ## bit ## _Ehdr *)pHead; \ + if (little_endian) { \ + pHeader->e_phoff = le ## bit ## toh(pHeader->e_phoff); \ + pHeader->e_phentsize = le16toh(pHeader->e_phentsize); \ + pHeader->e_phnum = le16toh(pHeader->e_phnum); \ + } else { \ + pHeader->e_phoff = be ## bit ## toh(pHeader->e_phoff); \ + pHeader->e_phentsize = be16toh(pHeader->e_phentsize); \ + pHeader->e_phnum = be16toh(pHeader->e_phnum); \ + } \ + if (pHeader->e_phentsize != sizeof(Minijail_Elf ## bit ## _Phdr)) \ + return ELFERROR; \ + \ + if (fseek(elf_file, pHeader->e_phoff, SEEK_SET) != 0) \ + return ELFERROR; \ + \ + for (i = 0; i < pHeader->e_phnum; i++) { \ + if (fread(&pheader, sizeof(pheader), 1, elf_file) == 1) { \ + if (pheader.p_type == PT_INTERP) { \ + ret = ELFDYNAMIC; \ + break; \ + } \ + } else { \ + ret = ELFERROR; \ + break; \ + } \ + } \ + return ret; \ +} +parseElftemplate(64) +parseElftemplate(32) + +/* Public function to determine the linkage of an ELF. */ +ElfType get_elf_linkage(const char *path) +{ + ElfType ret = ELFERROR; + FILE *elf_file = NULL; + uint8_t pHeader[HEADERSIZE] = ""; + + elf_file = fopen(path, "r"); + if (elf_file) { + if (fread(pHeader, 1, HEADERSIZE, elf_file) == HEADERSIZE) { + if (is_elf_magic(pHeader)) { + if ((pHeader[EI_DATA] == ELFDATA2LSB) && + (pHeader[EI_CLASS] == ELFCLASS64)) { + /* 64-bit little endian. */ + ret = parseElf64(elf_file, pHeader, 1); + } else if ((pHeader[EI_DATA] == ELFDATA2MSB) && + (pHeader[EI_CLASS] == ELFCLASS64)) { + /* 64-bit big endian. */ + ret = parseElf64(elf_file, pHeader, 0); + } else if ((pHeader[EI_DATA] == ELFDATA2LSB) && + (pHeader[EI_CLASS] == ELFCLASS32)) { + /* 32-bit little endian. */ + ret = parseElf32(elf_file, pHeader, 1); + } else if ((pHeader[EI_DATA] == ELFDATA2MSB) && + (pHeader[EI_CLASS] == ELFCLASS32)) { + /* 32-bit big endian. */ + ret = parseElf32(elf_file, pHeader, 0); + } + } else { + /* + * The binary is not an ELF. We assume it's a + * script. We should parse the #! line and + * check the interpreter to guard against + * static interpreters escaping the sandbox. + * As Minijail is only called from the rootfs + * it was deemed not necessary to check this. + * So we will just let execve(2) decide if this + * is valid. + */ + ret = ELFDYNAMIC; + } + } else { + /* + * The file is smaller than |HEADERSIZE| bytes. + * We assume it's a short script. See above for + * reasoning on scripts. + */ + ret = ELFDYNAMIC; + } + fclose(elf_file); + } + return ret; +}
diff --git a/minijail/elfparse.h b/minijail/elfparse.h new file mode 100644 index 0000000..599eb11 --- /dev/null +++ b/minijail/elfparse.h
@@ -0,0 +1,105 @@ +/* elfparse.h + * Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Elf parsing. + */ + +#ifndef _ELFPARSE_H_ +#define _ELFPARSE_H_ + +#include <stdio.h> +#include <stdlib.h> +#include <elf.h> +#include <unistd.h> +#include <stdint.h> +#include <endian.h> +#include <string.h> + +/* + * These structs come from elf.h + * The version in elf.h do not pack these structs so + * portability could be an issue. + * The compiler could mess with aligmment depending on arch + * so I'm redefining them here and packing them to 1-byte alignment. + */ +#if !defined(EI_NIDENT) +#define EI_NIDENT (16) +#endif +#pragma pack(push) +#pragma pack(1) +typedef struct +{ + unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ + Elf32_Half e_type; /* Object file type */ + Elf32_Half e_machine; /* Architecture */ + Elf32_Word e_version; /* Object file version */ + Elf32_Addr e_entry; /* Entry point virtual address */ + Elf32_Off e_phoff; /* Program header table file offset */ + Elf32_Off e_shoff; /* Section header table file offset */ + Elf32_Word e_flags; /* Processor-specific flags */ + Elf32_Half e_ehsize; /* ELF header size in bytes */ + Elf32_Half e_phentsize; /* Program header table entry size */ + Elf32_Half e_phnum; /* Program header table entry count */ + Elf32_Half e_shentsize; /* Section header table entry size */ + Elf32_Half e_shnum; /* Section header table entry count */ + Elf32_Half e_shstrndx; /* Section header string table index */ +} Minijail_Elf32_Ehdr; + +typedef struct +{ + unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ + Elf64_Half e_type; /* Object file type */ + Elf64_Half e_machine; /* Architecture */ + Elf64_Word e_version; /* Object file version */ + Elf64_Addr e_entry; /* Entry point virtual address */ + Elf64_Off e_phoff; /* Program header table file offset */ + Elf64_Off e_shoff; /* Section header table file offset */ + Elf64_Word e_flags; /* Processor-specific flags */ + Elf64_Half e_ehsize; /* ELF header size in bytes */ + Elf64_Half e_phentsize; /* Program header table entry size */ + Elf64_Half e_phnum; /* Program header table entry count */ + Elf64_Half e_shentsize; /* Section header table entry size */ + Elf64_Half e_shnum; /* Section header table entry count */ + Elf64_Half e_shstrndx; /* Section header string table index */ +} Minijail_Elf64_Ehdr; + +typedef struct +{ + Elf32_Word p_type; /* Segment type */ + Elf32_Off p_offset; /* Segment file offset */ + Elf32_Addr p_vaddr; /* Segment virtual address */ + Elf32_Addr p_paddr; /* Segment physical address */ + Elf32_Word p_filesz; /* Segment size in file */ + Elf32_Word p_memsz; /* Segment size in memory */ + Elf32_Word p_flags; /* Segment flags */ + Elf32_Word p_align; /* Segment alignment */ +} Minijail_Elf32_Phdr; + +typedef struct +{ + Elf64_Word p_type; /* Segment type */ + Elf64_Word p_flags; /* Segment flags */ + Elf64_Off p_offset; /* Segment file offset */ + Elf64_Addr p_vaddr; /* Segment virtual address */ + Elf64_Addr p_paddr; /* Segment physical address */ + Elf64_Xword p_filesz; /* Segment size in file */ + Elf64_Xword p_memsz; /* Segment size in memory */ + Elf64_Xword p_align; /* Segment alignment */ +} Minijail_Elf64_Phdr; +#pragma pack(pop) +/* End of definitions from elf.h */ + +enum ElfTypeEnum { ELFERROR=0, ELFSTATIC=1, ELFDYNAMIC=2 }; +typedef enum ElfTypeEnum ElfType; + +/* + * This is the initial amount of the ELF file we try and read. + * It is the same value that the kernel uses (BINPRM_BUF_SIZE). + */ +#define HEADERSIZE 128 + +ElfType get_elf_linkage(const char *path); + +#endif /* _ELFPARSE_H_ */
diff --git a/minijail/examples/drop_privs.cpp b/minijail/examples/drop_privs.cpp new file mode 100644 index 0000000..d2b0e17 --- /dev/null +++ b/minijail/examples/drop_privs.cpp
@@ -0,0 +1,73 @@ +// Copyright (C) 2015 The Android Open Source Project +// +// 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. + +#include <sys/types.h> +#include <sys/capability.h> +#include <unistd.h> + +#include <libminijail.h> + +#include <android-base/logging.h> + +gid_t groups[] = { 1001, 1002 }; + +void log_resugid() { + uid_t ruid, euid, suid; + gid_t rgid, egid, sgid; + getresuid(&ruid, &euid, &suid); + getresgid(&rgid, &egid, &sgid); + + LOG(INFO) << "ruid " << ruid << " euid " << euid << " suid " << suid; + LOG(INFO) << "rgid " << rgid << " egid " << egid << " sgid " << sgid; + + int nsupp_groups = getgroups(0, NULL); + if (nsupp_groups < 0) { + PLOG(FATAL) << "getgroups(0)"; + } + if (nsupp_groups == 0) { + LOG(INFO) << "no supplemental groups"; + return; + } + + gid_t *list = (gid_t*)calloc((size_t)nsupp_groups, sizeof(gid_t)); + nsupp_groups = getgroups(nsupp_groups, list); + if (nsupp_groups < 0) { + PLOG(FATAL) << "getgroups(nsupp_groups)"; + } + for (size_t i = 0; i < (size_t)nsupp_groups; i++) { + LOG(INFO) << "supp gid " << i + 1 << " " << list[i]; + } + free(list); +} + +int main(void) { + log_resugid(); + minijail *j = minijail_new(); + minijail_change_user(j, "system"); + minijail_change_group(j, "system"); + minijail_set_supplementary_gids(j, sizeof(groups) / sizeof(groups[0]), groups); + // minijail_use_caps(j, CAP_TO_MASK(CAP_SETUID) | CAP_TO_MASK(CAP_SETGID)); + // minijail_use_seccomp_filter(j); + // minijail_log_seccomp_filter_failures(j); + // minijail_parse_seccomp_filters(j, "/data/filter.policy"); + minijail_enter(j); + log_resugid(); + minijail_destroy(j); + // minijail *j2 = minijail_new(); + // minijail_change_uid(j2, 5000); + // minijail_change_gid(j2, 5000); + // minijail_enter(j2); + // log_resugid(); + return 0; +}
diff --git a/minijail/gen_constants.sh b/minijail/gen_constants.sh new file mode 100755 index 0000000..0f330b9 --- /dev/null +++ b/minijail/gen_constants.sh
@@ -0,0 +1,71 @@ +#!/bin/sh + +# Copyright 2015 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Generates a header file with a named constant table made up of "name", value +# entries by including several build target header files and emitting the list +# of defines. Use of the preprocessor is needed to recursively include all +# relevant headers. + +set -e + +if [ $# -ne 1 ] && [ $# -ne 2 ]; then + echo "Usage: $(basename "$0") OUTFILE" + echo "Usage: $(basename "$0") CC OUTFILE" + exit 1 +fi + +if [ $# -eq 2 ]; then + CC="$1" + shift +fi +OUTFILE="$1" + +INCLUDES=' +#include <errno.h> +#include <fcntl.h> +#include <linux/prctl.h> +#include <linux/sched.h> +#include <stddef.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h>' + +# Generate a dependency file which helps the build tool to see when it +# should regenerate ${OUTFILE}. +echo "${INCLUDES}" | ${CC} - -E -M -MF "${OUTFILE}.d.tmp" +# Correct the output filename. +(echo "${OUTFILE}: \\" ; sed -e 's/^-\.o://' -e 's/^-://' "${OUTFILE}.d.tmp") \ + > "${OUTFILE}.d" +rm "${OUTFILE}.d.tmp" + +# sed expression which extracts constants and converts them from: +# #define AT_FDWCD foo +# to: +# #ifdef AT_FDCWD +# { "AT_FDWCD", AT_FDCWD }, +# endif +SED_MULTILINE='s@#define ([[:upper:]][[:upper:]0-9_]*).*@#ifdef \1\ + { "\1", (unsigned long) \1 },\ +#endif // \1@' + +# Passes the previous list of #includes to the C preprocessor and prints out +# all #defines whose name is all-caps. Excludes a few symbols that are known +# macro functions that don't evaluate to a constant. +cat <<-EOF > "${OUTFILE}" +/* GENERATED BY MAKEFILE */ +$INCLUDES + +#include "libconstants.h" +const struct constant_entry constant_table[] = { +$(echo "$INCLUDES" | \ + ${CC} -dD - -E | \ + grep -E '^#define [[:upper:]][[:upper:]0-9_]*(\s)+[[:alnum:]]' | \ + grep -Ev '(SIGRTMAX|SIGRTMIN|SIG_|NULL)' | \ + sort -u | \ + sed -Ee "${SED_MULTILINE}") + { NULL, 0 }, +}; +EOF
diff --git a/minijail/gen_syscalls.sh b/minijail/gen_syscalls.sh new file mode 100755 index 0000000..f31031b --- /dev/null +++ b/minijail/gen_syscalls.sh
@@ -0,0 +1,56 @@ +#!/bin/sh + +# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +# Generates a header file with a system call table made up of "name", +# syscall_nr entries by including the build target <asm/unistd.h> and +# emitting the list of defines. Use of the compiler is needed to +# dereference the actual provider of syscall definitions. +# E.g., asm/unistd_32.h or asm/unistd_64.h, etc. + +set -e + +if [ $# -ne 1 ] && [ $# -ne 2 ]; then + echo "Usage: $(basename "$0") OUTFILE" + echo "Usage: $(basename "$0") CC OUTFILE" + exit 1 +fi + +if [ $# -eq 2 ]; then + CC="$1" + shift +fi +OUTFILE="$1" + +# Generate a dependency file which helps the build tool to see when it +# should regenerate ${OUTFILE}. +echo '#include <asm/unistd.h>' | ${CC} - -E -M -MF "${OUTFILE}.d.tmp" +# Correct the output filename. +(echo "${OUTFILE}: \\" ; sed -e 's/^-\.o://' -e 's/^-://' "${OUTFILE}.d.tmp") \ + > "${OUTFILE}.d" +rm "${OUTFILE}.d.tmp" + +# sed expression which extracts system calls that are +# defined via asm/unistd.h. It converts them from: +# #define __NR_read foo +# to: +# #ifdef __NR_read +# { "read", __NR_read }, +# #endif +SED_MULTILINE='s/#define __(ARM_)?(NR_)([[:lower:]0-9_]*) (.*)$/#ifdef __\1\2\3\ +{ "\1\3", __\1\2\3 },\ +#endif/g p;' + +cat <<-EOF > "${OUTFILE}" +/* GENERATED BY MAKEFILE */ +#include <stddef.h> +#include <asm/unistd.h> +#include "libsyscalls.h" +const struct syscall_entry syscall_table[] = { +$(echo '#include <asm/unistd.h>' | \ + ${CC} -dD - -E | sed -Ene "${SED_MULTILINE}") + { NULL, -1 }, +}; +EOF
diff --git a/minijail/libconstants.h b/minijail/libconstants.h new file mode 100644 index 0000000..c289955 --- /dev/null +++ b/minijail/libconstants.h
@@ -0,0 +1,15 @@ +/* Copyright 2015 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef MINIJAIL_LIBCONSTANTS_H_ +#define MINIJAIL_LIBCONSTANTS_H_ + +struct constant_entry { + const char *name; + unsigned long value; +}; + +extern const struct constant_entry constant_table[]; + +#endif /* MINIJAIL_LIBCONSTANTS_H_ */
diff --git a/minijail/libminijail-private.h b/minijail/libminijail-private.h new file mode 100644 index 0000000..926559f --- /dev/null +++ b/minijail/libminijail-private.h
@@ -0,0 +1,94 @@ +/* libminijail-private.h + * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Values shared between libminijailpreload and libminijail, but not visible to + * the outside world. + */ + +#ifndef LIBMINIJAIL_PRIVATE_H +#define LIBMINIJAIL_PRIVATE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* Explicitly declare exported functions so that -fvisibility tricks + * can be used for testing and minimal symbol leakage occurs. + */ +#define API __attribute__ ((visibility("default"))) + +static const char *kFdEnvVar = "__MINIJAIL_FD"; +static const char *kLdPreloadEnvVar = "LD_PRELOAD"; + +struct minijail; + +/* minijail_size: returns the size (in bytes) of @j if marshalled + * @j jail to compute size of + * + * Returns 0 on error. + */ +extern size_t minijail_size(const struct minijail *j); + +/* minijail_marshal: serializes @j to @buf + * @j minijail to serialize + * @buf buffer to serialize to + * @size size of @buf + * + * Returns 0 on success. + * + * Writes |j| to |buf| such that it can be reparsed by the same + * library on the same architecture. This is meant to be used + * by minijail0.c and libminijailpreload.c. minijail flags that + * require minijail_run() will be excluded. + * + * The marshalled data is not robust to differences between the child + * and parent process (personality, etc). + */ +extern int minijail_marshal(const struct minijail *j, + char *buf, + size_t size); + +/* minijail_unmarshal: initializes @j from @serialized + * @j minijail to initialize + * @serialized serialized jail buffer + * @length length of buffer + * + * Returns 0 on success. + */ +extern int minijail_unmarshal(struct minijail *j, + char *serialized, + size_t length); + +/* minijail_from_fd: builds @j from @fd + * @j minijail to initialize + * @fd fd to initialize from + * + * Returns 0 on success. + */ +extern int minijail_from_fd(int fd, struct minijail *j); + +/* minijail_to_fd: sends @j over @fd + * @j minijail to send + * @fd fd to send over + * + * Returns 0 on success. + */ +extern int minijail_to_fd(struct minijail *j, int fd); + +/* minijail_preexec: strips @j of all options handled by minijail_enter() + * @j jail to strip + */ +extern void minijail_preexec(struct minijail *j); + +/* minijail_preenter: strips @j of all options handled by minijail_run() + * @j jail to strip + */ +extern void minijail_preenter(struct minijail *j); + +#ifdef __cplusplus +}; /* extern "C" */ +#endif + +#endif /* !LIBMINIJAIL_PRIVATE_H */
diff --git a/minijail/libminijail.c b/minijail/libminijail.c new file mode 100644 index 0000000..8f63b85 --- /dev/null +++ b/minijail/libminijail.c
@@ -0,0 +1,2166 @@ +/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#define _BSD_SOURCE +#define _DEFAULT_SOURCE +#define _GNU_SOURCE + +#include <asm/unistd.h> +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <grp.h> +#include <inttypes.h> +#include <limits.h> +#include <linux/capability.h> +#include <pwd.h> +#include <sched.h> +#include <signal.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syscall.h> +#include <sys/capability.h> +#include <sys/mount.h> +#include <sys/param.h> +#include <sys/prctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/user.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "libminijail.h" +#include "libminijail-private.h" + +#include "signal_handler.h" +#include "syscall_filter.h" +#include "util.h" + +#ifdef HAVE_SECUREBITS_H +# include <linux/securebits.h> +#else +# define SECURE_ALL_BITS 0x55 +# define SECURE_ALL_LOCKS (SECURE_ALL_BITS << 1) +#endif +/* For kernels < 4.3. */ +#define OLD_SECURE_ALL_BITS 0x15 +#define OLD_SECURE_ALL_LOCKS (OLD_SECURE_ALL_BITS << 1) + +/* + * Assert the value of SECURE_ALL_BITS at compile-time. + * Brillo devices are currently compiled against 4.4 kernel headers. Kernel 4.3 + * added a new securebit. + * When a new securebit is added, the new SECURE_ALL_BITS mask will return EPERM + * when used on older kernels. The compile-time assert will catch this situation + * at compile time. + */ +#ifdef __BRILLO__ +_Static_assert(SECURE_ALL_BITS == 0x55, "SECURE_ALL_BITS == 0x55."); +#endif + +/* Until these are reliably available in linux/prctl.h. */ +#ifndef PR_SET_SECCOMP +# define PR_SET_SECCOMP 22 +#endif + +#ifndef PR_ALT_SYSCALL +# define PR_ALT_SYSCALL 0x43724f53 +#endif + +/* For seccomp_filter using BPF. */ +#ifndef PR_SET_NO_NEW_PRIVS +# define PR_SET_NO_NEW_PRIVS 38 +#endif +#ifndef SECCOMP_MODE_FILTER +# define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */ +#endif + +/* New cgroup namespace might not be in linux-headers yet. */ +#ifndef CLONE_NEWCGROUP +# define CLONE_NEWCGROUP 0x02000000 +#endif + +#define MAX_CGROUPS 10 /* 10 different controllers supported by Linux. */ + +struct mountpoint { + char *src; + char *dest; + char *type; + char *data; + int has_data; + unsigned long flags; + struct mountpoint *next; +}; + +struct minijail { + /* + * WARNING: if you add a flag here you need to make sure it's + * accounted for in minijail_pre{enter|exec}() below. + */ + struct { + int uid:1; + int gid:1; + int usergroups:1; + int suppl_gids:1; + int use_caps:1; + int capbset_drop:1; + int vfs:1; + int enter_vfs:1; + int skip_remount_private:1; + int pids:1; + int ipc:1; + int net:1; + int enter_net:1; + int ns_cgroups:1; + int userns:1; + int seccomp:1; + int remount_proc_ro:1; + int no_new_privs:1; + int seccomp_filter:1; + int log_seccomp_filter:1; + int chroot:1; + int pivot_root:1; + int mount_tmp:1; + int do_init:1; + int pid_file:1; + int cgroups:1; + int alt_syscall:1; + int reset_signal_mask:1; + } flags; + uid_t uid; + gid_t gid; + gid_t usergid; + char *user; + size_t suppl_gid_count; + gid_t *suppl_gid_list; + uint64_t caps; + uint64_t cap_bset; + pid_t initpid; + int mountns_fd; + int netns_fd; + char *chrootdir; + char *pid_file_path; + char *uidmap; + char *gidmap; + size_t filter_len; + struct sock_fprog *filter_prog; + char *alt_syscall_table; + struct mountpoint *mounts_head; + struct mountpoint *mounts_tail; + size_t mounts_count; + char *cgroups[MAX_CGROUPS]; + size_t cgroup_count; +}; + +/* + * Strip out flags meant for the parent. + * We keep things that are not inherited across execve(2) (e.g. capabilities), + * or are easier to set after execve(2) (e.g. seccomp filters). + */ +void minijail_preenter(struct minijail *j) +{ + j->flags.vfs = 0; + j->flags.enter_vfs = 0; + j->flags.skip_remount_private = 0; + j->flags.remount_proc_ro = 0; + j->flags.pids = 0; + j->flags.do_init = 0; + j->flags.pid_file = 0; + j->flags.cgroups = 0; +} + +/* + * Strip out flags meant for the child. + * We keep things that are inherited across execve(2). + */ +void minijail_preexec(struct minijail *j) +{ + int vfs = j->flags.vfs; + int enter_vfs = j->flags.enter_vfs; + int skip_remount_private = j->flags.skip_remount_private; + int remount_proc_ro = j->flags.remount_proc_ro; + int userns = j->flags.userns; + if (j->user) + free(j->user); + j->user = NULL; + if (j->suppl_gid_list) + free(j->suppl_gid_list); + j->suppl_gid_list = NULL; + memset(&j->flags, 0, sizeof(j->flags)); + /* Now restore anything we meant to keep. */ + j->flags.vfs = vfs; + j->flags.enter_vfs = enter_vfs; + j->flags.skip_remount_private = skip_remount_private; + j->flags.remount_proc_ro = remount_proc_ro; + j->flags.userns = userns; + /* Note, |pids| will already have been used before this call. */ +} + +/* Minijail API. */ + +struct minijail API *minijail_new(void) +{ + return calloc(1, sizeof(struct minijail)); +} + +void API minijail_change_uid(struct minijail *j, uid_t uid) +{ + if (uid == 0) + die("useless change to uid 0"); + j->uid = uid; + j->flags.uid = 1; +} + +void API minijail_change_gid(struct minijail *j, gid_t gid) +{ + if (gid == 0) + die("useless change to gid 0"); + j->gid = gid; + j->flags.gid = 1; +} + +void API minijail_set_supplementary_gids(struct minijail *j, size_t size, + const gid_t *list) +{ + size_t i; + + if (j->flags.usergroups) + die("cannot inherit *and* set supplementary groups"); + + if (size == 0) { + /* Clear supplementary groups. */ + j->suppl_gid_list = NULL; + j->suppl_gid_count = 0; + j->flags.suppl_gids = 1; + return; + } + + /* Copy the gid_t array. */ + j->suppl_gid_list = calloc(size, sizeof(gid_t)); + if (!j->suppl_gid_list) { + die("failed to allocate internal supplementary group array"); + } + for (i = 0; i < size; i++) { + j->suppl_gid_list[i] = list[i]; + } + j->suppl_gid_count = size; + j->flags.suppl_gids = 1; +} + +int API minijail_change_user(struct minijail *j, const char *user) +{ + char *buf = NULL; + struct passwd pw; + struct passwd *ppw = NULL; + ssize_t sz = sysconf(_SC_GETPW_R_SIZE_MAX); + if (sz == -1) + sz = 65536; /* your guess is as good as mine... */ + + /* + * sysconf(_SC_GETPW_R_SIZE_MAX), under glibc, is documented to return + * the maximum needed size of the buffer, so we don't have to search. + */ + buf = malloc(sz); + if (!buf) + return -ENOMEM; + getpwnam_r(user, &pw, buf, sz, &ppw); + /* + * We're safe to free the buffer here. The strings inside |pw| point + * inside |buf|, but we don't use any of them; this leaves the pointers + * dangling but it's safe. |ppw| points at |pw| if getpwnam_r(3) + * succeeded. + */ + free(buf); + /* getpwnam_r(3) does *not* set errno when |ppw| is NULL. */ + if (!ppw) + return -1; + minijail_change_uid(j, ppw->pw_uid); + j->user = strdup(user); + if (!j->user) + return -ENOMEM; + j->usergid = ppw->pw_gid; + return 0; +} + +int API minijail_change_group(struct minijail *j, const char *group) +{ + char *buf = NULL; + struct group gr; + struct group *pgr = NULL; + ssize_t sz = sysconf(_SC_GETGR_R_SIZE_MAX); + if (sz == -1) + sz = 65536; /* and mine is as good as yours, really */ + + /* + * sysconf(_SC_GETGR_R_SIZE_MAX), under glibc, is documented to return + * the maximum needed size of the buffer, so we don't have to search. + */ + buf = malloc(sz); + if (!buf) + return -ENOMEM; + getgrnam_r(group, &gr, buf, sz, &pgr); + /* + * We're safe to free the buffer here. The strings inside gr point + * inside buf, but we don't use any of them; this leaves the pointers + * dangling but it's safe. pgr points at gr if getgrnam_r succeeded. + */ + free(buf); + /* getgrnam_r(3) does *not* set errno when |pgr| is NULL. */ + if (!pgr) + return -1; + minijail_change_gid(j, pgr->gr_gid); + return 0; +} + +void API minijail_use_seccomp(struct minijail *j) +{ + j->flags.seccomp = 1; +} + +void API minijail_no_new_privs(struct minijail *j) +{ + j->flags.no_new_privs = 1; +} + +void API minijail_use_seccomp_filter(struct minijail *j) +{ + j->flags.seccomp_filter = 1; +} + +void API minijail_log_seccomp_filter_failures(struct minijail *j) +{ + j->flags.log_seccomp_filter = 1; +} + +void API minijail_use_caps(struct minijail *j, uint64_t capmask) +{ + /* + * 'minijail_use_caps' configures a runtime-capabilities-only + * environment, including a bounding set matching the thread's runtime + * (permitted|inheritable|effective) sets. + * Therefore, it will override any existing bounding set configurations + * since the latter would allow gaining extra runtime capabilities from + * file capabilities. + */ + if (j->flags.capbset_drop) { + warn("overriding bounding set configuration"); + j->cap_bset = 0; + j->flags.capbset_drop = 0; + } + j->caps = capmask; + j->flags.use_caps = 1; +} + +void API minijail_capbset_drop(struct minijail *j, uint64_t capmask) +{ + if (j->flags.use_caps) { + /* + * 'minijail_use_caps' will have already configured a capability + * bounding set matching the (permitted|inheritable|effective) + * sets. Abort if the user tries to configure a separate + * bounding set. 'minijail_capbset_drop' and 'minijail_use_caps' + * are mutually exclusive. + */ + die("runtime capabilities already configured, can't drop " + "bounding set separately"); + } + j->cap_bset = capmask; + j->flags.capbset_drop = 1; +} + +void API minijail_reset_signal_mask(struct minijail *j) +{ + j->flags.reset_signal_mask = 1; +} + +void API minijail_namespace_vfs(struct minijail *j) +{ + j->flags.vfs = 1; +} + +void API minijail_namespace_enter_vfs(struct minijail *j, const char *ns_path) +{ + int ns_fd = open(ns_path, O_RDONLY | O_CLOEXEC); + if (ns_fd < 0) { + pdie("failed to open namespace '%s'", ns_path); + } + j->mountns_fd = ns_fd; + j->flags.enter_vfs = 1; +} + +void API minijail_skip_remount_private(struct minijail *j) +{ + j->flags.skip_remount_private = 1; +} + +void API minijail_namespace_pids(struct minijail *j) +{ + j->flags.vfs = 1; + j->flags.remount_proc_ro = 1; + j->flags.pids = 1; + j->flags.do_init = 1; +} + +void API minijail_namespace_ipc(struct minijail *j) +{ + j->flags.ipc = 1; +} + +void API minijail_namespace_net(struct minijail *j) +{ + j->flags.net = 1; +} + +void API minijail_namespace_enter_net(struct minijail *j, const char *ns_path) +{ + int ns_fd = open(ns_path, O_RDONLY | O_CLOEXEC); + if (ns_fd < 0) { + pdie("failed to open namespace '%s'", ns_path); + } + j->netns_fd = ns_fd; + j->flags.enter_net = 1; +} + +void API minijail_namespace_cgroups(struct minijail *j) +{ + j->flags.ns_cgroups = 1; +} + +void API minijail_remount_proc_readonly(struct minijail *j) +{ + j->flags.vfs = 1; + j->flags.remount_proc_ro = 1; +} + +void API minijail_namespace_user(struct minijail *j) +{ + j->flags.userns = 1; +} + +int API minijail_uidmap(struct minijail *j, const char *uidmap) +{ + j->uidmap = strdup(uidmap); + if (!j->uidmap) + return -ENOMEM; + char *ch; + for (ch = j->uidmap; *ch; ch++) { + if (*ch == ',') + *ch = '\n'; + } + return 0; +} + +int API minijail_gidmap(struct minijail *j, const char *gidmap) +{ + j->gidmap = strdup(gidmap); + if (!j->gidmap) + return -ENOMEM; + char *ch; + for (ch = j->gidmap; *ch; ch++) { + if (*ch == ',') + *ch = '\n'; + } + return 0; +} + +void API minijail_inherit_usergroups(struct minijail *j) +{ + j->flags.usergroups = 1; +} + +void API minijail_run_as_init(struct minijail *j) +{ + /* + * Since the jailed program will become 'init' in the new PID namespace, + * Minijail does not need to fork an 'init' process. + */ + j->flags.do_init = 0; +} + +int API minijail_enter_chroot(struct minijail *j, const char *dir) +{ + if (j->chrootdir) + return -EINVAL; + j->chrootdir = strdup(dir); + if (!j->chrootdir) + return -ENOMEM; + j->flags.chroot = 1; + return 0; +} + +int API minijail_enter_pivot_root(struct minijail *j, const char *dir) +{ + if (j->chrootdir) + return -EINVAL; + j->chrootdir = strdup(dir); + if (!j->chrootdir) + return -ENOMEM; + j->flags.pivot_root = 1; + return 0; +} + +char API *minijail_get_original_path(struct minijail *j, + const char *path_inside_chroot) +{ + struct mountpoint *b; + + b = j->mounts_head; + while (b) { + /* + * If |path_inside_chroot| is the exact destination of a + * mount, then the original path is exactly the source of + * the mount. + * for example: "-b /some/path/exe,/chroot/path/exe" + * mount source = /some/path/exe, mount dest = + * /chroot/path/exe Then when getting the original path of + * "/chroot/path/exe", the source of that mount, + * "/some/path/exe" is what should be returned. + */ + if (!strcmp(b->dest, path_inside_chroot)) + return strdup(b->src); + + /* + * If |path_inside_chroot| is within the destination path of a + * mount, take the suffix of the chroot path relative to the + * mount destination path, and append it to the mount source + * path. + */ + if (!strncmp(b->dest, path_inside_chroot, strlen(b->dest))) { + const char *relative_path = + path_inside_chroot + strlen(b->dest); + return path_join(b->src, relative_path); + } + b = b->next; + } + + /* If there is a chroot path, append |path_inside_chroot| to that. */ + if (j->chrootdir) + return path_join(j->chrootdir, path_inside_chroot); + + /* No chroot, so the path outside is the same as it is inside. */ + return strdup(path_inside_chroot); +} + +void API minijail_mount_tmp(struct minijail *j) +{ + j->flags.mount_tmp = 1; +} + +int API minijail_write_pid_file(struct minijail *j, const char *path) +{ + j->pid_file_path = strdup(path); + if (!j->pid_file_path) + return -ENOMEM; + j->flags.pid_file = 1; + return 0; +} + +int API minijail_add_to_cgroup(struct minijail *j, const char *path) +{ + if (j->cgroup_count >= MAX_CGROUPS) + return -ENOMEM; + j->cgroups[j->cgroup_count] = strdup(path); + if (!j->cgroups[j->cgroup_count]) + return -ENOMEM; + j->cgroup_count++; + j->flags.cgroups = 1; + return 0; +} + +int API minijail_mount_with_data(struct minijail *j, const char *src, + const char *dest, const char *type, + unsigned long flags, const char *data) +{ + struct mountpoint *m; + + if (*dest != '/') + return -EINVAL; + m = calloc(1, sizeof(*m)); + if (!m) + return -ENOMEM; + m->dest = strdup(dest); + if (!m->dest) + goto error; + m->src = strdup(src); + if (!m->src) + goto error; + m->type = strdup(type); + if (!m->type) + goto error; + if (data) { + m->data = strdup(data); + if (!m->data) + goto error; + m->has_data = 1; + } + m->flags = flags; + + info("mount %s -> %s type '%s'", src, dest, type); + + /* + * Force vfs namespacing so the mounts don't leak out into the + * containing vfs namespace. + */ + minijail_namespace_vfs(j); + + if (j->mounts_tail) + j->mounts_tail->next = m; + else + j->mounts_head = m; + j->mounts_tail = m; + j->mounts_count++; + + return 0; + +error: + free(m->type); + free(m->src); + free(m->dest); + free(m); + return -ENOMEM; +} + +int API minijail_mount(struct minijail *j, const char *src, const char *dest, + const char *type, unsigned long flags) +{ + return minijail_mount_with_data(j, src, dest, type, flags, NULL); +} + +int API minijail_bind(struct minijail *j, const char *src, const char *dest, + int writeable) +{ + unsigned long flags = MS_BIND; + + if (!writeable) + flags |= MS_RDONLY; + + return minijail_mount(j, src, dest, "", flags); +} + +static int seccomp_should_parse_filters(struct minijail *j) +{ + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, NULL)) { + /* + * |errno| will be set to EINVAL when seccomp has not been + * compiled into the kernel. On certain platforms and kernel + * versions this is not a fatal failure. In that case, and only + * in that case, disable seccomp and skip loading the filters. + */ + if ((errno == EINVAL) && seccomp_can_softfail()) { + warn("not loading seccomp filters," + " seccomp not supported"); + j->flags.seccomp_filter = 0; + j->flags.log_seccomp_filter = 0; + j->filter_len = 0; + j->filter_prog = NULL; + j->flags.no_new_privs = 0; + return 0; + } + /* + * If |errno| != EINVAL or seccomp_can_softfail() is false, + * we can proceed. Worst case scenario minijail_enter() will + * abort() if seccomp fails. + */ + } + return 1; +} + +static int parse_seccomp_filters(struct minijail *j, FILE *policy_file) +{ + struct sock_fprog *fprog = malloc(sizeof(struct sock_fprog)); + if (compile_filter(policy_file, fprog, j->flags.log_seccomp_filter)) { + free(fprog); + return -1; + } + + j->filter_len = fprog->len; + j->filter_prog = fprog; + return 0; +} + +void API minijail_parse_seccomp_filters(struct minijail *j, const char *path) +{ + if (!seccomp_should_parse_filters(j)) + return; + + FILE *file = fopen(path, "r"); + if (!file) { + pdie("failed to open seccomp filter file '%s'", path); + } + + if (parse_seccomp_filters(j, file) != 0) { + die("failed to compile seccomp filter BPF program in '%s'", + path); + } + fclose(file); +} + +void API minijail_parse_seccomp_filters_from_fd(struct minijail *j, int fd) +{ + if (!seccomp_should_parse_filters(j)) + return; + + FILE *file = fdopen(fd, "r"); + if (!file) { + pdie("failed to associate stream with fd %d", fd); + } + + if (parse_seccomp_filters(j, file) != 0) { + die("failed to compile seccomp filter BPF program from fd %d", + fd); + } + fclose(file); +} + +int API minijail_use_alt_syscall(struct minijail *j, const char *table) +{ + j->alt_syscall_table = strdup(table); + if (!j->alt_syscall_table) + return -ENOMEM; + j->flags.alt_syscall = 1; + return 0; +} + +struct marshal_state { + size_t available; + size_t total; + char *buf; +}; + +void marshal_state_init(struct marshal_state *state, char *buf, + size_t available) +{ + state->available = available; + state->buf = buf; + state->total = 0; +} + +void marshal_append(struct marshal_state *state, void *src, size_t length) +{ + size_t copy_len = MIN(state->available, length); + + /* Up to |available| will be written. */ + if (copy_len) { + memcpy(state->buf, src, copy_len); + state->buf += copy_len; + state->available -= copy_len; + } + /* |total| will contain the expected length. */ + state->total += length; +} + +void marshal_mount(struct marshal_state *state, const struct mountpoint *m) +{ + marshal_append(state, m->src, strlen(m->src) + 1); + marshal_append(state, m->dest, strlen(m->dest) + 1); + marshal_append(state, m->type, strlen(m->type) + 1); + marshal_append(state, (char *)&m->has_data, sizeof(m->has_data)); + if (m->has_data) + marshal_append(state, m->data, strlen(m->data) + 1); + marshal_append(state, (char *)&m->flags, sizeof(m->flags)); +} + +void minijail_marshal_helper(struct marshal_state *state, + const struct minijail *j) +{ + struct mountpoint *m = NULL; + size_t i; + + marshal_append(state, (char *)j, sizeof(*j)); + if (j->user) + marshal_append(state, j->user, strlen(j->user) + 1); + if (j->suppl_gid_list) { + marshal_append(state, j->suppl_gid_list, + j->suppl_gid_count * sizeof(gid_t)); + } + if (j->chrootdir) + marshal_append(state, j->chrootdir, strlen(j->chrootdir) + 1); + if (j->alt_syscall_table) { + marshal_append(state, j->alt_syscall_table, + strlen(j->alt_syscall_table) + 1); + } + if (j->flags.seccomp_filter && j->filter_prog) { + struct sock_fprog *fp = j->filter_prog; + marshal_append(state, (char *)fp->filter, + fp->len * sizeof(struct sock_filter)); + } + for (m = j->mounts_head; m; m = m->next) { + marshal_mount(state, m); + } + for (i = 0; i < j->cgroup_count; ++i) + marshal_append(state, j->cgroups[i], strlen(j->cgroups[i]) + 1); +} + +size_t API minijail_size(const struct minijail *j) +{ + struct marshal_state state; + marshal_state_init(&state, NULL, 0); + minijail_marshal_helper(&state, j); + return state.total; +} + +int minijail_marshal(const struct minijail *j, char *buf, size_t available) +{ + struct marshal_state state; + marshal_state_init(&state, buf, available); + minijail_marshal_helper(&state, j); + return (state.total > available); +} + +int minijail_unmarshal(struct minijail *j, char *serialized, size_t length) +{ + size_t i; + size_t count; + int ret = -EINVAL; + + if (length < sizeof(*j)) + goto out; + memcpy((void *)j, serialized, sizeof(*j)); + serialized += sizeof(*j); + length -= sizeof(*j); + + /* Potentially stale pointers not used as signals. */ + j->pid_file_path = NULL; + j->uidmap = NULL; + j->gidmap = NULL; + j->mounts_head = NULL; + j->mounts_tail = NULL; + j->filter_prog = NULL; + + if (j->user) { /* stale pointer */ + char *user = consumestr(&serialized, &length); + if (!user) + goto clear_pointers; + j->user = strdup(user); + if (!j->user) + goto clear_pointers; + } + + if (j->suppl_gid_list) { /* stale pointer */ + if (j->suppl_gid_count > NGROUPS_MAX) { + goto bad_gid_list; + } + size_t gid_list_size = j->suppl_gid_count * sizeof(gid_t); + void *gid_list_bytes = + consumebytes(gid_list_size, &serialized, &length); + if (!gid_list_bytes) + goto bad_gid_list; + + j->suppl_gid_list = calloc(j->suppl_gid_count, sizeof(gid_t)); + if (!j->suppl_gid_list) + goto bad_gid_list; + + memcpy(j->suppl_gid_list, gid_list_bytes, gid_list_size); + } + + if (j->chrootdir) { /* stale pointer */ + char *chrootdir = consumestr(&serialized, &length); + if (!chrootdir) + goto bad_chrootdir; + j->chrootdir = strdup(chrootdir); + if (!j->chrootdir) + goto bad_chrootdir; + } + + if (j->alt_syscall_table) { /* stale pointer */ + char *alt_syscall_table = consumestr(&serialized, &length); + if (!alt_syscall_table) + goto bad_syscall_table; + j->alt_syscall_table = strdup(alt_syscall_table); + if (!j->alt_syscall_table) + goto bad_syscall_table; + } + + if (j->flags.seccomp_filter && j->filter_len > 0) { + size_t ninstrs = j->filter_len; + if (ninstrs > (SIZE_MAX / sizeof(struct sock_filter)) || + ninstrs > USHRT_MAX) + goto bad_filters; + + size_t program_len = ninstrs * sizeof(struct sock_filter); + void *program = consumebytes(program_len, &serialized, &length); + if (!program) + goto bad_filters; + + j->filter_prog = malloc(sizeof(struct sock_fprog)); + if (!j->filter_prog) + goto bad_filters; + + j->filter_prog->len = ninstrs; + j->filter_prog->filter = malloc(program_len); + if (!j->filter_prog->filter) + goto bad_filter_prog_instrs; + + memcpy(j->filter_prog->filter, program, program_len); + } + + count = j->mounts_count; + j->mounts_count = 0; + for (i = 0; i < count; ++i) { + unsigned long *flags; + int *has_data; + const char *dest; + const char *type; + const char *data = NULL; + const char *src = consumestr(&serialized, &length); + if (!src) + goto bad_mounts; + dest = consumestr(&serialized, &length); + if (!dest) + goto bad_mounts; + type = consumestr(&serialized, &length); + if (!type) + goto bad_mounts; + has_data = consumebytes(sizeof(*has_data), &serialized, + &length); + if (!has_data) + goto bad_mounts; + if (*has_data) { + data = consumestr(&serialized, &length); + if (!data) + goto bad_mounts; + } + flags = consumebytes(sizeof(*flags), &serialized, &length); + if (!flags) + goto bad_mounts; + if (minijail_mount_with_data(j, src, dest, type, *flags, data)) + goto bad_mounts; + } + + count = j->cgroup_count; + j->cgroup_count = 0; + for (i = 0; i < count; ++i) { + char *cgroup = consumestr(&serialized, &length); + if (!cgroup) + goto bad_cgroups; + j->cgroups[i] = strdup(cgroup); + if (!j->cgroups[i]) + goto bad_cgroups; + ++j->cgroup_count; + } + + return 0; + +bad_cgroups: + while (j->mounts_head) { + struct mountpoint *m = j->mounts_head; + j->mounts_head = j->mounts_head->next; + free(m->data); + free(m->type); + free(m->dest); + free(m->src); + free(m); + } + for (i = 0; i < j->cgroup_count; ++i) + free(j->cgroups[i]); +bad_mounts: + if (j->flags.seccomp_filter && j->filter_len > 0) { + free(j->filter_prog->filter); + free(j->filter_prog); + } +bad_filter_prog_instrs: + if (j->filter_prog) + free(j->filter_prog); +bad_filters: + if (j->alt_syscall_table) + free(j->alt_syscall_table); +bad_syscall_table: + if (j->chrootdir) + free(j->chrootdir); +bad_chrootdir: + if (j->suppl_gid_list) + free(j->suppl_gid_list); +bad_gid_list: + if (j->user) + free(j->user); +clear_pointers: + j->user = NULL; + j->suppl_gid_list = NULL; + j->chrootdir = NULL; + j->alt_syscall_table = NULL; + j->cgroup_count = 0; +out: + return ret; +} + +/* + * setup_mount_destination: Ensures the mount target exists. + * Creates it if needed and possible. + */ +static int setup_mount_destination(const char *source, const char *dest, + uid_t uid, uid_t gid) +{ + int rc; + struct stat st_buf; + + rc = stat(dest, &st_buf); + if (rc == 0) /* destination exists */ + return 0; + + /* + * Try to create the destination. + * Either make a directory or touch a file depending on the source type. + * If the source doesn't exist, assume it is a filesystem type such as + * "tmpfs" and create a directory to mount it on. + */ + rc = stat(source, &st_buf); + if (rc || S_ISDIR(st_buf.st_mode) || S_ISBLK(st_buf.st_mode)) { + if (mkdir(dest, 0700)) + return -errno; + } else { + int fd = open(dest, O_RDWR | O_CREAT, 0700); + if (fd < 0) + return -errno; + close(fd); + } + return chown(dest, uid, gid); +} + +/* + * mount_one: Applies mounts from @m for @j, recursing as needed. + * @j Minijail these mounts are for + * @m Head of list of mounts + * + * Returns 0 for success. + */ +static int mount_one(const struct minijail *j, struct mountpoint *m) +{ + int ret; + char *dest; + int remount_ro = 0; + + /* |dest| has a leading "/". */ + if (asprintf(&dest, "%s%s", j->chrootdir, m->dest) < 0) + return -ENOMEM; + + if (setup_mount_destination(m->src, dest, j->uid, j->gid)) + pdie("creating mount target '%s' failed", dest); + + /* + * R/O bind mounts have to be remounted since 'bind' and 'ro' + * can't both be specified in the original bind mount. + * Remount R/O after the initial mount. + */ + if ((m->flags & MS_BIND) && (m->flags & MS_RDONLY)) { + remount_ro = 1; + m->flags &= ~MS_RDONLY; + } + + ret = mount(m->src, dest, m->type, m->flags, m->data); + if (ret) + pdie("mount: %s -> %s", m->src, dest); + + if (remount_ro) { + m->flags |= MS_RDONLY; + ret = mount(m->src, dest, NULL, + m->flags | MS_REMOUNT, m->data); + if (ret) + pdie("bind ro: %s -> %s", m->src, dest); + } + + free(dest); + if (m->next) + return mount_one(j, m->next); + return ret; +} + +static int enter_chroot(const struct minijail *j) +{ + int ret; + + if (j->mounts_head && (ret = mount_one(j, j->mounts_head))) + return ret; + + if (chroot(j->chrootdir)) + return -errno; + + if (chdir("/")) + return -errno; + + return 0; +} + +static int enter_pivot_root(const struct minijail *j) +{ + int ret, oldroot, newroot; + + if (j->mounts_head && (ret = mount_one(j, j->mounts_head))) + return ret; + + /* + * Keep the fd for both old and new root. + * It will be used in fchdir(2) later. + */ + oldroot = open("/", O_DIRECTORY | O_RDONLY | O_CLOEXEC); + if (oldroot < 0) + pdie("failed to open / for fchdir"); + newroot = open(j->chrootdir, O_DIRECTORY | O_RDONLY | O_CLOEXEC); + if (newroot < 0) + pdie("failed to open %s for fchdir", j->chrootdir); + + /* + * To ensure j->chrootdir is the root of a filesystem, + * do a self bind mount. + */ + if (mount(j->chrootdir, j->chrootdir, "bind", MS_BIND | MS_REC, "")) + pdie("failed to bind mount '%s'", j->chrootdir); + if (chdir(j->chrootdir)) + return -errno; + if (syscall(SYS_pivot_root, ".", ".")) + pdie("pivot_root"); + + /* + * Now the old root is mounted on top of the new root. Use fchdir(2) to + * change to the old root and unmount it. + */ + if (fchdir(oldroot)) + pdie("failed to fchdir to old /"); + + /* + * If j->flags.skip_remount_private was enabled for minijail_enter(), + * there could be a shared mount point under |oldroot|. In that case, + * mounts under this shared mount point will be unmounted below, and + * this unmounting will propagate to the original mount namespace + * (because the mount point is shared). To prevent this unexpected + * unmounting, remove these mounts from their peer groups by recursively + * remounting them as MS_PRIVATE. + */ + if (mount(NULL, ".", NULL, MS_REC | MS_PRIVATE, NULL)) + pdie("failed to mount(/, private) before umount(/)"); + /* The old root might be busy, so use lazy unmount. */ + if (umount2(".", MNT_DETACH)) + pdie("umount(/)"); + /* Change back to the new root. */ + if (fchdir(newroot)) + return -errno; + if (close(oldroot)) + return -errno; + if (close(newroot)) + return -errno; + if (chroot("/")) + return -errno; + /* Set correct CWD for getcwd(3). */ + if (chdir("/")) + return -errno; + + return 0; +} + +static int mount_tmp(void) +{ + return mount("none", "/tmp", "tmpfs", 0, "size=64M,mode=777"); +} + +static int remount_proc_readonly(const struct minijail *j) +{ + const char *kProcPath = "/proc"; + const unsigned int kSafeFlags = MS_NODEV | MS_NOEXEC | MS_NOSUID; + /* + * Right now, we're holding a reference to our parent's old mount of + * /proc in our namespace, which means using MS_REMOUNT here would + * mutate our parent's mount as well, even though we're in a VFS + * namespace (!). Instead, remove their mount from our namespace lazily + * (MNT_DETACH) and make our own. + */ + if (umount2(kProcPath, MNT_DETACH)) { + /* + * If we are in a new user namespace, umount(2) will fail. + * See http://man7.org/linux/man-pages/man7/user_namespaces.7.html + */ + if (j->flags.userns) { + info("umount(/proc, MNT_DETACH) failed, " + "this is expected when using user namespaces"); + } else { + return -errno; + } + } + if (mount("", kProcPath, "proc", kSafeFlags | MS_RDONLY, "")) + return -errno; + return 0; +} + +static void kill_child_and_die(const struct minijail *j, const char *msg) +{ + kill(j->initpid, SIGKILL); + die("%s", msg); +} + +static void write_pid_file_or_die(const struct minijail *j) +{ + if (write_pid_to_path(j->initpid, j->pid_file_path)) + kill_child_and_die(j, "failed to write pid file"); +} + +static void add_to_cgroups_or_die(const struct minijail *j) +{ + size_t i; + + for (i = 0; i < j->cgroup_count; ++i) { + if (write_pid_to_path(j->initpid, j->cgroups[i])) + kill_child_and_die(j, "failed to add to cgroups"); + } +} + +static void write_ugid_maps_or_die(const struct minijail *j) +{ + if (j->uidmap && write_proc_file(j->initpid, j->uidmap, "uid_map") != 0) + kill_child_and_die(j, "failed to write uid_map"); + if (j->gidmap && write_proc_file(j->initpid, j->gidmap, "gid_map") != 0) + kill_child_and_die(j, "failed to write gid_map"); +} + +static void enter_user_namespace(const struct minijail *j) +{ + if (j->uidmap && setresuid(0, 0, 0)) + pdie("user_namespaces: setresuid(0, 0, 0) failed"); + if (j->gidmap && setresgid(0, 0, 0)) + pdie("user_namespaces: setresgid(0, 0, 0) failed"); +} + +static void parent_setup_complete(int *pipe_fds) +{ + close(pipe_fds[0]); + close(pipe_fds[1]); +} + +/* + * wait_for_parent_setup: Called by the child process to wait for any + * further parent-side setup to complete before continuing. + */ +static void wait_for_parent_setup(int *pipe_fds) +{ + char buf; + + close(pipe_fds[1]); + + /* Wait for parent to complete setup and close the pipe. */ + if (read(pipe_fds[0], &buf, 1) != 0) + die("failed to sync with parent"); + close(pipe_fds[0]); +} + +static void drop_ugid(const struct minijail *j) +{ + if (j->flags.usergroups && j->flags.suppl_gids) { + die("tried to inherit *and* set supplementary groups;" + " can only do one"); + } + + if (j->flags.usergroups) { + if (initgroups(j->user, j->usergid)) + pdie("initgroups"); + } else if (j->flags.suppl_gids) { + if (setgroups(j->suppl_gid_count, j->suppl_gid_list)) { + pdie("setgroups"); + } + } else { + /* + * Only attempt to clear supplementary groups if we are changing + * users. + */ + if ((j->uid || j->gid) && setgroups(0, NULL)) + pdie("setgroups"); + } + + if (j->flags.gid && setresgid(j->gid, j->gid, j->gid)) + pdie("setresgid"); + + if (j->flags.uid && setresuid(j->uid, j->uid, j->uid)) + pdie("setresuid"); +} + +/* + * We specifically do not use cap_valid() as that only tells us the last + * valid cap we were *compiled* against (i.e. what the version of kernel + * headers says). If we run on a different kernel version, then it's not + * uncommon for that to be less (if an older kernel) or more (if a newer + * kernel). + * Normally, we suck up the answer via /proc. On Android, not all processes are + * guaranteed to be able to access '/proc/sys/kernel/cap_last_cap' so we + * programmatically find the value by calling prctl(PR_CAPBSET_READ). + */ +static unsigned int get_last_valid_cap() +{ + unsigned int last_valid_cap = 0; + if (is_android()) { + for (; prctl(PR_CAPBSET_READ, last_valid_cap, 0, 0, 0) >= 0; + ++last_valid_cap); + + /* |last_valid_cap| will be the first failing value. */ + if (last_valid_cap > 0) { + last_valid_cap--; + } + } else { + const char cap_file[] = "/proc/sys/kernel/cap_last_cap"; + FILE *fp = fopen(cap_file, "re"); + if (fscanf(fp, "%u", &last_valid_cap) != 1) + pdie("fscanf(%s)", cap_file); + fclose(fp); + } + return last_valid_cap; +} + +static void drop_capbset(uint64_t keep_mask, unsigned int last_valid_cap) +{ + const uint64_t one = 1; + unsigned int i; + for (i = 0; i < sizeof(keep_mask) * 8 && i <= last_valid_cap; ++i) { + if (keep_mask & (one << i)) + continue; + if (prctl(PR_CAPBSET_DROP, i)) + pdie("could not drop capability from bounding set"); + } +} + +static void drop_caps(const struct minijail *j, unsigned int last_valid_cap) +{ + if (!j->flags.use_caps) + return; + + cap_t caps = cap_get_proc(); + cap_value_t flag[1]; + const uint64_t one = 1; + unsigned int i; + if (!caps) + die("can't get process caps"); + if (cap_clear_flag(caps, CAP_INHERITABLE)) + die("can't clear inheritable caps"); + if (cap_clear_flag(caps, CAP_EFFECTIVE)) + die("can't clear effective caps"); + if (cap_clear_flag(caps, CAP_PERMITTED)) + die("can't clear permitted caps"); + for (i = 0; i < sizeof(j->caps) * 8 && i <= last_valid_cap; ++i) { + /* Keep CAP_SETPCAP for dropping bounding set bits. */ + if (i != CAP_SETPCAP && !(j->caps & (one << i))) + continue; + flag[0] = i; + if (cap_set_flag(caps, CAP_EFFECTIVE, 1, flag, CAP_SET)) + die("can't add effective cap"); + if (cap_set_flag(caps, CAP_PERMITTED, 1, flag, CAP_SET)) + die("can't add permitted cap"); + if (cap_set_flag(caps, CAP_INHERITABLE, 1, flag, CAP_SET)) + die("can't add inheritable cap"); + } + if (cap_set_proc(caps)) + die("can't apply initial cleaned capset"); + + /* + * Instead of dropping bounding set first, do it here in case + * the caller had a more permissive bounding set which could + * have been used above to raise a capability that wasn't already + * present. This requires CAP_SETPCAP, so we raised/kept it above. + */ + drop_capbset(j->caps, last_valid_cap); + + /* If CAP_SETPCAP wasn't specifically requested, now we remove it. */ + if ((j->caps & (one << CAP_SETPCAP)) == 0) { + flag[0] = CAP_SETPCAP; + if (cap_set_flag(caps, CAP_EFFECTIVE, 1, flag, CAP_CLEAR)) + die("can't clear effective cap"); + if (cap_set_flag(caps, CAP_PERMITTED, 1, flag, CAP_CLEAR)) + die("can't clear permitted cap"); + if (cap_set_flag(caps, CAP_INHERITABLE, 1, flag, CAP_CLEAR)) + die("can't clear inheritable cap"); + } + + if (cap_set_proc(caps)) + die("can't apply final cleaned capset"); + + cap_free(caps); +} + +static void set_seccomp_filter(const struct minijail *j) +{ + /* + * Set no_new_privs. See </kernel/seccomp.c> and </kernel/sys.c> + * in the kernel source tree for an explanation of the parameters. + */ + if (j->flags.no_new_privs) { + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) + pdie("prctl(PR_SET_NO_NEW_PRIVS)"); + } + + /* + * Code running with ASan + * (https://github.com/google/sanitizers/wiki/AddressSanitizer) + * will make system calls not included in the syscall filter policy, + * which will likely crash the program. Skip setting seccomp filter in + * that case. + * 'running_with_asan()' has no inputs and is completely defined at + * build time, so this cannot be used by an attacker to skip setting + * seccomp filter. + */ + if (j->flags.seccomp_filter && running_with_asan()) { + warn("running with ASan, not setting seccomp filter"); + return; + } + + /* + * If we're logging seccomp filter failures, + * install the SIGSYS handler first. + */ + if (j->flags.seccomp_filter && j->flags.log_seccomp_filter) { + if (install_sigsys_handler()) + pdie("install SIGSYS handler"); + warn("logging seccomp filter failures"); + } + + /* + * Install the syscall filter. + */ + if (j->flags.seccomp_filter) { + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, + j->filter_prog)) { + if ((errno == EINVAL) && seccomp_can_softfail()) { + warn("seccomp not supported"); + return; + } + pdie("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER)"); + } + } +} + +void API minijail_enter(const struct minijail *j) +{ + /* + * If we're dropping caps, get the last valid cap from /proc now, + * since /proc can be unmounted before drop_caps() is called. + */ + unsigned int last_valid_cap = 0; + if (j->flags.capbset_drop || j->flags.use_caps) + last_valid_cap = get_last_valid_cap(); + + if (j->flags.pids) + die("tried to enter a pid-namespaced jail;" + " try minijail_run()?"); + + if (j->flags.usergroups && !j->user) + die("usergroup inheritance without username"); + + /* + * We can't recover from failures if we've dropped privileges partially, + * so we don't even try. If any of our operations fail, we abort() the + * entire process. + */ + if (j->flags.enter_vfs && setns(j->mountns_fd, CLONE_NEWNS)) + pdie("setns(CLONE_NEWNS)"); + + if (j->flags.vfs) { + if (unshare(CLONE_NEWNS)) + pdie("unshare(vfs)"); + /* + * Unless asked not to, remount all filesystems as private. + * If they are shared, new bind mounts will creep out of our + * namespace. + * https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt + */ + if (!j->flags.skip_remount_private) { + if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) + pdie("mount(/, private)"); + } + } + + if (j->flags.ipc && unshare(CLONE_NEWIPC)) { + pdie("unshare(ipc)"); + } + + if (j->flags.enter_net) { + if (setns(j->netns_fd, CLONE_NEWNET)) + pdie("setns(CLONE_NEWNET)"); + } else if (j->flags.net && unshare(CLONE_NEWNET)) { + pdie("unshare(net)"); + } + + if (j->flags.ns_cgroups && unshare(CLONE_NEWCGROUP)) + pdie("unshare(cgroups)"); + + if (j->flags.chroot && enter_chroot(j)) + pdie("chroot"); + + if (j->flags.pivot_root && enter_pivot_root(j)) + pdie("pivot_root"); + + if (j->flags.mount_tmp && mount_tmp()) + pdie("mount_tmp"); + + if (j->flags.remount_proc_ro && remount_proc_readonly(j)) + pdie("remount"); + + /* + * If we're only dropping capabilities from the bounding set, but not + * from the thread's (permitted|inheritable|effective) sets, do it now. + */ + if (j->flags.capbset_drop) { + drop_capbset(j->cap_bset, last_valid_cap); + } + + if (j->flags.use_caps) { + /* + * POSIX capabilities are a bit tricky. If we drop our + * capability to change uids, our attempt to use setuid() + * below will fail. Hang on to root caps across setuid(), then + * lock securebits. + */ + if (prctl(PR_SET_KEEPCAPS, 1)) + pdie("prctl(PR_SET_KEEPCAPS)"); + + /* + * Kernels 4.3+ define a new securebit + * (SECURE_NO_CAP_AMBIENT_RAISE), so using the SECURE_ALL_BITS + * and SECURE_ALL_LOCKS masks from newer kernel headers will + * return EPERM on older kernels. Detect this, and retry with + * the right mask for older (2.6.26-4.2) kernels. + */ + int securebits_ret = prctl(PR_SET_SECUREBITS, + SECURE_ALL_BITS | SECURE_ALL_LOCKS); + if (securebits_ret < 0) { + if (errno == EPERM) { + /* Possibly running on kernel < 4.3. */ + securebits_ret = prctl( + PR_SET_SECUREBITS, + OLD_SECURE_ALL_BITS | OLD_SECURE_ALL_LOCKS); + } + } + if (securebits_ret < 0) + pdie("prctl(PR_SET_SECUREBITS)"); + } + + if (j->flags.no_new_privs) { + /* + * If we're setting no_new_privs, we can drop privileges + * before setting seccomp filter. This way filter policies + * don't need to allow privilege-dropping syscalls. + */ + drop_ugid(j); + drop_caps(j, last_valid_cap); + set_seccomp_filter(j); + } else { + /* + * If we're not setting no_new_privs, + * we need to set seccomp filter *before* dropping privileges. + * WARNING: this means that filter policies *must* allow + * setgroups()/setresgid()/setresuid() for dropping root and + * capget()/capset()/prctl() for dropping caps. + */ + set_seccomp_filter(j); + drop_ugid(j); + drop_caps(j, last_valid_cap); + } + + /* + * Select the specified alternate syscall table. The table must not + * block prctl(2) if we're using seccomp as well. + */ + if (j->flags.alt_syscall) { + if (prctl(PR_ALT_SYSCALL, 1, j->alt_syscall_table)) + pdie("prctl(PR_ALT_SYSCALL)"); + } + + /* + * seccomp has to come last since it cuts off all the other + * privilege-dropping syscalls :) + */ + if (j->flags.seccomp && prctl(PR_SET_SECCOMP, 1)) { + if ((errno == EINVAL) && seccomp_can_softfail()) { + warn("seccomp not supported"); + return; + } + pdie("prctl(PR_SET_SECCOMP)"); + } +} + +/* TODO(wad): will visibility affect this variable? */ +static int init_exitstatus = 0; + +void init_term(int __attribute__ ((unused)) sig) +{ + _exit(init_exitstatus); +} + +void init(pid_t rootpid) +{ + pid_t pid; + int status; + /* So that we exit with the right status. */ + signal(SIGTERM, init_term); + /* TODO(wad): self jail with seccomp filters here. */ + while ((pid = wait(&status)) > 0) { + /* + * This loop will only end when either there are no processes + * left inside our pid namespace or we get a signal. + */ + if (pid == rootpid) + init_exitstatus = status; + } + if (!WIFEXITED(init_exitstatus)) + _exit(MINIJAIL_ERR_INIT); + _exit(WEXITSTATUS(init_exitstatus)); +} + +int API minijail_from_fd(int fd, struct minijail *j) +{ + size_t sz = 0; + size_t bytes = read(fd, &sz, sizeof(sz)); + char *buf; + int r; + if (sizeof(sz) != bytes) + return -EINVAL; + if (sz > USHRT_MAX) /* arbitrary sanity check */ + return -E2BIG; + buf = malloc(sz); + if (!buf) + return -ENOMEM; + bytes = read(fd, buf, sz); + if (bytes != sz) { + free(buf); + return -EINVAL; + } + r = minijail_unmarshal(j, buf, sz); + free(buf); + return r; +} + +int API minijail_to_fd(struct minijail *j, int fd) +{ + char *buf; + size_t sz = minijail_size(j); + ssize_t written; + int r; + + if (!sz) + return -EINVAL; + buf = malloc(sz); + r = minijail_marshal(j, buf, sz); + if (r) { + free(buf); + return r; + } + /* Sends [size][minijail]. */ + written = write(fd, &sz, sizeof(sz)); + if (written != sizeof(sz)) { + free(buf); + return -EFAULT; + } + written = write(fd, buf, sz); + if (written < 0 || (size_t) written != sz) { + free(buf); + return -EFAULT; + } + free(buf); + return 0; +} + +int setup_preload(void) +{ +#if defined(__ANDROID__) + /* Don't use LDPRELOAD on Brillo. */ + return 0; +#else + char *oldenv = getenv(kLdPreloadEnvVar) ? : ""; + char *newenv = malloc(strlen(oldenv) + 2 + strlen(PRELOADPATH)); + if (!newenv) + return -ENOMEM; + + /* Only insert a separating space if we have something to separate... */ + sprintf(newenv, "%s%s%s", oldenv, strlen(oldenv) ? " " : "", + PRELOADPATH); + + /* setenv() makes a copy of the string we give it. */ + setenv(kLdPreloadEnvVar, newenv, 1); + free(newenv); + return 0; +#endif +} + +int setup_pipe(int fds[2]) +{ + int r = pipe(fds); + char fd_buf[11]; + if (r) + return r; + r = snprintf(fd_buf, sizeof(fd_buf), "%d", fds[0]); + if (r <= 0) + return -EINVAL; + setenv(kFdEnvVar, fd_buf, 1); + return 0; +} + +int setup_pipe_end(int fds[2], size_t index) +{ + if (index > 1) + return -1; + + close(fds[1 - index]); + return fds[index]; +} + +int setup_and_dupe_pipe_end(int fds[2], size_t index, int fd) +{ + if (index > 1) + return -1; + + close(fds[1 - index]); + /* dup2(2) the corresponding end of the pipe into |fd|. */ + return dup2(fds[index], fd); +} + +int minijail_run_internal(struct minijail *j, const char *filename, + char *const argv[], pid_t *pchild_pid, + int *pstdin_fd, int *pstdout_fd, int *pstderr_fd, + int use_preload); + +int API minijail_run(struct minijail *j, const char *filename, + char *const argv[]) +{ + return minijail_run_internal(j, filename, argv, NULL, NULL, NULL, NULL, + true); +} + +int API minijail_run_pid(struct minijail *j, const char *filename, + char *const argv[], pid_t *pchild_pid) +{ + return minijail_run_internal(j, filename, argv, pchild_pid, + NULL, NULL, NULL, true); +} + +int API minijail_run_pipe(struct minijail *j, const char *filename, + char *const argv[], int *pstdin_fd) +{ + return minijail_run_internal(j, filename, argv, NULL, pstdin_fd, + NULL, NULL, true); +} + +int API minijail_run_pid_pipes(struct minijail *j, const char *filename, + char *const argv[], pid_t *pchild_pid, + int *pstdin_fd, int *pstdout_fd, int *pstderr_fd) +{ + return minijail_run_internal(j, filename, argv, pchild_pid, + pstdin_fd, pstdout_fd, pstderr_fd, true); +} + +int API minijail_run_no_preload(struct minijail *j, const char *filename, + char *const argv[]) +{ + return minijail_run_internal(j, filename, argv, NULL, NULL, NULL, NULL, + false); +} + +int API minijail_run_pid_pipes_no_preload(struct minijail *j, + const char *filename, + char *const argv[], + pid_t *pchild_pid, + int *pstdin_fd, int *pstdout_fd, + int *pstderr_fd) +{ + return minijail_run_internal(j, filename, argv, pchild_pid, + pstdin_fd, pstdout_fd, pstderr_fd, false); +} + +int minijail_run_internal(struct minijail *j, const char *filename, + char *const argv[], pid_t *pchild_pid, + int *pstdin_fd, int *pstdout_fd, int *pstderr_fd, + int use_preload) +{ + char *oldenv, *oldenv_copy = NULL; + pid_t child_pid; + int pipe_fds[2]; + int stdin_fds[2]; + int stdout_fds[2]; + int stderr_fds[2]; + int child_sync_pipe_fds[2]; + int sync_child = 0; + int ret; + /* We need to remember this across the minijail_preexec() call. */ + int pid_namespace = j->flags.pids; + int do_init = j->flags.do_init; + + if (use_preload) { + oldenv = getenv(kLdPreloadEnvVar); + if (oldenv) { + oldenv_copy = strdup(oldenv); + if (!oldenv_copy) + return -ENOMEM; + } + + if (setup_preload()) + return -EFAULT; + } + + if (!use_preload) { + if (j->flags.use_caps && j->caps != 0) + die("non-empty capabilities are not supported without LD_PRELOAD"); + } + + /* + * Make the process group ID of this process equal to its PID, so that + * both the Minijail process and the jailed process can be killed + * together. + * Don't fail on EPERM, since setpgid(0, 0) can only EPERM when + * the process is already a process group leader. + */ + if (setpgid(0 /* use calling PID */, 0 /* make PGID = PID */)) { + if (errno != EPERM) { + pdie("setpgid(0, 0)"); + } + } + + if (use_preload) { + /* + * Before we fork(2) and execve(2) the child process, we need + * to open a pipe(2) to send the minijail configuration over. + */ + if (setup_pipe(pipe_fds)) + return -EFAULT; + } + + /* + * If we want to write to the child process' standard input, + * create the pipe(2) now. + */ + if (pstdin_fd) { + if (pipe(stdin_fds)) + return -EFAULT; + } + + /* + * If we want to read from the child process' standard output, + * create the pipe(2) now. + */ + if (pstdout_fd) { + if (pipe(stdout_fds)) + return -EFAULT; + } + + /* + * If we want to read from the child process' standard error, + * create the pipe(2) now. + */ + if (pstderr_fd) { + if (pipe(stderr_fds)) + return -EFAULT; + } + + /* + * If we want to set up a new uid/gid map in the user namespace, + * or if we need to add the child process to cgroups, create the pipe(2) + * to sync between parent and child. + */ + if (j->flags.userns || j->flags.cgroups) { + sync_child = 1; + if (pipe(child_sync_pipe_fds)) + return -EFAULT; + } + + /* + * Use sys_clone() if and only if we're creating a pid namespace. + * + * tl;dr: WARNING: do not mix pid namespaces and multithreading. + * + * In multithreaded programs, there are a bunch of locks inside libc, + * some of which may be held by other threads at the time that we call + * minijail_run_pid(). If we call fork(), glibc does its level best to + * ensure that we hold all of these locks before it calls clone() + * internally and drop them after clone() returns, but when we call + * sys_clone(2) directly, all that gets bypassed and we end up with a + * child address space where some of libc's important locks are held by + * other threads (which did not get cloned, and hence will never release + * those locks). This is okay so long as we call exec() immediately + * after, but a bunch of seemingly-innocent libc functions like setenv() + * take locks. + * + * Hence, only call sys_clone() if we need to, in order to get at pid + * namespacing. If we follow this path, the child's address space might + * have broken locks; you may only call functions that do not acquire + * any locks. + * + * Unfortunately, fork() acquires every lock it can get its hands on, as + * previously detailed, so this function is highly likely to deadlock + * later on (see "deadlock here") if we're multithreaded. + * + * We might hack around this by having the clone()d child (init of the + * pid namespace) return directly, rather than leaving the clone()d + * process hanging around to be init for the new namespace (and having + * its fork()ed child return in turn), but that process would be + * crippled with its libc locks potentially broken. We might try + * fork()ing in the parent before we clone() to ensure that we own all + * the locks, but then we have to have the forked child hanging around + * consuming resources (and possibly having file descriptors / shared + * memory regions / etc attached). We'd need to keep the child around to + * avoid having its children get reparented to init. + * + * TODO(ellyjones): figure out if the "forked child hanging around" + * problem is fixable or not. It would be nice if we worked in this + * case. + */ + if (pid_namespace) { + int clone_flags = CLONE_NEWPID | SIGCHLD; + if (j->flags.userns) + clone_flags |= CLONE_NEWUSER; + child_pid = syscall(SYS_clone, clone_flags, NULL); + } else { + child_pid = fork(); + } + + if (child_pid < 0) { + if (use_preload) { + free(oldenv_copy); + } + die("failed to fork child"); + } + + if (child_pid) { + if (use_preload) { + /* Restore parent's LD_PRELOAD. */ + if (oldenv_copy) { + setenv(kLdPreloadEnvVar, oldenv_copy, 1); + free(oldenv_copy); + } else { + unsetenv(kLdPreloadEnvVar); + } + unsetenv(kFdEnvVar); + } + + j->initpid = child_pid; + + if (j->flags.pid_file) + write_pid_file_or_die(j); + + if (j->flags.cgroups) + add_to_cgroups_or_die(j); + + if (j->flags.userns) + write_ugid_maps_or_die(j); + + if (sync_child) + parent_setup_complete(child_sync_pipe_fds); + + if (use_preload) { + /* Send marshalled minijail. */ + close(pipe_fds[0]); /* read endpoint */ + ret = minijail_to_fd(j, pipe_fds[1]); + close(pipe_fds[1]); /* write endpoint */ + if (ret) { + kill(j->initpid, SIGKILL); + die("failed to send marshalled minijail"); + } + } + + if (pchild_pid) + *pchild_pid = child_pid; + + /* + * If we want to write to the child process' standard input, + * set up the write end of the pipe. + */ + if (pstdin_fd) + *pstdin_fd = setup_pipe_end(stdin_fds, + 1 /* write end */); + + /* + * If we want to read from the child process' standard output, + * set up the read end of the pipe. + */ + if (pstdout_fd) + *pstdout_fd = setup_pipe_end(stdout_fds, + 0 /* read end */); + + /* + * If we want to read from the child process' standard error, + * set up the read end of the pipe. + */ + if (pstderr_fd) + *pstderr_fd = setup_pipe_end(stderr_fds, + 0 /* read end */); + + return 0; + } + /* Child process. */ + free(oldenv_copy); + + if (j->flags.reset_signal_mask) { + sigset_t signal_mask; + if (sigemptyset(&signal_mask) != 0) + pdie("sigemptyset failed"); + if (sigprocmask(SIG_SETMASK, &signal_mask, NULL) != 0) + pdie("sigprocmask failed"); + } + + if (sync_child) + wait_for_parent_setup(child_sync_pipe_fds); + + if (j->flags.userns) + enter_user_namespace(j); + + /* + * If we want to write to the jailed process' standard input, + * set up the read end of the pipe. + */ + if (pstdin_fd) { + if (setup_and_dupe_pipe_end(stdin_fds, 0 /* read end */, + STDIN_FILENO) < 0) + die("failed to set up stdin pipe"); + } + + /* + * If we want to read from the jailed process' standard output, + * set up the write end of the pipe. + */ + if (pstdout_fd) { + if (setup_and_dupe_pipe_end(stdout_fds, 1 /* write end */, + STDOUT_FILENO) < 0) + die("failed to set up stdout pipe"); + } + + /* + * If we want to read from the jailed process' standard error, + * set up the write end of the pipe. + */ + if (pstderr_fd) { + if (setup_and_dupe_pipe_end(stderr_fds, 1 /* write end */, + STDERR_FILENO) < 0) + die("failed to set up stderr pipe"); + } + + /* If running an init program, let it decide when/how to mount /proc. */ + if (pid_namespace && !do_init) + j->flags.remount_proc_ro = 0; + + if (use_preload) { + /* Strip out flags that cannot be inherited across execve(2). */ + minijail_preexec(j); + } else { + /* + * If not using LD_PRELOAD, do all jailing before execve(2). + * Note that PID namespaces can only be entered on fork(2), + * so that flag is still cleared. + */ + j->flags.pids = 0; + } + /* Jail this process, then execve(2) the target. */ + minijail_enter(j); + + if (pid_namespace && do_init) { + /* + * pid namespace: this process will become init inside the new + * namespace. We don't want all programs we might exec to have + * to know how to be init. Normally (do_init == 1) we fork off + * a child to actually run the program. If |do_init == 0|, we + * let the program keep pid 1 and be init. + * + * If we're multithreaded, we'll probably deadlock here. See + * WARNING above. + */ + child_pid = fork(); + if (child_pid < 0) { + _exit(child_pid); + } else if (child_pid > 0) { + /* Best effort. Don't bother checking the return value. */ + prctl(PR_SET_NAME, "minijail-init"); + init(child_pid); /* Never returns. */ + } + } + + /* + * If we aren't pid-namespaced, or the jailed program asked to be init: + * calling process + * -> execve()-ing process + * If we are: + * calling process + * -> init()-ing process + * -> execve()-ing process + */ + ret = execve(filename, argv, environ); + if (ret == -1) { + pwarn("execve(%s) failed", filename); + } + _exit(ret); +} + +int API minijail_kill(struct minijail *j) +{ + int st; + if (kill(j->initpid, SIGTERM)) + return -errno; + if (waitpid(j->initpid, &st, 0) < 0) + return -errno; + return st; +} + +int API minijail_wait(struct minijail *j) +{ + int st; + if (waitpid(j->initpid, &st, 0) < 0) + return -errno; + + if (!WIFEXITED(st)) { + int error_status = st; + if (WIFSIGNALED(st)) { + int signum = WTERMSIG(st); + warn("child process %d received signal %d", + j->initpid, signum); + /* + * We return MINIJAIL_ERR_JAIL if the process received + * SIGSYS, which happens when a syscall is blocked by + * seccomp filters. + * If not, we do what bash(1) does: + * $? = 128 + signum + */ + if (signum == SIGSYS) { + error_status = MINIJAIL_ERR_JAIL; + } else { + error_status = 128 + signum; + } + } + return error_status; + } + + int exit_status = WEXITSTATUS(st); + if (exit_status != 0) + info("child process %d exited with status %d", + j->initpid, exit_status); + + return exit_status; +} + +void API minijail_destroy(struct minijail *j) +{ + size_t i; + + if (j->flags.seccomp_filter && j->filter_prog) { + free(j->filter_prog->filter); + free(j->filter_prog); + } + while (j->mounts_head) { + struct mountpoint *m = j->mounts_head; + j->mounts_head = j->mounts_head->next; + free(m->data); + free(m->type); + free(m->dest); + free(m->src); + free(m); + } + j->mounts_tail = NULL; + if (j->user) + free(j->user); + if (j->suppl_gid_list) + free(j->suppl_gid_list); + if (j->chrootdir) + free(j->chrootdir); + if (j->pid_file_path) + free(j->pid_file_path); + if (j->uidmap) + free(j->uidmap); + if (j->gidmap) + free(j->gidmap); + if (j->alt_syscall_table) + free(j->alt_syscall_table); + for (i = 0; i < j->cgroup_count; ++i) + free(j->cgroups[i]); + free(j); +}
diff --git a/minijail/libminijail.h b/minijail/libminijail.h new file mode 100644 index 0000000..62005c8 --- /dev/null +++ b/minijail/libminijail.h
@@ -0,0 +1,266 @@ +/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * The general pattern of use here: + * 1) Construct a minijail with minijail_new() + * 2) Apply the desired restrictions to it + * 3) Enter it, which locks the current process inside it, or: + * 3) Run a process inside it + * 4) Destroy it. + */ + +#ifndef _LIBMINIJAIL_H_ +#define _LIBMINIJAIL_H_ + +#include <stdint.h> +#include <sys/types.h> + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + MINIJAIL_ERR_PRELOAD = 252, + MINIJAIL_ERR_JAIL = 253, + MINIJAIL_ERR_INIT = 254, +}; + +struct minijail; + +/* Allocates a new minijail with no restrictions. */ +struct minijail *minijail_new(void); + +/* + * These functions add restrictions to the minijail. They are not applied until + * minijail_enter() is called. See the documentation in minijail0.1 for + * explanations in detail of what the restrictions do. + */ +void minijail_change_uid(struct minijail *j, uid_t uid); +void minijail_change_gid(struct minijail *j, gid_t gid); +/* Copies |list|. */ +void minijail_set_supplementary_gids(struct minijail *j, size_t size, + const gid_t *list); +/* Stores user to change to and copies |user| for internal consistency. */ +int minijail_change_user(struct minijail *j, const char *user); +/* Does not take ownership of |group|. */ +int minijail_change_group(struct minijail *j, const char *group); +void minijail_use_seccomp(struct minijail *j); +void minijail_no_new_privs(struct minijail *j); +void minijail_use_seccomp_filter(struct minijail *j); +void minijail_parse_seccomp_filters(struct minijail *j, const char *path); +void minijail_parse_seccomp_filters_from_fd(struct minijail *j, int fd); +void minijail_log_seccomp_filter_failures(struct minijail *j); +/* 'minijail_use_caps' and 'minijail_capbset_drop' are mutually exclusive. */ +void minijail_use_caps(struct minijail *j, uint64_t capmask); +void minijail_capbset_drop(struct minijail *j, uint64_t capmask); +void minijail_reset_signal_mask(struct minijail *j); +void minijail_namespace_vfs(struct minijail *j); +void minijail_namespace_enter_vfs(struct minijail *j, const char *ns_path); +/* + * This option is *dangerous* as it negates most of the functionality of + * minijail_namespace_vfs(). You very likely don't need this. + */ +void minijail_skip_remount_private(struct minijail *j); +void minijail_namespace_ipc(struct minijail *j); +void minijail_namespace_net(struct minijail *j); +void minijail_namespace_enter_net(struct minijail *j, const char *ns_path); +void minijail_namespace_cgroups(struct minijail *j); +/* + * Implies namespace_vfs and remount_proc_readonly. + * WARNING: this is NOT THREAD SAFE. See the block comment in </libminijail.c>. + */ +void minijail_namespace_pids(struct minijail *j); +void minijail_namespace_user(struct minijail *j); +int minijail_uidmap(struct minijail *j, const char *uidmap); +int minijail_gidmap(struct minijail *j, const char *gidmap); +void minijail_remount_proc_readonly(struct minijail *j); +void minijail_run_as_init(struct minijail *j); +int minijail_write_pid_file(struct minijail *j, const char *path); +void minijail_inherit_usergroups(struct minijail *j); +/* + * Changes the jailed process's syscall table to the alt_syscall table + * named |table|. + */ +int minijail_use_alt_syscall(struct minijail *j, const char *table); + +/* + * Adds the jailed process to the cgroup given by |path|. |path| should be the + * full path to the cgroups "tasks" file. + * Example: /sys/fs/cgroup/cpu/jailed_procs/tasks adds to the "jailed_procs" cpu + * cgroup. + */ +int minijail_add_to_cgroup(struct minijail *j, const char *path); + +/* + * minijail_enter_chroot: enables chroot() restriction for @j + * @j minijail to apply restriction to + * @dir directory to chroot() to. Owned by caller. + * + * Enters @dir, binding all bind mounts specified with minijail_bind() into + * place. Requires @dir to contain all necessary directories for bind mounts + * (i.e., if you have requested a bind mount at /etc, /etc must exist in @dir.) + * + * Returns 0 on success. + */ +int minijail_enter_chroot(struct minijail *j, const char *dir); +int minijail_enter_pivot_root(struct minijail *j, const char *dir); + +/* + * minijail_get_original_path: returns the path of a given file outside of the + * chroot. + * @j minijail to obtain the path from. + * @chroot_path path inside of the chroot() to. + * + * When executing a binary in a chroot or pivot_root, return path to the binary + * outside of the chroot. + * + * Returns a string containing the path. This must be freed by the caller. + */ +char *minijail_get_original_path(struct minijail *j, const char *chroot_path); + +/* + * minijail_mount_tmp: enables mounting of a tmpfs filesystem on /tmp. + * As be rules of bind mounts, /tmp must exist in chroot. + */ +void minijail_mount_tmp(struct minijail *j); + +/* + * minijail_mount_with_data: when entering minijail @j, + * mounts @src at @dst with @flags and @data. + * @j minijail to bind inside + * @src source to bind + * @dest location to bind (inside chroot) + * @type type of filesystem + * @flags flags passed to mount + * @data data arguments passed to mount(2), e.g. "mode=755" + * + * This may be called multiple times; all mounts will be applied in the order + * of minijail_mount() calls. + */ +int minijail_mount_with_data(struct minijail *j, const char *src, + const char *dest, const char *type, + unsigned long flags, const char *data); + +/* + * minijail_mount: when entering minijail @j, mounts @src at @dst with @flags + * @j minijail to bind inside + * @src source to bind + * @dest location to bind (inside chroot) + * @type type of filesystem + * @flags flags passed to mount + * + * This may be called multiple times; all mounts will be applied in the order + * of minijail_mount() calls. + */ +int minijail_mount(struct minijail *j, const char *src, const char *dest, + const char *type, unsigned long flags); + +/* + * minijail_bind: bind-mounts @src into @j as @dest, optionally writeable + * @j minijail to bind inside + * @src source to bind + * @dest location to bind (inside chroot) + * @writeable 1 if the bind mount should be writeable + * + * This may be called multiple times; all bindings will be applied in the order + * of minijail_bind() calls. + */ +int minijail_bind(struct minijail *j, const char *src, const char *dest, + int writeable); + +/* + * Lock this process into the given minijail. Note that this procedure cannot + * fail, since there is no way to undo privilege-dropping; therefore, if any + * part of the privilege-drop fails, minijail_enter() will abort the entire + * process. + * + * Some restrictions cannot be enabled this way (pid namespaces) and attempting + * to do so will cause an abort. + */ +void minijail_enter(const struct minijail *j); + +/* + * Run the specified command in the given minijail, execve(2)-style. This is + * required if minijail_namespace_pids() was used. + */ +int minijail_run(struct minijail *j, const char *filename, + char *const argv[]); + +/* + * Run the specified command in the given minijail, execve(2)-style. + * Used with static binaries, or on systems without support for LD_PRELOAD. + */ +int minijail_run_no_preload(struct minijail *j, const char *filename, + char *const argv[]); + +/* + * Run the specified command in the given minijail, execve(2)-style. + * Update |*pchild_pid| with the pid of the child. + */ +int minijail_run_pid(struct minijail *j, const char *filename, + char *const argv[], pid_t *pchild_pid); + +/* + * Run the specified command in the given minijail, execve(2)-style. + * Update |*pstdin_fd| with a fd that allows writing to the child's + * standard input. + */ +int minijail_run_pipe(struct minijail *j, const char *filename, + char *const argv[], int *pstdin_fd); + +/* + * Run the specified command in the given minijail, execve(2)-style. + * Update |*pchild_pid| with the pid of the child. + * Update |*pstdin_fd| with a fd that allows writing to the child's + * standard input. + * Update |*pstdout_fd| with a fd that allows reading from the child's + * standard output. + * Update |*pstderr_fd| with a fd that allows reading from the child's + * standard error. + */ +int minijail_run_pid_pipes(struct minijail *j, const char *filename, + char *const argv[], pid_t *pchild_pid, + int *pstdin_fd, int *pstdout_fd, int *pstderr_fd); + +/* + * Run the specified command in the given minijail, execve(2)-style. + * Update |*pchild_pid| with the pid of the child. + * Update |*pstdin_fd| with a fd that allows writing to the child's + * standard input. + * Update |*pstdout_fd| with a fd that allows reading from the child's + * standard output. + * Update |*pstderr_fd| with a fd that allows reading from the child's + * standard error. + * Used with static binaries, or on systems without support for LD_PRELOAD. + */ +int minijail_run_pid_pipes_no_preload(struct minijail *j, const char *filename, + char *const argv[], pid_t *pchild_pid, + int *pstdin_fd, int *pstdout_fd, + int *pstderr_fd); + +/* + * Kill the specified minijail. The minijail must have been created with pid + * namespacing; if it was, all processes inside it are atomically killed. + */ +int minijail_kill(struct minijail *j); + +/* + * Wait for all processes in the specified minijail to exit. Returns the exit + * status of the _first_ process spawned in the jail. + */ +int minijail_wait(struct minijail *j); + +/* + * Frees the given minijail. It does not matter if the process is inside the + * minijail or not. + */ +void minijail_destroy(struct minijail *j); + +#ifdef __cplusplus +}; /* extern "C" */ +#endif + +#endif /* !_LIBMINIJAIL_H_ */
diff --git a/minijail/libminijail.pc.in b/minijail/libminijail.pc.in new file mode 100644 index 0000000..29f7c77 --- /dev/null +++ b/minijail/libminijail.pc.in
@@ -0,0 +1,8 @@ +bslot=@BSLOT@ +include_dir=@INCLUDE_DIR@ + +Name: libminijail +Description: Minijail shared library. +Version: ${bslot} +CFlags: -I${include_dir} +Libs: -lminijail
diff --git a/minijail/libminijail_unittest.c b/minijail/libminijail_unittest.c new file mode 100644 index 0000000..e89c810 --- /dev/null +++ b/minijail/libminijail_unittest.c
@@ -0,0 +1,210 @@ +/* libminijail_unittest.c + * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Test platform independent logic of Minijail. + */ + +#include <errno.h> + +#include <sys/types.h> +#include <sys/wait.h> + +#include "test_harness.h" + +#include "libminijail.h" +#include "libminijail-private.h" +#include "util.h" + +/* Prototypes needed only by test. */ +void *consumebytes(size_t length, char **buf, size_t *buflength); +char *consumestr(char **buf, size_t *buflength); + +/* Silence unused variable warnings. */ +TEST(silence_unused) { + EXPECT_STREQ(kLdPreloadEnvVar, kLdPreloadEnvVar); + EXPECT_STREQ(kFdEnvVar, kFdEnvVar); + EXPECT_STRNE(kFdEnvVar, kLdPreloadEnvVar); +} + +TEST(consumebytes_zero) { + char buf[1024]; + size_t len = sizeof(buf); + char *pos = &buf[0]; + EXPECT_NE(NULL, consumebytes(0, &pos, &len)); + EXPECT_EQ(&buf[0], pos); + EXPECT_EQ(sizeof(buf), len); +} + +TEST(consumebytes_exact) { + char buf[1024]; + size_t len = sizeof(buf); + char *pos = &buf[0]; + /* One past the end since it consumes the whole buffer. */ + char *end = &buf[sizeof(buf)]; + EXPECT_NE(NULL, consumebytes(len, &pos, &len)); + EXPECT_EQ((size_t)0, len); + EXPECT_EQ(end, pos); +} + +TEST(consumebytes_half) { + char buf[1024]; + size_t len = sizeof(buf); + char *pos = &buf[0]; + /* One past the end since it consumes the whole buffer. */ + char *end = &buf[sizeof(buf) / 2]; + EXPECT_NE(NULL, consumebytes(len / 2, &pos, &len)); + EXPECT_EQ(sizeof(buf) / 2, len); + EXPECT_EQ(end, pos); +} + +TEST(consumebytes_toolong) { + char buf[1024]; + size_t len = sizeof(buf); + char *pos = &buf[0]; + /* One past the end since it consumes the whole buffer. */ + EXPECT_EQ(NULL, consumebytes(len + 1, &pos, &len)); + EXPECT_EQ(sizeof(buf), len); + EXPECT_EQ(&buf[0], pos); +} + +TEST(consumestr_zero) { + char buf[1024]; + size_t len = 0; + char *pos = &buf[0]; + memset(buf, 0xff, sizeof(buf)); + EXPECT_EQ(NULL, consumestr(&pos, &len)); + EXPECT_EQ((size_t)0, len); + EXPECT_EQ(&buf[0], pos); +} + +TEST(consumestr_nonul) { + char buf[1024]; + size_t len = sizeof(buf); + char *pos = &buf[0]; + memset(buf, 0xff, sizeof(buf)); + EXPECT_EQ(NULL, consumestr(&pos, &len)); + EXPECT_EQ(sizeof(buf), len); + EXPECT_EQ(&buf[0], pos); +} + +TEST(consumestr_full) { + char buf[1024]; + size_t len = sizeof(buf); + char *pos = &buf[0]; + memset(buf, 0xff, sizeof(buf)); + buf[sizeof(buf)-1] = '\0'; + EXPECT_EQ((void *)buf, consumestr(&pos, &len)); + EXPECT_EQ((size_t)0, len); + EXPECT_EQ(&buf[sizeof(buf)], pos); +} + +TEST(consumestr_trailing_nul) { + char buf[1024]; + size_t len = sizeof(buf) - 1; + char *pos = &buf[0]; + memset(buf, 0xff, sizeof(buf)); + buf[sizeof(buf)-1] = '\0'; + EXPECT_EQ(NULL, consumestr(&pos, &len)); + EXPECT_EQ(sizeof(buf) - 1, len); + EXPECT_EQ(&buf[0], pos); +} + +FIXTURE(marshal) { + char buf[4096]; + struct minijail *m; + struct minijail *j; + size_t size; +}; + +FIXTURE_SETUP(marshal) { + self->m = minijail_new(); + self->j = minijail_new(); + ASSERT_TRUE(self->m && self->j) TH_LOG("allocation failed"); + self->size = minijail_size(self->m); + ASSERT_GT(sizeof(self->buf), self->size) { + TH_LOG("static buffer too small for test"); + } +} + +FIXTURE_TEARDOWN(marshal) { + minijail_destroy(self->m); + minijail_destroy(self->j); +} + +TEST_F(marshal, empty) { + ASSERT_EQ(0, minijail_marshal(self->m, self->buf, sizeof(self->buf))); + EXPECT_EQ(0, minijail_unmarshal(self->j, self->buf, self->size)); +} + +TEST_F(marshal, 0xff) { + memset(self->buf, 0xff, sizeof(self->buf)); + /* Should fail on the first consumestr since a NUL will never be found. */ + EXPECT_EQ(-EINVAL, minijail_unmarshal(self->j, self->buf, sizeof(self->buf))); +} + +TEST(test_minijail_run_pid_pipes_no_preload) { + pid_t pid; + int child_stdin, child_stdout, child_stderr; + int mj_run_ret; + ssize_t write_ret, read_ret; + const size_t buf_len = 128; + char buf[buf_len]; + int status; +#if defined(__ANDROID__) + char filename[] = "/system/bin/cat"; +#else + char filename[] = "/bin/cat"; +#endif + char teststr[] = "test\n"; + size_t teststr_len = strlen(teststr); + char *argv[4]; + + struct minijail *j = minijail_new(); + + argv[0] = filename; + argv[1] = NULL; + mj_run_ret = minijail_run_pid_pipes_no_preload(j, argv[0], argv, + &pid, + &child_stdin, &child_stdout, + NULL); + EXPECT_EQ(mj_run_ret, 0); + + write_ret = write(child_stdin, teststr, teststr_len); + EXPECT_EQ(write_ret, (int)teststr_len); + + read_ret = read(child_stdout, buf, 8); + EXPECT_EQ(read_ret, (int)teststr_len); + buf[teststr_len] = 0; + EXPECT_EQ(strcmp(buf, teststr), 0); + + EXPECT_EQ(kill(pid, SIGTERM), 0); + waitpid(pid, &status, 0); + ASSERT_TRUE(WIFSIGNALED(status)); + EXPECT_EQ(WTERMSIG(status), SIGTERM); + +#if defined(__ANDROID__) + argv[0] = "/system/bin/sh"; +#else + argv[0] = "/bin/sh"; +#endif + argv[1] = "-c"; + argv[2] = "echo test >&2"; + argv[3] = NULL; + mj_run_ret = minijail_run_pid_pipes_no_preload(j, argv[0], argv, &pid, + &child_stdin, &child_stdout, + &child_stderr); + EXPECT_EQ(mj_run_ret, 0); + + read_ret = read(child_stderr, buf, buf_len); + EXPECT_GE(read_ret, (int)teststr_len); + + waitpid(pid, &status, 0); + ASSERT_TRUE(WIFEXITED(status)); + EXPECT_EQ(WEXITSTATUS(status), 0); + + minijail_destroy(j); +} + +TEST_HARNESS_MAIN
diff --git a/minijail/libminijail_unittest.cpp b/minijail/libminijail_unittest.cpp new file mode 100644 index 0000000..4e75bdc --- /dev/null +++ b/minijail/libminijail_unittest.cpp
@@ -0,0 +1,213 @@ +// libminijail_unittest.cpp +// Copyright (C) 2016 The Android Open Source Project +// +// 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. +// +// Test platform independent logic of Minijail using gtest. + +#include <errno.h> + +#include <sys/types.h> +#include <sys/wait.h> + +#include <gtest/gtest.h> + +#include "libminijail.h" +#include "libminijail-private.h" +#include "util.h" + +/* Prototypes needed only by test. */ +void *consumebytes(size_t length, char **buf, size_t *buflength); +char *consumestr(char **buf, size_t *buflength); + +/* Silence unused variable warnings. */ +TEST(silence, silence_unused) { + EXPECT_STREQ(kLdPreloadEnvVar, kLdPreloadEnvVar); + EXPECT_STREQ(kFdEnvVar, kFdEnvVar); + EXPECT_STRNE(kFdEnvVar, kLdPreloadEnvVar); +} + +TEST(consumebytes, zero) { + char buf[1024]; + size_t len = sizeof(buf); + char *pos = &buf[0]; + EXPECT_TRUE(NULL != consumebytes(0, &pos, &len)); + EXPECT_EQ(&buf[0], pos); + EXPECT_EQ(sizeof(buf), len); +} + +TEST(consumebytes, exact) { + char buf[1024]; + size_t len = sizeof(buf); + char *pos = &buf[0]; + /* One past the end since it consumes the whole buffer. */ + char *end = &buf[sizeof(buf)]; + EXPECT_TRUE(NULL != consumebytes(len, &pos, &len)); + EXPECT_EQ((size_t)0, len); + EXPECT_EQ(end, pos); +} + +TEST(consumebytes, half) { + char buf[1024]; + size_t len = sizeof(buf); + char *pos = &buf[0]; + /* One past the end since it consumes the whole buffer. */ + char *end = &buf[sizeof(buf) / 2]; + EXPECT_TRUE(NULL != consumebytes(len / 2, &pos, &len)); + EXPECT_EQ(sizeof(buf) / 2, len); + EXPECT_EQ(end, pos); +} + +TEST(consumebytes, toolong) { + char buf[1024]; + size_t len = sizeof(buf); + char *pos = &buf[0]; + /* One past the end since it consumes the whole buffer. */ + EXPECT_TRUE(NULL == consumebytes(len + 1, &pos, &len)); + EXPECT_EQ(sizeof(buf), len); + EXPECT_EQ(&buf[0], pos); +} + +TEST(consumestr, zero) { + char buf[1024]; + size_t len = 0; + char *pos = &buf[0]; + memset(buf, 0xff, sizeof(buf)); + EXPECT_EQ(NULL, consumestr(&pos, &len)); + EXPECT_EQ((size_t)0, len); + EXPECT_EQ(&buf[0], pos); +} + +TEST(consumestr, nonul) { + char buf[1024]; + size_t len = sizeof(buf); + char *pos = &buf[0]; + memset(buf, 0xff, sizeof(buf)); + EXPECT_EQ(NULL, consumestr(&pos, &len)); + EXPECT_EQ(sizeof(buf), len); + EXPECT_EQ(&buf[0], pos); +} + +TEST(consumestr, full) { + char buf[1024]; + size_t len = sizeof(buf); + char *pos = &buf[0]; + memset(buf, 0xff, sizeof(buf)); + buf[sizeof(buf)-1] = '\0'; + EXPECT_EQ((void *)buf, consumestr(&pos, &len)); + EXPECT_EQ((size_t)0, len); + EXPECT_EQ(&buf[sizeof(buf)], pos); +} + +TEST(consumestr, trailing_nul) { + char buf[1024]; + size_t len = sizeof(buf) - 1; + char *pos = &buf[0]; + memset(buf, 0xff, sizeof(buf)); + buf[sizeof(buf)-1] = '\0'; + EXPECT_EQ(NULL, consumestr(&pos, &len)); + EXPECT_EQ(sizeof(buf) - 1, len); + EXPECT_EQ(&buf[0], pos); +} + +class MarshalTest : public ::testing::Test { + protected: + virtual void SetUp() { + m_ = minijail_new(); + j_ = minijail_new(); + size_ = minijail_size(m_); + } + virtual void TearDown() { + minijail_destroy(m_); + minijail_destroy(j_); + } + + char buf_[4096]; + struct minijail *m_; + struct minijail *j_; + size_t size_; +}; + +TEST_F(MarshalTest, empty) { + ASSERT_EQ(0, minijail_marshal(m_, buf_, sizeof(buf_))); + EXPECT_EQ(0, minijail_unmarshal(j_, buf_, size_)); +} + +TEST_F(MarshalTest, 0xff) { + memset(buf_, 0xff, sizeof(buf_)); + /* Should fail on the first consumestr since a NUL will never be found. */ + EXPECT_EQ(-EINVAL, minijail_unmarshal(j_, buf_, sizeof(buf_))); +} + +TEST(Test, minijail_run_pid_pipes_no_preload) { + pid_t pid; + int child_stdin, child_stdout, child_stderr; + int mj_run_ret; + ssize_t write_ret, read_ret; + const size_t buf_len = 128; + char buf[buf_len]; + int status; +#if defined(__ANDROID__) + char filename[] = "/system/bin/cat"; +#else + char filename[] = "/bin/cat"; +#endif + char teststr[] = "test\n"; + size_t teststr_len = strlen(teststr); + char *argv[4]; + + struct minijail *j = minijail_new(); + + argv[0] = filename; + argv[1] = NULL; + mj_run_ret = minijail_run_pid_pipes_no_preload(j, argv[0], argv, + &pid, + &child_stdin, &child_stdout, + NULL); + EXPECT_EQ(mj_run_ret, 0); + + write_ret = write(child_stdin, teststr, teststr_len); + EXPECT_EQ(write_ret, (int)teststr_len); + + read_ret = read(child_stdout, buf, 8); + EXPECT_EQ(read_ret, (int)teststr_len); + buf[teststr_len] = 0; + EXPECT_EQ(strcmp(buf, teststr), 0); + + EXPECT_EQ(kill(pid, SIGTERM), 0); + waitpid(pid, &status, 0); + ASSERT_TRUE(WIFSIGNALED(status)); + EXPECT_EQ(WTERMSIG(status), SIGTERM); + +#if defined(__ANDROID__) + argv[0] = "/system/bin/sh"; +#else + argv[0] = "/bin/sh"; +#endif + argv[1] = "-c"; + argv[2] = "echo test >&2"; + argv[3] = NULL; + mj_run_ret = minijail_run_pid_pipes_no_preload(j, argv[0], argv, &pid, + &child_stdin, &child_stdout, + &child_stderr); + EXPECT_EQ(mj_run_ret, 0); + + read_ret = read(child_stderr, buf, buf_len); + EXPECT_GE(read_ret, (int)teststr_len); + + waitpid(pid, &status, 0); + ASSERT_TRUE(WIFEXITED(status)); + EXPECT_EQ(WEXITSTATUS(status), 0); + + minijail_destroy(j); +}
diff --git a/minijail/libminijailpreload.c b/minijail/libminijailpreload.c new file mode 100644 index 0000000..a1e376e --- /dev/null +++ b/minijail/libminijailpreload.c
@@ -0,0 +1,147 @@ +/* libminijailpreload.c - preload hack library + * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * This library is preloaded into every program launched by minijail_run(). + * DO NOT EXPORT ANY SYMBOLS FROM THIS LIBRARY. They will replace other symbols + * in the programs it is preloaded into and cause impossible-to-debug failures. + * See the minijail0.1 for a design explanation. + */ + +#include "libminijail.h" +#include "libminijail-private.h" + +#include <dlfcn.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <syslog.h> +#include <unistd.h> + +static int (*real_main) (int, char **, char **); +static void *libc_handle; + +static void die(const char *failed) +{ + syslog(LOG_ERR, "libminijail: %s", failed); + abort(); +} + +static void unset_in_env(char **envp, const char *name) +{ + int i; + for (i = 0; envp[i]; i++) + if (!strncmp(envp[i], name, strlen(name))) + envp[i][0] = '\0'; +} + +/** @brief Fake main(), spliced in before the real call to main() by + * __libc_start_main (see below). + * We get serialized commands from our invoking process over an fd specified + * by an environment variable (kFdEnvVar). The environment variable is a list + * of key=value pairs (see move_commands_to_env); we use them to construct a + * jail, then enter it. + */ +static int fake_main(int argc, char **argv, char **envp) +{ + char *fd_name = getenv(kFdEnvVar); + int fd = -1; + struct minijail *j; + if (geteuid() != getuid() || getegid() != getgid()) { + /* + * If we didn't do this check, an attacker could set kFdEnvVar + * for any setuid program that uses libminijail to cause it to + * get capabilities or a uid it did not expect. + */ + /* TODO(wad): why would libminijail interact here? */ + return MINIJAIL_ERR_PRELOAD; + } + if (!fd_name) + return MINIJAIL_ERR_PRELOAD; + fd = atoi(fd_name); + if (fd < 0) + return MINIJAIL_ERR_PRELOAD; + + j = minijail_new(); + if (!j) + die("preload: out of memory"); + if (minijail_from_fd(fd, j)) + die("preload: failed to parse minijail from parent"); + close(fd); + + /* TODO(ellyjones): this trashes existing preloads, so one can't do: + * LD_PRELOAD="/tmp/test.so libminijailpreload.so" prog; the + * descendants of prog will have no LD_PRELOAD set at all. + */ + unset_in_env(envp, kLdPreloadEnvVar); + /* Strip out flags meant for the parent. */ + minijail_preenter(j); + minijail_enter(j); + minijail_destroy(j); + dlclose(libc_handle); + return real_main(argc, argv, envp); +} + +/** @brief LD_PRELOAD override of __libc_start_main. + * + * It is really best if you do not look too closely at this function. We need + * to ensure that some of our code runs before the target program (see the + * minijail0.1 file in this directory for high-level details about this), and + * the only available place to hook is this function, which is normally + * responsible for calling main(). Our LD_PRELOAD will overwrite the real + * __libc_start_main with this one, so we have to look up the real one from + * libc and invoke it with a pointer to the fake main() we'd like to run before + * the real main(). We can't just run our setup code *here* because + * __libc_start_main is responsible for setting up the C runtime environment, + * so we can't rely on things like malloc() being available yet. + */ + +int API __libc_start_main(int (*main)(int, char **, char **), int argc, + char **ubp_av, void (*init)(void), void (*fini)(void), + void (*rtld_fini)(void), void(*stack_end)) +{ + void *sym; + /* + * This hack is unfortunately required by C99 - casting directly from + * void* to function pointers is left undefined. See POSIX.1-2003, the + * Rationale for the specification of dlsym(), and dlsym(3). This + * deliberately violates strict-aliasing rules, but gcc can't tell. + */ + union { + int (*fn)(int (*main)(int, char **, char **), int argc, + char **ubp_av, void (*init)(void), void (*fini)(void), + void (*rtld_fini)(void), void(*stack_end)); + void *symval; + } real_libc_start_main; + + /* + * We hold this handle for the duration of the real __libc_start_main() + * and drop it just before calling the real main(). + */ + libc_handle = dlopen("libc.so.6", RTLD_NOW); + + if (!libc_handle) { + syslog(LOG_ERR, "can't dlopen() libc"); + /* + * We dare not use abort() here because it will run atexit() + * handlers and try to flush stdio. + */ + _exit(1); + } + sym = dlsym(libc_handle, "__libc_start_main"); + if (!sym) { + syslog(LOG_ERR, "can't find the real __libc_start_main()"); + _exit(1); + } + real_libc_start_main.symval = sym; + real_main = main; + + /* + * Note that we swap fake_main in for main - fake_main knows that it + * should call real_main after it's done. + */ + return real_libc_start_main.fn(fake_main, argc, ubp_av, init, fini, + rtld_fini, stack_end); +}
diff --git a/minijail/libsyscalls.h b/minijail/libsyscalls.h new file mode 100644 index 0000000..f6c3212 --- /dev/null +++ b/minijail/libsyscalls.h
@@ -0,0 +1,16 @@ +/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef MINIJAIL_LIBSYSCALLS_H_ +#define MINIJAIL_LIBSYSCALLS_H_ +#include <sys/types.h> + +struct syscall_entry { + const char *name; + int nr; +}; + +extern const struct syscall_entry syscall_table[]; + +#endif /* MINIJAIL_LIBSYSCALLS_H_ */
diff --git a/minijail/linux-x86/libconstants.gen.c b/minijail/linux-x86/libconstants.gen.c new file mode 100644 index 0000000..36df6b0 --- /dev/null +++ b/minijail/linux-x86/libconstants.gen.c
@@ -0,0 +1,1245 @@ +/* GENERATED BY MAKEFILE */ + +#include <errno.h> +#include <fcntl.h> +#include <linux/prctl.h> +#include <linux/sched.h> +#include <stddef.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "libconstants.h" +const struct constant_entry constant_table[] = { +#ifdef AT_EACCESS + { "AT_EACCESS", (unsigned long) AT_EACCESS }, +#endif // AT_EACCESS +#ifdef AT_REMOVEDIR + { "AT_REMOVEDIR", (unsigned long) AT_REMOVEDIR }, +#endif // AT_REMOVEDIR +#ifdef AT_SYMLINK_FOLLOW + { "AT_SYMLINK_FOLLOW", (unsigned long) AT_SYMLINK_FOLLOW }, +#endif // AT_SYMLINK_FOLLOW +#ifdef AT_SYMLINK_NOFOLLOW + { "AT_SYMLINK_NOFOLLOW", (unsigned long) AT_SYMLINK_NOFOLLOW }, +#endif // AT_SYMLINK_NOFOLLOW +#ifdef BUS_ADRALN + { "BUS_ADRALN", (unsigned long) BUS_ADRALN }, +#endif // BUS_ADRALN +#ifdef BUS_ADRERR + { "BUS_ADRERR", (unsigned long) BUS_ADRERR }, +#endif // BUS_ADRERR +#ifdef BUS_MCEERR_AO + { "BUS_MCEERR_AO", (unsigned long) BUS_MCEERR_AO }, +#endif // BUS_MCEERR_AO +#ifdef BUS_MCEERR_AR + { "BUS_MCEERR_AR", (unsigned long) BUS_MCEERR_AR }, +#endif // BUS_MCEERR_AR +#ifdef BUS_OBJERR + { "BUS_OBJERR", (unsigned long) BUS_OBJERR }, +#endif // BUS_OBJERR +#ifdef CLD_CONTINUED + { "CLD_CONTINUED", (unsigned long) CLD_CONTINUED }, +#endif // CLD_CONTINUED +#ifdef CLD_DUMPED + { "CLD_DUMPED", (unsigned long) CLD_DUMPED }, +#endif // CLD_DUMPED +#ifdef CLD_EXITED + { "CLD_EXITED", (unsigned long) CLD_EXITED }, +#endif // CLD_EXITED +#ifdef CLD_KILLED + { "CLD_KILLED", (unsigned long) CLD_KILLED }, +#endif // CLD_KILLED +#ifdef CLD_STOPPED + { "CLD_STOPPED", (unsigned long) CLD_STOPPED }, +#endif // CLD_STOPPED +#ifdef CLD_TRAPPED + { "CLD_TRAPPED", (unsigned long) CLD_TRAPPED }, +#endif // CLD_TRAPPED +#ifdef CLONE_CHILD_CLEARTID + { "CLONE_CHILD_CLEARTID", (unsigned long) CLONE_CHILD_CLEARTID }, +#endif // CLONE_CHILD_CLEARTID +#ifdef CLONE_CHILD_SETTID + { "CLONE_CHILD_SETTID", (unsigned long) CLONE_CHILD_SETTID }, +#endif // CLONE_CHILD_SETTID +#ifdef CLONE_DETACHED + { "CLONE_DETACHED", (unsigned long) CLONE_DETACHED }, +#endif // CLONE_DETACHED +#ifdef CLONE_FILES + { "CLONE_FILES", (unsigned long) CLONE_FILES }, +#endif // CLONE_FILES +#ifdef CLONE_FS + { "CLONE_FS", (unsigned long) CLONE_FS }, +#endif // CLONE_FS +#ifdef CLONE_IO + { "CLONE_IO", (unsigned long) CLONE_IO }, +#endif // CLONE_IO +#ifdef CLONE_NEWIPC + { "CLONE_NEWIPC", (unsigned long) CLONE_NEWIPC }, +#endif // CLONE_NEWIPC +#ifdef CLONE_NEWNET + { "CLONE_NEWNET", (unsigned long) CLONE_NEWNET }, +#endif // CLONE_NEWNET +#ifdef CLONE_NEWNS + { "CLONE_NEWNS", (unsigned long) CLONE_NEWNS }, +#endif // CLONE_NEWNS +#ifdef CLONE_NEWPID + { "CLONE_NEWPID", (unsigned long) CLONE_NEWPID }, +#endif // CLONE_NEWPID +#ifdef CLONE_NEWUSER + { "CLONE_NEWUSER", (unsigned long) CLONE_NEWUSER }, +#endif // CLONE_NEWUSER +#ifdef CLONE_NEWUTS + { "CLONE_NEWUTS", (unsigned long) CLONE_NEWUTS }, +#endif // CLONE_NEWUTS +#ifdef CLONE_PARENT + { "CLONE_PARENT", (unsigned long) CLONE_PARENT }, +#endif // CLONE_PARENT +#ifdef CLONE_PARENT_SETTID + { "CLONE_PARENT_SETTID", (unsigned long) CLONE_PARENT_SETTID }, +#endif // CLONE_PARENT_SETTID +#ifdef CLONE_PTRACE + { "CLONE_PTRACE", (unsigned long) CLONE_PTRACE }, +#endif // CLONE_PTRACE +#ifdef CLONE_SETTLS + { "CLONE_SETTLS", (unsigned long) CLONE_SETTLS }, +#endif // CLONE_SETTLS +#ifdef CLONE_SIGHAND + { "CLONE_SIGHAND", (unsigned long) CLONE_SIGHAND }, +#endif // CLONE_SIGHAND +#ifdef CLONE_SYSVSEM + { "CLONE_SYSVSEM", (unsigned long) CLONE_SYSVSEM }, +#endif // CLONE_SYSVSEM +#ifdef CLONE_THREAD + { "CLONE_THREAD", (unsigned long) CLONE_THREAD }, +#endif // CLONE_THREAD +#ifdef CLONE_UNTRACED + { "CLONE_UNTRACED", (unsigned long) CLONE_UNTRACED }, +#endif // CLONE_UNTRACED +#ifdef CLONE_VFORK + { "CLONE_VFORK", (unsigned long) CLONE_VFORK }, +#endif // CLONE_VFORK +#ifdef CLONE_VM + { "CLONE_VM", (unsigned long) CLONE_VM }, +#endif // CLONE_VM +#ifdef CSIGNAL + { "CSIGNAL", (unsigned long) CSIGNAL }, +#endif // CSIGNAL +#ifdef E2BIG + { "E2BIG", (unsigned long) E2BIG }, +#endif // E2BIG +#ifdef EACCES + { "EACCES", (unsigned long) EACCES }, +#endif // EACCES +#ifdef EADDRINUSE + { "EADDRINUSE", (unsigned long) EADDRINUSE }, +#endif // EADDRINUSE +#ifdef EADDRNOTAVAIL + { "EADDRNOTAVAIL", (unsigned long) EADDRNOTAVAIL }, +#endif // EADDRNOTAVAIL +#ifdef EADV + { "EADV", (unsigned long) EADV }, +#endif // EADV +#ifdef EAFNOSUPPORT + { "EAFNOSUPPORT", (unsigned long) EAFNOSUPPORT }, +#endif // EAFNOSUPPORT +#ifdef EAGAIN + { "EAGAIN", (unsigned long) EAGAIN }, +#endif // EAGAIN +#ifdef EALREADY + { "EALREADY", (unsigned long) EALREADY }, +#endif // EALREADY +#ifdef EBADE + { "EBADE", (unsigned long) EBADE }, +#endif // EBADE +#ifdef EBADF + { "EBADF", (unsigned long) EBADF }, +#endif // EBADF +#ifdef EBADFD + { "EBADFD", (unsigned long) EBADFD }, +#endif // EBADFD +#ifdef EBADMSG + { "EBADMSG", (unsigned long) EBADMSG }, +#endif // EBADMSG +#ifdef EBADR + { "EBADR", (unsigned long) EBADR }, +#endif // EBADR +#ifdef EBADRQC + { "EBADRQC", (unsigned long) EBADRQC }, +#endif // EBADRQC +#ifdef EBADSLT + { "EBADSLT", (unsigned long) EBADSLT }, +#endif // EBADSLT +#ifdef EBFONT + { "EBFONT", (unsigned long) EBFONT }, +#endif // EBFONT +#ifdef EBUSY + { "EBUSY", (unsigned long) EBUSY }, +#endif // EBUSY +#ifdef ECANCELED + { "ECANCELED", (unsigned long) ECANCELED }, +#endif // ECANCELED +#ifdef ECHILD + { "ECHILD", (unsigned long) ECHILD }, +#endif // ECHILD +#ifdef ECHRNG + { "ECHRNG", (unsigned long) ECHRNG }, +#endif // ECHRNG +#ifdef ECOMM + { "ECOMM", (unsigned long) ECOMM }, +#endif // ECOMM +#ifdef ECONNABORTED + { "ECONNABORTED", (unsigned long) ECONNABORTED }, +#endif // ECONNABORTED +#ifdef ECONNREFUSED + { "ECONNREFUSED", (unsigned long) ECONNREFUSED }, +#endif // ECONNREFUSED +#ifdef ECONNRESET + { "ECONNRESET", (unsigned long) ECONNRESET }, +#endif // ECONNRESET +#ifdef EDEADLK + { "EDEADLK", (unsigned long) EDEADLK }, +#endif // EDEADLK +#ifdef EDEADLOCK + { "EDEADLOCK", (unsigned long) EDEADLOCK }, +#endif // EDEADLOCK +#ifdef EDESTADDRREQ + { "EDESTADDRREQ", (unsigned long) EDESTADDRREQ }, +#endif // EDESTADDRREQ +#ifdef EDOM + { "EDOM", (unsigned long) EDOM }, +#endif // EDOM +#ifdef EDOTDOT + { "EDOTDOT", (unsigned long) EDOTDOT }, +#endif // EDOTDOT +#ifdef EDQUOT + { "EDQUOT", (unsigned long) EDQUOT }, +#endif // EDQUOT +#ifdef EEXIST + { "EEXIST", (unsigned long) EEXIST }, +#endif // EEXIST +#ifdef EFAULT + { "EFAULT", (unsigned long) EFAULT }, +#endif // EFAULT +#ifdef EFBIG + { "EFBIG", (unsigned long) EFBIG }, +#endif // EFBIG +#ifdef EHOSTDOWN + { "EHOSTDOWN", (unsigned long) EHOSTDOWN }, +#endif // EHOSTDOWN +#ifdef EHOSTUNREACH + { "EHOSTUNREACH", (unsigned long) EHOSTUNREACH }, +#endif // EHOSTUNREACH +#ifdef EHWPOISON + { "EHWPOISON", (unsigned long) EHWPOISON }, +#endif // EHWPOISON +#ifdef EIDRM + { "EIDRM", (unsigned long) EIDRM }, +#endif // EIDRM +#ifdef EILSEQ + { "EILSEQ", (unsigned long) EILSEQ }, +#endif // EILSEQ +#ifdef EINPROGRESS + { "EINPROGRESS", (unsigned long) EINPROGRESS }, +#endif // EINPROGRESS +#ifdef EINTR + { "EINTR", (unsigned long) EINTR }, +#endif // EINTR +#ifdef EINVAL + { "EINVAL", (unsigned long) EINVAL }, +#endif // EINVAL +#ifdef EIO + { "EIO", (unsigned long) EIO }, +#endif // EIO +#ifdef EISCONN + { "EISCONN", (unsigned long) EISCONN }, +#endif // EISCONN +#ifdef EISDIR + { "EISDIR", (unsigned long) EISDIR }, +#endif // EISDIR +#ifdef EISNAM + { "EISNAM", (unsigned long) EISNAM }, +#endif // EISNAM +#ifdef EKEYEXPIRED + { "EKEYEXPIRED", (unsigned long) EKEYEXPIRED }, +#endif // EKEYEXPIRED +#ifdef EKEYREJECTED + { "EKEYREJECTED", (unsigned long) EKEYREJECTED }, +#endif // EKEYREJECTED +#ifdef EKEYREVOKED + { "EKEYREVOKED", (unsigned long) EKEYREVOKED }, +#endif // EKEYREVOKED +#ifdef EL2HLT + { "EL2HLT", (unsigned long) EL2HLT }, +#endif // EL2HLT +#ifdef EL2NSYNC + { "EL2NSYNC", (unsigned long) EL2NSYNC }, +#endif // EL2NSYNC +#ifdef EL3HLT + { "EL3HLT", (unsigned long) EL3HLT }, +#endif // EL3HLT +#ifdef EL3RST + { "EL3RST", (unsigned long) EL3RST }, +#endif // EL3RST +#ifdef ELIBACC + { "ELIBACC", (unsigned long) ELIBACC }, +#endif // ELIBACC +#ifdef ELIBBAD + { "ELIBBAD", (unsigned long) ELIBBAD }, +#endif // ELIBBAD +#ifdef ELIBEXEC + { "ELIBEXEC", (unsigned long) ELIBEXEC }, +#endif // ELIBEXEC +#ifdef ELIBMAX + { "ELIBMAX", (unsigned long) ELIBMAX }, +#endif // ELIBMAX +#ifdef ELIBSCN + { "ELIBSCN", (unsigned long) ELIBSCN }, +#endif // ELIBSCN +#ifdef ELNRNG + { "ELNRNG", (unsigned long) ELNRNG }, +#endif // ELNRNG +#ifdef ELOOP + { "ELOOP", (unsigned long) ELOOP }, +#endif // ELOOP +#ifdef EMEDIUMTYPE + { "EMEDIUMTYPE", (unsigned long) EMEDIUMTYPE }, +#endif // EMEDIUMTYPE +#ifdef EMFILE + { "EMFILE", (unsigned long) EMFILE }, +#endif // EMFILE +#ifdef EMLINK + { "EMLINK", (unsigned long) EMLINK }, +#endif // EMLINK +#ifdef EMSGSIZE + { "EMSGSIZE", (unsigned long) EMSGSIZE }, +#endif // EMSGSIZE +#ifdef EMULTIHOP + { "EMULTIHOP", (unsigned long) EMULTIHOP }, +#endif // EMULTIHOP +#ifdef ENAMETOOLONG + { "ENAMETOOLONG", (unsigned long) ENAMETOOLONG }, +#endif // ENAMETOOLONG +#ifdef ENAVAIL + { "ENAVAIL", (unsigned long) ENAVAIL }, +#endif // ENAVAIL +#ifdef ENETDOWN + { "ENETDOWN", (unsigned long) ENETDOWN }, +#endif // ENETDOWN +#ifdef ENETRESET + { "ENETRESET", (unsigned long) ENETRESET }, +#endif // ENETRESET +#ifdef ENETUNREACH + { "ENETUNREACH", (unsigned long) ENETUNREACH }, +#endif // ENETUNREACH +#ifdef ENFILE + { "ENFILE", (unsigned long) ENFILE }, +#endif // ENFILE +#ifdef ENOANO + { "ENOANO", (unsigned long) ENOANO }, +#endif // ENOANO +#ifdef ENOBUFS + { "ENOBUFS", (unsigned long) ENOBUFS }, +#endif // ENOBUFS +#ifdef ENOCSI + { "ENOCSI", (unsigned long) ENOCSI }, +#endif // ENOCSI +#ifdef ENODATA + { "ENODATA", (unsigned long) ENODATA }, +#endif // ENODATA +#ifdef ENODEV + { "ENODEV", (unsigned long) ENODEV }, +#endif // ENODEV +#ifdef ENOENT + { "ENOENT", (unsigned long) ENOENT }, +#endif // ENOENT +#ifdef ENOEXEC + { "ENOEXEC", (unsigned long) ENOEXEC }, +#endif // ENOEXEC +#ifdef ENOKEY + { "ENOKEY", (unsigned long) ENOKEY }, +#endif // ENOKEY +#ifdef ENOLCK + { "ENOLCK", (unsigned long) ENOLCK }, +#endif // ENOLCK +#ifdef ENOLINK + { "ENOLINK", (unsigned long) ENOLINK }, +#endif // ENOLINK +#ifdef ENOMEDIUM + { "ENOMEDIUM", (unsigned long) ENOMEDIUM }, +#endif // ENOMEDIUM +#ifdef ENOMEM + { "ENOMEM", (unsigned long) ENOMEM }, +#endif // ENOMEM +#ifdef ENOMSG + { "ENOMSG", (unsigned long) ENOMSG }, +#endif // ENOMSG +#ifdef ENONET + { "ENONET", (unsigned long) ENONET }, +#endif // ENONET +#ifdef ENOPKG + { "ENOPKG", (unsigned long) ENOPKG }, +#endif // ENOPKG +#ifdef ENOPROTOOPT + { "ENOPROTOOPT", (unsigned long) ENOPROTOOPT }, +#endif // ENOPROTOOPT +#ifdef ENOSPC + { "ENOSPC", (unsigned long) ENOSPC }, +#endif // ENOSPC +#ifdef ENOSR + { "ENOSR", (unsigned long) ENOSR }, +#endif // ENOSR +#ifdef ENOSTR + { "ENOSTR", (unsigned long) ENOSTR }, +#endif // ENOSTR +#ifdef ENOSYS + { "ENOSYS", (unsigned long) ENOSYS }, +#endif // ENOSYS +#ifdef ENOTBLK + { "ENOTBLK", (unsigned long) ENOTBLK }, +#endif // ENOTBLK +#ifdef ENOTCONN + { "ENOTCONN", (unsigned long) ENOTCONN }, +#endif // ENOTCONN +#ifdef ENOTDIR + { "ENOTDIR", (unsigned long) ENOTDIR }, +#endif // ENOTDIR +#ifdef ENOTEMPTY + { "ENOTEMPTY", (unsigned long) ENOTEMPTY }, +#endif // ENOTEMPTY +#ifdef ENOTNAM + { "ENOTNAM", (unsigned long) ENOTNAM }, +#endif // ENOTNAM +#ifdef ENOTRECOVERABLE + { "ENOTRECOVERABLE", (unsigned long) ENOTRECOVERABLE }, +#endif // ENOTRECOVERABLE +#ifdef ENOTSOCK + { "ENOTSOCK", (unsigned long) ENOTSOCK }, +#endif // ENOTSOCK +#ifdef ENOTSUP + { "ENOTSUP", (unsigned long) ENOTSUP }, +#endif // ENOTSUP +#ifdef ENOTTY + { "ENOTTY", (unsigned long) ENOTTY }, +#endif // ENOTTY +#ifdef ENOTUNIQ + { "ENOTUNIQ", (unsigned long) ENOTUNIQ }, +#endif // ENOTUNIQ +#ifdef ENXIO + { "ENXIO", (unsigned long) ENXIO }, +#endif // ENXIO +#ifdef EOPNOTSUPP + { "EOPNOTSUPP", (unsigned long) EOPNOTSUPP }, +#endif // EOPNOTSUPP +#ifdef EOVERFLOW + { "EOVERFLOW", (unsigned long) EOVERFLOW }, +#endif // EOVERFLOW +#ifdef EOWNERDEAD + { "EOWNERDEAD", (unsigned long) EOWNERDEAD }, +#endif // EOWNERDEAD +#ifdef EPERM + { "EPERM", (unsigned long) EPERM }, +#endif // EPERM +#ifdef EPFNOSUPPORT + { "EPFNOSUPPORT", (unsigned long) EPFNOSUPPORT }, +#endif // EPFNOSUPPORT +#ifdef EPIPE + { "EPIPE", (unsigned long) EPIPE }, +#endif // EPIPE +#ifdef EPROTO + { "EPROTO", (unsigned long) EPROTO }, +#endif // EPROTO +#ifdef EPROTONOSUPPORT + { "EPROTONOSUPPORT", (unsigned long) EPROTONOSUPPORT }, +#endif // EPROTONOSUPPORT +#ifdef EPROTOTYPE + { "EPROTOTYPE", (unsigned long) EPROTOTYPE }, +#endif // EPROTOTYPE +#ifdef ERANGE + { "ERANGE", (unsigned long) ERANGE }, +#endif // ERANGE +#ifdef EREMCHG + { "EREMCHG", (unsigned long) EREMCHG }, +#endif // EREMCHG +#ifdef EREMOTE + { "EREMOTE", (unsigned long) EREMOTE }, +#endif // EREMOTE +#ifdef EREMOTEIO + { "EREMOTEIO", (unsigned long) EREMOTEIO }, +#endif // EREMOTEIO +#ifdef ERESTART + { "ERESTART", (unsigned long) ERESTART }, +#endif // ERESTART +#ifdef ERFKILL + { "ERFKILL", (unsigned long) ERFKILL }, +#endif // ERFKILL +#ifdef EROFS + { "EROFS", (unsigned long) EROFS }, +#endif // EROFS +#ifdef ESHUTDOWN + { "ESHUTDOWN", (unsigned long) ESHUTDOWN }, +#endif // ESHUTDOWN +#ifdef ESOCKTNOSUPPORT + { "ESOCKTNOSUPPORT", (unsigned long) ESOCKTNOSUPPORT }, +#endif // ESOCKTNOSUPPORT +#ifdef ESPIPE + { "ESPIPE", (unsigned long) ESPIPE }, +#endif // ESPIPE +#ifdef ESRCH + { "ESRCH", (unsigned long) ESRCH }, +#endif // ESRCH +#ifdef ESRMNT + { "ESRMNT", (unsigned long) ESRMNT }, +#endif // ESRMNT +#ifdef ESTALE + { "ESTALE", (unsigned long) ESTALE }, +#endif // ESTALE +#ifdef ESTRPIPE + { "ESTRPIPE", (unsigned long) ESTRPIPE }, +#endif // ESTRPIPE +#ifdef ETIME + { "ETIME", (unsigned long) ETIME }, +#endif // ETIME +#ifdef ETIMEDOUT + { "ETIMEDOUT", (unsigned long) ETIMEDOUT }, +#endif // ETIMEDOUT +#ifdef ETOOMANYREFS + { "ETOOMANYREFS", (unsigned long) ETOOMANYREFS }, +#endif // ETOOMANYREFS +#ifdef ETXTBSY + { "ETXTBSY", (unsigned long) ETXTBSY }, +#endif // ETXTBSY +#ifdef EUCLEAN + { "EUCLEAN", (unsigned long) EUCLEAN }, +#endif // EUCLEAN +#ifdef EUNATCH + { "EUNATCH", (unsigned long) EUNATCH }, +#endif // EUNATCH +#ifdef EUSERS + { "EUSERS", (unsigned long) EUSERS }, +#endif // EUSERS +#ifdef EWOULDBLOCK + { "EWOULDBLOCK", (unsigned long) EWOULDBLOCK }, +#endif // EWOULDBLOCK +#ifdef EXDEV + { "EXDEV", (unsigned long) EXDEV }, +#endif // EXDEV +#ifdef EXFULL + { "EXFULL", (unsigned long) EXFULL }, +#endif // EXFULL +#ifdef FAPPEND + { "FAPPEND", (unsigned long) FAPPEND }, +#endif // FAPPEND +#ifdef FASYNC + { "FASYNC", (unsigned long) FASYNC }, +#endif // FASYNC +#ifdef FD_CLOEXEC + { "FD_CLOEXEC", (unsigned long) FD_CLOEXEC }, +#endif // FD_CLOEXEC +#ifdef F_DUPFD + { "F_DUPFD", (unsigned long) F_DUPFD }, +#endif // F_DUPFD +#ifdef F_DUPFD_CLOEXEC + { "F_DUPFD_CLOEXEC", (unsigned long) F_DUPFD_CLOEXEC }, +#endif // F_DUPFD_CLOEXEC +#ifdef F_EXLCK + { "F_EXLCK", (unsigned long) F_EXLCK }, +#endif // F_EXLCK +#ifdef FFSYNC + { "FFSYNC", (unsigned long) FFSYNC }, +#endif // FFSYNC +#ifdef F_GETFD + { "F_GETFD", (unsigned long) F_GETFD }, +#endif // F_GETFD +#ifdef F_GETFL + { "F_GETFL", (unsigned long) F_GETFL }, +#endif // F_GETFL +#ifdef F_GETLK + { "F_GETLK", (unsigned long) F_GETLK }, +#endif // F_GETLK +#ifdef F_GETLK64 + { "F_GETLK64", (unsigned long) F_GETLK64 }, +#endif // F_GETLK64 +#ifdef F_LOCK + { "F_LOCK", (unsigned long) F_LOCK }, +#endif // F_LOCK +#ifdef FNDELAY + { "FNDELAY", (unsigned long) FNDELAY }, +#endif // FNDELAY +#ifdef FNONBLOCK + { "FNONBLOCK", (unsigned long) FNONBLOCK }, +#endif // FNONBLOCK +#ifdef F_OK + { "F_OK", (unsigned long) F_OK }, +#endif // F_OK +#ifdef FPE_FLTDIV + { "FPE_FLTDIV", (unsigned long) FPE_FLTDIV }, +#endif // FPE_FLTDIV +#ifdef FPE_FLTINV + { "FPE_FLTINV", (unsigned long) FPE_FLTINV }, +#endif // FPE_FLTINV +#ifdef FPE_FLTOVF + { "FPE_FLTOVF", (unsigned long) FPE_FLTOVF }, +#endif // FPE_FLTOVF +#ifdef FPE_FLTRES + { "FPE_FLTRES", (unsigned long) FPE_FLTRES }, +#endif // FPE_FLTRES +#ifdef FPE_FLTSUB + { "FPE_FLTSUB", (unsigned long) FPE_FLTSUB }, +#endif // FPE_FLTSUB +#ifdef FPE_FLTUND + { "FPE_FLTUND", (unsigned long) FPE_FLTUND }, +#endif // FPE_FLTUND +#ifdef FPE_INTDIV + { "FPE_INTDIV", (unsigned long) FPE_INTDIV }, +#endif // FPE_INTDIV +#ifdef FPE_INTOVF + { "FPE_INTOVF", (unsigned long) FPE_INTOVF }, +#endif // FPE_INTOVF +#ifdef FP_XSTATE_MAGIC1 + { "FP_XSTATE_MAGIC1", (unsigned long) FP_XSTATE_MAGIC1 }, +#endif // FP_XSTATE_MAGIC1 +#ifdef FP_XSTATE_MAGIC2 + { "FP_XSTATE_MAGIC2", (unsigned long) FP_XSTATE_MAGIC2 }, +#endif // FP_XSTATE_MAGIC2 +#ifdef FP_XSTATE_MAGIC2_SIZE + { "FP_XSTATE_MAGIC2_SIZE", (unsigned long) FP_XSTATE_MAGIC2_SIZE }, +#endif // FP_XSTATE_MAGIC2_SIZE +#ifdef F_RDLCK + { "F_RDLCK", (unsigned long) F_RDLCK }, +#endif // F_RDLCK +#ifdef F_SETFD + { "F_SETFD", (unsigned long) F_SETFD }, +#endif // F_SETFD +#ifdef F_SETFL + { "F_SETFL", (unsigned long) F_SETFL }, +#endif // F_SETFL +#ifdef F_SETLK + { "F_SETLK", (unsigned long) F_SETLK }, +#endif // F_SETLK +#ifdef F_SETLK64 + { "F_SETLK64", (unsigned long) F_SETLK64 }, +#endif // F_SETLK64 +#ifdef F_SETLKW64 + { "F_SETLKW64", (unsigned long) F_SETLKW64 }, +#endif // F_SETLKW64 +#ifdef F_SETLKW + { "F_SETLKW", (unsigned long) F_SETLKW }, +#endif // F_SETLKW +#ifdef F_SHLCK + { "F_SHLCK", (unsigned long) F_SHLCK }, +#endif // F_SHLCK +#ifdef F_TEST + { "F_TEST", (unsigned long) F_TEST }, +#endif // F_TEST +#ifdef F_TLOCK + { "F_TLOCK", (unsigned long) F_TLOCK }, +#endif // F_TLOCK +#ifdef F_ULOCK + { "F_ULOCK", (unsigned long) F_ULOCK }, +#endif // F_ULOCK +#ifdef F_UNLCK + { "F_UNLCK", (unsigned long) F_UNLCK }, +#endif // F_UNLCK +#ifdef F_WRLCK + { "F_WRLCK", (unsigned long) F_WRLCK }, +#endif // F_WRLCK +#ifdef ILL_BADSTK + { "ILL_BADSTK", (unsigned long) ILL_BADSTK }, +#endif // ILL_BADSTK +#ifdef ILL_COPROC + { "ILL_COPROC", (unsigned long) ILL_COPROC }, +#endif // ILL_COPROC +#ifdef ILL_ILLADR + { "ILL_ILLADR", (unsigned long) ILL_ILLADR }, +#endif // ILL_ILLADR +#ifdef ILL_ILLOPC + { "ILL_ILLOPC", (unsigned long) ILL_ILLOPC }, +#endif // ILL_ILLOPC +#ifdef ILL_ILLOPN + { "ILL_ILLOPN", (unsigned long) ILL_ILLOPN }, +#endif // ILL_ILLOPN +#ifdef ILL_ILLTRP + { "ILL_ILLTRP", (unsigned long) ILL_ILLTRP }, +#endif // ILL_ILLTRP +#ifdef ILL_PRVOPC + { "ILL_PRVOPC", (unsigned long) ILL_PRVOPC }, +#endif // ILL_PRVOPC +#ifdef ILL_PRVREG + { "ILL_PRVREG", (unsigned long) ILL_PRVREG }, +#endif // ILL_PRVREG +#ifdef LOCK_EX + { "LOCK_EX", (unsigned long) LOCK_EX }, +#endif // LOCK_EX +#ifdef LOCK_NB + { "LOCK_NB", (unsigned long) LOCK_NB }, +#endif // LOCK_NB +#ifdef LOCK_SH + { "LOCK_SH", (unsigned long) LOCK_SH }, +#endif // LOCK_SH +#ifdef LOCK_UN + { "LOCK_UN", (unsigned long) LOCK_UN }, +#endif // LOCK_UN +#ifdef MINSIGSTKSZ + { "MINSIGSTKSZ", (unsigned long) MINSIGSTKSZ }, +#endif // MINSIGSTKSZ +#ifdef NGREG + { "NGREG", (unsigned long) NGREG }, +#endif // NGREG +#ifdef O_ACCMODE + { "O_ACCMODE", (unsigned long) O_ACCMODE }, +#endif // O_ACCMODE +#ifdef O_APPEND + { "O_APPEND", (unsigned long) O_APPEND }, +#endif // O_APPEND +#ifdef O_ASYNC + { "O_ASYNC", (unsigned long) O_ASYNC }, +#endif // O_ASYNC +#ifdef O_CREAT + { "O_CREAT", (unsigned long) O_CREAT }, +#endif // O_CREAT +#ifdef O_EXCL + { "O_EXCL", (unsigned long) O_EXCL }, +#endif // O_EXCL +#ifdef O_FSYNC + { "O_FSYNC", (unsigned long) O_FSYNC }, +#endif // O_FSYNC +#ifdef O_NDELAY + { "O_NDELAY", (unsigned long) O_NDELAY }, +#endif // O_NDELAY +#ifdef O_NOCTTY + { "O_NOCTTY", (unsigned long) O_NOCTTY }, +#endif // O_NOCTTY +#ifdef O_NONBLOCK + { "O_NONBLOCK", (unsigned long) O_NONBLOCK }, +#endif // O_NONBLOCK +#ifdef O_RDONLY + { "O_RDONLY", (unsigned long) O_RDONLY }, +#endif // O_RDONLY +#ifdef O_RDWR + { "O_RDWR", (unsigned long) O_RDWR }, +#endif // O_RDWR +#ifdef O_RSYNC + { "O_RSYNC", (unsigned long) O_RSYNC }, +#endif // O_RSYNC +#ifdef O_SYNC + { "O_SYNC", (unsigned long) O_SYNC }, +#endif // O_SYNC +#ifdef O_TRUNC + { "O_TRUNC", (unsigned long) O_TRUNC }, +#endif // O_TRUNC +#ifdef O_WRONLY + { "O_WRONLY", (unsigned long) O_WRONLY }, +#endif // O_WRONLY +#ifdef POLL_ERR + { "POLL_ERR", (unsigned long) POLL_ERR }, +#endif // POLL_ERR +#ifdef POLL_HUP + { "POLL_HUP", (unsigned long) POLL_HUP }, +#endif // POLL_HUP +#ifdef POLL_IN + { "POLL_IN", (unsigned long) POLL_IN }, +#endif // POLL_IN +#ifdef POLL_MSG + { "POLL_MSG", (unsigned long) POLL_MSG }, +#endif // POLL_MSG +#ifdef POLL_OUT + { "POLL_OUT", (unsigned long) POLL_OUT }, +#endif // POLL_OUT +#ifdef POLL_PRI + { "POLL_PRI", (unsigned long) POLL_PRI }, +#endif // POLL_PRI +#ifdef POSIX_FADV_NORMAL + { "POSIX_FADV_NORMAL", (unsigned long) POSIX_FADV_NORMAL }, +#endif // POSIX_FADV_NORMAL +#ifdef POSIX_FADV_RANDOM + { "POSIX_FADV_RANDOM", (unsigned long) POSIX_FADV_RANDOM }, +#endif // POSIX_FADV_RANDOM +#ifdef POSIX_FADV_SEQUENTIAL + { "POSIX_FADV_SEQUENTIAL", (unsigned long) POSIX_FADV_SEQUENTIAL }, +#endif // POSIX_FADV_SEQUENTIAL +#ifdef POSIX_FADV_WILLNEED + { "POSIX_FADV_WILLNEED", (unsigned long) POSIX_FADV_WILLNEED }, +#endif // POSIX_FADV_WILLNEED +#ifdef PR_CAPBSET_DROP + { "PR_CAPBSET_DROP", (unsigned long) PR_CAPBSET_DROP }, +#endif // PR_CAPBSET_DROP +#ifdef PR_CAPBSET_READ + { "PR_CAPBSET_READ", (unsigned long) PR_CAPBSET_READ }, +#endif // PR_CAPBSET_READ +#ifdef PR_ENDIAN_BIG + { "PR_ENDIAN_BIG", (unsigned long) PR_ENDIAN_BIG }, +#endif // PR_ENDIAN_BIG +#ifdef PR_ENDIAN_LITTLE + { "PR_ENDIAN_LITTLE", (unsigned long) PR_ENDIAN_LITTLE }, +#endif // PR_ENDIAN_LITTLE +#ifdef PR_ENDIAN_PPC_LITTLE + { "PR_ENDIAN_PPC_LITTLE", (unsigned long) PR_ENDIAN_PPC_LITTLE }, +#endif // PR_ENDIAN_PPC_LITTLE +#ifdef PR_FPEMU_NOPRINT + { "PR_FPEMU_NOPRINT", (unsigned long) PR_FPEMU_NOPRINT }, +#endif // PR_FPEMU_NOPRINT +#ifdef PR_FPEMU_SIGFPE + { "PR_FPEMU_SIGFPE", (unsigned long) PR_FPEMU_SIGFPE }, +#endif // PR_FPEMU_SIGFPE +#ifdef PR_FP_EXC_ASYNC + { "PR_FP_EXC_ASYNC", (unsigned long) PR_FP_EXC_ASYNC }, +#endif // PR_FP_EXC_ASYNC +#ifdef PR_FP_EXC_DISABLED + { "PR_FP_EXC_DISABLED", (unsigned long) PR_FP_EXC_DISABLED }, +#endif // PR_FP_EXC_DISABLED +#ifdef PR_FP_EXC_DIV + { "PR_FP_EXC_DIV", (unsigned long) PR_FP_EXC_DIV }, +#endif // PR_FP_EXC_DIV +#ifdef PR_FP_EXC_INV + { "PR_FP_EXC_INV", (unsigned long) PR_FP_EXC_INV }, +#endif // PR_FP_EXC_INV +#ifdef PR_FP_EXC_NONRECOV + { "PR_FP_EXC_NONRECOV", (unsigned long) PR_FP_EXC_NONRECOV }, +#endif // PR_FP_EXC_NONRECOV +#ifdef PR_FP_EXC_OVF + { "PR_FP_EXC_OVF", (unsigned long) PR_FP_EXC_OVF }, +#endif // PR_FP_EXC_OVF +#ifdef PR_FP_EXC_PRECISE + { "PR_FP_EXC_PRECISE", (unsigned long) PR_FP_EXC_PRECISE }, +#endif // PR_FP_EXC_PRECISE +#ifdef PR_FP_EXC_RES + { "PR_FP_EXC_RES", (unsigned long) PR_FP_EXC_RES }, +#endif // PR_FP_EXC_RES +#ifdef PR_FP_EXC_SW_ENABLE + { "PR_FP_EXC_SW_ENABLE", (unsigned long) PR_FP_EXC_SW_ENABLE }, +#endif // PR_FP_EXC_SW_ENABLE +#ifdef PR_FP_EXC_UND + { "PR_FP_EXC_UND", (unsigned long) PR_FP_EXC_UND }, +#endif // PR_FP_EXC_UND +#ifdef PR_GET_CHILD_SUBREAPER + { "PR_GET_CHILD_SUBREAPER", (unsigned long) PR_GET_CHILD_SUBREAPER }, +#endif // PR_GET_CHILD_SUBREAPER +#ifdef PR_GET_DUMPABLE + { "PR_GET_DUMPABLE", (unsigned long) PR_GET_DUMPABLE }, +#endif // PR_GET_DUMPABLE +#ifdef PR_GET_ENDIAN + { "PR_GET_ENDIAN", (unsigned long) PR_GET_ENDIAN }, +#endif // PR_GET_ENDIAN +#ifdef PR_GET_FPEMU + { "PR_GET_FPEMU", (unsigned long) PR_GET_FPEMU }, +#endif // PR_GET_FPEMU +#ifdef PR_GET_FPEXC + { "PR_GET_FPEXC", (unsigned long) PR_GET_FPEXC }, +#endif // PR_GET_FPEXC +#ifdef PR_GET_KEEPCAPS + { "PR_GET_KEEPCAPS", (unsigned long) PR_GET_KEEPCAPS }, +#endif // PR_GET_KEEPCAPS +#ifdef PR_GET_NAME + { "PR_GET_NAME", (unsigned long) PR_GET_NAME }, +#endif // PR_GET_NAME +#ifdef PR_GET_NO_NEW_PRIVS + { "PR_GET_NO_NEW_PRIVS", (unsigned long) PR_GET_NO_NEW_PRIVS }, +#endif // PR_GET_NO_NEW_PRIVS +#ifdef PR_GET_PDEATHSIG + { "PR_GET_PDEATHSIG", (unsigned long) PR_GET_PDEATHSIG }, +#endif // PR_GET_PDEATHSIG +#ifdef PR_GET_SECCOMP + { "PR_GET_SECCOMP", (unsigned long) PR_GET_SECCOMP }, +#endif // PR_GET_SECCOMP +#ifdef PR_GET_SECUREBITS + { "PR_GET_SECUREBITS", (unsigned long) PR_GET_SECUREBITS }, +#endif // PR_GET_SECUREBITS +#ifdef PR_GET_TID_ADDRESS + { "PR_GET_TID_ADDRESS", (unsigned long) PR_GET_TID_ADDRESS }, +#endif // PR_GET_TID_ADDRESS +#ifdef PR_GET_TIMERSLACK + { "PR_GET_TIMERSLACK", (unsigned long) PR_GET_TIMERSLACK }, +#endif // PR_GET_TIMERSLACK +#ifdef PR_GET_TIMING + { "PR_GET_TIMING", (unsigned long) PR_GET_TIMING }, +#endif // PR_GET_TIMING +#ifdef PR_GET_TSC + { "PR_GET_TSC", (unsigned long) PR_GET_TSC }, +#endif // PR_GET_TSC +#ifdef PR_GET_UNALIGN + { "PR_GET_UNALIGN", (unsigned long) PR_GET_UNALIGN }, +#endif // PR_GET_UNALIGN +#ifdef PR_MCE_KILL + { "PR_MCE_KILL", (unsigned long) PR_MCE_KILL }, +#endif // PR_MCE_KILL +#ifdef PR_MCE_KILL_CLEAR + { "PR_MCE_KILL_CLEAR", (unsigned long) PR_MCE_KILL_CLEAR }, +#endif // PR_MCE_KILL_CLEAR +#ifdef PR_MCE_KILL_DEFAULT + { "PR_MCE_KILL_DEFAULT", (unsigned long) PR_MCE_KILL_DEFAULT }, +#endif // PR_MCE_KILL_DEFAULT +#ifdef PR_MCE_KILL_EARLY + { "PR_MCE_KILL_EARLY", (unsigned long) PR_MCE_KILL_EARLY }, +#endif // PR_MCE_KILL_EARLY +#ifdef PR_MCE_KILL_GET + { "PR_MCE_KILL_GET", (unsigned long) PR_MCE_KILL_GET }, +#endif // PR_MCE_KILL_GET +#ifdef PR_MCE_KILL_LATE + { "PR_MCE_KILL_LATE", (unsigned long) PR_MCE_KILL_LATE }, +#endif // PR_MCE_KILL_LATE +#ifdef PR_MCE_KILL_SET + { "PR_MCE_KILL_SET", (unsigned long) PR_MCE_KILL_SET }, +#endif // PR_MCE_KILL_SET +#ifdef PR_SET_CHILD_SUBREAPER + { "PR_SET_CHILD_SUBREAPER", (unsigned long) PR_SET_CHILD_SUBREAPER }, +#endif // PR_SET_CHILD_SUBREAPER +#ifdef PR_SET_DUMPABLE + { "PR_SET_DUMPABLE", (unsigned long) PR_SET_DUMPABLE }, +#endif // PR_SET_DUMPABLE +#ifdef PR_SET_ENDIAN + { "PR_SET_ENDIAN", (unsigned long) PR_SET_ENDIAN }, +#endif // PR_SET_ENDIAN +#ifdef PR_SET_FPEMU + { "PR_SET_FPEMU", (unsigned long) PR_SET_FPEMU }, +#endif // PR_SET_FPEMU +#ifdef PR_SET_FPEXC + { "PR_SET_FPEXC", (unsigned long) PR_SET_FPEXC }, +#endif // PR_SET_FPEXC +#ifdef PR_SET_KEEPCAPS + { "PR_SET_KEEPCAPS", (unsigned long) PR_SET_KEEPCAPS }, +#endif // PR_SET_KEEPCAPS +#ifdef PR_SET_MM + { "PR_SET_MM", (unsigned long) PR_SET_MM }, +#endif // PR_SET_MM +#ifdef PR_SET_MM_ARG_END + { "PR_SET_MM_ARG_END", (unsigned long) PR_SET_MM_ARG_END }, +#endif // PR_SET_MM_ARG_END +#ifdef PR_SET_MM_ARG_START + { "PR_SET_MM_ARG_START", (unsigned long) PR_SET_MM_ARG_START }, +#endif // PR_SET_MM_ARG_START +#ifdef PR_SET_MM_AUXV + { "PR_SET_MM_AUXV", (unsigned long) PR_SET_MM_AUXV }, +#endif // PR_SET_MM_AUXV +#ifdef PR_SET_MM_BRK + { "PR_SET_MM_BRK", (unsigned long) PR_SET_MM_BRK }, +#endif // PR_SET_MM_BRK +#ifdef PR_SET_MM_END_CODE + { "PR_SET_MM_END_CODE", (unsigned long) PR_SET_MM_END_CODE }, +#endif // PR_SET_MM_END_CODE +#ifdef PR_SET_MM_END_DATA + { "PR_SET_MM_END_DATA", (unsigned long) PR_SET_MM_END_DATA }, +#endif // PR_SET_MM_END_DATA +#ifdef PR_SET_MM_ENV_END + { "PR_SET_MM_ENV_END", (unsigned long) PR_SET_MM_ENV_END }, +#endif // PR_SET_MM_ENV_END +#ifdef PR_SET_MM_ENV_START + { "PR_SET_MM_ENV_START", (unsigned long) PR_SET_MM_ENV_START }, +#endif // PR_SET_MM_ENV_START +#ifdef PR_SET_MM_EXE_FILE + { "PR_SET_MM_EXE_FILE", (unsigned long) PR_SET_MM_EXE_FILE }, +#endif // PR_SET_MM_EXE_FILE +#ifdef PR_SET_MM_START_BRK + { "PR_SET_MM_START_BRK", (unsigned long) PR_SET_MM_START_BRK }, +#endif // PR_SET_MM_START_BRK +#ifdef PR_SET_MM_START_CODE + { "PR_SET_MM_START_CODE", (unsigned long) PR_SET_MM_START_CODE }, +#endif // PR_SET_MM_START_CODE +#ifdef PR_SET_MM_START_DATA + { "PR_SET_MM_START_DATA", (unsigned long) PR_SET_MM_START_DATA }, +#endif // PR_SET_MM_START_DATA +#ifdef PR_SET_MM_START_STACK + { "PR_SET_MM_START_STACK", (unsigned long) PR_SET_MM_START_STACK }, +#endif // PR_SET_MM_START_STACK +#ifdef PR_SET_NAME + { "PR_SET_NAME", (unsigned long) PR_SET_NAME }, +#endif // PR_SET_NAME +#ifdef PR_SET_NO_NEW_PRIVS + { "PR_SET_NO_NEW_PRIVS", (unsigned long) PR_SET_NO_NEW_PRIVS }, +#endif // PR_SET_NO_NEW_PRIVS +#ifdef PR_SET_PDEATHSIG + { "PR_SET_PDEATHSIG", (unsigned long) PR_SET_PDEATHSIG }, +#endif // PR_SET_PDEATHSIG +#ifdef PR_SET_PTRACER + { "PR_SET_PTRACER", (unsigned long) PR_SET_PTRACER }, +#endif // PR_SET_PTRACER +#ifdef PR_SET_SECCOMP + { "PR_SET_SECCOMP", (unsigned long) PR_SET_SECCOMP }, +#endif // PR_SET_SECCOMP +#ifdef PR_SET_SECUREBITS + { "PR_SET_SECUREBITS", (unsigned long) PR_SET_SECUREBITS }, +#endif // PR_SET_SECUREBITS +#ifdef PR_SET_TIMERSLACK + { "PR_SET_TIMERSLACK", (unsigned long) PR_SET_TIMERSLACK }, +#endif // PR_SET_TIMERSLACK +#ifdef PR_SET_TIMING + { "PR_SET_TIMING", (unsigned long) PR_SET_TIMING }, +#endif // PR_SET_TIMING +#ifdef PR_SET_TSC + { "PR_SET_TSC", (unsigned long) PR_SET_TSC }, +#endif // PR_SET_TSC +#ifdef PR_SET_UNALIGN + { "PR_SET_UNALIGN", (unsigned long) PR_SET_UNALIGN }, +#endif // PR_SET_UNALIGN +#ifdef PR_TASK_PERF_EVENTS_DISABLE + { "PR_TASK_PERF_EVENTS_DISABLE", (unsigned long) PR_TASK_PERF_EVENTS_DISABLE }, +#endif // PR_TASK_PERF_EVENTS_DISABLE +#ifdef PR_TASK_PERF_EVENTS_ENABLE + { "PR_TASK_PERF_EVENTS_ENABLE", (unsigned long) PR_TASK_PERF_EVENTS_ENABLE }, +#endif // PR_TASK_PERF_EVENTS_ENABLE +#ifdef PR_TIMING_STATISTICAL + { "PR_TIMING_STATISTICAL", (unsigned long) PR_TIMING_STATISTICAL }, +#endif // PR_TIMING_STATISTICAL +#ifdef PR_TIMING_TIMESTAMP + { "PR_TIMING_TIMESTAMP", (unsigned long) PR_TIMING_TIMESTAMP }, +#endif // PR_TIMING_TIMESTAMP +#ifdef PR_TSC_ENABLE + { "PR_TSC_ENABLE", (unsigned long) PR_TSC_ENABLE }, +#endif // PR_TSC_ENABLE +#ifdef PR_TSC_SIGSEGV + { "PR_TSC_SIGSEGV", (unsigned long) PR_TSC_SIGSEGV }, +#endif // PR_TSC_SIGSEGV +#ifdef PR_UNALIGN_NOPRINT + { "PR_UNALIGN_NOPRINT", (unsigned long) PR_UNALIGN_NOPRINT }, +#endif // PR_UNALIGN_NOPRINT +#ifdef PR_UNALIGN_SIGBUS + { "PR_UNALIGN_SIGBUS", (unsigned long) PR_UNALIGN_SIGBUS }, +#endif // PR_UNALIGN_SIGBUS +#ifdef R_OK + { "R_OK", (unsigned long) R_OK }, +#endif // R_OK +#ifdef SA_INTERRUPT + { "SA_INTERRUPT", (unsigned long) SA_INTERRUPT }, +#endif // SA_INTERRUPT +#ifdef SA_NOCLDSTOP + { "SA_NOCLDSTOP", (unsigned long) SA_NOCLDSTOP }, +#endif // SA_NOCLDSTOP +#ifdef SA_NOCLDWAIT + { "SA_NOCLDWAIT", (unsigned long) SA_NOCLDWAIT }, +#endif // SA_NOCLDWAIT +#ifdef SA_NODEFER + { "SA_NODEFER", (unsigned long) SA_NODEFER }, +#endif // SA_NODEFER +#ifdef SA_NOMASK + { "SA_NOMASK", (unsigned long) SA_NOMASK }, +#endif // SA_NOMASK +#ifdef SA_ONESHOT + { "SA_ONESHOT", (unsigned long) SA_ONESHOT }, +#endif // SA_ONESHOT +#ifdef SA_ONSTACK + { "SA_ONSTACK", (unsigned long) SA_ONSTACK }, +#endif // SA_ONSTACK +#ifdef SA_RESETHAND + { "SA_RESETHAND", (unsigned long) SA_RESETHAND }, +#endif // SA_RESETHAND +#ifdef SA_RESTART + { "SA_RESTART", (unsigned long) SA_RESTART }, +#endif // SA_RESTART +#ifdef SA_SIGINFO + { "SA_SIGINFO", (unsigned long) SA_SIGINFO }, +#endif // SA_SIGINFO +#ifdef SA_STACK + { "SA_STACK", (unsigned long) SA_STACK }, +#endif // SA_STACK +#ifdef S_BLKSIZE + { "S_BLKSIZE", (unsigned long) S_BLKSIZE }, +#endif // S_BLKSIZE +#ifdef SCHED_BATCH + { "SCHED_BATCH", (unsigned long) SCHED_BATCH }, +#endif // SCHED_BATCH +#ifdef SCHED_FIFO + { "SCHED_FIFO", (unsigned long) SCHED_FIFO }, +#endif // SCHED_FIFO +#ifdef SCHED_IDLE + { "SCHED_IDLE", (unsigned long) SCHED_IDLE }, +#endif // SCHED_IDLE +#ifdef SCHED_NORMAL + { "SCHED_NORMAL", (unsigned long) SCHED_NORMAL }, +#endif // SCHED_NORMAL +#ifdef SCHED_RESET_ON_FORK + { "SCHED_RESET_ON_FORK", (unsigned long) SCHED_RESET_ON_FORK }, +#endif // SCHED_RESET_ON_FORK +#ifdef SCHED_RR + { "SCHED_RR", (unsigned long) SCHED_RR }, +#endif // SCHED_RR +#ifdef SEEK_CUR + { "SEEK_CUR", (unsigned long) SEEK_CUR }, +#endif // SEEK_CUR +#ifdef SEEK_END + { "SEEK_END", (unsigned long) SEEK_END }, +#endif // SEEK_END +#ifdef SEEK_SET + { "SEEK_SET", (unsigned long) SEEK_SET }, +#endif // SEEK_SET +#ifdef SEGV_ACCERR + { "SEGV_ACCERR", (unsigned long) SEGV_ACCERR }, +#endif // SEGV_ACCERR +#ifdef SEGV_MAPERR + { "SEGV_MAPERR", (unsigned long) SEGV_MAPERR }, +#endif // SEGV_MAPERR +#ifdef SI_ASYNCIO + { "SI_ASYNCIO", (unsigned long) SI_ASYNCIO }, +#endif // SI_ASYNCIO +#ifdef SI_ASYNCNL + { "SI_ASYNCNL", (unsigned long) SI_ASYNCNL }, +#endif // SI_ASYNCNL +#ifdef S_IEXEC + { "S_IEXEC", (unsigned long) S_IEXEC }, +#endif // S_IEXEC +#ifdef SIGABRT + { "SIGABRT", (unsigned long) SIGABRT }, +#endif // SIGABRT +#ifdef SIGALRM + { "SIGALRM", (unsigned long) SIGALRM }, +#endif // SIGALRM +#ifdef SIGBUS + { "SIGBUS", (unsigned long) SIGBUS }, +#endif // SIGBUS +#ifdef SIGCHLD + { "SIGCHLD", (unsigned long) SIGCHLD }, +#endif // SIGCHLD +#ifdef SIGCLD + { "SIGCLD", (unsigned long) SIGCLD }, +#endif // SIGCLD +#ifdef SIGCONT + { "SIGCONT", (unsigned long) SIGCONT }, +#endif // SIGCONT +#ifdef SIGEV_NONE + { "SIGEV_NONE", (unsigned long) SIGEV_NONE }, +#endif // SIGEV_NONE +#ifdef SIGEV_SIGNAL + { "SIGEV_SIGNAL", (unsigned long) SIGEV_SIGNAL }, +#endif // SIGEV_SIGNAL +#ifdef SIGEV_THREAD_ID + { "SIGEV_THREAD_ID", (unsigned long) SIGEV_THREAD_ID }, +#endif // SIGEV_THREAD_ID +#ifdef SIGEV_THREAD + { "SIGEV_THREAD", (unsigned long) SIGEV_THREAD }, +#endif // SIGEV_THREAD +#ifdef SIGFPE + { "SIGFPE", (unsigned long) SIGFPE }, +#endif // SIGFPE +#ifdef SIGHUP + { "SIGHUP", (unsigned long) SIGHUP }, +#endif // SIGHUP +#ifdef SIGILL + { "SIGILL", (unsigned long) SIGILL }, +#endif // SIGILL +#ifdef SIGINT + { "SIGINT", (unsigned long) SIGINT }, +#endif // SIGINT +#ifdef SIGIO + { "SIGIO", (unsigned long) SIGIO }, +#endif // SIGIO +#ifdef SIGIOT + { "SIGIOT", (unsigned long) SIGIOT }, +#endif // SIGIOT +#ifdef SIGKILL + { "SIGKILL", (unsigned long) SIGKILL }, +#endif // SIGKILL +#ifdef SIGPIPE + { "SIGPIPE", (unsigned long) SIGPIPE }, +#endif // SIGPIPE +#ifdef SIGPOLL + { "SIGPOLL", (unsigned long) SIGPOLL }, +#endif // SIGPOLL +#ifdef SIGPROF + { "SIGPROF", (unsigned long) SIGPROF }, +#endif // SIGPROF +#ifdef SIGPWR + { "SIGPWR", (unsigned long) SIGPWR }, +#endif // SIGPWR +#ifdef SIGQUIT + { "SIGQUIT", (unsigned long) SIGQUIT }, +#endif // SIGQUIT +#ifdef SIGSEGV + { "SIGSEGV", (unsigned long) SIGSEGV }, +#endif // SIGSEGV +#ifdef SIGSTKFLT + { "SIGSTKFLT", (unsigned long) SIGSTKFLT }, +#endif // SIGSTKFLT +#ifdef SIGSTKSZ + { "SIGSTKSZ", (unsigned long) SIGSTKSZ }, +#endif // SIGSTKSZ +#ifdef SIGSTOP + { "SIGSTOP", (unsigned long) SIGSTOP }, +#endif // SIGSTOP +#ifdef SIGSYS + { "SIGSYS", (unsigned long) SIGSYS }, +#endif // SIGSYS +#ifdef SIGTERM + { "SIGTERM", (unsigned long) SIGTERM }, +#endif // SIGTERM +#ifdef SIGTRAP + { "SIGTRAP", (unsigned long) SIGTRAP }, +#endif // SIGTRAP +#ifdef SIGTSTP + { "SIGTSTP", (unsigned long) SIGTSTP }, +#endif // SIGTSTP +#ifdef SIGTTIN + { "SIGTTIN", (unsigned long) SIGTTIN }, +#endif // SIGTTIN +#ifdef SIGTTOU + { "SIGTTOU", (unsigned long) SIGTTOU }, +#endif // SIGTTOU +#ifdef SIGUNUSED + { "SIGUNUSED", (unsigned long) SIGUNUSED }, +#endif // SIGUNUSED +#ifdef SIGURG + { "SIGURG", (unsigned long) SIGURG }, +#endif // SIGURG +#ifdef SIGUSR1 + { "SIGUSR1", (unsigned long) SIGUSR1 }, +#endif // SIGUSR1 +#ifdef SIGUSR2 + { "SIGUSR2", (unsigned long) SIGUSR2 }, +#endif // SIGUSR2 +#ifdef SIGVTALRM + { "SIGVTALRM", (unsigned long) SIGVTALRM }, +#endif // SIGVTALRM +#ifdef SIGWINCH + { "SIGWINCH", (unsigned long) SIGWINCH }, +#endif // SIGWINCH +#ifdef SIGXCPU + { "SIGXCPU", (unsigned long) SIGXCPU }, +#endif // SIGXCPU +#ifdef SIGXFSZ + { "SIGXFSZ", (unsigned long) SIGXFSZ }, +#endif // SIGXFSZ +#ifdef SI_KERNEL + { "SI_KERNEL", (unsigned long) SI_KERNEL }, +#endif // SI_KERNEL +#ifdef SI_MESGQ + { "SI_MESGQ", (unsigned long) SI_MESGQ }, +#endif // SI_MESGQ +#ifdef SI_QUEUE + { "SI_QUEUE", (unsigned long) SI_QUEUE }, +#endif // SI_QUEUE +#ifdef S_IREAD + { "S_IREAD", (unsigned long) S_IREAD }, +#endif // S_IREAD +#ifdef SI_SIGIO + { "SI_SIGIO", (unsigned long) SI_SIGIO }, +#endif // SI_SIGIO +#ifdef SI_TIMER + { "SI_TIMER", (unsigned long) SI_TIMER }, +#endif // SI_TIMER +#ifdef SI_TKILL + { "SI_TKILL", (unsigned long) SI_TKILL }, +#endif // SI_TKILL +#ifdef SI_USER + { "SI_USER", (unsigned long) SI_USER }, +#endif // SI_USER +#ifdef S_IWRITE + { "S_IWRITE", (unsigned long) S_IWRITE }, +#endif // S_IWRITE +#ifdef SS_DISABLE + { "SS_DISABLE", (unsigned long) SS_DISABLE }, +#endif // SS_DISABLE +#ifdef SS_ONSTACK + { "SS_ONSTACK", (unsigned long) SS_ONSTACK }, +#endif // SS_ONSTACK +#ifdef TRAP_BRKPT + { "TRAP_BRKPT", (unsigned long) TRAP_BRKPT }, +#endif // TRAP_BRKPT +#ifdef TRAP_TRACE + { "TRAP_TRACE", (unsigned long) TRAP_TRACE }, +#endif // TRAP_TRACE +#ifdef W_OK + { "W_OK", (unsigned long) W_OK }, +#endif // W_OK +#ifdef X_OK + { "X_OK", (unsigned long) X_OK }, +#endif // X_OK + { NULL, 0 }, +};
diff --git a/minijail/linux-x86/libsyscalls.gen.c b/minijail/linux-x86/libsyscalls.gen.c new file mode 100644 index 0000000..db00274 --- /dev/null +++ b/minijail/linux-x86/libsyscalls.gen.c
@@ -0,0 +1,961 @@ +/* GENERATED BY MAKEFILE */ +#include <stddef.h> +#include <asm/unistd.h> +#include "libsyscalls.h" +const struct syscall_entry syscall_table[] = { +#ifdef __NR_read +{ "read", __NR_read }, +#endif +#ifdef __NR_write +{ "write", __NR_write }, +#endif +#ifdef __NR_open +{ "open", __NR_open }, +#endif +#ifdef __NR_close +{ "close", __NR_close }, +#endif +#ifdef __NR_stat +{ "stat", __NR_stat }, +#endif +#ifdef __NR_fstat +{ "fstat", __NR_fstat }, +#endif +#ifdef __NR_lstat +{ "lstat", __NR_lstat }, +#endif +#ifdef __NR_poll +{ "poll", __NR_poll }, +#endif +#ifdef __NR_lseek +{ "lseek", __NR_lseek }, +#endif +#ifdef __NR_mmap +{ "mmap", __NR_mmap }, +#endif +#ifdef __NR_mprotect +{ "mprotect", __NR_mprotect }, +#endif +#ifdef __NR_munmap +{ "munmap", __NR_munmap }, +#endif +#ifdef __NR_brk +{ "brk", __NR_brk }, +#endif +#ifdef __NR_rt_sigaction +{ "rt_sigaction", __NR_rt_sigaction }, +#endif +#ifdef __NR_rt_sigprocmask +{ "rt_sigprocmask", __NR_rt_sigprocmask }, +#endif +#ifdef __NR_rt_sigreturn +{ "rt_sigreturn", __NR_rt_sigreturn }, +#endif +#ifdef __NR_ioctl +{ "ioctl", __NR_ioctl }, +#endif +#ifdef __NR_pread64 +{ "pread64", __NR_pread64 }, +#endif +#ifdef __NR_pwrite64 +{ "pwrite64", __NR_pwrite64 }, +#endif +#ifdef __NR_readv +{ "readv", __NR_readv }, +#endif +#ifdef __NR_writev +{ "writev", __NR_writev }, +#endif +#ifdef __NR_access +{ "access", __NR_access }, +#endif +#ifdef __NR_pipe +{ "pipe", __NR_pipe }, +#endif +#ifdef __NR_select +{ "select", __NR_select }, +#endif +#ifdef __NR_sched_yield +{ "sched_yield", __NR_sched_yield }, +#endif +#ifdef __NR_mremap +{ "mremap", __NR_mremap }, +#endif +#ifdef __NR_msync +{ "msync", __NR_msync }, +#endif +#ifdef __NR_mincore +{ "mincore", __NR_mincore }, +#endif +#ifdef __NR_madvise +{ "madvise", __NR_madvise }, +#endif +#ifdef __NR_shmget +{ "shmget", __NR_shmget }, +#endif +#ifdef __NR_shmat +{ "shmat", __NR_shmat }, +#endif +#ifdef __NR_shmctl +{ "shmctl", __NR_shmctl }, +#endif +#ifdef __NR_dup +{ "dup", __NR_dup }, +#endif +#ifdef __NR_dup2 +{ "dup2", __NR_dup2 }, +#endif +#ifdef __NR_pause +{ "pause", __NR_pause }, +#endif +#ifdef __NR_nanosleep +{ "nanosleep", __NR_nanosleep }, +#endif +#ifdef __NR_getitimer +{ "getitimer", __NR_getitimer }, +#endif +#ifdef __NR_alarm +{ "alarm", __NR_alarm }, +#endif +#ifdef __NR_setitimer +{ "setitimer", __NR_setitimer }, +#endif +#ifdef __NR_getpid +{ "getpid", __NR_getpid }, +#endif +#ifdef __NR_sendfile +{ "sendfile", __NR_sendfile }, +#endif +#ifdef __NR_socket +{ "socket", __NR_socket }, +#endif +#ifdef __NR_connect +{ "connect", __NR_connect }, +#endif +#ifdef __NR_accept +{ "accept", __NR_accept }, +#endif +#ifdef __NR_sendto +{ "sendto", __NR_sendto }, +#endif +#ifdef __NR_recvfrom +{ "recvfrom", __NR_recvfrom }, +#endif +#ifdef __NR_sendmsg +{ "sendmsg", __NR_sendmsg }, +#endif +#ifdef __NR_recvmsg +{ "recvmsg", __NR_recvmsg }, +#endif +#ifdef __NR_shutdown +{ "shutdown", __NR_shutdown }, +#endif +#ifdef __NR_bind +{ "bind", __NR_bind }, +#endif +#ifdef __NR_listen +{ "listen", __NR_listen }, +#endif +#ifdef __NR_getsockname +{ "getsockname", __NR_getsockname }, +#endif +#ifdef __NR_getpeername +{ "getpeername", __NR_getpeername }, +#endif +#ifdef __NR_socketpair +{ "socketpair", __NR_socketpair }, +#endif +#ifdef __NR_setsockopt +{ "setsockopt", __NR_setsockopt }, +#endif +#ifdef __NR_getsockopt +{ "getsockopt", __NR_getsockopt }, +#endif +#ifdef __NR_clone +{ "clone", __NR_clone }, +#endif +#ifdef __NR_fork +{ "fork", __NR_fork }, +#endif +#ifdef __NR_vfork +{ "vfork", __NR_vfork }, +#endif +#ifdef __NR_execve +{ "execve", __NR_execve }, +#endif +#ifdef __NR_exit +{ "exit", __NR_exit }, +#endif +#ifdef __NR_wait4 +{ "wait4", __NR_wait4 }, +#endif +#ifdef __NR_kill +{ "kill", __NR_kill }, +#endif +#ifdef __NR_uname +{ "uname", __NR_uname }, +#endif +#ifdef __NR_semget +{ "semget", __NR_semget }, +#endif +#ifdef __NR_semop +{ "semop", __NR_semop }, +#endif +#ifdef __NR_semctl +{ "semctl", __NR_semctl }, +#endif +#ifdef __NR_shmdt +{ "shmdt", __NR_shmdt }, +#endif +#ifdef __NR_msgget +{ "msgget", __NR_msgget }, +#endif +#ifdef __NR_msgsnd +{ "msgsnd", __NR_msgsnd }, +#endif +#ifdef __NR_msgrcv +{ "msgrcv", __NR_msgrcv }, +#endif +#ifdef __NR_msgctl +{ "msgctl", __NR_msgctl }, +#endif +#ifdef __NR_fcntl +{ "fcntl", __NR_fcntl }, +#endif +#ifdef __NR_flock +{ "flock", __NR_flock }, +#endif +#ifdef __NR_fsync +{ "fsync", __NR_fsync }, +#endif +#ifdef __NR_fdatasync +{ "fdatasync", __NR_fdatasync }, +#endif +#ifdef __NR_truncate +{ "truncate", __NR_truncate }, +#endif +#ifdef __NR_ftruncate +{ "ftruncate", __NR_ftruncate }, +#endif +#ifdef __NR_getdents +{ "getdents", __NR_getdents }, +#endif +#ifdef __NR_getcwd +{ "getcwd", __NR_getcwd }, +#endif +#ifdef __NR_chdir +{ "chdir", __NR_chdir }, +#endif +#ifdef __NR_fchdir +{ "fchdir", __NR_fchdir }, +#endif +#ifdef __NR_rename +{ "rename", __NR_rename }, +#endif +#ifdef __NR_mkdir +{ "mkdir", __NR_mkdir }, +#endif +#ifdef __NR_rmdir +{ "rmdir", __NR_rmdir }, +#endif +#ifdef __NR_creat +{ "creat", __NR_creat }, +#endif +#ifdef __NR_link +{ "link", __NR_link }, +#endif +#ifdef __NR_unlink +{ "unlink", __NR_unlink }, +#endif +#ifdef __NR_symlink +{ "symlink", __NR_symlink }, +#endif +#ifdef __NR_readlink +{ "readlink", __NR_readlink }, +#endif +#ifdef __NR_chmod +{ "chmod", __NR_chmod }, +#endif +#ifdef __NR_fchmod +{ "fchmod", __NR_fchmod }, +#endif +#ifdef __NR_chown +{ "chown", __NR_chown }, +#endif +#ifdef __NR_fchown +{ "fchown", __NR_fchown }, +#endif +#ifdef __NR_lchown +{ "lchown", __NR_lchown }, +#endif +#ifdef __NR_umask +{ "umask", __NR_umask }, +#endif +#ifdef __NR_gettimeofday +{ "gettimeofday", __NR_gettimeofday }, +#endif +#ifdef __NR_getrlimit +{ "getrlimit", __NR_getrlimit }, +#endif +#ifdef __NR_getrusage +{ "getrusage", __NR_getrusage }, +#endif +#ifdef __NR_sysinfo +{ "sysinfo", __NR_sysinfo }, +#endif +#ifdef __NR_times +{ "times", __NR_times }, +#endif +#ifdef __NR_ptrace +{ "ptrace", __NR_ptrace }, +#endif +#ifdef __NR_getuid +{ "getuid", __NR_getuid }, +#endif +#ifdef __NR_syslog +{ "syslog", __NR_syslog }, +#endif +#ifdef __NR_getgid +{ "getgid", __NR_getgid }, +#endif +#ifdef __NR_setuid +{ "setuid", __NR_setuid }, +#endif +#ifdef __NR_setgid +{ "setgid", __NR_setgid }, +#endif +#ifdef __NR_geteuid +{ "geteuid", __NR_geteuid }, +#endif +#ifdef __NR_getegid +{ "getegid", __NR_getegid }, +#endif +#ifdef __NR_setpgid +{ "setpgid", __NR_setpgid }, +#endif +#ifdef __NR_getppid +{ "getppid", __NR_getppid }, +#endif +#ifdef __NR_getpgrp +{ "getpgrp", __NR_getpgrp }, +#endif +#ifdef __NR_setsid +{ "setsid", __NR_setsid }, +#endif +#ifdef __NR_setreuid +{ "setreuid", __NR_setreuid }, +#endif +#ifdef __NR_setregid +{ "setregid", __NR_setregid }, +#endif +#ifdef __NR_getgroups +{ "getgroups", __NR_getgroups }, +#endif +#ifdef __NR_setgroups +{ "setgroups", __NR_setgroups }, +#endif +#ifdef __NR_setresuid +{ "setresuid", __NR_setresuid }, +#endif +#ifdef __NR_getresuid +{ "getresuid", __NR_getresuid }, +#endif +#ifdef __NR_setresgid +{ "setresgid", __NR_setresgid }, +#endif +#ifdef __NR_getresgid +{ "getresgid", __NR_getresgid }, +#endif +#ifdef __NR_getpgid +{ "getpgid", __NR_getpgid }, +#endif +#ifdef __NR_setfsuid +{ "setfsuid", __NR_setfsuid }, +#endif +#ifdef __NR_setfsgid +{ "setfsgid", __NR_setfsgid }, +#endif +#ifdef __NR_getsid +{ "getsid", __NR_getsid }, +#endif +#ifdef __NR_capget +{ "capget", __NR_capget }, +#endif +#ifdef __NR_capset +{ "capset", __NR_capset }, +#endif +#ifdef __NR_rt_sigpending +{ "rt_sigpending", __NR_rt_sigpending }, +#endif +#ifdef __NR_rt_sigtimedwait +{ "rt_sigtimedwait", __NR_rt_sigtimedwait }, +#endif +#ifdef __NR_rt_sigqueueinfo +{ "rt_sigqueueinfo", __NR_rt_sigqueueinfo }, +#endif +#ifdef __NR_rt_sigsuspend +{ "rt_sigsuspend", __NR_rt_sigsuspend }, +#endif +#ifdef __NR_sigaltstack +{ "sigaltstack", __NR_sigaltstack }, +#endif +#ifdef __NR_utime +{ "utime", __NR_utime }, +#endif +#ifdef __NR_mknod +{ "mknod", __NR_mknod }, +#endif +#ifdef __NR_uselib +{ "uselib", __NR_uselib }, +#endif +#ifdef __NR_personality +{ "personality", __NR_personality }, +#endif +#ifdef __NR_ustat +{ "ustat", __NR_ustat }, +#endif +#ifdef __NR_statfs +{ "statfs", __NR_statfs }, +#endif +#ifdef __NR_fstatfs +{ "fstatfs", __NR_fstatfs }, +#endif +#ifdef __NR_sysfs +{ "sysfs", __NR_sysfs }, +#endif +#ifdef __NR_getpriority +{ "getpriority", __NR_getpriority }, +#endif +#ifdef __NR_setpriority +{ "setpriority", __NR_setpriority }, +#endif +#ifdef __NR_sched_setparam +{ "sched_setparam", __NR_sched_setparam }, +#endif +#ifdef __NR_sched_getparam +{ "sched_getparam", __NR_sched_getparam }, +#endif +#ifdef __NR_sched_setscheduler +{ "sched_setscheduler", __NR_sched_setscheduler }, +#endif +#ifdef __NR_sched_getscheduler +{ "sched_getscheduler", __NR_sched_getscheduler }, +#endif +#ifdef __NR_sched_get_priority_max +{ "sched_get_priority_max", __NR_sched_get_priority_max }, +#endif +#ifdef __NR_sched_get_priority_min +{ "sched_get_priority_min", __NR_sched_get_priority_min }, +#endif +#ifdef __NR_sched_rr_get_interval +{ "sched_rr_get_interval", __NR_sched_rr_get_interval }, +#endif +#ifdef __NR_mlock +{ "mlock", __NR_mlock }, +#endif +#ifdef __NR_munlock +{ "munlock", __NR_munlock }, +#endif +#ifdef __NR_mlockall +{ "mlockall", __NR_mlockall }, +#endif +#ifdef __NR_munlockall +{ "munlockall", __NR_munlockall }, +#endif +#ifdef __NR_vhangup +{ "vhangup", __NR_vhangup }, +#endif +#ifdef __NR_modify_ldt +{ "modify_ldt", __NR_modify_ldt }, +#endif +#ifdef __NR_pivot_root +{ "pivot_root", __NR_pivot_root }, +#endif +#ifdef __NR__sysctl +{ "_sysctl", __NR__sysctl }, +#endif +#ifdef __NR_prctl +{ "prctl", __NR_prctl }, +#endif +#ifdef __NR_arch_prctl +{ "arch_prctl", __NR_arch_prctl }, +#endif +#ifdef __NR_adjtimex +{ "adjtimex", __NR_adjtimex }, +#endif +#ifdef __NR_setrlimit +{ "setrlimit", __NR_setrlimit }, +#endif +#ifdef __NR_chroot +{ "chroot", __NR_chroot }, +#endif +#ifdef __NR_sync +{ "sync", __NR_sync }, +#endif +#ifdef __NR_acct +{ "acct", __NR_acct }, +#endif +#ifdef __NR_settimeofday +{ "settimeofday", __NR_settimeofday }, +#endif +#ifdef __NR_mount +{ "mount", __NR_mount }, +#endif +#ifdef __NR_umount2 +{ "umount2", __NR_umount2 }, +#endif +#ifdef __NR_swapon +{ "swapon", __NR_swapon }, +#endif +#ifdef __NR_swapoff +{ "swapoff", __NR_swapoff }, +#endif +#ifdef __NR_reboot +{ "reboot", __NR_reboot }, +#endif +#ifdef __NR_sethostname +{ "sethostname", __NR_sethostname }, +#endif +#ifdef __NR_setdomainname +{ "setdomainname", __NR_setdomainname }, +#endif +#ifdef __NR_iopl +{ "iopl", __NR_iopl }, +#endif +#ifdef __NR_ioperm +{ "ioperm", __NR_ioperm }, +#endif +#ifdef __NR_create_module +{ "create_module", __NR_create_module }, +#endif +#ifdef __NR_init_module +{ "init_module", __NR_init_module }, +#endif +#ifdef __NR_delete_module +{ "delete_module", __NR_delete_module }, +#endif +#ifdef __NR_get_kernel_syms +{ "get_kernel_syms", __NR_get_kernel_syms }, +#endif +#ifdef __NR_query_module +{ "query_module", __NR_query_module }, +#endif +#ifdef __NR_quotactl +{ "quotactl", __NR_quotactl }, +#endif +#ifdef __NR_nfsservctl +{ "nfsservctl", __NR_nfsservctl }, +#endif +#ifdef __NR_getpmsg +{ "getpmsg", __NR_getpmsg }, +#endif +#ifdef __NR_putpmsg +{ "putpmsg", __NR_putpmsg }, +#endif +#ifdef __NR_afs_syscall +{ "afs_syscall", __NR_afs_syscall }, +#endif +#ifdef __NR_tuxcall +{ "tuxcall", __NR_tuxcall }, +#endif +#ifdef __NR_security +{ "security", __NR_security }, +#endif +#ifdef __NR_gettid +{ "gettid", __NR_gettid }, +#endif +#ifdef __NR_readahead +{ "readahead", __NR_readahead }, +#endif +#ifdef __NR_setxattr +{ "setxattr", __NR_setxattr }, +#endif +#ifdef __NR_lsetxattr +{ "lsetxattr", __NR_lsetxattr }, +#endif +#ifdef __NR_fsetxattr +{ "fsetxattr", __NR_fsetxattr }, +#endif +#ifdef __NR_getxattr +{ "getxattr", __NR_getxattr }, +#endif +#ifdef __NR_lgetxattr +{ "lgetxattr", __NR_lgetxattr }, +#endif +#ifdef __NR_fgetxattr +{ "fgetxattr", __NR_fgetxattr }, +#endif +#ifdef __NR_listxattr +{ "listxattr", __NR_listxattr }, +#endif +#ifdef __NR_llistxattr +{ "llistxattr", __NR_llistxattr }, +#endif +#ifdef __NR_flistxattr +{ "flistxattr", __NR_flistxattr }, +#endif +#ifdef __NR_removexattr +{ "removexattr", __NR_removexattr }, +#endif +#ifdef __NR_lremovexattr +{ "lremovexattr", __NR_lremovexattr }, +#endif +#ifdef __NR_fremovexattr +{ "fremovexattr", __NR_fremovexattr }, +#endif +#ifdef __NR_tkill +{ "tkill", __NR_tkill }, +#endif +#ifdef __NR_time +{ "time", __NR_time }, +#endif +#ifdef __NR_futex +{ "futex", __NR_futex }, +#endif +#ifdef __NR_sched_setaffinity +{ "sched_setaffinity", __NR_sched_setaffinity }, +#endif +#ifdef __NR_sched_getaffinity +{ "sched_getaffinity", __NR_sched_getaffinity }, +#endif +#ifdef __NR_set_thread_area +{ "set_thread_area", __NR_set_thread_area }, +#endif +#ifdef __NR_io_setup +{ "io_setup", __NR_io_setup }, +#endif +#ifdef __NR_io_destroy +{ "io_destroy", __NR_io_destroy }, +#endif +#ifdef __NR_io_getevents +{ "io_getevents", __NR_io_getevents }, +#endif +#ifdef __NR_io_submit +{ "io_submit", __NR_io_submit }, +#endif +#ifdef __NR_io_cancel +{ "io_cancel", __NR_io_cancel }, +#endif +#ifdef __NR_get_thread_area +{ "get_thread_area", __NR_get_thread_area }, +#endif +#ifdef __NR_lookup_dcookie +{ "lookup_dcookie", __NR_lookup_dcookie }, +#endif +#ifdef __NR_epoll_create +{ "epoll_create", __NR_epoll_create }, +#endif +#ifdef __NR_epoll_ctl_old +{ "epoll_ctl_old", __NR_epoll_ctl_old }, +#endif +#ifdef __NR_epoll_wait_old +{ "epoll_wait_old", __NR_epoll_wait_old }, +#endif +#ifdef __NR_remap_file_pages +{ "remap_file_pages", __NR_remap_file_pages }, +#endif +#ifdef __NR_getdents64 +{ "getdents64", __NR_getdents64 }, +#endif +#ifdef __NR_set_tid_address +{ "set_tid_address", __NR_set_tid_address }, +#endif +#ifdef __NR_restart_syscall +{ "restart_syscall", __NR_restart_syscall }, +#endif +#ifdef __NR_semtimedop +{ "semtimedop", __NR_semtimedop }, +#endif +#ifdef __NR_fadvise64 +{ "fadvise64", __NR_fadvise64 }, +#endif +#ifdef __NR_timer_create +{ "timer_create", __NR_timer_create }, +#endif +#ifdef __NR_timer_settime +{ "timer_settime", __NR_timer_settime }, +#endif +#ifdef __NR_timer_gettime +{ "timer_gettime", __NR_timer_gettime }, +#endif +#ifdef __NR_timer_getoverrun +{ "timer_getoverrun", __NR_timer_getoverrun }, +#endif +#ifdef __NR_timer_delete +{ "timer_delete", __NR_timer_delete }, +#endif +#ifdef __NR_clock_settime +{ "clock_settime", __NR_clock_settime }, +#endif +#ifdef __NR_clock_gettime +{ "clock_gettime", __NR_clock_gettime }, +#endif +#ifdef __NR_clock_getres +{ "clock_getres", __NR_clock_getres }, +#endif +#ifdef __NR_clock_nanosleep +{ "clock_nanosleep", __NR_clock_nanosleep }, +#endif +#ifdef __NR_exit_group +{ "exit_group", __NR_exit_group }, +#endif +#ifdef __NR_epoll_wait +{ "epoll_wait", __NR_epoll_wait }, +#endif +#ifdef __NR_epoll_ctl +{ "epoll_ctl", __NR_epoll_ctl }, +#endif +#ifdef __NR_tgkill +{ "tgkill", __NR_tgkill }, +#endif +#ifdef __NR_utimes +{ "utimes", __NR_utimes }, +#endif +#ifdef __NR_vserver +{ "vserver", __NR_vserver }, +#endif +#ifdef __NR_mbind +{ "mbind", __NR_mbind }, +#endif +#ifdef __NR_set_mempolicy +{ "set_mempolicy", __NR_set_mempolicy }, +#endif +#ifdef __NR_get_mempolicy +{ "get_mempolicy", __NR_get_mempolicy }, +#endif +#ifdef __NR_mq_open +{ "mq_open", __NR_mq_open }, +#endif +#ifdef __NR_mq_unlink +{ "mq_unlink", __NR_mq_unlink }, +#endif +#ifdef __NR_mq_timedsend +{ "mq_timedsend", __NR_mq_timedsend }, +#endif +#ifdef __NR_mq_timedreceive +{ "mq_timedreceive", __NR_mq_timedreceive }, +#endif +#ifdef __NR_mq_notify +{ "mq_notify", __NR_mq_notify }, +#endif +#ifdef __NR_mq_getsetattr +{ "mq_getsetattr", __NR_mq_getsetattr }, +#endif +#ifdef __NR_kexec_load +{ "kexec_load", __NR_kexec_load }, +#endif +#ifdef __NR_waitid +{ "waitid", __NR_waitid }, +#endif +#ifdef __NR_add_key +{ "add_key", __NR_add_key }, +#endif +#ifdef __NR_request_key +{ "request_key", __NR_request_key }, +#endif +#ifdef __NR_keyctl +{ "keyctl", __NR_keyctl }, +#endif +#ifdef __NR_ioprio_set +{ "ioprio_set", __NR_ioprio_set }, +#endif +#ifdef __NR_ioprio_get +{ "ioprio_get", __NR_ioprio_get }, +#endif +#ifdef __NR_inotify_init +{ "inotify_init", __NR_inotify_init }, +#endif +#ifdef __NR_inotify_add_watch +{ "inotify_add_watch", __NR_inotify_add_watch }, +#endif +#ifdef __NR_inotify_rm_watch +{ "inotify_rm_watch", __NR_inotify_rm_watch }, +#endif +#ifdef __NR_migrate_pages +{ "migrate_pages", __NR_migrate_pages }, +#endif +#ifdef __NR_openat +{ "openat", __NR_openat }, +#endif +#ifdef __NR_mkdirat +{ "mkdirat", __NR_mkdirat }, +#endif +#ifdef __NR_mknodat +{ "mknodat", __NR_mknodat }, +#endif +#ifdef __NR_fchownat +{ "fchownat", __NR_fchownat }, +#endif +#ifdef __NR_futimesat +{ "futimesat", __NR_futimesat }, +#endif +#ifdef __NR_newfstatat +{ "newfstatat", __NR_newfstatat }, +#endif +#ifdef __NR_unlinkat +{ "unlinkat", __NR_unlinkat }, +#endif +#ifdef __NR_renameat +{ "renameat", __NR_renameat }, +#endif +#ifdef __NR_linkat +{ "linkat", __NR_linkat }, +#endif +#ifdef __NR_symlinkat +{ "symlinkat", __NR_symlinkat }, +#endif +#ifdef __NR_readlinkat +{ "readlinkat", __NR_readlinkat }, +#endif +#ifdef __NR_fchmodat +{ "fchmodat", __NR_fchmodat }, +#endif +#ifdef __NR_faccessat +{ "faccessat", __NR_faccessat }, +#endif +#ifdef __NR_pselect6 +{ "pselect6", __NR_pselect6 }, +#endif +#ifdef __NR_ppoll +{ "ppoll", __NR_ppoll }, +#endif +#ifdef __NR_unshare +{ "unshare", __NR_unshare }, +#endif +#ifdef __NR_set_robust_list +{ "set_robust_list", __NR_set_robust_list }, +#endif +#ifdef __NR_get_robust_list +{ "get_robust_list", __NR_get_robust_list }, +#endif +#ifdef __NR_splice +{ "splice", __NR_splice }, +#endif +#ifdef __NR_tee +{ "tee", __NR_tee }, +#endif +#ifdef __NR_sync_file_range +{ "sync_file_range", __NR_sync_file_range }, +#endif +#ifdef __NR_vmsplice +{ "vmsplice", __NR_vmsplice }, +#endif +#ifdef __NR_move_pages +{ "move_pages", __NR_move_pages }, +#endif +#ifdef __NR_utimensat +{ "utimensat", __NR_utimensat }, +#endif +#ifdef __NR_epoll_pwait +{ "epoll_pwait", __NR_epoll_pwait }, +#endif +#ifdef __NR_signalfd +{ "signalfd", __NR_signalfd }, +#endif +#ifdef __NR_timerfd_create +{ "timerfd_create", __NR_timerfd_create }, +#endif +#ifdef __NR_eventfd +{ "eventfd", __NR_eventfd }, +#endif +#ifdef __NR_fallocate +{ "fallocate", __NR_fallocate }, +#endif +#ifdef __NR_timerfd_settime +{ "timerfd_settime", __NR_timerfd_settime }, +#endif +#ifdef __NR_timerfd_gettime +{ "timerfd_gettime", __NR_timerfd_gettime }, +#endif +#ifdef __NR_accept4 +{ "accept4", __NR_accept4 }, +#endif +#ifdef __NR_signalfd4 +{ "signalfd4", __NR_signalfd4 }, +#endif +#ifdef __NR_eventfd2 +{ "eventfd2", __NR_eventfd2 }, +#endif +#ifdef __NR_epoll_create1 +{ "epoll_create1", __NR_epoll_create1 }, +#endif +#ifdef __NR_dup3 +{ "dup3", __NR_dup3 }, +#endif +#ifdef __NR_pipe2 +{ "pipe2", __NR_pipe2 }, +#endif +#ifdef __NR_inotify_init1 +{ "inotify_init1", __NR_inotify_init1 }, +#endif +#ifdef __NR_preadv +{ "preadv", __NR_preadv }, +#endif +#ifdef __NR_pwritev +{ "pwritev", __NR_pwritev }, +#endif +#ifdef __NR_rt_tgsigqueueinfo +{ "rt_tgsigqueueinfo", __NR_rt_tgsigqueueinfo }, +#endif +#ifdef __NR_perf_event_open +{ "perf_event_open", __NR_perf_event_open }, +#endif +#ifdef __NR_recvmmsg +{ "recvmmsg", __NR_recvmmsg }, +#endif +#ifdef __NR_fanotify_init +{ "fanotify_init", __NR_fanotify_init }, +#endif +#ifdef __NR_fanotify_mark +{ "fanotify_mark", __NR_fanotify_mark }, +#endif +#ifdef __NR_prlimit64 +{ "prlimit64", __NR_prlimit64 }, +#endif +#ifdef __NR_name_to_handle_at +{ "name_to_handle_at", __NR_name_to_handle_at }, +#endif +#ifdef __NR_open_by_handle_at +{ "open_by_handle_at", __NR_open_by_handle_at }, +#endif +#ifdef __NR_clock_adjtime +{ "clock_adjtime", __NR_clock_adjtime }, +#endif +#ifdef __NR_syncfs +{ "syncfs", __NR_syncfs }, +#endif +#ifdef __NR_sendmmsg +{ "sendmmsg", __NR_sendmmsg }, +#endif +#ifdef __NR_setns +{ "setns", __NR_setns }, +#endif +#ifdef __NR_getcpu +{ "getcpu", __NR_getcpu }, +#endif +#ifdef __NR_process_vm_readv +{ "process_vm_readv", __NR_process_vm_readv }, +#endif +#ifdef __NR_process_vm_writev +{ "process_vm_writev", __NR_process_vm_writev }, +#endif +#ifdef __NR_kcmp +{ "kcmp", __NR_kcmp }, +#endif +#ifdef __NR_finit_module +{ "finit_module", __NR_finit_module }, +#endif +#ifdef __NR_sched_setattr +{ "sched_setattr", __NR_sched_setattr }, +#endif +#ifdef __NR_sched_getattr +{ "sched_getattr", __NR_sched_getattr }, +#endif +#ifdef __NR_renameat2 +{ "renameat2", __NR_renameat2 }, +#endif +#ifdef __NR_seccomp +{ "seccomp", __NR_seccomp }, +#endif + { NULL, -1 }, +};
diff --git a/minijail/minijail0.1 b/minijail/minijail0.1 new file mode 100644 index 0000000..3e077c1 --- /dev/null +++ b/minijail/minijail0.1
@@ -0,0 +1,157 @@ +.TH MINIJAIL0 "1" "March 2016" "Chromium OS" "User Commands" +.SH NAME +minijail0 \- sandbox a process +.SH SYNOPSIS +.B minijail0 +[\fIOPTION\fR]... <\fIprogram\fR> [\fIargs\fR]... +.SH DESCRIPTION +.PP +Runs PROGRAM inside a sandbox. +.TP +\fB-a <table>\fR +Run using the alternate syscall table named \fItable\fR. Only available on kernels +and architectures that support the PR_ALT_SYSCALL option of prctl(2). +.TP +\fB-b <src>,<dest>[,<writeable>] +Bind-mount \fIsrc\fR into the chroot directory at \fIdest\fR, optionally writeable. +.TP +\fB-c <caps>\fR +Restrict capabilities to \fIcaps\fR. When used in conjunction with \fB-u\fR and +\fB-g\fR, this allows a program to have access to only certain parts of root's +default privileges while running as another user and group ID altogether. Note +that these capabilities are not inherited by subprocesses of the process given +capabilities unless those subprocesses have POSIX file capabilities. See +\fBcapabilities\fR(7). +.TP +\fB-C <dir>\fR +Change root (using chroot(2)) to \fIdir\fR. +.TP +\fB-e[file]\fR +Enter a new network namespace, or if \fIfile\fR is specified, enter an existing +network namespace specified by \fIfile\fR which is typically of the form +/proc/<pid>/ns/net. +.TP +\fB-f <file>\fR +Write the pid of the jailed process to \fIfile\fR. +.TP +\fB-G\fR +Inherit all the supplementary groups of the user specified with \fB-u\fR. It +is an error to use this option without having specified a \fBuser name\fR to +\fB-u\fR. +.TP +\fB-g <group>\fR +Change groups to \fIgroup\fR, which may be either a group name or a numeric +group ID. +.TP +\fB-h\fR +Print a help message. +.TP +\fB-H\fR +Print a help message detailing supported system call names for seccomp_filter. +(Other direct numbers may be specified if minijail0 is not in sync with the + host kernel or something like 32/64-bit compatibility issues exist.) +.TP +\fB-I\fR +Run \fIprogram\fR as init (pid 1) inside a new pid namespace (implies \fB-p\fR). +.TP +\fB-k <src>,<dest>,<type>[,<flags>]\fR +Mount \fIsrc\fR, a \fItype\fR filesystem, into the chroot directory at \fIdest\fR, with optional \fIflags\fR. +.TP +\fB-K\fR +Don't mark all existing mounts as MS_PRIVATE. +This option is \fBdangerous\fR as it negates most of the functionality of \fB-v\fR. +You very likely don't need this. +.TP +\fB-l\fR +Run inside a new IPC namespace. This option makes the program's System V IPC +namespace independent. +.TP +\fB-L\fR +Report blocked syscalls to syslog when using seccomp filter. This option will +force certain syscalls to be allowed in order to achieve this, depending on the +system. +.TP +\fB-m "<uid> <loweruid> <count>[,<uid> <loweruid> <count>]"\fR +Set the uid mapping of a user namespace (implies \fB-pU\fR). Same arguments as +\fBnewuidmap(1)\fR. Multiple mappings should be separated by ','. +.TP +\fB-M "<uid> <loweruid> <count>[,<uid> <loweruid> <count>]"\fR +Set the gid mapping of a user namespace (implies \fB-pU\fR). Same arguments as +\fBnewgidmap(1)\fR. Multiple mappings should be separated by ','. +.TP +\fB-n\fR +Set the process's \fIno_new_privs\fR bit. See \fBprctl(2)\fR and the kernel +source file \fIDocumentation/prctl/no_new_privs.txt\fR for more info. +.TP +\fB-N\fR +Run inside a new cgroup namespace. This option runs the program with a cgroup +view showing the program's cgroup as the root. This is only available on v4.6+ +of the Linux kernel. +.TP +\fB-p\fR +Run inside a new PID namespace. This option will make it impossible for the +program to see or affect processes that are not its descendants. This implies +\fB-v\fR and \fB-r\fR, since otherwise the process can see outside its namespace +by inspecting /proc. +.TP +\fB-P <dir>\fR +Set \fIdir\fR as the root fs using \fBpivot_root\fR. Implies \fB-v\fR, not +compatible with \fB-C\fR. +.TP +\fB-r\fR +Remount /proc readonly. This implies \fB-v\fR. Remounting /proc readonly means +that even if the process has write access to a system config knob in /proc +(e.g., in /sys/kernel), it cannot change the value. +.TP +\fB-s\fR +Enable seccomp(2) in mode 1, which restricts the child process to a very small +set of system calls. +.TP +\fB-S <arch-specific seccomp_filter policy file>\fR +Enable seccomp(2) in mode 13 which restricts the child process to a set of +system calls defined in the policy file. Note that system calls often change +names based on the architecture or mode. (uname -m is your friend.) +.TP +\fB-t\fR +Mounts a tmpfs filesystem on /tmp. /tmp must exist in the chroot. +This must be used with \fB-C\fR. The default filesystem has a max size of 128M +and has standard /tmp permissions (777). +.TP +\fB-T <type>\fR +Assume program's ELF linkage type is \fItype\fR, +which should be either 'static' or 'dynamic'. +.TP +\fB-u <user>\fR +Change users to \fIuser\fR, which may be either a user name or a numeric user +ID. +.TP +\fB-U\fR +Enter a new user namespace (implies \fB-p\fR). +.TP +\fB-v\fR +Run inside a new VFS namespace. This option makes the program's mountpoints +independent of the rest of the system's. +.TP +\fB-V <file>\fR +Enter the VFS namespace specified by \fIfile\fR. +.SH IMPLEMENTATION +This program is broken up into two parts: \fBminijail0\fR (the frontend) and a helper +library called \fBlibminijailpreload\fR. Some jailings can only be achieved from +the process to which they will actually apply - specifically capability use +(since capabilities are not inherited to an exec'd process unless the exec'd +process has POSIX file capabilities), seccomp (since we can't exec() once we're +seccomp'd), and ptrace-disable (which is always cleared on exec()). + +To this end, \fBlibminijailpreload\fR is forcibly loaded into all +dynamically-linked target programs if any of these restrictions are in effect; +we pass the specific restrictions in an environment variable which the preloaded +library looks for. The forcibly-loaded library then applies the restrictions +to the newly-loaded program. + +.SH AUTHOR +The Chromium OS Authors <chromiumos-dev@chromium.org> +.SH COPYRIGHT +Copyright \(co 2011 The Chromium OS Authors +License BSD-like. +.SH "SEE ALSO" +\fBlibminijail.h\fR \fBminijail0(5)\fR
diff --git a/minijail/minijail0.5 b/minijail/minijail0.5 new file mode 100644 index 0000000..b9036b9 --- /dev/null +++ b/minijail/minijail0.5
@@ -0,0 +1,85 @@ +.TH MINIJAIL0 "1" "July 2011" "Chromium OS" "User Commands" +.SH NAME +minijail0 \- sandbox a process +.SH DESCRIPTION +.PP +Runs PROGRAM inside a sandbox. See minijail(1) for details. +.SH EXAMPLES + +Safely switch from root to nobody while dropping all capabilities and +inheriting any groups from nobody: + + # minijail0 -c 0 -G -u nobody /usr/bin/whoami + nobody + +Run in a PID and VFS namespace without superuser capabilities (but still +as root) and with a private view of /proc: + + # minijail0 -p -v -r -c 0 /bin/ps + PID TTY TIME CMD + 1 pts/0 00:00:00 minijail0 + 2 pts/0 00:00:00 ps + +Running a process with a seccomp filter policy at reduced privileges: + + # minijail0 -S /usr/share/minijail0/$(uname -m)/cat.policy -- \\ + /bin/cat /proc/self/seccomp_filter + ... + +.SH SECCOMP_FILTER POLICY +The policy file supplied to the \fB-S\fR argument supports the following syntax: + + \fB<syscall_name>\fR:\fB<ftrace filter policy>\fR + \fB<syscall_number>\fR:\fB<ftrace filter policy>\fR + \fB<empty line>\fR + \fB# any single line comment\fR + +A policy that emulates seccomp(2) in mode 1 may look like: + read: 1 + write: 1 + sig_return: 1 + exit: 1 + +The "1" acts as a wildcard and allows any use of the mentioned system +call. More advanced filtering is possible if your kernel supports +CONFIG_FTRACE_SYSCALLS. For example, we can allow a process to open any +file read only and mmap PROT_READ only: + + # open with O_LARGEFILE|O_RDONLY|O_NONBLOCK or some combination + open: flags == 32768 || flags == 0 || flags == 34816 || flags == 2048 + mmap2: prot == 0x0 + munmap: 1 + close: 1 + +The supported arguments may be found by reviewing the system call +prototypes in the Linux kernel source code. Be aware that any +non-numeric comparison may be subject to time-of-check-time-of-use +attacks and cannot be considered safe. + +\fBexecve\fR may only be used when invoking with CAP_SYS_ADMIN privileges. + +.SH SECCOMP_FILTER POLICY WRITING + +Determining policy for seccomp_filter can be time consuming. System +calls are often named in arch-specific, or legacy tainted, ways. E.g., +geteuid versus geteuid32. On process death due to a seccomp filter +rule, the offending system call number will be supplied with a best +guess of the ABI defined name. This information may be used to produce +working baseline policies. However, if the process being contained has +a fairly tight working domain, using \fBstrace -e raw=all <program>\fR +can generate the list of system calls that are needed. Note that when +using libminijail or minijail with preloading, supporting initial +process setup calls will not be required. Be conservative. + +It's also possible to analyze the binary checking for all non-dead +functions and determining if any of them issue system calls. There is +no active implementation for this, but something like +code.google.com/p/seccompsandbox is one possible runtime variant. + +.SH AUTHOR +The Chromium OS Authors <chromiumos-dev@chromium.org> +.SH COPYRIGHT +Copyright \(co 2011 The Chromium OS Authors +License BSD-like. +.SH "SEE ALSO" +\fBminijail\fR(1)
diff --git a/minijail/minijail0.c b/minijail/minijail0.c new file mode 100644 index 0000000..e21d056 --- /dev/null +++ b/minijail/minijail0.c
@@ -0,0 +1,468 @@ +/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <dlfcn.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "libminijail.h" +#include "libsyscalls.h" + +#include "elfparse.h" +#include "util.h" + +#define IDMAP_LEN 32U + +static void set_user(struct minijail *j, const char *arg) +{ + char *end = NULL; + int uid = strtod(arg, &end); + if (!*end && *arg) { + minijail_change_uid(j, uid); + return; + } + + if (minijail_change_user(j, arg)) { + fprintf(stderr, "Bad user: '%s'\n", arg); + exit(1); + } +} + +static void set_group(struct minijail *j, const char *arg) +{ + char *end = NULL; + int gid = strtod(arg, &end); + if (!*end && *arg) { + minijail_change_gid(j, gid); + return; + } + + if (minijail_change_group(j, arg)) { + fprintf(stderr, "Bad group: '%s'\n", arg); + exit(1); + } +} + +static void use_caps(struct minijail *j, const char *arg) +{ + uint64_t caps; + char *end = NULL; + caps = strtoull(arg, &end, 16); + if (*end) { + fprintf(stderr, "Invalid cap set: '%s'\n", arg); + exit(1); + } + minijail_use_caps(j, caps); +} + +static void add_binding(struct minijail *j, char *arg) +{ + char *src = strtok(arg, ","); + char *dest = strtok(NULL, ","); + char *flags = strtok(NULL, ","); + if (!src || !dest) { + fprintf(stderr, "Bad binding: %s %s\n", src, dest); + exit(1); + } + if (minijail_bind(j, src, dest, flags ? atoi(flags) : 0)) { + fprintf(stderr, "minijail_bind failed.\n"); + exit(1); + } +} + +static void add_mount(struct minijail *j, char *arg) +{ + char *src = strtok(arg, ","); + char *dest = strtok(NULL, ","); + char *type = strtok(NULL, ","); + char *flags = strtok(NULL, ","); + char *data = strtok(NULL, ","); + if (!src || !dest || !type) { + fprintf(stderr, "Bad mount: %s %s %s\n", src, dest, type); + exit(1); + } + if (minijail_mount_with_data(j, src, dest, type, + flags ? strtoul(flags, NULL, 16) : 0, + data)) { + fprintf(stderr, "minijail_mount failed.\n"); + exit(1); + } +} + +static char *build_idmap(id_t id, id_t lowerid) +{ + int ret; + char *idmap = malloc(IDMAP_LEN); + ret = snprintf(idmap, IDMAP_LEN, "%d %d 1", id, lowerid); + if (ret < 0 || (size_t)ret >= IDMAP_LEN) { + free(idmap); + fprintf(stderr, "Could not build id map.\n"); + exit(1); + } + return idmap; +} + +static void usage(const char *progn) +{ + size_t i; + + printf("Usage: %s [-GhiIlnprstUv]\n" + " [-b <src>,<dest>[,<writeable>]] [-f <file>]" + " [-c <caps>] [-C <dir>] [-g <group>] [-u <user>]\n" + " [-S <file>] [-k <src>,<dest>,<type>[,<flags>][,<data>]] [-T <type>]\n" + " [-m[<uid> <loweruid> <count>]*] [-M[<gid> <lowergid> <count>]*]\n" + " <program> [args...]\n" + " -a <table>: Use alternate syscall table <table>.\n" + " -b: Bind <src> to <dest> in chroot.\n" + " Multiple instances allowed.\n" + " -k: Mount <src> at <dest> in chroot.\n" + " <flags> and <data> can be specified as in mount(2).\n" + " Multiple instances allowed.\n" + " -c <caps>: Restrict caps to <caps>.\n" + " -C <dir>: chroot(2) to <dir>.\n" + " Not compatible with -P.\n" + " -e[file]: Enter new network namespace, or existing one if |file| is provided.\n" + " -f <file>: Write the pid of the jailed process to <file>.\n" + " -G: Inherit supplementary groups from uid.\n" + " -g <group>: Change gid to <group>.\n" + " -h: Help (this message).\n" + " -H: Seccomp filter help message.\n" + " -i: Exit immediately after fork (do not act as init).\n" + " Not compatible with -p.\n" + " -I: Run <program> as init (pid 1) inside a new pid namespace (implies -p).\n" + " -K: Don't mark all existing mounts as MS_PRIVATE.\n" + " -l: Enter new IPC namespace.\n" + " -L: Report blocked syscalls to syslog when using seccomp filter.\n" + " Forces the following syscalls to be allowed:\n" + " ", progn); + for (i = 0; i < log_syscalls_len; i++) + printf("%s ", log_syscalls[i]); + + printf("\n" + " -m: Set the uid mapping of a user namespace (implies -pU).\n" + " Same arguments as newuidmap(1), multiple mappings should be separated by ',' (comma).\n" + " Not compatible with -b without the 'writable' option.\n" + " -M: Set the gid mapping of a user namespace (implies -pU).\n" + " Same arguments as newgidmap(1), multiple mappings should be separated by ',' (comma).\n" + " Not compatible with -b without the 'writable' option.\n" + " -n: Set no_new_privs.\n" + " -N: Enter a new cgroup namespace.\n" + " -p: Enter new pid namespace (implies -vr).\n" + " -P <dir>: pivot_root(2) to <dir> (implies -v).\n" + " Not compatible with -C.\n" + " -r: Remount /proc read-only (implies -v).\n" + " -s: Use seccomp.\n" + " -S <file>: Set seccomp filter using <file>.\n" + " E.g., '-S /usr/share/filters/<prog>.$(uname -m)'.\n" + " Requires -n when not running as root.\n" + " -t: Mount tmpfs at /tmp inside chroot.\n" + " -T <type>: Don't access <program> before execve(2), assume <type> ELF binary.\n" + " <type> must be 'static' or 'dynamic'.\n" + " -u <user>: Change uid to <user>.\n" + " -U: Enter new user namespace (implies -p).\n" + " -v: Enter new mount namespace.\n" + " -V <file>: Enter specified mount namespace.\n"); +} + +static void seccomp_filter_usage(const char *progn) +{ + const struct syscall_entry *entry = syscall_table; + printf("Usage: %s -S <policy.file> <program> [args...]\n\n" + "System call names supported:\n", progn); + for (; entry->name && entry->nr >= 0; ++entry) + printf(" %s [%d]\n", entry->name, entry->nr); + printf("\nSee minijail0(5) for example policies.\n"); +} + +static int parse_args(struct minijail *j, int argc, char *argv[], + int *exit_immediately, ElfType *elftype) +{ + int opt; + int use_seccomp_filter = 0; + int binding = 0; + int pivot_root = 0, chroot = 0; + int mount_ns = 0, skip_remount = 0; + const size_t path_max = 4096; + char *map; + const char *filter_path; + if (argc > 1 && argv[1][0] != '-') + return 1; + + const char *optstring = + "u:g:sS:c:C:P:b:V:f:m::M:k:a:e::T:vrGhHinNplLtIUK"; + while ((opt = getopt(argc, argv, optstring)) != -1) { + switch (opt) { + case 'u': + set_user(j, optarg); + break; + case 'g': + set_group(j, optarg); + break; + case 'n': + minijail_no_new_privs(j); + break; + case 's': + minijail_use_seccomp(j); + break; + case 'S': + minijail_use_seccomp_filter(j); + if (strlen(optarg) >= path_max) { + fprintf(stderr, "Filter path is too long.\n"); + exit(1); + } + filter_path = strndup(optarg, path_max); + if (!filter_path) { + fprintf(stderr, + "Could not strndup(3) filter path.\n"); + exit(1); + } + use_seccomp_filter = 1; + break; + case 'l': + minijail_namespace_ipc(j); + break; + case 'L': + minijail_log_seccomp_filter_failures(j); + break; + case 'b': + add_binding(j, optarg); + binding = 1; + break; + case 'c': + use_caps(j, optarg); + break; + case 'C': + if (pivot_root) { + fprintf(stderr, "Could not set chroot because " + "'-P' was specified.\n"); + exit(1); + } + if (0 != minijail_enter_chroot(j, optarg)) { + fprintf(stderr, "Could not set chroot.\n"); + exit(1); + } + chroot = 1; + break; + case 'k': + add_mount(j, optarg); + break; + case 'K': + minijail_skip_remount_private(j); + skip_remount = 1; + break; + case 'P': + if (chroot) { + fprintf(stderr, + "Could not set pivot_root because " + "'-C' was specified.\n"); + exit(1); + } + if (0 != minijail_enter_pivot_root(j, optarg)) { + fprintf(stderr, "Could not set pivot_root.\n"); + exit(1); + } + minijail_namespace_vfs(j); + pivot_root = 1; + break; + case 'f': + if (0 != minijail_write_pid_file(j, optarg)) { + fprintf(stderr, + "Could not prepare pid file path.\n"); + exit(1); + } + break; + case 't': + minijail_mount_tmp(j); + break; + case 'v': + minijail_namespace_vfs(j); + mount_ns = 1; + break; + case 'V': + minijail_namespace_enter_vfs(j, optarg); + break; + case 'r': + minijail_remount_proc_readonly(j); + break; + case 'G': + minijail_inherit_usergroups(j); + break; + case 'N': + minijail_namespace_cgroups(j); + break; + case 'p': + minijail_namespace_pids(j); + break; + case 'e': + if (optarg) + minijail_namespace_enter_net(j, optarg); + else + minijail_namespace_net(j); + break; + case 'i': + *exit_immediately = 1; + break; + case 'H': + seccomp_filter_usage(argv[0]); + exit(1); + case 'I': + minijail_namespace_pids(j); + minijail_run_as_init(j); + break; + case 'U': + minijail_namespace_user(j); + minijail_namespace_pids(j); + break; + case 'm': + minijail_namespace_user(j); + minijail_namespace_pids(j); + + if (optarg) { + map = strdup(optarg); + } else { + /* + * If no map is passed, map the current uid to + * root. + */ + map = build_idmap(0, getuid()); + } + if (0 != minijail_uidmap(j, map)) { + fprintf(stderr, "Could not set uid map.\n"); + exit(1); + } + free(map); + break; + case 'M': + minijail_namespace_user(j); + minijail_namespace_pids(j); + if (0 != minijail_gidmap(j, optarg)) { + fprintf(stderr, "Could not set gid map.\n"); + exit(1); + } + break; + case 'a': + if (0 != minijail_use_alt_syscall(j, optarg)) { + fprintf(stderr, + "Could not set alt-syscall table.\n"); + exit(1); + } + break; + case 'T': + if (!strcmp(optarg, "static")) + *elftype = ELFSTATIC; + else if (!strcmp(optarg, "dynamic")) + *elftype = ELFDYNAMIC; + else { + fprintf(stderr, "ELF type must be 'static' or " + "'dynamic'.\n"); + exit(1); + } + break; + default: + usage(argv[0]); + exit(1); + } + if (optind < argc && argv[optind][0] != '-') + break; + } + + /* Only allow bind mounts when entering a chroot or using pivot_root. */ + if (binding && !(chroot || pivot_root)) { + fprintf(stderr, "Can't add bind mounts without chroot or" + " pivot_root.\n"); + exit(1); + } + + /* + * Remounting / as MS_PRIVATE only happens when entering a new mount + * namespace, so skipping it only applies in that case. + */ + if (skip_remount && !mount_ns) { + fprintf(stderr, "Can't skip marking mounts as MS_PRIVATE" + " without mount namespaces.\n"); + exit(1); + } + + /* + * We parse seccomp filters here to make sure we've collected all + * cmdline options. + */ + if (use_seccomp_filter) { + minijail_parse_seccomp_filters(j, filter_path); + free((void*)filter_path); + } + + if (argc == optind) { + usage(argv[0]); + exit(1); + } + + return optind; +} + +int main(int argc, char *argv[]) +{ + struct minijail *j = minijail_new(); + const char *dl_mesg = NULL; + int exit_immediately = 0; + ElfType elftype = ELFERROR; + int consumed = parse_args(j, argc, argv, &exit_immediately, &elftype); + argc -= consumed; + argv += consumed; + + if (elftype == ELFERROR) { + /* + * -T was not specified. + * Get the path to the program adjusted for changing root. + */ + char *program_path = minijail_get_original_path(j, argv[0]); + + /* Check that we can access the target program. */ + if (access(program_path, X_OK)) { + fprintf(stderr, + "Target program '%s' is not accessible.\n", + argv[0]); + return 1; + } + + /* Check if target is statically or dynamically linked. */ + elftype = get_elf_linkage(program_path); + free(program_path); + } + + if (elftype == ELFSTATIC) { + /* + * Target binary is statically linked so we cannot use + * libminijailpreload.so. + */ + minijail_run_no_preload(j, argv[0], argv); + } else if (elftype == ELFDYNAMIC) { + /* + * Target binary is dynamically linked so we can + * inject libminijailpreload.so into it. + */ + + /* Check that we can dlopen() libminijailpreload.so. */ + if (!dlopen(PRELOADPATH, RTLD_LAZY | RTLD_LOCAL)) { + dl_mesg = dlerror(); + fprintf(stderr, "dlopen(): %s\n", dl_mesg); + return 1; + } + minijail_run(j, argv[0], argv); + } else { + fprintf(stderr, + "Target program '%s' is not a valid ELF file.\n", + argv[0]); + return 1; + } + + if (exit_immediately) { + info("not running init loop, exiting immediately"); + return 0; + } + return minijail_wait(j); +}
diff --git a/minijail/platform2_preinstall.sh b/minijail/platform2_preinstall.sh new file mode 100755 index 0000000..7d19d99 --- /dev/null +++ b/minijail/platform2_preinstall.sh
@@ -0,0 +1,15 @@ +#!/bin/bash + +# Copyright 2015 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +set -e + +v=$1 +include_dir=$2 + +sed \ + -e "s/@BSLOT@/${v}/g" \ + -e "s:@INCLUDE_DIR@:${include_dir}:g" \ + "libminijail.pc.in" > "libminijail.pc"
diff --git a/minijail/scoped_minijail.h b/minijail/scoped_minijail.h new file mode 100644 index 0000000..449364f --- /dev/null +++ b/minijail/scoped_minijail.h
@@ -0,0 +1,29 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// 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. + +#include <memory> + +#include "libminijail.h" + +namespace { +struct ScopedMinijailDeleter { + inline void operator()(minijail *j) const { + if (j) { + minijail_destroy(j); + } + } +}; +} + +using ScopedMinijail = std::unique_ptr<minijail, ScopedMinijailDeleter>;
diff --git a/minijail/signal_handler.c b/minijail/signal_handler.c new file mode 100644 index 0000000..5a5ae9c --- /dev/null +++ b/minijail/signal_handler.c
@@ -0,0 +1,73 @@ +/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* These header files need to be included before asm/siginfo.h such that + * pid_t, timer_t, and clock_t are defined. */ +#include <stdlib.h> +#include <unistd.h> + +#include <asm/siginfo.h> +#define __have_siginfo_t 1 +#define __have_sigval_t 1 +#define __have_sigevent_t 1 + +#include <signal.h> +#include <string.h> + +#include "signal_handler.h" + +#include "util.h" + +struct local_sigsys { + void *ip; + int nr; + unsigned int arch; +}; + +void log_sigsys_handler(int nr, siginfo_t *info, void *void_context) +{ + struct local_sigsys sigsys; + const char *syscall_name; + memcpy(&sigsys, &info->_sifields, sizeof(sigsys)); + syscall_name = lookup_syscall_name(sigsys.nr); + + (void) void_context; + + if (syscall_name) + die("blocked syscall: %s", syscall_name); + else + die("blocked syscall: %d", nr); + + /* + * We trapped on a syscall that should have killed the process. + * This should never ever return, but we're paranoid. + */ + for (;;) + _exit(1); +} + +int install_sigsys_handler() +{ + int ret = 0; + struct sigaction act; + sigset_t mask; + + memset(&act, 0, sizeof(act)); + act.sa_sigaction = &log_sigsys_handler; + act.sa_flags = SA_SIGINFO; + + sigemptyset(&mask); + sigaddset(&mask, SIGSYS); + + ret = sigaction(SIGSYS, &act, NULL); + if (ret < 0) + return ret; + + ret = sigprocmask(SIG_UNBLOCK, &mask, NULL); + if (ret < 0) + return ret; + + return 0; +}
diff --git a/minijail/signal_handler.h b/minijail/signal_handler.h new file mode 100644 index 0000000..939a582 --- /dev/null +++ b/minijail/signal_handler.h
@@ -0,0 +1,14 @@ +/* signal_handler.h + * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Signal handling functions. + */ + +#ifndef SIGNAL_HANDLER_H +#define SIGNAL_HANDLER_H + +int install_sigsys_handler(); + +#endif /* SIGNAL_HANDLER_H */
diff --git a/minijail/syscall_filter.c b/minijail/syscall_filter.c new file mode 100644 index 0000000..3fcbe4f --- /dev/null +++ b/minijail/syscall_filter.c
@@ -0,0 +1,571 @@ +/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "syscall_filter.h" + +#include "util.h" + +#define MAX_LINE_LENGTH 1024 +#define MAX_POLICY_LINE_LENGTH 1024 + +#define ONE_INSTR 1 +#define TWO_INSTRS 2 + +int seccomp_can_softfail() +{ +#if defined(USE_SECCOMP_SOFTFAIL) + /* + * On Android devices seccomp is allowed to soft-fail on kernels < 3.8. + */ + if (is_android()) { + if (kernel_lessthan_3_8()) + return 1; + else + return 0; + } else { + return 1; + } +#endif + return 0; +} + +int str_to_op(const char *op_str) +{ + if (!strcmp(op_str, "==")) { + return EQ; + } else if (!strcmp(op_str, "!=")) { + return NE; + } else if (!strcmp(op_str, "&")) { + return SET; + } else { + return 0; + } +} + +struct sock_filter *new_instr_buf(size_t count) +{ + struct sock_filter *buf = calloc(count, sizeof(struct sock_filter)); + if (!buf) + die("could not allocate BPF instruction buffer"); + + return buf; +} + +struct filter_block *new_filter_block() +{ + struct filter_block *block = calloc(1, sizeof(struct filter_block)); + if (!block) + die("could not allocate BPF filter block"); + + block->instrs = NULL; + block->last = block->next = NULL; + + return block; +} + +void append_filter_block(struct filter_block *head, struct sock_filter *instrs, + size_t len) +{ + struct filter_block *new_last; + + /* + * If |head| has no filter assigned yet, + * we don't create a new node. + */ + if (head->instrs == NULL) { + new_last = head; + } else { + new_last = new_filter_block(); + if (head->next != NULL) { + head->last->next = new_last; + head->last = new_last; + } else { + head->last = head->next = new_last; + } + head->total_len += len; + } + + new_last->instrs = instrs; + new_last->total_len = new_last->len = len; + new_last->last = new_last->next = NULL; +} + +void extend_filter_block_list(struct filter_block *list, + struct filter_block *another) +{ + if (list->last != NULL) { + list->last->next = another; + list->last = another->last; + } else { + list->next = another; + list->last = another->last; + } + list->total_len += another->total_len; +} + +void append_ret_kill(struct filter_block *head) +{ + struct sock_filter *filter = new_instr_buf(ONE_INSTR); + set_bpf_ret_kill(filter); + append_filter_block(head, filter, ONE_INSTR); +} + +void append_ret_trap(struct filter_block *head) +{ + struct sock_filter *filter = new_instr_buf(ONE_INSTR); + set_bpf_ret_trap(filter); + append_filter_block(head, filter, ONE_INSTR); +} + +void append_ret_errno(struct filter_block *head, int errno_val) +{ + struct sock_filter *filter = new_instr_buf(ONE_INSTR); + set_bpf_ret_errno(filter, errno_val); + append_filter_block(head, filter, ONE_INSTR); +} + +void append_allow_syscall(struct filter_block *head, int nr) +{ + struct sock_filter *filter = new_instr_buf(ALLOW_SYSCALL_LEN); + size_t len = bpf_allow_syscall(filter, nr); + if (len != ALLOW_SYSCALL_LEN) + die("error building syscall number comparison"); + + append_filter_block(head, filter, len); +} + +void allow_log_syscalls(struct filter_block *head) +{ + unsigned int i; + for (i = 0; i < log_syscalls_len; i++) { + warn("allowing syscall: %s", log_syscalls[i]); + append_allow_syscall(head, lookup_syscall(log_syscalls[i])); + } +} + +unsigned int get_label_id(struct bpf_labels *labels, const char *label_str) +{ + int label_id = bpf_label_id(labels, label_str); + if (label_id < 0) + die("could not allocate BPF label string"); + return label_id; +} + +unsigned int group_end_lbl(struct bpf_labels *labels, int nr, int idx) +{ + char lbl_str[MAX_BPF_LABEL_LEN]; + snprintf(lbl_str, MAX_BPF_LABEL_LEN, "%d_%d_end", nr, idx); + return get_label_id(labels, lbl_str); +} + +unsigned int success_lbl(struct bpf_labels *labels, int nr) +{ + char lbl_str[MAX_BPF_LABEL_LEN]; + snprintf(lbl_str, MAX_BPF_LABEL_LEN, "%d_success", nr); + return get_label_id(labels, lbl_str); +} + +int compile_atom(struct filter_block *head, char *atom, + struct bpf_labels *labels, int nr, int group_idx) +{ + /* Splits the atom. */ + char *atom_ptr; + char *argidx_str = strtok_r(atom, " ", &atom_ptr); + char *operator_str = strtok_r(NULL, " ", &atom_ptr); + char *constant_str = strtok_r(NULL, " ", &atom_ptr); + + if (argidx_str == NULL || operator_str == NULL || constant_str == NULL) + return -1; + + int op = str_to_op(operator_str); + if (op < MIN_OPERATOR) + return -1; + + if (strncmp(argidx_str, "arg", 3)) { + return -1; + } + + char *argidx_ptr; + long int argidx = strtol(argidx_str + 3, &argidx_ptr, 10); + /* + * Checks to see if an actual argument index + * was parsed. + */ + if (argidx_ptr == argidx_str + 3) + return -1; + + char *constant_str_ptr; + long int c = parse_constant(constant_str, &constant_str_ptr); + if (constant_str_ptr == constant_str) + return -1; + + /* + * Looks up the label for the end of the AND statement + * this atom belongs to. + */ + unsigned int id = group_end_lbl(labels, nr, group_idx); + + /* + * Builds a BPF comparison between a syscall argument + * and a constant. + * The comparison lives inside an AND statement. + * If the comparison succeeds, we continue + * to the next comparison. + * If this comparison fails, the whole AND statement + * will fail, so we jump to the end of this AND statement. + */ + struct sock_filter *comp_block; + size_t len = bpf_arg_comp(&comp_block, op, argidx, c, id); + if (len == 0) + return -1; + + append_filter_block(head, comp_block, len); + return 0; +} + +int compile_errno(struct filter_block *head, char *ret_errno, + int log_failures) +{ + char *errno_ptr; + + /* Splits the 'return' keyword and the actual errno value. */ + char *ret_str = strtok_r(ret_errno, " ", &errno_ptr); + if (strncmp(ret_str, "return", strlen("return"))) + return -1; + + char *errno_val_str = strtok_r(NULL, " ", &errno_ptr); + + if (errno_val_str) { + char *errno_val_ptr; + int errno_val = parse_constant(errno_val_str, &errno_val_ptr); + /* Checks to see if we parsed an actual errno. */ + if (errno_val_ptr == errno_val_str || errno_val == -1) + return -1; + + append_ret_errno(head, errno_val); + } else { + if (!log_failures) + append_ret_kill(head); + else + append_ret_trap(head); + } + return 0; +} + +struct filter_block *compile_section(int nr, const char *policy_line, + unsigned int entry_lbl_id, + struct bpf_labels *labels, + int log_failures) +{ + /* + * |policy_line| should be an expression of the form: + * "arg0 == 3 && arg1 == 5 || arg0 == 0x8" + * + * This is, an expression in DNF (disjunctive normal form); + * a disjunction ('||') of one or more conjunctions ('&&') + * of one or more atoms. + * + * Atoms are of the form "arg{DNUM} {OP} {NUM}" + * where: + * - DNUM is a decimal number. + * - OP is an operator: ==, !=, or & (flags set). + * - NUM is an octal, decimal, or hexadecimal number. + * + * When the syscall arguments make the expression true, + * the syscall is allowed. If not, the process is killed. + * + * To block a syscall without killing the process, + * |policy_line| can be of the form: + * "return <errno>" + * + * This "return {NUM}" policy line will block the syscall, + * make it return -1 and set |errno| to NUM. + * + * A regular policy line can also include a "return <errno>" clause, + * separated by a semicolon (';'): + * "arg0 == 3 && arg1 == 5 || arg0 == 0x8; return {NUM}" + * + * If the syscall arguments don't make the expression true, + * the syscall will be blocked as above instead of killing the process. + */ + + size_t len = 0; + int group_idx = 0; + + /* Checks for overly long policy lines. */ + if (strlen(policy_line) >= MAX_POLICY_LINE_LENGTH) + return NULL; + + /* We will modify |policy_line|, so let's make a copy. */ + char *line = strndup(policy_line, MAX_POLICY_LINE_LENGTH); + if (!line) + return NULL; + + /* + * We build the filter section as a collection of smaller + * "filter blocks" linked together in a singly-linked list. + */ + struct filter_block *head = new_filter_block(); + + /* + * Filter sections begin with a label where the main filter + * will jump after checking the syscall number. + */ + struct sock_filter *entry_label = new_instr_buf(ONE_INSTR); + set_bpf_lbl(entry_label, entry_lbl_id); + append_filter_block(head, entry_label, ONE_INSTR); + + /* Checks whether we're unconditionally blocking this syscall. */ + if (strncmp(line, "return", strlen("return")) == 0) { + if (compile_errno(head, line, log_failures) < 0) + return NULL; + free(line); + return head; + } + + /* Splits the optional "return <errno>" part. */ + char *line_ptr; + char *arg_filter = strtok_r(line, ";", &line_ptr); + char *ret_errno = strtok_r(NULL, ";", &line_ptr); + + /* + * Splits the policy line by '||' into conjunctions and each conjunction + * by '&&' into atoms. + */ + char *arg_filter_str = arg_filter; + char *group; + while ((group = tokenize(&arg_filter_str, "||")) != NULL) { + char *group_str = group; + char *comp; + while ((comp = tokenize(&group_str, "&&")) != NULL) { + /* Compiles each atom into a BPF block. */ + if (compile_atom(head, comp, labels, nr, group_idx) < 0) + return NULL; + } + /* + * If the AND statement succeeds, we're done, + * so jump to SUCCESS line. + */ + unsigned int id = success_lbl(labels, nr); + struct sock_filter *group_end_block = new_instr_buf(TWO_INSTRS); + len = set_bpf_jump_lbl(group_end_block, id); + /* + * The end of each AND statement falls after the + * jump to SUCCESS. + */ + id = group_end_lbl(labels, nr, group_idx++); + len += set_bpf_lbl(group_end_block + len, id); + append_filter_block(head, group_end_block, len); + } + + /* + * If no AND statements succeed, we end up here, + * because we never jumped to SUCCESS. + * If we have to return an errno, do it, + * otherwise just kill the task. + */ + if (ret_errno) { + if (compile_errno(head, ret_errno, log_failures) < 0) + return NULL; + } else { + if (!log_failures) + append_ret_kill(head); + else + append_ret_trap(head); + } + + /* + * Every time the filter succeeds we jump to a predefined SUCCESS + * label. Add that label and BPF RET_ALLOW code now. + */ + unsigned int id = success_lbl(labels, nr); + struct sock_filter *success_block = new_instr_buf(TWO_INSTRS); + len = set_bpf_lbl(success_block, id); + len += set_bpf_ret_allow(success_block + len); + append_filter_block(head, success_block, len); + + free(line); + return head; +} + +int compile_filter(FILE *policy_file, struct sock_fprog *prog, int log_failures) +{ + char line[MAX_LINE_LENGTH]; + int line_count = 0; + + struct bpf_labels labels; + labels.count = 0; + + if (!policy_file) + return -1; + + struct filter_block *head = new_filter_block(); + struct filter_block *arg_blocks = NULL; + + /* Start filter by validating arch. */ + struct sock_filter *valid_arch = new_instr_buf(ARCH_VALIDATION_LEN); + size_t len = bpf_validate_arch(valid_arch); + append_filter_block(head, valid_arch, len); + + /* Load syscall number. */ + struct sock_filter *load_nr = new_instr_buf(ONE_INSTR); + len = bpf_load_syscall_nr(load_nr); + append_filter_block(head, load_nr, len); + + /* If we're logging failures, allow the necessary syscalls first. */ + if (log_failures) + allow_log_syscalls(head); + + /* + * Loop through all the lines in the policy file. + * Build a jump table for the syscall number. + * If the policy line has an arg filter, build the arg filter + * as well. + * Chain the filter sections together and dump them into + * the final buffer at the end. + */ + while (fgets(line, sizeof(line), policy_file)) { + ++line_count; + char *policy_line = line; + char *syscall_name = strsep(&policy_line, ":"); + int nr = -1; + + syscall_name = strip(syscall_name); + + /* Allow comments and empty lines. */ + if (*syscall_name == '#' || *syscall_name == '\0') + continue; + + if (!policy_line) + return -1; + + nr = lookup_syscall(syscall_name); + if (nr < 0) { + warn("compile_filter: nonexistent syscall '%s'", + syscall_name); + if (log_failures) { + /* + * If we're logging failures, assume we're in a + * debugging case and continue. + * This is not super risky because an invalid + * syscall name is likely caused by a typo or by + * leftover lines from a different architecture. + * In either case, not including a policy line + * is equivalent to killing the process if the + * syscall is made, so there's no added attack + * surface. + */ + continue; + } + return -1; + } + + policy_line = strip(policy_line); + + /* + * For each syscall, add either a simple ALLOW, + * or an arg filter block. + */ + if (strcmp(policy_line, "1") == 0) { + /* Add simple ALLOW. */ + append_allow_syscall(head, nr); + } else { + /* + * Create and jump to the label that will hold + * the arg filter block. + */ + unsigned int id = bpf_label_id(&labels, syscall_name); + struct sock_filter *nr_comp = + new_instr_buf(ALLOW_SYSCALL_LEN); + bpf_allow_syscall_args(nr_comp, nr, id); + append_filter_block(head, nr_comp, ALLOW_SYSCALL_LEN); + + /* Build the arg filter block. */ + struct filter_block *block = + compile_section(nr, policy_line, id, &labels, + log_failures); + + if (!block) + return -1; + + if (arg_blocks) { + extend_filter_block_list(arg_blocks, block); + } else { + arg_blocks = block; + } + } + } + + /* + * If none of the syscalls match, either fall back to KILL, + * or return TRAP. + */ + if (!log_failures) + append_ret_kill(head); + else + append_ret_trap(head); + + /* Allocate the final buffer, now that we know its size. */ + size_t final_filter_len = + head->total_len + (arg_blocks ? arg_blocks->total_len : 0); + if (final_filter_len > BPF_MAXINSNS) + return -1; + + struct sock_filter *final_filter = + calloc(final_filter_len, sizeof(struct sock_filter)); + + if (flatten_block_list(head, final_filter, 0, final_filter_len) < 0) + return -1; + + if (flatten_block_list(arg_blocks, final_filter, head->total_len, + final_filter_len) < 0) + return -1; + + free_block_list(head); + free_block_list(arg_blocks); + + bpf_resolve_jumps(&labels, final_filter, final_filter_len); + + free_label_strings(&labels); + + prog->filter = final_filter; + prog->len = final_filter_len; + return 0; +} + +int flatten_block_list(struct filter_block *head, struct sock_filter *filter, + size_t index, size_t cap) +{ + size_t _index = index; + + struct filter_block *curr; + size_t i; + + for (curr = head; curr; curr = curr->next) { + for (i = 0; i < curr->len; i++) { + if (_index >= cap) + return -1; + filter[_index++] = curr->instrs[i]; + } + } + return 0; +} + +void free_block_list(struct filter_block *head) +{ + struct filter_block *current, *prev; + + current = head; + while (current) { + free(current->instrs); + prev = current; + current = current->next; + free(prev); + } +}
diff --git a/minijail/syscall_filter.h b/minijail/syscall_filter.h new file mode 100644 index 0000000..09c6550 --- /dev/null +++ b/minijail/syscall_filter.h
@@ -0,0 +1,48 @@ +/* syscall_filter.h + * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Syscall filter functions. + */ + +#ifndef SYSCALL_FILTER_H +#define SYSCALL_FILTER_H + +#include "bpf.h" + +#define NO_LOGGING 0 +#define USE_LOGGING 1 + +#ifdef __cplusplus +extern "C" { +#endif + +struct filter_block { + struct sock_filter *instrs; + size_t len; + + struct filter_block *next; + struct filter_block *last; + size_t total_len; +}; + +struct bpf_labels; + +struct filter_block *compile_section(int nr, const char *policy_line, + unsigned int label_id, + struct bpf_labels *labels, + int log_failures); +int compile_filter(FILE *policy_file, struct sock_fprog *prog, int log_failures); + +int flatten_block_list(struct filter_block *head, struct sock_filter *filter, + size_t index, size_t cap); +void free_block_list(struct filter_block *head); + +int seccomp_can_softfail(); + +#ifdef __cplusplus +}; /* extern "C" */ +#endif + +#endif /* SYSCALL_FILTER_H */
diff --git a/minijail/syscall_filter_unittest.c b/minijail/syscall_filter_unittest.c new file mode 100644 index 0000000..603479e --- /dev/null +++ b/minijail/syscall_filter_unittest.c
@@ -0,0 +1,792 @@ +/* syscall_filter_unittest.c + * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Test syscall filtering. + */ + +#include <asm/unistd.h> +#include <errno.h> +#include <fcntl.h> /* For O_WRONLY. */ + +#include "test_harness.h" + +#include "bpf.h" +#include "syscall_filter.h" + +#include "util.h" + +#include "syscall_filter_unittest_macros.h" + +FIXTURE(bpf) {}; + +FIXTURE_SETUP(bpf) {} +FIXTURE_TEARDOWN(bpf) {} + +/* Test that setting one BPF instruction works. */ +TEST_F(bpf, set_bpf_instr) { + struct sock_filter instr; + unsigned char code = BPF_LD+BPF_W+BPF_ABS; + unsigned int k = 4; + unsigned char jt = 1, jf = 2; + + size_t len = set_bpf_instr(&instr, code, k, jt, jf); + + EXPECT_EQ(len, 1U); + EXPECT_EQ_BLOCK(&instr, code, k, jt, jf); +} + +TEST_F(bpf, bpf_load_arg) { + struct sock_filter load_arg[BPF_LOAD_ARG_LEN]; + int argidx = 1; + size_t len = bpf_load_arg(load_arg, argidx); + + EXPECT_EQ(len, BPF_LOAD_ARG_LEN); + +#if defined(BITS32) + EXPECT_EQ_STMT(&load_arg[0], BPF_LD+BPF_W+BPF_ABS, LO_ARG(argidx)); +#elif defined(BITS64) + EXPECT_EQ_STMT(&load_arg[0], BPF_LD+BPF_W+BPF_ABS, LO_ARG(argidx)); + EXPECT_EQ_STMT(&load_arg[1], BPF_ST, 0); + EXPECT_EQ_STMT(&load_arg[2], BPF_LD+BPF_W+BPF_ABS, HI_ARG(argidx)); + EXPECT_EQ_STMT(&load_arg[3], BPF_ST, 1); +#endif +} + +TEST_F(bpf, bpf_comp_jeq) { + struct sock_filter comp_jeq[BPF_COMP_LEN]; + unsigned long c = 1; + unsigned char jt = 1; + unsigned char jf = 2; + + size_t len = bpf_comp_jeq(comp_jeq, c, jt, jf); + + EXPECT_EQ(len, BPF_COMP_LEN); + +#if defined(BITS32) + EXPECT_EQ_BLOCK(&comp_jeq[0], + BPF_JMP+BPF_JEQ+BPF_K, c, jt, jf); +#elif defined(BITS64) + EXPECT_EQ_BLOCK(&comp_jeq[0], + BPF_JMP+BPF_JEQ+BPF_K, 0, 0, jf + 2); + EXPECT_EQ_STMT(&comp_jeq[1], BPF_LD+BPF_MEM, 0); + EXPECT_EQ_BLOCK(&comp_jeq[2], + BPF_JMP+BPF_JEQ+BPF_K, c, jt, jf); +#endif +} + +TEST_F(bpf, bpf_comp_jset) { + struct sock_filter comp_jset[BPF_COMP_LEN]; + unsigned long mask = O_WRONLY; + unsigned char jt = 1; + unsigned char jf = 2; + + size_t len = bpf_comp_jset(comp_jset, mask, jt, jf); + + EXPECT_EQ(len, BPF_COMP_LEN); + +#if defined(BITS32) + EXPECT_EQ_BLOCK(&comp_jset[0], + BPF_JMP+BPF_JSET+BPF_K, mask, jt, jf); +#elif defined(BITS64) + EXPECT_EQ_BLOCK(&comp_jset[0], + BPF_JMP+BPF_JSET+BPF_K, 0, jt + 2, 0); + EXPECT_EQ_STMT(&comp_jset[1], BPF_LD+BPF_MEM, 0); + EXPECT_EQ_BLOCK(&comp_jset[2], + BPF_JMP+BPF_JSET+BPF_K, mask, jt, jf); +#endif +} + +TEST_F(bpf, bpf_arg_comp) { + struct sock_filter *arg_comp; + int op = EQ; + int argidx = 1; + unsigned long c = 3; + unsigned int label_id = 0; + + size_t len = bpf_arg_comp(&arg_comp, op, argidx, c, label_id); + + EXPECT_EQ(len, BPF_ARG_COMP_LEN + 1); + +#if defined(BITS32) + EXPECT_EQ_STMT(&arg_comp[0], + BPF_LD+BPF_W+BPF_ABS, LO_ARG(argidx)); + EXPECT_EQ_BLOCK(&arg_comp[1], + BPF_JMP+BPF_JEQ+BPF_K, c, 1, 0); + EXPECT_JUMP_LBL(&arg_comp[2]); +#elif defined(BITS64) + EXPECT_EQ_STMT(&arg_comp[0], + BPF_LD+BPF_W+BPF_ABS, LO_ARG(argidx)); + EXPECT_EQ_STMT(&arg_comp[1], BPF_ST, 0); + EXPECT_EQ_STMT(&arg_comp[2], + BPF_LD+BPF_W+BPF_ABS, HI_ARG(argidx)); + EXPECT_EQ_STMT(&arg_comp[3], BPF_ST, 1); + + EXPECT_EQ_BLOCK(&arg_comp[4], + BPF_JMP+BPF_JEQ+BPF_K, 0, 0, 2); + EXPECT_EQ_STMT(&arg_comp[5], BPF_LD+BPF_MEM, 0); + EXPECT_EQ_BLOCK(&arg_comp[6], + BPF_JMP+BPF_JEQ+BPF_K, c, 1, 0); + EXPECT_JUMP_LBL(&arg_comp[7]); +#endif + free(arg_comp); +} + +TEST_F(bpf, bpf_validate_arch) { + struct sock_filter validate_arch[ARCH_VALIDATION_LEN]; + + size_t len = bpf_validate_arch(validate_arch); + + EXPECT_EQ(len, ARCH_VALIDATION_LEN); + EXPECT_ARCH_VALIDATION(validate_arch); +} + +TEST_F(bpf, bpf_allow_syscall) { + struct sock_filter allow_syscall[ALLOW_SYSCALL_LEN]; + int nr = 1; + + size_t len = bpf_allow_syscall(allow_syscall, nr); + + EXPECT_EQ(len, ALLOW_SYSCALL_LEN); + EXPECT_ALLOW_SYSCALL(allow_syscall, nr); +} + +TEST_F(bpf, bpf_allow_syscall_args) { + struct sock_filter allow_syscall[ALLOW_SYSCALL_LEN]; + int nr = 1; + unsigned int id = 1024; + + size_t len = bpf_allow_syscall_args(allow_syscall, nr, id); + + EXPECT_EQ(len, ALLOW_SYSCALL_LEN); + EXPECT_ALLOW_SYSCALL_ARGS(allow_syscall, nr, id, JUMP_JT, JUMP_JF); +} + +FIXTURE(arg_filter) { + struct bpf_labels labels; +}; + +FIXTURE_SETUP(arg_filter) {} +FIXTURE_TEARDOWN(arg_filter) { + free_label_strings(&self->labels); +} + +TEST_F(arg_filter, arg0_equals) { + const char *fragment = "arg0 == 0"; + int nr = 1; + unsigned int id = 0; + struct filter_block *block = + compile_section(nr, fragment, id, &self->labels, NO_LOGGING); + + ASSERT_NE(block, NULL); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_NE(curr_block, NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison. */ + curr_block = block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_GROUP_END(curr_block); + + /* Fourth block is SECCOMP_RET_KILL. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_KILL(curr_block); + + /* Fifth block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_EQ(curr_block->next, NULL); + + free_block_list(block); +} + +TEST_F(arg_filter, arg0_mask) { + const char *fragment = "arg1 & O_RDWR"; + int nr = 1; + unsigned int id = 0; + struct filter_block *block = + compile_section(nr, fragment, id, &self->labels, NO_LOGGING); + + ASSERT_NE(block, NULL); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_NE(curr_block, NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison. */ + curr_block = block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_GROUP_END(curr_block); + + /* Fourth block is SECCOMP_RET_KILL. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_KILL(curr_block); + + /* Fifth block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_EQ(curr_block->next, NULL); + + free_block_list(block); +} + +TEST_F(arg_filter, arg0_eq_mask) { + const char *fragment = "arg1 == O_WRONLY|O_CREAT"; + int nr = 1; + unsigned int id = 0; + struct filter_block *block = + compile_section(nr, fragment, id, &self->labels, NO_LOGGING); + + ASSERT_NE(block, NULL); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_NE(curr_block, NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison. */ + curr_block = block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_COMP(curr_block); + EXPECT_EQ(curr_block->instrs[BPF_ARG_COMP_LEN - 1].k, + (unsigned int)(O_WRONLY | O_CREAT)); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_GROUP_END(curr_block); + + /* Fourth block is SECCOMP_RET_KILL. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_KILL(curr_block); + + /* Fifth block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_EQ(curr_block->next, NULL); + + free_block_list(block); +} + +TEST_F(arg_filter, and_or) { + const char *fragment = "arg0 == 0 && arg1 == 0 || arg0 == 1"; + int nr = 1; + unsigned int id = 0; + + struct filter_block *block = + compile_section(nr, fragment, id, &self->labels, NO_LOGGING); + ASSERT_NE(block, NULL); + size_t exp_total_len = 1 + 3 * (BPF_ARG_COMP_LEN + 1) + 2 + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_NE(curr_block, NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison ("arg0 == 0"). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_COMP(curr_block); + + /* Third block is a comparison ("arg1 == 0"). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_COMP(curr_block); + + /* Fourth block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_GROUP_END(curr_block); + + /* Fifth block is a comparison ("arg0 == 1"). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_COMP(curr_block); + + /* Sixth block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_GROUP_END(curr_block); + + /* Seventh block is SECCOMP_RET_KILL. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_KILL(curr_block); + + /* Eigth block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_EQ(curr_block->next, NULL); + + free_block_list(block); + free_label_strings(&self->labels); +} + +TEST_F(arg_filter, ret_errno) { + const char *fragment = "arg0 == 0 || arg0 == 1; return 1"; + int nr = 1; + unsigned int id = 0; + + struct filter_block *block = + compile_section(nr, fragment, id, &self->labels, NO_LOGGING); + ASSERT_NE(block, NULL); + size_t exp_total_len = 1 + 2 * (BPF_ARG_COMP_LEN + 1) + 2 + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_NE(curr_block, NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison ("arg0 == 0"). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_GROUP_END(curr_block); + + /* Fourth block is a comparison ("arg0 == 1"). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_COMP(curr_block); + + /* Fifth block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_GROUP_END(curr_block); + + /* Sixth block is SECCOMP_RET_ERRNO. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_EQ(curr_block->len, 1U); + EXPECT_EQ_STMT(curr_block->instrs, + BPF_RET+BPF_K, + SECCOMP_RET_ERRNO | (1 & SECCOMP_RET_DATA)); + + /* Seventh block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_EQ(curr_block->next, NULL); + + free_block_list(block); + free_label_strings(&self->labels); +} + +TEST_F(arg_filter, unconditional_errno) { + const char *fragment = "return 1"; + int nr = 1; + unsigned int id = 0; + + struct filter_block *block = + compile_section(nr, fragment, id, &self->labels, NO_LOGGING); + ASSERT_NE(block, NULL); + size_t exp_total_len = 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_NE(curr_block, NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is SECCOMP_RET_ERRNO. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_EQ(curr_block->len, 1U); + EXPECT_EQ_STMT(curr_block->instrs, + BPF_RET+BPF_K, + SECCOMP_RET_ERRNO | (1 & SECCOMP_RET_DATA)); + + EXPECT_EQ(curr_block->next, NULL); + + free_block_list(block); + free_label_strings(&self->labels); +} + +TEST_F(arg_filter, invalid) { + const char *fragment = "argnn == 0"; + int nr = 1; + unsigned int id = 0; + + struct filter_block *block = + compile_section(nr, fragment, id, &self->labels, NO_LOGGING); + ASSERT_EQ(block, NULL); + + fragment = "arg0 == 0 && arg1 == 1; return errno"; + block = compile_section(nr, fragment, id, &self->labels, NO_LOGGING); + ASSERT_EQ(block, NULL); +} + +TEST_F(arg_filter, log_no_ret_error) { + const char *fragment = "arg0 == 0"; + int nr = 1; + unsigned int id = 0; + struct filter_block *block = + compile_section(nr, fragment, id, &self->labels, USE_LOGGING); + + ASSERT_NE(block, NULL); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_NE(curr_block, NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison. */ + curr_block = block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_GROUP_END(curr_block); + + /* Fourth block is SECCOMP_RET_TRAP, with no errno. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_TRAP(curr_block); + + /* Fifth block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_EQ(curr_block->next, NULL); + + free_block_list(block); + free_label_strings(&self->labels); +} + +TEST_F(arg_filter, log_bad_ret_error) { + const char *fragment = "arg0 == 0; return"; + int nr = 1; + unsigned int id = 0; + + struct filter_block *block = + compile_section(nr, fragment, id, &self->labels, NO_LOGGING); + ASSERT_NE(block, NULL); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_NE(curr_block, NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison ("arg0 == 0"). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_GROUP_END(curr_block); + + /* + * Sixth block is NOT SECCOMP_RET_ERRNO, it should be SECCOMP_RET_KILL. + */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_KILL(curr_block); + + /* Seventh block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_EQ(curr_block->next, NULL); + + free_block_list(block); + free_label_strings(&self->labels); +} + +TEST_F(arg_filter, no_log_bad_ret_error) { + const char *fragment = "arg0 == 0; return"; + int nr = 1; + unsigned int id = 0; + + struct filter_block *block = + compile_section(nr, fragment, id, &self->labels, USE_LOGGING); + ASSERT_NE(block, NULL); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_NE(curr_block, NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison ("arg0 == 0"). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_GROUP_END(curr_block); + + /* + * Sixth block is NOT SECCOMP_RET_ERRNO, it should be SECCOMP_RET_TRAP. + */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_TRAP(curr_block); + + /* Seventh block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_NE(curr_block, NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_EQ(curr_block->next, NULL); + + free_block_list(block); + free_label_strings(&self->labels); +} + +FIXTURE(filter) {}; + +FIXTURE_SETUP(filter) {} +FIXTURE_TEARDOWN(filter) {} + +FILE *write_policy_to_pipe(const char *policy, size_t len) { + int pipefd[2]; + if (pipe(pipefd) == -1) { + pwarn("pipe(pipefd) failed"); + return NULL; + } + + size_t i = 0; + unsigned int attempts = 0; + ssize_t ret; + while (i < len) { + ret = write(pipefd[1], &policy[i], len - i); + if (ret == -1) { + close(pipefd[0]); + close(pipefd[1]); + return NULL; + } + + /* If we write 0 bytes three times in a row, fail. */ + if (ret == 0) { + if (++attempts >= 3) { + close(pipefd[0]); + close(pipefd[1]); + warn("write() returned 0 three times in a row"); + return NULL; + } + continue; + } + + attempts = 0; + i += (size_t)ret; + } + + close(pipefd[1]); + return fdopen(pipefd[0], "r"); +} + +TEST_F(filter, seccomp_mode1) { + struct sock_fprog actual; + const char *policy = + "read: 1\n" + "write: 1\n" + "rt_sigreturn: 1\n" + "exit: 1\n"; + + FILE *policy_file = write_policy_to_pipe(policy, strlen(policy)); + ASSERT_NE(policy_file, NULL); + + int res = compile_filter(policy_file, &actual, NO_LOGGING); + fclose(policy_file); + + /* + * Checks return value, filter length, and that the filter + * validates arch, loads syscall number, and + * only allows expected syscalls. + */ + ASSERT_EQ(res, 0); + EXPECT_EQ(actual.len, 13); + EXPECT_ARCH_VALIDATION(actual.filter); + EXPECT_EQ_STMT(actual.filter + ARCH_VALIDATION_LEN, + BPF_LD+BPF_W+BPF_ABS, syscall_nr); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 1, + __NR_read); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 3, + __NR_write); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 5, + __NR_rt_sigreturn); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 7, + __NR_exit); + EXPECT_EQ_STMT(actual.filter + ARCH_VALIDATION_LEN + 9, BPF_RET+BPF_K, + SECCOMP_RET_KILL); + + free(actual.filter); +} + +TEST_F(filter, seccomp_read_write) { + struct sock_fprog actual; + const char *policy = + "read: arg0 == 0\n" + "write: arg0 == 1 || arg0 == 2\n" + "rt_sigreturn: 1\n" + "exit: 1\n"; + + FILE *policy_file = write_policy_to_pipe(policy, strlen(policy)); + ASSERT_NE(policy_file, NULL); + + int res = compile_filter(policy_file, &actual, NO_LOGGING); + fclose(policy_file); + + /* + * Checks return value, filter length, and that the filter + * validates arch, loads syscall number, and + * only allows expected syscalls, jumping to correct arg filter + * offsets. + */ + ASSERT_EQ(res, 0); + size_t exp_total_len = 27 + 3 * (BPF_ARG_COMP_LEN + 1); + EXPECT_EQ(actual.len, exp_total_len); + + EXPECT_ARCH_VALIDATION(actual.filter); + EXPECT_EQ_STMT(actual.filter + ARCH_VALIDATION_LEN, + BPF_LD+BPF_W+BPF_ABS, syscall_nr); + EXPECT_ALLOW_SYSCALL_ARGS(actual.filter + ARCH_VALIDATION_LEN + 1, + __NR_read, 7, 0, 0); + EXPECT_ALLOW_SYSCALL_ARGS(actual.filter + ARCH_VALIDATION_LEN + 3, + __NR_write, 12 + BPF_ARG_COMP_LEN, 0, 0); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 5, + __NR_rt_sigreturn); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 7, + __NR_exit); + EXPECT_EQ_STMT(actual.filter + ARCH_VALIDATION_LEN + 9, BPF_RET+BPF_K, + SECCOMP_RET_KILL); + + free(actual.filter); +} + +TEST_F(filter, invalid_name) { + struct sock_fprog actual; + const char *policy = "notasyscall: 1\n"; + + FILE *policy_file = write_policy_to_pipe(policy, strlen(policy)); + ASSERT_NE(policy_file, NULL); + + int res = compile_filter(policy_file, &actual, NO_LOGGING); + fclose(policy_file); + ASSERT_NE(res, 0); +} + +TEST_F(filter, invalid_arg) { + struct sock_fprog actual; + const char *policy = "open: argnn ==\n"; + + FILE *policy_file = write_policy_to_pipe(policy, strlen(policy)); + ASSERT_NE(policy_file, NULL); + + int res = compile_filter(policy_file, &actual, NO_LOGGING); + fclose(policy_file); + ASSERT_NE(res, 0); +} + +TEST_F(filter, nonexistent) { + struct sock_fprog actual; + int res = compile_filter(NULL, &actual, NO_LOGGING); + ASSERT_NE(res, 0); +} + +TEST_F(filter, log) { + struct sock_fprog actual; + const char *policy = + "read: 1\n" + "write: 1\n" + "rt_sigreturn: 1\n" + "exit: 1\n"; + + FILE *policy_file = write_policy_to_pipe(policy, strlen(policy)); + ASSERT_NE(policy_file, NULL); + + int res = compile_filter(policy_file, &actual, USE_LOGGING); + fclose(policy_file); + + size_t i; + size_t index = 0; + /* + * Checks return value, filter length, and that the filter + * validates arch, loads syscall number, only allows expected syscalls, + * and returns TRAP on failure. + * NOTE(jorgelo): the filter is longer since we add the syscalls needed + * for logging. + */ + ASSERT_EQ(res, 0); + EXPECT_EQ(actual.len, 13 + 2 * log_syscalls_len); + EXPECT_ARCH_VALIDATION(actual.filter); + EXPECT_EQ_STMT(actual.filter + ARCH_VALIDATION_LEN, + BPF_LD+BPF_W+BPF_ABS, syscall_nr); + + index = ARCH_VALIDATION_LEN + 1; + for (i = 0; i < log_syscalls_len; i++) + EXPECT_ALLOW_SYSCALL(actual.filter + (index + 2 * i), + lookup_syscall(log_syscalls[i])); + + index += 2 * log_syscalls_len; + + EXPECT_ALLOW_SYSCALL(actual.filter + index, __NR_read); + EXPECT_ALLOW_SYSCALL(actual.filter + index + 2, __NR_write); + EXPECT_ALLOW_SYSCALL(actual.filter + index + 4, __NR_rt_sigreturn); + EXPECT_ALLOW_SYSCALL(actual.filter + index + 6, __NR_exit); + EXPECT_EQ_STMT(actual.filter + index + 8, BPF_RET+BPF_K, + SECCOMP_RET_TRAP); + + free(actual.filter); +} + +TEST_HARNESS_MAIN
diff --git a/minijail/syscall_filter_unittest.cpp b/minijail/syscall_filter_unittest.cpp new file mode 100644 index 0000000..09e98f6 --- /dev/null +++ b/minijail/syscall_filter_unittest.cpp
@@ -0,0 +1,769 @@ +// syscall_filter_unittest.cpp +// Copyright (C) 2016 The Android Open Source Project +// +// 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. +// +// Test syscall filtering using gtest. + +#include <asm/unistd.h> +#include <errno.h> +#include <fcntl.h> /* For O_WRONLY. */ + +#include <gtest/gtest.h> + +#include "bpf.h" +#include "syscall_filter.h" +#include "syscall_filter_unittest_macros.h" +#include "util.h" + +/* Test that setting one BPF instruction works. */ +TEST(bpf, set_bpf_instr) { + struct sock_filter instr; + unsigned char code = BPF_LD + BPF_W + BPF_ABS; + unsigned int k = 4; + unsigned char jt = 1, jf = 2; + + size_t len = set_bpf_instr(&instr, code, k, jt, jf); + + EXPECT_EQ(len, 1U); + EXPECT_EQ_BLOCK(&instr, code, k, jt, jf); +} + +TEST(bpf, bpf_load_arg) { + struct sock_filter load_arg[BPF_LOAD_ARG_LEN]; + int argidx = 1; + size_t len = bpf_load_arg(load_arg, argidx); + + EXPECT_EQ(len, BPF_LOAD_ARG_LEN); + +#if defined(BITS32) + EXPECT_EQ_STMT(&load_arg[0], BPF_LD + BPF_W + BPF_ABS, LO_ARG(argidx)); +#elif defined(BITS64) + EXPECT_EQ_STMT(&load_arg[0], BPF_LD + BPF_W + BPF_ABS, LO_ARG(argidx)); + EXPECT_EQ_STMT(&load_arg[1], BPF_ST, 0); + EXPECT_EQ_STMT(&load_arg[2], BPF_LD + BPF_W + BPF_ABS, HI_ARG(argidx)); + EXPECT_EQ_STMT(&load_arg[3], BPF_ST, 1); +#endif +} + +TEST(bpf, bpf_comp_jeq) { + struct sock_filter comp_jeq[BPF_COMP_LEN]; + unsigned long c = 1; + unsigned char jt = 1; + unsigned char jf = 2; + + size_t len = bpf_comp_jeq(comp_jeq, c, jt, jf); + + EXPECT_EQ(len, BPF_COMP_LEN); + +#if defined(BITS32) + EXPECT_EQ_BLOCK(&comp_jeq[0], BPF_JMP + BPF_JEQ + BPF_K, c, jt, jf); +#elif defined(BITS64) + EXPECT_EQ_BLOCK(&comp_jeq[0], BPF_JMP + BPF_JEQ + BPF_K, 0, 0, jf + 2); + EXPECT_EQ_STMT(&comp_jeq[1], BPF_LD + BPF_MEM, 0); + EXPECT_EQ_BLOCK(&comp_jeq[2], BPF_JMP + BPF_JEQ + BPF_K, c, jt, jf); +#endif +} + +TEST(bpf, bpf_comp_jset) { + struct sock_filter comp_jset[BPF_COMP_LEN]; + unsigned long mask = O_WRONLY; + unsigned char jt = 1; + unsigned char jf = 2; + + size_t len = bpf_comp_jset(comp_jset, mask, jt, jf); + + EXPECT_EQ(len, BPF_COMP_LEN); + +#if defined(BITS32) + EXPECT_EQ_BLOCK(&comp_jset[0], BPF_JMP + BPF_JSET + BPF_K, mask, jt, jf); +#elif defined(BITS64) + EXPECT_EQ_BLOCK(&comp_jset[0], BPF_JMP + BPF_JSET + BPF_K, 0, jt + 2, 0); + EXPECT_EQ_STMT(&comp_jset[1], BPF_LD + BPF_MEM, 0); + EXPECT_EQ_BLOCK(&comp_jset[2], BPF_JMP + BPF_JSET + BPF_K, mask, jt, jf); +#endif +} + +TEST(bpf, bpf_arg_comp) { + struct sock_filter *arg_comp; + int op = EQ; + int argidx = 1; + unsigned long c = 3; + unsigned int label_id = 0; + + size_t len = bpf_arg_comp(&arg_comp, op, argidx, c, label_id); + + EXPECT_EQ(len, BPF_ARG_COMP_LEN + 1); + +#if defined(BITS32) + EXPECT_EQ_STMT(&arg_comp[0], BPF_LD + BPF_W + BPF_ABS, LO_ARG(argidx)); + EXPECT_EQ_BLOCK(&arg_comp[1], BPF_JMP + BPF_JEQ + BPF_K, c, 1, 0); + EXPECT_JUMP_LBL(&arg_comp[2]); +#elif defined(BITS64) + EXPECT_EQ_STMT(&arg_comp[0], BPF_LD + BPF_W + BPF_ABS, LO_ARG(argidx)); + EXPECT_EQ_STMT(&arg_comp[1], BPF_ST, 0); + EXPECT_EQ_STMT(&arg_comp[2], BPF_LD + BPF_W + BPF_ABS, HI_ARG(argidx)); + EXPECT_EQ_STMT(&arg_comp[3], BPF_ST, 1); + + EXPECT_EQ_BLOCK(&arg_comp[4], BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 2); + EXPECT_EQ_STMT(&arg_comp[5], BPF_LD + BPF_MEM, 0); + EXPECT_EQ_BLOCK(&arg_comp[6], BPF_JMP + BPF_JEQ + BPF_K, c, 1, 0); + EXPECT_JUMP_LBL(&arg_comp[7]); +#endif + free(arg_comp); +} + +TEST(bpf, bpf_validate_arch) { + struct sock_filter validate_arch[ARCH_VALIDATION_LEN]; + + size_t len = bpf_validate_arch(validate_arch); + + EXPECT_EQ(len, ARCH_VALIDATION_LEN); + EXPECT_ARCH_VALIDATION(validate_arch); +} + +TEST(bpf, bpf_allow_syscall) { + struct sock_filter allow_syscall[ALLOW_SYSCALL_LEN]; + int nr = 1; + + size_t len = bpf_allow_syscall(allow_syscall, nr); + + EXPECT_EQ(len, ALLOW_SYSCALL_LEN); + EXPECT_ALLOW_SYSCALL(allow_syscall, nr); +} + +TEST(bpf, bpf_allow_syscall_args) { + struct sock_filter allow_syscall[ALLOW_SYSCALL_LEN]; + int nr = 1; + unsigned int id = 1024; + + size_t len = bpf_allow_syscall_args(allow_syscall, nr, id); + + EXPECT_EQ(len, ALLOW_SYSCALL_LEN); + EXPECT_ALLOW_SYSCALL_ARGS(allow_syscall, nr, id, JUMP_JT, JUMP_JF); +} + +class ArgFilterTest : public ::testing::Test { + protected: + virtual void TearDown() { free_label_strings(&labels_); } + struct bpf_labels labels_; +}; + +TEST_F(ArgFilterTest, arg0_equals) { + const char *fragment = "arg0 == 0"; + int nr = 1; + unsigned int id = 0; + struct filter_block *block = + compile_section(nr, fragment, id, &labels_, NO_LOGGING); + + ASSERT_TRUE(block != NULL); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_TRUE(curr_block != NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison. */ + curr_block = block->next; + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_GROUP_END(curr_block); + + /* Fourth block is SECCOMP_RET_KILL. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_KILL(curr_block); + + /* Fifth block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_TRUE(curr_block->next == NULL); + + free_block_list(block); +} + +TEST_F(ArgFilterTest, arg0_mask) { + const char *fragment = "arg1 & O_RDWR"; + int nr = 1; + unsigned int id = 0; + struct filter_block *block = + compile_section(nr, fragment, id, &labels_, NO_LOGGING); + + ASSERT_TRUE(block != NULL); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_TRUE(curr_block != NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison. */ + curr_block = block->next; + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_GROUP_END(curr_block); + + /* Fourth block is SECCOMP_RET_KILL. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_KILL(curr_block); + + /* Fifth block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_TRUE(curr_block->next == NULL); + + free_block_list(block); +} + +TEST_F(ArgFilterTest, arg0_eq_mask) { + const char *fragment = "arg1 == O_WRONLY|O_CREAT"; + int nr = 1; + unsigned int id = 0; + + struct filter_block *block = + compile_section(nr, fragment, id, &labels_, NO_LOGGING); + + ASSERT_TRUE(block != NULL); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_TRUE(curr_block != NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison. */ + curr_block = block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_COMP(curr_block); + EXPECT_EQ(curr_block->instrs[BPF_ARG_COMP_LEN - 1].k, + (unsigned int)(O_WRONLY | O_CREAT)); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_GROUP_END(curr_block); + + /* Fourth block is SECCOMP_RET_KILL. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_KILL(curr_block); + + /* Fifth block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_TRUE(curr_block->next == NULL); + + free_block_list(block); +} + +TEST_F(ArgFilterTest, and_or) { + const char *fragment = "arg0 == 0 && arg1 == 0 || arg0 == 1"; + int nr = 1; + unsigned int id = 0; + + struct filter_block *block = + compile_section(nr, fragment, id, &labels_, NO_LOGGING); + ASSERT_TRUE(block != NULL); + size_t exp_total_len = 1 + 3 * (BPF_ARG_COMP_LEN + 1) + 2 + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_TRUE(curr_block != NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison ("arg0 == 0"). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_COMP(curr_block); + + /* Third block is a comparison ("arg1 == 0"). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_COMP(curr_block); + + /* Fourth block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_GROUP_END(curr_block); + + /* Fifth block is a comparison ("arg0 == 1"). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_COMP(curr_block); + + /* Sixth block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_GROUP_END(curr_block); + + /* Seventh block is SECCOMP_RET_KILL. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_KILL(curr_block); + + /* Eigth block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_TRUE(curr_block->next == NULL); + + free_block_list(block); +} + +TEST_F(ArgFilterTest, ret_errno) { + const char *fragment = "arg0 == 0 || arg0 == 1; return 1"; + int nr = 1; + unsigned int id = 0; + + struct filter_block *block = + compile_section(nr, fragment, id, &labels_, NO_LOGGING); + ASSERT_TRUE(block != NULL); + size_t exp_total_len = 1 + 2 * (BPF_ARG_COMP_LEN + 1) + 2 + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_TRUE(curr_block != NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison ("arg0 == 0"). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_GROUP_END(curr_block); + + /* Fourth block is a comparison ("arg0 == 1"). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_COMP(curr_block); + + /* Fifth block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_GROUP_END(curr_block); + + /* Sixth block is SECCOMP_RET_ERRNO. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_EQ(curr_block->len, 1U); + EXPECT_EQ_STMT(curr_block->instrs, + BPF_RET + BPF_K, + SECCOMP_RET_ERRNO | (1 & SECCOMP_RET_DATA)); + + /* Seventh block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_TRUE(curr_block->next == NULL); + + free_block_list(block); +} + +TEST_F(ArgFilterTest, unconditional_errno) { + const char *fragment = "return 1"; + int nr = 1; + unsigned int id = 0; + + struct filter_block *block = + compile_section(nr, fragment, id, &labels_, NO_LOGGING); + ASSERT_TRUE(block != NULL); + size_t exp_total_len = 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_TRUE(curr_block != NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is SECCOMP_RET_ERRNO. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_EQ(curr_block->len, 1U); + EXPECT_EQ_STMT(curr_block->instrs, + BPF_RET + BPF_K, + SECCOMP_RET_ERRNO | (1 & SECCOMP_RET_DATA)); + + EXPECT_TRUE(curr_block->next == NULL); + + free_block_list(block); +} + +TEST_F(ArgFilterTest, invalid) { + const char *fragment = "argnn == 0"; + int nr = 1; + unsigned int id = 0; + + struct filter_block *block = + compile_section(nr, fragment, id, &labels_, NO_LOGGING); + ASSERT_TRUE(block == NULL); + + fragment = "arg0 == 0 && arg1 == 1; return errno"; + block = compile_section(nr, fragment, id, &labels_, NO_LOGGING); + ASSERT_TRUE(block == NULL); +} + +TEST_F(ArgFilterTest, log_no_ret_error) { + const char *fragment = "arg0 == 0"; + int nr = 1; + unsigned int id = 0; + struct filter_block *block = + compile_section(nr, fragment, id, &labels_, USE_LOGGING); + + ASSERT_TRUE(block != NULL); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_TRUE(curr_block != NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison. */ + curr_block = block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_GROUP_END(curr_block); + + /* Fourth block is SECCOMP_RET_TRAP, with no errno. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_TRAP(curr_block); + + /* Fifth block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_TRUE(curr_block->next == NULL); + + free_block_list(block); +} + +TEST_F(ArgFilterTest, log_bad_ret_error) { + const char *fragment = "arg0 == 0; return"; + int nr = 1; + unsigned int id = 0; + + struct filter_block *block = + compile_section(nr, fragment, id, &labels_, NO_LOGGING); + ASSERT_TRUE(block != NULL); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_TRUE(curr_block != NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison ("arg0 == 0"). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_GROUP_END(curr_block); + + /* + * Sixth block is NOT SECCOMP_RET_ERRNO, it should be SECCOMP_RET_KILL. + */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_KILL(curr_block); + + /* Seventh block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_TRUE(curr_block->next == NULL); + + free_block_list(block); +} + +TEST_F(ArgFilterTest, no_log_bad_ret_error) { + const char *fragment = "arg0 == 0; return"; + int nr = 1; + unsigned int id = 0; + + struct filter_block *block = + compile_section(nr, fragment, id, &labels_, USE_LOGGING); + ASSERT_TRUE(block != NULL); + size_t exp_total_len = 1 + (BPF_ARG_COMP_LEN + 1) + 2 + 1 + 2; + EXPECT_EQ(block->total_len, exp_total_len); + + /* First block is a label. */ + struct filter_block *curr_block = block; + ASSERT_TRUE(curr_block != NULL); + EXPECT_EQ(block->len, 1U); + EXPECT_LBL(curr_block->instrs); + + /* Second block is a comparison ("arg0 == 0"). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_COMP(curr_block); + + /* Third block is a jump and a label (end of AND group). */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_GROUP_END(curr_block); + + /* + * Sixth block is *not* SECCOMP_RET_ERRNO, it should be + * SECCOMP_RET_TRAP. + */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_TRAP(curr_block); + + /* Seventh block is "SUCCESS" label and SECCOMP_RET_ALLOW. */ + curr_block = curr_block->next; + ASSERT_TRUE(curr_block != NULL); + EXPECT_ALLOW(curr_block); + + EXPECT_TRUE(curr_block->next == NULL); + + free_block_list(block); +} + +FILE *write_policy_to_pipe(const char *policy, size_t len) { + int pipefd[2]; + if (pipe(pipefd) == -1) { + pwarn("pipe(pipefd) failed"); + return NULL; + } + + size_t i = 0; + unsigned int attempts = 0; + ssize_t ret; + while (i < len) { + ret = write(pipefd[1], &policy[i], len - i); + if (ret == -1) { + close(pipefd[0]); + close(pipefd[1]); + return NULL; + } + + /* If we write 0 bytes three times in a row, fail. */ + if (ret == 0) { + if (++attempts >= 3) { + close(pipefd[0]); + close(pipefd[1]); + warn("write() returned 0 three times in a row"); + return NULL; + } + continue; + } + + attempts = 0; + i += (size_t)ret; + } + + close(pipefd[1]); + return fdopen(pipefd[0], "r"); +} + +TEST(FilterTest, seccomp_mode1) { + struct sock_fprog actual; + const char *policy = + "read: 1\n" + "write: 1\n" + "rt_sigreturn: 1\n" + "exit: 1\n"; + + FILE *policy_file = write_policy_to_pipe(policy, strlen(policy)); + ASSERT_TRUE(policy_file != NULL); + + int res = compile_filter(policy_file, &actual, NO_LOGGING); + fclose(policy_file); + + /* + * Checks return value, filter length, and that the filter + * validates arch, loads syscall number, and + * only allows expected syscalls. + */ + ASSERT_EQ(res, 0); + EXPECT_EQ(actual.len, 13); + EXPECT_ARCH_VALIDATION(actual.filter); + EXPECT_EQ_STMT(actual.filter + ARCH_VALIDATION_LEN, + BPF_LD + BPF_W + BPF_ABS, + syscall_nr); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 1, __NR_read); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 3, __NR_write); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 5, + __NR_rt_sigreturn); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 7, __NR_exit); + EXPECT_EQ_STMT(actual.filter + ARCH_VALIDATION_LEN + 9, + BPF_RET + BPF_K, + SECCOMP_RET_KILL); + + free(actual.filter); +} + +TEST(FilterTest, seccomp_read_write) { + struct sock_fprog actual; + const char *policy = + "read: arg0 == 0\n" + "write: arg0 == 1 || arg0 == 2\n" + "rt_sigreturn: 1\n" + "exit: 1\n"; + + FILE *policy_file = write_policy_to_pipe(policy, strlen(policy)); + ASSERT_TRUE(policy_file != NULL); + + int res = compile_filter(policy_file, &actual, NO_LOGGING); + fclose(policy_file); + + /* + * Checks return value, filter length, and that the filter + * validates arch, loads syscall number, and + * only allows expected syscalls, jumping to correct arg filter + * offsets. + */ + ASSERT_EQ(res, 0); + size_t exp_total_len = 27 + 3 * (BPF_ARG_COMP_LEN + 1); + EXPECT_EQ(actual.len, exp_total_len); + + EXPECT_ARCH_VALIDATION(actual.filter); + EXPECT_EQ_STMT(actual.filter + ARCH_VALIDATION_LEN, + BPF_LD + BPF_W + BPF_ABS, + syscall_nr); + EXPECT_ALLOW_SYSCALL_ARGS( + actual.filter + ARCH_VALIDATION_LEN + 1, __NR_read, 7, 0, 0); + EXPECT_ALLOW_SYSCALL_ARGS(actual.filter + ARCH_VALIDATION_LEN + 3, + __NR_write, + 12 + BPF_ARG_COMP_LEN, + 0, + 0); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 5, + __NR_rt_sigreturn); + EXPECT_ALLOW_SYSCALL(actual.filter + ARCH_VALIDATION_LEN + 7, __NR_exit); + EXPECT_EQ_STMT(actual.filter + ARCH_VALIDATION_LEN + 9, + BPF_RET + BPF_K, + SECCOMP_RET_KILL); + + free(actual.filter); +} + +TEST(FilterTest, invalid_name) { + struct sock_fprog actual; + const char *policy = "notasyscall: 1\n"; + + FILE *policy_file = write_policy_to_pipe(policy, strlen(policy)); + ASSERT_TRUE(policy_file != NULL); + + int res = compile_filter(policy_file, &actual, NO_LOGGING); + fclose(policy_file); + ASSERT_NE(res, 0); +} + +TEST(FilterTest, invalid_arg) { + struct sock_fprog actual; + const char *policy = "open: argnn ==\n"; + + FILE *policy_file = write_policy_to_pipe(policy, strlen(policy)); + ASSERT_TRUE(policy_file != NULL); + + int res = compile_filter(policy_file, &actual, NO_LOGGING); + fclose(policy_file); + ASSERT_NE(res, 0); +} + +TEST(FilterTest, nonexistent) { + struct sock_fprog actual; + int res = compile_filter(NULL, &actual, NO_LOGGING); + ASSERT_NE(res, 0); +} + +TEST(FilterTest, log) { + struct sock_fprog actual; + const char *policy = + "read: 1\n" + "write: 1\n" + "rt_sigreturn: 1\n" + "exit: 1\n"; + + FILE *policy_file = write_policy_to_pipe(policy, strlen(policy)); + ASSERT_TRUE(policy_file != NULL); + + int res = compile_filter(policy_file, &actual, USE_LOGGING); + fclose(policy_file); + + size_t i; + size_t index = 0; + /* + * Checks return value, filter length, and that the filter + * validates arch, loads syscall number, only allows expected syscalls, + * and returns TRAP on failure. + * NOTE(jorgelo): the filter is longer since we add the syscalls needed + * for logging. + */ + ASSERT_EQ(res, 0); + EXPECT_EQ(actual.len, 13 + 2 * log_syscalls_len); + EXPECT_ARCH_VALIDATION(actual.filter); + EXPECT_EQ_STMT(actual.filter + ARCH_VALIDATION_LEN, + BPF_LD + BPF_W + BPF_ABS, + syscall_nr); + + index = ARCH_VALIDATION_LEN + 1; + for (i = 0; i < log_syscalls_len; i++) + EXPECT_ALLOW_SYSCALL(actual.filter + (index + 2 * i), + lookup_syscall(log_syscalls[i])); + + index += 2 * log_syscalls_len; + + EXPECT_ALLOW_SYSCALL(actual.filter + index, __NR_read); + EXPECT_ALLOW_SYSCALL(actual.filter + index + 2, __NR_write); + EXPECT_ALLOW_SYSCALL(actual.filter + index + 4, __NR_rt_sigreturn); + EXPECT_ALLOW_SYSCALL(actual.filter + index + 6, __NR_exit); + EXPECT_EQ_STMT(actual.filter + index + 8, BPF_RET + BPF_K, SECCOMP_RET_TRAP); + + free(actual.filter); +}
diff --git a/minijail/syscall_filter_unittest_macros.h b/minijail/syscall_filter_unittest_macros.h new file mode 100644 index 0000000..ec86374 --- /dev/null +++ b/minijail/syscall_filter_unittest_macros.h
@@ -0,0 +1,99 @@ +/* Copyright (C) 2016 The Android Open Source Project + * + * 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. + */ + +/* BPF testing macros. */ +#define EXPECT_EQ_BLOCK(_block, _code, _k, _jt, _jf) \ +do { \ + EXPECT_EQ((_block)->code, _code); \ + EXPECT_EQ((_block)->k, (unsigned int)(_k)); \ + EXPECT_EQ((_block)->jt, _jt); \ + EXPECT_EQ((_block)->jf, _jf); \ +} while (0) + +#define EXPECT_EQ_STMT(_block, _code, _k) \ + EXPECT_EQ_BLOCK(_block, _code, _k, 0, 0) + +#define EXPECT_COMP(_block) \ +do { \ + EXPECT_EQ((_block)->len, BPF_ARG_COMP_LEN + 1); \ + EXPECT_EQ((_block)->instrs->code, BPF_LD+BPF_W+BPF_ABS); \ +} while (0) + +#define EXPECT_LBL(_block) \ +do { \ + EXPECT_TRUE((_block)->code == (BPF_JMP+BPF_JA)); \ + EXPECT_TRUE((_block)->jt == LABEL_JT); \ + EXPECT_TRUE((_block)->jf == LABEL_JF); \ +} while (0) + +#define EXPECT_JUMP_LBL(_block) \ +do { \ + EXPECT_EQ((_block)->code, BPF_JMP+BPF_JA); \ + EXPECT_EQ((_block)->jt, JUMP_JT); \ + EXPECT_EQ((_block)->jf, JUMP_JF); \ +} while (0) + +#define EXPECT_GROUP_END(_block) \ +do { \ + EXPECT_EQ((_block)->len, 2U); \ + EXPECT_JUMP_LBL(&(_block)->instrs[0]); \ + EXPECT_LBL(&(_block)->instrs[1]); \ +} while (0) + +#define EXPECT_KILL(_block) \ +do { \ + EXPECT_EQ((_block)->len, 1U); \ + EXPECT_EQ_STMT((_block)->instrs, \ + BPF_RET+BPF_K, SECCOMP_RET_KILL); \ +} while (0) + +#define EXPECT_TRAP(_block) \ +do { \ + EXPECT_EQ((_block)->len, 1U); \ + EXPECT_EQ_STMT((_block)->instrs, \ + BPF_RET+BPF_K, SECCOMP_RET_TRAP); \ +} while (0) + +#define EXPECT_ALLOW(_block) \ +do { \ + EXPECT_EQ((_block)->len, 2U); \ + EXPECT_LBL(&(_block)->instrs[0]); \ + EXPECT_EQ_STMT(&(_block)->instrs[1], \ + BPF_RET+BPF_K, SECCOMP_RET_ALLOW); \ +} while (0) + +#define EXPECT_ARCH_VALIDATION(_filter) \ +do { \ + EXPECT_EQ_STMT(&(_filter)[0], BPF_LD+BPF_W+BPF_ABS, arch_nr); \ + EXPECT_EQ_BLOCK(&(_filter)[1], \ + BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, SKIP, NEXT); \ + EXPECT_EQ_STMT(&(_filter)[2], BPF_RET+BPF_K, SECCOMP_RET_KILL); \ +} while (0) + +#define EXPECT_ALLOW_SYSCALL(_filter, _nr) \ +do { \ + EXPECT_EQ_BLOCK(&(_filter)[0], \ + BPF_JMP+BPF_JEQ+BPF_K, (_nr), NEXT, SKIP); \ + EXPECT_EQ_STMT(&(_filter)[1], \ + BPF_RET+BPF_K, SECCOMP_RET_ALLOW); \ +} while (0) + +#define EXPECT_ALLOW_SYSCALL_ARGS(_filter, _nr, _id, _jt, _jf) \ +do { \ + EXPECT_EQ_BLOCK(&(_filter)[0], \ + BPF_JMP+BPF_JEQ+BPF_K, (_nr), NEXT, SKIP); \ + EXPECT_EQ_BLOCK(&(_filter)[1], \ + BPF_JMP+BPF_JA, (_id), (_jt), (_jf)); \ +} while (0)
diff --git a/minijail/test/.clang-format b/minijail/test/.clang-format new file mode 100644 index 0000000..c98efc2 --- /dev/null +++ b/minijail/test/.clang-format
@@ -0,0 +1,10 @@ +BasedOnStyle: Google +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +BinPackArguments: false +BinPackParameters: false +CommentPragmas: NOLINT:.* +DerivePointerAlignment: false +PointerAlignment: Left +TabWidth: 2
diff --git a/minijail/test/invalid_arg_filter.policy b/minijail/test/invalid_arg_filter.policy new file mode 100644 index 0000000..b79b31a --- /dev/null +++ b/minijail/test/invalid_arg_filter.policy
@@ -0,0 +1 @@ +open: argnn ==
diff --git a/minijail/test/invalid_syscall_name.policy b/minijail/test/invalid_syscall_name.policy new file mode 100644 index 0000000..3e6d403 --- /dev/null +++ b/minijail/test/invalid_syscall_name.policy
@@ -0,0 +1 @@ +notasyscall: 1
diff --git a/minijail/test/libminijail_test.cpp b/minijail/test/libminijail_test.cpp new file mode 100644 index 0000000..6499a37 --- /dev/null +++ b/minijail/test/libminijail_test.cpp
@@ -0,0 +1,134 @@ +// Copyright (C) 2016 The Android Open Source Project +// +// 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. + +#include <sys/capability.h> +#include <sys/types.h> +#include <unistd.h> + +#include <libminijail.h> + +#include <android-base/logging.h> + +const uid_t kSystemUid = 1000U; + +gid_t groups[] = {kSystemUid + 1, kSystemUid + 2}; + +size_t getgroups_with_alloc(gid_t** plist) { + *plist = NULL; + + int nsupp_groups = getgroups(0, NULL); + if (nsupp_groups < 0) { + PLOG(ERROR) << "getgroups(0)"; + return 0; + } + if (nsupp_groups == 0) { + LOG(INFO) << "No supplementary groups."; + return 0; + } + + *plist = (gid_t*)calloc((size_t)nsupp_groups, sizeof(gid_t)); + nsupp_groups = getgroups(nsupp_groups, *plist); + if (nsupp_groups < 0) { + PLOG(ERROR) << "getgroups(nsupp_groups)"; + free(*plist); + return 0; + } + return nsupp_groups; +} + +bool check_ugid(uid_t expected_id) { + bool success = true; + + uid_t ruid = getuid(); + if (ruid != expected_id) { + LOG(ERROR) << "rUID " << ruid << " is not " << expected_id; + success = false; + } + gid_t rgid = getgid(); + if (rgid != expected_id) { + LOG(ERROR) << "rGID " << ruid << " is not " << expected_id; + success = false; + } + return success; +} + +bool check_groups(size_t expected_size, gid_t* expected_list) { + bool success = true; + + gid_t* actual_list; + size_t actual_size = getgroups_with_alloc(&actual_list); + + if (expected_size != actual_size) { + LOG(ERROR) << "Mismatched supplementary group list size: expected " + << expected_size << ", actual " << actual_size; + success = false; + } + + for (size_t i = 0; i < expected_size; i++) { + bool found = false; + for (size_t j = 0; j < actual_size; j++) { + if (expected_list[i] == actual_list[j]) { + // Test next expected GID. + found = true; + break; + } + } + if (!found) { + LOG(ERROR) << "Expected GID " << expected_list[i] << " not found."; + success = false; + } + } + free(actual_list); + return success; +} + +void log_resugid() { + uid_t ruid, euid, suid; + gid_t rgid, egid, sgid; + getresuid(&ruid, &euid, &suid); + getresgid(&rgid, &egid, &sgid); + + LOG(INFO) << "ruid " << ruid << " euid " << euid << " suid " << suid; + LOG(INFO) << "rgid " << rgid << " egid " << egid << " sgid " << sgid; + + gid_t* list; + size_t nsupp_groups = getgroups_with_alloc(&list); + for (size_t i = 0; i < (size_t)nsupp_groups; i++) { + LOG(INFO) << "supp gid " << i + 1 << " " << list[i]; + } + free(list); +} + +int main(void) { + minijail* j = minijail_new(); + minijail_change_user(j, "system"); + minijail_change_group(j, "system"); + size_t num_groups = sizeof(groups) / sizeof(groups[0]); + minijail_set_supplementary_gids(j, num_groups, groups); + minijail_use_caps(j, CAP_TO_MASK(CAP_SETUID) | CAP_TO_MASK(CAP_SETGID)); + minijail_enter(j); + + bool success = check_ugid(kSystemUid); + success = success && check_groups(num_groups, groups); + + minijail_destroy(j); + minijail* j2 = minijail_new(); + minijail_change_uid(j2, 5 * kSystemUid); + minijail_change_gid(j2, 5 * kSystemUid); + minijail_enter(j2); + + success = success && check_ugid(5 * kSystemUid); + + return success ? 0 : 1; +}
diff --git a/minijail/test/read_stdin b/minijail/test/read_stdin new file mode 100755 index 0000000..29578a6 --- /dev/null +++ b/minijail/test/read_stdin
@@ -0,0 +1,13 @@ +#!/bin/bash + +# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +read line + +if [ "$line" == "test" ]; then + exit 0 +else + exit 1 +fi
diff --git a/minijail/test/seccomp.policy b/minijail/test/seccomp.policy new file mode 100644 index 0000000..b983631 --- /dev/null +++ b/minijail/test/seccomp.policy
@@ -0,0 +1,4 @@ +read: 1 +write: 1 +rt_sigreturn: 1 +exit: 1
diff --git a/minijail/test/stdin_stdout.policy b/minijail/test/stdin_stdout.policy new file mode 100644 index 0000000..874a5ca --- /dev/null +++ b/minijail/test/stdin_stdout.policy
@@ -0,0 +1,4 @@ +read: arg0 == 0 +write: arg0 == 1 || arg0 == 2 +rt_sigreturn: 1 +exit: 1
diff --git a/minijail/test_harness.h b/minijail/test_harness.h new file mode 100644 index 0000000..c05f1a7 --- /dev/null +++ b/minijail/test_harness.h
@@ -0,0 +1,439 @@ +/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * test_harness.h: simple C unit test helper. + * + * Usage: + * #include "test_harness.h" + * TEST(standalone_test) { + * do_some_stuff; + * EXPECT_GT(10, stuff) { + * stuff_state_t state; + * enumerate_stuff_state(&state); + * TH_LOG("expectation failed with state: %s", state.msg); + * } + * more_stuff; + * ASSERT_NE(some_stuff, NULL) TH_LOG("how did it happen?!"); + * last_stuff; + * EXPECT_EQ(0, last_stuff); + * } + * + * FIXTURE(my_fixture) { + * mytype_t *data; + * int awesomeness_level; + * }; + * FIXTURE_SETUP(my_fixture) { + * self->data = mytype_new(); + * ASSERT_NE(NULL, self->data); + * } + * FIXTURE_TEARDOWN(my_fixture) { + * mytype_free(self->data); + * } + * TEST_F(my_fixture, data_is_good) { + * EXPECT_EQ(1, is_my_data_good(self->data)); + * } + * + * TEST_HARNESS_MAIN + * + * API inspired by code.google.com/p/googletest + */ +#ifndef TEST_HARNESS_H_ +#define TEST_HARNESS_H_ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +/* All exported functionality should be declared through this macro. */ +#define TEST_API(x) _##x + +/* + * Exported APIs + */ + +/* TEST(name) { implementation } + * Defines a test by name. + * Names must be unique and tests must not be run in parallel. The + * implementation containing block is a function and scoping should be treated + * as such. Returning early may be performed with a bare "return;" statement. + * + * EXPECT_* and ASSERT_* are valid in a TEST() { } context. + */ +#define TEST TEST_API(TEST) + +/* FIXTURE(datatype name) { + * type property1; + * ... + * }; + * Defines the data provided to TEST_F()-defined tests as |self|. It should be + * populated and cleaned up using FIXTURE_SETUP and FIXTURE_TEARDOWN. + */ +#define FIXTURE TEST_API(FIXTURE) + +/* FIXTURE_DATA(datatype name) + * This call may be used when the type of the fixture data + * is needed. In general, this should not be needed unless + * the |self| is being passed to a helper directly. + */ +#define FIXTURE_DATA TEST_API(FIXTURE_DATA) + +/* FIXTURE_SETUP(fixture name) { implementation } + * Populates the required "setup" function for a fixture. An instance of the + * datatype defined with _FIXTURE_DATA will be exposed as |self| for the + * implementation. + * + * ASSERT_* are valid for use in this context and will prempt the execution + * of any dependent fixture tests. + * + * A bare "return;" statement may be used to return early. + */ +#define FIXTURE_SETUP TEST_API(FIXTURE_SETUP) + +/* FIXTURE_TEARDOWN(fixture name) { implementation } + * Populates the required "teardown" function for a fixture. An instance of the + * datatype defined with _FIXTURE_DATA will be exposed as |self| for the + * implementation to clean up. + * + * A bare "return;" statement may be used to return early. + */ +#define FIXTURE_TEARDOWN TEST_API(FIXTURE_TEARDOWN) + +/* TEST_F(fixture, name) { implementation } + * Defines a test that depends on a fixture (e.g., is part of a test case). + * Very similar to TEST() except that |self| is the setup instance of fixture's + * datatype exposed for use by the implementation. + */ +#define TEST_F TEST_API(TEST_F) + +/* Use once to append a main() to the test file. E.g., + * TEST_HARNESS_MAIN + */ +#define TEST_HARNESS_MAIN TEST_API(TEST_HARNESS_MAIN) + +/* + * Operators for use in TEST and TEST_F. + * ASSERT_* calls will stop test execution immediately. + * EXPECT_* calls will emit a failure warning, note it, and continue. + */ + +/* ASSERT_EQ(expected, measured): expected == measured */ +#define ASSERT_EQ TEST_API(ASSERT_EQ) +/* ASSERT_NE(expected, measured): expected != measured */ +#define ASSERT_NE TEST_API(ASSERT_NE) +/* ASSERT_LT(expected, measured): expected < measured */ +#define ASSERT_LT TEST_API(ASSERT_LT) +/* ASSERT_LE(expected, measured): expected <= measured */ +#define ASSERT_LE TEST_API(ASSERT_LE) +/* ASSERT_GT(expected, measured): expected > measured */ +#define ASSERT_GT TEST_API(ASSERT_GT) +/* ASSERT_GE(expected, measured): expected >= measured */ +#define ASSERT_GE TEST_API(ASSERT_GE) +/* ASSERT_NULL(measured): NULL == measured */ +#define ASSERT_NULL TEST_API(ASSERT_NULL) +/* ASSERT_TRUE(measured): measured != 0 */ +#define ASSERT_TRUE TEST_API(ASSERT_TRUE) +/* ASSERT_FALSE(measured): measured == 0 */ +#define ASSERT_FALSE TEST_API(ASSERT_FALSE) +/* ASSERT_STREQ(expected, measured): !strcmp(expected, measured) */ +#define ASSERT_STREQ TEST_API(ASSERT_STREQ) +/* ASSERT_STRNE(expected, measured): strcmp(expected, measured) */ +#define ASSERT_STRNE TEST_API(ASSERT_STRNE) +/* EXPECT_EQ(expected, measured): expected == measured */ +#define EXPECT_EQ TEST_API(EXPECT_EQ) +/* EXPECT_NE(expected, measured): expected != measured */ +#define EXPECT_NE TEST_API(EXPECT_NE) +/* EXPECT_LT(expected, measured): expected < measured */ +#define EXPECT_LT TEST_API(EXPECT_LT) +/* EXPECT_LE(expected, measured): expected <= measured */ +#define EXPECT_LE TEST_API(EXPECT_LE) +/* EXPECT_GT(expected, measured): expected > measured */ +#define EXPECT_GT TEST_API(EXPECT_GT) +/* EXPECT_GE(expected, measured): expected >= measured */ +#define EXPECT_GE TEST_API(EXPECT_GE) +/* EXPECT_NULL(measured): NULL == measured */ +#define EXPECT_NULL TEST_API(EXPECT_NULL) +/* EXPECT_TRUE(measured): 0 != measured */ +#define EXPECT_TRUE TEST_API(EXPECT_TRUE) +/* EXPECT_FALSE(measured): 0 == measured */ +#define EXPECT_FALSE TEST_API(EXPECT_FALSE) +/* EXPECT_STREQ(expected, measured): !strcmp(expected, measured) */ +#define EXPECT_STREQ TEST_API(EXPECT_STREQ) +/* EXPECT_STRNE(expected, measured): strcmp(expected, measured) */ +#define EXPECT_STRNE TEST_API(EXPECT_STRNE) + +/* TH_LOG(format, ...) + * Optional debug logging function available for use in tests. + * Logging may be enabled or disabled by defining TH_LOG_ENABLED. + * E.g., #define TH_LOG_ENABLED 1 + * If no definition is provided, logging is enabled by default. + */ +#define TH_LOG TEST_API(TH_LOG) + +/* + * Internal implementation. + * + */ + +/* Utilities exposed to the test definitions */ +#ifndef TH_LOG_STREAM +# define TH_LOG_STREAM stderr +#endif + +#ifndef TH_LOG_ENABLED +# define TH_LOG_ENABLED 1 +#endif + +#define _TH_LOG(fmt, ...) do { \ + if (TH_LOG_ENABLED) \ + __TH_LOG(fmt, ##__VA_ARGS__); \ +} while (0) + +/* Unconditional logger for internal use. */ +#define __TH_LOG(fmt, ...) \ + fprintf(TH_LOG_STREAM, "%s:%d:%s:" fmt "\n", \ + __FILE__, __LINE__, _metadata->name, ##__VA_ARGS__) + +/* Defines the test function and creates the registration stub. */ +#define _TEST(test_name) \ + static void test_name(struct __test_metadata *_metadata); \ + static struct __test_metadata _##test_name##_object = \ + { .name= "global." #test_name, .fn= &(test_name) }; \ + static void __attribute__((constructor)) _register_##test_name(void) { \ + __register_test(&_##test_name##_object); \ + } \ + static void test_name( \ + struct __test_metadata __attribute__((unused)) *_metadata) + +/* Wraps the struct name so we have one less argument to pass around. */ +#define _FIXTURE_DATA(fixture_name) struct _test_data_##fixture_name + +/* Called once per fixture to setup the data and register. */ +#define _FIXTURE(fixture_name) \ + static void __attribute__((constructor)) \ + _register_##fixture_name##_data(void) { \ + __fixture_count++; \ + } \ + _FIXTURE_DATA(fixture_name) + +/* Prepares the setup function for the fixture. |_metadata| is included + * so that ASSERT_* work as a convenience. + */ +#define _FIXTURE_SETUP(fixture_name) \ + void fixture_name##_setup( \ + struct __test_metadata __attribute__((unused)) *_metadata, \ + _FIXTURE_DATA(fixture_name) __attribute__((unused)) *self) +#define _FIXTURE_TEARDOWN(fixture_name) \ + void fixture_name##_teardown( \ + struct __test_metadata __attribute__((unused)) *_metadata, \ + _FIXTURE_DATA(fixture_name) __attribute__((unused)) *self) + +/* Emits test registration and helpers for fixture-based test + * cases. + * TODO(wad) register fixtures on dedicated test lists. + */ +#define _TEST_F(fixture_name, test_name) \ + static void fixture_name##_##test_name( \ + struct __test_metadata *_metadata, \ + _FIXTURE_DATA(fixture_name) *self); \ + static inline void wrapper_##fixture_name##_##test_name( \ + struct __test_metadata *_metadata) { \ + /* fixture data is allocated, setup, and torn down per call. */ \ + _FIXTURE_DATA(fixture_name) self; \ + memset(&self, 0, sizeof(_FIXTURE_DATA(fixture_name))); \ + fixture_name##_setup(_metadata, &self); \ + /* Let setup failure terminate early. */ \ + if (!_metadata->passed) return; \ + fixture_name##_##test_name(_metadata, &self); \ + fixture_name##_teardown(_metadata, &self); \ + } \ + static struct __test_metadata _##fixture_name##_##test_name##_object = { \ + .name= #fixture_name "." #test_name, \ + .fn= &wrapper_##fixture_name##_##test_name, \ + }; \ + static void __attribute__((constructor)) \ + _register_##fixture_name##_##test_name(void) { \ + __register_test(&_##fixture_name##_##test_name##_object); \ + } \ + static void fixture_name##_##test_name( \ + struct __test_metadata __attribute__((unused)) *_metadata, \ + _FIXTURE_DATA(fixture_name) __attribute__((unused)) *self) + +/* Exports a simple wrapper to run the test harness. */ +#define _TEST_HARNESS_MAIN \ + int main(int argc, char **argv) { return test_harness_run(argc, argv); } + +#define _ASSERT_EQ(_expected, _seen) \ + __EXPECT(_expected, _seen, ==, 1) +#define _ASSERT_NE(_expected, _seen) \ + __EXPECT(_expected, _seen, !=, 1) +#define _ASSERT_LT(_expected, _seen) \ + __EXPECT(_expected, _seen, <, 1) +#define _ASSERT_LE(_expected, _seen) \ + __EXPECT(_expected, _seen, <=, 1) +#define _ASSERT_GT(_expected, _seen) \ + __EXPECT(_expected, _seen, >, 1) +#define _ASSERT_GE(_expected, _seen) \ + __EXPECT(_expected, _seen, >=, 1) +#define _ASSERT_NULL(_seen) \ + __EXPECT(NULL, _seen, ==, 1) + +#define _ASSERT_TRUE(_seen) \ + _ASSERT_NE(0, _seen) +#define _ASSERT_FALSE(_seen) \ + _ASSERT_EQ(0, _seen) +#define _ASSERT_STREQ(_expected, _seen) \ + __EXPECT_STR(_expected, _seen, ==, 1) +#define _ASSERT_STRNE(_expected, _seen) \ + __EXPECT_STR(_expected, _seen, !=, 1) + +#define _EXPECT_EQ(_expected, _seen) \ + __EXPECT(_expected, _seen, ==, 0) +#define _EXPECT_NE(_expected, _seen) \ + __EXPECT(_expected, _seen, !=, 0) +#define _EXPECT_LT(_expected, _seen) \ + __EXPECT(_expected, _seen, <, 0) +#define _EXPECT_LE(_expected, _seen) \ + __EXPECT(_expected, _seen, <=, 0) +#define _EXPECT_GT(_expected, _seen) \ + __EXPECT(_expected, _seen, >, 0) +#define _EXPECT_GE(_expected, _seen) \ + __EXPECT(_expected, _seen, >=, 0) + +#define _EXPECT_NULL(_seen) \ + __EXPECT(NULL, _seen, ==, 0) +#define _EXPECT_TRUE(_seen) \ + _EXPECT_NE(0, _seen) +#define _EXPECT_FALSE(_seen) \ + _EXPECT_EQ(0, _seen) + +#define _EXPECT_STREQ(_expected, _seen) \ + __EXPECT_STR(_expected, _seen, ==, 0) +#define _EXPECT_STRNE(_expected, _seen) \ + __EXPECT_STR(_expected, _seen, !=, 0) + +/* Support an optional handler after and ASSERT_* or EXPECT_*. The approach is + * not thread-safe, but it should be fine in most sane test scenarios. + * + * Using __bail(), which optionally abort()s, is the easiest way to early + * return while still providing an optional block to the API consumer. + */ +#define OPTIONAL_HANDLER(_assert) \ + for (; _metadata->trigger; _metadata->trigger = __bail(_assert)) + +#define __EXPECT(_expected, _seen, _t, _assert) do { \ + /* Avoid multiple evaluation of the cases */ \ + __typeof__(_expected) __exp = (_expected); \ + __typeof__(_seen) __seen = (_seen); \ + if (!(__exp _t __seen)) { \ + unsigned long long __exp_print = 0; \ + unsigned long long __seen_print = 0; \ + /* Avoid casting complaints the scariest way we can. */ \ + memcpy(&__exp_print, &__exp, sizeof(__exp)); \ + memcpy(&__seen_print, &__seen, sizeof(__seen)); \ + __TH_LOG("Expected %s (%llu) %s %s (%llu)", \ + #_expected, __exp_print, #_t, \ + #_seen, __seen_print); \ + _metadata->passed = 0; \ + /* Ensure the optional handler is triggered */ \ + _metadata->trigger = 1; \ + } \ +} while (0); OPTIONAL_HANDLER(_assert) + +/* NOLINT: clang-tidy adds wrong parentheses around _t. */ +#define __EXPECT_STR(_expected, _seen, _t, _assert) do { \ + const char *__exp = (_expected); \ + const char *__seen = (_seen); \ + if (!(strcmp(__exp, __seen) _t 0)) { /* NOLINT */ \ + __TH_LOG("Expected '%s' %s '%s'.", __exp, #_t, __seen); \ + _metadata->passed = 0; \ + _metadata->trigger = 1; \ + } \ +} while (0); OPTIONAL_HANDLER(_assert) + +/* Contains all the information for test execution and status checking. */ +struct __test_metadata { + const char *name; + void (*fn)(struct __test_metadata *); + int passed; + int trigger; /* extra handler after the evaluation */ + struct __test_metadata *prev, *next; +}; + +/* Storage for the (global) tests to be run. */ +static struct __test_metadata *__test_list = NULL; +static unsigned int __test_count = 0; +static unsigned int __fixture_count = 0; + +static inline void __register_test(struct __test_metadata *t) { + __test_count++; + /* Circular linked list where only prev is circular. */ + if (__test_list == NULL) { + __test_list = t; + t->next = NULL; + t->prev = t; + return; + } + t->next = NULL; + t->prev = __test_list->prev; + t->prev->next = t; + __test_list->prev = t; +} + +static inline int __bail(int for_realz) { + if (for_realz) + abort(); + return 0; +} + +static int test_harness_run(int __attribute__((unused)) argc, + char __attribute__((unused)) **argv) { + struct __test_metadata *t; + int ret = 0; + unsigned int count = 0; + + /* TODO(wad) add optional arguments similar to gtest. */ + printf("[==========] Running %u tests from %u test cases.\n", + __test_count, __fixture_count + 1); + for (t = __test_list; t; t = t->next) { + pid_t child_pid; + int status; + count++; + t->passed = 1; + t->trigger = 0; + printf("[ RUN ] %s\n", t->name); + child_pid = fork(); + if (child_pid < 0) { + printf("ERROR SPAWNING TEST CHILD\n"); + t->passed = 0; + } else if (child_pid == 0) { + t->fn(t); + _exit(t->passed); + } else { + /* TODO(wad) add timeout support. */ + waitpid(child_pid, &status, 0); + if (WIFEXITED(status)) + t->passed = WEXITSTATUS(status); + if (WIFSIGNALED(status)) { + t->passed = 0; + fprintf(TH_LOG_STREAM, + "%s: Test terminated unexpectedly by signal %d\n", + t->name, + WTERMSIG(status)); + } + } + printf("[ %4s ] %s\n", (t->passed ? "OK" : "FAIL"), t->name); + if (!t->passed) + ret = 1; + } + /* TODO(wad) organize by fixtures since ordering is not guaranteed now. */ + printf("[==========] %u tests ran.\n", count); + printf("[ %s ]\n", (ret ? "FAILED" : "PASSED")); + return ret; +} + +#endif /* TEST_HARNESS_H_ */
diff --git a/minijail/tools/generate_seccomp_policy.py b/minijail/tools/generate_seccomp_policy.py new file mode 100755 index 0000000..0b51e71 --- /dev/null +++ b/minijail/tools/generate_seccomp_policy.py
@@ -0,0 +1,169 @@ +#!/usr/bin/python +# +# Copyright (C) 2016 The Android Open Source Project +# +# 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. +# +# This script will take any number of trace files generated by strace(1) +# and output a system call filtering policy suitable for use with Minijail. + +from collections import namedtuple +import sys + +NOTICE = """# Copyright (C) 2016 The Android Open Source Project +# +# 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. +""" + +ALLOW = "%s: 1" + +SOCKETCALLS = ["accept", "bind", "connect", "getpeername", "getsockname", + "getsockopt", "listen", "recv", "recvfrom", "recvmsg", "send", + "sendmsg", "sendto", "setsockopt", "shutdown", "socket", + "socketpair"] + +# /* Protocol families. */ +# #define PF_UNSPEC 0 /* Unspecified. */ +# #define PF_LOCAL 1 /* Local to host (pipes and file-domain). */ +# #define PF_UNIX PF_LOCAL /* POSIX name for PF_LOCAL. */ +# #define PF_FILE PF_LOCAL /* Another non-standard name for PF_LOCAL. */ +# #define PF_INET 2 /* IP protocol family. */ +# #define PF_AX25 3 /* Amateur Radio AX.25. */ +# #define PF_IPX 4 /* Novell Internet Protocol. */ +# #define PF_APPLETALK 5 /* Appletalk DDP. */ +# #define PF_NETROM 6 /* Amateur radio NetROM. */ +# #define PF_BRIDGE 7 /* Multiprotocol bridge. */ +# #define PF_ATMPVC 8 /* ATM PVCs. */ +# #define PF_X25 9 /* Reserved for X.25 project. */ +# #define PF_INET6 10 /* IP version 6. */ +# #define PF_ROSE 11 /* Amateur Radio X.25 PLP. */ +# #define PF_DECnet 12 /* Reserved for DECnet project. */ +# #define PF_NETBEUI 13 /* Reserved for 802.2LLC project. */ +# #define PF_SECURITY 14 /* Security callback pseudo AF. */ +# #define PF_KEY 15 /* PF_KEY key management API. */ +# #define PF_NETLINK 16 + +ArgInspectionEntry = namedtuple("ArgInspectionEntry", "arg_index value_set") + + +def usage(argv): + print "%s <trace file> [trace files...]" % argv[0] + + +def main(traces): + syscalls = {} + + uses_socketcall = False + + basic_set = ["restart_syscall", "exit", "exit_group", + "rt_sigreturn"] + frequent_set = [] + + syscall_sets = {} + syscall_set_list = [["sigreturn", "rt_sigreturn"], + ["sigaction", "rt_sigaction"], + ["sigprocmask", "rt_sigprocmask"], + ["open", "openat"], + ["mmap", "mremap"], + ["mmap2", "mremap"]] + + arg_inspection = { + "socket": ArgInspectionEntry(0, set([])), # int domain + "ioctl": ArgInspectionEntry(1, set([])), # int request + "prctl": ArgInspectionEntry(0, set([])) # int option + } + + for syscall_list in syscall_set_list: + for syscall in syscall_list: + other_syscalls = syscall_list[:] + other_syscalls.remove(syscall) + syscall_sets[syscall] = other_syscalls + + for trace_filename in traces: + if "i386" in trace_filename or ("x86" in trace_filename and + "64" not in trace_filename): + uses_socketcall = True + + trace_file = open(trace_filename) + for line in trace_file: + if "---" in line or '(' not in line: + continue + + syscall, args = line.strip().split('(', 1) + if uses_socketcall and syscall in SOCKETCALLS: + syscall = "socketcall" + + if syscall in syscalls: + syscalls[syscall] += 1 + else: + syscalls[syscall] = 1 + + args = [arg.strip() for arg in args.split(')', 1)[0].split(',')] + + if syscall in arg_inspection: + arg_value = args[arg_inspection[syscall].arg_index] + arg_inspection[syscall].value_set.add(arg_value) + + sorted_syscalls = list(zip(*sorted(syscalls.iteritems(), + key=lambda pair: pair[1], + reverse=True))[0]) + + print NOTICE + + # Add frequent syscalls first. + for frequent_syscall in frequent_set: + sorted_syscalls.remove(frequent_syscall) + + all_syscalls = frequent_set + sorted_syscalls + + # Add the basic set once the frequency drops below 2. + below_ten_index = -1 + for sorted_syscall in sorted_syscalls: + if syscalls[sorted_syscall] < 2: + below_ten_index = all_syscalls.index(sorted_syscall) + break + + first_half = all_syscalls[:below_ten_index] + for basic_syscall in basic_set: + if basic_syscall not in all_syscalls: + first_half.append(basic_syscall) + + all_syscalls = first_half + all_syscalls[below_ten_index:] + + for syscall in all_syscalls: + if syscall in arg_inspection: + arg_index = arg_inspection[syscall].arg_index + arg_values = arg_inspection[syscall].value_set + arg_filter = " || ".join(["arg%d == %s" % (arg_index, arg_value) + for arg_value in arg_values]) + print syscall + ": " + arg_filter + else: + print ALLOW % syscall + + +if __name__ == "__main__": + if len(sys.argv) < 2: + usage(sys.argv) + sys.exit(1) + + main(sys.argv[1:])
diff --git a/minijail/util.c b/minijail/util.c new file mode 100644 index 0000000..b82e308 --- /dev/null +++ b/minijail/util.c
@@ -0,0 +1,289 @@ +/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <sys/utsname.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "util.h" + +#include "libconstants.h" +#include "libsyscalls.h" + +/* + * These are syscalls used by the syslog() C library call. You can find them + * by running a simple test program. See below for x86_64 behavior: + * $ cat test.c + * #include <syslog.h> + * main() { syslog(0, "foo"); } + * $ gcc test.c -static + * $ strace ./a.out + * ... + * socket(PF_FILE, SOCK_DGRAM|SOCK_CLOEXEC, 0) = 3 <- look for socket connection + * connect(...) <- important + * sendto(...) <- important + * exit_group(0) <- finish! + */ +#if defined(__x86_64__) +#if defined(__ANDROID__) +const char *log_syscalls[] = {"socket", "connect", "fcntl", "writev"}; +#else +const char *log_syscalls[] = {"connect", "sendto"}; +#endif +#elif defined(__i386__) +#if defined(__ANDROID__) +const char *log_syscalls[] = {"socketcall", "writev", "fcntl64", + "clock_gettime"}; +#else +const char *log_syscalls[] = {"socketcall", "time"}; +#endif +#elif defined(__arm__) +#if defined(__ANDROID__) +const char *log_syscalls[] = {"clock_gettime", "connect", "fcntl64", "socket", + "writev"}; +#else +const char *log_syscalls[] = {"connect", "gettimeofday", "send"}; +#endif +#elif defined(__aarch64__) +#if defined(__ANDROID__) +const char *log_syscalls[] = {"connect", "fcntl", "sendto", "socket", "writev"}; +#else +const char *log_syscalls[] = {"connect", "send"}; +#endif +#elif defined(__powerpc__) || defined(__ia64__) || defined(__hppa__) || \ + defined(__sparc__) || defined(__mips__) +const char *log_syscalls[] = {"connect", "send"}; +#else +#error "Unsupported platform" +#endif + +const size_t log_syscalls_len = sizeof(log_syscalls)/sizeof(log_syscalls[0]); + +int lookup_syscall(const char *name) +{ + const struct syscall_entry *entry = syscall_table; + for (; entry->name && entry->nr >= 0; ++entry) + if (!strcmp(entry->name, name)) + return entry->nr; + return -1; +} + +const char *lookup_syscall_name(int nr) +{ + const struct syscall_entry *entry = syscall_table; + for (; entry->name && entry->nr >= 0; ++entry) + if (entry->nr == nr) + return entry->name; + return NULL; +} + +long int parse_single_constant(char *constant_str, char **endptr) +{ + const struct constant_entry *entry = constant_table; + for (; entry->name; ++entry) { + if (!strcmp(entry->name, constant_str)) { + if (endptr) + *endptr = constant_str + strlen(constant_str); + + return entry->value; + } + } + + return strtol(constant_str, endptr, 0); +} + +long int parse_constant(char *constant_str, char **endptr) +{ + long int value = 0; + char *group, *lastpos = constant_str; + char *original_constant_str = constant_str; + + /* + * Try to parse constants separated by pipes. Note that since + * |constant_str| is an atom, there can be no spaces between the + * constant and the pipe. Constants can be either a named constant + * defined in libconstants.gen.c or a number parsed with strtol. + * + * If there is an error parsing any of the constants, the whole process + * fails. + */ + while ((group = tokenize(&constant_str, "|")) != NULL) { + char *end = group; + value |= parse_single_constant(group, &end); + if (end == group) { + lastpos = original_constant_str; + value = 0; + break; + } + lastpos = end; + } + if (endptr) + *endptr = lastpos; + return value; +} + +char *strip(char *s) +{ + char *end; + while (*s && isblank(*s)) + s++; + end = s + strlen(s) - 1; + while (end >= s && *end && (isblank(*end) || *end == '\n')) + end--; + *(end + 1) = '\0'; + return s; +} + +char *tokenize(char **stringp, const char *delim) +{ + char *ret = NULL; + + /* If the string is NULL or empty, there are no tokens to be found. */ + if (stringp == NULL || *stringp == NULL || **stringp == '\0') + return NULL; + + /* + * If the delimiter is NULL or empty, + * the full string makes up the only token. + */ + if (delim == NULL || *delim == '\0') { + ret = *stringp; + *stringp = NULL; + return ret; + } + + char *found; + while (**stringp != '\0') { + found = strstr(*stringp, delim); + + if (!found) { + /* + * The delimiter was not found, so the full string + * makes up the only token, and we're done. + */ + ret = *stringp; + *stringp = NULL; + break; + } + + if (found != *stringp) { + /* There's a non-empty token before the delimiter. */ + *found = '\0'; + ret = *stringp; + *stringp = found + strlen(delim); + break; + } + + /* + * The delimiter was found at the start of the string, + * skip it and keep looking for a non-empty token. + */ + *stringp += strlen(delim); + } + + return ret; +} + +int kernel_lessthan_3_8() +{ + int major, minor; + struct utsname uts; + return (uname(&uts) != -1 && + sscanf(uts.release, "%d.%d", &major, &minor) == 2 && + ((major < 3) || ((major == 3) && (minor < 8)))); +} + +char *path_join(const char *external_path, const char *internal_path) +{ + char *path; + size_t pathlen; + + /* One extra char for '/' and one for '\0', hence + 2. */ + pathlen = strlen(external_path) + strlen(internal_path) + 2; + path = malloc(pathlen); + snprintf(path, pathlen, "%s/%s", external_path, internal_path); + + return path; +} + +int write_proc_file(pid_t pid, const char *content, const char *basename) +{ + int fd, ret; + size_t sz, len; + ssize_t written; + char filename[32]; + + sz = sizeof(filename); + ret = snprintf(filename, sz, "/proc/%d/%s", pid, basename); + if (ret < 0 || (size_t)ret >= sz) { + warn("failed to generate %s filename", basename); + return -1; + } + + fd = open(filename, O_WRONLY | O_CLOEXEC); + if (fd < 0) { + pwarn("failed to open '%s'", filename); + return -errno; + } + + len = strlen(content); + written = write(fd, content, len); + if (written < 0) { + pwarn("failed to write '%s'", filename); + return -1; + } + + if ((size_t)written < len) { + warn("failed to write %zu bytes to '%s'", len, filename); + return -1; + } + close(fd); + return 0; +} + +int write_pid_to_path(pid_t pid, const char *path) +{ + FILE *fp = fopen(path, "w"); + + if (!fp) { + pwarn("failed to open '%s'", path); + return -errno; + } + if (fprintf(fp, "%d\n", (int)pid) < 0) { + /* fprintf(3) does not set errno on failure. */ + warn("fprintf(%s) failed", path); + return -1; + } + if (fclose(fp)) { + pwarn("fclose(%s) failed", path); + return -errno; + } + + return 0; +} + +void *consumebytes(size_t length, char **buf, size_t *buflength) +{ + char *p = *buf; + if (length > *buflength) + return NULL; + *buf += length; + *buflength -= length; + return p; +} + +char *consumestr(char **buf, size_t *buflength) +{ + size_t len = strnlen(*buf, *buflength); + if (len == *buflength) + /* There's no null-terminator. */ + return NULL; + return consumebytes(len + 1, buf, buflength); +}
diff --git a/minijail/util.h b/minijail/util.h new file mode 100644 index 0000000..faf6315 --- /dev/null +++ b/minijail/util.h
@@ -0,0 +1,98 @@ +/* util.h + * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Logging and other utility functions. + */ + +#ifndef _UTIL_H_ +#define _UTIL_H_ + +#include <stdlib.h> +#include <sys/types.h> +#include <syslog.h> +#include <unistd.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define die(_msg, ...) do { \ + syslog(LOG_ERR, "libminijail[%d]: " _msg, getpid(), ## __VA_ARGS__); \ + abort(); \ +} while (0) + +#define pdie(_msg, ...) \ + die(_msg ": %m", ## __VA_ARGS__) + +#define warn(_msg, ...) \ + syslog(LOG_WARNING, "libminijail[%d]: " _msg, getpid(), ## __VA_ARGS__) + +#define pwarn(_msg, ...) \ + warn(_msg ": %m", ## __VA_ARGS__) + +#define info(_msg, ...) \ + syslog(LOG_INFO, "libminijail[%d]: " _msg, getpid(), ## __VA_ARGS__) + +extern const char *log_syscalls[]; +extern const size_t log_syscalls_len; + +static inline int is_android() { +#if defined(__ANDROID__) + return 1; +#else + return 0; +#endif +} + +static inline int running_with_asan() { +#ifndef __has_feature +#define __has_feature(x) 0 +#endif + +#if __has_feature(address_sanitizer) + return 1; +#else + return 0; +#endif +} + +int lookup_syscall(const char *name); +const char *lookup_syscall_name(int nr); + +long int parse_constant(char *constant_str, char **endptr); + +char *strip(char *s); +char *tokenize(char **stringp, const char *delim); + +int kernel_lessthan_3_8(); + +char *path_join(const char *external_path, const char *internal_path); +int write_proc_file(pid_t pid, const char *content, const char *basename); +int write_pid_to_path(pid_t pid, const char *path); + +/* + * consumebytes: consumes @length bytes from a buffer @buf of length @buflength + * @length Number of bytes to consume + * @buf Buffer to consume from + * @buflength Size of @buf + * + * Returns a pointer to the base of the bytes, or NULL for errors. + */ +void *consumebytes(size_t length, char **buf, size_t *buflength); + +/* + * consumestr: consumes a C string from a buffer @buf of length @length + * @buf Buffer to consume + * @length Length of buffer + * + * Returns a pointer to the base of the string, or NULL for errors. + */ +char *consumestr(char **buf, size_t *buflength); + +#ifdef __cplusplus +}; /* extern "C" */ +#endif + +#endif /* _UTIL_H_ */