Commit message: Fix vulnerability in UntrustedCacheMalloc

The pointer array is stored in untrusted memory, so we cannot trust the
value even after validation. We should validate the pointer is pointing
to untrusted memory after it's stored inside the enclave.

PiperOrigin-RevId: 358474391
Change-Id: I63cf6c251bdaf1b491dbf06cc0dcf77f7b141756
CVE: CVE-2021-22550
CWE: CWE-200
Vulnerability description: An attacker can modify the pointers in enclave memory to overwrite arbitrary memory addresses within the secure enclave. It is recommended to update past 0.6.3 or git commit https://github.com/google/asylo/commit/a47ef55db2337d29de19c50cd29b0deb2871d31c

Project: asylo
File: untrusted_cache_malloc.cc

void *untrusted_cache_malloc(size_t size) {
  asylo::UntrustedCacheMalloc *instance =
      asylo::UntrustedCacheMalloc::Instance();
  return instance->Malloc(size);
}

Project: asylo
File: untrusted_cache_malloc.cc

void *UntrustedCacheMalloc::GetBuffer() {
  void **buffers = nullptr;
  void *buffer;
  bool is_pool_empty;

  {
    LockGuard spin_lock(&lock_);
    is_pool_empty = buffer_pool_.empty();
    if (is_pool_empty) {
      buffers =
          primitives::AllocateUntrustedBuffers(kPoolIncrement, kPoolEntrySize);
      for (int i = 0; i < kPoolIncrement; i++) {
        void *buf = buffers[i];
        if (!buf || !TrustedPrimitives::IsOutsideEnclave(buf, kPoolEntrySize)) {
          TrustedPrimitives::BestEffortAbort(
              "Cached buffer is not outside the enclave");
        }
        buffer_pool_.push(buf);
      }
    }
    buffer = buffer_pool_.top();
    buffer_pool_.pop();
    busy_buffers_.insert(buffer);
  }

  if (is_pool_empty) {
    // Free memory held by the array of buffer pointers returned by
    // AllocateUntrustedBuffers.
    Free(buffers);
  }
  return buffer;
}

Project: asylo
File: untrusted_cache_malloc.cc

void UntrustedCacheMalloc::PushToFreeList(void *buffer) {
  free_list_->buffers.get()[free_list_->count] = buffer;
  free_list_->count++;

  if (free_list_->count == kFreeListCapacity) {
    primitives::DeAllocateUntrustedBuffers(free_list_->buffers.get(),
                                           kFreeListCapacity);
    free_list_->count = 0;
  }
}

Project: asylo
File: untrusted_cache_malloc.cc

void untrusted_cache_free(void *buffer) {
  asylo::UntrustedCacheMalloc *instance =
      asylo::UntrustedCacheMalloc::Instance();
  instance->Free(buffer);
}

Project: asylo
File: untrusted_cache_malloc.cc

void *UntrustedCacheMalloc::Malloc(size_t size) {
  // Don't access UnturstedCacheMalloc if not running on normal heap, otherwise
  // it will cause error when UntrustedCacheMalloc tries to free the memory on
  // the normal heap.
  if (is_destroyed_ || (size > kPoolEntrySize) || GetSwitchedHeapNext()) {
    return primitives::TrustedPrimitives::UntrustedLocalAlloc(size);
  }
  return GetBuffer();
}

Project: asylo
File: untrusted_cache_malloc.cc

UntrustedCacheMalloc *UntrustedCacheMalloc::Instance() {
  static TrustedSpinLock lock(/*is_recursive=*/false);
  static UntrustedCacheMalloc *instance = nullptr;
  if (instance == nullptr) {
    LockGuard guard(&lock);
    if (instance == nullptr) {
      instance = new UntrustedCacheMalloc();
    }
  }
  return instance;
}

Project: asylo
File: untrusted_cache_malloc.cc

UntrustedCacheMalloc::UntrustedCacheMalloc() : lock_(/*is_recursive=*/true) {
  if (is_destroyed_) {
    return;
  }
  // Initialize a free list object in the trusted heap. The free list object
  // stores an array of buffers stored in the untrusted heap.
  free_list_ = absl::make_unique<FreeList>();
  free_list_->buffers.reset(reinterpret_cast<void **>(
      primitives::TrustedPrimitives::UntrustedLocalAlloc(sizeof(void *) *
                                                         kFreeListCapacity)));
  free_list_->count = 0;
}

Project: asylo
File: untrusted_cache_malloc.cc

void UntrustedCacheMalloc::Free(void *buffer) {
  if (is_destroyed_ || GetSwitchedHeapNext()) {
    primitives::TrustedPrimitives::UntrustedLocalFree(buffer);
    return;
  }
  LockGuard spin_lock(&lock_);

  // Add the buffer to the free list if it was not allocated from the buffer
  // pool and was allocated via UntrustedLocalAlloc. If the
  // buffer was allocated from the buffer pool push it back to the pool.
  if (busy_buffers_.find(buffer) == busy_buffers_.end()) {
    PushToFreeList(buffer);
    return;
  }
  busy_buffers_.erase(buffer);
  buffer_pool_.push(buffer);
}

Project: asylo
File: untrusted_cache_malloc.cc

UntrustedCacheMalloc::~UntrustedCacheMalloc() {
  while (!buffer_pool_.empty()) {
    PushToFreeList(buffer_pool_.top());
    buffer_pool_.pop();
  }

  // Free remaining elements in the free_list_.
  // The free_list_ object and the struct FreeList member buffers are destroyed
  // when the unique pointers referencing them go out of scope.
  if (free_list_->count > 0) {
    primitives::DeAllocateUntrustedBuffers(free_list_->buffers.get(),
                                           free_list_->count);
  }
  is_destroyed_ = true;
}