unplugged-system/test/vts-testcase/security/avb/kernel_version_test.cpp

371 lines
13 KiB
C++

/*
* 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 <algorithm>
#include <memory>
#include <optional>
#include <ostream>
#include <sstream>
#include <string>
#include <android-base/file.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/result-gmock.h>
#include <android-base/strings.h>
#include <android/api-level.h>
#include <gmock/gmock.h>
#include <google/protobuf/repeated_field.h>
#include <google/protobuf/text_format.h>
#include <gtest/gtest.h>
#include <kver/kernel_release.h>
#include <libvts_vintf_test_common/common.h>
#include <vintf/Version.h>
#include <vintf/VintfObject.h>
#include <vintf/parse_string.h>
#include "gsi_validation_utils.h"
#include "kernel_version_matrix.pb.h"
namespace {
using android::base::testing::Ok;
using KernelVersionMatrix = std::map<uint64_t, AndroidReleaseRequirement>;
std::ostream& operator<<(std::ostream& os, const Kmi& kmi) {
os << "android";
if (kmi.android_release() != 0) {
os << kmi.android_release();
}
return os << "-" << kmi.kernel_version().major_version() << "."
<< kmi.kernel_version().minor_version();
}
// Represents the information about the running kernel.
struct ActualKmi {
public:
ActualKmi(const android::vintf::KernelVersion& kernel_version,
const std::optional<android::kver::KernelRelease>& kernel_release)
: kernel_version_(kernel_version), kernel_release_(kernel_release) {}
[[nodiscard]] android::vintf::Version kernel_version() const {
return kernel_version_.dropMinor();
}
[[nodiscard]] std::optional<uint64_t> android_release() const {
if (kernel_release_.has_value()) {
return kernel_release_->android_release();
}
return std::nullopt;
}
[[nodiscard]] std::string string() const {
std::string ret = android::vintf::to_string(kernel_version_);
if (kernel_release_.has_value()) {
ret += " (" + kernel_release_->string() + ")";
}
return ret;
}
friend std::ostream& operator<<(std::ostream& os, const ActualKmi& kmi) {
os << kmi.kernel_version_;
if (kmi.kernel_release_.has_value()) {
os << " (" + kmi.kernel_release_->string() << ")";
}
return os;
}
private:
android::vintf::KernelVersion kernel_version_;
std::optional<android::kver::KernelRelease> kernel_release_;
};
// Read the raw compatibility matrix from test data.
std::optional<RawKernelVersionMatrix> ReadRawKernelVersionMatrix() {
auto exec_dir = android::base::GetExecutableDirectory();
auto matrix_path = exec_dir + "/kernel_version_matrix.textproto";
std::string matrix_content;
if (!android::base::ReadFileToString(matrix_path, &matrix_content)) {
ADD_FAILURE() << "Can't read " << matrix_path;
return std::nullopt;
}
RawKernelVersionMatrix ret;
if (!google::protobuf::TextFormat::ParseFromString(matrix_content, &ret)) {
ADD_FAILURE() << matrix_path << " is not valid";
return std::nullopt;
}
return ret;
}
// Parse a raw KMI string (e.g. "android14-5.15") to structured Kmi object.
bool FromRaw(const std::string& s, Kmi* mutable_out) {
auto tokens = android::base::Split(s, "-");
if (tokens.size() != 2) {
ADD_FAILURE() << "Unrecognized requirement: " << s;
return false;
}
std::string_view android_release_sv = tokens[0];
if (!android::base::ConsumePrefix(&android_release_sv, "android")) {
ADD_FAILURE() << "Unrecognized requirement: " << s;
return false;
}
uint64_t android_release;
if (android_release_sv.empty()) {
mutable_out->clear_android_release();
} else {
if (!android::base::ParseUint(std::string(android_release_sv),
&android_release)) {
ADD_FAILURE() << "Unrecognized requirement: " << s;
return false;
}
mutable_out->set_android_release(android_release);
}
android::vintf::Version vintf_kernel_version;
if (!android::vintf::parse(tokens[1], &vintf_kernel_version)) {
ADD_FAILURE() << "Unrecognized requirement: " << s;
return false;
}
mutable_out->mutable_kernel_version()->set_major_version(
vintf_kernel_version.majorVer);
mutable_out->mutable_kernel_version()->set_minor_version(
vintf_kernel_version.minorVer);
return true;
}
// Parse an array of raw KMI strings to an array of structured Kmi object.
bool FromRaw(const google::protobuf::RepeatedPtrField<std::string>& raw_in,
google::protobuf::RepeatedPtrField<Kmi>* mutable_out) {
mutable_out->Reserve(raw_in.size());
for (const auto& raw_s : raw_in) {
if (!FromRaw(raw_s, mutable_out->Add())) {
return false;
}
}
return true;
}
// Turn the raw compatibility matrix into structured data.
std::optional<KernelVersionMatrix> FromRaw(
const RawKernelVersionMatrix& raw_kernel_version_matrix) {
KernelVersionMatrix ret;
for (const auto& [api_level, raw_android_release_requirements] :
raw_kernel_version_matrix.release_requirements()) {
AndroidReleaseRequirement ret_value;
if (!FromRaw(raw_android_release_requirements.upgrade(),
ret_value.mutable_upgrade())) {
return std::nullopt;
}
if (!FromRaw(raw_android_release_requirements.launch(),
ret_value.mutable_launch())) {
return std::nullopt;
}
if (!FromRaw(raw_android_release_requirements.launch_grf(),
ret_value.mutable_launch_grf())) {
return std::nullopt;
}
ret.emplace(api_level, std::move(ret_value));
}
return ret;
}
// Return requirements on kernel version and KMI for the given platform SDK
// level. Return nullptr on failure.
const google::protobuf::RepeatedPtrField<Kmi>* GetKernelVersionRequirements(
const KernelVersionMatrix& kernel_version_matrix,
uint32_t android_platform_release, bool is_launch, bool is_grf) {
auto release_requirements_it =
kernel_version_matrix.find(android_platform_release);
if (release_requirements_it == kernel_version_matrix.end()) {
ADD_FAILURE() << "Unable to find requirement for SDK level "
<< android_platform_release << ". Is the test updated?";
return nullptr;
}
if (is_launch) {
if (is_grf) {
return &release_requirements_it->second.launch_grf();
}
return &release_requirements_it->second.launch();
}
return &release_requirements_it->second.upgrade();
}
// Read the information about the running kernel that this test needs.
std::optional<ActualKmi> GetActualKmi() {
auto vintf_object = android::vintf::VintfObject::GetInstance();
if (vintf_object == nullptr) {
ADD_FAILURE() << "Cannot get VintfObject instance";
return std::nullopt;
}
auto runtime_info = vintf_object->getRuntimeInfo(
android::vintf::RuntimeInfo::FetchFlag::CPU_VERSION);
if (runtime_info == nullptr) {
ADD_FAILURE() << "Cannot get kernel release";
return std::nullopt;
}
auto kernel_release = android::kver::KernelRelease::Parse(
runtime_info->osRelease(), true /* allow suffix */);
auto kernel_version = runtime_info->kernelVersion();
return std::make_optional<ActualKmi>(kernel_version, kernel_release);
}
// Check kernel version and KMI against a list of requirements.
bool KernelVersionIsSupported(
const ActualKmi& actual,
const google::protobuf::RepeatedPtrField<Kmi>& requirements,
std::string* error) {
for (const auto& req : requirements) {
if (req.kernel_version().major_version() !=
actual.kernel_version().majorVer ||
req.kernel_version().minor_version() !=
actual.kernel_version().minorVer) {
GTEST_LOG_(INFO) << "Failed to match " << actual << " against required "
<< req << ": kernel version does not match.";
continue; // check next item
}
if (req.android_release() != actual.android_release().value_or(0)) {
GTEST_LOG_(INFO) << "Failed to match " << actual << " against required "
<< req
<< ": The Android release part of KMI does not match.";
continue; // check next item
}
GTEST_LOG_(INFO) << "Matched " << actual << " against requirement " << req;
return true;
}
std::stringstream error_stream;
error_stream << "Kernel " << actual << " is not valid. It must be one of [\n";
for (const auto& req : requirements) {
error_stream << " " << req << ",\n";
}
error_stream << "].";
*error = error_stream.str();
return false;
}
// If ro.build.version.codename == REL, expect the given condition is true.
// Otherwise, only log a warning message, but don't fail the test.
struct ExpectTrueOnRelease : public std::stringstream {
explicit ExpectTrueOnRelease(bool cond) : cond_(cond) {}
~ExpectTrueOnRelease() override {
if (cond_) {
return;
}
if (!IsReleasedAndroidVersion()) {
GTEST_LOG_(ERROR) << str() << " This will be an error upon release.";
} else {
ADD_FAILURE() << str();
}
}
private:
bool cond_;
};
bool IsGrf() {
return !android::base::GetProperty("ro.board.first_api_level", "").empty() ||
!android::base::GetProperty("ro.board.api_level", "").empty();
}
TEST(KernelVersionTest, AgainstPlatformRelease) {
auto raw_kernel_version_matrix = ReadRawKernelVersionMatrix();
ASSERT_TRUE(raw_kernel_version_matrix.has_value());
ASSERT_GT(raw_kernel_version_matrix->release_requirements_size(), 0);
auto kernel_version_matrix = FromRaw(*raw_kernel_version_matrix);
ASSERT_TRUE(kernel_version_matrix.has_value());
ASSERT_FALSE(kernel_version_matrix->empty());
auto android_platform_release = GetSdkLevel();
auto min_enforcing_android_release = kernel_version_matrix->begin()->first;
if (android_platform_release < min_enforcing_android_release) {
GTEST_SKIP() << "Kernel version is not enforced for platform SDK level "
<< android_platform_release << " ( < "
<< min_enforcing_android_release << " )";
}
auto product_first_api_level = GetProductFirstApiLevel();
ExpectTrueOnRelease(product_first_api_level <= android_platform_release)
<< "Product first API level " << product_first_api_level
<< " should not exceed the platform release " << android_platform_release
<< ".";
bool is_launch = product_first_api_level >= android_platform_release;
auto requirements = GetKernelVersionRequirements(
*kernel_version_matrix, android_platform_release, is_launch, IsGrf());
ASSERT_NE(requirements, nullptr);
auto actual = GetActualKmi();
ASSERT_TRUE(actual.has_value());
std::string error;
ExpectTrueOnRelease(KernelVersionIsSupported(*actual, *requirements, &error))
<< error;
}
TEST(KernelVersionTest, GrfDevicesMustUseLatestKernel) {
if (!IsGrf()) {
GTEST_SKIP() << "Non-GRF device kernel requirements are checked in "
"SystemVendorTest.KernelCompatibility";
}
// Use board API level, not vendor API level, to ignore
// ro.product.first_api_level. ro.vendor.api_level is considering
// the ro.product.api_level which could potentially yield a lower api_level
// than the ro.board.{first_,}api_level.
auto board_api_level = GetBoardApiLevel();
ASSERT_TRUE(board_api_level.has_value())
<< "Unable to determine board API level on GRF devices";
if (*board_api_level <= __ANDROID_API_R__) {
GTEST_SKIP() << "[VSR-3.4.1-001] does not enforce latest kernel x.y for "
<< "board_api_level == " << *board_api_level << " <= R";
}
auto corresponding_vintf_level =
android::vintf::testing::GetFcmVersionFromApiLevel(*board_api_level);
ASSERT_THAT(corresponding_vintf_level, Ok());
auto latest_min_lts =
android::vintf::VintfObject::GetInstance()->getLatestMinLtsAtFcmVersion(
*corresponding_vintf_level);
ASSERT_THAT(latest_min_lts, Ok());
auto runtime_info =
android::vintf::VintfObject::GetInstance()->getRuntimeInfo(
android::vintf::RuntimeInfo::FetchFlag::CPU_VERSION);
ASSERT_NE(runtime_info, nullptr);
auto kernel_version = runtime_info->kernelVersion();
ASSERT_GE(kernel_version, *latest_min_lts)
<< "[VSR-3.4.1-001] CHIPSETs that are on GRF and are frozen on API level "
<< *board_api_level << " (corresponding to VINTF level "
<< *corresponding_vintf_level << ") must use kernel version ("
<< *latest_min_lts << ")+, but kernel version is " << kernel_version;
}
} // namespace