| // Copyright 2022 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "vtpm/backends/real_tpm_handle_manager.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <trunks/mock_response_serializer.h> |
| #include <trunks/mock_tpm.h> |
| #include <trunks/mock_tpm_utility.h> |
| #include <trunks/tpm_generated.h> |
| #include <trunks/trunks_factory_for_test.h> |
| |
| #include <base/logging.h> |
| |
| #include "vtpm/backends/fake_blob.h" |
| #include "vtpm/backends/scoped_host_key_handle.h" |
| |
| namespace vtpm { |
| |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::ElementsAre; |
| using ::testing::Invoke; |
| using ::testing::Return; |
| using ::testing::StrictMock; |
| using ::testing::UnorderedElementsAreArray; |
| |
| constexpr trunks::TPM_HANDLE kFakeHandle1 = trunks::PERSISTENT_FIRST + 10; |
| constexpr trunks::TPM_HANDLE kFakeHandle2 = trunks::PERSISTENT_FIRST + 100; |
| constexpr trunks::TPM_HANDLE kFakeHandle3 = trunks::PERSISTENT_FIRST + 1000; |
| constexpr char kFakeBlob1[] = "blob1"; |
| constexpr char kFakeBlob2[] = "blob2"; |
| constexpr char kFakeBlob3[] = "blob3"; |
| |
| static_assert(kFakeHandle1 < kFakeHandle2, ""); |
| static_assert(kFakeHandle2 < kFakeHandle3, ""); |
| |
| } // namespace |
| |
| class RealTpmHandleManagerTest : public testing::Test { |
| public: |
| void SetUp() override { |
| std::map<trunks::TPM_HANDLE, Blob*> table{ |
| {kFakeHandle1, &mock_blob_1_}, |
| {kFakeHandle2, &mock_blob_2_}, |
| {kFakeHandle3, &mock_blob_3_}, |
| }; |
| manager_ = std::make_unique<RealTpmHandleManager>(&trunks_factory_, table); |
| |
| trunks_factory_.set_tpm_utility(&mock_tpm_utility_); |
| trunks_factory_.set_tpm(&mock_tpm_); |
| SetDefaultLoadFlushBehavior(); |
| } |
| void TearDown() override { |
| // Make sure no memory leak in any case. |
| EXPECT_EQ(flushed_host_handles_.size(), loaded_host_handles_.size()); |
| EXPECT_THAT(flushed_host_handles_, |
| UnorderedElementsAreArray(loaded_host_handles_)); |
| } |
| |
| protected: |
| trunks::TPM_RC FakeLoadKey(const std::string& /*key_blob*/, |
| trunks::AuthorizationDelegate* /*delegate*/, |
| trunks::TPM_HANDLE* key_handle) { |
| *key_handle = trunks::TRANSIENT_FIRST + loaded_host_handles_.size(); |
| loaded_host_handles_.push_back(*key_handle); |
| return trunks::TPM_RC_SUCCESS; |
| } |
| trunks::TPM_RC FakeFlushKey( |
| trunks::TPM_HANDLE key_handle, |
| trunks::AuthorizationDelegate* /*authorization_delegate*/) { |
| flushed_host_handles_.push_back(key_handle); |
| return trunks::TPM_RC_SUCCESS; |
| } |
| void SetDefaultLoadFlushBehavior() { |
| ON_CALL(mock_tpm_utility_, LoadKey(_, _, _)) |
| .WillByDefault(Invoke(this, &RealTpmHandleManagerTest::FakeLoadKey)); |
| ON_CALL(mock_tpm_, FlushContextSync(_, _)) |
| .WillByDefault(Invoke(this, &RealTpmHandleManagerTest::FakeFlushKey)); |
| } |
| |
| std::vector<trunks::TPM_HANDLE> loaded_host_handles_; |
| std::vector<trunks::TPM_HANDLE> flushed_host_handles_; |
| |
| StrictMock<FakeBlob> mock_blob_1_{kFakeBlob1}; |
| StrictMock<FakeBlob> mock_blob_2_{kFakeBlob2}; |
| StrictMock<FakeBlob> mock_blob_3_{kFakeBlob3}; |
| trunks::TrunksFactoryForTest trunks_factory_; |
| trunks::MockTpmUtility mock_tpm_utility_; |
| trunks::MockTpm mock_tpm_; |
| std::unique_ptr<RealTpmHandleManager> manager_; |
| }; |
| |
| namespace { |
| |
| TEST_F(RealTpmHandleManagerTest, IsHandleTypeSuppoerted) { |
| EXPECT_TRUE(manager_->IsHandleTypeSuppoerted(trunks::HR_PERSISTENT)); |
| EXPECT_TRUE(manager_->IsHandleTypeSuppoerted(trunks::HR_PERSISTENT + 1)); |
| EXPECT_FALSE(manager_->IsHandleTypeSuppoerted(trunks::HR_PERMANENT)); |
| } |
| |
| TEST_F(RealTpmHandleManagerTest, GetHandleListPersistentHandles) { |
| EXPECT_CALL(mock_blob_1_, Get(_)); |
| EXPECT_CALL(mock_blob_2_, Get(_)); |
| EXPECT_CALL(mock_blob_3_, Get(_)); |
| std::vector<trunks::TPM_HANDLE> found_handles; |
| EXPECT_EQ(manager_->GetHandleList(trunks::PERSISTENT_FIRST, &found_handles), |
| trunks::TPM_RC_SUCCESS); |
| EXPECT_THAT(found_handles, |
| ElementsAre(kFakeHandle1, kFakeHandle2, kFakeHandle3)); |
| } |
| |
| TEST_F(RealTpmHandleManagerTest, GetHandleListPersistentHandlesSkipFirst) { |
| EXPECT_CALL(mock_blob_2_, Get(_)); |
| EXPECT_CALL(mock_blob_3_, Get(_)); |
| std::vector<trunks::TPM_HANDLE> found_handles; |
| EXPECT_EQ(manager_->GetHandleList(kFakeHandle1 + 1, &found_handles), |
| trunks::TPM_RC_SUCCESS); |
| EXPECT_THAT(found_handles, ElementsAre(kFakeHandle2, kFakeHandle3)); |
| } |
| |
| TEST_F(RealTpmHandleManagerTest, GetHandleListPersistentHandlesEmpty) { |
| std::vector<trunks::TPM_HANDLE> found_handles; |
| EXPECT_EQ(manager_->GetHandleList(kFakeHandle3 + 1, &found_handles), |
| trunks::TPM_RC_SUCCESS); |
| EXPECT_TRUE(found_handles.empty()); |
| } |
| |
| TEST_F(RealTpmHandleManagerTest, GetHandleListPersistentHandlesError) { |
| EXPECT_CALL(mock_blob_1_, Get(_)); |
| EXPECT_CALL(mock_blob_2_, Get(_)).WillOnce(Return(trunks::TPM_RC_FAILURE)); |
| std::vector<trunks::TPM_HANDLE> found_handles; |
| EXPECT_EQ(manager_->GetHandleList(trunks::PERSISTENT_FIRST, &found_handles), |
| trunks::TPM_RC_FAILURE); |
| } |
| |
| TEST_F(RealTpmHandleManagerTest, GetHandleListTransientHandles) { |
| std::vector<trunks::TPM_HANDLE> found_handles; |
| // Initially it should be empty. |
| EXPECT_EQ(manager_->GetHandleList(trunks::TRANSIENT_FIRST, &found_handles), |
| trunks::TPM_RC_SUCCESS); |
| EXPECT_TRUE(found_handles.empty()); |
| |
| // Simulate the loading of a transient object. |
| constexpr trunks::TPM_HANDLE kFakeParent = trunks::TRANSIENT_FIRST; |
| constexpr trunks::TPM_HANDLE kFakeChild = trunks::TRANSIENT_FIRST + 1; |
| manager_->OnLoad(kFakeParent, kFakeChild); |
| EXPECT_EQ(manager_->GetHandleList(trunks::TRANSIENT_FIRST, &found_handles), |
| trunks::TPM_RC_SUCCESS); |
| EXPECT_THAT(found_handles, ElementsAre(kFakeChild)); |
| } |
| |
| TEST_F(RealTpmHandleManagerTest, |
| GetHandleListTransientHandlesStartingTooLarge) { |
| std::vector<trunks::TPM_HANDLE> found_handles; |
| // Initially it should be empty. |
| EXPECT_EQ(manager_->GetHandleList(trunks::TRANSIENT_FIRST, &found_handles), |
| trunks::TPM_RC_SUCCESS); |
| EXPECT_TRUE(found_handles.empty()); |
| |
| // Simulate the loading of a transient object. |
| constexpr trunks::TPM_HANDLE kFakeParent = trunks::TRANSIENT_FIRST; |
| constexpr trunks::TPM_HANDLE kFakeChild = trunks::TRANSIENT_FIRST + 1; |
| manager_->OnLoad(kFakeParent, kFakeChild); |
| EXPECT_EQ(manager_->GetHandleList(kFakeChild + 1, &found_handles), |
| trunks::TPM_RC_SUCCESS); |
| EXPECT_TRUE(found_handles.empty()); |
| } |
| |
| TEST_F(RealTpmHandleManagerTest, TranslateHandleSuccessPersistentHandles) { |
| EXPECT_CALL(mock_blob_1_, Get(_)); |
| ScopedHostKeyHandle host_handle; |
| EXPECT_CALL(mock_tpm_utility_, LoadKey(kFakeBlob1, _, _)); |
| EXPECT_EQ(manager_->TranslateHandle(kFakeHandle1, &host_handle), |
| trunks::TPM_RC_SUCCESS); |
| // NOTE that we don't validate the exact value of the returned handle because |
| // it's up to implementation of the mocks. |
| EXPECT_NE(host_handle.Get(), trunks::TPM_HANDLE()); |
| EXPECT_CALL(mock_tpm_, FlushContextSync(host_handle.Get(), _)); |
| } |
| |
| TEST_F(RealTpmHandleManagerTest, |
| TranslateHandleSuccessPersistentHandlesMovedScopedHostHandle) { |
| EXPECT_CALL(mock_blob_1_, Get(_)); |
| ScopedHostKeyHandle host_handle; |
| EXPECT_CALL(mock_tpm_utility_, LoadKey(kFakeBlob1, _, _)); |
| EXPECT_EQ(manager_->TranslateHandle(kFakeHandle1, &host_handle), |
| trunks::TPM_RC_SUCCESS); |
| // NOTE that we don't validate the exact value of the returned handle because |
| // it's up to implementation of the mocks. |
| EXPECT_NE(host_handle.Get(), trunks::TPM_HANDLE()); |
| EXPECT_CALL(mock_tpm_, FlushContextSync(host_handle.Get(), _)); |
| ScopedHostKeyHandle moved_host_handle = std::move(host_handle); |
| } |
| |
| TEST_F(RealTpmHandleManagerTest, TranslateHandleTransientHandles) { |
| // First, load a virtual persistent handle. Technically this is not necessary; |
| // it is just to make sure we don't have memory leak in normal operation |
| // flows. |
| ScopedHostKeyHandle parent_host_handle; |
| EXPECT_CALL(mock_blob_1_, Get(_)); |
| EXPECT_CALL(mock_tpm_utility_, LoadKey(kFakeBlob1, _, _)); |
| EXPECT_EQ(manager_->TranslateHandle(kFakeHandle1, &parent_host_handle), |
| trunks::TPM_RC_SUCCESS); |
| // NOTE that through the entire flow no flush of a virtual transient handle |
| // should take place because the guest flushes the loaded object by their own |
| // instead of vtpm loading/unloading them transparently. Strick mock will |
| // verify. |
| ScopedHostKeyHandle host_handle; |
| const trunks::TPM_HANDLE fake_parent = parent_host_handle.Get(); |
| constexpr trunks::TPM_HANDLE kFakeChild1 = trunks::TRANSIENT_FIRST + 1; |
| constexpr trunks::TPM_HANDLE kFakeChild2 = trunks::TRANSIENT_FIRST + 2; |
| |
| // Deny the unloaded handle. |
| EXPECT_EQ(manager_->TranslateHandle(kFakeChild1, &host_handle), |
| trunks::TPM_RC_HANDLE); |
| EXPECT_EQ(manager_->TranslateHandle(kFakeChild2, &host_handle), |
| trunks::TPM_RC_HANDLE); |
| |
| manager_->OnLoad(fake_parent, kFakeChild1); |
| EXPECT_EQ(manager_->TranslateHandle(kFakeChild1, &host_handle), |
| trunks::TPM_RC_SUCCESS); |
| EXPECT_EQ(host_handle.Get(), kFakeChild1); |
| EXPECT_EQ(manager_->TranslateHandle(kFakeChild2, &host_handle), |
| trunks::TPM_RC_HANDLE); |
| |
| // Let go of the parent host handle. Note that it should not be flushed |
| // because the child handles force the parent to be retained. Strict mock will |
| // verify. |
| parent_host_handle = ScopedHostKeyHandle(); |
| |
| manager_->OnLoad(fake_parent, kFakeChild2); |
| EXPECT_EQ(manager_->TranslateHandle(kFakeChild1, &host_handle), |
| trunks::TPM_RC_SUCCESS); |
| EXPECT_EQ(host_handle.Get(), kFakeChild1); |
| EXPECT_EQ(manager_->TranslateHandle(kFakeChild2, &host_handle), |
| trunks::TPM_RC_SUCCESS); |
| EXPECT_EQ(host_handle.Get(), kFakeChild2); |
| |
| manager_->OnUnload(kFakeChild2); |
| EXPECT_EQ(manager_->TranslateHandle(kFakeChild1, &host_handle), |
| trunks::TPM_RC_SUCCESS); |
| EXPECT_EQ(host_handle.Get(), kFakeChild1); |
| EXPECT_EQ(manager_->TranslateHandle(kFakeChild2, &host_handle), |
| trunks::TPM_RC_HANDLE); |
| |
| // Unlaoding the last child of the parent should flush the parent handle. |
| EXPECT_CALL(mock_tpm_, FlushContextSync(fake_parent, _)); |
| |
| manager_->OnUnload(kFakeChild1); |
| EXPECT_EQ(manager_->TranslateHandle(kFakeChild1, &host_handle), |
| trunks::TPM_RC_HANDLE); |
| EXPECT_EQ(manager_->TranslateHandle(kFakeChild2, &host_handle), |
| trunks::TPM_RC_HANDLE); |
| } |
| |
| TEST_F(RealTpmHandleManagerTest, UnloadNoexistentHandle) { |
| // Unloading a non-existent handle should not cause any consequence. In best |
| // effort we ensure it doesn't crash. |
| manager_->OnUnload(trunks::TRANSIENT_FIRST); |
| } |
| |
| } // namespace |
| |
| } // namespace vtpm |