// Copyright 2021 The Pigweed Authors // // 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 // // https://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 #include "gtest/gtest.h" #include "pw_kvs/fake_flash_memory.h" #include "pw_kvs/test_key_value_store.h" #include "pw_software_update/bundled_update_backend.h" #include "pw_software_update/update_bundle_accessor.h" #include "pw_stream/memory_stream.h" #include "test_bundles.h" #define ASSERT_OK(status) ASSERT_EQ(OkStatus(), status) #define ASSERT_FAIL(status) ASSERT_NE(OkStatus(), status) namespace pw::software_update { namespace { constexpr size_t kBufferSize = 256; static constexpr size_t kFlashAlignment = 16; constexpr size_t kSectorSize = 2048; constexpr size_t kSectorCount = 2; constexpr size_t kMetadataBufferSize = blob_store::BlobStore::BlobWriter::RequiredMetadataBufferSize(0); class TestBundledUpdateBackend final : public BundledUpdateBackend { public: TestBundledUpdateBackend() : manifest_reader_({}), trusted_root_memory_reader_({}) {} Status ApplyReboot() override { return Status::Unimplemented(); } Status PostRebootFinalize() override { return OkStatus(); } Status ApplyTargetFile(std::string_view, stream::Reader&, size_t) override { return OkStatus(); } Result EnableBundleTransferHandler(std::string_view) override { return 0; } void DisableBundleTransferHandler() override {} void SetTrustedRoot(ConstByteSpan trusted_root) { trusted_root_memory_reader_ = stream::MemoryReader(trusted_root); trusted_root_reader_ = stream::IntervalReader( trusted_root_memory_reader_, 0, trusted_root_memory_reader_.ConservativeReadLimit()); } void SetCurrentManifest(ConstByteSpan current_manifest) { manifest_reader_ = stream::MemoryReader(current_manifest); } void SetManifestWriter(stream::Writer* writer) { manifest_writer_ = writer; } virtual Result GetRootMetadataReader() override { return &trusted_root_reader_; }; Status BeforeManifestRead() override { before_manifest_read_called_ = true; if (manifest_reader_.ConservativeReadLimit() > 0) { return OkStatus(); } return Status::NotFound(); }; bool BeforeManifestReadCalled() { return before_manifest_read_called_; } Result GetManifestReader() override { return &manifest_reader_; } Status BeforeManifestWrite() override { before_manifest_write_called_ = true; return (manifest_writer_) ? OkStatus() : Status::NotFound(); } bool BeforeManifestWriteCalled() { return before_manifest_write_called_; } Status AfterManifestWrite() override { after_manifest_write_called_ = true; return OkStatus(); } bool AfterManifestWriteCalled() { return after_manifest_write_called_; } Result GetManifestWriter() override { return manifest_writer_; } virtual Status SafelyPersistRootMetadata( [[maybe_unused]] stream::IntervalReader root_metadata) override { new_root_persisted_ = true; trusted_root_reader_ = root_metadata; return OkStatus(); }; bool IsNewRootPersisted() const { return new_root_persisted_; } private: stream::IntervalReader trusted_root_reader_; stream::MemoryReader manifest_reader_; stream::Writer* manifest_writer_ = nullptr; bool before_manifest_read_called_ = false; bool before_manifest_write_called_ = false; bool after_manifest_write_called_ = false; bool new_root_persisted_ = false; size_t backend_verified_files_ = 0; // A memory reader for buffer passed by SetTrustedRoot(). This will be used // to back `trusted_root_reader_` stream::MemoryReader trusted_root_memory_reader_; }; class UpdateBundleTest : public testing::Test { public: UpdateBundleTest() : blob_flash_(kFlashAlignment), blob_partition_(&blob_flash_), bundle_blob_("TestBundle", blob_partition_, nullptr, kvs::TestKvs(), kBufferSize) {} blob_store::BlobStoreBuffer& bundle_blob() { return bundle_blob_; } TestBundledUpdateBackend& backend() { return backend_; } void StageTestBundle(ConstByteSpan bundle_data) { ASSERT_OK(bundle_blob_.Init()); blob_store::BlobStore::BlobWriter blob_writer(bundle_blob(), metadata_buffer_); ASSERT_OK(blob_writer.Open()); ASSERT_OK(blob_writer.Write(bundle_data)); ASSERT_OK(blob_writer.Close()); } // A helper to verify that all bundle operations are disallowed because // the bundle is not open or verified. void VerifyAllBundleOperationsDisallowed( UpdateBundleAccessor& update_bundle) { // We need to check specificially that failure is due to rejecting // unverified/unopen bundle, not anything else. ASSERT_EQ(update_bundle.GetManifest().status(), Status::FailedPrecondition()); ASSERT_EQ(update_bundle.GetTargetPayload("any").status(), Status::FailedPrecondition()); ASSERT_EQ(update_bundle.GetTargetPayload(protobuf::String({})).status(), Status::FailedPrecondition()); ASSERT_EQ(update_bundle.PersistManifest(), Status::FailedPrecondition()); ASSERT_EQ(update_bundle.GetTotalPayloadSize().status(), Status::FailedPrecondition()); } // A helper to verify that UpdateBundleAccessor::OpenAndVerify() fails and // that all bundle operations are disallowed as a result. Also check whether // root metadata should be expected to be persisted. void CheckOpenAndVerifyFail(UpdateBundleAccessor& update_bundle, bool expect_new_root_persisted) { ASSERT_FALSE(backend().IsNewRootPersisted()); ASSERT_FAIL(update_bundle.OpenAndVerify()); ASSERT_EQ(backend().IsNewRootPersisted(), expect_new_root_persisted); VerifyAllBundleOperationsDisallowed(update_bundle); ASSERT_OK(update_bundle.Close()); VerifyAllBundleOperationsDisallowed(update_bundle); } private: kvs::FakeFlashMemoryBuffer blob_flash_; kvs::FlashPartition blob_partition_; blob_store::BlobStoreBuffer bundle_blob_; std::array metadata_buffer_; TestBundledUpdateBackend backend_; }; } // namespace TEST_F(UpdateBundleTest, GetTargetPayload) { backend().SetTrustedRoot(kDevSignedRoot); StageTestBundle(kTestDevBundle); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); ASSERT_OK(update_bundle.OpenAndVerify()); { stream::IntervalReader res = update_bundle.GetTargetPayload("file1"); ASSERT_OK(res.status()); const char kExpectedContent[] = "file 1 content"; char read_buffer[sizeof(kExpectedContent) + 1] = {0}; ASSERT_TRUE(res.Read(read_buffer, sizeof(kExpectedContent)).ok()); ASSERT_STREQ(read_buffer, kExpectedContent); } { stream::IntervalReader res = update_bundle.GetTargetPayload("file2"); ASSERT_OK(res.status()); const char kExpectedContent[] = "file 2 content"; char read_buffer[sizeof(kExpectedContent) + 1] = {0}; ASSERT_TRUE(res.Read(read_buffer, sizeof(kExpectedContent)).ok()); ASSERT_STREQ(read_buffer, kExpectedContent); } { stream::IntervalReader res = update_bundle.GetTargetPayload("non-exist"); ASSERT_EQ(res.status(), Status::NotFound()); } } TEST_F(UpdateBundleTest, PersistManifest) { backend().SetTrustedRoot(kDevSignedRoot); StageTestBundle(kTestDevBundle); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); ASSERT_OK(update_bundle.OpenAndVerify()); std::byte manifest_buffer[sizeof(kTestBundleManifest)] = {}; stream::MemoryWriter manifest_writer(manifest_buffer); backend().SetManifestWriter(&manifest_writer); ASSERT_FALSE(backend().BeforeManifestWriteCalled()); ASSERT_FALSE(backend().AfterManifestWriteCalled()); ASSERT_OK(update_bundle.PersistManifest()); ASSERT_TRUE(backend().BeforeManifestWriteCalled()); ASSERT_TRUE(backend().AfterManifestWriteCalled()); ASSERT_EQ( memcmp(manifest_buffer, kTestBundleManifest, sizeof(kTestBundleManifest)), 0); } TEST_F(UpdateBundleTest, PersistManifestFailIfNotVerified) { backend().SetTrustedRoot(kDevSignedRoot); StageTestBundle(kTestBadProdSignature); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); ASSERT_FAIL(update_bundle.OpenAndVerify()); std::byte manifest_buffer[sizeof(kTestBundleManifest)]; stream::MemoryWriter manifest_writer(manifest_buffer); backend().SetManifestWriter(&manifest_writer); ASSERT_FALSE(backend().BeforeManifestWriteCalled()); ASSERT_FALSE(backend().AfterManifestWriteCalled()); ASSERT_FAIL(update_bundle.PersistManifest()); ASSERT_FALSE(backend().BeforeManifestWriteCalled()); ASSERT_FALSE(backend().AfterManifestWriteCalled()); } TEST_F(UpdateBundleTest, SelfVerificationWithIncomingRoot) { StageTestBundle(kTestDevBundleWithRoot); UpdateBundleAccessor update_bundle( bundle_blob(), backend(), /* disable_verification = */ true); ASSERT_OK(update_bundle.OpenAndVerify()); // Self verification must not persist anything. ASSERT_FALSE(backend().IsNewRootPersisted()); // Manifest persisting should be allowed as well. std::byte manifest_buffer[sizeof(kTestBundleManifest)]; stream::MemoryWriter manifest_writer(manifest_buffer); backend().SetManifestWriter(&manifest_writer); ASSERT_OK(update_bundle.PersistManifest()); ASSERT_EQ( memcmp(manifest_buffer, kTestBundleManifest, sizeof(kTestBundleManifest)), 0); } TEST_F(UpdateBundleTest, SelfVerificationWithoutIncomingRoot) { StageTestBundle(kTestDevBundle); UpdateBundleAccessor update_bundle( bundle_blob(), backend(), /* disable_verification = */ true); ASSERT_OK(update_bundle.OpenAndVerify()); } TEST_F(UpdateBundleTest, SelfVerificationWithMessedUpRoot) { StageTestBundle(kTestDevBundleWithProdRoot); UpdateBundleAccessor update_bundle( bundle_blob(), backend(), /* disable_verification = */ true); ASSERT_FAIL(update_bundle.OpenAndVerify()); } TEST_F(UpdateBundleTest, SelfVerificationChecksMissingHashes) { StageTestBundle(kTestBundleMissingTargetHashFile0); UpdateBundleAccessor update_bundle( bundle_blob(), backend(), /* disable_verification = */ true); ASSERT_FAIL(update_bundle.OpenAndVerify()); } TEST_F(UpdateBundleTest, SelfVerificationChecksBadHashes) { StageTestBundle(kTestBundleMismatchedTargetHashFile0); UpdateBundleAccessor update_bundle( bundle_blob(), backend(), /* disable_verification = */ true); ASSERT_FAIL(update_bundle.OpenAndVerify()); } TEST_F(UpdateBundleTest, SelfVerificationIgnoresUnsignedBundle) { StageTestBundle(kTestUnsignedBundleWithRoot); UpdateBundleAccessor update_bundle( bundle_blob(), backend(), /* disable_verification = */ true); ASSERT_OK(update_bundle.OpenAndVerify()); } TEST_F(UpdateBundleTest, OpenAndVerifySucceedsWithAllVerification) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); StageTestBundle(kTestProdBundle); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); ASSERT_FALSE(backend().IsNewRootPersisted()); ASSERT_FALSE(backend().BeforeManifestReadCalled()); ASSERT_OK(update_bundle.OpenAndVerify()); ASSERT_TRUE(backend().IsNewRootPersisted()); ASSERT_TRUE(backend().BeforeManifestReadCalled()); ASSERT_OK(update_bundle.Close()); VerifyAllBundleOperationsDisallowed(update_bundle); } TEST_F(UpdateBundleTest, OpenAndVerifyWithoutIncomingRootSucceedsWithAllVerification) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); // kTestDevBundle does not contain an incoming root. See // pw_software_update/py/pw_software_update/generate_test_bundle.py for // detail of generation. StageTestBundle(kTestDevBundle); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); ASSERT_FALSE(backend().IsNewRootPersisted()); ASSERT_FALSE(backend().BeforeManifestReadCalled()); ASSERT_OK(update_bundle.OpenAndVerify()); ASSERT_FALSE(backend().IsNewRootPersisted()); ASSERT_TRUE(backend().BeforeManifestReadCalled()); ASSERT_OK(update_bundle.Close()); VerifyAllBundleOperationsDisallowed(update_bundle); } TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMismatchedRootKeyAndSignature) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); // kTestMismatchedRootKeyAndSignature has a dev root metadata that is // prod signed. The root metadata will not be able to verify itself. // See pw_software_update/py/pw_software_update/generate_test_bundle.py for // detail of generation. StageTestBundle(kTestMismatchedRootKeyAndSignature); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); CheckOpenAndVerifyFail(update_bundle, false); } TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnBadProdSignature) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); StageTestBundle(kTestBadProdSignature); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); CheckOpenAndVerifyFail(update_bundle, false); } TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnBadTargetsSignature) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); StageTestBundle(kTestBadTargetsSignature); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); CheckOpenAndVerifyFail(update_bundle, true); } TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnBadTargetsRollBack) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); StageTestBundle(kTestTargetsRollback); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); CheckOpenAndVerifyFail(update_bundle, true); } TEST_F(UpdateBundleTest, OpenAndVerifySucceedsWithoutExistingManifest) { backend().SetTrustedRoot(kDevSignedRoot); StageTestBundle(kTestProdBundle); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); ASSERT_FALSE(backend().IsNewRootPersisted()); ASSERT_OK(update_bundle.OpenAndVerify()); ASSERT_TRUE(backend().IsNewRootPersisted()); } TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnRootRollback) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); StageTestBundle(kTestRootRollback); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); CheckOpenAndVerifyFail(update_bundle, false); } TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMismatchedTargetHashFile0) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); // `kTestBundleMismatchedTargetHashFile0` is auto generated by // pw_software_update/py/pw_software_update/generate_test_bundle.py. // The hash value for file 0 in the targets metadata is made incorrect. StageTestBundle(kTestBundleMismatchedTargetHashFile0); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); CheckOpenAndVerifyFail(update_bundle, true); } TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMismatchedTargetHashFile1) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); // `kTestBundleMismatchedTargetHashFile1` is auto generated by // pw_software_update/py/pw_software_update/generate_test_bundle.py // The hash value for file 1 in the targets metadata is made incorrect. StageTestBundle(kTestBundleMismatchedTargetHashFile1); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); CheckOpenAndVerifyFail(update_bundle, true); } TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMissingTargetHashFile0) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); // `kTestBundleMismatchedTargetHashFile0` is auto generated by // pw_software_update/py/pw_software_update/generate_test_bundle.py. // The hash value for file 0 is removed. StageTestBundle(kTestBundleMissingTargetHashFile0); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); CheckOpenAndVerifyFail(update_bundle, true); } TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMissingTargetHashFile1) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); // `kTestBundleMismatchedTargetHashFile1` is auto generated by // pw_software_update/py/pw_software_update/generate_test_bundle.py // The hash value for file 1 is removed. StageTestBundle(kTestBundleMissingTargetHashFile1); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); CheckOpenAndVerifyFail(update_bundle, true); } TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMismatchedTargetLengthFile0) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); // `kTestBundleMismatchedTargetLengthFile0` is auto generated by // pw_software_update/py/pw_software_update/generate_test_bundle.py. // The length value for file 0 in the targets metadata is made incorrect (1). StageTestBundle(kTestBundleMismatchedTargetLengthFile0); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); CheckOpenAndVerifyFail(update_bundle, true); } TEST_F(UpdateBundleTest, OpenAndVerifyFailsOnMismatchedTargetLengthFile1) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); // `kTestBundleMismatchedTargetLengthFile1` is auto generated by // pw_software_update/py/pw_software_update/generate_test_bundle.py. // The length value for file 0 in the targets metadata is made incorrect (1). StageTestBundle(kTestBundleMismatchedTargetLengthFile1); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); CheckOpenAndVerifyFail(update_bundle, true); } TEST_F(UpdateBundleTest, OpenAndVerifySucceedsWithPersonalizedOutFile0) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); // `kTestBundlePersonalizedOutFile0` is auto generated by // pw_software_update/py/pw_software_update/generate_test_bundle.py // The payload for file 0 is removed from the bundle to emulate being // personalized out. StageTestBundle(kTestBundlePersonalizedOutFile0); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); ASSERT_OK(update_bundle.OpenAndVerify()); } TEST_F(UpdateBundleTest, OpenAndVerifySucceedsWithPersonalizedOutFile1) { backend().SetTrustedRoot(kDevSignedRoot); backend().SetCurrentManifest(kTestBundleManifest); // `kTestBundlePersonalizedOutFile1` is auto generated by // pw_software_update/py/pw_software_update/generate_test_bundle.py // The payload for file 1 is removed from the bundle to emulate being // personalized out. StageTestBundle(kTestBundlePersonalizedOutFile1); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); ASSERT_OK(update_bundle.OpenAndVerify()); } TEST_F(UpdateBundleTest, PersonalizationVerificationFailsWithoutDeviceManifest) { backend().SetTrustedRoot(kDevSignedRoot); // `kTestBundlePersonalizedOutFile0` is auto generated by // pw_software_update/py/pw_software_update/generate_test_bundle.py // The payload for file 0 is removed from the bundle to emulate being // personalized out. StageTestBundle(kTestBundlePersonalizedOutFile0); UpdateBundleAccessor update_bundle(bundle_blob(), backend()); ASSERT_FAIL(update_bundle.OpenAndVerify()); } } // namespace pw::software_update