Commit message: Store untrusted output pointer in enclave

Validate the pointer after it's stored in enclave to avoid unexpected
modifications after it's validated.

PiperOrigin-RevId: 365648810
Change-Id: I3079128040c142e86bab8255b07d03562a6fcb61
CVE: CVE-2021-22548
Vulnerability description: An attacker can change the pointer to untrusted memory to point to trusted memory region which causes copying trusted memory to trusted memory, if the latter is later copied out, it allows for reading of memory regions from the trusted region. It is recommended to update past 0.6.2 or git commit https://github.com/google/asylo/commit/53ed5d8fd8118ced1466e509606dd2f473707a5c

Project: asylo
File: trusted_sgx.cc

uint32_t enc_untrusted_qe_get_quote_size(uint32_t *quote_size) {
  uint32_t result;
  CHECK_OCALL(ocall_enc_untrusted_qe_get_quote_size(&result, quote_size));
  return result;
}

Project: asylo
File: trusted_sgx.cc

PrimitiveStatus DonateThread(void *context, MessageReader *in,
                             MessageWriter *out) {
  if (in) {
    ASYLO_RETURN_IF_INCORRECT_READER_ARGUMENTS(*in, 1);
  }
  int result = 0;
  try {
    ThreadManager *thread_manager = ThreadManager::GetInstance();
    result = thread_manager->StartThread(in->next<pid_t>());
  } catch (...) {
    TrustedPrimitives::BestEffortAbort(
        "Uncaught exception in enclave entry handler: DonateThread. Failed to "
        "get ThreadManager instance or start the thread.");
  }
  return PrimitiveStatus(result);
}

Project: asylo
File: trusted_sgx.cc

PrimitiveStatus TrustedPrimitives::RegisterEntryHandler(
    uint64_t selector, const EntryHandler &handler) {
  return asylo::primitives::RegisterEntryHandler(selector, handler);
}

Project: asylo
File: trusted_sgx.cc

void TrustedPrimitives::BestEffortAbort(const char *message) {
  DebugPuts(message);
  delete UntrustedCacheMalloc::Instance();
  enc_reject_entries();
  MarkEnclaveAborted();
  abort();
}

Project: asylo

int DeliverSignal(int linux_signum, int linux_sigcode) {
  absl::optional<int> signum = FromkLinuxSignalNumber(linux_signum);
  if (!signum) {
    return 1;
  }
  siginfo_t info;
  info.si_signo = *signum;
  info.si_code = linux_sigcode;
  SignalManager *signal_manager = SignalManager::GetInstance();
  const sigset_t mask = signal_manager->GetSignalMask();

  // If the signal is blocked and still passed into the enclave. The signal
  // masks inside the enclave is out of sync with the untrusted signal mask.
  if (sigismember(&mask, *signum)) {
    return -1;
  }
  signal_manager->HandleSignal(*signum, &info, /*ucontext=*/nullptr);
  return 0;
}

Project: asylo
File: trusted_sgx.cc

int TrustedPrimitives::CreateThread() {
  MessageWriter input;
  MessageReader output;
  PrimitiveStatus status =
      UntrustedCall(kSelectorCreateThread, &input, &output);
  if (!status.ok()) {
    DebugPuts("CreateThread failed.");
    return -1;
  }
  if (output.size() != 1) {
    TrustedPrimitives::BestEffortAbort(
        "CreateThread error: unexpected output size received.");
  }
  return output.next<int>();
}

Project: asylo
File: trusted_sgx.cc

int asylo_enclave_call(uint64_t selector, void *buffer) {
  SgxParams *const sgx_params = reinterpret_cast<SgxParams *>(buffer);
  if (!IsValidUntrustedAddress(sgx_params)) {
    PrimitiveStatus status{primitives::AbslStatusCode::kInvalidArgument,
                           "input should lie within untrusted memory."};
    return status.error_code();
  }

  const void *input = sgx_params->input;
  size_t input_size = sgx_params->input_size;
  size_t output_size = 0;

  MessageReader in;
  MessageWriter out;
  // Copy untrusted input to a trusted buffer before deserializing to prevent
  // TOC/TOU attacks.
  auto trusted_input = CopyFromUntrusted(input, input_size);
  if (trusted_input) {
    in.Deserialize(trusted_input.get(), input_size);
  }

  PrimitiveStatus status = InvokeEntryHandler(selector, &in, &out);

  // Serialize |out| to untrusted memory and pass that as output. The untrusted
  // caller is still responsible for freeing |*output|, which now points to
  // untrusted memory.
  output_size = out.MessageSize();
  if (out.MessageSize() > 0) {
    // Serialize to a trusted output buffer first to prevent TOC/TOU attacks.
    std::unique_ptr<char[]> trusted_output(new char[output_size]);
    out.Serialize(trusted_output.get());
    sgx_params->output = CopyToUntrusted(trusted_output.get(), output_size);
  }
  sgx_params->output_size = static_cast<uint64_t>(output_size);
  return status.error_code();
}

