blob: 37418917965d47ec731dc5ac68e13c7b42e6747f [file] [log] [blame]
/*
* Copyright (C) 2023 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 <android-base/properties.h>
#include <android-base/test_utils.h>
#include <android/api-level.h>
#include <elf.h>
#include <elfutils/parse.h>
#include <gtest/gtest.h>
#include <procinfo/process_map.h>
class Vts16KPageSizeTest : public ::testing::Test {
protected:
static int VendorApiLevel() {
// "ro.vendor.api_level" is added in Android T.
// Undefined indicates S or below
return android::base::GetIntProperty("ro.vendor.api_level", __ANDROID_API_S__);
}
static int ProductPageSize() {
return android::base::GetIntProperty("ro.product.page_size", 0);
}
static int BootPageSize() {
return android::base::GetIntProperty("ro.boot.hardware.cpu.pagesize", 0);
}
static bool NoBionicPageSizeMacroProperty() {
// "ro.product.build.no_bionic_page_size_macro" was added in Android V and is
// set to true when Android is build with PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true.
return android::base::GetBoolProperty("ro.product.build.no_bionic_page_size_macro", false);
}
static std::string Architecture() { return android::base::GetProperty("ro.bionic.arch", ""); }
static ssize_t MaxPageSize(const std::string& filepath) {
ssize_t maxPageSize = -1;
android::elfutils::Elf64Binary elf;
// 32bit ELFs only need to support a max-page-size of 4KiB
if (!android::elfutils::Elf64Parser::IsElf64(filepath)) {
return 4096;
}
if (!android::elfutils::Elf64Parser::ParseElfFile(filepath, elf)) {
return -1;
}
for (int i = 0; i < elf.phdrs.size(); i++) {
Elf64_Phdr phdr = elf.phdrs[i];
if ((phdr.p_type != PT_LOAD) || !(phdr.p_type & PF_X)) {
continue;
}
maxPageSize = phdr.p_align;
break;
}
return maxPageSize;
}
static void SetUpTestSuite() {
if (VendorApiLevel() < 202404 && ProductPageSize() != 16384) {
GTEST_SKIP() << "16kB support is only required on V and later releases as well as on "
"products directly booting with 16kB kernels.";
}
}
/*
* x86_64 also needs to be at least 16KB aligned, since Android
* supports page size emulation in x86_64 for app development.
*/
size_t RequiredMaxPageSize() {
if (mArch == "arm64" || mArch == "aarch64" || mArch == "x86_64") {
return 0x4000;
} else {
return 0x1000;
}
}
const std::string mArch = Architecture();
};
/**
* Checks the max-page-size of init against the architecture's
* required max-page-size.
*
* Note: a more comprehensive version of this test exists in
* elf_alignment_test. This has turned out to be a canary test
* to give visibility on this when checking all 16K tests.
*/
// @VsrTest = 3.14.1
TEST_F(Vts16KPageSizeTest, InitMaxPageSizeTest) {
constexpr char initPath[] = "/system/bin/init";
ssize_t expectedMaxPageSize = RequiredMaxPageSize();
ASSERT_NE(expectedMaxPageSize, -1)
<< "Failed to get required max page size for arch: " << mArch;
ssize_t initMaxPageSize = MaxPageSize(initPath);
ASSERT_NE(initMaxPageSize, -1) << "Failed to get max page size of ELF: " << initPath;
ASSERT_EQ(initMaxPageSize % expectedMaxPageSize, 0)
<< "ELF " << initPath << " with page size " << initMaxPageSize
<< " was not built with the required max-page-size " << expectedMaxPageSize;
}
/**
* Checks if the vendor's build was compiled with the define
* PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO based on the product property
* ro.product.build.no_bionic_page_size_macro.
*/
// @VsrTest = 3.14.2
TEST_F(Vts16KPageSizeTest, NoBionicPageSizeMacro) {
/**
* TODO(b/315034809): switch to error when final decision is made.
*/
if (!NoBionicPageSizeMacroProperty())
GTEST_SKIP() << "Device was not built with: PRODUCT_NO_BIONIC_PAGE_SIZE_MACRO := true";
}
/**
* Checks if the device has page size which was set using TARGET_BOOTS_16K
*/
TEST_F(Vts16KPageSizeTest, ProductPageSize) {
// We can't set the default value to be 4096 since device which will have 16KB page size and
// doesn't set TARGET_BOOTS_16K, won't have this property and will fail the test.
int requiredPageSize = ProductPageSize();
if (requiredPageSize != 0) {
int currentPageSize = getpagesize();
ASSERT_EQ(requiredPageSize, currentPageSize);
} else {
GTEST_SKIP() << "Device was not built with option TARGET_BOOTS_16K = true";
}
}
/**
* Check boot reported or CPU reported page size that is currently being used.
*/
TEST_F(Vts16KPageSizeTest, BootPageSize) {
ASSERT_EQ(BootPageSize(), getpagesize());
}
/**
* Check that the process VMAs are page aligned. This is mostly to ensure
* x86_64 16KiB page size emulation is working correctly.
*/
TEST_F(Vts16KPageSizeTest, ProcessVmasArePageAligned) {
ASSERT_TRUE(android::procinfo::ReadProcessMaps(
getpid(), [&](const android::procinfo::MapInfo& mapinfo) {
EXPECT_EQ(mapinfo.start % getpagesize(), 0u) << mapinfo.start;
EXPECT_EQ(mapinfo.end % getpagesize(), 0u) << mapinfo.end;
}));
}
/**
* The platform ELFs are built with separate loadable segments.
* This means that the ELF mappings should be completely covered by
* the backing file, and should not generate a SIGBUS on reading.
*/
void fault_file_pages(const android::procinfo::MapInfo& mapinfo) {
std::vector<uint8_t> first_bytes;
for (size_t i = mapinfo.start; i < mapinfo.end; i += getpagesize()) {
first_bytes.push_back(*(reinterpret_cast<uint8_t*>(i)));
}
if (first_bytes.size() > 0) exit(0);
exit(1);
}
/**
* Ensure that apps don't crash with SIGBUS when attempting to read
* file mapped platform ELFs.
*/
TEST_F(Vts16KPageSizeTest, CanReadProcessFileMappedContents) {
// random accesses may trigger MTE on hwasan builds
SKIP_WITH_HWASAN;
std::vector<android::procinfo::MapInfo> maps;
ASSERT_TRUE(android::procinfo::ReadProcessMaps(
getpid(), [&](const android::procinfo::MapInfo& mapinfo) {
if ((mapinfo.flags & PROT_READ) == 0) return;
// Don't check anonymous mapping.
if (!android::base::StartsWith(mapinfo.name, "/")) return;
// Skip devices
if (android::base::StartsWith(mapinfo.name, "/dev/")) return;
maps.push_back(mapinfo);
}));
for (const auto& map : maps) {
ASSERT_EXIT(fault_file_pages(map), ::testing::ExitedWithCode(0), "")
<< "Failed to read maps: " << map.name;
}
}
void setUnsetProp(const std::string& prop) {
// save and set the default
bool defaultValue = android::base::GetBoolProperty(prop, false);
// set and verify property.
ASSERT_EQ(android::base::SetProperty(prop, "true"), true);
ASSERT_EQ(android::base::GetBoolProperty(prop, false), true);
// reset
ASSERT_EQ(android::base::SetProperty(prop, std::to_string(defaultValue)), true);
}
TEST_F(Vts16KPageSizeTest, BackCompatSupport) {
// Backcompat support is added in Android B
int apiLevel = VendorApiLevel();
if (apiLevel < 36 /* Android B */) {
GTEST_SKIP() << "16 KB backcompat support is only required on Android B and later release";
}
std::string prop = "bionic.linker.16kb.app_compat.enabled";
setUnsetProp(prop);
}
TEST_F(Vts16KPageSizeTest, PackageManagerDisableBackCompat) {
// Package manager support for backcompat is added in Android B
int apiLevel = VendorApiLevel();
if (apiLevel < 36 /* Android B */) {
GTEST_SKIP() << "16 KB backcompat support in package manager is only required on Android B "
"and later release";
}
std::string prop = "pm.16kb.app_compat.disabled";
setUnsetProp(prop);
}