1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <chrono>
18 #include <cstdint>
19 #include <format>
20 #include <limits>
21 #include <regex>
22 #include <sstream>
23 
24 #include <android-base/file.h>
25 #include <android-base/parseint.h>
26 #include <gtest/gtest.h>
27 #include <kver/kernel_release.h>
28 #include <tinyxml2.h>
29 #include <vintf/Version.h>
30 #include <vintf/VintfObject.h>
31 
32 using android::vintf::KernelVersion;
33 using android::vintf::Level;
34 using android::vintf::RuntimeInfo;
35 using android::vintf::Version;
36 using android::vintf::VintfObject;
37 
38 namespace {
39 
40 const std::string kernel_lifetimes_config_path =
41     "/system/etc/kernel/kernel-lifetimes.xml";
42 
parseDate(std::string_view date_string,std::chrono::year_month_day & date)43 bool parseDate(std::string_view date_string,
44                std::chrono::year_month_day& date) {
45   const std::regex date_regex("(\\d\\d\\d\\d)-(\\d\\d)-(\\d\\d)");
46   std::cmatch date_match;
47   if (!std::regex_match(date_string.data(), date_match, date_regex)) {
48     return false;
49   }
50 
51   uint32_t year, month, day;
52   android::base::ParseUint(date_match[1].str(), &year);
53   android::base::ParseUint(date_match[2].str(), &month);
54   android::base::ParseUint(date_match[3].str(), &day);
55   date = std::chrono::year_month_day(std::chrono::year(year),
56                                      std::chrono::month(month),
57                                      std::chrono::day(day));
58   return true;
59 }
60 
parseKernelVersion(std::string_view kernel_version_string)61 KernelVersion parseKernelVersion(std::string_view kernel_version_string) {
62   const std::regex kernel_version_regex("(\\d+)\\.(\\d+)\\.(\\d+)");
63   std::cmatch kernel_version_match;
64   if (!std::regex_match(kernel_version_string.data(), kernel_version_match,
65                         kernel_version_regex)) {
66     return {};
67   }
68 
69   uint32_t v, mj, mi;
70   android::base::ParseUint(kernel_version_match[1].str(), &v);
71   android::base::ParseUint(kernel_version_match[2].str(), &mj);
72   android::base::ParseUint(kernel_version_match[3].str(), &mi);
73   return KernelVersion(v, mj, mi);
74 }
75 
76 }  // namespace
77 
78 class EolEnforcementTest : public testing::Test {
79  public:
SetUp()80   virtual void SetUp() override {
81     // Get current date.
82     today = std::chrono::year_month_day(std::chrono::floor<std::chrono::days>(
83         std::chrono::system_clock::now()));
84 
85     // Get runtime info.
86     auto vintf = VintfObject::GetInstance();
87     ASSERT_NE(vintf, nullptr);
88     runtime_info = vintf->getRuntimeInfo(RuntimeInfo::FetchFlag::CPU_VERSION |
89                                          RuntimeInfo::FetchFlag::CONFIG_GZ);
90     ASSERT_NE(runtime_info, nullptr);
91   }
92 
93   bool isReleaseEol(std::string_view date) const;
94 
95   std::chrono::year_month_day today;
96   std::shared_ptr<const RuntimeInfo> runtime_info;
97 };
98 
isReleaseEol(std::string_view date_string) const99 bool EolEnforcementTest::isReleaseEol(std::string_view date_string) const {
100   std::chrono::year_month_day date;
101   if (!parseDate(date_string, date)) {
102     ADD_FAILURE() << "Failed to parse date string: " << date_string;
103   }
104   return today > date;
105 }
106 
TEST_F(EolEnforcementTest,KernelNotEol)107 TEST_F(EolEnforcementTest, KernelNotEol) {
108   ASSERT_GE(runtime_info->kernelVersion().dropMinor(), (Version{4, 14}))
109       << "Kernel versions below 4.14 are EOL";
110 
111   std::string kernel_lifetimes_content;
112   ASSERT_TRUE(android::base::ReadFileToString(kernel_lifetimes_config_path,
113                                               &kernel_lifetimes_content))
114       << "Failed to read approved OGKI builds config at "
115       << kernel_lifetimes_config_path;
116 
117   tinyxml2::XMLDocument kernel_lifetimes_xml;
118   const auto xml_error =
119       kernel_lifetimes_xml.Parse(kernel_lifetimes_content.c_str());
120   ASSERT_EQ(xml_error, tinyxml2::XMLError::XML_SUCCESS)
121       << "Failed to parse approved builds config: "
122       << tinyxml2::XMLDocument::ErrorIDToName(xml_error);
123 
124   const auto kernel_version = runtime_info->kernelVersion();
125   std::string branch_name;
126   if (kernel_version.dropMinor() < Version{5, 4}) {
127     branch_name = std::format("android-{}.{}", kernel_version.version,
128                               kernel_version.majorRev);
129   } else if (kernel_version.dropMinor() == Version{5, 4} &&
130              VintfObject::GetInstance()->getKernelLevel() == Level::R) {
131     // Kernel release string on Android 11 is not GKI compatible.
132     branch_name = "android11-5.4";
133   } else {
134     const auto kernel_release = android::kver::KernelRelease::Parse(
135         android::vintf::VintfObject::GetRuntimeInfo()->osRelease(),
136         /* allow_suffix = */ true);
137     ASSERT_TRUE(kernel_release.has_value())
138         << "Failed to parse the kernel release string";
139     branch_name =
140         std::format("android{}-{}.{}", kernel_release->android_release(),
141                     kernel_version.version, kernel_version.majorRev);
142   }
143 
144   tinyxml2::XMLElement* branch_element = nullptr;
145   const auto kernels_element = kernel_lifetimes_xml.RootElement();
146   for (auto branch = kernels_element->FirstChildElement("branch"); branch;
147        branch = branch->NextSiblingElement("branch")) {
148     if (branch->Attribute("name", branch_name.c_str())) {
149       branch_element = branch;
150       break;
151     }
152   }
153   ASSERT_NE(branch_element, nullptr)
154       << "Branch '" << branch_name << "' not found in approved builds config";
155 
156   // Test against branch EOL is there are no releases for the branch.
157   if (const auto no_releases = branch_element->FirstChildElement("no-releases");
158       no_releases != nullptr) {
159     std::chrono::year_month_day eol;
160     ASSERT_TRUE(parseDate(branch_element->Attribute("eol"), eol))
161         << "Failed to parse branch '" << branch_name
162         << "' EOL date: " << branch_element->Attribute("eol");
163     EXPECT_GE(eol, today);
164     return;
165   }
166 
167   // Test against kernel release EOL.
168   const auto lts_versions = branch_element->FirstChildElement("lts-versions");
169   const auto release_version =
170       std::format("{}.{}.{}", kernel_version.version, kernel_version.majorRev,
171                   kernel_version.minorRev);
172   tinyxml2::XMLElement* latest_release = nullptr;
173   KernelVersion latest_kernel_version;
174   for (auto release = lts_versions->FirstChildElement("release"); release;
175        release = release->NextSiblingElement("release")) {
176     if (release->Attribute("version", release_version.c_str())) {
177       EXPECT_FALSE(isReleaseEol(release->Attribute("eol")));
178       return;
179     } else if (auto kernel_version =
180                    parseKernelVersion(release->Attribute("version"));
181                latest_release == nullptr ||
182                kernel_version > latest_kernel_version) {
183       latest_release = release;
184       latest_kernel_version = kernel_version;
185     }
186   }
187 
188   // If current kernel version is newer than the latest kernel version found in
189   // the config, then this might be a kernel release which is yet to get a
190   // release config. Test against the latest kernel release version if this is
191   // the case.
192   if (kernel_version > latest_kernel_version) {
193     EXPECT_FALSE(isReleaseEol(latest_release->Attribute("eol")));
194   } else {
195     FAIL() << "Kernel release '" << release_version << "' is not recognised";
196   }
197 }
198