Update secure storage glue code to use RBMP FS.

This changes the NVRAM storage layer to store data in the RPMB file
system. This gives us good tamper-resistance behavior.

BUG: 29147036
Change-Id: I1011b0881186ac936d2981244f717bb1f6b9a6ae
diff --git a/secure_storage.cpp b/secure_storage.cpp
index ee744c1..8e70b9f 100644
--- a/secure_storage.cpp
+++ b/secure_storage.cpp
@@ -29,70 +29,95 @@
 #include <nvram/core/logger.h>
 
 // This file implements the NVRAM storage layer on top of Trusty's secure
-// storage service.
-// TODO: Make sure to use RPMB-backed storage here in order to get
-// tamper-resistant semantics.
+// storage service. It keeps header and space data in files stored in the RPMB
+// file system.
 
 namespace nvram {
 namespace storage {
 
 namespace {
 
-// Name of the storage object holding the header.
+// Name of the file holding the header.
 const char kHeaderFileName[] = "header";
 
-// Pattern for space data storage object names.
+// Pattern for space data file names.
 const char kSpaceDataFileNamePattern[] = "space_%08x";
 
-// Maximum size of objects we're willing to read and write.
+// Maximum file size we're willing to read and write.
 const size_t kMaxFileSize = 2048;
 
 // Buffer size for formatting names.
 using NameBuffer = char[16];
 
-// Formats the storage object name for the given space index.
+// Formats the storage files name for the given space index.
 bool FormatSpaceFileName(NameBuffer name, uint32_t index) {
   int ret =
       snprintf(name, sizeof(NameBuffer), kSpaceDataFileNamePattern, index);
   return ret >= 0 && ret < static_cast<int>(sizeof(NameBuffer));
 };
 
-// Translates an Trusty error code returned by secure storage (as defined in
-// lk/include/errno.h, mostly following POSIX) to the corresponding NVRAM
-// storage status. Most error conditions are subsumed into the generic
-// Status::kStorageError. More granular status codes can be added as needed.
-Status ErrorToStatus(int error) {
-  switch (error) {
-    case 0:
-      return Status::kSuccess;
-    case -ENOENT:
-      return Status::kNotFound;
-    default:
-      break;
-  }
-
-  return Status::kStorageError;
-}
-
 // An RAII wrapper for storage_session_t.
 class StorageSession {
  public:
-  StorageSession() { error_ = storage_open_session(&session_); }
+  StorageSession() {
+    // The Access-Controlled NVRAM HAL specification states that NVRAM spaces
+    // which are subject to persistent write lock must be prevented from being
+    // written until the next 'full device reset’, which is defined to be an
+    // event that 'clears device state including all user data'. In other words,
+    // a factory reset that wipes user data must clear NVRAM data as well, and
+    // vice versa, losing NVRAM data isn't acceptable unless all other device
+    // state gets wiped as well.
+    //
+    // Trusty's secure storage subsystem offers storage modes that either have
+    // tampering detection (tampering can be detected but not prevented, i.e.
+    // the non-secure OS can delete all data) or tamper-proof behavior (data is
+    // stored in RPMB, which can only be written by the secure OS).
+    //
+    // Given these storage modes, there are two ways to achieve the semantics
+    // required by the HAL spec:
+    //  1. Use tamper-evident storage. Upon discovering tampering, force a full
+    //     device reset. Unfortunately, secure storage currently doesn't surface
+    //     an indication of detected tampering, so it's currently impossible to
+    //     distinguish the situation of first initialization (i.e. no data
+    //     present) from detected tampering (i.e. full data deletion by
+    //     non-secure OS).
+    //  2. Use tamper-proof storage. This requires further integration work with
+    //     the bootloader / factory reset / recovery flow to make sure NVRAM
+    //     data gets wiped during full device reset as mandated by the HAL
+    //     specification.
+    //
+    // Either way, there's further work required to meet the HAL specification
+    // requirements. The code below passes STORAGE_CLIENT_TDEA_PORT to request
+    // tamper-evident semantics. Since we can't reliably detect tampering right
+    // now, this DOES NOT satisfy the specification requirements. However, going
+    // with tamper-evident behavior at least gives us the correct device reset
+    // behavior for now as long as we don't have bootloader / factory reset /
+    // recovery integration.
+    //
+    // TODO(mnissler): Revisit once tampering can be reliably detected and/or
+    // bootloader and recovery flows can handle wiping NVRAM state.
+    error_ = storage_open_session(&handle_, STORAGE_CLIENT_TDEA_PORT);
+    if (error_ != 0) {
+      NVRAM_LOG_ERR("Failed to open storage session: %d", error_);
+    }
+  }
   ~StorageSession() {
     if (error_ != 0) {
       return;
     }
 
-    storage_close_session(session_);
+    // Close the storage session. Note that this will discard any non-committed
+    // transactions.
+    storage_close_session(handle_);
     error_ = -EINVAL;
   }
 
   int error() const { return error_; }
-  storage_session_t session() { return session_; }
+  storage_session_t handle() { return handle_; }
 
  private:
   int error_ = -EINVAL;
-  storage_session_t session_ = 0;
+  storage_session_t handle_ = 0;
 };
 
 // An RAII wrapper for file_handle_t.
@@ -100,8 +125,9 @@
  public:
   FileHandle(const char* name, uint32_t flags) {
     if (session_.error() == 0) {
-      error_ = storage_open_file(session(), &handle_, const_cast<char*>(name),
-                                 flags);
+      error_ = storage_open_file(session_.handle(), &handle_,
+                                 const_cast<char*>(name), flags,
+                                 0 /* don't commit */);
     } else {
       error_ = session_.error();
     }
@@ -111,12 +137,12 @@
       return;
     }
 
-    storage_close_file(session(), handle_);
+    storage_close_file(handle_);
     error_ = -EINVAL;
   }
 
+  StorageSession* session() { return &session_; }
   int error() const { return error_; }
-  storage_session_t session() { return session_.session(); }
   file_handle_t handle() { return handle_; }
 
  private:
@@ -125,19 +151,24 @@
   file_handle_t handle_ = 0;
 };
 
-// Loads the storage object identified by |name|.
+// Loads the file identified by |name|.
 Status LoadFile(const char* name, Blob* blob) {
-  FileHandle file(name, 0);
-  if (file.error()) {
-    NVRAM_LOG_ERR("Failed to open %s: %d" , name, file.error());
-    return ErrorToStatus(file.error());
+  FileHandle file(name, 0 /* no open flags, just read */);
+  switch (file.error()) {
+    case 0:
+      break;
+    case -ENOENT:
+      return Status::kNotFound;
+    default:
+      NVRAM_LOG_ERR("Failed to open %s: %d" , name, file.error());
+      return Status::kStorageError;
   }
 
   storage_off_t size = 0;
-  int error = storage_get_file_size(file.session(), file.handle(), &size);
+  int error = storage_get_file_size(file.handle(), &size);
   if (error != 0) {
     NVRAM_LOG_ERR("Failed to get size for %s: %d" , name, error);
-    return ErrorToStatus(error);
+    return Status::kStorageError;
   }
 
   if (size > kMaxFileSize) {
@@ -145,24 +176,15 @@
     return Status::kStorageError;
   }
 
-  // File creation and write are separate, i.e. the write path does not perform
-  // them as a single atomic operation. Hence there's a chance that an object
-  // has been created, but the write didn't go through. Just report the object
-  // as absent in this case.
-  if (size == 0) {
-    return Status::kNotFound;
-  }
-
   if (!blob->Resize(size)) {
     NVRAM_LOG_ERR("Failed to allocate read buffer for %s" , name);
     return Status::kStorageError;
   }
 
-  int size_read = storage_read(file.session(), file.handle(), 0, blob->data(),
-                               blob->size());
+  int size_read = storage_read(file.handle(), 0, blob->data(), blob->size());
   if (size_read < 0) {
     NVRAM_LOG_ERR("Failed to read %s: %d" , name, size_read);
-    return ErrorToStatus(size_read);
+    return Status::kStorageError;
   }
 
   if (static_cast<size_t>(size_read) != blob->size()) {
@@ -174,25 +196,27 @@
   return Status::kSuccess;
 }
 
-// Writes blob to the storage object indicated by |name|.
+// Writes blob to the file indicated by |name|.
 Status StoreFile(const char* name, const Blob& blob) {
   if (blob.size() > kMaxFileSize) {
-    NVRAM_LOG_ERR("Bad object size for %s: %u" , name, blob.size());
+    NVRAM_LOG_ERR("Bad file size for %s: %u" , name, blob.size());
     return Status::kStorageError;
   }
 
-  FileHandle file(name, STORAGE_FILE_OPEN_CREATE);
+  // Note that it's OK to truncate on open, since the changes to the file will
+  // only become visible when the transaction gets committed.
+  FileHandle file(name, STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE);
   if (file.error()) {
     NVRAM_LOG_ERR("Failed to open %s: %d" , name, file.error());
-    return ErrorToStatus(file.error());
+    return Status::kStorageError;
   }
 
   int size_written =
-      storage_write(file.session(), file.handle(), 0,
-                    const_cast<uint8_t*>(blob.data()), blob.size());
+      storage_write(file.handle(), 0, const_cast<uint8_t*>(blob.data()),
+                    blob.size(), 0 /* don't commit */);
   if (size_written < 0) {
     NVRAM_LOG_ERR("Failed to write %s: %d" , name, size_written);
-    return ErrorToStatus(size_written);
+    return Status::kStorageError;
   }
 
   if (static_cast<size_t>(size_written) != blob.size()) {
@@ -201,22 +225,12 @@
     return Status::kStorageError;
   }
 
-  // Adjust the file size in case the data shrunk. Note that this is a separate
-  // operation, so may not go through if we crash.. However, the higher layers
-  // can deal with trailing data safely, so it is OK if we crash before the size
-  // update hits disk.
-  //
-  // Note that the alternative of opening the file with the truncate flag is not
-  // OK - this would result in the truncation getting committed to disk
-  // separately from the write, thus creating the risk of data loss when
-  // crashing after the truncate, but before the write.
-  //
-  // TODO(mnissler): Revisit this when switching to the RPMB file system, which
-  // is going to provide more powerful transactional semantics.
-  int error = storage_set_file_size(file.session(), file.handle(), blob.size());
+  // File updated successful, so commit the storage transaction now.
+  int error =
+      storage_end_transaction(file.session()->handle(), true /* commit */);
   if (error) {
-    NVRAM_LOG_ERR("Failed to adjust file size for %s: %d\n", name, error);
-    // Return success below.
+    NVRAM_LOG_ERR("Failed to commit %s after write: %d", name, error);
+    return Status::kStorageError;
   }
 
   return Status::kSuccess;
@@ -256,15 +270,16 @@
 
   StorageSession session;
   if (session.error() != 0) {
-    return ErrorToStatus(session.error());
+    return Status::kStorageError;
   }
 
-  int error = storage_delete_file(session.session(), name);
-  if (error == -ENOENT) {
+  int error = storage_delete_file(session.handle(), name, STORAGE_OP_COMPLETE);
+  if (error == 0 || error == -ENOENT) {
     return Status::kSuccess;
   }
 
-  return ErrorToStatus(error);
+  NVRAM_LOG_ERR("Failed to delete %s: %d", name, error);
+  return Status::kStorageError;
 }
 
 }  // namespace strorage