blob: 927c0e6ed584559ff048b12d5578f4a04585b633 [file] [log] [blame]
// 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 <array>
#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<uint32_t> 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<stream::SeekableReader*> 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<stream::SeekableReader*> 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<stream::Writer*> 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<kBufferSize>& 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<kSectorSize, kSectorCount> blob_flash_;
kvs::FlashPartition blob_partition_;
blob_store::BlobStoreBuffer<kBufferSize> bundle_blob_;
std::array<std::byte, kMetadataBufferSize> 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