diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:10:33 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:10:33 +0000 |
commit | abcc5e018c5932ff900b09aad2c53cecced44be6 (patch) | |
tree | aac272e0ee4230388fcb775ba5e352c8ded3aebd | |
parent | c613ab273be552a046aaa7d8dadb1958e59da980 (diff) | |
parent | d3e36182e87f07b0e0bfaa6d14c8950a1c43bdfd (diff) | |
download | minijail-android14-mainline-sdkext-release.tar.gz |
Snap for 10453563 from d3e36182e87f07b0e0bfaa6d14c8950a1c43bdfd to mainline-sdkext-releaseaml_sdk_341510000aml_sdk_341410000aml_sdk_341110080aml_sdk_341110000aml_sdk_341010000aml_sdk_340912010android14-mainline-sdkext-release
Change-Id: I0ef8762e4092f3c206d1d316a9f7772afb32f40c
78 files changed, 1913 insertions, 514 deletions
@@ -52,6 +52,7 @@ license { libminijailSrcFiles = [ "bpf.c", + "landlock_util.c", "libminijail.c", "signal_handler.c", "syscall_filter.c", @@ -74,10 +75,10 @@ cc_defaults { "-DALLOW_DEBUG_LOGGING", "-DALLOW_DUPLICATE_SYSCALLS", "-DDEFAULT_PIVOT_ROOT=\"/var/empty\"", + "-DBINDMOUNT_ALLOWED_PREFIXES=\"\"", "-Wall", "-Werror", ], - c_std: "gnu11", target: { darwin: { enabled: false, @@ -459,6 +460,9 @@ cc_test { }, }, data: ["test/*"], + test_options: { + tags: ["no-remote"], + } } @@ -550,55 +554,3 @@ cc_binary { static_libs: ["libminijail_generated"], shared_libs: minijailCommonLibraries + ["libminijail"], } - -rust_defaults { - name: "libminijail_rust_defaults", - target: { - darwin: { - enabled: false, - }, - }, -} - -// This target was generated by cargo2android.py --run --device, with some -// manual fixes. -rust_library { - name: "libminijail_sys", - defaults: ["libminijail_rust_defaults"], - host_supported: true, - crate_name: "minijail_sys", - srcs: ["rust/minijail-sys/lib.rs"], - edition: "2018", - rustlibs: [ - "liblibc", - ], - shared_libs: [ - "libcap", - "libminijail", - ], - apex_available: [ - "//apex_available:platform", - "com.android.compos", - "com.android.virt", - ], -} - -// This target was generated by cargo2android.py --run --device, with some -// manual fixes. -rust_library { - name: "libminijail_rust", - defaults: ["libminijail_rust_defaults"], - host_supported: true, - crate_name: "minijail", - srcs: ["rust/minijail/src/lib.rs"], - edition: "2018", - rustlibs: [ - "liblibc", - "libminijail_sys", - ], - apex_available: [ - "//apex_available:platform", - "com.android.compos", - "com.android.virt", - ], -} @@ -38,10 +38,10 @@ Building the tests will automatically execute them. ## Code Review We use [Android Review] for Minijail code review. The easiest way to submit -changes for review is using `repo upload` on a Chromium OS or Android checkout. +changes for review is using `repo upload` on a ChromiumOS or Android checkout. Go to [Android Review HTTP Credentials] to obtain credentials to push code. For more detailed instructions see the [Android source documentation] or the -[Chromium OS documentation]. +[ChromiumOS documentation]. ## Source Style @@ -81,6 +81,6 @@ Please keep it in sync with [minijail0_cli.c]. [Android Review]: https://android-review.googlesource.com/ [Android Review HTTP Credentials]: https://android-review.googlesource.com/settings/#HTTPCredentials [Android source documentation]: https://source.android.com/setup/start -[Chromium OS documentation]: https://chromium.googlesource.com/chromiumos/docs/+/HEAD/developer_guide.md +[ChromiumOS documentation]: https://chromium.googlesource.com/chromiumos/docs/+/HEAD/developer_guide.md [Google Markdown style guide]: https://github.com/google/styleguide/blob/gh-pages/docguide/style.md [Google Test]: https://github.com/google/googletest @@ -1,4 +1,4 @@ -/* Copyright 2012 The Chromium OS Authors. All rights reserved. +/* Copyright 2012 The ChromiumOS Authors * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are @@ -10,7 +10,7 @@ * 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 + * * Neither the name of Google LLC nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * @@ -1,4 +1,4 @@ -# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +# Copyright 2012 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. @@ -17,15 +17,35 @@ BUILD_STATIC_LIBS ?= no DEFAULT_PIVOT_ROOT ?= /var/empty CPPFLAGS += -DDEFAULT_PIVOT_ROOT='"$(DEFAULT_PIVOT_ROOT)"' +# These are configurable strictness settings. Not every use case for Minijail +# has the same requirements. + +# Allow seccomp to fail without a warning. You probably don't want this. ifeq ($(USE_seccomp),no) CPPFLAGS += -DUSE_SECCOMP_SOFTFAIL endif +# Prevent Minijail configuration files from residing in a noexec +# filesystem. +# +# The rationale here is that a configuration file that controls how a program +# executes should be subject to the same restrictions as the executable it +# controls. In essence, a configuration file should be considered to have as +# much power as an executable. Files can only be executed from filesystems *not* +# mounted as noexec, so configuration files should not reside in noexec +# filesystems. +# +# For example, on ChromeOS executable filesystems are mounted read-only. Noexec +# filesystems are allowed to be mounted read-write. If a configuration file +# were allowed to reside in a noexec filesystem, an attacker would be able to +# influence how a program is executed by modifying the configuration file. BLOCK_NOEXEC_CONF ?= no ifeq ($(BLOCK_NOEXEC_CONF),yes) CPPFLAGS += -DBLOCK_NOEXEC_CONF endif +# Prevent Minijail configuration files from residing in a partition different +# from the partition mounted at /. This is primarily used in ChromeOS. ENFORCE_ROOTFS_CONF ?= no ifeq ($(ENFORCE_ROOTFS_CONF),yes) CPPFLAGS += -DENFORCE_ROOTFS_CONF @@ -40,6 +60,26 @@ CPPFLAGS += -DSECCOMP_DEFAULT_RET_LOG endif endif +# Prevent Minijail from following symlinks when performing bind mounts. +# BINDMOUNT_ALLOWED_PREFIXES allows some flexibility. This is especially useful +# for directories that are not normally modifiable by non-root users. +# If a process can modify these directories, they probably don't need to mess +# with Minijail bind mounts to gain root privileges. +BINDMOUNT_ALLOWED_PREFIXES ?= /dev,/sys +CPPFLAGS += -DBINDMOUNT_ALLOWED_PREFIXES='"$(BINDMOUNT_ALLOWED_PREFIXES)"' +BLOCK_SYMLINKS_IN_BINDMOUNT_PATHS ?= no +ifeq ($(BLOCK_SYMLINKS_IN_BINDMOUNT_PATHS),yes) +CPPFLAGS += -DBLOCK_SYMLINKS_IN_BINDMOUNT_PATHS +endif + +# Prevents symlinks from being followed in the /tmp folder. +# Symlinks could be followed to modify arbitrary files when a process +# had access to the /tmp folder. +BLOCK_SYMLINKS_IN_NONINIT_MOUNTNS_TMP ?= no +ifeq ($(BLOCK_SYMLINKS_IN_NONINIT_MOUNTNS_TMP),yes) +CPPFLAGS += -DBLOCK_SYMLINKS_IN_NONINIT_MOUNTNS_TMP +endif + ifeq ($(USE_ASAN),yes) CPPFLAGS += -fsanitize=address -fno-omit-frame-pointer LDFLAGS += -fsanitize=address -fno-omit-frame-pointer @@ -80,7 +120,7 @@ endif UNITTEST_LIBS += $(GTEST_LIBS) CORE_OBJECT_FILES := libminijail.o syscall_filter.o signal_handler.o \ - bpf.o util.o system.o syscall_wrapper.o \ + bpf.o landlock_util.o util.o system.o syscall_wrapper.o \ config_parser.o libconstants.gen.o libsyscalls.gen.o UNITTEST_DEPS += $(CORE_OBJECT_FILES) @@ -161,7 +201,7 @@ clean: CLEAN(util_unittest) CXX_BINARY(parse_seccomp_policy): parse_seccomp_policy.o syscall_filter.o \ - bpf.o util.o libconstants.gen.o libsyscalls.gen.o + bpf.o landlock_util.o util.o libconstants.gen.o libsyscalls.gen.o clean: CLEAN(parse_seccomp_policy) @@ -1,4 +1,4 @@ -// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Copyright 2014 The ChromiumOS Authors // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are @@ -10,7 +10,7 @@ // 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 +// * Neither the name of Google LLC nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // @@ -1,5 +1,10 @@ set noparent -include OWNERS_GENERAL +# Android: exclude upstream owners so that code review requests will be routed +# to the Android/downstream owners below. +#include OWNERS_GENERAL +adelva@google.com +victorhsieh@google.com + # Emeritus. -drewry@google.com -keescook@google.com +#drewry@google.com +#keescook@google.com diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index 254a062..c7d9e54 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -3,3 +3,6 @@ clang_format = true [Builtin Hooks Options] clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c + +[Hook Scripts] +repo_upload_warning = ./tools/repo_upload_warning ${PREUPLOAD_COMMIT} @@ -4,7 +4,7 @@ The Minijail homepage is https://google.github.io/minijail/. The main source repo is -https://android.googlesource.com/platform/external/minijail/. +https://chromium.googlesource.com/chromiumos/platform/minijail. There might be other copies floating around, but this is the official one! @@ -12,7 +12,7 @@ There might be other copies floating around, but this is the official one! ## What is it? -Minijail is a sandboxing and containment tool used in Chrome OS and Android. +Minijail is a sandboxing and containment tool used in ChromeOS and Android. It provides an executable that can be used to launch and sandbox other programs, and a library that can be used by code to sandbox itself. @@ -21,12 +21,12 @@ and a library that can be used by code to sandbox itself. You're one `git clone` away from happiness. ``` -$ git clone https://android.googlesource.com/platform/external/minijail +$ git clone https://chromium.googlesource.com/chromiumos/platform/minijail $ cd minijail ``` Releases are tagged as `linux-vXX`: -https://android.googlesource.com/platform/external/minijail/+refs +https://chromium.googlesource.com/chromiumos/platform/minijail/+refs ## Building @@ -49,14 +49,14 @@ We've got a couple of contact points. * [minijail-dev@google.com]: Internal Google developer mailing list. * [crbug.com/list]: Existing bug reports & feature requests. * [crbug.com/new]: File new bug reports & feature requests. -* [AOSP Gerrit]: Code reviews. +* [Chromium Gerrit]: Code reviews. [minijail@chromium.org]: https://groups.google.com/a/chromium.org/forum/#!forum/minijail [minijail-users@google.com]: https://groups.google.com/a/google.com/forum/#!forum/minijail-users [minijail-dev@google.com]: https://groups.google.com/a/google.com/forum/#!forum/minijail-dev [crbug.com/list]: https://crbug.com/?q=component:OS>Systems>Minijail [crbug.com/new]: https://bugs.chromium.org/p/chromium/issues/entry?components=OS>Systems>Minijail -[AOSP Gerrit]: https://android-review.googlesource.com/q/project:platform/external/minijail +[Chromium Gerrit]: https://chromium-review.googlesource.com/q/project:chromiumos/platform/minijail ## Talks and presentations @@ -67,7 +67,7 @@ The following talk serves as a good introduction to Minijail and how it can be u ## Example usage -The Chromium OS project has a comprehensive +The ChromiumOS project has a comprehensive [sandboxing](https://chromium.googlesource.com/chromiumos/docs/+/master/sandboxing.md) document that is largely based on Minijail. @@ -100,7 +100,7 @@ Q. "Why is it called minijail0?" A. It is minijail0 because it was a rewrite of an earlier program named minijail, which was considerably less mini, and in particular had a dependency -on libchrome (the Chrome OS packaged version of Chromium's //base). We needed a +on libchrome (the ChromeOS packaged version of Chromium's //base). We needed a new name to not collide with the deprecated one. We didn't want to call it minijail2 or something that would make people @@ -114,51 +114,3 @@ controlled surprise system call use. https://crrev.com/c/4585/ added the original implementation. Source: Conversations with original authors, ellyjones@ and wad@. - -## How to manually upgrade Minijail on Chrome OS - -Minijail is manually upgraded on Chrome OS so that there is a way to test -changes in the Chrome OS commit queue. Committed changes have already passed -Android's presubmit checks, but the ebuild upgrade CL goes through the Chrome -OS commit queue and must pass the tests before any additional changes are -available for use on Chrome OS. To upgrade minijail on Chrome OS, complete the -following steps. - -```bash -# Sync Minijail repo -cd ~/chromiumos/src/aosp/external/minijail -git checkout m/main -repo sync . - -# Set up local branch. -cd ~/trunk/src/third_party/chromiumos-overlay/ -repo start minijail . # replace minijail with the local branch name you want. - -# Run upgrade script. -~/trunk/chromite/scripts/cros_uprev --force --overlay-type public \ - --packages chromeos-base/minijail:dev-rust/minijail-sys:dev-rust/minijail -``` - -At this point the Minijail-related packages should be upgraded, so you may want -to add the changes to a commit and do some local testing before uploading a -change list. Here are the recommended local tests to try (make sure you are -**not** working on the minijail packages first i.e. `cros_workon list-all`): - -```bash -# Check build. -./build_packages --board=${BOARD} - -# Check unit tests. -FEATURES=test emerge-${BOARD} chromeos-base/minijail dev-rust/minijail-sys \ - dev-rust/minijail - -# Check integration tests. -cros deploy <DUT> chromeos-base/minijail -tast run <DUT> security.Minijail.* security.MinijailSeccomp -``` - -Finally, when uploading the CL make sure to include the list of changes -since the last uprev. The command to generate the list is as follows: -```bash -git log --oneline --no-merges <previous hash in ebuild file>..HEAD -``` @@ -1,5 +1,5 @@ /* arch.h - * Copyright 2014 The Chromium OS Authors. All rights reserved. + * Copyright 2014 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * @@ -1,4 +1,4 @@ -/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +/* Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -362,7 +362,7 @@ int bpf_label_id(struct bpf_labels *labels, const char *label) } end = begin + labels->count; for (id = 0; begin < end; ++begin, ++id) { - if (!strcmp(label, begin->label)) { + if (streq(label, begin->label)) { return id; } } @@ -1,5 +1,5 @@ /* bpf.h - * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * @@ -1,4 +1,4 @@ -# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +# Copyright 2012 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # diff --git a/config_parser.c b/config_parser.c index 4972cf5..9b3aa22 100644 --- a/config_parser.c +++ b/config_parser.c @@ -1,4 +1,4 @@ -/* Copyright 2021 The Chromium OS Authors. All rights reserved. +/* Copyright 2021 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -87,7 +87,7 @@ bool parse_config_line(const char *config_line, struct config_entry *entry) static bool match_special_directive(const char *line) { - return (strcmp(line, "% minijail-config-file v0\n") == 0); + return streq(line, "% minijail-config-file v0\n"); } bool parse_config_file(FILE *config_file, struct config_entry_list *list) @@ -131,8 +131,9 @@ bool parse_config_file(FILE *config_file, struct config_entry_list *list) /* * getmultiline() behaves similarly with getline(3). It returns -1 * when read into EOF or the following errors. + * Caveat: EINVAL may happen when EOF is encountered in a valid stream. */ - if (errno == EINVAL || errno == ENOMEM) { + if ((errno == EINVAL && config_file == NULL) || errno == ENOMEM) { return false; } diff --git a/config_parser.h b/config_parser.h index 36c96db..b158e5c 100644 --- a/config_parser.h +++ b/config_parser.h @@ -1,4 +1,4 @@ -/* Copyright 2021 The Chromium OS Authors. All rights reserved. +/* Copyright 2021 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ diff --git a/config_parser_unittest.cc b/config_parser_unittest.cc index a9c6571..2e4ad17 100644 --- a/config_parser_unittest.cc +++ b/config_parser_unittest.cc @@ -1,4 +1,4 @@ -/* Copyright 2021 The Chromium OS Authors. All rights reserved. +/* Copyright 2021 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * diff --git a/dump_constants.cc b/dump_constants.cc index f3d7074..6a0a3ba 100644 --- a/dump_constants.cc +++ b/dump_constants.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 The Chromium OS Authors. All rights reserved. +/* Copyright 2018 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -1,4 +1,4 @@ -/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. +/* Copyright 2014 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -1,5 +1,5 @@ /* elfparse.h - * Copyright (c) 2014 The Chromium OS Authors. All rights reserved. + * Copyright 2014 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * diff --git a/gen_constants-inl.h b/gen_constants-inl.h index 8a09adb..d09349c 100644 --- a/gen_constants-inl.h +++ b/gen_constants-inl.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. +/* Copyright 2014 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -73,3 +73,7 @@ struct fscrypt_policy_v1 { #if !defined(FS_IOC_GET_ENCRYPTION_POLICY_EX) #define FS_IOC_GET_ENCRYPTION_POLICY_EX _IOWR('f', 22, __u8[9]) #endif + +#if !defined(MADV_FREE) +#define MADV_FREE 8 +#endif diff --git a/gen_constants.sh b/gen_constants.sh index 20f9f42..628a13d 100755 --- a/gen_constants.sh +++ b/gen_constants.sh @@ -1,6 +1,6 @@ #!/bin/sh -# Copyright 2015 The Chromium OS Authors. All rights reserved. +# Copyright 2015 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/gen_syscalls-inl.h b/gen_syscalls-inl.h index d7c43aa..e631424 100644 --- a/gen_syscalls-inl.h +++ b/gen_syscalls-inl.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2014 The Chromium OS Authors. All rights reserved. +/* Copyright 2014 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -77,3 +77,37 @@ #ifndef __NR_faccessat2 #define __NR_faccessat2 439 #endif + +#ifndef __NR_rseq +#ifdef __x86_64__ +#define __NR_rseq 334 +#elif __i386__ +#define __NR_rseq 386 +#elif __arm64__ +#define __NR_rseq 293 +#endif +#endif /* __NR_rseq */ + +#ifndef __NR_clone3 +#define __NR_clone3 435 +#endif + +#ifndef __NR_userfaultfd +#ifdef __x86_64__ +#define __NR_userfaultfd 323 +#elif __i386__ +#define __NR_userfaultfd 374 +#elif __arm64__ +#define __NR_userfaultfd 282 +#endif +#endif /* __NR_userfaultfd */ + +#ifndef __NR_membarrier +#ifdef __x86_64__ +#define __NR_membarrier 324 +#elif __i386__ +#define __NR_membarrier 375 +#elif __arm64__ +#define __NR_membarrier 283 +#endif +#endif /* __NR_membarrier */ diff --git a/gen_syscalls.sh b/gen_syscalls.sh index 7e1707c..d5155e8 100755 --- a/gen_syscalls.sh +++ b/gen_syscalls.sh @@ -1,6 +1,6 @@ #!/bin/sh -# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +# Copyright 2012 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/landlock.h b/landlock.h new file mode 100644 index 0000000..1ce5b80 --- /dev/null +++ b/landlock.h @@ -0,0 +1,132 @@ +/* Copyright 2022 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * Landlock system definitions. + * + * These definitions are based on <linux/landlock.h>. However, because we + * can't guarantee that header will be available on all systems that need to + * build Minijail, they are extracted here. + */ + +#ifndef _LANDLOCK_H +#define _LANDLOCK_H + +#include <linux/types.h> + +/** + * struct landlock_ruleset_attr - Ruleset definition + * + * Argument of sys_landlock_create_ruleset(). This structure can grow in + * future versions. + */ +struct minijail_landlock_ruleset_attr { + /** + * @handled_access_fs: Bitmask of actions (cf. `Filesystem flags`_) + * that is handled by this ruleset and should then be forbidden if no + * rule explicitly allow them. This is needed for backward + * compatibility reasons. + */ + __u64 handled_access_fs; +}; + +/* + * sys_landlock_create_ruleset() flags: + * + * - %LANDLOCK_CREATE_RULESET_VERSION: Get the highest supported Landlock ABI + * version. + */ +#ifndef LANDLOCK_CREATE_RULESET_VERSION +#define LANDLOCK_CREATE_RULESET_VERSION (1U << 0) +#endif + +/** + * enum landlock_rule_type - Landlock rule type + * + * Argument of sys_landlock_add_rule(). + */ +enum minijail_landlock_rule_type { + /** + * @LANDLOCK_RULE_PATH_BENEATH: Type of a &struct + * landlock_path_beneath_attr . + */ + LANDLOCK_RULE_PATH_BENEATH = 1, +}; + +/** + * struct landlock_path_beneath_attr - Path hierarchy definition + * + * Argument of sys_landlock_add_rule(). + */ +struct minijail_landlock_path_beneath_attr { + /** + * @allowed_access: Bitmask of allowed actions for this file hierarchy + * (cf. `Filesystem flags`_). + */ + __u64 allowed_access; + /** + * @parent_fd: File descriptor, open with ``O_PATH``, which identifies + * the parent directory of a file hierarchy, or just a file. + */ + __s32 parent_fd; + /* + * This struct is packed to avoid trailing reserved members. + * Cf. security/landlock/syscalls.c:build_check_abi() + */ +} __attribute__((__packed__)); + +#ifndef LANDLOCK_ACCESS_FS_EXECUTE +#define LANDLOCK_ACCESS_FS_EXECUTE (1ULL << 0) +#endif + +#ifndef LANDLOCK_ACCESS_FS_WRITE_FILE +#define LANDLOCK_ACCESS_FS_WRITE_FILE (1ULL << 1) +#endif + +#ifndef LANDLOCK_ACCESS_FS_READ_FILE +#define LANDLOCK_ACCESS_FS_READ_FILE (1ULL << 2) +#endif + +#ifndef LANDLOCK_ACCESS_FS_READ_DIR +#define LANDLOCK_ACCESS_FS_READ_DIR (1ULL << 3) +#endif + +#ifndef LANDLOCK_ACCESS_FS_REMOVE_DIR +#define LANDLOCK_ACCESS_FS_REMOVE_DIR (1ULL << 4) +#endif + +#ifndef LANDLOCK_ACCESS_FS_REMOVE_FILE +#define LANDLOCK_ACCESS_FS_REMOVE_FILE (1ULL << 5) +#endif + +#ifndef LANDLOCK_ACCESS_FS_MAKE_CHAR +#define LANDLOCK_ACCESS_FS_MAKE_CHAR (1ULL << 6) +#endif + +#ifndef LANDLOCK_ACCESS_FS_MAKE_DIR +#define LANDLOCK_ACCESS_FS_MAKE_DIR (1ULL << 7) +#endif + +#ifndef LANDLOCK_ACCESS_FS_MAKE_REG +#define LANDLOCK_ACCESS_FS_MAKE_REG (1ULL << 8) +#endif + +#ifndef LANDLOCK_ACCESS_FS_MAKE_SOCK +#define LANDLOCK_ACCESS_FS_MAKE_SOCK (1ULL << 9) +#endif + +#ifndef LANDLOCK_ACCESS_FS_MAKE_FIFO +#define LANDLOCK_ACCESS_FS_MAKE_FIFO (1ULL << 10) +#endif + +#ifndef LANDLOCK_ACCESS_FS_MAKE_BLOCK +#define LANDLOCK_ACCESS_FS_MAKE_BLOCK (1ULL << 11) +#endif + +#ifndef LANDLOCK_ACCESS_FS_MAKE_SYM +#define LANDLOCK_ACCESS_FS_MAKE_SYM (1ULL << 12) +#endif + +#endif /* _LANDLOCK_H */ diff --git a/landlock_util.c b/landlock_util.c new file mode 100644 index 0000000..2aa8336 --- /dev/null +++ b/landlock_util.c @@ -0,0 +1,65 @@ +/* Copyright 2022 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* Define _GNU_SOURCE because we need O_PATH to resolve correctly. */ +#define _GNU_SOURCE + +#include "landlock_util.h" + +#include <fcntl.h> +#include <sys/stat.h> + +#include "util.h" + + +int landlock_create_ruleset(const struct + minijail_landlock_ruleset_attr *const attr, + const size_t size, const __u32 flags) +{ + return syscall(__NR_landlock_create_ruleset, attr, size, flags); +} + +int landlock_add_rule(const int ruleset_fd, + const enum minijail_landlock_rule_type rule_type, + const void *const rule_attr, const __u32 flags) +{ + return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, + rule_attr, flags); +} + +int landlock_restrict_self(const int ruleset_fd, + const __u32 flags) +{ + return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); +} + +bool populate_ruleset_internal(const char *const path, + const int ruleset_fd, + const uint64_t allowed_access) +{ + struct minijail_landlock_path_beneath_attr path_beneath = { + .parent_fd = -1, + }; + struct stat statbuf; + attribute_cleanup_fd int parent_fd = open(path, O_PATH | O_CLOEXEC); + path_beneath.parent_fd = parent_fd; + if (path_beneath.parent_fd < 0) { + pwarn("Failed to open \"%s\"", path); + return false; + } + if (fstat(path_beneath.parent_fd, &statbuf)) { + return false; + } + path_beneath.allowed_access = allowed_access; + if (!S_ISDIR(statbuf.st_mode)) { + path_beneath.allowed_access &= ACCESS_FILE; + } + if (landlock_add_rule(ruleset_fd, LANDLOCK_RULE_PATH_BENEATH, + &path_beneath, 0)) { + pwarn("Failed to update ruleset \"%s\"", path); + return false; + } + return true; +} diff --git a/landlock_util.h b/landlock_util.h new file mode 100644 index 0000000..ab1b472 --- /dev/null +++ b/landlock_util.h @@ -0,0 +1,106 @@ +/* Copyright 2022 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* + * Landlock functions and constants. + */ + +#ifndef _LANDLOCK_UTIL_H_ +#define _LANDLOCK_UTIL_H_ + +#include <asm/unistd.h> +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include "landlock.h" + + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef __NR_landlock_create_ruleset +#define __NR_landlock_create_ruleset 444 +#endif + +#ifndef __NR_landlock_add_rule +#define __NR_landlock_add_rule 445 +#endif + +#ifndef __NR_landlock_restrict_self +#define __NR_landlock_restrict_self 446 +#endif + +#define ACCESS_FS_ROUGHLY_READ ( \ + LANDLOCK_ACCESS_FS_READ_FILE | \ + LANDLOCK_ACCESS_FS_READ_DIR) + +#define ACCESS_FS_ROUGHLY_READ_EXECUTE ( \ + LANDLOCK_ACCESS_FS_EXECUTE | \ + LANDLOCK_ACCESS_FS_READ_FILE | \ + LANDLOCK_ACCESS_FS_READ_DIR) + +#define ACCESS_FS_ROUGHLY_BASIC_WRITE ( \ + LANDLOCK_ACCESS_FS_WRITE_FILE | \ + LANDLOCK_ACCESS_FS_REMOVE_DIR | \ + LANDLOCK_ACCESS_FS_REMOVE_FILE | \ + LANDLOCK_ACCESS_FS_MAKE_DIR | \ + LANDLOCK_ACCESS_FS_MAKE_REG) + +#define ACCESS_FS_ROUGHLY_EDIT ( \ + LANDLOCK_ACCESS_FS_WRITE_FILE | \ + LANDLOCK_ACCESS_FS_REMOVE_DIR | \ + LANDLOCK_ACCESS_FS_REMOVE_FILE) + +#define ACCESS_FS_ROUGHLY_FULL_WRITE ( \ + LANDLOCK_ACCESS_FS_WRITE_FILE | \ + LANDLOCK_ACCESS_FS_REMOVE_DIR | \ + LANDLOCK_ACCESS_FS_REMOVE_FILE | \ + LANDLOCK_ACCESS_FS_MAKE_CHAR | \ + LANDLOCK_ACCESS_FS_MAKE_DIR | \ + LANDLOCK_ACCESS_FS_MAKE_REG | \ + LANDLOCK_ACCESS_FS_MAKE_SOCK | \ + LANDLOCK_ACCESS_FS_MAKE_FIFO | \ + LANDLOCK_ACCESS_FS_MAKE_BLOCK | \ + LANDLOCK_ACCESS_FS_MAKE_SYM) + +#define ACCESS_FILE ( \ + LANDLOCK_ACCESS_FS_EXECUTE | \ + LANDLOCK_ACCESS_FS_WRITE_FILE | \ + LANDLOCK_ACCESS_FS_READ_FILE) + +#define HANDLED_ACCESS_TYPES (ACCESS_FS_ROUGHLY_READ_EXECUTE | \ + ACCESS_FS_ROUGHLY_FULL_WRITE) + +/* + * Performs Landlock create ruleset syscall. + * + * Returns the ruleset file descriptor on success, returns an error code + * otherwise. + */ +extern int landlock_create_ruleset(const struct + minijail_landlock_ruleset_attr *const attr, + const size_t size, const __u32 flags); + +/* Performs Landlock add rule syscall. */ +extern int landlock_add_rule(const int ruleset_fd, + const enum minijail_landlock_rule_type rule_type, + const void *const rule_attr, const __u32 flags); + +/* Performs Landlock restrict self syscall. */ +extern int landlock_restrict_self(const int ruleset_fd, + const __u32 flags); + +/* Populates the landlock ruleset for a path and any needed paths beneath. */ +extern bool populate_ruleset_internal(const char *const path, + const int ruleset_fd, + const uint64_t allowed_access); + +#ifdef __cplusplus +}; /* extern "C" */ +#endif + +#endif /* _LANDLOCK_UTIL_H_ */ diff --git a/libconstants.h b/libconstants.h index c289955..0548de9 100644 --- a/libconstants.h +++ b/libconstants.h @@ -1,4 +1,4 @@ -/* Copyright 2015 The Chromium OS Authors. All rights reserved. +/* Copyright 2015 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ diff --git a/libminijail-private.h b/libminijail-private.h index 8feec55..6351f8e 100644 --- a/libminijail-private.h +++ b/libminijail-private.h @@ -1,5 +1,5 @@ /* libminijail-private.h - * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + * Copyright 2011 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * diff --git a/libminijail.c b/libminijail.c index aab1294..bb60904 100644 --- a/libminijail.c +++ b/libminijail.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +/* Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -36,6 +36,7 @@ #include <syscall.h> #include <unistd.h> +#include "landlock_util.h" #include "libminijail-private.h" #include "libminijail.h" @@ -72,6 +73,15 @@ (MS_NOSUID | MS_NODEV | MS_NOEXEC | MS_NOATIME | MS_NODIRATIME | \ MS_RELATIME | MS_RDONLY) +/* + * TODO(b/235960683): Drop this after CrOS upgrades to glibc >= 2.34 + * because MS_NOSYMFOLLOW will be defined in sys/mount.h. + */ +#ifndef MS_NOSYMFOLLOW +/* Added locally in kernels 4.x+. */ +#define MS_NOSYMFOLLOW 256 +#endif + struct minijail_rlimit { int type; rlim_t cur; @@ -101,6 +111,12 @@ struct hook { struct hook *next; }; +struct fs_rule { + char *path; + uint64_t landlock_flags; + struct fs_rule *next; +}; + struct preserved_fd { int parent_fd; int child_fd; @@ -112,46 +128,46 @@ struct minijail { * accounted for in minijail_pre{enter|exec}() below. */ struct { - int uid : 1; - int gid : 1; - int inherit_suppl_gids : 1; - int set_suppl_gids : 1; - int keep_suppl_gids : 1; - int use_caps : 1; - int capbset_drop : 1; - int set_ambient_caps : 1; - int vfs : 1; - int enter_vfs : 1; - int pids : 1; - int ipc : 1; - int uts : 1; - int net : 1; - int enter_net : 1; - int ns_cgroups : 1; - int userns : 1; - int disable_setgroups : 1; - int seccomp : 1; - int remount_proc_ro : 1; - int no_new_privs : 1; - int seccomp_filter : 1; - int seccomp_filter_tsync : 1; - int seccomp_filter_logging : 1; - int seccomp_filter_allow_speculation : 1; - int chroot : 1; - int pivot_root : 1; - int mount_dev : 1; - int mount_tmp : 1; - int do_init : 1; - int run_as_init : 1; - int pid_file : 1; - int cgroups : 1; - int alt_syscall : 1; - int reset_signal_mask : 1; - int reset_signal_handlers : 1; - int close_open_fds : 1; - int new_session_keyring : 1; - int forward_signals : 1; - int setsid : 1; + bool uid : 1; + bool gid : 1; + bool inherit_suppl_gids : 1; + bool set_suppl_gids : 1; + bool keep_suppl_gids : 1; + bool use_caps : 1; + bool capbset_drop : 1; + bool set_ambient_caps : 1; + bool vfs : 1; + bool enter_vfs : 1; + bool pids : 1; + bool ipc : 1; + bool uts : 1; + bool net : 1; + bool enter_net : 1; + bool ns_cgroups : 1; + bool userns : 1; + bool disable_setgroups : 1; + bool seccomp : 1; + bool remount_proc_ro : 1; + bool no_new_privs : 1; + bool seccomp_filter : 1; + bool seccomp_filter_tsync : 1; + bool seccomp_filter_logging : 1; + bool seccomp_filter_allow_speculation : 1; + bool chroot : 1; + bool pivot_root : 1; + bool mount_dev : 1; + bool mount_tmp : 1; + bool do_init : 1; + bool run_as_init : 1; + bool pid_file : 1; + bool cgroups : 1; + bool alt_syscall : 1; + bool reset_signal_mask : 1; + bool reset_signal_handlers : 1; + bool close_open_fds : 1; + bool new_session_keyring : 1; + bool forward_signals : 1; + bool setsid : 1; } flags; uid_t uid; gid_t gid; @@ -180,6 +196,9 @@ struct minijail { struct minijail_remount *remounts_head; struct minijail_remount *remounts_tail; size_t tmpfs_size; + bool using_minimalistic_mountns; + struct fs_rule *fs_rules_head; + struct fs_rule *fs_rules_tail; char *cgroups[MAX_CGROUPS]; size_t cgroup_count; struct minijail_rlimit rlimits[MAX_RLIMITS]; @@ -282,6 +301,40 @@ void minijail_preenter(struct minijail *j) free_remounts_list(j); } +/* Adds a rule for a given path to apply once minijail is entered. */ +int add_fs_restriction_path(struct minijail *j, + const char *path, + uint64_t landlock_flags) +{ + struct fs_rule *r = calloc(1, sizeof(*r)); + if (!r) + return -ENOMEM; + r->path = strdup(path); + r->landlock_flags = landlock_flags; + + if (j->fs_rules_tail) { + j->fs_rules_tail->next = r; + j->fs_rules_tail = r; + } else { + j->fs_rules_head = r; + j->fs_rules_tail = r; + } + + return 0; +} + +bool mount_has_bind_flag(struct mountpoint *m) { + return !!(m->flags & MS_BIND); +} + +bool mount_has_readonly_flag(struct mountpoint *m) { + return !!(m->flags & MS_RDONLY); +} + +bool mount_events_allowed(struct mountpoint *m) { + return !!(m->flags & MS_SHARED) || !!(m->flags & MS_SLAVE); +} + /* * Strip out flags meant for the child. * We keep things that are inherited across execve(2). @@ -324,6 +377,7 @@ struct minijail API *minijail_new(void) struct minijail *j = calloc(1, sizeof(struct minijail)); if (j) { j->remount_mode = MS_PRIVATE; + j->using_minimalistic_mountns = false; } return j; } @@ -474,6 +528,50 @@ void API minijail_log_seccomp_filter_failures(struct minijail *j) } } +void API minijail_set_using_minimalistic_mountns(struct minijail *j) +{ + j->using_minimalistic_mountns = true; +} + +void API minijail_add_minimalistic_mountns_fs_rules(struct minijail *j) +{ + struct mountpoint *m = j->mounts_head; + bool landlock_enabled_by_profile = false; + if (!j->using_minimalistic_mountns) + return; + + /* Apply Landlock rules. */ + while (m) { + landlock_enabled_by_profile = true; + minijail_add_fs_restriction_rx(j, m->dest); + /* Allow rw if mounted as writable, or mount flags allow mount events.*/ + if (!mount_has_readonly_flag(m) || mount_events_allowed(m)) + minijail_add_fs_restriction_rw(j, m->dest); + m = m->next; + } + if (landlock_enabled_by_profile) { + minijail_enable_default_fs_restrictions(j); + minijail_add_fs_restriction_edit(j, "/dev"); + minijail_add_fs_restriction_ro(j, "/proc"); + if (j->flags.vfs) + minijail_add_fs_restriction_rw(j, "/tmp"); + } +} + +void API minijail_enable_default_fs_restrictions(struct minijail *j) +{ + // Common library locations. + minijail_add_fs_restriction_rx(j, "/lib"); + minijail_add_fs_restriction_rx(j, "/lib64"); + minijail_add_fs_restriction_rx(j, "/usr/lib"); + minijail_add_fs_restriction_rx(j, "/usr/lib64"); + // Common locations for services invoking Minijail. + minijail_add_fs_restriction_rx(j, "/bin"); + minijail_add_fs_restriction_rx(j, "/sbin"); + minijail_add_fs_restriction_rx(j, "/usr/sbin"); + minijail_add_fs_restriction_rx(j, "/usr/bin"); +} + void API minijail_use_caps(struct minijail *j, uint64_t capmask) { /* @@ -719,7 +817,7 @@ char API *minijail_get_original_path(struct minijail *j, * "/chroot/path/exe", the source of that mount, * "/some/path/exe" is what should be returned. */ - if (!strcmp(b->dest, path_inside_chroot)) + if (streq(b->dest, path_inside_chroot)) return strdup(b->src); /* @@ -812,6 +910,74 @@ int API minijail_create_session(struct minijail *j) return 0; } +int API minijail_add_fs_restriction_rx(struct minijail *j, const char *path) +{ + return !add_fs_restriction_path(j, path, + ACCESS_FS_ROUGHLY_READ_EXECUTE); +} + +int API minijail_add_fs_restriction_ro(struct minijail *j, const char *path) +{ + return !add_fs_restriction_path(j, path, ACCESS_FS_ROUGHLY_READ); +} + +int API minijail_add_fs_restriction_rw(struct minijail *j, const char *path) +{ + return !add_fs_restriction_path(j, path, + ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_BASIC_WRITE); +} + +int API minijail_add_fs_restriction_advanced_rw(struct minijail *j, + const char *path) +{ + return !add_fs_restriction_path(j, path, + ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_FULL_WRITE); +} + +int API minijail_add_fs_restriction_edit(struct minijail *j, + const char *path) +{ + return !add_fs_restriction_path(j, path, + ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_EDIT); +} + +static bool is_valid_bind_path(const char *path) +{ + if (!block_symlinks_in_bindmount_paths()) { + return true; + } + + /* + * tokenize() will modify both the |prefixes| pointer and the contents + * of the string, so: + * -Copy |BINDMOUNT_ALLOWED_PREFIXES| since it lives in .rodata. + * -Save the original pointer for free()ing. + */ + char *prefixes = strdup(BINDMOUNT_ALLOWED_PREFIXES); + attribute_cleanup_str char *orig_prefixes = prefixes; + (void)orig_prefixes; + + char *prefix = NULL; + bool found_prefix = false; + if (!is_canonical_path(path)) { + while ((prefix = tokenize(&prefixes, ",")) != NULL) { + if (path_is_parent(prefix, path)) { + found_prefix = true; + break; + } + } + if (!found_prefix) { + /* + * If the path does not include one of the allowed + * prefixes, fail. + */ + warn("path '%s' is not a canonical path", path); + return false; + } + } + return true; +} + int API minijail_mount_with_data(struct minijail *j, const char *src, const char *dest, const char *type, unsigned long flags, const char *data) @@ -840,7 +1006,7 @@ int API minijail_mount_with_data(struct minijail *j, const char *src, * people use these in practice, it's probably OK. If they want * the kernel defaults, they can pass data="" instead of NULL. */ - if (!strcmp(type, "tmpfs")) { + if (streq(type, "tmpfs")) { /* tmpfs defaults to mode=1777 and size=50%. */ data = "mode=0755,size=10M"; } @@ -895,10 +1061,30 @@ int API minijail_bind(struct minijail *j, const char *src, const char *dest, { unsigned long flags = MS_BIND; + /* + * Check for symlinks in bind-mount source paths to warn the user early. + * Minijail will perform one final check immediately before the mount() + * call. + */ + if (!is_valid_bind_path(src)) { + warn("src '%s' is not a valid bind mount path", src); + return -ELOOP; + } + + /* + * Symlinks in |dest| are blocked by the ChromiumOS LSM: + * <kernel>/security/chromiumos/lsm.c#77 + */ + if (!writeable) flags |= MS_RDONLY; - return minijail_mount(j, src, dest, "", flags); + /* + * |type| is ignored for bind mounts, use it to signal that this mount + * came from minijail_bind(). + * TODO(b/238362528): Implement a better way to signal this. + */ + return minijail_mount(j, src, dest, "minijail_bind", flags); } int API minijail_add_remount(struct minijail *j, const char *mount_name, @@ -1337,6 +1523,8 @@ int minijail_unmarshal(struct minijail *j, char *serialized, size_t length) j->filter_prog = NULL; j->hooks_head = NULL; j->hooks_tail = NULL; + j->fs_rules_head = NULL; + j->fs_rules_tail = NULL; if (j->user) { /* stale pointer */ char *user = consumestr(&serialized, &length); @@ -1693,7 +1881,9 @@ static int mount_one(const struct minijail *j, struct mountpoint *m, { int ret; char *dest; - int remount = 0; + bool do_remount = false; + bool has_bind_flag = mount_has_bind_flag(m); + bool has_remount_flag = !!(m->flags & MS_REMOUNT); unsigned long original_mnt_flags = 0; /* We assume |dest| has a leading "/". */ @@ -1708,39 +1898,73 @@ static int mount_one(const struct minijail *j, struct mountpoint *m, return -ENOMEM; } - ret = - setup_mount_destination(m->src, dest, j->uid, j->gid, - (m->flags & MS_BIND), &original_mnt_flags); + ret = setup_mount_destination(m->src, dest, j->uid, j->gid, + has_bind_flag); if (ret) { warn("cannot create mount target '%s'", dest); goto error; } /* - * Bind mounts that change the 'ro' flag have to be remounted since - * 'bind' and other flags can't both be specified in the same command. - * Remount after the initial mount. + * Remount bind mounts that: + * - Come from the minijail_bind() API, and + * - Add the 'ro' flag + * since 'bind' and other flags can't both be specified in the same + * mount(2) call. + * Callers using minijail_mount() to perform bind mounts are expected to + * know what they're doing and call minijail_mount() with MS_REMOUNT as + * needed. + * Therefore, if the caller is asking for a remount (using MS_REMOUNT), + * there is no need to do an extra remount here. */ - if ((m->flags & MS_BIND) && - ((m->flags & MS_RDONLY) != (original_mnt_flags & MS_RDONLY))) { - remount = 1; + if (has_bind_flag && strcmp(m->type, "minijail_bind") == 0 && + !has_remount_flag) { /* - * Restrict the mount flags to those that are user-settable in a - * MS_REMOUNT request, but excluding MS_RDONLY. The - * user-requested mount flags will dictate whether the remount - * will have that flag or not. + * Grab the mount flags of the source. These are used to figure + * out whether the bind mount needs to be remounted read-only. */ - original_mnt_flags &= (MS_USER_SETTABLE_MASK & ~MS_RDONLY); + if (get_mount_flags(m->src, &original_mnt_flags)) { + warn("cannot get mount flags for '%s'", m->src); + goto error; + } + + if ((m->flags & MS_RDONLY) != + (original_mnt_flags & MS_RDONLY)) { + do_remount = 1; + /* + * Restrict the mount flags to those that are + * user-settable in a MS_REMOUNT request, but excluding + * MS_RDONLY. The user-requested mount flags will + * dictate whether the remount will have that flag or + * not. + */ + original_mnt_flags &= + (MS_USER_SETTABLE_MASK & ~MS_RDONLY); + } + } + + /* + * Do a final check for symlinks in |m->src|. + * |m->src| will only contain a valid path when purely bind-mounting + * (but not when remounting a bind mount). + * + * Short of having a version of mount(2) that can take fd's, this is the + * smallest we can make the TOCTOU window. + */ + if (has_bind_flag && !has_remount_flag && !is_valid_bind_path(m->src)) { + warn("src '%s' is not a valid bind mount path", m->src); + goto error; } ret = mount(m->src, dest, m->type, m->flags, m->data); if (ret) { - pwarn("cannot bind-mount '%s' as '%s' with flags %#lx", m->src, - dest, m->flags); + pwarn("cannot mount '%s' as '%s' with flags %#lx", m->src, dest, + m->flags); goto error; } - if (remount) { + /* Remount *after* the initial mount. */ + if (do_remount) { ret = mount(m->src, dest, NULL, m->flags | original_mnt_flags | MS_REMOUNT, m->data); @@ -1774,6 +1998,8 @@ static void process_mounts_or_die(const struct minijail *j) pdie("mount_dev failed"); if (j->mounts_head && mount_one(j, j->mounts_head, dev_path)) { + warn("mount_one failed with /dev at '%s'", dev_path); + if (dev_path) mount_dev_cleanup(dev_path); @@ -1876,8 +2102,14 @@ static int mount_tmp(const struct minijail *j) pdie("tmpfs size spec error"); else if ((size_t)ret >= sizeof(data)) pdie("tmpfs size spec too large"); - return mount("none", "/tmp", "tmpfs", MS_NODEV | MS_NOEXEC | MS_NOSUID, - data); + + unsigned long flags = MS_NODEV | MS_NOEXEC | MS_NOSUID; + + if (block_symlinks_in_noninit_mountns_tmp()) { + flags |= MS_NOSYMFOLLOW; + } + + return mount("none", "/tmp", "tmpfs", flags, data); } static int remount_proc_readonly(const struct minijail *j) @@ -2164,6 +2396,45 @@ static void drop_caps(const struct minijail *j, unsigned int last_valid_cap) cap_free(caps); } +/* Creates a ruleset for current inodes then calls landlock_restrict_self(). */ +static void apply_landlock_restrictions(const struct minijail *j) +{ + struct fs_rule *r; + attribute_cleanup_fd int ruleset_fd = -1; + + r = j->fs_rules_head; + while (r) { + if (ruleset_fd < 0) { + struct minijail_landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = HANDLED_ACCESS_TYPES + }; + ruleset_fd = landlock_create_ruleset( + &ruleset_attr, sizeof(ruleset_attr), 0); + if (ruleset_fd < 0) { + const int err = errno; + pwarn("Failed to create a ruleset"); + switch (err) { + case ENOSYS: + pwarn("Landlock is not supported by the current kernel"); + break; + case EOPNOTSUPP: + pwarn("Landlock is currently disabled by kernel config"); + break; + } + return; + } + } + populate_ruleset_internal(r->path, ruleset_fd, r->landlock_flags); + r = r->next; + } + + if (ruleset_fd >= 0) { + if (landlock_restrict_self(ruleset_fd, 0)) { + pdie("Failed to enforce ruleset"); + } + } +} + static void set_seccomp_filter(const struct minijail *j) { /* @@ -2457,8 +2728,14 @@ void API minijail_enter(const struct minijail *j) */ drop_ugid(j); drop_caps(j, last_valid_cap); + + // Landlock is applied as late as possible. If no_new_privs is + // set, then it can be applied after dropping caps. + apply_landlock_restrictions(j); set_seccomp_filter(j); } else { + apply_landlock_restrictions(j); + /* * If we're not setting no_new_privs, * we need to set seccomp filter *before* dropping privileges. @@ -3659,6 +3936,12 @@ void API minijail_destroy(struct minijail *j) free(c); } j->hooks_tail = NULL; + while (j->fs_rules_head) { + struct fs_rule *r = j->fs_rules_head; + j->fs_rules_head = r->next; + free(r); + } + j->fs_rules_tail = NULL; if (j->user) free(j->user); if (j->suppl_gid_list) diff --git a/libminijail.h b/libminijail.h index d2dce7a..1125169 100644 --- a/libminijail.h +++ b/libminijail.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +/* Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -106,6 +106,10 @@ 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_set_seccomp_filter_tsync(struct minijail *j); +/* Sets using_minimalistic_mountns to true. */ +void minijail_set_using_minimalistic_mountns(struct minijail *j); +void minijail_add_minimalistic_mountns_fs_rules(struct minijail *j); +void minijail_enable_default_fs_restrictions(struct minijail *j); /* * Allow speculative execution features that may cause data leaks across * processes, by setting the SECCOMP_FILTER_FLAG_SPEC_ALLOW seccomp flag. @@ -188,6 +192,26 @@ int minijail_rlimit(struct minijail *j, int type, rlim_t cur, rlim_t max); int minijail_add_to_cgroup(struct minijail *j, const char *path); /* + * These functions are used for filesystem restrictions. + */ + +/* Adds a read-execute path. */ +int minijail_add_fs_restriction_rx(struct minijail *j, const char *path); + +/* Adds a read-only path. */ +int minijail_add_fs_restriction_ro(struct minijail *j, const char *path); + +/* Adds a path with read and basic write permissions. */ +int minijail_add_fs_restriction_rw(struct minijail *j, const char *path); + +/* Adds a path with read and advanced write permissions. */ +int minijail_add_fs_restriction_advanced_rw(struct minijail *j, + const char *path); + +/* Adds a path with read and write permissions that exclude create. */ +int minijail_add_fs_restriction_edit(struct minijail *j, const char *path); + +/* * Install signal handlers in the minijail process that forward received * signals to the jailed child process. */ @@ -503,7 +527,8 @@ int minijail_wait(struct minijail *j); /* * Frees the given minijail. It does not matter if the process is inside the - * minijail or not. + * minijail or not. It will not kill the process, see minijail_kill() if that is + * desired. */ void minijail_destroy(struct minijail *j); diff --git a/libminijail_unittest.cc b/libminijail_unittest.cc index 868b7d7..7ffbde5 100644 --- a/libminijail_unittest.cc +++ b/libminijail_unittest.cc @@ -1,4 +1,4 @@ -/* Copyright 2016 The Chromium OS Authors. All rights reserved. +/* Copyright 2016 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * @@ -23,9 +23,11 @@ #include <set> #include <string> +#include "landlock_util.h" #include "libminijail-private.h" #include "libminijail.h" #include "scoped_minijail.h" +#include "unittest_util.h" #include "util.h" namespace { @@ -101,6 +103,21 @@ std::map<std::string, std::string> GetNamespaces( return namespaces; } +void set_preload_path(minijail *j) { +#if defined(__ANDROID__) + // libminijailpreload.so isn't available in android, so skip trying to load + // it. Even without the preload, all the test cases either pass or are skipped + // for other reasons. + return; +#endif + // We need to get the absolute path because entering a new mntns will + // implicitly chdir(/) for us. + char *preload_path = realpath(kPreloadPath, nullptr); + ASSERT_NE(preload_path, nullptr); + minijail_set_preload_path(j, preload_path); + free(preload_path); +} + } // namespace /* Silence unused variable warnings. */ @@ -569,7 +586,7 @@ TEST(Test, minijail_run_env_pid_pipes) { GTEST_SKIP(); ScopedMinijail j(minijail_new()); - minijail_set_preload_path(j.get(), kPreloadPath); + set_preload_path(j.get()); char *argv[4]; argv[0] = const_cast<char*>(kCatPath); @@ -632,7 +649,7 @@ TEST(Test, minijail_run_fd_env_pid_pipes) { GTEST_SKIP(); ScopedMinijail j(minijail_new()); - minijail_set_preload_path(j.get(), kPreloadPath); + set_preload_path(j.get()); char *argv[4]; argv[0] = const_cast<char*>(kShellPath); @@ -722,7 +739,7 @@ TEST(Test, minijail_run_env_pid_pipes_with_local_preload) { ASSERT_EQ(setenv("TEST_PARENT", "test", 1 /*overwrite*/), 0); // Use the preload library from this test build. - ASSERT_EQ(0, minijail_set_preload_path(j.get(), "./libminijailpreload.so")); + set_preload_path(j.get()); int child_stderr; mj_run_ret = @@ -938,7 +955,7 @@ TEST(Test, test_minijail_preserve_fd) { status = read(read_pipe[0], buf, 8); EXPECT_EQ(status, (int)teststr_len); buf[teststr_len] = 0; - EXPECT_EQ(strcmp(buf, teststr), 0); + EXPECT_STREQ(buf, teststr); status = minijail_wait(j); EXPECT_EQ(status, 0); @@ -1006,11 +1023,68 @@ TEST(Test, test_minijail_reset_signal_handlers) { minijail_destroy(j); } +// Test that bind mounting onto a non-existing location works. +TEST(Test, test_bind_mount_nonexistent_dest) { + TemporaryDir dir; + ASSERT_TRUE(dir.is_valid()); + + // minijail_bind() expects absolute paths, but TemporaryDir::path can return + // relative paths on Linux. + std::string path = dir.path; + if (!is_android()) { + std::string cwd(getcwd(NULL, 0)); + path = cwd + "/" + path; + } + + std::string path_src = path + "/src"; + std::string path_dest = path + "/dest"; + + EXPECT_EQ(mkdir(path_src.c_str(), 0700), 0); + + ScopedMinijail j(minijail_new()); + int bind_res = minijail_bind(j.get(), path_src.c_str(), path_dest.c_str(), + 0 /*writable*/); + EXPECT_EQ(bind_res, 0); +} + +// Test that bind mounting with a symlink behaves according to build-time +// configuration. +TEST(Test, test_bind_mount_symlink) { + TemporaryDir dir; + ASSERT_TRUE(dir.is_valid()); + + // minijail_bind() expects absolute paths, but TemporaryDir::path can return + // relative paths on Linux. + std::string path = dir.path; + if (!is_android()) { + std::string cwd(getcwd(NULL, 0)); + path = cwd + "/" + path; + } + + std::string path_src = path + "/src"; + std::string path_dest = path + "/dest"; + std::string path_sym = path + "/symlink"; + + EXPECT_EQ(mkdir(path_src.c_str(), 0700), 0); + EXPECT_EQ(mkdir(path_dest.c_str(), 0700), 0); + EXPECT_EQ(symlink(path_src.c_str(), path_sym.c_str()), 0); + + ScopedMinijail j(minijail_new()); + int bind_res = minijail_bind(j.get(), path_sym.c_str(), path_dest.c_str(), + 0 /*writable*/); + if (block_symlinks_in_bindmount_paths()) { + EXPECT_NE(bind_res, 0); + } else { + EXPECT_EQ(bind_res, 0); + } + EXPECT_EQ(unlink(path_sym.c_str()), 0); +} + namespace { // Tests that require userns access. // Android unit tests don't currently support entering user namespaces as -// unprivileged users due to having an older kernel. Chrome OS unit tests +// unprivileged users due to having an older kernel. ChromeOS unit tests // don't support it either due to being in a chroot environment (see man 2 // clone for more information about failure modes with the CLONE_NEWUSER flag). class NamespaceTest : public ::testing::Test { @@ -1119,7 +1193,7 @@ TEST_F(NamespaceTest, test_namespaces) { {minijail_run_pid_pipes, minijail_run_pid_pipes_no_preload}) { for (const auto& test_function : test_functions) { ScopedMinijail j(minijail_new()); - minijail_set_preload_path(j.get(), kPreloadPath); + set_preload_path(j.get()); // Enter all the namespaces we can. minijail_namespace_cgroups(j.get()); @@ -1155,7 +1229,7 @@ TEST_F(NamespaceTest, test_namespaces) { ssize_t read_ret = read(child_stdout, buf, 8); EXPECT_EQ(read_ret, static_cast<ssize_t>(teststr_len)); buf[teststr_len] = 0; - EXPECT_EQ(strcmp(buf, teststr), 0); + EXPECT_STREQ(buf, teststr); // Grab the set of namespaces in every container process. They must not // match the ones in the init namespace, and they must all match each @@ -1214,11 +1288,7 @@ TEST_F(NamespaceTest, test_enter_ns) { // Finally enter those namespaces. j = minijail_new(); - // We need to get the absolute path because entering a new mntns will - // implicitly chdir(/) for us. - char *path = realpath(kPreloadPath, nullptr); - ASSERT_NE(nullptr, path); - minijail_set_preload_path(j, path); + set_preload_path(j); minijail_namespace_net(j); minijail_namespace_vfs(j); @@ -1396,6 +1466,367 @@ TEST_F(NamespaceTest, test_remount_one_shared) { minijail_destroy(j); } +// Test that using minijail_mount() for bind mounts works. +TEST_F(NamespaceTest, test_remount_ro_using_mount) { + int status; + char uidmap[kBufferSize], gidmap[kBufferSize]; + constexpr uid_t kTargetUid = 1000; // Any non-zero value will do. + constexpr gid_t kTargetGid = 1000; + + if (!userns_supported_) + GTEST_SKIP(); + + struct minijail *j = minijail_new(); + + minijail_namespace_pids(j); + minijail_namespace_vfs(j); + minijail_mount_tmp(j); + minijail_run_as_init(j); + + // Perform userns mapping. + minijail_namespace_user(j); + snprintf(uidmap, sizeof(uidmap), "%d %d 1", kTargetUid, getuid()); + snprintf(gidmap, sizeof(gidmap), "%d %d 1", kTargetGid, getgid()); + minijail_change_uid(j, kTargetUid); + minijail_change_gid(j, kTargetGid); + minijail_uidmap(j, uidmap); + minijail_gidmap(j, gidmap); + minijail_namespace_user_disable_setgroups(j); + + // Perform a RO remount using minijail_mount(). + minijail_mount(j, "none", "/", "none", MS_REMOUNT | MS_BIND | MS_RDONLY); + + char *argv[] = {"/bin/true", nullptr}; + minijail_run_no_preload(j, argv[0], argv); + + status = minijail_wait(j); + EXPECT_EQ(status, 0); + + minijail_destroy(j); +} + +namespace { + +// Tests that require Landlock support. +// +// These subclass NamespaceTest because they also require userns access. +// TODO(akhna): ideally, Landlock unit tests should be able to run w/o +// namespace_pids or namespace_user. +class LandlockTest : public NamespaceTest { + protected: + static void SetUpTestCase() { + run_landlock_tests_ = LandlockSupported() && UsernsSupported(); + } + + // Whether Landlock tests should be run. + static bool run_landlock_tests_; + + static bool LandlockSupported() { + // Check the Landlock version w/o creating a ruleset file descriptor. + int landlock_version = landlock_create_ruleset( + NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); + if (landlock_version <= 0) { + const int err = errno; + warn("Skipping Landlock tests"); + switch (err) { + case ENOSYS: + warn("Landlock not supported by the current kernel."); + break; + case EOPNOTSUPP: + warn("Landlock is currently disabled."); + break; + } + return false; + } + return true; + } + + // Sets up a minijail to make Landlock syscalls and child processes. + void SetupLandlockTestingNamespaces(struct minijail *j) { + minijail_namespace_pids(j); + minijail_namespace_user(j); + } +}; + +bool LandlockTest::run_landlock_tests_; + +// Constants used in Landlock tests. +constexpr char kBinPath[] = "/bin"; +constexpr char kEtcPath[] = "/etc"; +constexpr char kLibPath[] = "/lib"; +constexpr char kLib64Path[] = "/lib64"; +constexpr char kTmpPath[] = "/tmp"; +constexpr char kLsPath[] = "/bin/ls"; +constexpr char kTestSymlinkScript[] = R"( + unlink /tmp/test-sym-link-1; + ln -s /bin /tmp/test-sym-link-1 + )"; + +} // namespace + +TEST_F(LandlockTest, test_rule_rx_allow) { + int mj_run_ret; + int status; + char *argv[3]; + if (!run_landlock_tests_) + GTEST_SKIP(); + ScopedMinijail j(minijail_new()); + SetupLandlockTestingNamespaces(j.get()); + minijail_add_fs_restriction_rx(j.get(), kBinPath); + minijail_add_fs_restriction_rx(j.get(), kEtcPath); + minijail_add_fs_restriction_rx(j.get(), kLibPath); + minijail_add_fs_restriction_rx(j.get(), kLib64Path); + + argv[0] = const_cast<char*>(kLsPath); + argv[1] = const_cast<char*>(kCatPath); + argv[2] = NULL; + + mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv); + EXPECT_EQ(mj_run_ret, 0); + status = minijail_wait(j.get()); + EXPECT_EQ(status, 0); +} + +TEST_F(LandlockTest, test_rule_rx_deny) { + int mj_run_ret; + int status; + char *argv[3]; + if (!run_landlock_tests_) + GTEST_SKIP(); + ScopedMinijail j(minijail_new()); + SetupLandlockTestingNamespaces(j.get()); + // Add irrelevant Landlock rule. + minijail_add_fs_restriction_rx(j.get(), "/var"); + + argv[0] = const_cast<char*>(kLsPath); + argv[1] = const_cast<char*>(kCatPath); + argv[2] = NULL; + + mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv); + EXPECT_EQ(mj_run_ret, 0); + status = minijail_wait(j.get()); + EXPECT_NE(status, 0); +} + +TEST_F(LandlockTest, test_rule_ro_allow) { + int mj_run_ret; + int status; + char *argv[3]; + if (!run_landlock_tests_) + GTEST_SKIP(); + ScopedMinijail j(minijail_new()); + SetupLandlockTestingNamespaces(j.get()); + minijail_add_fs_restriction_rx(j.get(), kBinPath); + minijail_add_fs_restriction_rx(j.get(), kEtcPath); + minijail_add_fs_restriction_rx(j.get(), kLibPath); + minijail_add_fs_restriction_rx(j.get(), kLib64Path); + // Add RO rule. + minijail_add_fs_restriction_ro(j.get(), "/var"); + + argv[0] = const_cast<char*>(kLsPath); + argv[1] = "/var"; + argv[2] = NULL; + + mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv); + EXPECT_EQ(mj_run_ret, 0); + status = minijail_wait(j.get()); + EXPECT_EQ(status, 0); +} + +TEST_F(LandlockTest, test_rule_ro_deny) { + int mj_run_ret; + int status; + char *argv[3]; + if (!run_landlock_tests_) + GTEST_SKIP(); + ScopedMinijail j(minijail_new()); + SetupLandlockTestingNamespaces(j.get()); + minijail_add_fs_restriction_rx(j.get(), kBinPath); + minijail_add_fs_restriction_rx(j.get(), kEtcPath); + minijail_add_fs_restriction_rx(j.get(), kLibPath); + minijail_add_fs_restriction_rx(j.get(), kLib64Path); + // No RO rule for /var, because we want the cmd to fail. + + argv[0] = const_cast<char*>(kLsPath); + argv[1] = "/var"; + argv[2] = NULL; + + mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv); + EXPECT_EQ(mj_run_ret, 0); + status = minijail_wait(j.get()); + EXPECT_NE(status, 0); +} + +TEST_F(LandlockTest, test_rule_rw_allow) { + int mj_run_ret; + int status; + char *argv[4]; + if (!run_landlock_tests_) + GTEST_SKIP(); + ScopedMinijail j(minijail_new()); + SetupLandlockTestingNamespaces(j.get()); + minijail_add_fs_restriction_rx(j.get(), kBinPath); + minijail_add_fs_restriction_rx(j.get(), kEtcPath); + minijail_add_fs_restriction_rx(j.get(), kLibPath); + minijail_add_fs_restriction_rx(j.get(), kLib64Path); + // Add RW Landlock rule. + minijail_add_fs_restriction_rw(j.get(), kTmpPath); + + argv[0] = const_cast<char*>(kShellPath); + argv[1] = "-c"; + argv[2] = "exec echo 'bar' > /tmp/baz"; + argv[3] = NULL; + + mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv); + EXPECT_EQ(mj_run_ret, 0); + status = minijail_wait(j.get()); + EXPECT_EQ(status, 0); +} + +TEST_F(LandlockTest, test_rule_rw_deny) { + int mj_run_ret; + int status; + char *argv[4]; + if (!run_landlock_tests_) + GTEST_SKIP(); + ScopedMinijail j(minijail_new()); + SetupLandlockTestingNamespaces(j.get()); + minijail_add_fs_restriction_rx(j.get(), kBinPath); + minijail_add_fs_restriction_rx(j.get(), kEtcPath); + minijail_add_fs_restriction_rx(j.get(), kLibPath); + minijail_add_fs_restriction_rx(j.get(), kLib64Path); + // No RW rule, because we want the cmd to fail. + + argv[0] = const_cast<char*>(kShellPath); + argv[1] = "-c"; + argv[2] = "exec echo 'bar' > /tmp/baz"; + argv[3] = NULL; + + mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv); + EXPECT_EQ(mj_run_ret, 0); + status = minijail_wait(j.get()); + EXPECT_NE(status, 0); +} + +TEST_F(LandlockTest, test_rule_allow_symlinks_advanced_rw) { + int mj_run_ret; + int status; + if (!run_landlock_tests_) + GTEST_SKIP(); + ScopedMinijail j(minijail_new()); + SetupLandlockTestingNamespaces(j.get()); + minijail_add_fs_restriction_rx(j.get(), kBinPath); + minijail_add_fs_restriction_rx(j.get(), kEtcPath); + minijail_add_fs_restriction_rx(j.get(), kLibPath); + minijail_add_fs_restriction_rx(j.get(), kLib64Path); + minijail_add_fs_restriction_advanced_rw(j.get(), kTmpPath); + + char* const argv[] = {"sh", "-c", const_cast<char*>(kTestSymlinkScript), + nullptr}; + + mj_run_ret = minijail_run_no_preload(j.get(), kShellPath, argv); + EXPECT_EQ(mj_run_ret, 0); + status = minijail_wait(j.get()); + EXPECT_EQ(status, 0); +} + +TEST_F(LandlockTest, test_rule_deny_symlinks_basic_rw) { + int mj_run_ret; + int status; + if (!run_landlock_tests_) + GTEST_SKIP(); + ScopedMinijail j(minijail_new()); + SetupLandlockTestingNamespaces(j.get()); + minijail_add_fs_restriction_rx(j.get(), kBinPath); + minijail_add_fs_restriction_rx(j.get(), kEtcPath); + minijail_add_fs_restriction_rx(j.get(), kLibPath); + minijail_add_fs_restriction_rx(j.get(), kLib64Path); + minijail_add_fs_restriction_rw(j.get(), kTmpPath); + + char* const argv[] = {"sh", "-c", const_cast<char*>(kTestSymlinkScript), + nullptr}; + + mj_run_ret = minijail_run_no_preload(j.get(), kShellPath, argv); + EXPECT_EQ(mj_run_ret, 0); + status = minijail_wait(j.get()); + EXPECT_NE(status, 0); +} + +TEST_F(LandlockTest, test_rule_rx_cannot_write) { + int mj_run_ret; + int status; + char *argv[4]; + if (!run_landlock_tests_) + GTEST_SKIP(); + ScopedMinijail j(minijail_new()); + SetupLandlockTestingNamespaces(j.get()); + minijail_add_fs_restriction_rx(j.get(), kBinPath); + minijail_add_fs_restriction_rx(j.get(), kEtcPath); + minijail_add_fs_restriction_rx(j.get(), kLibPath); + minijail_add_fs_restriction_rx(j.get(), kLib64Path); + minijail_add_fs_restriction_rx(j.get(), kTmpPath); + + argv[0] = const_cast<char*>(kShellPath); + argv[1] = "-c"; + argv[2] = "exec echo 'bar' > /tmp/baz"; + argv[3] = NULL; + + mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv); + EXPECT_EQ(mj_run_ret, 0); + status = minijail_wait(j.get()); + EXPECT_NE(status, 0); +} + +TEST_F(LandlockTest, test_rule_ro_cannot_wx) { + int mj_run_ret; + int status; + char *argv[4]; + if (!run_landlock_tests_) + GTEST_SKIP(); + ScopedMinijail j(minijail_new()); + SetupLandlockTestingNamespaces(j.get()); + minijail_add_fs_restriction_ro(j.get(), kBinPath); + minijail_add_fs_restriction_ro(j.get(), kEtcPath); + minijail_add_fs_restriction_ro(j.get(), kLibPath); + minijail_add_fs_restriction_ro(j.get(), kLib64Path); + minijail_add_fs_restriction_ro(j.get(), kTmpPath); + + argv[0] = const_cast<char*>(kShellPath); + argv[1] = "-c"; + argv[2] = "exec echo 'bar' > /tmp/baz"; + argv[3] = NULL; + + mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv); + EXPECT_EQ(mj_run_ret, 0); + status = minijail_wait(j.get()); + EXPECT_NE(status, 0); +} + +TEST_F(LandlockTest, test_rule_rw_cannot_exec) { + int mj_run_ret; + int status; + char *argv[4]; + if (!run_landlock_tests_) + GTEST_SKIP(); + ScopedMinijail j(minijail_new()); + SetupLandlockTestingNamespaces(j.get()); + minijail_add_fs_restriction_rw(j.get(), kBinPath); + minijail_add_fs_restriction_rw(j.get(), kEtcPath); + minijail_add_fs_restriction_rw(j.get(), kLibPath); + minijail_add_fs_restriction_rw(j.get(), kLib64Path); + minijail_add_fs_restriction_rw(j.get(), kTmpPath); + + argv[0] = const_cast<char*>(kShellPath); + argv[1] = "-c"; + argv[2] = "exec echo 'bar' > /tmp/baz"; + argv[3] = NULL; + + mj_run_ret = minijail_run_no_preload(j.get(), argv[0], argv); + EXPECT_EQ(mj_run_ret, 0); + status = minijail_wait(j.get()); + EXPECT_NE(status, 0); +} + void TestCreateSession(bool create_session) { int status; int pipe_fds[2]; diff --git a/libminijailpreload.c b/libminijailpreload.c index b5a3c75..17c8f97 100644 --- a/libminijailpreload.c +++ b/libminijailpreload.c @@ -1,5 +1,5 @@ /* libminijailpreload.c - preload hack library - * Copyright (c) 2011 The Chromium OS Authors. All rights reserved. + * Copyright 2011 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * diff --git a/libsyscalls.h b/libsyscalls.h index 29583ce..50a92a9 100644 --- a/libsyscalls.h +++ b/libsyscalls.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2011 The Chromium OS Authors. All rights reserved. +/* Copyright 2011 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ diff --git a/minijail0.1 b/minijail0.1 index a53ec6f..c323f2d 100644 --- a/minijail0.1 +++ b/minijail0.1 @@ -1,4 +1,4 @@ -.TH MINIJAIL0 "1" "March 2016" "Chromium OS" "User Commands" +.TH MINIJAIL0 "1" "March 2016" "ChromiumOS" "User Commands" .SH NAME minijail0 \- sandbox a process .SH SYNOPSIS @@ -390,9 +390,9 @@ allows the operations to jail \fIprogram\fR. .RE .SH AUTHOR -The Chromium OS Authors <chromiumos-dev@chromium.org> +The ChromiumOS Authors <chromiumos-dev@chromium.org> .SH COPYRIGHT -Copyright \(co 2011 The Chromium OS Authors +Copyright \(co 2011 The ChromiumOS Authors License BSD-like. .SH "SEE ALSO" .BR libminijail.h , diff --git a/minijail0.5 b/minijail0.5 index 3e4f114..c0e18e8 100644 --- a/minijail0.5 +++ b/minijail0.5 @@ -1,4 +1,4 @@ -.TH MINIJAIL0 "5" "July 2011" "Chromium OS" "User Commands" +.TH MINIJAIL0 "5" "July 2011" "ChromiumOS" "User Commands" .SH NAME minijail0 \- sandbox a process .SH DESCRIPTION @@ -6,10 +6,11 @@ minijail0 \- sandbox a process Runs PROGRAM inside a sandbox. See \fBminijail0\fR(1) for details. .SH EXAMPLES -Safely switch from root to nobody while dropping all capabilities and -inheriting any groups from nobody: +Safely switch from user \fIroot\fR to \fInobody\fR, switch to primary group +\fInobody\fR, drop all capabilities, and inherit any supplementary groups from +user \fInobody\fR: - # minijail0 -c 0 -G -u nobody /usr/bin/whoami + # minijail0 -u nobody -g nobody -c 0 -G /usr/bin/whoami nobody Run in a PID and VFS namespace without superuser capabilities (but still @@ -183,9 +184,9 @@ will occupy a single line, without '=' and value. Otherwise, any string that is given after the '=' is interpreted as the argument. .SH AUTHOR -The Chromium OS Authors <chromiumos-dev@chromium.org> +The ChromiumOS Authors <chromiumos-dev@chromium.org> .SH COPYRIGHT -Copyright \(co 2011 The Chromium OS Authors +Copyright \(co 2011 The ChromiumOS Authors License BSD-like. .SH "SEE ALSO" .BR minijail0 (1) diff --git a/minijail0.c b/minijail0.c index 9b1fcf3..7ef74b5 100644 --- a/minijail0.c +++ b/minijail0.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +/* Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ diff --git a/minijail0.sh b/minijail0.sh index cd5303a..21d9174 100755 --- a/minijail0.sh +++ b/minijail0.sh @@ -1,5 +1,5 @@ #!/bin/sh -# Copyright 2020 The Chromium OS Authors. All rights reserved. +# Copyright 2020 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/minijail0_cli.c b/minijail0_cli.c index e366846..25aa930 100644 --- a/minijail0_cli.c +++ b/minijail0_cli.c @@ -1,4 +1,4 @@ -/* Copyright 2018 The Chromium OS Authors. All rights reserved. +/* Copyright 2018 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -191,9 +191,9 @@ static void add_binding(struct minijail *j, char *arg) if (dest == NULL || dest[0] == '\0') dest = src; int writable; - if (flags == NULL || flags[0] == '\0' || !strcmp(flags, "0")) + if (flags == NULL || flags[0] == '\0' || streq(flags, "0")) writable = 0; - else if (!strcmp(flags, "1")) + else if (streq(flags, "1")) writable = 1; else errx(1, "Bad value for <writable>: %s", flags); @@ -213,7 +213,7 @@ static void add_rlimit(struct minijail *j, char *arg) } rlim_t cur_rlim; rlim_t max_rlim; - if (!strcmp(cur, "unlimited")) { + if (streq(cur, "unlimited")) { cur_rlim = RLIM_INFINITY; } else { end = NULL; @@ -221,7 +221,7 @@ static void add_rlimit(struct minijail *j, char *arg) if (*end) errx(1, "Bad soft limit: '%s'", cur); } - if (!strcmp(max, "unlimited")) { + if (streq(max, "unlimited")) { max_rlim = RLIM_INFINITY; } else { end = NULL; @@ -386,14 +386,14 @@ static void use_profile(struct minijail *j, const char *profile, { /* Note: New profiles should be added in minijail0_cli_unittest.cc. */ - if (!strcmp(profile, "minimalistic-mountns") || - !strcmp(profile, "minimalistic-mountns-nodev")) { + if (streq(profile, "minimalistic-mountns") || + streq(profile, "minimalistic-mountns-nodev")) { minijail_namespace_vfs(j); if (minijail_bind(j, "/", "/", 0)) errx(1, "minijail_bind(/) failed"); if (minijail_bind(j, "/proc", "/proc", 0)) errx(1, "minijail_bind(/proc) failed"); - if (!strcmp(profile, "minimalistic-mountns")) { + if (streq(profile, "minimalistic-mountns")) { if (minijail_bind(j, "/dev/log", "/dev/log", 0)) errx(1, "minijail_bind(/dev/log) failed"); minijail_mount_dev(j); @@ -403,6 +403,7 @@ static void use_profile(struct minijail *j, const char *profile, *tmp_size = DEFAULT_TMP_SIZE; } minijail_remount_proc_readonly(j); + minijail_set_using_minimalistic_mountns(j); use_pivot_root(j, DEFAULT_PIVOT_ROOT, pivot_root, chroot); } else errx(1, "Unrecognized profile name '%s'", profile); @@ -411,13 +412,13 @@ static void use_profile(struct minijail *j, const char *profile, static void set_remount_mode(struct minijail *j, const char *mode) { unsigned long msmode; - if (!strcmp(mode, "shared")) + if (streq(mode, "shared")) msmode = MS_SHARED; - else if (!strcmp(mode, "private")) + else if (streq(mode, "private")) msmode = MS_PRIVATE; - else if (!strcmp(mode, "slave")) + else if (streq(mode, "slave")) msmode = MS_SLAVE; - else if (!strcmp(mode, "unbindable")) + else if (streq(mode, "unbindable")) msmode = MS_UNBINDABLE; else errx(1, "Unknown remount mode: '%s'", mode); @@ -467,6 +468,11 @@ enum { OPT_CONFIG, OPT_ENV_ADD, OPT_ENV_RESET, + OPT_FS_DEFAULT_PATHS, + OPT_FS_PATH_RX, + OPT_FS_PATH_RO, + OPT_FS_PATH_RW, + OPT_FS_PATH_ADVANCED_RW, OPT_LOGGING, OPT_PRELOAD_LIBRARY, OPT_PROFILE, @@ -501,6 +507,11 @@ static const struct option long_options[] = { {"mount", required_argument, 0, 'k'}, {"bind-mount", required_argument, 0, 'b'}, {"ns-mount", no_argument, 0, 'v'}, + {"fs-default-paths", no_argument, 0, OPT_FS_DEFAULT_PATHS}, + {"fs-path-rx", required_argument, 0, OPT_FS_PATH_RX}, + {"fs-path-ro", required_argument, 0, OPT_FS_PATH_RO}, + {"fs-path-rw", required_argument, 0, OPT_FS_PATH_RW}, + {"fs-path-advanced-rw", required_argument, 0, OPT_FS_PATH_ADVANCED_RW}, {0, 0, 0, 0}, }; @@ -615,6 +626,17 @@ static const char help_text[] = "Uncommon options:\n" " --allow-speculative-execution\n" " Allow speculative execution by disabling mitigations.\n" +" --fs-default-paths\n" +" Adds a set of allowed paths to allow running common system \n" +" executables.\n" +" --fs-path-rx\n" +" Adds an allowed read-execute path.\n" +" --fs-path-ro\n" +" Adds an allowed read-only path.\n" +" --fs-path-rw\n" +" Adds an allowed read-write path.\n" +" --fs-path-advanced-rw\n" +" Adds an allowed advanced read-write path.\n" " --preload-library=<file>\n" " Overrides the path to \"" PRELOADPATH "\".\n" " This is only really useful for local testing.\n" @@ -672,7 +694,7 @@ static int getopt_from_conf(const struct option *longopts, const struct option *curr_opt; for (curr_opt = &longopts[0]; curr_opt->name != NULL; curr_opt = &longopts[++i]) - if (strcmp(entry->key, curr_opt->name) == 0) + if (streq(entry->key, curr_opt->name)) break; if (curr_opt->name == NULL) { errx(1, @@ -953,9 +975,9 @@ int parse_args(struct minijail *j, int argc, char *const argv[], add_rlimit(j, optarg); break; case 'T': - if (!strcmp(optarg, "static")) + if (streq(optarg, "static")) *elftype = ELFSTATIC; - else if (!strcmp(optarg, "dynamic")) + else if (streq(optarg, "dynamic")) *elftype = ELFDYNAMIC; else { errx(1, "ELF type must be 'static' or " @@ -986,11 +1008,11 @@ int parse_args(struct minijail *j, int argc, char *const argv[], minijail_namespace_set_hostname(j, optarg); break; case OPT_LOGGING: - if (!strcmp(optarg, "auto")) + if (streq(optarg, "auto")) log_to_stderr = -1; - else if (!strcmp(optarg, "syslog")) + else if (streq(optarg, "syslog")) log_to_stderr = 0; - else if (!strcmp(optarg, "stderr")) + else if (streq(optarg, "stderr")) log_to_stderr = 1; else errx(1, @@ -1002,6 +1024,21 @@ int parse_args(struct minijail *j, int argc, char *const argv[], case OPT_PRELOAD_LIBRARY: *preload_path = optarg; break; + case OPT_FS_DEFAULT_PATHS: + minijail_enable_default_fs_restrictions(j); + break; + case OPT_FS_PATH_RX: + minijail_add_fs_restriction_rx(j, optarg); + break; + case OPT_FS_PATH_RO: + minijail_add_fs_restriction_ro(j, optarg); + break; + case OPT_FS_PATH_RW: + minijail_add_fs_restriction_rw(j, optarg); + break; + case OPT_FS_PATH_ADVANCED_RW: + minijail_add_fs_restriction_advanced_rw(j, optarg); + break; case OPT_SECCOMP_BPF_BINARY: if (seccomp != None && seccomp != BpfBinaryFilter) { errx(1, "Do not use -s, -S, or " diff --git a/minijail0_cli.h b/minijail0_cli.h index cd504b3..00a541c 100644 --- a/minijail0_cli.h +++ b/minijail0_cli.h @@ -1,4 +1,4 @@ -/* Copyright 2018 The Chromium OS Authors. All rights reserved. +/* Copyright 2018 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * diff --git a/minijail0_cli_unittest.cc b/minijail0_cli_unittest.cc index 7b20ecd..8674e07 100644 --- a/minijail0_cli_unittest.cc +++ b/minijail0_cli_unittest.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 The Chromium OS Authors. All rights reserved. +/* Copyright 2018 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * @@ -605,4 +605,21 @@ TEST_F(CliTest, conf_parsing) { ASSERT_TRUE(parse_args_(argv)); } +TEST_F(CliTest, conf_parsing_with_dac_override) { + std::vector<std::string> argv = {"-c 2", "--config", + source_path("test/valid.conf"), + "/bin/sh"}; + + ASSERT_TRUE(parse_args_(argv)); +} + +TEST_F(CliTest, conf_fs_path) { + std::vector<std::string> argv = {"-c 2", "--config", + source_path("test/landlock.conf"), + "/bin/sh"}; + + ASSERT_TRUE(parse_args_(argv)); +} + + #endif // !__ANDROID__ diff --git a/parse_seccomp_policy.cc b/parse_seccomp_policy.cc index a6daac5..e511156 100644 --- a/parse_seccomp_policy.cc +++ b/parse_seccomp_policy.cc @@ -1,4 +1,4 @@ -/* Copyright 2016 The Chromium OS Authors. All rights reserved. +/* Copyright 2016 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -73,7 +73,7 @@ int main(int argc, char** argv) { FILE* f = stdin; // If there is at least one additional unparsed argument, treat it as the // policy script. - if (argc > optind && strcmp(argv[optind], "-") != 0) + if (argc > optind && !streq(argv[optind], "-")) f = fopen(argv[optind], "re"); if (!f) pdie("fopen(%s) failed", argv[1]); diff --git a/platform2_preinstall.sh b/platform2_preinstall.sh index 7d19d99..418c9fc 100755 --- a/platform2_preinstall.sh +++ b/platform2_preinstall.sh @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2015 The Chromium OS Authors. All rights reserved. +# Copyright 2015 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/rust/OWNERS b/rust/OWNERS index f8111bb..ae85cbb 100644 --- a/rust/OWNERS +++ b/rust/OWNERS @@ -1,4 +1,4 @@ -# Copyright 2019 The Chromium OS Authors. All rights reserved. +# Copyright 2019 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/rust/minijail-sys/Android.bp b/rust/minijail-sys/Android.bp new file mode 100644 index 0000000..86c092e --- /dev/null +++ b/rust/minijail-sys/Android.bp @@ -0,0 +1,35 @@ +// This file is generated by cargo2android.py --config cargo2android.json. +// Do not modify this file as changes will be overridden on upgrade. + + + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "external_minijail_license" + // to get the below license kinds: + // SPDX-license-identifier-BSD + default_applicable_licenses: ["external_minijail_license"], +} + +rust_library { + name: "libminijail_sys", + host_supported: true, + crate_name: "minijail_sys", + cargo_env_compat: true, + cargo_pkg_version: "0.0.13", + srcs: ["lib.rs"], + edition: "2018", + rustlibs: [ + "liblibc", + ], + shared_libs: [ + "libcap", + "libminijail", + ], + apex_available: [ + "//apex_available:platform", + "com.android.compos", + "com.android.virt", + ], +} diff --git a/rust/minijail-sys/Cargo.toml b/rust/minijail-sys/Cargo.toml index 4c49c95..6745d14 100644 --- a/rust/minijail-sys/Cargo.toml +++ b/rust/minijail-sys/Cargo.toml @@ -2,7 +2,7 @@ name = "minijail-sys" version = "0.0.13" description = "Provides raw (unsafe) bindings to the libminijail C library." -authors = ["The Chromium OS Authors"] +authors = ["The ChromiumOS Authors"] edition = "2018" build = "build.rs" diff --git a/rust/minijail-sys/build.rs b/rust/minijail-sys/build.rs index f73191c..4aa172d 100644 --- a/rust/minijail-sys/build.rs +++ b/rust/minijail-sys/build.rs @@ -1,4 +1,4 @@ -// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Copyright 2019 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -85,15 +85,16 @@ fn bindings_generation() -> io::Result<()> { println!("cargo:rerun-if-changed={}", header_path.display()); let status = Command::new(&bindgen) .args(&["--default-enum-style", "rust"]) - .args(&["--blacklist-type", "__rlim64_t"]) + .args(&["--blocklist-type", "__rlim64_t"]) .args(&["--raw-line", "pub type __rlim64_t = u64;"]) - .args(&["--blacklist-type", "__u\\d{1,2}"]) + .args(&["--blocklist-type", "__u\\d{1,2}"]) .args(&["--raw-line", "pub type __u8 = u8;"]) .args(&["--raw-line", "pub type __u16 = u16;"]) .args(&["--raw-line", "pub type __u32 = u32;"]) - .args(&["--blacklist-type", "__uint64_t"]) - .args(&["--whitelist-function", "^minijail_.*"]) - .args(&["--whitelist-var", "^MINIJAIL_.*"]) + .args(&["--blocklist-type", "__uint64_t"]) + .args(&["--allowlist-function", "^minijail_.*"]) + .args(&["--allowlist-var", "^MINIJAIL_.*"]) + .arg("--size_t-is-usize") .arg("--no-layout-tests") .arg("--disable-header-comment") .args(&["--output", gen_file.to_str().unwrap()]) diff --git a/rust/minijail-sys/cargo2android.json b/rust/minijail-sys/cargo2android.json new file mode 100644 index 0000000..4fb4f8f --- /dev/null +++ b/rust/minijail-sys/cargo2android.json @@ -0,0 +1,13 @@ +{ + "run": true, + "device": true, + "apex-available": [ + "//apex_available:platform", + "com.android.compos", + "com.android.virt" + ], + "patch": "patches/Android.bp.patch", + "lib-blocklist": [ + "minijail.pic" + ] +} diff --git a/rust/minijail-sys/lib.rs b/rust/minijail-sys/lib.rs index c418150..0285594 100644 --- a/rust/minijail-sys/lib.rs +++ b/rust/minijail-sys/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2019 The Chromium OS Authors. All rights reserved. +// Copyright 2019 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -18,15 +18,15 @@ // // Generated in CrOS SDK chroot with: // bindgen --default-enum-style rust \ -// --blacklist-type '__rlim64_t' \ +// --blocklist-type '__rlim64_t' \ // --raw-line 'pub type __rlim64_t = u64;' \ -// --blacklist-type '__u\d{1,2}' \ +// --blocklist-type '__u\d{1,2}' \ // --raw-line 'pub type __u8 = u8;' \ // --raw-line 'pub type __u16 = u16;' \ // --raw-line 'pub type __u32 = u32;' \ -// --blacklist-type '__uint64_t' \ -// --whitelist-function '^minijail_.*' \ -// --whitelist-var '^MINIJAIL_.*' \ +// --blocklist-type '__uint64_t' \ +// --allowlist-function '^minijail_.*' \ +// --allowlist-var '^MINIJAIL_.*' \ // --no-layout-tests \ // --output libminijail.rs \ // libminijail.h -- \ diff --git a/rust/minijail-sys/libminijail.rs b/rust/minijail-sys/libminijail.rs index 42b1a8d..a867aee 100644 --- a/rust/minijail-sys/libminijail.rs +++ b/rust/minijail-sys/libminijail.rs @@ -10,7 +10,6 @@ pub type rlim_t = __rlim64_t; pub type gid_t = __gid_t; pub type uid_t = __uid_t; pub type pid_t = __pid_t; -pub type size_t = ::std::os::raw::c_ulong; #[repr(C)] pub struct sock_filter { pub code: __u16, @@ -68,7 +67,7 @@ extern "C" { pub fn minijail_change_gid(j: *mut minijail, gid: gid_t); } extern "C" { - pub fn minijail_set_supplementary_gids(j: *mut minijail, size: size_t, list: *const gid_t); + pub fn minijail_set_supplementary_gids(j: *mut minijail, size: usize, list: *const gid_t); } extern "C" { pub fn minijail_keep_supplementary_gids(j: *mut minijail); @@ -98,6 +97,15 @@ extern "C" { pub fn minijail_set_seccomp_filter_tsync(j: *mut minijail); } extern "C" { + pub fn minijail_set_using_minimalistic_mountns(j: *mut minijail); +} +extern "C" { + pub fn minijail_add_minimalistic_mountns_fs_rules(j: *mut minijail); +} +extern "C" { + pub fn minijail_enable_default_fs_restrictions(j: *mut minijail); +} +extern "C" { pub fn minijail_set_seccomp_filter_allow_speculation(j: *mut minijail); } extern "C" { @@ -229,6 +237,36 @@ extern "C" { ) -> ::std::os::raw::c_int; } extern "C" { + pub fn minijail_add_fs_restriction_rx( + j: *mut minijail, + path: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn minijail_add_fs_restriction_ro( + j: *mut minijail, + path: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn minijail_add_fs_restriction_rw( + j: *mut minijail, + path: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn minijail_add_fs_restriction_advanced_rw( + j: *mut minijail, + path: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { + pub fn minijail_add_fs_restriction_edit( + j: *mut minijail, + path: *const ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { pub fn minijail_forward_signals(j: *mut minijail) -> ::std::os::raw::c_int; } extern "C" { @@ -256,7 +294,7 @@ extern "C" { pub fn minijail_mount_tmp(j: *mut minijail); } extern "C" { - pub fn minijail_mount_tmp_size(j: *mut minijail, size: size_t); + pub fn minijail_mount_tmp_size(j: *mut minijail, size: usize); } extern "C" { pub fn minijail_mount_dev(j: *mut minijail); @@ -320,6 +358,14 @@ extern "C" { pub fn minijail_enter(j: *const minijail); } extern "C" { + pub fn minijail_run_env( + j: *mut minijail, + filename: *const ::std::os::raw::c_char, + argv: *const *mut ::std::os::raw::c_char, + envp: *const *mut ::std::os::raw::c_char, + ) -> ::std::os::raw::c_int; +} +extern "C" { pub fn minijail_run( j: *mut minijail, filename: *const ::std::os::raw::c_char, diff --git a/rust/minijail-sys/patches/Android.bp.patch b/rust/minijail-sys/patches/Android.bp.patch new file mode 100644 index 0000000..fe5115c --- /dev/null +++ b/rust/minijail-sys/patches/Android.bp.patch @@ -0,0 +1,16 @@ +diff --git a/rust/minijail-sys/Android.bp b/rust/minijail-sys/Android.bp +index bdba0d7..788dc77 100644 +--- a/rust/minijail-sys/Android.bp ++++ b/rust/minijail-sys/Android.bp +@@ -14,7 +14,10 @@ rust_library { + rustlibs: [ + "liblibc", + ], +- shared_libs: ["libcap"], ++ shared_libs: [ ++ "libcap", ++ "libminijail", ++ ], + apex_available: [ + "//apex_available:platform", + "com.android.compos", diff --git a/rust/minijail/Android.bp b/rust/minijail/Android.bp new file mode 100644 index 0000000..ba66d81 --- /dev/null +++ b/rust/minijail/Android.bp @@ -0,0 +1,33 @@ +// This file is generated by cargo2android.py --config cargo2android.json. +// Do not modify this file as changes will be overridden on upgrade. + + + +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "external_minijail_license" + // to get the below license kinds: + // SPDX-license-identifier-BSD + default_applicable_licenses: ["external_minijail_license"], +} + +rust_library { + name: "libminijail_rust", + stem: "libminijail", + host_supported: true, + crate_name: "minijail", + cargo_env_compat: true, + cargo_pkg_version: "0.2.3", + srcs: ["src/lib.rs"], + edition: "2018", + rustlibs: [ + "liblibc", + "libminijail_sys", + ], + apex_available: [ + "//apex_available:platform", + "com.android.compos", + "com.android.virt", + ], +} diff --git a/rust/minijail/Cargo.toml b/rust/minijail/Cargo.toml index e6c08b8..5c4087f 100644 --- a/rust/minijail/Cargo.toml +++ b/rust/minijail/Cargo.toml @@ -2,14 +2,9 @@ name = "minijail" version = "0.2.3" description = "Provides a safe Rust friendly interface to libminijail." -authors = ["The Chromium OS Authors"] +authors = ["The ChromiumOS Authors"] edition = "2018" [dependencies] libc = "0.2.44" minijail-sys = { path = "../minijail-sys" } # provided by ebuild - -[[test]] -name = "fork_remap" -path = "tests/fork_remap.rs" -harness = false diff --git a/rust/minijail/cargo2android.json b/rust/minijail/cargo2android.json new file mode 100644 index 0000000..d2adf3e --- /dev/null +++ b/rust/minijail/cargo2android.json @@ -0,0 +1,9 @@ +{ + "run": true, + "device": true, + "apex-available": [ + "//apex_available:platform", + "com.android.compos", + "com.android.virt" + ] +} diff --git a/rust/minijail/src/lib.rs b/rust/minijail/src/lib.rs index 5028041..5d7d7fe 100644 --- a/rust/minijail/src/lib.rs +++ b/rust/minijail/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2017 The Chromium OS Authors. All rights reserved. +// Copyright 2017 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -319,7 +319,11 @@ impl Display for Error { SeccompViolation(s) => write!(f, "seccomp violation syscall #{}", s), Killed(s) => write!(f, "killed with signal number {}", s), ReturnCode(e) => write!(f, "exited with code {}", e), - Wait(errno) => write!(f, "failed to wait: {}", io::Error::from_raw_os_error(*errno)), + Wait(errno) => write!( + f, + "failed to wait: {}", + io::Error::from_raw_os_error(*errno) + ), } } } @@ -465,7 +469,7 @@ impl Minijail { } pub fn set_supplementary_gids(&mut self, ids: &[libc::gid_t]) { unsafe { - minijail_set_supplementary_gids(self.jail, ids.len() as size_t, ids.as_ptr()); + minijail_set_supplementary_gids(self.jail, ids.len(), ids.as_ptr()); } } pub fn keep_supplementary_gids(&mut self) { @@ -767,7 +771,7 @@ impl Minijail { } pub fn mount_tmp_size(&mut self, size: usize) { unsafe { - minijail_mount_tmp_size(self.jail, size as size_t); + minijail_mount_tmp_size(self.jail, size); } } pub fn mount_bind<P1: AsRef<Path>, P2: AsRef<Path>>( @@ -920,6 +924,9 @@ impl Minijail { /// /// This Function may abort in the child on error because a partially /// entered jail isn't recoverable. + /// + /// Once this is invoked the object is no longer usable, after this call + /// this minijail object is invalid. pub unsafe fn fork(&self, inheritable_fds: Option<&[RawFd]>) -> Result<pid_t> { let m: Vec<(RawFd, RawFd)> = inheritable_fds .unwrap_or(&[]) @@ -1000,7 +1007,8 @@ impl Minijail { } impl Drop for Minijail { - /// Frees the Minijail created in Minijail::new. + /// Frees the Minijail created in Minijail::new. This will not terminate the + /// minijailed process. fn drop(&mut self) { unsafe { // Destroys the minijail's memory. It is safe to do here because all references to @@ -1189,7 +1197,7 @@ fi #[test] fn runnable_fd_success() { let bin_file = File::open("/bin/true").unwrap(); - // On Chrome OS targets /bin/true is actually a script, so drop CLOEXEC to prevent ENOENT. + // On ChromeOS targets /bin/true is actually a script, so drop CLOEXEC to prevent ENOENT. clear_cloexec(&bin_file).unwrap(); let j = Minijail::new().unwrap(); diff --git a/rust/minijail/tests/fork_remap.rs b/rust/minijail/tests/fork_remap.rs index 6cf3415..21f7388 100644 --- a/rust/minijail/tests/fork_remap.rs +++ b/rust/minijail/tests/fork_remap.rs @@ -1,4 +1,4 @@ -// Copyright 2021 The Chromium OS Authors. All rights reserved. +// Copyright 2021 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. diff --git a/scoped_minijail.h b/scoped_minijail.h index 38f1a91..160cd50 100644 --- a/scoped_minijail.h +++ b/scoped_minijail.h @@ -1,4 +1,4 @@ -/* Copyright 2016 The Chromium OS Authors. All rights reserved. +/* Copyright 2016 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ diff --git a/signal_handler.c b/signal_handler.c index 175c9eb..77b32ca 100644 --- a/signal_handler.c +++ b/signal_handler.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +/* Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ diff --git a/signal_handler.h b/signal_handler.h index 756273f..2b08018 100644 --- a/signal_handler.h +++ b/signal_handler.h @@ -1,5 +1,5 @@ /* signal_handler.h - * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * diff --git a/syscall_filter.c b/syscall_filter.c index de5441c..c986f3c 100644 --- a/syscall_filter.c +++ b/syscall_filter.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +/* Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -34,21 +34,21 @@ int seccomp_can_softfail(void) int str_to_op(const char *op_str) { - if (!strcmp(op_str, "==")) { + if (streq(op_str, "==")) { return EQ; - } else if (!strcmp(op_str, "!=")) { + } else if (streq(op_str, "!=")) { return NE; - } else if (!strcmp(op_str, "<")) { + } else if (streq(op_str, "<")) { return LT; - } else if (!strcmp(op_str, "<=")) { + } else if (streq(op_str, "<=")) { return LE; - } else if (!strcmp(op_str, ">")) { + } else if (streq(op_str, ">")) { return GT; - } else if (!strcmp(op_str, ">=")) { + } else if (streq(op_str, ">=")) { return GE; - } else if (!strcmp(op_str, "&")) { + } else if (streq(op_str, "&")) { return SET; - } else if (!strcmp(op_str, "in")) { + } else if (streq(op_str, "in")) { return IN; } else { return 0; @@ -705,7 +705,7 @@ int compile_file(const char *filename, FILE *policy_file, * For each syscall, add either a simple ALLOW, * or an arg filter block. */ - if (strcmp(policy_line, "1") == 0) { + if (streq(policy_line, "1")) { /* Add simple ALLOW. */ append_allow_syscall(head, nr); } else { @@ -742,8 +742,8 @@ int compile_file(const char *filename, FILE *policy_file, } /* Reuse |line| in the next getline() call. */ } - /* getline(3) returned -1. This can mean EOF or the below errors. */ - if (errno == EINVAL || errno == ENOMEM) { + /* getline(3) returned -1. This can mean EOF or an error. */ + if (!feof(policy_file)) { if (*arg_blocks) { free_block_list(*arg_blocks); *arg_blocks = NULL; diff --git a/syscall_filter.h b/syscall_filter.h index 304f8c0..dac5c2e 100644 --- a/syscall_filter.h +++ b/syscall_filter.h @@ -1,5 +1,5 @@ /* syscall_filter.h - * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * diff --git a/syscall_filter_unittest.cc b/syscall_filter_unittest.cc index 79755f9..c202b02 100644 --- a/syscall_filter_unittest.cc +++ b/syscall_filter_unittest.cc @@ -1,4 +1,4 @@ -/* Copyright 2016 The Chromium OS Authors. All rights reserved. +/* Copyright 2016 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * diff --git a/syscall_filter_unittest_macros.h b/syscall_filter_unittest_macros.h index b58dd7e..3848541 100644 --- a/syscall_filter_unittest_macros.h +++ b/syscall_filter_unittest_macros.h @@ -1,4 +1,4 @@ -/* Copyright 2016 The Chromium OS Authors. All rights reserved. +/* Copyright 2016 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ diff --git a/syscall_wrapper.c b/syscall_wrapper.c index dd6f826..dfdbfcd 100644 --- a/syscall_wrapper.c +++ b/syscall_wrapper.c @@ -1,4 +1,4 @@ -/* Copyright 2016 The Chromium OS Authors. All rights reserved. +/* Copyright 2016 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ diff --git a/syscall_wrapper.h b/syscall_wrapper.h index 7769108..c1988ac 100644 --- a/syscall_wrapper.h +++ b/syscall_wrapper.h @@ -1,4 +1,4 @@ -/* Copyright 2016 The Chromium OS Authors. All rights reserved. +/* Copyright 2016 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -1,4 +1,4 @@ -/* Copyright 2017 The Chromium OS Authors. All rights reserved. +/* Copyright 2017 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -284,11 +284,30 @@ int mkdir_p(const char *path, mode_t mode, bool isdir) } /* + * get_mount_flags: Obtain the mount flags of the mount where |source| lives. + */ +int get_mount_flags(const char *source, unsigned long *mnt_flags) +{ + if (mnt_flags) { + struct statvfs stvfs_buf; + int rc = statvfs(source, &stvfs_buf); + if (rc) { + rc = errno; + pwarn("failed to look up mount flags: source=%s", + source); + return -rc; + } + *mnt_flags = stvfs_buf.f_flag; + } + return 0; +} + +/* * setup_mount_destination: Ensures the mount target exists. * Creates it if needed and possible. */ int setup_mount_destination(const char *source, const char *dest, uid_t uid, - uid_t gid, bool bind, unsigned long *mnt_flags) + uid_t gid, bool bind) { int rc; struct stat st_buf; @@ -329,20 +348,6 @@ int setup_mount_destination(const char *source, const char *dest, uid_t uid, domkdir = S_ISDIR(st_buf.st_mode) || (!bind && (S_ISBLK(st_buf.st_mode) || S_ISCHR(st_buf.st_mode))); - - /* If bind mounting, also grab the mount flags of the source. */ - if (bind && mnt_flags) { - struct statvfs stvfs_buf; - rc = statvfs(source, &stvfs_buf); - if (rc) { - rc = errno; - pwarn( - "failed to look up mount flags: source=%s", - source); - return -rc; - } - *mnt_flags = stvfs_buf.f_flag; - } } else { /* The source is a relative path -- assume it's a pseudo fs. */ @@ -367,8 +372,8 @@ int setup_mount_destination(const char *source, const char *dest, uid_t uid, if (rc) return rc; if (!domkdir) { - attribute_cleanup_fd int fd = open( - dest, O_RDWR | O_CREAT | O_CLOEXEC, 0700); + attribute_cleanup_fd int fd = + open(dest, O_RDWR | O_CREAT | O_CLOEXEC, 0700); if (fd < 0) { rc = errno; pwarn("open(%s) failed", dest); @@ -542,3 +547,21 @@ bool seccomp_filter_flags_available(unsigned int flags) return sys_seccomp(SECCOMP_SET_MODE_FILTER, flags, NULL) != -1 || errno != EINVAL; } + +bool is_canonical_path(const char *path) +{ + attribute_cleanup_str char *rp = realpath(path, NULL); + if (!rp) { + return false; + } + + if (streq(path, rp)) { + return true; + } + + size_t path_len = strlen(path); + size_t rp_len = strlen(rp); + /* If |path| has a single trailing slash, that's OK. */ + return path_len == rp_len + 1 && strncmp(path, rp, rp_len) == 0 && + path[path_len - 1] == '/'; +} @@ -1,4 +1,4 @@ -/* Copyright 2017 The Chromium OS Authors. All rights reserved. +/* Copyright 2017 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * @@ -51,8 +51,10 @@ int write_proc_file(pid_t pid, const char *content, const char *basename); int mkdir_p(const char *path, mode_t mode, bool isdir); +int get_mount_flags(const char *source, unsigned long *mnt_flags); + int setup_mount_destination(const char *source, const char *dest, uid_t uid, - uid_t gid, bool bind, unsigned long *mnt_flags); + uid_t gid, bool bind); int lookup_user(const char *user, uid_t *uid, gid_t *gid); int lookup_group(const char *group, gid_t *gid); @@ -61,6 +63,16 @@ int seccomp_ret_log_available(void); int seccomp_ret_kill_process_available(void); bool seccomp_filter_flags_available(unsigned int flags); +/* + * is_canonical_path: checks whether @path is a canonical path. + * This means: + * -Absolute. + * -No symlinks. + * -No /./, /../, or extra '/'. + * -Single trailing '/' is OK. + */ +bool is_canonical_path(const char *path); + #ifdef __cplusplus }; /* extern "C" */ #endif diff --git a/system_unittest.cc b/system_unittest.cc index 97c1d4e..5fa8076 100644 --- a/system_unittest.cc +++ b/system_unittest.cc @@ -1,4 +1,4 @@ -/* Copyright 2017 The Chromium OS Authors. All rights reserved. +/* Copyright 2017 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * @@ -20,6 +20,7 @@ #include <string> #include "system.h" +#include "unittest_util.h" namespace { @@ -35,91 +36,6 @@ constexpr const char kValidDir[] = "/"; // A random character device that should exist. constexpr const char kValidCharDev[] = "/dev/null"; -constexpr bool is_android() { -#if defined(__ANDROID__) - return true; -#else - return false; -#endif -} - -// Returns a template path that can be used as an argument to mkstemp / mkdtemp. -constexpr const char* temp_path_pattern() { - if (is_android()) - return "/data/local/tmp/minijail.tests.XXXXXX"; - else - return "minijail.tests.XXXXXX"; -} - -// Recursively deletes the subtree rooted at |path|. -bool rmdir_recursive(const std::string& path) { - auto callback = [](const char* child, const struct stat*, int file_type, - struct FTW*) -> int { - if (file_type == FTW_DP) { - if (rmdir(child) == -1) { - fprintf(stderr, "rmdir(%s): %s", child, strerror(errno)); - return -1; - } - } else if (file_type == FTW_F) { - if (unlink(child) == -1) { - fprintf(stderr, "unlink(%s): %s", child, strerror(errno)); - return -1; - } - } - return 0; - }; - - return nftw(path.c_str(), callback, 128, FTW_DEPTH) == 0; -} - -// Creates a temporary directory that will be cleaned up upon leaving scope. -class TemporaryDir { - public: - TemporaryDir() : path(temp_path_pattern()) { - if (mkdtemp(const_cast<char*>(path.c_str())) == nullptr) - path.clear(); - } - ~TemporaryDir() { - if (!is_valid()) - return; - rmdir_recursive(path.c_str()); - } - - bool is_valid() const { return !path.empty(); } - - std::string path; - - private: - TemporaryDir(const TemporaryDir&) = delete; - TemporaryDir& operator=(const TemporaryDir&) = delete; -}; - -// Creates a named temporary file that will be cleaned up upon leaving scope. -class TemporaryFile { - public: - TemporaryFile() : path(temp_path_pattern()) { - int fd = mkstemp(const_cast<char*>(path.c_str())); - if (fd == -1) { - path.clear(); - return; - } - close(fd); - } - ~TemporaryFile() { - if (!is_valid()) - return; - unlink(path.c_str()); - } - - bool is_valid() const { return !path.empty(); } - - std::string path; - - private: - TemporaryFile(const TemporaryFile&) = delete; - TemporaryFile& operator=(const TemporaryFile&) = delete; -}; - } // namespace TEST(secure_noroot_set_and_locked, zero_mask) { @@ -173,7 +89,7 @@ TEST(write_pid_to_path, basic) { char data[6] = {}; EXPECT_EQ(5u, fread(data, 1, sizeof(data), fp)); fclose(fp); - EXPECT_EQ(0, strcmp(data, "1234\n")); + EXPECT_STREQ(data, "1234\n"); } // If the destination exists, there's nothing to do. @@ -214,18 +130,13 @@ TEST(mkdir_p, create_tree) { EXPECT_EQ(true, S_ISDIR(st.st_mode)); } -// If the destination exists, there's nothing to do. -TEST(setup_mount_destination, dest_exists) { - // Pick some paths that should always exist. We pass in invalid pointers - // for other args so we crash if the dest check doesn't short circuit. - EXPECT_EQ(0, setup_mount_destination(nullptr, kValidDir, 0, 0, false, - nullptr)); - EXPECT_EQ(0, setup_mount_destination(nullptr, "/proc", 0, 0, true, nullptr)); - EXPECT_EQ(0, setup_mount_destination(nullptr, "/dev", 0, 0, false, nullptr)); +// Return success on NULL pointer. +TEST(get_mount_flags, null_ptr) { + ASSERT_EQ(0, get_mount_flags("/proc", nullptr)); } -// Mount flags should be obtained for bind-mounts. -TEST(setup_mount_destination, mount_flags) { +// Successfully obtain mount flags. +TEST(get_mount_flags, mount_flags) { struct statvfs stvfs_buf; ASSERT_EQ(0, statvfs("/proc", &stvfs_buf)); @@ -233,26 +144,34 @@ TEST(setup_mount_destination, mount_flags) { ASSERT_TRUE(dir.is_valid()); unsigned long mount_flags = -1; - // Passing -1 for user ID/group ID tells chown to make no changes. - std::string proc = dir.path + "/proc"; - EXPECT_EQ(0, setup_mount_destination("/proc", proc.c_str(), -1, -1, true, - &mount_flags)); + ASSERT_EQ(0, get_mount_flags("/proc", &mount_flags)); EXPECT_EQ(stvfs_buf.f_flag, mount_flags); - EXPECT_EQ(0, rmdir(proc.c_str())); // Same thing holds for children of a mount. mount_flags = -1; - std::string proc_self = dir.path + "/proc_self"; - EXPECT_EQ(0, setup_mount_destination("/proc/self", proc_self.c_str(), -1, -1, - true, &mount_flags)); + ASSERT_EQ(0, get_mount_flags("/proc/self", &mount_flags)); EXPECT_EQ(stvfs_buf.f_flag, mount_flags); - EXPECT_EQ(0, rmdir(proc_self.c_str())); +} + +// Non-existent paths fail with the proper errno value. +TEST(get_mount_flags, nonexistent_path) { + unsigned long mount_flags = -1; + ASSERT_EQ(-ENOENT, get_mount_flags("/does/not/exist", &mount_flags)); +} + +// If the destination exists, there's nothing to do. +TEST(setup_mount_destination, dest_exists) { + // Pick some paths that should always exist. We pass in invalid pointers + // for other args so we crash if the dest check doesn't short circuit. + EXPECT_EQ(0, setup_mount_destination(nullptr, kValidDir, 0, 0, false)); + EXPECT_EQ(0, setup_mount_destination(nullptr, "/proc", 0, 0, true)); + EXPECT_EQ(0, setup_mount_destination(nullptr, "/dev", 0, 0, false)); } // When given a bind mount where the source is relative, reject it. TEST(setup_mount_destination, reject_relative_bind) { // Pick a destination we know doesn't exist. - EXPECT_NE(0, setup_mount_destination("foo", kNoSuchDir, 0, 0, true, nullptr)); + EXPECT_NE(0, setup_mount_destination("foo", kNoSuchDir, 0, 0, true)); } // A mount of a pseudo filesystem should make the destination dir. @@ -262,8 +181,8 @@ TEST(setup_mount_destination, create_pseudo_fs) { // Passing -1 for user ID/group ID tells chown to make no changes. std::string no_chmod = dir.path + "/no_chmod"; - EXPECT_EQ(0, setup_mount_destination("none", no_chmod.c_str(), -1, -1, false, - nullptr)); + EXPECT_EQ(0, setup_mount_destination("none", no_chmod.c_str(), -1, -1, + false)); // We check it's a directory by deleting it as such. EXPECT_EQ(0, rmdir(no_chmod.c_str())); @@ -274,18 +193,15 @@ TEST(setup_mount_destination, create_pseudo_fs) { if (!is_android()) { std::string with_chmod = dir.path + "/with_chmod"; EXPECT_NE(0, setup_mount_destination("none", with_chmod.c_str(), - UINT_MAX / 2, UINT_MAX / 2, false, - nullptr)); + UINT_MAX / 2, UINT_MAX / 2, false)); } } // If the source path does not exist, we should error out. TEST(setup_mount_destination, missing_source) { // The missing dest path is so we can exercise the source logic. - EXPECT_NE(0, setup_mount_destination(kNoSuchDir, kNoSuchDir, 0, 0, false, - nullptr)); - EXPECT_NE(0, setup_mount_destination(kNoSuchDir, kNoSuchDir, 0, 0, true, - nullptr)); + EXPECT_NE(0, setup_mount_destination(kNoSuchDir, kNoSuchDir, 0, 0, false)); + EXPECT_NE(0, setup_mount_destination(kNoSuchDir, kNoSuchDir, 0, 0, true)); } // A bind mount of a directory should create the destination dir. @@ -296,7 +212,7 @@ TEST(setup_mount_destination, create_bind_dir) { // Passing -1 for user ID/group ID tells chown to make no changes. std::string child_dir = dir.path + "/child_dir"; EXPECT_EQ(0, setup_mount_destination(kValidDir, child_dir.c_str(), -1, -1, - true, nullptr)); + true)); // We check it's a directory by deleting it as such. EXPECT_EQ(0, rmdir(child_dir.c_str())); } @@ -309,7 +225,7 @@ TEST(setup_mount_destination, create_bind_file) { // Passing -1 for user ID/group ID tells chown to make no changes. std::string child_file = dir.path + "/child_file"; EXPECT_EQ(0, setup_mount_destination(kValidFile, child_file.c_str(), -1, -1, - true, nullptr)); + true)); // We check it's a file by deleting it as such. EXPECT_EQ(0, unlink(child_file.c_str())); } @@ -322,7 +238,7 @@ TEST(setup_mount_destination, create_char_dev) { // Passing -1 for user ID/group ID tells chown to make no changes. std::string child_dev = dir.path + "/child_dev"; EXPECT_EQ(0, setup_mount_destination(kValidCharDev, child_dev.c_str(), -1, -1, - false, nullptr)); + false)); // We check it's a directory by deleting it as such. EXPECT_EQ(0, rmdir(child_dev.c_str())); } @@ -331,3 +247,19 @@ TEST(seccomp_actions_available, smoke) { seccomp_ret_log_available(); seccomp_ret_kill_process_available(); } + +TEST(is_canonical_path, basic) { + EXPECT_FALSE(is_canonical_path("/proc/self")); + EXPECT_FALSE(is_canonical_path("relative")); + EXPECT_FALSE(is_canonical_path("/proc/./1")); + EXPECT_FALSE(is_canonical_path("/proc/../proc/1")); + + EXPECT_TRUE(is_canonical_path("/")); + EXPECT_TRUE(is_canonical_path("/proc")); + EXPECT_TRUE(is_canonical_path("/proc/1")); +} + +TEST(is_canonical_path, trailing_slash) { + EXPECT_TRUE(is_canonical_path("/proc/1/")); + EXPECT_FALSE(is_canonical_path("/proc/1//")); +} diff --git a/test/landlock.conf b/test/landlock.conf new file mode 100644 index 0000000..65ffe81 --- /dev/null +++ b/test/landlock.conf @@ -0,0 +1,7 @@ +% minijail-config-file v0 + +fs-default-paths +fs-path-ro = / +fs-path-rx = /lib +fs-path-rw = /tmp +fs-path-advanced-rw = /tmp
\ No newline at end of file diff --git a/test/read_stdin b/test/read_stdin index 29578a6..6200bb5 100644 --- a/test/read_stdin +++ b/test/read_stdin @@ -1,6 +1,6 @@ #!/bin/bash -# Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +# Copyright 2012 The ChromiumOS Authors # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. diff --git a/test_util.cc b/test_util.cc index cb751ff..bbe0215 100644 --- a/test_util.cc +++ b/test_util.cc @@ -1,4 +1,4 @@ -/* Copyright 2021 The Chromium OS Authors. All rights reserved. +/* Copyright 2021 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ diff --git a/test_util.h b/test_util.h index e915086..7f923ed 100644 --- a/test_util.h +++ b/test_util.h @@ -1,5 +1,5 @@ /* test_util.h - * Copyright 2021 The Chromium OS Authors. All rights reserved. + * Copyright 2021 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * diff --git a/testrunner.cc b/testrunner.cc index 162f0e5..70010f2 100644 --- a/testrunner.cc +++ b/testrunner.cc @@ -1,4 +1,4 @@ -/* Copyright 2017 The Chromium OS Authors. All rights reserved. +/* Copyright 2017 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * diff --git a/tools/Android.bp b/tools/Android.bp index 71bb82d..62b3a88 100644 --- a/tools/Android.bp +++ b/tools/Android.bp @@ -31,14 +31,6 @@ python_binary_host { "compiler.py", "parser.py", ], - version: { - py2: { - enabled: false, - }, - py3: { - enabled: true, - }, - }, } python_test_host { @@ -53,14 +45,6 @@ python_test_host { data: [ "testdata/arch_64.json", ], - version: { - py2: { - enabled: false, - }, - py3: { - enabled: true, - }, - }, } python_test_host { @@ -76,14 +60,6 @@ python_test_host { data: [ "testdata/arch_64.json", ], - version: { - py2: { - enabled: false, - }, - py3: { - enabled: true, - }, - }, } python_binary_host { @@ -92,14 +68,6 @@ python_binary_host { srcs: [ "generate_constants_json.py", ], - version: { - py2: { - enabled: false, - }, - py3: { - enabled: true, - }, - }, } diff --git a/tools/generate_constants_json.py b/tools/generate_constants_json.py index 6b38022..005fff8 100755 --- a/tools/generate_constants_json.py +++ b/tools/generate_constants_json.py @@ -38,6 +38,21 @@ _TABLE_ENTRY_RE = re.compile( # number. _TABLE_ENTRY_CONTENTS = re.compile(r'.*?(null|@[a-zA-Z0-9.]+).* (-?\d+)') +# When testing clang-r458909, we found a new constant_entry pattern: +# %struct.constant_entry { ptr @.str.894, i32 ptrtoint (ptr @.str.895 to i32) }, +# For the same constant, current clang-r458507 generates: +# %struct.constant_entry { i8* getelementptr inbounds +# ([19 x i8], [19 x i8]* @.str.894, i32 0, i32 0), +# i32 ptrtoint ([9 x i8]* @.str.895 to i32) }, +# This is for a char* constant defined in linux-x86/libconstants.gen.c: +# { "FS_KEY_DESC_PREFIX", (unsigned long) FS_KEY_DESC_PREFIX }, +# and FS_KEY_DESC_PREFIX is defined as a char* "fscrypt:" +# Current output for that constant in constants.json is: +# "FS_KEY_DESC_PREFIX": 0, +# but that value does not seem to be useful or accurate. +# So here we define a pattern to ignore such pointer constants: +_IGNORED_ENTRY_CONTENTS = re.compile(r'.*? ptrto.* \(.*\)') + ParseResults = collections.namedtuple('ParseResults', ['table_name', 'table_entries']) @@ -65,6 +80,8 @@ def parse_llvm_ir(ir): for entry in _TABLE_ENTRY_RE.findall(line): groups = _TABLE_ENTRY_CONTENTS.match(entry) if not groups: + if _IGNORED_ENTRY_CONTENTS.match(entry): + continue raise ValueError('Failed to parse table entry %r' % entry) name, value = groups.groups() if name == 'null': @@ -113,6 +130,8 @@ def main(argv=None): constants_json['arch_name'] = 'arm64' elif constants_json['arch_nr'] == 0x40000028: constants_json['arch_name'] = 'arm' + elif constants_json['arch_nr'] == 0xC00000F3: + constants_json['arch_name'] = 'riscv64' else: raise ValueError('Unknown architecture: 0x%08X' % constants_json['arch_nr']) diff --git a/tools/repo_upload_warning b/tools/repo_upload_warning new file mode 100755 index 0000000..3333eba --- /dev/null +++ b/tools/repo_upload_warning @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +if git log -n 1 --format='%B' $1 | grep -q -E "^Ignore-Upstream-First: .+" ; then + # Change is explicitly marked as ok to skip upstream + exit 0 +elif git log -n 1 --format='%s' $1 | grep -q -E "^(UPSTREAM|Upstream): .+" ; then + # Change is explicitly marked as coming from the upstream + exit 0 +fi + +echo "WARNING: Here is not the upstream." +echo "" +echo "Do not submit changes to this repository directly. Please submit changes to upstream" +echo "from https://chromium-review.googlesource.com/q/project:chromiumos/platform/minijail" +echo "" +echo "If the change is from the upstream, please prepend \"UPSTREAM: \" to the subject." +echo "" +echo "If indeed necessary, please add \"Ignore-Upstream-First: <reason>\" to commit message" +echo "to bypass." + +exit 1 diff --git a/unittest_util.h b/unittest_util.h new file mode 100644 index 0000000..4dcfe80 --- /dev/null +++ b/unittest_util.h @@ -0,0 +1,104 @@ +/* unittest_util.h + * Copyright 2022 The ChromiumOS Authors + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + * + * Utility functions for unit tests. + */ + +#include <errno.h> +#include <ftw.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "util.h" + +namespace { + +constexpr bool is_android_constexpr() { +#if defined(__ANDROID__) + return true; +#else + return false; +#endif +} + +// Returns a template path that can be used as an argument to mkstemp / mkdtemp. +constexpr const char* temp_path_pattern() { + if (is_android_constexpr()) + return "/data/local/tmp/minijail.tests.XXXXXX"; + else + return "minijail.tests.XXXXXX"; +} + +// Recursively deletes the subtree rooted at |path|. +bool rmdir_recursive(const std::string& path) { + auto callback = [](const char* child, const struct stat*, int file_type, + struct FTW*) -> int { + if (file_type == FTW_DP) { + if (rmdir(child) == -1) { + fprintf(stderr, "rmdir(%s): %s\n", child, strerror(errno)); + return -1; + } + } else if (file_type == FTW_F) { + if (unlink(child) == -1) { + fprintf(stderr, "unlink(%s): %s\n", child, strerror(errno)); + return -1; + } + } + return 0; + }; + + return nftw(path.c_str(), callback, 128, FTW_DEPTH) == 0; +} + +} // namespace + +// Creates a temporary directory that will be cleaned up upon leaving scope. +class TemporaryDir { + public: + TemporaryDir() : path(temp_path_pattern()) { + if (mkdtemp(const_cast<char*>(path.c_str())) == nullptr) + path.clear(); + } + ~TemporaryDir() { + if (!is_valid()) + return; + rmdir_recursive(path.c_str()); + } + + bool is_valid() const { return !path.empty(); } + + std::string path; + + private: + TemporaryDir(const TemporaryDir&) = delete; + TemporaryDir& operator=(const TemporaryDir&) = delete; +}; + +// Creates a named temporary file that will be cleaned up upon leaving scope. +class TemporaryFile { + public: + TemporaryFile() : path(temp_path_pattern()) { + int fd = mkstemp(const_cast<char*>(path.c_str())); + if (fd == -1) { + path.clear(); + return; + } + close(fd); + } + ~TemporaryFile() { + if (!is_valid()) + return; + unlink(path.c_str()); + } + + bool is_valid() const { return !path.empty(); } + + std::string path; + + private: + TemporaryFile(const TemporaryFile&) = delete; + TemporaryFile& operator=(const TemporaryFile&) = delete; +}; @@ -1,4 +1,4 @@ -/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved. +/* Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ @@ -33,42 +33,47 @@ * sendto(...) <- important * exit_group(0) <- finish! */ +const char *const log_syscalls[] = { #if defined(__x86_64__) -#if defined(__ANDROID__) -const char *const log_syscalls[] = {"socket", "connect", "fcntl", "writev"}; -#else -const char *const log_syscalls[] = {"socket", "connect", "sendto", "writev"}; -#endif +# if defined(__ANDROID__) + "socket", "connect", "fcntl", "writev", +# else + "socket", "connect", "sendto", "writev", +# endif #elif defined(__i386__) -#if defined(__ANDROID__) -const char *const log_syscalls[] = {"socketcall", "writev", "fcntl64", - "clock_gettime"}; -#else -const char *const log_syscalls[] = {"socketcall", "time", "writev"}; -#endif +# if defined(__ANDROID__) + "socketcall", "writev", "fcntl64", "clock_gettime", +# else + "socketcall", "time", "writev", +# endif #elif defined(__arm__) -#if defined(__ANDROID__) -const char *const log_syscalls[] = {"clock_gettime", "connect", "fcntl64", - "socket", "writev"}; -#else -const char *const log_syscalls[] = {"socket", "connect", "gettimeofday", "send", - "writev"}; -#endif +# if defined(__ANDROID__) + "clock_gettime", "connect", "fcntl64", "socket", "writev", +# else + "socket", "connect", "gettimeofday", "send", "writev", +# endif #elif defined(__aarch64__) -#if defined(__ANDROID__) -const char *const log_syscalls[] = {"connect", "fcntl", "sendto", "socket", - "writev"}; -#else -const char *const log_syscalls[] = {"socket", "connect", "send", "writev"}; -#endif -#elif defined(__powerpc__) || defined(__ia64__) || defined(__hppa__) || \ - defined(__sparc__) || defined(__mips__) -const char *const log_syscalls[] = {"socket", "connect", "send"}; +# if defined(__ANDROID__) + "connect", "fcntl", "sendto", "socket", "writev", +# else + "socket", "connect", "send", "writev", +# endif +#elif defined(__hppa__) || \ + defined(__ia64__) || \ + defined(__mips__) || \ + defined(__powerpc__) || \ + defined(__sparc__) + "socket", "connect", "send", #elif defined(__riscv) -const char *const log_syscalls[] = {"socket", "connect", "sendto"}; +# if defined(__ANDROID__) + "connect", "fcntl", "sendto", "socket", "writev", +# else + "socket", "connect", "sendto", +# endif #else -#error "Unsupported platform" +# error "Unsupported platform" #endif +}; const size_t log_syscalls_len = ARRAY_SIZE(log_syscalls); @@ -161,7 +166,7 @@ int lookup_syscall(const char *name, size_t *ind) size_t ind_tmp = 0; const struct syscall_entry *entry = syscall_table; for (; entry->name && entry->nr >= 0; ++entry) { - if (!strcmp(entry->name, name)) { + if (streq(entry->name, name)) { if (ind != NULL) *ind = ind_tmp; return entry->nr; @@ -187,7 +192,7 @@ long int parse_single_constant(char *constant_str, char **endptr) const struct constant_entry *entry = constant_table; long int res = 0; for (; entry->name; ++entry) { - if (!strcmp(entry->name, constant_str)) { + if (streq(entry->name, constant_str)) { *endptr = constant_str + strlen(constant_str); return entry->value; } @@ -449,6 +454,22 @@ char *path_join(const char *external_path, const char *internal_path) : path; } +bool path_is_parent(const char *parent, const char *child) +{ + /* + * -Make sure |child| starts with |parent|. + * -Make sure that if |child| is longer than |parent|, either: + * --the last character in |parent| is a path separator, or + * --the character immediately following |parent| in |child| is a path + * separator. + */ + size_t parent_len = strlen(parent); + return strncmp(parent, child, parent_len) == 0 && + (strlen(child) > parent_len ? (parent[parent_len - 1] == '/' || + child[parent_len] == '/') + : false); +} + void *consumebytes(size_t length, char **buf, size_t *buflength) { char *p = *buf; @@ -1,5 +1,5 @@ /* util.h - * Copyright (c) 2012 The Chromium OS Authors. All rights reserved. + * Copyright 2012 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * @@ -12,6 +12,7 @@ #include <stdbool.h> #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <sys/types.h> #include <syslog.h> #include <unistd.h> @@ -220,6 +221,24 @@ static inline bool seccomp_default_ret_log(void) #endif } +static inline bool block_symlinks_in_bindmount_paths(void) +{ +#if defined(BLOCK_SYMLINKS_IN_BINDMOUNT_PATHS) + return true; +#else + return false; +#endif +} + +static inline bool block_symlinks_in_noninit_mountns_tmp(void) +{ +#if defined(BLOCK_SYMLINKS_IN_NONINIT_MOUNTNS_TMP) + return true; +#else + return false; +#endif +} + static inline size_t get_num_syscalls(void) { return syscall_table_size; @@ -235,6 +254,14 @@ int parse_size(size_t *size, const char *sizespec); char *strip(char *s); /* + * streq: determine whether two strings are equal. + */ +static inline bool streq(const char *s1, const char *s2) +{ + return strcmp(s1, s2) == 0; +} + +/* * tokenize: locate the next token in @stringp using the @delim * @stringp A pointer to the string to scan for tokens * @delim The delimiter to split by @@ -250,6 +277,13 @@ char *tokenize(char **stringp, const char *delim); char *path_join(const char *external_path, const char *internal_path); /* + * path_is_parent: checks whether @parent is a parent of @child. + * Note: this function does not evaluate '.' or '..' nor does it resolve + * symlinks. + */ +bool path_is_parent(const char *parent, const char *child); + +/* * consumebytes: consumes @length bytes from a buffer @buf of length @buflength * @length Number of bytes to consume * @buf Buffer to consume from diff --git a/util_unittest.cc b/util_unittest.cc index b9e6dfc..b3a2350 100644 --- a/util_unittest.cc +++ b/util_unittest.cc @@ -1,4 +1,4 @@ -/* Copyright 2018 The Chromium OS Authors. All rights reserved. +/* Copyright 2018 The ChromiumOS Authors * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * @@ -402,6 +402,20 @@ TEST(path_join, basic) { free(path); } +TEST(path_is_parent, simple) { + EXPECT_TRUE(path_is_parent("/dev", "/dev/rtc")); + EXPECT_TRUE(path_is_parent("/dev/", "/dev/rtc")); + EXPECT_TRUE(path_is_parent("/sys", "/sys/power")); + EXPECT_TRUE(path_is_parent("/sys/power", "/sys/power/something")); + EXPECT_TRUE(path_is_parent("/sys", "/sys/sys/power")); + + EXPECT_FALSE(path_is_parent("/dev", "")); + EXPECT_FALSE(path_is_parent("/dev", "/sys")); + EXPECT_FALSE(path_is_parent("/dev", "dev")); + EXPECT_FALSE(path_is_parent("/dev", "/sys/dev")); + EXPECT_FALSE(path_is_parent("/dev", "/device")); +} + TEST(getmultiline, basic) { std::string config = "\n" |