Project: asylo
File: trusted_sgx.cc

void RegisterInternalHandlers() {
  // Register the enclave donate thread entry handler.
  if (!TrustedPrimitives::RegisterEntryHandler(kSelectorAsyloDonateThread,
                                               EntryHandler{DonateThread})
           .ok()) {
    TrustedPrimitives::BestEffortAbort(
        "Could not register entry handler: DonateThread.");
  }

  // Register the enclave finalization entry handler.
  if (!TrustedPrimitives::RegisterEntryHandler(kSelectorAsyloFini,
                                               EntryHandler{FinalizeEnclave})
           .ok()) {
    TrustedPrimitives::BestEffortAbort(
        "Could not register entry handler: FinalizeEnclave");
  }
}

Project: asylo
File: trusted_sgx.cc

void *TrustedPrimitives::UntrustedLocalAlloc(size_t size) noexcept {
  void *result;
  CHECK_OCALL(
      ocall_untrusted_local_alloc(&result, static_cast<uint64_t>(size)));
  if (result && !IsOutsideEnclave(result, static_cast<uint64_t>(size))) {
    TrustedPrimitives::BestEffortAbort(
        "Allocated memory not found to be outside the enclave.");
  }

  // On error, malloc returns nullptr and sets errno to ENOMEM.
  if (!result) {
    errno = ENOMEM;
    TrustedPrimitives::DebugPuts("UntrustedLocalAlloc on SGX failed.");
  }
  return result;
}

Project: asylo
File: trusted_sgx.cc

void DeAllocateUntrustedBuffers(void **free_list, size_t count) {
  if (!IsValidUntrustedAddress(free_list)) {
    TrustedPrimitives::BestEffortAbort(
        "free_list expected to be in untrusted memory.");
  }
  CHECK_OCALL(ocall_enc_untrusted_deallocate_free_list(
      free_list, static_cast<uint64_t>(count)));
}

Project: asylo
File: trusted_sgx.cc

void TrustedPrimitives::DebugPuts(const char *message) {
  int result;
  CHECK_OCALL(ocall_untrusted_debug_puts(&result, message));
  if (result < 0) {
    errno = EOF;
  }
}

Project: asylo
File: trusted_sgx.cc

bool TrustedPrimitives::IsInsideEnclave(const void *addr, size_t size) {
  return sgx_is_within_enclave(addr, size) == 1;
}

Project: asylo
File: trusted_sgx.cc

bool TrustedPrimitives::IsOutsideEnclave(const void *addr, size_t size) {
  return sgx_is_outside_enclave(addr, size) == 1;
}

Project: asylo

PrimitiveStatus TrustedPrimitives::UntrustedCall(uint64_t untrusted_selector,
                                                 MessageWriter *input,
                                                 MessageReader *output) {
  int ret;

  UntrustedCacheMalloc *untrusted_cache = UntrustedCacheMalloc::Instance();

  SgxParams *const sgx_params =
      reinterpret_cast<SgxParams *>(untrusted_cache->Malloc(sizeof(SgxParams)));
  if (!TrustedPrimitives::IsOutsideEnclave(sgx_params, sizeof(SgxParams))) {
    TrustedPrimitives::BestEffortAbort(
        "UntrustedCall: sgx_param should be in untrusted memory");
  }
  Cleanup clean_up(
      [sgx_params, untrusted_cache] { untrusted_cache->Free(sgx_params); });
  sgx_params->input_size = 0;
  sgx_params->input = nullptr;
  if (input) {
    sgx_params->input_size = input->MessageSize();
    if (sgx_params->input_size > 0) {
      // Allocate and copy data to |input_buffer|.
      sgx_params->input = untrusted_cache->Malloc(sgx_params->input_size);
      const void *input_pointer = sgx_params->input;
      uint64_t input_size = sgx_params->input_size;
      if (!TrustedPrimitives::IsOutsideEnclave(input_pointer, input_size)) {
        TrustedPrimitives::BestEffortAbort(
            "UntrustedCall: sgx_param input should be in untrusted memory");
      }
      input->Serialize(const_cast<void *>(input_pointer));
    }
  }
  sgx_params->output_size = 0;
  sgx_params->output = nullptr;
  CHECK_OCALL(
      ocall_dispatch_untrusted_call(&ret, untrusted_selector, sgx_params));
  if (sgx_params->input) {
    untrusted_cache->Free(const_cast<void *>(sgx_params->input));
  }
  const void *output_pointer = sgx_params->output;
  uint64_t output_size = sgx_params->output_size;
  if (!TrustedPrimitives::IsOutsideEnclave(output_pointer, output_size)) {
    TrustedPrimitives::BestEffortAbort(
        "UntrustedCall: sgx_param output should be in untrusted memory");
  }
  if (sgx_params->output) {
    // For the results obtained in |output_buffer|, copy them to |output|
    // before freeing the buffer.
    output->Deserialize(output_pointer, output_size);
    TrustedPrimitives::UntrustedLocalFree(sgx_params->output);
  }
  return PrimitiveStatus::OkStatus();
}

