blob: c733545f5bbc728e28ee915213d791fda7103ae9 [file] [log] [blame]
// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "arc/setup/art_container.h"
#include <stdlib.h>
#include <sys/mount.h>
#include <unistd.h>
#include <memory>
#include <random>
#include <string>
#include <utility>
#include <vector>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/process/process.h>
#include <chromeos/libminijail.h>
#include <scoped_minijail.h>
namespace arc {
namespace {
constexpr char kAndroidRootfs[] =
"/opt/google/containers/arc-art/mountpoints/container-root";
constexpr char kAndroidVendor[] =
"/opt/google/containers/arc-art/mountpoints/vendor";
constexpr char kLibLogStderrName[] = "liblog_stderr.so";
// Variables defined in Android <pi-arc>/art/build/art.go
constexpr uint32_t kArtBaseAddressMaxDelta = 0x1000000;
constexpr uint32_t kArtBaseAddressMinDelta = -0x1000000;
constexpr char kArtDevRootfsDirectory[] =
"/opt/google/containers/arc-art/mountpoints/dev-rootfs";
constexpr char kArtDevRootfsImage[] =
"/opt/google/containers/arc-art/dev-rootfs.squashfs";
constexpr char kArtRootfsDirectory[] =
"/opt/google/containers/arc-art/mountpoints/container-root";
// TODO(xzhou): Use sysconf(_SC_PAGESIZE).
constexpr uint32_t kPageSize = 4096;
constexpr char kPatchOat[] = "/system/bin/patchoat";
constexpr char kPidFile[] = "/run/patchoat.pid";
constexpr char kSystemImage[] = "/opt/google/containers/android/system.raw.img";
constexpr char kVendorImage[] = "/opt/google/containers/android/vendor.raw.img";
// Ported from Andriod nyc
template <typename T>
struct Identity {
using type = T;
};
#define CHECK_CONSTEXPR(x, out, dummy) \
(UNLIKELY(!(x))) ? (LOG(ERROR) << "Check failed: " << #x out, dummy):
#define DCHECK_CONSTEXPR(x, out, dummy) CHECK_CONSTEXPR(x, out, dummy)
template <typename T>
static constexpr int CLZ(T x) {
static_assert(std::is_integral<T>::value, "T must be integral");
static_assert(std::is_unsigned<T>::value, "T must be unsigned");
static_assert(sizeof(T) <= sizeof(long long), // NOLINT [runtime/int] [4]
"T too large, must be smaller than long long");
return DCHECK_CONSTEXPR(x != 0, "x must not be zero",
T(0))(sizeof(T) == sizeof(uint32_t))
? __builtin_clz(x)
: __builtin_clzll(x);
}
template <typename T>
static constexpr bool IsPowerOfTwo(T x) {
static_assert(std::is_integral<T>::value, "T must be integral");
return (x & (x - 1)) == 0;
}
// For rounding integers.
template <typename T>
static constexpr T RoundDown(T x, typename Identity<T>::type n) {
return DCHECK_CONSTEXPR(IsPowerOfTwo(n), , T(0))(x & -n);
}
template <typename T>
static constexpr T RoundUp(T x, typename std::remove_reference<T>::type n) {
return RoundDown(x + n - 1, n);
}
template <int n, typename T>
static constexpr bool IsAligned(T x) {
static_assert((n & (n - 1)) == 0, "n is not a power of two");
return (x & (n - 1)) == 0;
}
#define CHECK_ALIGNED(value, alignment) \
CHECK(IsAligned<alignment>(value)) << reinterpret_cast<const void*>(value)
template <typename T>
T GetRandomNumber(T min, T max) {
CHECK_LT(min, max);
std::uniform_int_distribution<T> dist(min, max);
std::random_device rng;
return dist(rng);
}
int32_t GetOffset(int32_t min, int32_t max, uint64_t offset_seed) {
CHECK_LT(min, max);
const uint32_t range = max - min;
const uint32_t offset = offset_seed % range;
CHECK_LT(offset, range);
return min + offset;
}
int32_t ChooseRelocationOffsetDelta(int32_t min_delta,
int32_t max_delta,
uint64_t offset_seed) {
CHECK_ALIGNED(min_delta, kPageSize);
CHECK_ALIGNED(max_delta, kPageSize);
CHECK_LT(min_delta, max_delta);
int32_t r = offset_seed ? GetOffset(min_delta, max_delta, offset_seed)
: GetRandomNumber(min_delta, max_delta);
if (r % 2 == 0) {
r = RoundUp(r, kPageSize);
} else {
r = RoundDown(r, kPageSize);
}
CHECK_LE(min_delta, r);
CHECK_GE(max_delta, r);
CHECK_ALIGNED(r, kPageSize);
return r;
}
} // namespace
struct ArtContainerPaths {
const base::FilePath art_dalvik_cache_directory{kArtDalvikCacheDirectory};
const base::FilePath art_dev_rootfs_directory{kArtDevRootfsDirectory};
const base::FilePath art_rootfs_directory{kArtRootfsDirectory};
const base::FilePath vendor_directory{kAndroidVendor};
};
// static
std::unique_ptr<ArtContainer> ArtContainer::CreateContainer(
ArcMounter* mounter, AndroidSdkVersion sdk_version) {
auto art_paths = std::make_unique<ArtContainerPaths>();
// Make sure the data directory exits.
CHECK(base::PathExists(art_paths->art_dalvik_cache_directory));
return std::unique_ptr<ArtContainer>(
new ArtContainer(mounter, std::move(art_paths), sdk_version));
}
// static
std::vector<std::string> ArtContainer::GetIsas() {
std::vector<std::string> isas;
for (const std::string& arch : {"arm", "arm64", "x86", "x86_64"}) {
if (base::DirectoryExists(base::FilePath(kFrameworkPath).Append(arch)))
isas.push_back(arch);
}
return isas;
}
ArtContainer::ArtContainer(ArcMounter* mounter,
std::unique_ptr<ArtContainerPaths> art_paths,
AndroidSdkVersion sdk_version)
: mounter_(mounter),
art_paths_(std::move(art_paths)),
sdk_version_(sdk_version) {}
ArtContainer::~ArtContainer() = default;
bool ArtContainer::PatchImage(uint64_t offset_seed) {
int pid = fork();
if (pid == -1) {
PLOG(ERROR) << "Failed to fork()";
return false;
}
if (pid == 0) {
// Avoid doing any cleanup in the child process. This avoids problems where
// an atexit(3) handler (if any) that can modify global state (e.g. mounts)
// is called before it is expected (i.e. when main() returns in the parent).
_exit(PatchImageChild(offset_seed) ? EXIT_SUCCESS : EXIT_FAILURE);
}
base::Process process(pid);
int exit_code = -1;
if (!process.WaitForExit(&exit_code)) {
PLOG(ERROR) << "Failed to wait for the ART container";
return false;
}
if (exit_code != EXIT_SUCCESS) {
LOG(ERROR) << "The ART container exited with non-zero code: " << exit_code;
return false;
}
return true;
}
bool ArtContainer::PatchImageChild(uint64_t offset_seed) {
{
// Enter an intermediate mount namespace to avoid leaking mounts.
ScopedMinijail art_jail(minijail_new());
if (!art_jail)
return false;
minijail_namespace_vfs(art_jail.get());
minijail_enter(art_jail.get());
}
// Mount rootfs, /vendor, /dev, /data for ART container.
// TODO(xzhou): Simplify using minijail mounts.
std::unique_ptr<ScopedMount> art_rootfs = ScopedMount::CreateScopedLoopMount(
mounter_, kSystemImage, art_paths_->art_rootfs_directory,
MS_RDONLY | MS_NOSUID | MS_NODEV);
if (!art_rootfs)
return false;
std::unique_ptr<ScopedMount> loop_vendor = ScopedMount::CreateScopedLoopMount(
mounter_, kVendorImage, art_paths_->vendor_directory,
MS_RDONLY | MS_NOEXEC | MS_NOSUID);
if (!loop_vendor)
return false;
std::unique_ptr<ScopedMount> art_vendor = ScopedMount::CreateScopedBindMount(
mounter_, art_paths_->vendor_directory,
art_paths_->art_rootfs_directory.Append("vendor"));
if (!art_vendor)
return false;
// Patchoat did not map /dev/ashmem with PROT_EXEC and using MS_NOEXEC here.
std::unique_ptr<ScopedMount> loop_dev = ScopedMount::CreateScopedLoopMount(
mounter_, kArtDevRootfsImage, art_paths_->art_dev_rootfs_directory,
MS_RDONLY | MS_NOEXEC | MS_NOSUID);
if (!loop_dev)
return false;
std::unique_ptr<ScopedMount> art_dev = ScopedMount::CreateScopedBindMount(
mounter_, art_paths_->art_dev_rootfs_directory.Append("dev"),
art_paths_->art_rootfs_directory.Append("dev"));
if (!art_dev)
return false;
// Bind mount /dev/ashmem from host to art container. The minor number of
// ashmem is dynamic, we overwrite the one in art_dev.
std::unique_ptr<ScopedMount> ashmem = ScopedMount::CreateScopedBindMount(
mounter_, base::FilePath("/dev/ashmem"),
art_paths_->art_rootfs_directory.Append("dev/ashmem"));
if (!ashmem)
return false;
std::unique_ptr<ScopedMount> art_data = ScopedMount::CreateScopedBindMount(
mounter_, art_paths_->art_dalvik_cache_directory.DirName(),
art_paths_->art_rootfs_directory.Append("data"));
if (!art_data)
return false;
for (const std::string& isa : ArtContainer::GetIsas()) {
if (!PatchImage(isa, offset_seed))
return false;
}
return true;
}
bool ArtContainer::PatchImage(const std::string& isa, uint64_t offset_seed) {
// Remove outdated files in dalvik-cache directory.
if (!DeleteFilesInDir(art_paths_->art_dalvik_cache_directory.Append(isa))) {
LOG(ERROR) << "Failed to delete existing images in "
<< kArtDalvikCacheDirectory;
return false;
}
LOG(INFO) << "Running patchoat container...";
// Start the patchoat container and relocate boot images.
ScopedMinijail art_jail(minijail_new());
minijail_no_new_privs(art_jail.get());
int ret = minijail_enter_pivot_root(art_jail.get(), kAndroidRootfs);
if (ret != 0) {
LOG(ERROR) << "Can not set pivot root: " << strerror(-ret);
return false;
}
minijail_namespace_vfs(art_jail.get());
minijail_namespace_net(art_jail.get());
minijail_namespace_pids(art_jail.get());
minijail_skip_remount_private(art_jail.get());
minijail_use_alt_syscall(art_jail.get(), "android");
minijail_write_pid_file(art_jail.get(), kPidFile);
// Preserve stdout and stderr.
minijail_preserve_fd(art_jail.get(), STDOUT_FILENO, STDOUT_FILENO);
minijail_preserve_fd(art_jail.get(), STDERR_FILENO, STDERR_FILENO);
// Log to host stderr with priority 4, which is LOG_INFO and cannot
// #include <syslog.h> due to name collisions with base/logging.h.
minijail_log_to_fd(STDOUT_FILENO, 4);
minijail_log_to_fd(STDERR_FILENO, 4);
ret = minijail_mount_with_data(art_jail.get(), "proc", "/proc", "proc",
MS_RDONLY | MS_NODEV | MS_NOEXEC, nullptr);
// Set up private mount points.
if (ret != 0) {
LOG(ERROR) << "Failed to mount /proc: " << strerror(-ret);
return false;
}
const base::FilePath art_container_data_directory =
base::FilePath(kArtDalvikCacheDirectory).DirName();
ret = minijail_bind(art_jail.get(),
art_container_data_directory.value().c_str(), "/data", 1);
if (ret != 0) {
LOG(ERROR) << "Failed to mount container data dir: " << strerror(-ret);
return false;
}
ret = minijail_mount_with_data(art_jail.get(), "none", "/", "none",
MS_REC | MS_PRIVATE, nullptr);
if (ret != 0) {
LOG(ERROR) << "Failed to mark PRIVATE recursively under pivot root "
<< strerror(-ret);
return false;
}
std::string output_arg;
switch (sdk_version_) {
case AndroidSdkVersion::UNKNOWN:
LOG(ERROR) << "Unknown Android sdk version.";
return false;
// For Android P, use --output-image-directory.
default:
output_arg = "--output-image-directory=/data/dalvik-cache/" + isa;
break;
}
std::string isa_arg = "--instruction-set=" + isa;
int32_t offset = ChooseRelocationOffsetDelta(
kArtBaseAddressMinDelta, kArtBaseAddressMaxDelta, offset_seed);
std::string base_offset_arg = "--base-offset-delta=" + std::to_string(offset);
const char* argv[] = {kPatchOat,
"--input-image-location=/system/framework/boot.art",
output_arg.c_str(),
isa_arg.c_str(),
base_offset_arg.c_str(),
"--dump-timings",
nullptr};
// Set LD_PRELOAD to redirect all logd messages to stderr.
base::FilePath liblog_stderr_path =
art_paths_->art_rootfs_directory.Append("system/lib")
.Append(kLibLogStderrName);
// TODO(xzhou): Remove the check once the DSO is added to master-arc-dev.
if (base::PathExists(liblog_stderr_path)) {
LOG(INFO) << "Preloading " << kLibLogStderrName << " ...";
// This is executed in a child process and does not change parent's env.
setenv("LD_PRELOAD", kLibLogStderrName, 1 /* overwrite */);
} else {
LOG(ERROR) << "liblog_stderr does not exist, logd message not available";
}
LOG(INFO) << "Running " << kPatchOat << " for isa " << isa;
// Need a android environment, no preload.
// TODO(xzhou): Fix b/65159408 and run container as non root user.
ret = minijail_run_no_preload(art_jail.get(), argv[0],
const_cast<char**>(argv));
if (ret != 0) {
LOG(ERROR) << "Failed to run minijail: " << strerror(-ret);
return false;
}
do {
ret = minijail_wait(art_jail.get());
} while (ret == -EINTR);
LOG(INFO) << "minijail wait return status: " << ret;
return ret == EXIT_SUCCESS;
}
// static
int32_t ArtContainer::ChooseRelocationOffsetDeltaForTesting(
int32_t min_delta, int32_t max_delta, uint64_t offset_seed) {
return ChooseRelocationOffsetDelta(min_delta, max_delta, offset_seed);
}
} // namespace arc