diff --git a/fastboot/fuzzy_fastboot/fixtures.h b/fastboot/fuzzy_fastboot/fixtures.h index efbf43c20..e47d0fd80 100644 --- a/fastboot/fuzzy_fastboot/fixtures.h +++ b/fastboot/fuzzy_fastboot/fixtures.h @@ -133,4 +133,6 @@ class FuzzAnyPartitionLocked : public ExtensionsPartition {}; class UserdataPartition : public ExtensionsPartition {}; +class SparseTestPartition : public ExtensionsPartition {}; + } // end namespace fastboot diff --git a/fastboot/fuzzy_fastboot/main.cpp b/fastboot/fuzzy_fastboot/main.cpp index 8dd46bc75..14bf5bff3 100644 --- a/fastboot/fuzzy_fastboot/main.cpp +++ b/fastboot/fuzzy_fastboot/main.cpp @@ -80,6 +80,9 @@ std::vector> std::vector> PACKED_XML_SUCCESS_TESTS; std::vector> PACKED_XML_FAIL_TESTS; +// This only has 1 or zero elements so it will disappear from gtest when empty +std::vector> + SINGLE_PARTITION_XML_WRITE_HASHABLE; const std::string DEFAULT_OUPUT_NAME = "out.img"; // const char scratch_partition[] = "userdata"; @@ -135,6 +138,22 @@ bool PartitionHash(FastBootDriver* fb, const std::string& part, std::string* has return true; } +bool SparseToBuf(sparse_file* sf, std::vector* out, bool with_crc = false) { + int64_t len = sparse_file_len(sf, true, with_crc); + if (len <= 0) { + return false; + } + out->clear(); + auto cb = [](void* priv, const void* data, size_t len) { + auto vec = static_cast*>(priv); + const char* cbuf = static_cast(data); + vec->insert(vec->end(), cbuf, cbuf + len); + return 0; + }; + + return !sparse_file_callback(sf, true, with_crc, cb, out); +} + // Only allow alphanumeric, _, -, and . const auto not_allowed = [](char c) -> int { return !(isalnum(c) || c == '_' || c == '-' || c == '.'); @@ -524,6 +543,42 @@ TEST_F(Conformance, SparseDownload3) { EXPECT_EQ(fb->Flash("userdata"), SUCCESS) << "Flashing sparse failed: " << sparse.Rep(); } +TEST_F(Conformance, SparseVersionCheck) { + SparseWrapper sparse(4096, 4096); + ASSERT_TRUE(*sparse) << "Sparse image creation failed"; + std::vector buf; + ASSERT_TRUE(SparseToBuf(*sparse, &buf)) << "Sparse buffer creation failed"; + // Invalid, right after magic + buf[4] = 0xff; + ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command"; + ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed"; + + // It can either reject this download or reject it during flash + if (HandleResponse() != DEVICE_FAIL) { + EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) + << "Flashing an invalid sparse version should fail " << sparse.Rep(); + } +} + +TEST_F(Conformance, SparseCRCCheck) { + SparseWrapper sparse(4096, 4096); + ASSERT_TRUE(*sparse) << "Sparse image creation failed"; + std::vector buf = RandomBuf(4096); + ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 0), 0) + << "Adding data failed to sparse file: " << sparse.Rep(); + ASSERT_TRUE(SparseToBuf(*sparse, &buf, true)) << "Sparse buffer creation failed"; + // Flip a bit in the crc + buf.back() = buf.back() ^ 0x01; + ASSERT_EQ(DownloadCommand(buf.size()), SUCCESS) << "Device rejected download command"; + ASSERT_EQ(SendBuffer(buf), SUCCESS) << "Downloading payload failed"; + printf("%02x\n", (unsigned char)buf.back()); + // It can either reject this download or reject it during flash + if (HandleResponse() != DEVICE_FAIL) { + EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) + << "Flashing an invalid sparse version should fail " << sparse.Rep(); + } +} + TEST_F(UnlockPermissions, Download) { std::vector buf{'a', 'o', 's', 'p'}; EXPECT_EQ(fb->Download(buf), SUCCESS) << "Download 4-byte payload failed"; @@ -767,6 +822,47 @@ TEST_F(Fuzz, CommandMissingArgs) { } } +TEST_F(Fuzz, SparseZeroLength) { + SparseWrapper sparse(4096, 0); + ASSERT_TRUE(*sparse) << "Sparse image creation failed"; + RetCode ret = fb->Download(*sparse); + // Two ways to handle it + if (ret != DEVICE_FAIL) { // if lazily parsed it better fail on a flash + EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) + << "Flashing zero length sparse image did not fail: " << sparse.Rep(); + } + ret = fb->Download(*sparse, true); + if (ret != DEVICE_FAIL) { // if lazily parsed it better fail on a flash + EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) + << "Flashing zero length sparse image did not fail " << sparse.Rep(); + } +} + +TEST_F(Fuzz, SparseTooManyChunks) { + SparseWrapper sparse(4096, 4096); // 1 block, but we send two chunks that will use 2 blocks + ASSERT_TRUE(*sparse) << "Sparse image creation failed"; + std::vector buf = RandomBuf(4096); + ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 0), 0) + << "Adding data failed to sparse file: " << sparse.Rep(); + // We take advantage of the fact the sparse library does not check this + ASSERT_EQ(sparse_file_add_fill(*sparse, 0xdeadbeef, 4096, 1), 0) + << "Adding fill to sparse file failed: " << sparse.Rep(); + + RetCode ret = fb->Download(*sparse); + // Two ways to handle it + if (ret != DEVICE_FAIL) { // if lazily parsed it better fail on a flash + EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) + << "Flashing sparse image with 'total_blks' in header 1 too small did not fail " + << sparse.Rep(); + } + ret = fb->Download(*sparse, true); + if (ret != DEVICE_FAIL) { // if lazily parsed it better fail on a flash + EXPECT_EQ(fb->Flash("userdata"), DEVICE_FAIL) + << "Flashing sparse image with 'total_blks' in header 1 too small did not fail " + << sparse.Rep(); + } +} + TEST_F(Fuzz, USBResetSpam) { auto start = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed; @@ -1379,6 +1475,109 @@ TEST_P(ExtensionsOemConformance, RunOEMTest) { INSTANTIATE_TEST_CASE_P(XMLOEM, ExtensionsOemConformance, ::testing::ValuesIn(OEM_XML_TESTS)); +// Sparse Tests +TEST_P(SparseTestPartition, SparseSingleBlock) { + const std::string name = GetParam().first; + auto part_info = GetParam().second; + const std::string part_name = name + (part_info.slots ? "_a" : ""); + SparseWrapper sparse(4096, 4096); + ASSERT_TRUE(*sparse) << "Sparse image creation failed"; + std::vector buf = RandomBuf(4096); + ASSERT_EQ(sparse_file_add_data(*sparse, buf.data(), buf.size(), 0), 0) + << "Adding data failed to sparse file: " << sparse.Rep(); + + EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep(); + EXPECT_EQ(fb->Flash(part_name), SUCCESS) << "Flashing sparse failed: " << sparse.Rep(); + std::string hash, hash_new, err_msg; + int retcode; + ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg; + ASSERT_EQ(retcode, 0) << err_msg; + // Now flash it the non-sparse way + EXPECT_EQ(fb->FlashPartition(part_name, buf), SUCCESS) << "Flashing image failed: "; + ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_new, &retcode, &err_msg)) << err_msg; + ASSERT_EQ(retcode, 0) << err_msg; + + EXPECT_EQ(hash, hash_new) << "Flashing a random buffer of 4096 using sparse and non-sparse " + "methods did not result in the same hash"; +} + +TEST_P(SparseTestPartition, SparseFill) { + const std::string name = GetParam().first; + auto part_info = GetParam().second; + const std::string part_name = name + (part_info.slots ? "_a" : ""); + int64_t size = (max_dl / 4096) * 4096; + SparseWrapper sparse(4096, size); + ASSERT_TRUE(*sparse) << "Sparse image creation failed"; + ASSERT_EQ(sparse_file_add_fill(*sparse, 0xdeadbeef, size, 0), 0) + << "Adding data failed to sparse file: " << sparse.Rep(); + + EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep(); + EXPECT_EQ(fb->Flash(part_name), SUCCESS) << "Flashing sparse failed: " << sparse.Rep(); + std::string hash, hash_new, err_msg; + int retcode; + ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg; + ASSERT_EQ(retcode, 0) << err_msg; + // Now flash it the non-sparse way + std::vector buf(size); + for (auto iter = buf.begin(); iter < buf.end(); iter += 4) { + iter[0] = 0xef; + iter[1] = 0xbe; + iter[2] = 0xad; + iter[3] = 0xde; + } + EXPECT_EQ(fb->FlashPartition(part_name, buf), SUCCESS) << "Flashing image failed: "; + ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_new, &retcode, &err_msg)) << err_msg; + ASSERT_EQ(retcode, 0) << err_msg; + + EXPECT_EQ(hash, hash_new) << "Flashing a random buffer of 4096 using sparse and non-sparse " + "methods did not result in the same hash"; +} + +// This tests to make sure it does not overwrite previous flashes +TEST_P(SparseTestPartition, SparseMultiple) { + const std::string name = GetParam().first; + auto part_info = GetParam().second; + const std::string part_name = name + (part_info.slots ? "_a" : ""); + int64_t size = (max_dl / 4096) * 4096; + SparseWrapper sparse(4096, size / 2); + ASSERT_TRUE(*sparse) << "Sparse image creation failed"; + ASSERT_EQ(sparse_file_add_fill(*sparse, 0xdeadbeef, size / 2, 0), 0) + << "Adding data failed to sparse file: " << sparse.Rep(); + EXPECT_EQ(fb->Download(*sparse), SUCCESS) << "Download sparse failed: " << sparse.Rep(); + EXPECT_EQ(fb->Flash(part_name), SUCCESS) << "Flashing sparse failed: " << sparse.Rep(); + + SparseWrapper sparse2(4096, size / 2); + ASSERT_TRUE(*sparse) << "Sparse image creation failed"; + std::vector buf = RandomBuf(size / 2); + ASSERT_EQ(sparse_file_add_data(*sparse2, buf.data(), buf.size(), (size / 2) / 4096), 0) + << "Adding data failed to sparse file: " << sparse2.Rep(); + EXPECT_EQ(fb->Download(*sparse2), SUCCESS) << "Download sparse failed: " << sparse2.Rep(); + EXPECT_EQ(fb->Flash(part_name), SUCCESS) << "Flashing sparse failed: " << sparse2.Rep(); + + std::string hash, hash_new, err_msg; + int retcode; + ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash, &retcode, &err_msg)) << err_msg; + ASSERT_EQ(retcode, 0) << err_msg; + // Now flash it the non-sparse way + std::vector fbuf(size); + for (auto iter = fbuf.begin(); iter < fbuf.begin() + size / 2; iter += 4) { + iter[0] = 0xef; + iter[1] = 0xbe; + iter[2] = 0xad; + iter[3] = 0xde; + } + fbuf.assign(buf.begin(), buf.end()); + EXPECT_EQ(fb->FlashPartition(part_name, fbuf), SUCCESS) << "Flashing image failed: "; + ASSERT_TRUE(PartitionHash(fb.get(), part_name, &hash_new, &retcode, &err_msg)) << err_msg; + ASSERT_EQ(retcode, 0) << err_msg; + + EXPECT_EQ(hash, hash_new) << "Flashing a random buffer of 4096 using sparse and non-sparse " + "methods did not result in the same hash"; +} + +INSTANTIATE_TEST_CASE_P(XMLSparseTest, SparseTestPartition, + ::testing::ValuesIn(SINGLE_PARTITION_XML_WRITE_HASHABLE)); + void GenerateXmlTests(const extension::Configuration& config) { // Build the getvar tests for (const auto it : config.getvars) { @@ -1430,6 +1629,10 @@ void GenerateXmlTests(const extension::Configuration& config) { std::make_tuple(part_info->first, part_info->second)); } + if (!PARTITION_XML_WRITE_HASHABLE.empty()) { + SINGLE_PARTITION_XML_WRITE_HASHABLE.push_back(PARTITION_XML_WRITE_HASHABLE.front()); + } + // Build oem tests for (const auto it : config.oem) { auto oem_cmd = it.second;