blob: bbe0da352ca52a872e0feb1381f3474dbae98265 [file] [log] [blame]
/*
* Copyright (C) 2024 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 "dexopt_chroot_setup.h"
#include <unistd.h>
#include <cstring>
#include <filesystem>
#include <string>
#include <string_view>
#include <utility>
#include "aidl/com/android/server/art/BnDexoptChrootSetup.h"
#include "android-base/file.h"
#include "android-base/properties.h"
#include "android-base/result-gmock.h"
#include "android-base/scopeguard.h"
#include "android/binder_auto_utils.h"
#include "base/common_art_test.h"
#include "base/macros.h"
#include "exec_utils.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "selinux/selinux.h"
#include "tools/binder_utils.h"
#include "tools/cmdline_builder.h"
namespace art {
namespace dexopt_chroot_setup {
namespace {
using ::android::base::GetProperty;
using ::android::base::ScopeGuard;
using ::android::base::SetProperty;
using ::android::base::WaitForProperty;
using ::android::base::testing::HasError;
using ::android::base::testing::HasValue;
using ::android::base::testing::WithMessage;
using ::art::tools::CmdlineBuilder;
class DexoptChrootSetupTest : public CommonArtTest {
protected:
void SetUp() override {
CommonArtTest::SetUp();
dexopt_chroot_setup_ = ndk::SharedRefBase::make<DexoptChrootSetup>();
// TODO(jiakaiz): Delete this one the SDK version is bumped to 35.
char* con;
if (getfilecon(DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR, &con) < 0) {
ASSERT_EQ(errno, ENOENT) << ART_FORMAT("Failed to getfilecon '{}': {}",
DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR,
strerror(errno));
GTEST_SKIP() << ART_FORMAT("This platform is too old and doesn't have directory '{}'",
DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR);
}
{
auto cleanup = ScopeGuard([&]() { freecon(con); });
constexpr std::string_view kExpectedCon = "u:object_r:pre_reboot_dexopt_file:s0";
if (con != kExpectedCon) {
GTEST_SKIP() << ART_FORMAT("This platform is too old and doesn't have SELinux context '{}'",
kExpectedCon);
}
}
// Note that if a real Pre-reboot Dexopt is kicked off after this check, the test will still
// fail, but that should be very rare.
if (std::filesystem::exists(DexoptChrootSetup::CHROOT_DIR)) {
GTEST_SKIP() << "A real Pre-reboot Dexopt is running";
}
ASSERT_TRUE(
WaitForProperty("dev.bootcomplete", "1", /*relative_timeout=*/std::chrono::minutes(3)));
test_skipped_ = false;
scratch_dir_ = std::make_unique<ScratchDir>();
scratch_path_ = scratch_dir_->GetPath();
// Remove the trailing '/';
scratch_path_.resize(scratch_path_.length() - 1);
partitions_sysprop_value_ = GetProperty(kAdditionalPartitionsSysprop, /*default_value=*/"");
ASSERT_TRUE(SetProperty(kAdditionalPartitionsSysprop, "odm:/odm,system_dlkm:/system_dlkm"));
partitions_sysprop_set_ = true;
}
void TearDown() override {
if (test_skipped_) {
return;
}
if (partitions_sysprop_set_ &&
!SetProperty(kAdditionalPartitionsSysprop, partitions_sysprop_value_)) {
LOG(ERROR) << ART_FORMAT("Failed to recover sysprop '{}'", kAdditionalPartitionsSysprop);
}
scratch_dir_.reset();
dexopt_chroot_setup_->tearDown(/*in_allowConcurrent=*/false);
CommonArtTest::TearDown();
}
std::shared_ptr<DexoptChrootSetup> dexopt_chroot_setup_;
std::unique_ptr<ScratchDir> scratch_dir_;
std::string scratch_path_;
bool test_skipped_ = true;
std::string partitions_sysprop_value_;
bool partitions_sysprop_set_ = false;
};
TEST_F(DexoptChrootSetupTest, Run) {
// We only test the Mainline update case here. There isn't an easy way to test the OTA update case
// in such a unit test. The OTA update case is assumed to be covered by the E2E test.
ASSERT_STATUS_OK(
dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt, /*in_mapSnapshotsForOta=*/false));
ASSERT_STATUS_OK(dexopt_chroot_setup_->init());
std::string mounts;
ASSERT_TRUE(android::base::ReadFileToString("/proc/mounts", &mounts, /*follow_symlinks=*/true));
// Some important dirs that should be the same as outside.
std::vector<const char*> same_dirs = {
"/",
"/system",
"/system_ext",
"/vendor",
"/product",
"/data",
"/mnt/expand",
"/dev",
"/dev/cpuctl",
"/dev/cpuset",
"/proc",
"/sys",
"/sys/fs/cgroup",
"/sys/fs/selinux",
"/metadata",
"/odm",
"/system_dlkm",
};
for (const std::string& dir : same_dirs) {
struct stat st_outside;
ASSERT_EQ(stat(dir.c_str(), &st_outside), 0);
struct stat st_inside;
ASSERT_EQ(stat(PathInChroot(dir).c_str(), &st_inside), 0);
EXPECT_EQ(std::make_pair(st_outside.st_dev, st_outside.st_ino),
std::make_pair(st_inside.st_dev, st_inside.st_ino))
<< ART_FORMAT("Unexpected different directory in chroot: '{}'\n", dir) << mounts;
}
// Some important dirs that are expected to be writable.
std::vector<const char*> writable_dirs = {
"/data",
"/mnt/expand",
};
for (const std::string& dir : writable_dirs) {
EXPECT_EQ(access(PathInChroot(dir).c_str(), W_OK), 0);
}
// Some important dirs that are not the same as outside but should be prepared.
std::vector<const char*> prepared_dirs = {
"/apex/com.android.art",
"/linkerconfig/com.android.art",
};
for (const std::string& dir : prepared_dirs) {
EXPECT_FALSE(std::filesystem::is_empty(PathInChroot(dir)));
}
EXPECT_TRUE(std::filesystem::is_directory(PathInChroot("/mnt/artd_tmp")));
// Check that the chroot environment is capable to run programs. `dex2oat` is arbitrarily picked
// here. The test dex file and the scratch dir in /data are the same inside the chroot as outside.
CmdlineBuilder args;
args.Add(GetArtBinDir() + "/art_exec")
.Add("--chroot=%s", DexoptChrootSetup::CHROOT_DIR)
.Add("--")
.Add(GetArtBinDir() + "/dex2oat" + (Is64BitInstructionSet(kRuntimeISA) ? "64" : "32"))
.Add("--dex-file=%s", GetTestDexFileName("Main"))
.Add("--oat-file=%s", scratch_path_ + "/output.odex")
.Add("--output-vdex=%s", scratch_path_ + "/output.vdex")
.Add("--compiler-filter=speed")
.Add("--boot-image=/nonx/boot.art");
std::string error_msg;
EXPECT_TRUE(Exec(args.Get(), &error_msg)) << error_msg;
// Check that `setUp` can be repeatedly called, to simulate the case where an instance of the
// caller (typically system_server) called `setUp` and crashed later, and a new instance called
// `setUp` again.
ASSERT_STATUS_OK(
dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt, /*in_mapSnapshotsForOta=*/false));
ASSERT_STATUS_OK(dexopt_chroot_setup_->init());
// Check that `init` cannot be repeatedly called.
ndk::ScopedAStatus status = dexopt_chroot_setup_->init();
EXPECT_FALSE(status.isOk());
EXPECT_EQ(status.getExceptionCode(), EX_ILLEGAL_STATE);
EXPECT_STREQ(status.getMessage(), "init must not be repeatedly called");
ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown(/*in_allowConcurrent=*/false));
EXPECT_FALSE(std::filesystem::exists(DexoptChrootSetup::CHROOT_DIR));
// Check that `tearDown` can be repeatedly called too.
ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown(/*in_allowConcurrent=*/false));
// Check that `setUp` can be followed directly by a `tearDown`.
ASSERT_STATUS_OK(
dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt, /*in_mapSnapshotsForOta=*/false));
ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown(/*in_allowConcurrent=*/false));
EXPECT_FALSE(std::filesystem::exists(DexoptChrootSetup::CHROOT_DIR));
}
TEST(DexoptChrootSetupUnitTest, ConstructLinkerConfigCompatEnvSection) {
std::string art_linker_config_content = R"(dir.com.android.art = /apex/com.android.art/bin
[com.android.art]
additional.namespaces = com_android_art,system
namespace.default.isolated = true
namespace.default.links = com_android_art,system
namespace.default.link.com_android_art.allow_all_shared_libs = true
namespace.default.link.system.shared_libs = libartpalette-system.so:libbinder_ndk.so:libc.so:libdl.so:libdl_android.so:liblog.so:libm.so
namespace.com_android_art.isolated = true
namespace.com_android_art.visible = true
namespace.com_android_art.search.paths = /apex/com.android.art/${LIB}
namespace.com_android_art.permitted.paths = /apex/com.android.art/${LIB}
namespace.com_android_art.permitted.paths += /system/${LIB}
namespace.com_android_art.permitted.paths += /system_ext/${LIB}
namespace.com_android_art.permitted.paths += /data
namespace.com_android_art.permitted.paths += /apex/com.android.art/javalib
namespace.com_android_art.links = system
namespace.com_android_art.link.system.shared_libs = libartpalette-system.so:libbinder_ndk.so:libc.so:libdl.so:libdl_android.so:liblog.so:libm.so
namespace.system.isolated = true
namespace.system.visible = true
namespace.system.search.paths = /system/${LIB}
namespace.system.search.paths += /system_ext/${LIB}
namespace.system.permitted.paths = /system/${LIB}/drm:/system/${LIB}/extractors:/system/${LIB}/hw
namespace.system.permitted.paths += /system_ext/${LIB}
namespace.system.permitted.paths += /system/framework
namespace.system.permitted.paths += /data
namespace.system.permitted.paths += /apex/com.android.runtime/${LIB}/bionic
namespace.system.permitted.paths += /system/${LIB}/bootstrap
namespace.system.links = com_android_art
namespace.system.link.com_android_art.shared_libs = libdexfile.so:libjdwp.so:libnativebridge.so:libnativehelper.so:libnativeloader.so:libsigchain.so
[some_other_section]
)";
std::string expected_compat_env_section = R"([com.android.art.compat]
additional.namespaces = com_android_art,system
namespace.default.isolated = true
namespace.default.links = com_android_art,system
namespace.default.link.com_android_art.allow_all_shared_libs = true
namespace.default.link.system.shared_libs = libartpalette-system.so:libbinder_ndk.so:libc.so:libdl.so:libdl_android.so:liblog.so:libm.so
namespace.com_android_art.isolated = true
namespace.com_android_art.visible = true
namespace.com_android_art.search.paths = /apex/com.android.art/${LIB}
namespace.com_android_art.permitted.paths = /apex/com.android.art/${LIB}
namespace.com_android_art.permitted.paths += /mnt/compat_env/system/${LIB}
namespace.com_android_art.permitted.paths += /mnt/compat_env/system_ext/${LIB}
namespace.com_android_art.permitted.paths += /data
namespace.com_android_art.permitted.paths += /apex/com.android.art/javalib
namespace.com_android_art.links = system
namespace.com_android_art.link.system.shared_libs = libartpalette-system.so:libbinder_ndk.so:libc.so:libdl.so:libdl_android.so:liblog.so:libm.so
namespace.system.isolated = true
namespace.system.visible = true
namespace.system.search.paths = /mnt/compat_env/system/${LIB}
namespace.system.search.paths += /mnt/compat_env/system_ext/${LIB}
namespace.system.permitted.paths = /mnt/compat_env/system/${LIB}/drm:/mnt/compat_env/system/${LIB}/extractors:/mnt/compat_env/system/${LIB}/hw
namespace.system.permitted.paths += /mnt/compat_env/system_ext/${LIB}
namespace.system.permitted.paths += /system/framework
namespace.system.permitted.paths += /data
namespace.system.permitted.paths += /apex/com.android.runtime/${LIB}/bionic
namespace.system.permitted.paths += /mnt/compat_env/system/${LIB}/bootstrap
namespace.system.links = com_android_art
namespace.system.link.com_android_art.shared_libs = libdexfile.so:libjdwp.so:libnativebridge.so:libnativehelper.so:libnativeloader.so:libsigchain.so
)";
EXPECT_THAT(ConstructLinkerConfigCompatEnvSection(art_linker_config_content),
HasValue(expected_compat_env_section));
}
TEST(DexoptChrootSetupUnitTest, ConstructLinkerConfigCompatEnvSectionNoMatch) {
std::string art_linker_config_content = R"(dir.com.android.art = /apex/com.android.art/bin
[com.android.art]
additional.namespaces = com_android_art,system
namespace.default.isolated = true
namespace.default.links = com_android_art,system
namespace.default.link.com_android_art.allow_all_shared_libs = true
namespace.default.link.system.shared_libs = libartpalette-system.so:libbinder_ndk.so:libc.so:libdl.so:libdl_android.so:liblog.so:libm.so
namespace.com_android_art.isolated = true
namespace.com_android_art.visible = true
namespace.com_android_art.search.paths = /apex/com.android.art/${LIB}
namespace.com_android_art.permitted.paths = /apex/com.android.art/${LIB}
namespace.com_android_art.permitted.paths += /foo/${LIB}
namespace.com_android_art.permitted.paths += /foo_ext/${LIB}
namespace.com_android_art.permitted.paths += /data
namespace.com_android_art.permitted.paths += /apex/com.android.art/javalib
namespace.com_android_art.links = system
namespace.com_android_art.link.system.shared_libs = libartpalette-system.so:libbinder_ndk.so:libc.so:libdl.so:libdl_android.so:liblog.so:libm.so
namespace.system.isolated = true
namespace.system.visible = true
namespace.system.search.paths = /foo/${LIB}
namespace.system.search.paths += /foo_ext/${LIB}
namespace.system.permitted.paths = /foo/${LIB}/drm:/foo/${LIB}/extractors:/foo/${LIB}/hw
namespace.system.permitted.paths += /foo_ext/${LIB}
namespace.system.permitted.paths += /system/framework
namespace.system.permitted.paths += /data
namespace.system.permitted.paths += /apex/com.android.runtime/${LIB}/bionic
namespace.system.permitted.paths += /foo/${LIB}/bootstrap
namespace.system.links = com_android_art
namespace.system.link.com_android_art.shared_libs = libdexfile.so:libjdwp.so:libnativebridge.so:libnativehelper.so:libnativeloader.so:libsigchain.so
[some_other_section]
)";
EXPECT_THAT(ConstructLinkerConfigCompatEnvSection(art_linker_config_content),
HasError(WithMessage("No matching lines to patch in ART linker config")));
}
} // namespace
} // namespace dexopt_chroot_setup
} // namespace art