1 /*
2 * Copyright (C) 2021 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 <string.h>
18 #include <sys/stat.h>
19 #include <sys/types.h>
20 #include <unistd.h>
21
22 #include <filesystem>
23 #include <optional>
24 #include <string_view>
25
26 #include <android-base/file.h>
27 #include <android-base/logging.h>
28 #include <android-base/result.h>
29 #include <android-base/strings.h>
30 #include <bootimg.h>
31 #include <gmock/gmock.h>
32 #include <gtest/gtest.h>
33 #include <libavb/libavb.h>
34
35 #include "vendor_boot_img_utils.h"
36
37 using android::base::borrowed_fd;
38 using android::base::ErrnoError;
39 using android::base::GetExecutableDirectory;
40 using android::base::ReadFdToString;
41 using android::base::Result;
42 using testing::AllOf;
43 using testing::Each;
44 using testing::Eq;
45 using testing::HasSubstr;
46 using testing::Not;
47 using testing::Property;
48 using std::string_literals::operator""s;
49
50 // Expect that the Result<T> returned by |expr| is successful, and value matches |result_matcher|.
51 #define EXPECT_RESULT(expr, result_matcher) \
52 EXPECT_THAT(expr, AllOf(Property(&decltype(expr)::ok, Eq(true)), \
53 Property(&decltype(expr)::value, result_matcher)))
54
55 // Expect that the Result<T> returned by |expr| fails, and error message matches |error_matcher|.
56 #define EXPECT_ERROR(expr, error_matcher) \
57 do { \
58 EXPECT_THAT( \
59 expr, \
60 AllOf(Property(&decltype(expr)::ok, Eq(false)), \
61 Property(&decltype(expr)::error, \
62 Property(&decltype(expr)::error_type::message, error_matcher)))); \
63 } while (0)
64
65 namespace {
66
67 // Wrapper of fstat.
FileSize(borrowed_fd fd,std::filesystem::path path)68 Result<uint64_t> FileSize(borrowed_fd fd, std::filesystem::path path) {
69 struct stat sb;
70 if (fstat(fd.get(), &sb) == -1) return ErrnoError() << "fstat(" << path << ")";
71 return sb.st_size;
72 }
73
74 // Seek to beginning then read the whole file.
ReadStartOfFdToString(borrowed_fd fd,std::filesystem::path path)75 Result<std::string> ReadStartOfFdToString(borrowed_fd fd, std::filesystem::path path) {
76 if (lseek64(fd.get(), 0, SEEK_SET) != 0)
77 return ErrnoError() << "lseek64(" << path << ", 0, SEEK_SET)";
78 std::string content;
79 if (!android::base::ReadFdToString(fd, &content)) return ErrnoError() << "read(" << path << ")";
80 return content;
81 }
82
83 // Round |value| up to page boundary.
round_up(uint32_t value,uint32_t page_size)84 inline uint32_t round_up(uint32_t value, uint32_t page_size) {
85 return (value + page_size - 1) / page_size * page_size;
86 }
87
88 // Match is successful if |arg| is a zero-padded version of |expected|.
89 MATCHER_P(IsPadded, expected, (negation ? "is" : "isn't") + " zero-padded of expected value"s) {
90 if (arg.size() < expected.size()) return false;
91 if (0 != memcmp(arg.data(), expected.data(), expected.size())) return false;
92 auto remainder = std::string_view(arg).substr(expected.size());
93 for (char e : remainder)
94 if (e != '\0') return false;
95 return true;
96 }
97
98 // Same as Eq, but don't print the content to avoid spam.
99 MATCHER_P(MemEq, expected, (negation ? "is" : "isn't") + " expected value"s) {
100 if (arg.size() != expected.size()) return false;
101 return 0 == memcmp(arg.data(), expected.data(), expected.size());
102 }
103
104 // Expect that |arg| and |expected| has the same AVB footer.
105 MATCHER_P(HasSameAvbFooter, expected,
106 (negation ? "has" : "does not have") + "expected AVB footer"s) {
107 if (expected.size() < AVB_FOOTER_SIZE || arg.size() < AVB_FOOTER_SIZE) return false;
108 return std::string_view(expected).substr(expected.size() - AVB_FOOTER_SIZE) ==
109 std::string_view(arg).substr(arg.size() - AVB_FOOTER_SIZE);
110 }
111
112 // A lazy handle of a file.
113 struct TestFileHandle {
114 virtual ~TestFileHandle() = default;
115 // Lazily call OpenImpl(), cache result in open_result_.
Open__anon39f087e00111::TestFileHandle116 android::base::Result<void> Open() {
117 if (!open_result_.has_value()) open_result_ = OpenImpl();
118 return open_result_.value();
119 }
120 // The original size at the time when the file is opened. If the file has been modified,
121 // this field is NOT updated.
size__anon39f087e00111::TestFileHandle122 uint64_t size() {
123 CHECK(open_result_.has_value());
124 return size_;
125 }
126 // The current size of the file. If the file has been modified since opened,
127 // this is updated.
fsize__anon39f087e00111::TestFileHandle128 Result<uint64_t> fsize() {
129 CHECK(open_result_.has_value());
130 return FileSize(fd_, abs_path_);
131 }
fd__anon39f087e00111::TestFileHandle132 borrowed_fd fd() {
133 CHECK(open_result_.has_value());
134 return fd_;
135 }
Read__anon39f087e00111::TestFileHandle136 Result<std::string> Read() {
137 CHECK(open_result_.has_value());
138 return ReadStartOfFdToString(fd_, abs_path_);
139 }
140
141 private:
142 std::filesystem::path abs_path_;
143 uint64_t size_;
144 std::optional<android::base::Result<void>> open_result_;
145 borrowed_fd fd_{-1};
146 // Opens |rel_path_| as a readonly fd, pass it to Transform, and store result to
147 // |borrowed_fd_|.
OpenImpl__anon39f087e00111::TestFileHandle148 android::base::Result<void> OpenImpl() {
149 android::base::unique_fd read_fd(TEMP_FAILURE_RETRY(
150 open(abs_path_.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_BINARY)));
151 if (!read_fd.ok()) return ErrnoError() << "open(" << abs_path_ << ")";
152 auto size = FileSize(read_fd, abs_path_);
153 if (!size.ok()) return size.error();
154 size_ = *size;
155
156 auto borrowed_fd = Transform(abs_path_, std::move(read_fd));
157 if (!borrowed_fd.ok()) return borrowed_fd.error();
158 fd_ = borrowed_fd.value();
159
160 return {};
161 }
162
163 protected:
164 // |rel_path| is the relative path under test data directory.
TestFileHandle__anon39f087e00111::TestFileHandle165 TestFileHandle(const std::filesystem::path& rel_path)
166 : abs_path_(std::move(std::filesystem::path(GetExecutableDirectory()) / rel_path)) {}
167 // Given |read_fd|, the readonly fd on the test file, return an fd that's suitable for client
168 // to use. Implementation is responsible for managing the lifetime of the returned fd.
169 virtual android::base::Result<borrowed_fd> Transform(const std::filesystem::path& abs_path,
170 android::base::unique_fd read_fd) = 0;
171 };
172
173 // A TestFileHandle where the file is readonly.
174 struct ReadOnlyTestFileHandle : TestFileHandle {
ReadOnlyTestFileHandle__anon39f087e00111::ReadOnlyTestFileHandle175 ReadOnlyTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {}
176
177 private:
178 android::base::unique_fd owned_fd_;
Transform__anon39f087e00111::ReadOnlyTestFileHandle179 android::base::Result<borrowed_fd> Transform(const std::filesystem::path&,
180 android::base::unique_fd read_fd) override {
181 owned_fd_ = std::move(read_fd);
182 return owned_fd_;
183 }
184 };
185
186 // A TestFileHandle where the test file is copies, hence read-writable.
187 struct ReadWriteTestFileHandle : TestFileHandle {
ReadWriteTestFileHandle__anon39f087e00111::ReadWriteTestFileHandle188 ReadWriteTestFileHandle(const std::filesystem::path& rel_path) : TestFileHandle(rel_path) {}
189
190 private:
191 std::unique_ptr<TemporaryFile> temp_file_;
192
Transform__anon39f087e00111::ReadWriteTestFileHandle193 android::base::Result<borrowed_fd> Transform(const std::filesystem::path& abs_path,
194 android::base::unique_fd read_fd) override {
195 // Make a copy to avoid writing to test data. Test files are small, so it is okay
196 // to read the whole file.
197 auto content = ReadStartOfFdToString(read_fd, abs_path);
198 if (!content.ok()) return content.error();
199 temp_file_ = std::make_unique<TemporaryFile>();
200 if (temp_file_->fd == -1)
201 return ErrnoError() << "copy " << abs_path << ": open temp file failed";
202 if (!android::base::WriteStringToFd(*content, temp_file_->fd))
203 return ErrnoError() << "copy " << abs_path << ": write temp file failed";
204
205 return temp_file_->fd;
206 }
207 };
208
209 class RepackVendorBootImgTestEnv : public ::testing::Environment {
210 public:
SetUp()211 virtual void SetUp() {
212 OpenTestFile("test_dtb.img", &dtb, &dtb_content);
213 OpenTestFile("test_bootconfig.img", &bootconfig, &bootconfig_content);
214 OpenTestFile("test_vendor_ramdisk_none.img", &none, &none_content);
215 OpenTestFile("test_vendor_ramdisk_platform.img", &platform, &platform_content);
216 OpenTestFile("test_vendor_ramdisk_replace.img", &replace, &replace_content);
217 }
218
219 std::unique_ptr<TestFileHandle> dtb;
220 std::string dtb_content;
221 std::unique_ptr<TestFileHandle> bootconfig;
222 std::string bootconfig_content;
223 std::unique_ptr<TestFileHandle> none;
224 std::string none_content;
225 std::unique_ptr<TestFileHandle> platform;
226 std::string platform_content;
227 std::unique_ptr<TestFileHandle> replace;
228 std::string replace_content;
229
230 private:
OpenTestFile(const char * rel_path,std::unique_ptr<TestFileHandle> * handle,std::string * content)231 void OpenTestFile(const char* rel_path, std::unique_ptr<TestFileHandle>* handle,
232 std::string* content) {
233 *handle = std::make_unique<ReadOnlyTestFileHandle>(rel_path);
234 ASSERT_RESULT_OK((*handle)->Open());
235 auto content_res = (*handle)->Read();
236 ASSERT_RESULT_OK(content_res);
237 *content = *content_res;
238 }
239 };
240 RepackVendorBootImgTestEnv* env = nullptr;
241
242 struct RepackVendorBootImgTestParam {
243 std::string vendor_boot_file_name;
244 uint32_t expected_header_version;
operator <<(std::ostream & os,const RepackVendorBootImgTestParam & param)245 friend std::ostream& operator<<(std::ostream& os, const RepackVendorBootImgTestParam& param) {
246 return os << param.vendor_boot_file_name;
247 }
248 };
249
250 class RepackVendorBootImgTest : public ::testing::TestWithParam<RepackVendorBootImgTestParam> {
251 public:
SetUp()252 virtual void SetUp() {
253 vboot = std::make_unique<ReadWriteTestFileHandle>(GetParam().vendor_boot_file_name);
254 ASSERT_RESULT_OK(vboot->Open());
255 }
256 std::unique_ptr<TestFileHandle> vboot;
257 };
258
TEST_P(RepackVendorBootImgTest,InvalidSize)259 TEST_P(RepackVendorBootImgTest, InvalidSize) {
260 EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size() + 1, "default",
261 env->replace->fd(), env->replace->size()),
262 HasSubstr("Size of vendor boot does not match"));
263 EXPECT_ERROR(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default", env->replace->fd(),
264 env->replace->size() + 1),
265 HasSubstr("Size of new vendor ramdisk does not match"));
266 }
267
TEST_P(RepackVendorBootImgTest,ReplaceUnknown)268 TEST_P(RepackVendorBootImgTest, ReplaceUnknown) {
269 auto res = replace_vendor_ramdisk(vboot->fd(), vboot->size(), "unknown", env->replace->fd(),
270 env->replace->size());
271 if (GetParam().expected_header_version == 3) {
272 EXPECT_ERROR(res, Eq("Require vendor boot header V4 but is V3"));
273 } else if (GetParam().expected_header_version == 4) {
274 EXPECT_ERROR(res, Eq("Vendor ramdisk 'unknown' not found"));
275 }
276 }
277
TEST_P(RepackVendorBootImgTest,ReplaceDefault)278 TEST_P(RepackVendorBootImgTest, ReplaceDefault) {
279 auto old_content = vboot->Read();
280 ASSERT_RESULT_OK(old_content);
281
282 ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), "default",
283 env->replace->fd(), env->replace->size()));
284 EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack";
285
286 auto new_content_res = vboot->Read();
287 ASSERT_RESULT_OK(new_content_res);
288 std::string_view new_content(*new_content_res);
289
290 auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v3*>(new_content.data());
291 ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE));
292 ASSERT_EQ(GetParam().expected_header_version, hdr->header_version);
293 EXPECT_EQ(hdr->vendor_ramdisk_size, env->replace->size());
294 EXPECT_EQ(hdr->dtb_size, env->dtb->size());
295
296 auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size);
297 auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
298 auto q = round_up(hdr->dtb_size, hdr->page_size);
299
300 EXPECT_THAT(new_content.substr(o, p), IsPadded(env->replace_content));
301 EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content));
302
303 if (hdr->header_version < 4) return;
304
305 auto hdr_v4 = static_cast<const vendor_boot_img_hdr_v4*>(hdr);
306 EXPECT_EQ(hdr_v4->vendor_ramdisk_table_entry_num, 1);
307 EXPECT_EQ(hdr_v4->vendor_ramdisk_table_size, 1 * hdr_v4->vendor_ramdisk_table_entry_size);
308 EXPECT_GE(hdr_v4->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4));
309 auto entry = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(&new_content[o + p + q]);
310 EXPECT_EQ(entry->ramdisk_offset, 0);
311 EXPECT_EQ(entry->ramdisk_size, hdr_v4->vendor_ramdisk_size);
312 EXPECT_EQ(entry->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE);
313
314 EXPECT_EQ(hdr_v4->bootconfig_size, env->bootconfig->size());
315 auto r = round_up(hdr_v4->vendor_ramdisk_table_size, hdr_v4->page_size);
316 auto s = round_up(hdr_v4->bootconfig_size, hdr_v4->page_size);
317 EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content));
318
319 EXPECT_THAT(new_content, HasSameAvbFooter(*old_content));
320 }
321
322 INSTANTIATE_TEST_SUITE_P(
323 RepackVendorBootImgTest, RepackVendorBootImgTest,
324 ::testing::Values(RepackVendorBootImgTestParam{"vendor_boot_v3.img", 3},
325 RepackVendorBootImgTestParam{"vendor_boot_v4_with_frag.img", 4},
326 RepackVendorBootImgTestParam{"vendor_boot_v4_without_frag.img", 4}),
__anon39f087e00202(const auto& info) 327 [](const auto& info) {
328 return android::base::StringReplace(info.param.vendor_boot_file_name, ".", "_", false);
329 });
330
GetRamdiskName(const vendor_ramdisk_table_entry_v4 * entry)331 std::string_view GetRamdiskName(const vendor_ramdisk_table_entry_v4* entry) {
332 auto ramdisk_name = reinterpret_cast<const char*>(entry->ramdisk_name);
333 return std::string_view(ramdisk_name, strnlen(ramdisk_name, VENDOR_RAMDISK_NAME_SIZE));
334 }
335
336 class RepackVendorBootImgTestV4 : public ::testing::TestWithParam<uint32_t /* ramdisk type */> {
337 public:
SetUp()338 virtual void SetUp() {
339 vboot = std::make_unique<ReadWriteTestFileHandle>("vendor_boot_v4_with_frag.img");
340 ASSERT_RESULT_OK(vboot->Open());
341 }
342 std::unique_ptr<TestFileHandle> vboot;
343 };
344
TEST_P(RepackVendorBootImgTestV4,Replace)345 TEST_P(RepackVendorBootImgTestV4, Replace) {
346 uint32_t replace_ramdisk_type = GetParam();
347 std::string replace_ramdisk_name;
348 std::string expect_new_ramdisk_content;
349 uint32_t expect_none_size = env->none->size();
350 uint32_t expect_platform_size = env->platform->size();
351 switch (replace_ramdisk_type) {
352 case VENDOR_RAMDISK_TYPE_NONE:
353 replace_ramdisk_name = "none_ramdisk";
354 expect_new_ramdisk_content = env->replace_content + env->platform_content;
355 expect_none_size = env->replace->size();
356 break;
357 case VENDOR_RAMDISK_TYPE_PLATFORM:
358 replace_ramdisk_name = "platform_ramdisk";
359 expect_new_ramdisk_content = env->none_content + env->replace_content;
360 expect_platform_size = env->replace->size();
361 break;
362 default:
363 LOG(FATAL) << "Ramdisk type " << replace_ramdisk_type
364 << " is not supported by this test.";
365 }
366
367 auto old_content = vboot->Read();
368 ASSERT_RESULT_OK(old_content);
369
370 ASSERT_RESULT_OK(replace_vendor_ramdisk(vboot->fd(), vboot->size(), replace_ramdisk_name,
371 env->replace->fd(), env->replace->size()));
372 EXPECT_RESULT(vboot->fsize(), vboot->size()) << "File size should not change after repack";
373
374 auto new_content_res = vboot->Read();
375 ASSERT_RESULT_OK(new_content_res);
376 std::string_view new_content(*new_content_res);
377
378 auto hdr = reinterpret_cast<const vendor_boot_img_hdr_v4*>(new_content.data());
379 ASSERT_EQ(0, memcmp(VENDOR_BOOT_MAGIC, hdr->magic, VENDOR_BOOT_MAGIC_SIZE));
380 ASSERT_EQ(4, hdr->header_version);
381 EXPECT_EQ(hdr->vendor_ramdisk_size, expect_none_size + expect_platform_size);
382 EXPECT_EQ(hdr->dtb_size, env->dtb->size());
383 EXPECT_EQ(hdr->bootconfig_size, env->bootconfig->size());
384
385 auto o = round_up(sizeof(vendor_boot_img_hdr_v3), hdr->page_size);
386 auto p = round_up(hdr->vendor_ramdisk_size, hdr->page_size);
387 auto q = round_up(hdr->dtb_size, hdr->page_size);
388 auto r = round_up(hdr->vendor_ramdisk_table_size, hdr->page_size);
389 auto s = round_up(hdr->bootconfig_size, hdr->page_size);
390
391 EXPECT_THAT(new_content.substr(o, p), IsPadded(expect_new_ramdisk_content));
392 EXPECT_THAT(new_content.substr(o + p, q), IsPadded(env->dtb_content));
393
394 // Check changes in table.
395 EXPECT_EQ(hdr->vendor_ramdisk_table_entry_num, 2);
396 EXPECT_EQ(hdr->vendor_ramdisk_table_size, 2 * hdr->vendor_ramdisk_table_entry_size);
397 EXPECT_GE(hdr->vendor_ramdisk_table_entry_size, sizeof(vendor_ramdisk_table_entry_v4));
398 auto entry_none =
399 reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(&new_content[o + p + q]);
400 EXPECT_EQ(entry_none->ramdisk_offset, 0);
401 EXPECT_EQ(entry_none->ramdisk_size, expect_none_size);
402 EXPECT_EQ(entry_none->ramdisk_type, VENDOR_RAMDISK_TYPE_NONE);
403 EXPECT_EQ(GetRamdiskName(entry_none), "none_ramdisk");
404
405 auto entry_platform = reinterpret_cast<const vendor_ramdisk_table_entry_v4*>(
406 &new_content[o + p + q + hdr->vendor_ramdisk_table_entry_size]);
407 EXPECT_EQ(entry_platform->ramdisk_offset, expect_none_size);
408 EXPECT_EQ(entry_platform->ramdisk_size, expect_platform_size);
409 EXPECT_EQ(entry_platform->ramdisk_type, VENDOR_RAMDISK_TYPE_PLATFORM);
410 EXPECT_EQ(GetRamdiskName(entry_platform), "platform_ramdisk");
411
412 EXPECT_THAT(new_content.substr(o + p + q + r, s), IsPadded(env->bootconfig_content));
413
414 EXPECT_THAT(new_content, HasSameAvbFooter(*old_content));
415 }
416 INSTANTIATE_TEST_SUITE_P(RepackVendorBootImgTest, RepackVendorBootImgTestV4,
417 ::testing::Values(VENDOR_RAMDISK_TYPE_NONE, VENDOR_RAMDISK_TYPE_PLATFORM),
__anon39f087e00302(const auto& info) 418 [](const auto& info) {
419 return info.param == VENDOR_RAMDISK_TYPE_NONE ? "none" : "platform";
420 });
421
422 } // namespace
423
main(int argc,char * argv[])424 int main(int argc, char* argv[]) {
425 ::testing::InitGoogleTest(&argc, argv);
426 env = static_cast<RepackVendorBootImgTestEnv*>(
427 testing::AddGlobalTestEnvironment(new RepackVendorBootImgTestEnv));
428 return RUN_ALL_TESTS();
429 }
430