init: split security functions out of init.cpp
This change splits out the selinux initialization and supporting
functionality into selinux.cpp and splits the security related
initialization of the rng, etc to security.cpp. It also provides
additional documentation for SEPolicy loading as this has been
requested by some teams.
It additionally cleans up sehandle and sehandle_prop. The former is
static within selinux.cpp and new wrapper functions are created around
selabel_lookup*() to better serve the users. The latter is moved to
property_service.cpp as it is isolated to that file for its usage.
Test: boot bullhead
Merged-In: Idc95d493cebc681fbe686b5160502f36af149f60
Change-Id: Idc95d493cebc681fbe686b5160502f36af149f60
diff --git a/init/Android.bp b/init/Android.bp
index 432c298..db64f71 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -70,6 +70,8 @@
"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 eea78fd..944fcee 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -225,23 +225,22 @@
return insmod(filename.c_str(), options.c_str(), flags);
}
+// mkdir <path> [mode] [owner] [group]
static int do_mkdir(const std::vector<std::string>& args) {
mode_t mode = 0755;
- int ret;
-
- /* mkdir <path> [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 @@
/* 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 @@
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 @@
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 0cb639a..cc5b948 100644
--- a/init/descriptors.cpp
+++ b/init/descriptors.cpp
@@ -86,8 +86,7 @@
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 13cf991..d59f53c 100644
--- a/init/devices.cpp
+++ b/init/devices.cpp
@@ -30,6 +30,7 @@
#include <selinux/android.h>
#include <selinux/selinux.h>
+#include "selinux.h"
#include "ueventd.h"
#include "util.h"
@@ -224,18 +225,13 @@
auto[mode, uid, gid] = GetDevicePermissions(path, links);
mode |= (block ? S_IFBLK : S_IFCHR);
- char* secontext = nullptr;
- if (sehandle_) {
- std::vector<const char*> 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 @@
}
/* 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 @@
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 @@
PLOG(FATAL) << "setegid(AID_ROOT) failed";
}
- if (secontext) {
- freecon(secontext);
+ if (!secontext.empty()) {
setfscreatecon(nullptr);
}
}
@@ -351,7 +346,7 @@
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 @@
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 @@
: 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 c64f5fb..dd44337 100644
--- a/init/devices.h
+++ b/init/devices.h
@@ -124,7 +124,6 @@
std::vector<Permissions> dev_permissions_;
std::vector<SysfsPermissions> sysfs_permissions_;
std::vector<Subsystem> 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 ac4ab9b..eba00cb 100644
--- a/init/devices_test.cpp
+++ b/init/devices_test.cpp
@@ -35,13 +35,13 @@
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<std::string> result;
result = device_handler_.GetBlockDeviceSymlinks(uevent);
diff --git a/init/init.cpp b/init/init.cpp
index 63a993f..ee3a84c 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -16,27 +16,17 @@
#include "init.h"
-#include <ctype.h>
#include <dirent.h>
-#include <errno.h>
#include <fcntl.h>
-#include <inttypes.h>
-#include <libgen.h>
#include <paths.h>
#include <seccomp_policy.h>
#include <signal.h>
-#include <stdarg.h>
-#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/mount.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
-#include <sys/un.h>
-#include <sys/wait.h>
#include <unistd.h>
#include <android-base/chrono_utils.h>
@@ -44,25 +34,22 @@
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
#include <keyutils.h>
#include <libavb/libavb.h>
#include <private/android_filesystem_config.h>
#include <selinux/android.h>
-#include <selinux/selinux.h>
-#include <fstream>
#include <memory>
#include <optional>
-#include <vector>
-#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 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 @@
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<std::string>& 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<std::string>& 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<std::string>& 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<std::string>& args)
{
keychord_init();
@@ -582,355 +377,6 @@
});
}
-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<property_audit_data*>(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 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;
- }
-
- // clang-format off
- const char* compile_args[] = {
- "/system/bin/secilc",
- plat_policy_cil_file,
- "-M", "true",
- // Target the highest policy language version supported by the kernel
- "-c", std::to_string(max_policy_version).c_str(),
- "/system/etc/selinux/mapping_sepolicy.cil",
- "/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("/plat_property_contexts", 0);
- selinux_android_restorecon("/nonplat_property_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.
@@ -1036,13 +482,14 @@
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);
@@ -1058,7 +505,7 @@
// 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.
@@ -1099,8 +546,9 @@
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) {
@@ -1132,9 +580,9 @@
// 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");
@@ -1143,7 +591,7 @@
// 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 92b9b70..50a7c83 100644
--- a/init/init.h
+++ b/init/init.h
@@ -18,8 +18,7 @@
#define _INIT_INIT_H
#include <string>
-
-#include <selinux/label.h>
+#include <vector>
#include "action.h"
#include "parser.h"
@@ -33,8 +32,6 @@
// 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<std::string> late_import_paths;
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 925cc9b..1db8cb7 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -68,6 +68,8 @@
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";
@@ -733,11 +735,30 @@
load_recovery_id_prop();
}
+static int SelinuxAuditCallback(void* data, security_class_t /*cls*/, char* buf, size_t len) {
+ property_audit_data* d = reinterpret_cast<property_audit_data*>(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 cfd703e..32816fe 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -48,6 +48,7 @@
#include <fs_mgr.h>
#include <logwrap/logwrap.h>
#include <private/android_filesystem_config.h>
+#include <selinux/selinux.h>
#include "capabilities.h"
#include "init.h"
diff --git a/init/security.cpp b/init/security.cpp
new file mode 100644
index 0000000..45a5412
--- /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 <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include <fstream>
+
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+
+#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<std::string>& 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<std::string>& 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<std::string>& 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 0000000..c489de1
--- /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 <string>
+#include <vector>
+
+namespace android {
+namespace init {
+
+int MixHwrngIntoLinuxRngAction(const std::vector<std::string>& args);
+int SetMmapRndBitsAction(const std::vector<std::string>& args);
+int SetKptrRestrictAction(const std::vector<std::string>& args);
+
+} // namespace init
+} // namespace android
+
+#endif
diff --git a/init/selinux.cpp b/init/selinux.cpp
new file mode 100644
index 0000000..3a4a715
--- /dev/null
+++ b/init/selinux.cpp
@@ -0,0 +1,432 @@
+/*
+ * 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 <fcntl.h>
+#include <paths.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <android-base/chrono_utils.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <selinux/android.h>
+
+#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;
+}
+
+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;
+ }
+
+ // clang-format off
+ const char* compile_args[] = {
+ "/system/bin/secilc",
+ plat_policy_cil_file,
+ "-M", "true",
+ // Target the highest policy language version supported by the kernel
+ "-c", std::to_string(max_policy_version).c_str(),
+ "/system/etc/selinux/mapping_sepolicy.cil",
+ "/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("/plat_property_contexts", 0);
+ selinux_android_restorecon("/nonplat_property_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<std::string>& aliases, int type,
+ std::string* result) {
+ result->clear();
+
+ if (!sehandle) return true;
+
+ std::vector<const char*> 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 0000000..7b880ec
--- /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 <string>
+#include <vector>
+
+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<std::string>& aliases, int type,
+ std::string* result);
+
+} // namespace init
+} // namespace android
+
+#endif
diff --git a/init/ueventd.cpp b/init/ueventd.cpp
index c0eae1e..b71945a 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 @@
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 fdcb22d..e037987 100644
--- a/init/util.cpp
+++ b/init/util.cpp
@@ -42,6 +42,7 @@
#include <selinux/android.h>
#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 @@
* variable ANDROID_SOCKET_ENV_PREFIX<name> ("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 @@
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 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 @@
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 @@
}
}
-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 29c10cb..a81df47 100644
--- a/init/util.h
+++ b/init/util.h
@@ -37,18 +37,18 @@
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<void(const std::string&, const std::string&, bool)>&);
-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 c16ab74..5dd271c 100644
--- a/init/util_test.cpp
+++ b/init/util_test.cpp
@@ -170,7 +170,7 @@
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_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);