Project: asylo
File: trusted_sgx.cc

void TrustedPrimitives::UntrustedLocalFree(void *ptr) noexcept {
  CHECK_OCALL(ocall_untrusted_local_free(ptr));
}

Project: asylo
File: trusted_sgx.cc

uint32_t enc_untrusted_qe_get_quote(const sgx_report_t *app_report,
                                    uint32_t quote_size, uint8_t *quote) {
  uint32_t result;
  CHECK_OCALL(
      ocall_enc_untrusted_qe_get_quote(&result, app_report, quote_size, quote));
  return result;
}

Project: asylo
File: trusted_sgx.cc

uint32_t enc_untrusted_qe_get_target_info(sgx_target_info_t *qe_target_info) {
  uint32_t result;
  CHECK_OCALL(ocall_enc_untrusted_qe_get_target_info(&result, qe_target_info));
  return result;
}

Project: asylo

int RegisterSignalHandler(int signum,
                          void (*klinux_sigaction)(int, klinux_siginfo_t *,
                                                   void *),
                          const sigset_t mask, int flags) {
  // Never pass SA_SIGINFO through this layer. It's always appended to the flags
  // over in the untrusted layer.
  flags &= ~SA_SIGINFO;

  flags &= SA_NODEFER | SA_RESETHAND;

  absl::optional<int> klinux_signum = TokLinuxSignalNumber(signum);
  absl::optional<int> klinux_flags = TokLinuxSignalFlag(flags);
  if (!klinux_signum || !klinux_flags) {
    errno = EINVAL;
    return -1;
  }
  klinux_sigset_t klinux_mask;
  TokLinuxSigset(&mask, &klinux_mask);
  int ret;
  CHECK_OCALL(ocall_enc_untrusted_register_signal_handler(
      &ret, *klinux_signum, reinterpret_cast<void *>(klinux_sigaction),
      reinterpret_cast<void *>(&klinux_mask), sizeof(klinux_mask),
      *klinux_flags));
  return ret;
}

Project: asylo
File: trusted_sgx.cc

void *TrustedPrimitives::UntrustedLocalMemcpy(void *dest, const void *src,
                                              size_t size) noexcept {
  return memcpy(dest, src, size);
}

Project: asylo
File: trusted_sgx.cc

void **AllocateUntrustedBuffers(size_t count, size_t size) {
  void **buffers;
  CHECK_OCALL(ocall_enc_untrusted_allocate_buffers(
      &buffers, static_cast<uint64_t>(count), static_cast<uint64_t>(size)));
  if (!buffers || !TrustedPrimitives::IsOutsideEnclave(buffers, size)) {
    TrustedPrimitives::BestEffortAbort(
        "allocated buffers (for use by UntrustedCacheMalloc) found to not be "
        "in untrusted memory.");
  }
  return buffers;
}

Project: asylo
File: trusted_sgx.cc

pid_t InvokeFork(const char *enclave_name, bool restore_snapshot) {
  int32_t ret;
  sgx_status_t status =
      ocall_enc_untrusted_fork(&ret, enclave_name, restore_snapshot);
  if (status != SGX_SUCCESS) {
    errno = EINTR;
    return -1;
  }
  return ret;
}

Project: asylo
File: trusted_sgx.cc

uint32_t enc_untrusted_ql_set_quote_config(const sgx_ql_config_t *config) {
  uint32_t result;
  CHECK_OCALL(ocall_enc_untrusted_ql_set_quote_config(
      &result, config, config->cert_data_size, config->p_cert_data));
  return result;
}

Project: asylo
File: trusted_sgx.cc

PrimitiveStatus FinalizeEnclave(void *context, MessageReader *in,
                                MessageWriter *out) {
  if (in) {
    ASYLO_RETURN_IF_READER_NOT_EMPTY(*in);
  }
  // Delete instance of the global memory pool singleton freeing all memory held
  // by the pool.
  delete UntrustedCacheMalloc::Instance();
  return asylo_enclave_fini();
}