| // Copyright 2015 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 "trunks/resource_manager.h" |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/callback.h> |
| |
| #include "trunks/error_codes.h" |
| |
| #define IS_TPM_CC_VENDOR_CMD(c) (((c) == TPM_CC_VENDOR_SPECIFIC_MASK) || \ |
| ((c) == TPM_CC_CR50_EXTENSION_COMMAND)) |
| namespace { |
| |
| const int kMaxSuspendDurationSec = 10; |
| const int kMaxCommandAttempts = 3; |
| const size_t kMinimumAuthorizationSize = 9; |
| const size_t kMessageHeaderSize = 10; |
| const trunks::TPM_HANDLE kMaxVirtualHandle = |
| (trunks::HR_TRANSIENT + trunks::HR_HANDLE_MASK); |
| |
| class ScopedBool { |
| public: |
| ScopedBool() : target_(nullptr) {} |
| ~ScopedBool() { |
| if (target_) { |
| *target_ = false; |
| } |
| } |
| void Enable(bool* target) { |
| target_ = target; |
| *target_ = true; |
| } |
| |
| private: |
| bool* target_; |
| }; |
| |
| } // namespace |
| |
| namespace trunks { |
| |
| ResourceManager::ResourceManager(const TrunksFactory& factory, |
| CommandTransceiver* next_transceiver) |
| : factory_(factory), next_transceiver_(next_transceiver), |
| max_suspend_duration_( |
| base::TimeDelta::FromSeconds(kMaxSuspendDurationSec)) {} |
| |
| ResourceManager::~ResourceManager() {} |
| |
| void ResourceManager::Initialize() { |
| // Abort if the TPM is not in a reasonable state and we can't get it into one. |
| std::unique_ptr<TpmUtility> tpm_utility = factory_.GetTpmUtility(); |
| CHECK_EQ(tpm_utility->CheckState(), TPM_RC_SUCCESS); |
| |
| // Full control of the TPM is assumed and required. Existing transient object |
| // and session handles are mercilessly flushed. |
| for (UINT32 handle_type : |
| {HR_TRANSIENT, HR_HMAC_SESSION, HR_POLICY_SESSION}) { |
| TPMI_YES_NO more_data = YES; |
| TPMS_CAPABILITY_DATA data; |
| UINT32 handle_range = handle_type; |
| while (more_data) { |
| TPM_RC result = factory_.GetTpm()->GetCapabilitySync( |
| TPM_CAP_HANDLES, handle_range, MAX_CAP_HANDLES, &more_data, &data, |
| nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| LOG(WARNING) << "Failed to query existing handles: " |
| << GetErrorString(result); |
| break; |
| } |
| const TPML_HANDLE& handle_list = data.data.handles; |
| for (UINT32 i = 0; i < handle_list.count; ++i) { |
| factory_.GetTpm()->FlushContextSync(handle_list.handle[i], nullptr); |
| } |
| if (more_data) { |
| // Adjust the range to be greater than the most recent handle so on the |
| // next query we'll start where we left off. |
| handle_range = handle_list.handle[handle_list.count - 1]; |
| } |
| } |
| } |
| } |
| |
| void ResourceManager::SendCommand(const std::string& command, |
| const ResponseCallback& callback) { |
| callback.Run(SendCommandAndWait(command)); |
| } |
| |
| std::string ResourceManager::SendCommandAndWait(const std::string& command) { |
| // Sanitize the |command|. If this succeeds consistency of the command header |
| // and the size of all other sections can be assumed. |
| MessageInfo command_info; |
| TPM_RC result = ParseCommand(command, &command_info); |
| if (result != TPM_RC_SUCCESS) { |
| return CreateErrorResponse(result); |
| } |
| // Block all commands with handles when suspended. |
| // TODO(apronin): Add metrics to track cases when we receive commands |
| // while in the suspended state, auto-resume from it, block commands |
| // with handles as a result. |
| if (suspended_) { |
| LOG(WARNING) << "Received command CC 0x" |
| << std::hex << command_info.code << " while suspended."; |
| // Make sure we resume after the maximum allowed suspend duration even |
| // if the resume event is somehow lost. Should be enough to go through |
| // suspend preparaion - and that's all we care about. |
| base::TimeTicks now = base::TimeTicks::Now(); |
| if (now < suspended_timestamp_ || |
| now >= suspended_timestamp_ + max_suspend_duration_) { |
| LOG(WARNING) << "Auto-resuming Resource Manager."; |
| suspended_ = false; |
| } else if (GetNumberOfRequestHandles(command_info.code) || |
| GetNumberOfResponseHandles(command_info.code)) { |
| LOG(WARNING) << "Blocking command while suspended."; |
| return CreateErrorResponse(TPM_RC_RETRY); |
| } |
| } |
| // A special case for FlushContext. It requires special handling because it |
| // has a handle as a parameter and because we need to cleanup if it succeeds. |
| if (command_info.code == TPM_CC_FlushContext) { |
| return ProcessFlushContext(command, command_info); |
| } |
| // Process all the input handles, e.g. map virtual handles. |
| std::vector<TPM_HANDLE> updated_handles; |
| for (auto handle : command_info.handles) { |
| TPM_HANDLE tpm_handle; |
| result = ProcessInputHandle(command_info, handle, &tpm_handle); |
| if (result != TPM_RC_SUCCESS) { |
| return CreateErrorResponse(result); |
| } |
| updated_handles.push_back(tpm_handle); |
| } |
| std::string updated_command = ReplaceHandles(command, updated_handles); |
| // Make sure all the required sessions are loaded. |
| for (auto handle : command_info.all_session_handles) { |
| result = EnsureSessionIsLoaded(command_info, handle); |
| if (result != TPM_RC_SUCCESS) { |
| return CreateErrorResponse(result); |
| } |
| } |
| // On a ContextLoad we may need to map virtualized context data. |
| if (command_info.code == TPM_CC_ContextLoad) { |
| std::string actual_load_data = |
| GetActualContextFromExternalContext(command_info.parameter_data); |
| // Check equality to see if replacement is necessary, and check size to see |
| // if the command looks like we expect (the idea is to avoid 'fixing' |
| // malformed commands). Note: updated_command.size() is guaranteed to be >= |
| // kMessageHeaderSize based on the sanitization in ParseCommand. |
| if (actual_load_data != command_info.parameter_data && |
| actual_load_data.size() == |
| updated_command.size() - kMessageHeaderSize) { |
| // Replace the parameter section of the command with |actual_load_data|. |
| VLOG(1) << "REPLACE_EXTERNAL_CONTEXT"; |
| updated_command.replace(kMessageHeaderSize, std::string::npos, |
| actual_load_data); |
| } |
| } |
| // Send the |updated_command| to the next layer. Attempt to fix any actionable |
| // warnings for up to kMaxCommandAttempts. |
| std::string response; |
| MessageInfo response_info; |
| int attempts = 0; |
| while (attempts++ < kMaxCommandAttempts) { |
| response = next_transceiver_->SendCommandAndWait(updated_command); |
| result = ParseResponse(command_info, response, &response_info); |
| if (result != TPM_RC_SUCCESS) { |
| return CreateErrorResponse(result); |
| } |
| if (!FixWarnings(command_info, response_info.code)) { |
| // No actionable warnings were handled. |
| break; |
| } |
| } |
| if (response_info.code == TPM_RC_SUCCESS) { |
| if (response_info.session_continued.size() != |
| command_info.auth_session_handles.size()) { |
| LOG(WARNING) << "Session count mismatch!"; |
| } |
| // Cleanup any sessions that were not continued. |
| for (size_t i = 0; i < command_info.auth_session_handles.size(); ++i) { |
| if (i < response_info.session_continued.size() && |
| !response_info.session_continued[i]) { |
| CleanupFlushedHandle(command_info.auth_session_handles[i]); |
| } |
| } |
| // On a successful context save we need to cache the context data in case it |
| // needs to be virtualized later. |
| if (command_info.code == TPM_CC_ContextSave) { |
| ProcessExternalContextSave(command_info, response_info); |
| } |
| // Process all the output handles, which is loosely the inverse of the input |
| // handle processing. E.g. virtualize handles. |
| std::vector<TPM_HANDLE> virtual_handles; |
| for (auto handle : response_info.handles) { |
| virtual_handles.push_back(ProcessOutputHandle(handle)); |
| } |
| response = ReplaceHandles(response, virtual_handles); |
| } |
| return response; |
| } |
| |
| void ResourceManager::Suspend() { |
| VLOG(1) << __func__; |
| if (!suspended_) { |
| suspended_timestamp_ = base::TimeTicks::Now(); |
| suspended_ = true; |
| SaveAllContexts(); |
| } |
| } |
| |
| void ResourceManager::Resume() { |
| VLOG(1) << __func__ << " (suspended = " << suspended_ << ")."; |
| suspended_ = false; |
| } |
| |
| bool ResourceManager::ChooseSessionToEvict( |
| const std::vector<TPM_HANDLE>& sessions_to_retain, |
| TPM_HANDLE* session_to_evict) { |
| // Build a list of candidates by excluding |sessions_to_retain|. |
| std::vector<TPM_HANDLE> candidates; |
| for (auto& item : session_handles_) { |
| HandleInfo& info = item.second; |
| if (info.is_loaded && |
| std::find(sessions_to_retain.begin(), sessions_to_retain.end(), |
| info.tpm_handle) == sessions_to_retain.end()) { |
| candidates.push_back(item.first); |
| } |
| } |
| if (candidates.empty()) { |
| LOG(WARNING) << "No sessions to evict."; |
| return false; |
| } |
| // Choose the candidate with the earliest |time_of_last_use|. |
| auto oldest_iter = std::min_element( |
| candidates.begin(), candidates.end(), [this](TPM_HANDLE a, TPM_HANDLE b) { |
| return (session_handles_[a].time_of_last_use < |
| session_handles_[b].time_of_last_use); |
| }); |
| *session_to_evict = *oldest_iter; |
| return true; |
| } |
| |
| void ResourceManager::CleanupFlushedHandle(TPM_HANDLE flushed_handle) { |
| if (IsObjectHandle(flushed_handle)) { |
| // For transient object handles, remove both the actual and virtual handles. |
| if (virtual_object_handles_.count(flushed_handle) > 0) { |
| tpm_object_handles_.erase( |
| virtual_object_handles_[flushed_handle].tpm_handle); |
| virtual_object_handles_.erase(flushed_handle); |
| } |
| } else if (IsSessionHandle(flushed_handle)) { |
| auto iter = session_handles_.find(flushed_handle); |
| if (iter == session_handles_.end()) { |
| return; |
| } |
| // For session handles, remove the handle and any associated context data. |
| HandleInfo& info = iter->second; |
| if (!info.is_loaded) { |
| std::string actual_context_data; |
| Serialize_TPMS_CONTEXT(info.context, &actual_context_data); |
| if (actual_context_to_external_.count(actual_context_data) > 0) { |
| external_context_to_actual_.erase( |
| actual_context_to_external_[actual_context_data]); |
| actual_context_to_external_.erase(actual_context_data); |
| } |
| } |
| session_handles_.erase(flushed_handle); |
| VLOG(1) << "CLEANUP_SESSION: " << std::hex << flushed_handle; |
| } |
| } |
| |
| TPM_HANDLE ResourceManager::CreateVirtualHandle() { |
| TPM_HANDLE handle; |
| do { |
| handle = next_virtual_handle_; |
| if (next_virtual_handle_ == kMaxVirtualHandle) { |
| next_virtual_handle_ = TRANSIENT_FIRST; |
| } else { |
| ++next_virtual_handle_; |
| } |
| } while (virtual_object_handles_.count(handle) > 0); |
| return handle; |
| } |
| |
| TPM_RC ResourceManager::EnsureSessionIsLoaded(const MessageInfo& command_info, |
| TPM_HANDLE session_handle) { |
| // A password authorization can skip all this. |
| if (session_handle == TPM_RS_PW) { |
| return TPM_RC_SUCCESS; |
| } |
| auto handle_iter = session_handles_.find(session_handle); |
| if (handle_iter == session_handles_.end()) { |
| return MakeError(TPM_RC_HANDLE, FROM_HERE); |
| } |
| HandleInfo& handle_info = handle_iter->second; |
| if (!handle_info.is_loaded) { |
| TPM_RC result = LoadContext(command_info, &handle_info); |
| if (result != TPM_RC_SUCCESS) { |
| return result; |
| } |
| VLOG(1) << "RELOAD_SESSION: " << std::hex << session_handle; |
| } |
| handle_info.time_of_last_use = base::TimeTicks::Now(); |
| return TPM_RC_SUCCESS; |
| } |
| |
| void ResourceManager::EvictObjects(const MessageInfo& command_info) { |
| for (auto& item : virtual_object_handles_) { |
| HandleInfo& info = item.second; |
| if (!info.is_loaded || |
| std::find(command_info.handles.begin(), command_info.handles.end(), |
| item.first) != command_info.handles.end()) { |
| continue; |
| } |
| TPM_RC result = SaveContext(command_info, &info); |
| if (result != TPM_RC_SUCCESS) { |
| LOG(WARNING) << "Failed to save transient object: " |
| << GetErrorString(result); |
| continue; |
| } |
| result = factory_.GetTpm()->FlushContextSync(info.tpm_handle, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| LOG(WARNING) << "Failed to evict transient object: " |
| << GetErrorString(result); |
| continue; |
| } |
| tpm_object_handles_.erase(info.tpm_handle); |
| VLOG(1) << "EVICT_OBJECT: " << std::hex << info.tpm_handle; |
| } |
| } |
| |
| void ResourceManager::EvictSession(const MessageInfo& command_info) { |
| TPM_HANDLE session_to_evict; |
| if (!ChooseSessionToEvict(command_info.all_session_handles, |
| &session_to_evict)) { |
| return; |
| } |
| HandleInfo& info = session_handles_[session_to_evict]; |
| TPM_RC result = SaveContext(command_info, &info); |
| if (result != TPM_RC_SUCCESS) { |
| LOG(WARNING) << "Failed to evict session: " << GetErrorString(result); |
| } |
| VLOG(1) << "EVICT_SESSION: " << std::hex << session_to_evict; |
| } |
| |
| void ResourceManager::SaveAllContexts() { |
| EvictObjects(MessageInfo()); |
| LOG(INFO) << "Finished saving contexts."; |
| } |
| |
| std::vector<TPM_HANDLE> ResourceManager::ExtractHandlesFromBuffer( |
| size_t number_of_handles, |
| std::string* buffer) { |
| std::vector<TPM_HANDLE> handles(number_of_handles); |
| for (auto& handle : handles) { |
| if (Parse_TPM_HANDLE(buffer, &handle, nullptr) != TPM_RC_SUCCESS) { |
| LOG(WARNING) << "Failed to extract the expected number of handles"; |
| handles.clear(); |
| break; |
| } |
| } |
| return handles; |
| } |
| |
| void ResourceManager::FixContextGap(const MessageInfo& command_info) { |
| std::vector<TPM_HANDLE> sessions_to_ungap; |
| for (const auto& item : session_handles_) { |
| const HandleInfo& info = item.second; |
| if (!info.is_loaded) { |
| sessions_to_ungap.push_back(item.first); |
| } |
| } |
| // Sort by |time_of_create|. |
| std::sort(sessions_to_ungap.begin(), sessions_to_ungap.end(), |
| [this](TPM_HANDLE a, TPM_HANDLE b) { |
| return (session_handles_[a].time_of_create < |
| session_handles_[b].time_of_create); |
| }); |
| for (auto handle : sessions_to_ungap) { |
| HandleInfo& info = session_handles_[handle]; |
| // Loading and re-saving allows the TPM to assign a new context counter. |
| std::string old_context_blob; |
| Serialize_TPMS_CONTEXT(info.context, &old_context_blob); |
| TPM_RC result = LoadContext(command_info, &info); |
| if (result != TPM_RC_SUCCESS) { |
| LOG(WARNING) << "Failed to un-gap session (load): " |
| << GetErrorString(result); |
| continue; |
| } |
| result = SaveContext(command_info, &info); |
| if (result != TPM_RC_SUCCESS) { |
| LOG(WARNING) << "Failed to un-gap session (save): " |
| << GetErrorString(result); |
| continue; |
| } |
| // If this context is one that we're tracking for external use, update it. |
| auto iter = actual_context_to_external_.find(old_context_blob); |
| if (iter == actual_context_to_external_.end()) { |
| continue; |
| } |
| std::string new_context_blob; |
| Serialize_TPMS_CONTEXT(info.context, &new_context_blob); |
| const std::string& external_context_blob = iter->second; |
| actual_context_to_external_[new_context_blob] = external_context_blob; |
| external_context_to_actual_[external_context_blob] = new_context_blob; |
| actual_context_to_external_.erase(old_context_blob); |
| } |
| } |
| |
| bool ResourceManager::FixWarnings(const MessageInfo& command_info, |
| TPM_RC result) { |
| if ((result & RC_WARN) == 0) { |
| return false; |
| } |
| // This method can be called anytime without tracking whether the current |
| // operation is already an attempt to fix a warning. All re-entrance issues |
| // are dealt with here using the following rule: Never attempt to fix the same |
| // warning twice. |
| ScopedBool scoped_bool; |
| if (!fixing_warnings_) { |
| scoped_bool.Enable(&fixing_warnings_); |
| warnings_already_seen_.clear(); |
| } else if (warnings_already_seen_.count(result) > 0) { |
| return false; |
| } |
| warnings_already_seen_.insert(result); |
| switch (result) { |
| case TPM_RC_CONTEXT_GAP: |
| FixContextGap(command_info); |
| return true; |
| case TPM_RC_OBJECT_MEMORY: |
| case TPM_RC_OBJECT_HANDLES: |
| EvictObjects(command_info); |
| return true; |
| case TPM_RC_SESSION_MEMORY: |
| EvictSession(command_info); |
| return true; |
| case TPM_RC_MEMORY: |
| EvictObjects(command_info); |
| EvictSession(command_info); |
| return true; |
| case TPM_RC_SESSION_HANDLES: |
| FlushSession(command_info); |
| return true; |
| } |
| return false; |
| } |
| |
| void ResourceManager::FlushSession(const MessageInfo& command_info) { |
| TPM_HANDLE session_to_flush; |
| LOG(WARNING) << "Resource manager needs to flush a session."; |
| if (!ChooseSessionToEvict(command_info.all_session_handles, |
| &session_to_flush)) { |
| return; |
| } |
| TPM_RC result = |
| factory_.GetTpm()->FlushContextSync(session_to_flush, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| LOG(WARNING) << "Failed to flush session: " << GetErrorString(result); |
| return; |
| } |
| CleanupFlushedHandle(session_to_flush); |
| } |
| |
| std::string ResourceManager::GetActualContextFromExternalContext( |
| const std::string& external_context) { |
| auto iter = external_context_to_actual_.find(external_context); |
| if (iter == external_context_to_actual_.end()) { |
| return external_context; |
| } |
| return iter->second; |
| } |
| |
| bool ResourceManager::IsObjectHandle(TPM_HANDLE handle) const { |
| return ((handle & HR_RANGE_MASK) == HR_TRANSIENT); |
| } |
| |
| bool ResourceManager::IsSessionHandle(TPM_HANDLE handle) const { |
| return ((handle & HR_RANGE_MASK) == HR_HMAC_SESSION || |
| (handle & HR_RANGE_MASK) == HR_POLICY_SESSION); |
| } |
| |
| TPM_RC ResourceManager::LoadContext(const MessageInfo& command_info, |
| HandleInfo* handle_info) { |
| CHECK(!handle_info->is_loaded); |
| TPM_RC result = TPM_RC_SUCCESS; |
| int attempts = 0; |
| while (attempts++ < kMaxCommandAttempts) { |
| result = factory_.GetTpm()->ContextLoadSync( |
| handle_info->context, &handle_info->tpm_handle, nullptr); |
| if (!FixWarnings(command_info, result)) { |
| break; |
| } |
| } |
| if (result != TPM_RC_SUCCESS) { |
| LOG(ERROR) << __func__ |
| << ": Failed to load context: " << GetErrorString(result); |
| return result; |
| } |
| handle_info->is_loaded = true; |
| return result; |
| } |
| |
| TPM_RC ResourceManager::MakeError(TPM_RC tpm_error, |
| const ::base::Location& location) { |
| LOG(ERROR) << "ResourceManager::" << location.function_name() << ":" |
| << location.line_number() << ": " << GetErrorString(tpm_error); |
| return tpm_error + kResourceManagerTpmErrorBase; |
| } |
| |
| TPM_RC ResourceManager::ParseCommand(const std::string& command, |
| MessageInfo* command_info) { |
| CHECK(command_info); |
| std::string buffer = command; |
| TPM_ST tag; |
| TPM_RC result = Parse_TPM_ST(&buffer, &tag, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| if (tag != TPM_ST_SESSIONS && tag != TPM_ST_NO_SESSIONS) { |
| return MakeError(TPM_RC_TAG, FROM_HERE); |
| } |
| command_info->has_sessions = (tag == TPM_ST_SESSIONS); |
| |
| UINT32 size = 0; |
| result = Parse_UINT32(&buffer, &size, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| if (size != command.size()) { |
| return MakeError(TPM_RC_SIZE, FROM_HERE); |
| } |
| |
| result = Parse_TPM_CC(&buffer, &command_info->code, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| |
| if (IS_TPM_CC_VENDOR_CMD(command_info->code)) { |
| // Vendor-specific commands must have no sessions & no handles. |
| // All remaining data is parameter data. |
| if (!command_info->has_sessions) { |
| command_info->parameter_data = buffer; |
| return TPM_RC_SUCCESS; |
| } |
| } |
| |
| if (!IS_TPM2_CMD(command_info->code)) { |
| return MakeError(TPM_RC_COMMAND_CODE, FROM_HERE); |
| } |
| |
| size_t number_of_handles = GetNumberOfRequestHandles(command_info->code); |
| command_info->handles = ExtractHandlesFromBuffer(number_of_handles, &buffer); |
| if (number_of_handles != command_info->handles.size()) { |
| return MakeError(TPM_RC_SIZE, FROM_HERE); |
| } |
| for (const auto handle : command_info->handles) { |
| if (IsSessionHandle(handle)) { |
| command_info->all_session_handles.push_back(handle); |
| } |
| } |
| |
| if (command_info->has_sessions) { |
| // Sessions exist, so we're expecting a valid authorization size value. |
| UINT32 authorization_size = 0; |
| result = Parse_UINT32(&buffer, &authorization_size, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| if (buffer.size() < authorization_size || |
| authorization_size < kMinimumAuthorizationSize) { |
| return MakeError(TPM_RC_SIZE, FROM_HERE); |
| } |
| // Move out the parameter bytes, leaving only the authorization section. |
| command_info->parameter_data = buffer.substr(authorization_size); |
| buffer.erase(authorization_size); |
| // Parse as many authorization sessions as there are in the section. |
| while (!buffer.empty()) { |
| TPM_HANDLE handle; |
| result = Parse_TPM_HANDLE(&buffer, &handle, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| if (handle != TPM_RS_PW && session_handles_.count(handle) == 0) { |
| return MakeError(TPM_RC_HANDLE, FROM_HERE); |
| } |
| TPM2B_NONCE nonce; |
| result = Parse_TPM2B_NONCE(&buffer, &nonce, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| BYTE attributes; |
| result = Parse_BYTE(&buffer, &attributes, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| TPM2B_DIGEST authorization; |
| result = Parse_TPM2B_DIGEST(&buffer, &authorization, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| command_info->auth_session_handles.push_back(handle); |
| command_info->all_session_handles.push_back(handle); |
| command_info->session_continued.push_back((attributes & 1) == 1); |
| } |
| } else { |
| // No sessions, so all remaining data is parameter data. |
| command_info->parameter_data = buffer; |
| } |
| return TPM_RC_SUCCESS; |
| } |
| |
| TPM_RC ResourceManager::ParseResponse(const MessageInfo& command_info, |
| const std::string& response, |
| MessageInfo* response_info) { |
| CHECK(response_info); |
| std::string buffer = response; |
| TPM_ST tag; |
| TPM_RC result = Parse_TPM_ST(&buffer, &tag, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| if (tag != TPM_ST_SESSIONS && tag != TPM_ST_NO_SESSIONS) { |
| return MakeError(TPM_RC_TAG, FROM_HERE); |
| } |
| response_info->has_sessions = (tag == TPM_ST_SESSIONS); |
| |
| UINT32 size = 0; |
| result = Parse_UINT32(&buffer, &size, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| if (size != response.size()) { |
| LOG(ERROR) << "Invalid response: size field = " << size |
| << ", actual = " << response.size(); |
| return MakeError(TPM_RC_SIZE, FROM_HERE); |
| } |
| |
| result = Parse_TPM_RC(&buffer, &response_info->code, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| |
| if (IS_TPM_CC_VENDOR_CMD(command_info.code)) { |
| // Vendor-specific commands should have no sessions & no handles. |
| // All remaining data is parameter data. |
| response_info->parameter_data = buffer; |
| return TPM_RC_SUCCESS; |
| } |
| |
| if (response_info->code != TPM_RC_SUCCESS) { |
| // We have received an error response for a standard (non vendor-specific) |
| // TPM command. Error responses include only a header and error code. Return |
| // immediately; don't attempt to parse handles or sessions. |
| return TPM_RC_SUCCESS; |
| } |
| |
| size_t number_of_handles = GetNumberOfResponseHandles(command_info.code); |
| response_info->handles = ExtractHandlesFromBuffer(number_of_handles, &buffer); |
| if (number_of_handles != response_info->handles.size()) { |
| return MakeError(TPM_RC_SIZE, FROM_HERE); |
| } |
| if (response_info->has_sessions) { |
| // Sessions exist, so we're expecting a valid parameter size value. |
| UINT32 parameter_size = 0; |
| result = Parse_UINT32(&buffer, ¶meter_size, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| if (buffer.size() < parameter_size) { |
| return MakeError(TPM_RC_SIZE, FROM_HERE); |
| } |
| // Move out the parameter bytes, leaving only the authorization section. |
| response_info->parameter_data = buffer.substr(0, parameter_size); |
| buffer.erase(0, parameter_size); |
| // Parse as many authorization sessions as there are in the section. |
| while (!buffer.empty()) { |
| TPM2B_NONCE nonce; |
| result = Parse_TPM2B_NONCE(&buffer, &nonce, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| BYTE attributes; |
| result = Parse_BYTE(&buffer, &attributes, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| TPM2B_DIGEST acknowledgement; |
| result = Parse_TPM2B_DIGEST(&buffer, &acknowledgement, nullptr); |
| if (result != TPM_RC_SUCCESS) { |
| return MakeError(result, FROM_HERE); |
| } |
| response_info->session_continued.push_back((attributes & 1) == 1); |
| } |
| } else { |
| // No sessions, so all remaining data is parameter data. |
| response_info->parameter_data = buffer; |
| } |
| return TPM_RC_SUCCESS; |
| } |
| |
| void ResourceManager::ProcessExternalContextSave( |
| const MessageInfo& command_info, |
| const MessageInfo& response_info) { |
| CHECK_EQ(command_info.code, TPM_CC_ContextSave); |
| if (command_info.handles.size() != 1) { |
| LOG(WARNING) << "Invalid context save command."; |
| return; |
| } |
| // We know command_info.handles[0] is valid because this is validated when the |
| // command is parsed. |
| TPM_HANDLE saved_handle = command_info.handles[0]; |
| // Only track external context data for session handles. |
| if (!IsSessionHandle(saved_handle)) { |
| return; |
| } |
| std::string mutable_parameter = response_info.parameter_data; |
| TPMS_CONTEXT context; |
| std::string context_blob; |
| TPM_RC result = |
| Parse_TPMS_CONTEXT(&mutable_parameter, &context, &context_blob); |
| if (result != TPM_RC_SUCCESS) { |
| LOG(WARNING) << "Invalid context save response: " << GetErrorString(result); |
| return; |
| } |
| if (!mutable_parameter.empty()) { |
| LOG(WARNING) << "Invalid length of context save response string."; |
| return; |
| } |
| auto iter = session_handles_.find(saved_handle); |
| if (iter != session_handles_.end()) { |
| iter->second.is_loaded = false; |
| iter->second.context = context; |
| } else { |
| // Unknown handle? Not anymore. |
| LOG(WARNING) << "Context for unknown handle."; |
| HandleInfo new_handle_info; |
| new_handle_info.Init(saved_handle); |
| new_handle_info.is_loaded = false; |
| new_handle_info.context = context; |
| session_handles_[saved_handle] = new_handle_info; |
| } |
| // Use the original context data as the 'external' context data. If this gets |
| // virtualized, only the 'actual' context data will change. |
| external_context_to_actual_[context_blob] = context_blob; |
| actual_context_to_external_[context_blob] = context_blob; |
| } |
| |
| std::string ResourceManager::ProcessFlushContext( |
| const std::string& command, |
| const MessageInfo& command_info) { |
| std::string buffer = command_info.parameter_data; |
| // There must be exactly one handle in the parameters section. |
| std::vector<TPM_HANDLE> handles = ExtractHandlesFromBuffer(1, &buffer); |
| if (handles.size() != 1) { |
| return CreateErrorResponse(MakeError(TPM_RC_SIZE, FROM_HERE)); |
| } |
| TPM_HANDLE handle = handles[0]; |
| TPM_HANDLE actual_handle = handle; |
| if (IsObjectHandle(handle)) { |
| auto iter = virtual_object_handles_.find(handle); |
| if (iter == virtual_object_handles_.end()) { |
| return CreateErrorResponse(MakeError(TPM_RC_HANDLE, FROM_HERE)); |
| } |
| if (!iter->second.is_loaded) { |
| // The handle wasn't loaded so no need to bother the TPM. |
| CleanupFlushedHandle(handle); |
| return CreateErrorResponse(TPM_RC_SUCCESS); |
| } |
| actual_handle = iter->second.tpm_handle; |
| } |
| // Send a command with the original header but with |actual_handle| as the |
| // parameter. |
| std::string handle_blob; |
| Serialize_TPM_HANDLE(actual_handle, &handle_blob); |
| std::string updated_command = |
| command.substr(0, kMessageHeaderSize) + handle_blob; |
| // No need to loop and fix warnings, there are no actionable warnings on when |
| // flushing context. |
| std::string response = next_transceiver_->SendCommandAndWait(updated_command); |
| MessageInfo response_info; |
| TPM_RC result = ParseResponse(command_info, response, &response_info); |
| if (result != TPM_RC_SUCCESS) { |
| return CreateErrorResponse(result); |
| } |
| // Cleanup the handle locally even if the TPM did not recognize it. |
| if (response_info.code == TPM_RC_SUCCESS || |
| response_info.code == TPM_RC_HANDLE) { |
| CleanupFlushedHandle(handle); |
| } |
| return response; |
| } |
| |
| TPM_RC ResourceManager::ProcessInputHandle(const MessageInfo& command_info, |
| TPM_HANDLE virtual_handle, |
| TPM_HANDLE* actual_handle) { |
| // Only transient object handles are virtualized. |
| if (!IsObjectHandle(virtual_handle)) { |
| *actual_handle = virtual_handle; |
| return TPM_RC_SUCCESS; |
| } |
| auto handle_iter = virtual_object_handles_.find(virtual_handle); |
| if (handle_iter == virtual_object_handles_.end()) { |
| return MakeError(TPM_RC_HANDLE, FROM_HERE); |
| } |
| HandleInfo& handle_info = handle_iter->second; |
| if (!handle_info.is_loaded) { |
| TPM_RC result = LoadContext(command_info, &handle_info); |
| if (result != TPM_RC_SUCCESS) { |
| return result; |
| } |
| tpm_object_handles_[handle_info.tpm_handle] = virtual_handle; |
| VLOG(1) << "RELOAD_OBJECT: " << std::hex << virtual_handle; |
| } |
| VLOG(1) << "INPUT_HANDLE_REPLACE: " << std::hex << virtual_handle << " -> " |
| << std::hex << handle_info.tpm_handle; |
| *actual_handle = handle_info.tpm_handle; |
| return TPM_RC_SUCCESS; |
| } |
| |
| TPM_HANDLE ResourceManager::ProcessOutputHandle(TPM_HANDLE handle) { |
| // Track, but do not virtualize, session handles. |
| if (IsSessionHandle(handle)) { |
| auto session_handle_iter = session_handles_.find(handle); |
| if (session_handle_iter == session_handles_.end()) { |
| HandleInfo new_handle_info; |
| new_handle_info.Init(handle); |
| session_handles_[handle] = new_handle_info; |
| VLOG(1) << "OUTPUT_HANDLE_NEW_SESSION: " << std::hex << handle; |
| } |
| return handle; |
| } |
| // Only transient object handles are virtualized. |
| if (!IsObjectHandle(handle)) { |
| return handle; |
| } |
| auto virtual_handle_iter = tpm_object_handles_.find(handle); |
| if (virtual_handle_iter == tpm_object_handles_.end()) { |
| TPM_HANDLE new_virtual_handle = CreateVirtualHandle(); |
| HandleInfo new_handle_info; |
| new_handle_info.Init(handle); |
| virtual_object_handles_[new_virtual_handle] = new_handle_info; |
| tpm_object_handles_[handle] = new_virtual_handle; |
| VLOG(1) << "OUTPUT_HANDLE_NEW_VIRTUAL: " << std::hex << handle << " -> " |
| << std::hex << new_virtual_handle; |
| return new_virtual_handle; |
| } |
| VLOG(1) << "OUTPUT_HANDLE_REPLACE: " << std::hex << handle << " -> " |
| << std::hex << virtual_handle_iter->second; |
| return virtual_handle_iter->second; |
| } |
| |
| std::string ResourceManager::ReplaceHandles( |
| const std::string& message, |
| const std::vector<TPM_HANDLE>& new_handles) { |
| std::string handles_blob; |
| for (auto handle : new_handles) { |
| CHECK_EQ(Serialize_TPM_HANDLE(handle, &handles_blob), TPM_RC_SUCCESS); |
| } |
| std::string mutable_message = message; |
| CHECK_GE(message.size(), kMessageHeaderSize + handles_blob.size()); |
| return mutable_message.replace(kMessageHeaderSize, handles_blob.size(), |
| handles_blob); |
| } |
| |
| TPM_RC ResourceManager::SaveContext(const MessageInfo& command_info, |
| HandleInfo* handle_info) { |
| CHECK(handle_info->is_loaded); |
| TPM_RC result = TPM_RC_SUCCESS; |
| int attempts = 0; |
| while (attempts++ < kMaxCommandAttempts) { |
| std::string tpm_handle_name; |
| Serialize_TPM_HANDLE(handle_info->tpm_handle, &tpm_handle_name); |
| result = factory_.GetTpm()->ContextSaveSync(handle_info->tpm_handle, |
| tpm_handle_name, |
| &handle_info->context, nullptr); |
| if (!FixWarnings(command_info, result)) { |
| break; |
| } |
| } |
| if (result != TPM_RC_SUCCESS) { |
| LOG(ERROR) << __func__ |
| << ": Failed to load context: " << GetErrorString(result); |
| return result; |
| } |
| handle_info->is_loaded = false; |
| return result; |
| } |
| |
| ResourceManager::HandleInfo::HandleInfo() : is_loaded(false), tpm_handle(0) { |
| memset(&context, 0, sizeof(TPMS_CONTEXT)); |
| } |
| |
| void ResourceManager::HandleInfo::Init(TPM_HANDLE handle) { |
| tpm_handle = handle; |
| is_loaded = true; |
| time_of_create = base::TimeTicks::Now(); |
| time_of_last_use = base::TimeTicks::Now(); |
| } |
| |
| } // namespace trunks |