diff --git a/init/Android.bp b/init/Android.bp index 432c29870..db64f71d4 100644 --- a/init/Android.bp +++ b/init/Android.bp @@ -70,6 +70,8 @@ cc_library_static { "log.cpp", "parser.cpp", "property_service.cpp", + "security.cpp", + "selinux.cpp", "service.cpp", "tokenizer.cpp", "uevent_listener.cpp", diff --git a/init/builtins.cpp b/init/builtins.cpp index eea78fd1c..944fcee22 100644 --- a/init/builtins.cpp +++ b/init/builtins.cpp @@ -225,23 +225,22 @@ static int do_insmod(const std::vector& args) { return insmod(filename.c_str(), options.c_str(), flags); } +// mkdir [mode] [owner] [group] static int do_mkdir(const std::vector& args) { mode_t mode = 0755; - int ret; - - /* mkdir [mode] [owner] [group] */ - if (args.size() >= 3) { mode = std::strtoul(args[2].c_str(), 0, 8); } - ret = make_dir(args[1].c_str(), mode, sehandle); - /* chmod in case the directory already exists */ - if (ret == -1 && errno == EEXIST) { - ret = fchmodat(AT_FDCWD, args[1].c_str(), mode, AT_SYMLINK_NOFOLLOW); - } - if (ret == -1) { - return -errno; + if (!make_dir(args[1], mode)) { + /* chmod in case the directory already exists */ + if (errno == EEXIST) { + if (fchmodat(AT_FDCWD, args[1].c_str(), mode, AT_SYMLINK_NOFOLLOW) == -1) { + return -errno; + } + } else { + return -errno; + } } if (args.size() >= 4) { @@ -266,8 +265,7 @@ static int do_mkdir(const std::vector& args) { /* chown may have cleared S_ISUID and S_ISGID, chmod again */ if (mode & (S_ISUID | S_ISGID)) { - ret = fchmodat(AT_FDCWD, args[1].c_str(), mode, AT_SYMLINK_NOFOLLOW); - if (ret == -1) { + if (fchmodat(AT_FDCWD, args[1].c_str(), mode, AT_SYMLINK_NOFOLLOW) == -1) { return -errno; } } @@ -895,17 +893,6 @@ static int do_wait_for_prop(const std::vector& args) { return 0; } -/* - * Callback to make a directory from the ext4 code - */ -static int do_installkeys_ensure_dir_exists(const char* dir) { - if (make_dir(dir, 0700, sehandle) && errno != EEXIST) { - return -1; - } - - return 0; -} - static bool is_file_crypto() { return android::base::GetProperty("ro.crypto.type", "") == "file"; } @@ -915,7 +902,7 @@ static int do_installkey(const std::vector& args) { return 0; } auto unencrypted_dir = args[1] + e4crypt_unencrypted_folder; - if (do_installkeys_ensure_dir_exists(unencrypted_dir.c_str())) { + if (!make_dir(unencrypted_dir, 0700) && errno != EEXIST) { PLOG(ERROR) << "Failed to create " << unencrypted_dir; return -1; } diff --git a/init/descriptors.cpp b/init/descriptors.cpp index 0cb639a91..cc5b948e9 100644 --- a/init/descriptors.cpp +++ b/init/descriptors.cpp @@ -86,8 +86,7 @@ int SocketInfo::Create(const std::string& context) const { int flags = ((types[0] == "stream" ? SOCK_STREAM : (types[0] == "dgram" ? SOCK_DGRAM : SOCK_SEQPACKET))); bool passcred = types.size() > 1 && types[1] == "passcred"; - return CreateSocket(name().c_str(), flags, passcred, perm(), uid(), gid(), context.c_str(), - sehandle); + return CreateSocket(name().c_str(), flags, passcred, perm(), uid(), gid(), context.c_str()); } const std::string SocketInfo::key() const { diff --git a/init/devices.cpp b/init/devices.cpp index 13cf991ca..d59f53c74 100644 --- a/init/devices.cpp +++ b/init/devices.cpp @@ -30,6 +30,7 @@ #include #include +#include "selinux.h" #include "ueventd.h" #include "util.h" @@ -224,18 +225,13 @@ void DeviceHandler::MakeDevice(const std::string& path, bool block, int major, i auto[mode, uid, gid] = GetDevicePermissions(path, links); mode |= (block ? S_IFBLK : S_IFCHR); - char* secontext = nullptr; - if (sehandle_) { - std::vector c_links; - for (const auto& link : links) { - c_links.emplace_back(link.c_str()); - } - c_links.emplace_back(nullptr); - if (selabel_lookup_best_match(sehandle_, &secontext, path.c_str(), &c_links[0], mode)) { - PLOG(ERROR) << "Device '" << path << "' not created; cannot find SELinux label"; - return; - } - setfscreatecon(secontext); + std::string secontext; + if (!SelabelLookupFileContextBestMatch(path, links, mode, &secontext)) { + PLOG(ERROR) << "Device '" << path << "' not created; cannot find SELinux label"; + return; + } + if (!secontext.empty()) { + setfscreatecon(secontext.c_str()); } dev_t dev = makedev(major, minor); @@ -250,7 +246,7 @@ void DeviceHandler::MakeDevice(const std::string& path, bool block, int major, i } /* If the node already exists update its SELinux label to handle cases when * it was created with the wrong context during coldboot procedure. */ - if (mknod(path.c_str(), mode, dev) && (errno == EEXIST) && secontext) { + if (mknod(path.c_str(), mode, dev) && (errno == EEXIST) && !secontext.empty()) { char* fcon = nullptr; int rc = lgetfilecon(path.c_str(), &fcon); if (rc < 0) { @@ -258,10 +254,10 @@ void DeviceHandler::MakeDevice(const std::string& path, bool block, int major, i goto out; } - bool different = strcmp(fcon, secontext) != 0; + bool different = fcon != secontext; freecon(fcon); - if (different && lsetfilecon(path.c_str(), secontext)) { + if (different && lsetfilecon(path.c_str(), secontext.c_str())) { PLOG(ERROR) << "Cannot set '" << secontext << "' SELinux label on '" << path << "' device"; } @@ -273,8 +269,7 @@ out: PLOG(FATAL) << "setegid(AID_ROOT) failed"; } - if (secontext) { - freecon(secontext); + if (!secontext.empty()) { setfscreatecon(nullptr); } } @@ -351,7 +346,7 @@ void DeviceHandler::HandleDevice(const std::string& action, const std::string& d if (action == "add") { MakeDevice(devpath, block, major, minor, links); for (const auto& link : links) { - if (mkdir_recursive(Dirname(link), 0755, sehandle_)) { + if (!mkdir_recursive(Dirname(link), 0755)) { PLOG(ERROR) << "Failed to create directory " << Dirname(link); } @@ -415,7 +410,7 @@ void DeviceHandler::HandleDeviceEvent(const Uevent& uevent) { devpath = "/dev/" + Basename(uevent.path); } - mkdir_recursive(Dirname(devpath), 0755, sehandle_); + mkdir_recursive(Dirname(devpath), 0755); HandleDevice(uevent.action, devpath, block, uevent.major, uevent.minor, links); } @@ -426,7 +421,6 @@ DeviceHandler::DeviceHandler(std::vector dev_permissions, : dev_permissions_(std::move(dev_permissions)), sysfs_permissions_(std::move(sysfs_permissions)), subsystems_(std::move(subsystems)), - sehandle_(selinux_android_file_context_handle()), skip_restorecon_(skip_restorecon), sysfs_mount_point_("/sys") {} diff --git a/init/devices.h b/init/devices.h index c64f5fb97..dd44337dc 100644 --- a/init/devices.h +++ b/init/devices.h @@ -124,7 +124,6 @@ class DeviceHandler { std::vector dev_permissions_; std::vector sysfs_permissions_; std::vector subsystems_; - selabel_handle* sehandle_; bool skip_restorecon_; std::string sysfs_mount_point_; }; diff --git a/init/devices_test.cpp b/init/devices_test.cpp index ac4ab9b30..eba00cb78 100644 --- a/init/devices_test.cpp +++ b/init/devices_test.cpp @@ -35,13 +35,13 @@ class DeviceHandlerTester { device_handler_.sysfs_mount_point_ = fake_sys_root.path; std::string platform_device_dir = fake_sys_root.path + platform_device; - mkdir_recursive(platform_device_dir, 0777, nullptr); + mkdir_recursive(platform_device_dir, 0777); std::string platform_bus = fake_sys_root.path + "/bus/platform"s; - mkdir_recursive(platform_bus, 0777, nullptr); + mkdir_recursive(platform_bus, 0777); symlink(platform_bus.c_str(), (platform_device_dir + "/subsystem").c_str()); - mkdir_recursive(android::base::Dirname(fake_sys_root.path + uevent.path), 0777, nullptr); + mkdir_recursive(android::base::Dirname(fake_sys_root.path + uevent.path), 0777); std::vector result; result = device_handler_.GetBlockDeviceSymlinks(uevent); diff --git a/init/init.cpp b/init/init.cpp index 5e98707d2..ee3a84c29 100644 --- a/init/init.cpp +++ b/init/init.cpp @@ -16,27 +16,17 @@ #include "init.h" -#include #include -#include #include -#include -#include #include #include #include -#include -#include #include #include #include #include -#include -#include #include #include -#include -#include #include #include @@ -44,25 +34,22 @@ #include #include #include -#include #include #include #include #include -#include -#include #include #include -#include -#include "bootchart.h" #include "import_parser.h" #include "init_first_stage.h" #include "keychords.h" #include "log.h" #include "property_service.h" #include "reboot.h" +#include "security.h" +#include "selinux.h" #include "signal_handler.h" #include "ueventd.h" #include "util.h" @@ -73,14 +60,10 @@ using namespace std::string_literals; using android::base::boot_clock; using android::base::GetProperty; using android::base::Timer; -using android::base::unique_fd; namespace android { namespace init { -struct selabel_handle *sehandle; -struct selabel_handle *sehandle_prop; - static int property_triggers_enabled = 0; static char qemu[32]; @@ -277,194 +260,6 @@ static int wait_for_coldboot_done_action(const std::vector& args) { return 0; } -/* - * Writes 512 bytes of output from Hardware RNG (/dev/hw_random, backed - * by Linux kernel's hw_random framework) into Linux RNG's via /dev/urandom. - * Does nothing if Hardware RNG is not present. - * - * Since we don't yet trust the quality of Hardware RNG, these bytes are not - * mixed into the primary pool of Linux RNG and the entropy estimate is left - * unmodified. - * - * If the HW RNG device /dev/hw_random is present, we require that at least - * 512 bytes read from it are written into Linux RNG. QA is expected to catch - * devices/configurations where these I/O operations are blocking for a long - * time. We do not reboot or halt on failures, as this is a best-effort - * attempt. - */ -static int mix_hwrng_into_linux_rng_action(const std::vector& args) { - unique_fd hwrandom_fd( - TEMP_FAILURE_RETRY(open("/dev/hw_random", O_RDONLY | O_NOFOLLOW | O_CLOEXEC))); - if (hwrandom_fd == -1) { - if (errno == ENOENT) { - LOG(INFO) << "/dev/hw_random not found"; - // It's not an error to not have a Hardware RNG. - return 0; - } - PLOG(ERROR) << "Failed to open /dev/hw_random"; - return -1; - } - - unique_fd urandom_fd( - TEMP_FAILURE_RETRY(open("/dev/urandom", O_WRONLY | O_NOFOLLOW | O_CLOEXEC))); - if (urandom_fd == -1) { - PLOG(ERROR) << "Failed to open /dev/urandom"; - return -1; - } - - char buf[512]; - size_t total_bytes_written = 0; - while (total_bytes_written < sizeof(buf)) { - ssize_t chunk_size = - TEMP_FAILURE_RETRY(read(hwrandom_fd, buf, sizeof(buf) - total_bytes_written)); - if (chunk_size == -1) { - PLOG(ERROR) << "Failed to read from /dev/hw_random"; - return -1; - } else if (chunk_size == 0) { - LOG(ERROR) << "Failed to read from /dev/hw_random: EOF"; - return -1; - } - - chunk_size = TEMP_FAILURE_RETRY(write(urandom_fd, buf, chunk_size)); - if (chunk_size == -1) { - PLOG(ERROR) << "Failed to write to /dev/urandom"; - return -1; - } - total_bytes_written += chunk_size; - } - - LOG(INFO) << "Mixed " << total_bytes_written << " bytes from /dev/hw_random into /dev/urandom"; - return 0; -} - -static void security_failure() { - LOG(ERROR) << "Security failure..."; - panic(); -} - -static bool set_highest_available_option_value(std::string path, int min, int max) -{ - std::ifstream inf(path, std::fstream::in); - if (!inf) { - LOG(ERROR) << "Cannot open for reading: " << path; - return false; - } - - int current = max; - while (current >= min) { - // try to write out new value - std::string str_val = std::to_string(current); - std::ofstream of(path, std::fstream::out); - if (!of) { - LOG(ERROR) << "Cannot open for writing: " << path; - return false; - } - of << str_val << std::endl; - of.close(); - - // check to make sure it was recorded - inf.seekg(0); - std::string str_rec; - inf >> str_rec; - if (str_val.compare(str_rec) == 0) { - break; - } - current--; - } - inf.close(); - - if (current < min) { - LOG(ERROR) << "Unable to set minimum option value " << min << " in " << path; - return false; - } - return true; -} - -#define MMAP_RND_PATH "/proc/sys/vm/mmap_rnd_bits" -#define MMAP_RND_COMPAT_PATH "/proc/sys/vm/mmap_rnd_compat_bits" - -/* __attribute__((unused)) due to lack of mips support: see mips block - * in set_mmap_rnd_bits_action */ -static bool __attribute__((unused)) set_mmap_rnd_bits_min(int start, int min, bool compat) { - std::string path; - if (compat) { - path = MMAP_RND_COMPAT_PATH; - } else { - path = MMAP_RND_PATH; - } - - return set_highest_available_option_value(path, min, start); -} - -/* - * Set /proc/sys/vm/mmap_rnd_bits and potentially - * /proc/sys/vm/mmap_rnd_compat_bits to the maximum supported values. - * Returns -1 if unable to set these to an acceptable value. - * - * To support this sysctl, the following upstream commits are needed: - * - * d07e22597d1d mm: mmap: add new /proc tunable for mmap_base ASLR - * e0c25d958f78 arm: mm: support ARCH_MMAP_RND_BITS - * 8f0d3aa9de57 arm64: mm: support ARCH_MMAP_RND_BITS - * 9e08f57d684a x86: mm: support ARCH_MMAP_RND_BITS - * ec9ee4acd97c drivers: char: random: add get_random_long() - * 5ef11c35ce86 mm: ASLR: use get_random_long() - */ -static int set_mmap_rnd_bits_action(const std::vector& args) { -/* values are arch-dependent */ -#if defined(USER_MODE_LINUX) - /* uml does not support mmap_rnd_bits */ - return 0; -#elif defined(__aarch64__) - /* arm64 supports 18 - 33 bits depending on pagesize and VA_SIZE */ - if (set_mmap_rnd_bits_min(33, 24, false) - && set_mmap_rnd_bits_min(16, 16, true)) { - return 0; - } -#elif defined(__x86_64__) - /* x86_64 supports 28 - 32 bits */ - if (set_mmap_rnd_bits_min(32, 32, false) - && set_mmap_rnd_bits_min(16, 16, true)) { - return 0; - } -#elif defined(__arm__) || defined(__i386__) - /* check to see if we're running on 64-bit kernel */ - bool h64 = !access(MMAP_RND_COMPAT_PATH, F_OK); - /* supported 32-bit architecture must have 16 bits set */ - if (set_mmap_rnd_bits_min(16, 16, h64)) { - return 0; - } -#elif defined(__mips__) || defined(__mips64__) - // TODO: add mips support b/27788820 - return 0; -#else - LOG(ERROR) << "Unknown architecture"; -#endif - - LOG(ERROR) << "Unable to set adequate mmap entropy value!"; - security_failure(); - return -1; -} - -#define KPTR_RESTRICT_PATH "/proc/sys/kernel/kptr_restrict" -#define KPTR_RESTRICT_MINVALUE 2 -#define KPTR_RESTRICT_MAXVALUE 4 - -/* Set kptr_restrict to the highest available level. - * - * Aborts if unable to set this to an acceptable value. - */ -static int set_kptr_restrict_action(const std::vector& args) -{ - std::string path = KPTR_RESTRICT_PATH; - - if (!set_highest_available_option_value(path, KPTR_RESTRICT_MINVALUE, KPTR_RESTRICT_MAXVALUE)) { - LOG(ERROR) << "Unable to set adequate kptr_restrict value!"; - security_failure(); - } - return 0; -} - static int keychord_init_action(const std::vector& args) { keychord_init(); @@ -582,386 +377,6 @@ static void global_seccomp() { }); } -static void selinux_init_all_handles(void) -{ - sehandle = selinux_android_file_context_handle(); - selinux_android_set_sehandle(sehandle); - sehandle_prop = selinux_android_prop_context_handle(); -} - -enum selinux_enforcing_status { SELINUX_PERMISSIVE, SELINUX_ENFORCING }; - -static selinux_enforcing_status selinux_status_from_cmdline() { - selinux_enforcing_status status = SELINUX_ENFORCING; - - import_kernel_cmdline(false, [&](const std::string& key, const std::string& value, bool in_qemu) { - if (key == "androidboot.selinux" && value == "permissive") { - status = SELINUX_PERMISSIVE; - } - }); - - return status; -} - -static bool selinux_is_enforcing(void) -{ - if (ALLOW_PERMISSIVE_SELINUX) { - return selinux_status_from_cmdline() == SELINUX_ENFORCING; - } - return true; -} - -static int audit_callback(void *data, security_class_t /*cls*/, char *buf, size_t len) { - - property_audit_data *d = reinterpret_cast(data); - - if (!d || !d->name || !d->cr) { - LOG(ERROR) << "audit_callback invoked with null data arguments!"; - return 0; - } - - snprintf(buf, len, "property=%s pid=%d uid=%d gid=%d", d->name, - d->cr->pid, d->cr->uid, d->cr->gid); - return 0; -} - -/* - * Forks, executes the provided program in the child, and waits for the completion in the parent. - * Child's stderr is captured and logged using LOG(ERROR). - * - * Returns true if the child exited with status code 0, returns false otherwise. - */ -static bool fork_execve_and_wait_for_completion(const char* filename, char* const argv[], - char* const envp[]) { - // Create a pipe used for redirecting child process's output. - // * pipe_fds[0] is the FD the parent will use for reading. - // * pipe_fds[1] is the FD the child will use for writing. - int pipe_fds[2]; - if (pipe(pipe_fds) == -1) { - PLOG(ERROR) << "Failed to create pipe"; - return false; - } - - pid_t child_pid = fork(); - if (child_pid == -1) { - PLOG(ERROR) << "Failed to fork for " << filename; - return false; - } - - if (child_pid == 0) { - // fork succeeded -- this is executing in the child process - - // Close the pipe FD not used by this process - TEMP_FAILURE_RETRY(close(pipe_fds[0])); - - // Redirect stderr to the pipe FD provided by the parent - if (TEMP_FAILURE_RETRY(dup2(pipe_fds[1], STDERR_FILENO)) == -1) { - PLOG(ERROR) << "Failed to redirect stderr of " << filename; - _exit(127); - return false; - } - TEMP_FAILURE_RETRY(close(pipe_fds[1])); - - if (execve(filename, argv, envp) == -1) { - PLOG(ERROR) << "Failed to execve " << filename; - return false; - } - // Unreachable because execve will have succeeded and replaced this code - // with child process's code. - _exit(127); - return false; - } else { - // fork succeeded -- this is executing in the original/parent process - - // Close the pipe FD not used by this process - TEMP_FAILURE_RETRY(close(pipe_fds[1])); - - // Log the redirected output of the child process. - // It's unfortunate that there's no standard way to obtain an istream for a file descriptor. - // As a result, we're buffering all output and logging it in one go at the end of the - // invocation, instead of logging it as it comes in. - const int child_out_fd = pipe_fds[0]; - std::string child_output; - if (!android::base::ReadFdToString(child_out_fd, &child_output)) { - PLOG(ERROR) << "Failed to capture full output of " << filename; - } - TEMP_FAILURE_RETRY(close(child_out_fd)); - if (!child_output.empty()) { - // Log captured output, line by line, because LOG expects to be invoked for each line - std::istringstream in(child_output); - std::string line; - while (std::getline(in, line)) { - LOG(ERROR) << filename << ": " << line; - } - } - - // Wait for child to terminate - int status; - if (TEMP_FAILURE_RETRY(waitpid(child_pid, &status, 0)) != child_pid) { - PLOG(ERROR) << "Failed to wait for " << filename; - return false; - } - - if (WIFEXITED(status)) { - int status_code = WEXITSTATUS(status); - if (status_code == 0) { - return true; - } else { - LOG(ERROR) << filename << " exited with status " << status_code; - } - } else if (WIFSIGNALED(status)) { - LOG(ERROR) << filename << " killed by signal " << WTERMSIG(status); - } else if (WIFSTOPPED(status)) { - LOG(ERROR) << filename << " stopped by signal " << WSTOPSIG(status); - } else { - LOG(ERROR) << "waitpid for " << filename << " returned unexpected status: " << status; - } - - return false; - } -} - -static bool read_first_line(const char* file, std::string* line) { - line->clear(); - - std::string contents; - if (!android::base::ReadFileToString(file, &contents, true /* follow symlinks */)) { - return false; - } - std::istringstream in(contents); - std::getline(in, *line); - return true; -} - -static bool selinux_find_precompiled_split_policy(std::string* file) { - file->clear(); - - static constexpr const char precompiled_sepolicy[] = "/vendor/etc/selinux/precompiled_sepolicy"; - if (access(precompiled_sepolicy, R_OK) == -1) { - return false; - } - std::string actual_plat_id; - if (!read_first_line("/system/etc/selinux/plat_and_mapping_sepolicy.cil.sha256", - &actual_plat_id)) { - PLOG(INFO) << "Failed to read " - "/system/etc/selinux/plat_and_mapping_sepolicy.cil.sha256"; - return false; - } - std::string precompiled_plat_id; - if (!read_first_line("/vendor/etc/selinux/precompiled_sepolicy.plat_and_mapping.sha256", - &precompiled_plat_id)) { - PLOG(INFO) << "Failed to read " - "/vendor/etc/selinux/" - "precompiled_sepolicy.plat_and_mapping.sha256"; - return false; - } - if ((actual_plat_id.empty()) || (actual_plat_id != precompiled_plat_id)) { - return false; - } - - *file = precompiled_sepolicy; - return true; -} - -static bool selinux_get_vendor_mapping_version(std::string* plat_vers) { - if (!read_first_line("/vendor/etc/selinux/plat_sepolicy_vers.txt", plat_vers)) { - PLOG(ERROR) << "Failed to read /vendor/etc/selinux/plat_sepolicy_vers.txt"; - return false; - } - if (plat_vers->empty()) { - LOG(ERROR) << "No version present in plat_sepolicy_vers.txt"; - return false; - } - return true; -} - -static constexpr const char plat_policy_cil_file[] = "/system/etc/selinux/plat_sepolicy.cil"; - -static bool selinux_is_split_policy_device() { return access(plat_policy_cil_file, R_OK) != -1; } - -/* - * Loads SELinux policy split across platform/system and non-platform/vendor files. - * - * Returns true upon success, false otherwise (failure cause is logged). - */ -static bool selinux_load_split_policy() { - // IMPLEMENTATION NOTE: Split policy consists of three CIL files: - // * platform -- policy needed due to logic contained in the system image, - // * non-platform -- policy needed due to logic contained in the vendor image, - // * mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy - // with newer versions of platform policy. - // - // secilc is invoked to compile the above three policy files into a single monolithic policy - // file. This file is then loaded into the kernel. - - // Load precompiled policy from vendor image, if a matching policy is found there. The policy - // must match the platform policy on the system image. - std::string precompiled_sepolicy_file; - if (selinux_find_precompiled_split_policy(&precompiled_sepolicy_file)) { - android::base::unique_fd fd( - open(precompiled_sepolicy_file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY)); - if (fd != -1) { - if (selinux_android_load_policy_from_fd(fd, precompiled_sepolicy_file.c_str()) < 0) { - LOG(ERROR) << "Failed to load SELinux policy from " << precompiled_sepolicy_file; - return false; - } - return true; - } - } - // No suitable precompiled policy could be loaded - - LOG(INFO) << "Compiling SELinux policy"; - - // Determine the highest policy language version supported by the kernel - set_selinuxmnt("/sys/fs/selinux"); - int max_policy_version = security_policyvers(); - if (max_policy_version == -1) { - PLOG(ERROR) << "Failed to determine highest policy version supported by kernel"; - return false; - } - - // We store the output of the compilation on /dev because this is the most convenient tmpfs - // storage mount available this early in the boot sequence. - char compiled_sepolicy[] = "/dev/sepolicy.XXXXXX"; - android::base::unique_fd compiled_sepolicy_fd(mkostemp(compiled_sepolicy, O_CLOEXEC)); - if (compiled_sepolicy_fd < 0) { - PLOG(ERROR) << "Failed to create temporary file " << compiled_sepolicy; - return false; - } - - // Determine which mapping file to include - std::string vend_plat_vers; - if (!selinux_get_vendor_mapping_version(&vend_plat_vers)) { - return false; - } - std::string mapping_file("/system/etc/selinux/mapping/" + vend_plat_vers + ".cil"); - // clang-format off - const char* compile_args[] = { - "/system/bin/secilc", - plat_policy_cil_file, - "-M", "true", "-G", "-N", - // Target the highest policy language version supported by the kernel - "-c", std::to_string(max_policy_version).c_str(), - mapping_file.c_str(), - "/vendor/etc/selinux/nonplat_sepolicy.cil", - "-o", compiled_sepolicy, - // We don't care about file_contexts output by the compiler - "-f", "/sys/fs/selinux/null", // /dev/null is not yet available - nullptr}; - // clang-format on - - if (!fork_execve_and_wait_for_completion(compile_args[0], (char**)compile_args, (char**)ENV)) { - unlink(compiled_sepolicy); - return false; - } - unlink(compiled_sepolicy); - - LOG(INFO) << "Loading compiled SELinux policy"; - if (selinux_android_load_policy_from_fd(compiled_sepolicy_fd, compiled_sepolicy) < 0) { - LOG(ERROR) << "Failed to load SELinux policy from " << compiled_sepolicy; - return false; - } - - return true; -} - -/* - * Loads SELinux policy from a monolithic file. - * - * Returns true upon success, false otherwise (failure cause is logged). - */ -static bool selinux_load_monolithic_policy() { - LOG(VERBOSE) << "Loading SELinux policy from monolithic file"; - if (selinux_android_load_policy() < 0) { - PLOG(ERROR) << "Failed to load monolithic SELinux policy"; - return false; - } - return true; -} - -/* - * Loads SELinux policy into the kernel. - * - * Returns true upon success, false otherwise (failure cause is logged). - */ -static bool selinux_load_policy() { - return selinux_is_split_policy_device() ? selinux_load_split_policy() - : selinux_load_monolithic_policy(); -} - -static void selinux_initialize(bool in_kernel_domain) { - Timer t; - - selinux_callback cb; - cb.func_log = selinux_klog_callback; - selinux_set_callback(SELINUX_CB_LOG, cb); - cb.func_audit = audit_callback; - selinux_set_callback(SELINUX_CB_AUDIT, cb); - - if (in_kernel_domain) { - LOG(INFO) << "Loading SELinux policy"; - if (!selinux_load_policy()) { - panic(); - } - - bool kernel_enforcing = (security_getenforce() == 1); - bool is_enforcing = selinux_is_enforcing(); - if (kernel_enforcing != is_enforcing) { - if (security_setenforce(is_enforcing)) { - PLOG(ERROR) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false"); - security_failure(); - } - } - - std::string err; - if (!WriteFile("/sys/fs/selinux/checkreqprot", "0", &err)) { - LOG(ERROR) << err; - security_failure(); - } - - // init's first stage can't set properties, so pass the time to the second stage. - setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1); - } else { - selinux_init_all_handles(); - } -} - -// The files and directories that were created before initial sepolicy load or -// files on ramdisk need to have their security context restored to the proper -// value. This must happen before /dev is populated by ueventd. -static void selinux_restore_context() { - LOG(INFO) << "Running restorecon..."; - selinux_android_restorecon("/dev", 0); - selinux_android_restorecon("/dev/kmsg", 0); - if constexpr (WORLD_WRITABLE_KMSG) { - selinux_android_restorecon("/dev/kmsg_debug", 0); - } - selinux_android_restorecon("/dev/socket", 0); - selinux_android_restorecon("/dev/random", 0); - selinux_android_restorecon("/dev/urandom", 0); - selinux_android_restorecon("/dev/__properties__", 0); - - selinux_android_restorecon("/file_contexts.bin", 0); - selinux_android_restorecon("/plat_file_contexts", 0); - selinux_android_restorecon("/nonplat_file_contexts", 0); - selinux_android_restorecon("/plat_property_contexts", 0); - selinux_android_restorecon("/nonplat_property_contexts", 0); - selinux_android_restorecon("/plat_seapp_contexts", 0); - selinux_android_restorecon("/nonplat_seapp_contexts", 0); - selinux_android_restorecon("/plat_service_contexts", 0); - selinux_android_restorecon("/nonplat_service_contexts", 0); - selinux_android_restorecon("/plat_hwservice_contexts", 0); - selinux_android_restorecon("/nonplat_hwservice_contexts", 0); - selinux_android_restorecon("/sepolicy", 0); - selinux_android_restorecon("/vndservice_contexts", 0); - - selinux_android_restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE); - selinux_android_restorecon("/dev/device-mapper", 0); - - selinux_android_restorecon("/sbin/mke2fs_static", 0); - selinux_android_restorecon("/sbin/e2fsdroid_static", 0); -} - // Set the UDC controller for the ConfigFS USB Gadgets. // Read the UDC controller in use from "/sys/class/udc". // In case of multiple UDC controllers select the first one. @@ -1067,13 +482,14 @@ int main(int argc, char** argv) { global_seccomp(); // Set up SELinux, loading the SELinux policy. - selinux_initialize(true); + SelinuxSetupKernelLogging(); + SelinuxInitialize(); // We're in the kernel domain, so re-exec init to transition to the init domain now // that the SELinux policy has been loaded. if (selinux_android_restorecon("/init", 0) == -1) { - PLOG(ERROR) << "restorecon failed"; - security_failure(); + PLOG(ERROR) << "restorecon failed of /init failed"; + panic(); } setenv("INIT_SECOND_STAGE", "true", 1); @@ -1089,7 +505,7 @@ int main(int argc, char** argv) { // execv() only returns if an error happened, in which case we // panic and never fall through this conditional. PLOG(ERROR) << "execv(\"" << path << "\") failed"; - security_failure(); + panic(); } // At this point we're in the second stage of init. @@ -1130,8 +546,9 @@ int main(int argc, char** argv) { unsetenv("INIT_AVB_VERSION"); // Now set up SELinux for second stage. - selinux_initialize(false); - selinux_restore_context(); + SelinuxSetupKernelLogging(); + SelabelInitialize(); + SelinuxRestoreContext(); epoll_fd = epoll_create1(EPOLL_CLOEXEC); if (epoll_fd == -1) { @@ -1163,9 +580,9 @@ int main(int argc, char** argv) { // Queue an action that waits for coldboot done so we know ueventd has set up all of /dev... am.QueueBuiltinAction(wait_for_coldboot_done_action, "wait_for_coldboot_done"); // ... so that we can start queuing up actions that require stuff from /dev. - am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); - am.QueueBuiltinAction(set_mmap_rnd_bits_action, "set_mmap_rnd_bits"); - am.QueueBuiltinAction(set_kptr_restrict_action, "set_kptr_restrict"); + am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng"); + am.QueueBuiltinAction(SetMmapRndBitsAction, "SetMmapRndBits"); + am.QueueBuiltinAction(SetKptrRestrictAction, "SetKptrRestrict"); am.QueueBuiltinAction(keychord_init_action, "keychord_init"); am.QueueBuiltinAction(console_init_action, "console_init"); @@ -1174,7 +591,7 @@ int main(int argc, char** argv) { // Repeat mix_hwrng_into_linux_rng in case /dev/hw_random or /dev/random // wasn't ready immediately after wait_for_coldboot_done - am.QueueBuiltinAction(mix_hwrng_into_linux_rng_action, "mix_hwrng_into_linux_rng"); + am.QueueBuiltinAction(MixHwrngIntoLinuxRngAction, "MixHwrngIntoLinuxRng"); // Don't mount filesystems or start core system services in charger mode. std::string bootmode = GetProperty("ro.bootmode", ""); diff --git a/init/init.h b/init/init.h index 92b9b7003..50a7c8352 100644 --- a/init/init.h +++ b/init/init.h @@ -18,8 +18,7 @@ #define _INIT_INIT_H #include - -#include +#include #include "action.h" #include "parser.h" @@ -33,8 +32,6 @@ namespace init { // TODO: Have an Init class and remove all globals. extern const char *ENV[32]; extern std::string default_console; -extern struct selabel_handle *sehandle; -extern struct selabel_handle *sehandle_prop; extern std::vector late_import_paths; diff --git a/init/property_service.cpp b/init/property_service.cpp index fd14bd66f..bd42913b6 100644 --- a/init/property_service.cpp +++ b/init/property_service.cpp @@ -68,6 +68,8 @@ static int persistent_properties_loaded = 0; static int property_set_fd = -1; +static struct selabel_handle* sehandle_prop; + void property_init() { if (__system_property_area_init()) { LOG(ERROR) << "Failed to initialize property area"; @@ -740,11 +742,30 @@ void load_system_props() { load_recovery_id_prop(); } +static int SelinuxAuditCallback(void* data, security_class_t /*cls*/, char* buf, size_t len) { + property_audit_data* d = reinterpret_cast(data); + + if (!d || !d->name || !d->cr) { + LOG(ERROR) << "AuditCallback invoked with null data arguments!"; + return 0; + } + + snprintf(buf, len, "property=%s pid=%d uid=%d gid=%d", d->name, d->cr->pid, d->cr->uid, + d->cr->gid); + return 0; +} + void start_property_service() { + sehandle_prop = selinux_android_prop_context_handle(); + + selinux_callback cb; + cb.func_audit = SelinuxAuditCallback; + selinux_set_callback(SELINUX_CB_AUDIT, cb); + property_set("ro.property_service.version", "2"); property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, - false, 0666, 0, 0, nullptr, sehandle); + false, 0666, 0, 0, nullptr); if (property_set_fd == -1) { PLOG(ERROR) << "start_property_service socket creation failed"; exit(1); diff --git a/init/reboot.cpp b/init/reboot.cpp index cfd703ec1..32816fe40 100644 --- a/init/reboot.cpp +++ b/init/reboot.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include "capabilities.h" #include "init.h" diff --git a/init/security.cpp b/init/security.cpp new file mode 100644 index 000000000..45a5412bf --- /dev/null +++ b/init/security.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "security.h" + +#include +#include +#include + +#include + +#include +#include + +#include "util.h" + +using android::base::unique_fd; + +namespace android { +namespace init { + +// Writes 512 bytes of output from Hardware RNG (/dev/hw_random, backed +// by Linux kernel's hw_random framework) into Linux RNG's via /dev/urandom. +// Does nothing if Hardware RNG is not present. +// +// Since we don't yet trust the quality of Hardware RNG, these bytes are not +// mixed into the primary pool of Linux RNG and the entropy estimate is left +// unmodified. +// +// If the HW RNG device /dev/hw_random is present, we require that at least +// 512 bytes read from it are written into Linux RNG. QA is expected to catch +// devices/configurations where these I/O operations are blocking for a long +// time. We do not reboot or halt on failures, as this is a best-effort +// attempt. +int MixHwrngIntoLinuxRngAction(const std::vector& args) { + unique_fd hwrandom_fd( + TEMP_FAILURE_RETRY(open("/dev/hw_random", O_RDONLY | O_NOFOLLOW | O_CLOEXEC))); + if (hwrandom_fd == -1) { + if (errno == ENOENT) { + LOG(INFO) << "/dev/hw_random not found"; + // It's not an error to not have a Hardware RNG. + return 0; + } + PLOG(ERROR) << "Failed to open /dev/hw_random"; + return -1; + } + + unique_fd urandom_fd( + TEMP_FAILURE_RETRY(open("/dev/urandom", O_WRONLY | O_NOFOLLOW | O_CLOEXEC))); + if (urandom_fd == -1) { + PLOG(ERROR) << "Failed to open /dev/urandom"; + return -1; + } + + char buf[512]; + size_t total_bytes_written = 0; + while (total_bytes_written < sizeof(buf)) { + ssize_t chunk_size = + TEMP_FAILURE_RETRY(read(hwrandom_fd, buf, sizeof(buf) - total_bytes_written)); + if (chunk_size == -1) { + PLOG(ERROR) << "Failed to read from /dev/hw_random"; + return -1; + } else if (chunk_size == 0) { + LOG(ERROR) << "Failed to read from /dev/hw_random: EOF"; + return -1; + } + + chunk_size = TEMP_FAILURE_RETRY(write(urandom_fd, buf, chunk_size)); + if (chunk_size == -1) { + PLOG(ERROR) << "Failed to write to /dev/urandom"; + return -1; + } + total_bytes_written += chunk_size; + } + + LOG(INFO) << "Mixed " << total_bytes_written << " bytes from /dev/hw_random into /dev/urandom"; + return 0; +} + +static bool SetHighestAvailableOptionValue(std::string path, int min, int max) { + std::ifstream inf(path, std::fstream::in); + if (!inf) { + LOG(ERROR) << "Cannot open for reading: " << path; + return false; + } + + int current = max; + while (current >= min) { + // try to write out new value + std::string str_val = std::to_string(current); + std::ofstream of(path, std::fstream::out); + if (!of) { + LOG(ERROR) << "Cannot open for writing: " << path; + return false; + } + of << str_val << std::endl; + of.close(); + + // check to make sure it was recorded + inf.seekg(0); + std::string str_rec; + inf >> str_rec; + if (str_val.compare(str_rec) == 0) { + break; + } + current--; + } + inf.close(); + + if (current < min) { + LOG(ERROR) << "Unable to set minimum option value " << min << " in " << path; + return false; + } + return true; +} + +#define MMAP_RND_PATH "/proc/sys/vm/mmap_rnd_bits" +#define MMAP_RND_COMPAT_PATH "/proc/sys/vm/mmap_rnd_compat_bits" + +// __attribute__((unused)) due to lack of mips support: see mips block in SetMmapRndBitsAction +static bool __attribute__((unused)) SetMmapRndBitsMin(int start, int min, bool compat) { + std::string path; + if (compat) { + path = MMAP_RND_COMPAT_PATH; + } else { + path = MMAP_RND_PATH; + } + + return SetHighestAvailableOptionValue(path, min, start); +} + +// Set /proc/sys/vm/mmap_rnd_bits and potentially +// /proc/sys/vm/mmap_rnd_compat_bits to the maximum supported values. +// Returns -1 if unable to set these to an acceptable value. +// +// To support this sysctl, the following upstream commits are needed: +// +// d07e22597d1d mm: mmap: add new /proc tunable for mmap_base ASLR +// e0c25d958f78 arm: mm: support ARCH_MMAP_RND_BITS +// 8f0d3aa9de57 arm64: mm: support ARCH_MMAP_RND_BITS +// 9e08f57d684a x86: mm: support ARCH_MMAP_RND_BITS +// ec9ee4acd97c drivers: char: random: add get_random_long() +// 5ef11c35ce86 mm: ASLR: use get_random_long() +int SetMmapRndBitsAction(const std::vector& args) { +// values are arch-dependent +#if defined(USER_MODE_LINUX) + // uml does not support mmap_rnd_bits + return 0; +#elif defined(__aarch64__) + // arm64 supports 18 - 33 bits depending on pagesize and VA_SIZE + if (SetMmapRndBitsMin(33, 24, false) && SetMmapRndBitsMin(16, 16, true)) { + return 0; + } +#elif defined(__x86_64__) + // x86_64 supports 28 - 32 bits + if (SetMmapRndBitsMin(32, 32, false) && SetMmapRndBitsMin(16, 16, true)) { + return 0; + } +#elif defined(__arm__) || defined(__i386__) + // check to see if we're running on 64-bit kernel + bool h64 = !access(MMAP_RND_COMPAT_PATH, F_OK); + // supported 32-bit architecture must have 16 bits set + if (SetMmapRndBitsMin(16, 16, h64)) { + return 0; + } +#elif defined(__mips__) || defined(__mips64__) + // TODO: add mips support b/27788820 + return 0; +#else + LOG(ERROR) << "Unknown architecture"; +#endif + + LOG(ERROR) << "Unable to set adequate mmap entropy value!"; + panic(); + return -1; +} + +#define KPTR_RESTRICT_PATH "/proc/sys/kernel/kptr_restrict" +#define KPTR_RESTRICT_MINVALUE 2 +#define KPTR_RESTRICT_MAXVALUE 4 + +// Set kptr_restrict to the highest available level. +// +// Aborts if unable to set this to an acceptable value. +int SetKptrRestrictAction(const std::vector& args) { + std::string path = KPTR_RESTRICT_PATH; + + if (!SetHighestAvailableOptionValue(path, KPTR_RESTRICT_MINVALUE, KPTR_RESTRICT_MAXVALUE)) { + LOG(ERROR) << "Unable to set adequate kptr_restrict value!"; + panic(); + } + return 0; +} + +} // namespace init +} // namespace android diff --git a/init/security.h b/init/security.h new file mode 100644 index 000000000..c489de143 --- /dev/null +++ b/init/security.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _INIT_SECURITY_H +#define _INIT_SECURITY_H + +#include +#include + +namespace android { +namespace init { + +int MixHwrngIntoLinuxRngAction(const std::vector& args); +int SetMmapRndBitsAction(const std::vector& args); +int SetKptrRestrictAction(const std::vector& args); + +} // namespace init +} // namespace android + +#endif diff --git a/init/selinux.cpp b/init/selinux.cpp new file mode 100644 index 000000000..af5d2397f --- /dev/null +++ b/init/selinux.cpp @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// This file contains the functions that initialize SELinux during boot as well as helper functions +// for SELinux operation for init. + +// When the system boots, there is no SEPolicy present and init is running in the kernel domain. +// Init loads the SEPolicy from the file system, restores the context of /init based on this +// SEPolicy, and finally exec()'s itself to run in the proper domain. + +// The SEPolicy on Android comes in two variants: monolithic and split. + +// The monolithic policy variant is for legacy non-treble devices that contain a single SEPolicy +// file located at /sepolicy and is directly loaded into the kernel SELinux subsystem. + +// The split policy is for supporting treble devices. It splits the SEPolicy across files on +// /system/etc/selinux (the 'plat' portion of the policy) and /vendor/etc/selinux (the 'nonplat' +// portion of the policy). This is necessary to allow the system image to be updated independently +// of the vendor image, while maintaining contributions from both partitions in the SEPolicy. This +// is especially important for VTS testing, where the SEPolicy on the Google System Image may not be +// identical to the system image shipped on a vendor's device. + +// The split SEPolicy is loaded as described below: +// 1) There is a precompiled SEPolicy located at /vendor/etc/selinux/precompiled_sepolicy. +// Stored along with this file is the sha256 hash of the parts of the SEPolicy on /system that +// were used to compile this precompiled policy. The system partition contains a similar sha256 +// of the parts of the SEPolicy that it currently contains. If these two hashes match, then the +// system loads this precompiled_sepolicy directly. +// 2) If these hashes do not match, then /system has been updated out of sync with /vendor and the +// init needs to compile the SEPolicy. /system contains the SEPolicy compiler, secilc, and it +// is used by the LoadSplitPolicy() function below to compile the SEPolicy to a temp directory +// and load it. That function contains even more documentation with the specific implementation +// details of how the SEPolicy is compiled if needed. + +#include "selinux.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "log.h" +#include "util.h" + +using android::base::Timer; +using android::base::unique_fd; + +namespace android { +namespace init { + +static struct selabel_handle* sehandle = nullptr; + +namespace { + +enum EnforcingStatus { SELINUX_PERMISSIVE, SELINUX_ENFORCING }; + +EnforcingStatus StatusFromCmdline() { + EnforcingStatus status = SELINUX_ENFORCING; + + import_kernel_cmdline(false, + [&](const std::string& key, const std::string& value, bool in_qemu) { + if (key == "androidboot.selinux" && value == "permissive") { + status = SELINUX_PERMISSIVE; + } + }); + + return status; +} + +bool IsEnforcing() { + if (ALLOW_PERMISSIVE_SELINUX) { + return StatusFromCmdline() == SELINUX_ENFORCING; + } + return true; +} + +// Forks, executes the provided program in the child, and waits for the completion in the parent. +// Child's stderr is captured and logged using LOG(ERROR). +bool ForkExecveAndWaitForCompletion(const char* filename, char* const argv[]) { + // Create a pipe used for redirecting child process's output. + // * pipe_fds[0] is the FD the parent will use for reading. + // * pipe_fds[1] is the FD the child will use for writing. + int pipe_fds[2]; + if (pipe(pipe_fds) == -1) { + PLOG(ERROR) << "Failed to create pipe"; + return false; + } + + pid_t child_pid = fork(); + if (child_pid == -1) { + PLOG(ERROR) << "Failed to fork for " << filename; + return false; + } + + if (child_pid == 0) { + // fork succeeded -- this is executing in the child process + + // Close the pipe FD not used by this process + TEMP_FAILURE_RETRY(close(pipe_fds[0])); + + // Redirect stderr to the pipe FD provided by the parent + if (TEMP_FAILURE_RETRY(dup2(pipe_fds[1], STDERR_FILENO)) == -1) { + PLOG(ERROR) << "Failed to redirect stderr of " << filename; + _exit(127); + return false; + } + TEMP_FAILURE_RETRY(close(pipe_fds[1])); + + const char* envp[] = {_PATH_DEFPATH, nullptr}; + if (execve(filename, argv, (char**)envp) == -1) { + PLOG(ERROR) << "Failed to execve " << filename; + return false; + } + // Unreachable because execve will have succeeded and replaced this code + // with child process's code. + _exit(127); + return false; + } else { + // fork succeeded -- this is executing in the original/parent process + + // Close the pipe FD not used by this process + TEMP_FAILURE_RETRY(close(pipe_fds[1])); + + // Log the redirected output of the child process. + // It's unfortunate that there's no standard way to obtain an istream for a file descriptor. + // As a result, we're buffering all output and logging it in one go at the end of the + // invocation, instead of logging it as it comes in. + const int child_out_fd = pipe_fds[0]; + std::string child_output; + if (!android::base::ReadFdToString(child_out_fd, &child_output)) { + PLOG(ERROR) << "Failed to capture full output of " << filename; + } + TEMP_FAILURE_RETRY(close(child_out_fd)); + if (!child_output.empty()) { + // Log captured output, line by line, because LOG expects to be invoked for each line + std::istringstream in(child_output); + std::string line; + while (std::getline(in, line)) { + LOG(ERROR) << filename << ": " << line; + } + } + + // Wait for child to terminate + int status; + if (TEMP_FAILURE_RETRY(waitpid(child_pid, &status, 0)) != child_pid) { + PLOG(ERROR) << "Failed to wait for " << filename; + return false; + } + + if (WIFEXITED(status)) { + int status_code = WEXITSTATUS(status); + if (status_code == 0) { + return true; + } else { + LOG(ERROR) << filename << " exited with status " << status_code; + } + } else if (WIFSIGNALED(status)) { + LOG(ERROR) << filename << " killed by signal " << WTERMSIG(status); + } else if (WIFSTOPPED(status)) { + LOG(ERROR) << filename << " stopped by signal " << WSTOPSIG(status); + } else { + LOG(ERROR) << "waitpid for " << filename << " returned unexpected status: " << status; + } + + return false; + } +} + +bool ReadFirstLine(const char* file, std::string* line) { + line->clear(); + + std::string contents; + if (!android::base::ReadFileToString(file, &contents, true /* follow symlinks */)) { + return false; + } + std::istringstream in(contents); + std::getline(in, *line); + return true; +} + +bool FindPrecompiledSplitPolicy(std::string* file) { + file->clear(); + + static constexpr const char precompiled_sepolicy[] = "/vendor/etc/selinux/precompiled_sepolicy"; + if (access(precompiled_sepolicy, R_OK) == -1) { + return false; + } + std::string actual_plat_id; + if (!ReadFirstLine("/system/etc/selinux/plat_and_mapping_sepolicy.cil.sha256", &actual_plat_id)) { + PLOG(INFO) << "Failed to read " + "/system/etc/selinux/plat_and_mapping_sepolicy.cil.sha256"; + return false; + } + std::string precompiled_plat_id; + if (!ReadFirstLine("/vendor/etc/selinux/precompiled_sepolicy.plat_and_mapping.sha256", + &precompiled_plat_id)) { + PLOG(INFO) << "Failed to read " + "/vendor/etc/selinux/" + "precompiled_sepolicy.plat_and_mapping.sha256"; + return false; + } + if ((actual_plat_id.empty()) || (actual_plat_id != precompiled_plat_id)) { + return false; + } + + *file = precompiled_sepolicy; + return true; +} + +bool GetVendorMappingVersion(std::string* plat_vers) { + if (!ReadFirstLine("/vendor/etc/selinux/plat_sepolicy_vers.txt", plat_vers)) { + PLOG(ERROR) << "Failed to read /vendor/etc/selinux/plat_sepolicy_vers.txt"; + return false; + } + if (plat_vers->empty()) { + LOG(ERROR) << "No version present in plat_sepolicy_vers.txt"; + return false; + } + return true; +} + +constexpr const char plat_policy_cil_file[] = "/system/etc/selinux/plat_sepolicy.cil"; + +bool IsSplitPolicyDevice() { + return access(plat_policy_cil_file, R_OK) != -1; +} + +bool LoadSplitPolicy() { + // IMPLEMENTATION NOTE: Split policy consists of three CIL files: + // * platform -- policy needed due to logic contained in the system image, + // * non-platform -- policy needed due to logic contained in the vendor image, + // * mapping -- mapping policy which helps preserve forward-compatibility of non-platform policy + // with newer versions of platform policy. + // + // secilc is invoked to compile the above three policy files into a single monolithic policy + // file. This file is then loaded into the kernel. + + // Load precompiled policy from vendor image, if a matching policy is found there. The policy + // must match the platform policy on the system image. + std::string precompiled_sepolicy_file; + if (FindPrecompiledSplitPolicy(&precompiled_sepolicy_file)) { + unique_fd fd(open(precompiled_sepolicy_file.c_str(), O_RDONLY | O_CLOEXEC | O_BINARY)); + if (fd != -1) { + if (selinux_android_load_policy_from_fd(fd, precompiled_sepolicy_file.c_str()) < 0) { + LOG(ERROR) << "Failed to load SELinux policy from " << precompiled_sepolicy_file; + return false; + } + return true; + } + } + // No suitable precompiled policy could be loaded + + LOG(INFO) << "Compiling SELinux policy"; + + // Determine the highest policy language version supported by the kernel + set_selinuxmnt("/sys/fs/selinux"); + int max_policy_version = security_policyvers(); + if (max_policy_version == -1) { + PLOG(ERROR) << "Failed to determine highest policy version supported by kernel"; + return false; + } + + // We store the output of the compilation on /dev because this is the most convenient tmpfs + // storage mount available this early in the boot sequence. + char compiled_sepolicy[] = "/dev/sepolicy.XXXXXX"; + unique_fd compiled_sepolicy_fd(mkostemp(compiled_sepolicy, O_CLOEXEC)); + if (compiled_sepolicy_fd < 0) { + PLOG(ERROR) << "Failed to create temporary file " << compiled_sepolicy; + return false; + } + + // Determine which mapping file to include + std::string vend_plat_vers; + if (!GetVendorMappingVersion(&vend_plat_vers)) { + return false; + } + std::string mapping_file("/system/etc/selinux/mapping/" + vend_plat_vers + ".cil"); + // clang-format off + const char* compile_args[] = { + "/system/bin/secilc", + plat_policy_cil_file, + "-M", "true", "-G", "-N", + // Target the highest policy language version supported by the kernel + "-c", std::to_string(max_policy_version).c_str(), + mapping_file.c_str(), + "/vendor/etc/selinux/nonplat_sepolicy.cil", + "-o", compiled_sepolicy, + // We don't care about file_contexts output by the compiler + "-f", "/sys/fs/selinux/null", // /dev/null is not yet available + nullptr}; + // clang-format on + + if (!ForkExecveAndWaitForCompletion(compile_args[0], (char**)compile_args)) { + unlink(compiled_sepolicy); + return false; + } + unlink(compiled_sepolicy); + + LOG(INFO) << "Loading compiled SELinux policy"; + if (selinux_android_load_policy_from_fd(compiled_sepolicy_fd, compiled_sepolicy) < 0) { + LOG(ERROR) << "Failed to load SELinux policy from " << compiled_sepolicy; + return false; + } + + return true; +} + +bool LoadMonolithicPolicy() { + LOG(VERBOSE) << "Loading SELinux policy from monolithic file"; + if (selinux_android_load_policy() < 0) { + PLOG(ERROR) << "Failed to load monolithic SELinux policy"; + return false; + } + return true; +} + +bool LoadPolicy() { + return IsSplitPolicyDevice() ? LoadSplitPolicy() : LoadMonolithicPolicy(); +} + +} // namespace + +void SelinuxInitialize() { + Timer t; + + LOG(INFO) << "Loading SELinux policy"; + if (!LoadPolicy()) { + panic(); + } + + bool kernel_enforcing = (security_getenforce() == 1); + bool is_enforcing = IsEnforcing(); + if (kernel_enforcing != is_enforcing) { + if (security_setenforce(is_enforcing)) { + PLOG(ERROR) << "security_setenforce(%s) failed" << (is_enforcing ? "true" : "false"); + panic(); + } + } + + std::string err; + if (!WriteFile("/sys/fs/selinux/checkreqprot", "0", &err)) { + LOG(ERROR) << err; + panic(); + } + + // init's first stage can't set properties, so pass the time to the second stage. + setenv("INIT_SELINUX_TOOK", std::to_string(t.duration().count()).c_str(), 1); +} + +// The files and directories that were created before initial sepolicy load or +// files on ramdisk need to have their security context restored to the proper +// value. This must happen before /dev is populated by ueventd. +void SelinuxRestoreContext() { + LOG(INFO) << "Running restorecon..."; + selinux_android_restorecon("/dev", 0); + selinux_android_restorecon("/dev/kmsg", 0); + if constexpr (WORLD_WRITABLE_KMSG) { + selinux_android_restorecon("/dev/kmsg_debug", 0); + } + selinux_android_restorecon("/dev/socket", 0); + selinux_android_restorecon("/dev/random", 0); + selinux_android_restorecon("/dev/urandom", 0); + selinux_android_restorecon("/dev/__properties__", 0); + + selinux_android_restorecon("/file_contexts.bin", 0); + selinux_android_restorecon("/plat_file_contexts", 0); + selinux_android_restorecon("/nonplat_file_contexts", 0); + selinux_android_restorecon("/plat_property_contexts", 0); + selinux_android_restorecon("/nonplat_property_contexts", 0); + selinux_android_restorecon("/plat_seapp_contexts", 0); + selinux_android_restorecon("/nonplat_seapp_contexts", 0); + selinux_android_restorecon("/plat_service_contexts", 0); + selinux_android_restorecon("/nonplat_service_contexts", 0); + selinux_android_restorecon("/plat_hwservice_contexts", 0); + selinux_android_restorecon("/nonplat_hwservice_contexts", 0); + selinux_android_restorecon("/sepolicy", 0); + selinux_android_restorecon("/vndservice_contexts", 0); + + selinux_android_restorecon("/dev/block", SELINUX_ANDROID_RESTORECON_RECURSE); + selinux_android_restorecon("/dev/device-mapper", 0); + + selinux_android_restorecon("/sbin/mke2fs_static", 0); + selinux_android_restorecon("/sbin/e2fsdroid_static", 0); +} + +// This function sets up SELinux logging to be written to kmsg, to match init's logging. +void SelinuxSetupKernelLogging() { + selinux_callback cb; + cb.func_log = selinux_klog_callback; + selinux_set_callback(SELINUX_CB_LOG, cb); +} + +// selinux_android_file_context_handle() takes on the order of 10+ms to run, so we want to cache +// its value. selinux_android_restorecon() also needs an sehandle for file context look up. It +// will create and store its own copy, but selinux_android_set_sehandle() can be used to provide +// one, thus eliminating an extra call to selinux_android_file_context_handle(). +void SelabelInitialize() { + sehandle = selinux_android_file_context_handle(); + selinux_android_set_sehandle(sehandle); +} + +// A C++ wrapper around selabel_lookup() using the cached sehandle. +// If sehandle is null, this returns success with an empty context. +bool SelabelLookupFileContext(const std::string& key, int type, std::string* result) { + result->clear(); + + if (!sehandle) return true; + + char* context; + if (selabel_lookup(sehandle, &context, key.c_str(), type) != 0) { + return false; + } + *result = context; + free(context); + return true; +} + +// A C++ wrapper around selabel_lookup_best_match() using the cached sehandle. +// If sehandle is null, this returns success with an empty context. +bool SelabelLookupFileContextBestMatch(const std::string& key, + const std::vector& aliases, int type, + std::string* result) { + result->clear(); + + if (!sehandle) return true; + + std::vector c_aliases; + for (const auto& alias : aliases) { + c_aliases.emplace_back(alias.c_str()); + } + c_aliases.emplace_back(nullptr); + + char* context; + if (selabel_lookup_best_match(sehandle, &context, key.c_str(), &c_aliases[0], type) != 0) { + return false; + } + *result = context; + free(context); + return true; +} + +} // namespace init +} // namespace android diff --git a/init/selinux.h b/init/selinux.h new file mode 100644 index 000000000..7b880eccc --- /dev/null +++ b/init/selinux.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _INIT_SELINUX_H +#define _INIT_SELINUX_H + +#include +#include + +namespace android { +namespace init { + +void SelinuxInitialize(); +void SelinuxRestoreContext(); + +void SelinuxSetupKernelLogging(); + +void SelabelInitialize(); +bool SelabelLookupFileContext(const std::string& key, int type, std::string* result); +bool SelabelLookupFileContextBestMatch(const std::string& key, + const std::vector& aliases, int type, + std::string* result); + +} // namespace init +} // namespace android + +#endif diff --git a/init/ueventd.cpp b/init/ueventd.cpp index c0eae1e9a..b71945acc 100644 --- a/init/ueventd.cpp +++ b/init/ueventd.cpp @@ -36,6 +36,7 @@ #include "devices.h" #include "firmware_handler.h" #include "log.h" +#include "selinux.h" #include "uevent_listener.h" #include "ueventd_parser.h" #include "util.h" @@ -257,9 +258,8 @@ int ueventd_main(int argc, char** argv) { LOG(INFO) << "ueventd started!"; - selinux_callback cb; - cb.func_log = selinux_klog_callback; - selinux_set_callback(SELINUX_CB_LOG, cb); + SelinuxSetupKernelLogging(); + SelabelInitialize(); DeviceHandler device_handler = CreateDeviceHandler(); UeventListener uevent_listener; diff --git a/init/util.cpp b/init/util.cpp index fdcb22d1c..e0379876d 100644 --- a/init/util.cpp +++ b/init/util.cpp @@ -42,6 +42,7 @@ #include #include "reboot.h" +#include "selinux.h" #ifdef _INIT_INIT_H #error "Do not include init.h in files used by ueventd or watchdogd; it will expose init's globals" @@ -89,7 +90,7 @@ bool DecodeUid(const std::string& name, uid_t* uid, std::string* err) { * variable ANDROID_SOCKET_ENV_PREFIX ("ANDROID_SOCKET_foo"). */ int CreateSocket(const char* name, int type, bool passcred, mode_t perm, uid_t uid, gid_t gid, - const char* socketcon, selabel_handle* sehandle) { + const char* socketcon) { if (socketcon) { if (setsockcreatecon(socketcon) == -1) { PLOG(ERROR) << "setsockcreatecon(\"" << socketcon << "\") failed"; @@ -116,11 +117,9 @@ int CreateSocket(const char* name, int type, bool passcred, mode_t perm, uid_t u return -1; } - char *filecon = NULL; - if (sehandle) { - if (selabel_lookup(sehandle, &filecon, addr.sun_path, S_IFSOCK) == 0) { - setfscreatecon(filecon); - } + std::string secontext; + if (SelabelLookupFileContext(addr.sun_path, S_IFSOCK, &secontext) && !secontext.empty()) { + setfscreatecon(secontext.c_str()); } if (passcred) { @@ -134,8 +133,9 @@ int CreateSocket(const char* name, int type, bool passcred, mode_t perm, uid_t u int ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr)); int savederrno = errno; - setfscreatecon(NULL); - freecon(filecon); + if (!secontext.empty()) { + setfscreatecon(nullptr); + } if (ret) { errno = savederrno; @@ -210,19 +210,19 @@ bool WriteFile(const std::string& path, const std::string& content, std::string* return true; } -int mkdir_recursive(const std::string& path, mode_t mode, selabel_handle* sehandle) { +bool mkdir_recursive(const std::string& path, mode_t mode) { std::string::size_type slash = 0; while ((slash = path.find('/', slash + 1)) != std::string::npos) { auto directory = path.substr(0, slash); struct stat info; if (stat(directory.c_str(), &info) != 0) { - auto ret = make_dir(directory.c_str(), mode, sehandle); - if (ret && errno != EEXIST) return ret; + auto ret = make_dir(directory, mode); + if (!ret && errno != EEXIST) return false; } } - auto ret = make_dir(path.c_str(), mode, sehandle); - if (ret && errno != EEXIST) return ret; - return 0; + auto ret = make_dir(path, mode); + if (!ret && errno != EEXIST) return false; + return true; } int wait_for_file(const char* filename, std::chrono::nanoseconds timeout) { @@ -249,26 +249,21 @@ void import_kernel_cmdline(bool in_qemu, } } -int make_dir(const char* path, mode_t mode, selabel_handle* sehandle) { - int rc; - - char *secontext = NULL; - - if (sehandle) { - selabel_lookup(sehandle, &secontext, path, mode); - setfscreatecon(secontext); +bool make_dir(const std::string& path, mode_t mode) { + std::string secontext; + if (SelabelLookupFileContext(path, mode, &secontext) && !secontext.empty()) { + setfscreatecon(secontext.c_str()); } - rc = mkdir(path, mode); + int rc = mkdir(path.c_str(), mode); - if (secontext) { + if (!secontext.empty()) { int save_errno = errno; - freecon(secontext); - setfscreatecon(NULL); + setfscreatecon(nullptr); errno = save_errno; } - return rc; + return rc == 0; } /* diff --git a/init/util.h b/init/util.h index 29c10cba2..a81df478a 100644 --- a/init/util.h +++ b/init/util.h @@ -37,18 +37,18 @@ namespace android { namespace init { int CreateSocket(const char* name, int type, bool passcred, mode_t perm, uid_t uid, gid_t gid, - const char* socketcon, selabel_handle* sehandle); + const char* socketcon); bool ReadFile(const std::string& path, std::string* content, std::string* err); bool WriteFile(const std::string& path, const std::string& content, std::string* err); bool DecodeUid(const std::string& name, uid_t* uid, std::string* err); -int mkdir_recursive(const std::string& pathname, mode_t mode, selabel_handle* sehandle); +bool mkdir_recursive(const std::string& pathname, mode_t mode); int wait_for_file(const char *filename, std::chrono::nanoseconds timeout); void import_kernel_cmdline(bool in_qemu, const std::function&); -int make_dir(const char* path, mode_t mode, selabel_handle* sehandle); +bool make_dir(const std::string& path, mode_t mode); std::string bytes_to_hex(const uint8_t *bytes, size_t bytes_len); bool is_dir(const char* pathname); bool expand_props(const std::string& src, std::string* dst); diff --git a/init/util_test.cpp b/init/util_test.cpp index c16ab7431..5dd271cd0 100644 --- a/init/util_test.cpp +++ b/init/util_test.cpp @@ -170,7 +170,7 @@ TEST(util, is_dir) { TEST(util, mkdir_recursive) { TemporaryDir test_dir; std::string path = android::base::StringPrintf("%s/three/directories/deep", test_dir.path); - EXPECT_EQ(0, mkdir_recursive(path, 0755, nullptr)); + EXPECT_TRUE(mkdir_recursive(path, 0755)); std::string path1 = android::base::StringPrintf("%s/three", test_dir.path); EXPECT_TRUE(is_dir(path1.c_str())); std::string path2 = android::base::StringPrintf("%s/three/directories", test_dir.path); @@ -182,7 +182,7 @@ TEST(util, mkdir_recursive) { TEST(util, mkdir_recursive_extra_slashes) { TemporaryDir test_dir; std::string path = android::base::StringPrintf("%s/three////directories/deep//", test_dir.path); - EXPECT_EQ(0, mkdir_recursive(path, 0755, nullptr)); + EXPECT_TRUE(mkdir_recursive(path, 0755)); std::string path1 = android::base::StringPrintf("%s/three", test_dir.path); EXPECT_TRUE(is_dir(path1.c_str())); std::string path2 = android::base::StringPrintf("%s/three/directories", test_dir.path);