1 /*
2 * Copyright (C) 2025 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 <filesystem>
18 #include <iomanip>
19 #include <sstream>
20 #include <string>
21 #include <string_view>
22 #include <unordered_map>
23 #include <unordered_set>
24 #include <vector>
25
26 #include <android-base/file.h>
27 #include <android-base/logging.h>
28 #include <android-base/properties.h>
29 #include <android-base/result.h>
30 #include <android-base/strings.h>
31
32 #include <gtest/gtest.h>
33 #include <kver/kernel_release.h>
34 #include <libelf64/parse.h>
35 #include <openssl/sha.h>
36 #include <tinyxml2.h>
37 #include <vintf/VintfObject.h>
38
39 #include "ramdisk_utils.h"
40
41 namespace android {
42 namespace {
43
44 constexpr std::string_view kOptionalKernelModulesConfigPath =
45 "/system/etc/kernel/kernel-modules.xml";
46
47 class ModinfoTags {
48 public:
ParseData(const std::vector<char> & data)49 void ParseData(const std::vector<char>& data) {
50 size_t offset = 0;
51 while (offset < data.size()) {
52 std::string_view chunk(data.data() + offset);
53 offset += chunk.size() + 1;
54 // Probably just padding
55 if (chunk.empty()) continue;
56 const auto delimiter = chunk.find('=');
57 // Malformed chunk, just ignore it.
58 if (delimiter == std::string_view::npos) continue;
59 tags_[std::string(chunk.substr(0, delimiter))].emplace_back(
60 chunk.substr(delimiter + 1));
61 }
62 }
63
64 // Returns all the values found for the given |tag| joined with a new line.
TagValue(const std::string & tag) const65 std::string TagValue(const std::string& tag) const {
66 const auto& tag_values = tags_.find(tag);
67 if (tag_values == tags_.end()) {
68 return "";
69 }
70 return android::base::Join(tag_values->second, "\n");
71 }
72
73 private:
74 std::unordered_map<std::string, std::vector<std::string>> tags_;
75 };
76
AddModulesFromPath(std::unordered_set<std::string> & modules,const std::string_view & config_path,bool optional)77 android::base::Result<void> AddModulesFromPath(
78 std::unordered_set<std::string>& modules,
79 const std::string_view& config_path, bool optional) {
80 if (!std::filesystem::exists(config_path)) {
81 if (optional) {
82 GTEST_LOG_(INFO) << "Config file " << config_path << " does not exist.";
83 return {};
84 }
85 return android::base::Error()
86 << "Config file " << config_path << " does not exist.";
87 }
88 std::string kernel_modules_content;
89 if (!android::base::ReadFileToString(std::string(config_path),
90 &kernel_modules_content)) {
91 return android::base::ErrnoError()
92 << "Failed to read file at " << config_path;
93 }
94 tinyxml2::XMLDocument kernel_modules_xml;
95 const auto& xml_error =
96 kernel_modules_xml.Parse(kernel_modules_content.c_str());
97 if (tinyxml2::XMLError::XML_SUCCESS != xml_error) {
98 return android::base::Error()
99 << "Failed to parse kernel modules config: "
100 << tinyxml2::XMLDocument::ErrorIDToName(xml_error);
101 }
102 const tinyxml2::XMLElement* const kernel_modules_element =
103 kernel_modules_xml.RootElement();
104 for (const tinyxml2::XMLElement* module_element =
105 kernel_modules_element->FirstChildElement("module");
106 module_element != nullptr;
107 module_element = module_element->NextSiblingElement("module")) {
108 modules.insert(std::string(module_element->Attribute("value")));
109 }
110 return {};
111 }
112
GetAckModules()113 android::base::Result<std::unordered_set<std::string>> GetAckModules() {
114 std::unordered_set<std::string> modules;
115 // Load information from the test data.
116 const auto& exec_dir = android::base::GetExecutableDirectory();
117 const auto& kernel_modules_config = exec_dir + "/kernel-modules.xml";
118 if (!AddModulesFromPath(modules, kernel_modules_config,
119 /* optional = */ false)
120 .ok()) {
121 return android::base::Error()
122 << "Failed to read test data from " << kernel_modules_config;
123 }
124 // Then check if there is additional information available from the device.
125 if (!AddModulesFromPath(modules, kOptionalKernelModulesConfigPath,
126 /* optional = */ true)
127 .ok()) {
128 return android::base::Error() << "Failed to read test data from "
129 << kOptionalKernelModulesConfigPath;
130 }
131 return modules;
132 }
133
sha256(const std::string & content)134 std::string sha256(const std::string& content) {
135 unsigned char hash[SHA256_DIGEST_LENGTH];
136 const unsigned char* data = (const unsigned char*)content.data();
137 SHA256(data, content.size(), hash);
138 std::ostringstream os;
139 os << std::hex << std::setfill('0');
140 for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
141 os << std::setw(2) << static_cast<unsigned int>(hash[i]);
142 }
143 return os.str();
144 }
145
146 class BuiltWithDdkTest : public testing::Test {
147 protected:
SetUp()148 void SetUp() override {
149 // Fetch device runtime information.
150 const auto& runtime_info = android::vintf::VintfObject::GetRuntimeInfo();
151 ASSERT_NE(nullptr, runtime_info);
152
153 constexpr uint64_t kMinAndroidRelease = 16;
154 const auto& kernel_version = runtime_info->kernelVersion();
155 const auto& kernel_release = android::kver::KernelRelease::Parse(
156 runtime_info->osRelease(), /* allow_suffix = */ true);
157
158 if (!kernel_release.has_value() ||
159 (kernel_release->android_release() < kMinAndroidRelease)) {
160 GTEST_SKIP() << "The test only applies to android" << kMinAndroidRelease
161 << " or later kernels.";
162 }
163 if (runtime_info->kernelVersion().dropMinor() <
164 android::vintf::Version{6, 12}) {
165 GTEST_SKIP() << "Exempt from built with DDK test. Kernel: "
166 << kernel_version.version << " " << kernel_version.majorRev;
167 }
168
169 slot_suffix_ = android::base::GetProperty("ro.boot.slot_suffix", "");
170
171 // Get the information of ACK modules from the test data and from the system
172 // if available.
173 ack_modules_ = GetAckModules();
174 ASSERT_RESULT_OK(ack_modules_) << "Unable to read list of ACK modules.";
175 }
176 android::base::Result<std::unordered_set<std::string>> ack_modules_;
177 std::string slot_suffix_;
178 };
179
ModuleHash(const std::string & name,const std::string & author,const std::string & license)180 std::string ModuleHash(const std::string& name, const std::string& author,
181 const std::string& license) {
182 return sha256(name + author + license);
183 }
184
AddModulesFromPartition(std::vector<std::filesystem::path> & modules,const std::string & partition)185 android::base::Result<void> AddModulesFromPartition(
186 std::vector<std::filesystem::path>& modules, const std::string& partition) {
187 int modules_found = 0;
188 if (!std::filesystem::is_directory(partition)) {
189 return android::base::Error() << "Unable to analyze path " << partition;
190 }
191 for (const auto& path_entry :
192 std::filesystem::recursive_directory_iterator(partition)) {
193 if (path_entry.path().extension() == ".ko") {
194 modules.push_back(path_entry);
195 ++modules_found;
196 }
197 }
198 GTEST_LOG_(INFO) << modules_found << " modules found within " << partition;
199 return {};
200 }
201
InspectModule(const std::unordered_set<std::string> & ack_modules,const std::filesystem::path & module_path)202 android::base::Result<void> InspectModule(
203 const std::unordered_set<std::string>& ack_modules,
204 const std::filesystem::path& module_path) {
205 android::elf64::Elf64Binary elf;
206 if (!android::elf64::Elf64Parser::ParseElfFile(module_path, elf)) {
207 GTEST_LOG_(WARNING) << "Unable to parse module at " << module_path;
208 return {};
209 }
210 ModinfoTags modinfo_tags;
211 for (int i = 0; i < elf.sections.size(); i++) {
212 android::elf64::Elf64_Sc& section = elf.sections[i];
213 // Skip irrelevant sections
214 if (section.name != ".modinfo") continue;
215 // Ensure the buffer is zero terminated.
216 if (section.data.back() != '\0') {
217 section.data.push_back('\0');
218 }
219 modinfo_tags.ParseData(section.data);
220 break;
221 }
222 // GKI Module
223 // TODO: b/374932907 -- Despite the fact that technically GKI modules are a
224 // subset of ACK modules add a dedicated check for them in V2.
225
226 // ACK
227 const std::string module_hash =
228 ModuleHash(modinfo_tags.TagValue("name"), modinfo_tags.TagValue("author"),
229 modinfo_tags.TagValue("license"));
230 if (ack_modules.contains(module_hash)) {
231 return {};
232 }
233 // DDK
234 if (modinfo_tags.TagValue("built_with") == "DDK") {
235 return {};
236 }
237 return android::base::Error()
238 << "Non compliant module found: " << module_path;
239 }
240
241 // @VsrTest = 3.4.2
TEST_F(BuiltWithDdkTest,SystemModules)242 TEST_F(BuiltWithDdkTest, SystemModules) {
243 std::vector<std::filesystem::path> device_module_paths;
244 ASSERT_RESULT_OK(
245 AddModulesFromPartition(device_module_paths, "/vendor_dlkm/"));
246 ASSERT_RESULT_OK(
247 AddModulesFromPartition(device_module_paths, "/system_dlkm/"));
248
249 // Run the inspection for each module found.
250 for (const auto& module_path : device_module_paths) {
251 EXPECT_RESULT_OK(InspectModule(ack_modules_.value(), module_path));
252 }
253 }
254
InspectExtractedRamdisk(const std::filesystem::path & extracted_ramdisk_path,const std::unordered_set<std::string> & ack_modules)255 void InspectExtractedRamdisk(
256 const std::filesystem::path& extracted_ramdisk_path,
257 const std::unordered_set<std::string>& ack_modules) {
258 std::vector<std::filesystem::path> kernel_module_paths;
259
260 for (auto& path_entry :
261 std::filesystem::recursive_directory_iterator(extracted_ramdisk_path)) {
262 if (path_entry.path().extension() == ".ko") {
263 kernel_module_paths.push_back(path_entry);
264 }
265 }
266
267 // Run the inspection for each module found.
268 for (const auto& module_path : kernel_module_paths) {
269 EXPECT_RESULT_OK(InspectModule(ack_modules, module_path));
270 }
271 }
272
273 // @VsrTest = 3.4.2
TEST_F(BuiltWithDdkTest,BootModules)274 TEST_F(BuiltWithDdkTest, BootModules) {
275 const std::string boot_path = "/dev/block/by-name/init_boot" + slot_suffix_;
276 if (!std::filesystem::exists(boot_path)) {
277 GTEST_SKIP() << "Boot path " << boot_path << " does not exist.";
278 }
279 const auto extracted_ramdisk = android::ExtractRamdiskToDirectory(boot_path);
280 ASSERT_TRUE(extracted_ramdisk.ok())
281 << "Failed to extract ramdisk: " << extracted_ramdisk.error();
282 InspectExtractedRamdisk((*extracted_ramdisk)->path, ack_modules_.value());
283 }
284
285 // TODO: b/374932907 -- Verify if this is enough for vendor_kernel_boot as well.
286 // @VsrTest = 3.4.2
TEST_F(BuiltWithDdkTest,VendorBootModules)287 TEST_F(BuiltWithDdkTest, VendorBootModules) {
288 const std::string vendor_boot_path =
289 "/dev/block/by-name/vendor_boot" + slot_suffix_;
290
291 if (!std::filesystem::exists(vendor_boot_path)) {
292 GTEST_SKIP() << "Boot path " << vendor_boot_path << " does not exist.";
293 }
294 const auto extracted_vendor_ramdisk =
295 android::ExtractVendorRamdiskToDirectory(vendor_boot_path);
296
297 ASSERT_TRUE(extracted_vendor_ramdisk.ok())
298 << "Failed to extract vendor_ramdisk: "
299 << extracted_vendor_ramdisk.error();
300
301 InspectExtractedRamdisk((*extracted_vendor_ramdisk)->path,
302 ack_modules_.value());
303 }
304
305 } // namespace
306 } // namespace android
307
main(int argc,char * argv[])308 int main(int argc, char* argv[]) {
309 ::testing::InitGoogleTest(&argc, argv);
310 android::base::InitLogging(argv, android::base::StderrLogger);
311 return RUN_ALL_TESTS();
312 }
313