交易指南

簡介

使用未經過沙箱處理的 C/C++ 程式庫時,連結器會確保編譯後提供所有必要函式,因此不必擔心 API 呼叫可能會在執行階段失敗。

不過,使用沙箱化程式庫時,程式庫的執行作業會位於獨立程序中。如果 API 呼叫失敗,就必須檢查與透過 RPC 層傳遞呼叫相關的所有問題。有時 RPC 層錯誤可能不重要,例如執行大量處理作業時,沙箱剛重新啟動。

不過,基於上述原因,請務必擴大對沙箱化 API 呼叫傳回值的定期錯誤檢查,包括檢查 RPC 層是否傳回錯誤。因此,所有程式庫函式原型都會傳回 ::sapi::StatusOr<T>,而非 T。如果程式庫函式呼叫失敗 (例如因違反沙箱規定),回傳值會包含發生錯誤的詳細資料。

處理 RPC 層錯誤表示每次呼叫 SandboxedLibrary 後,都會額外檢查 SAPI 的 RPC 層。為處理這些例外狀況,SAPI 提供 SAPI 交易模組 (transaction.h)。這個模組包含 ::sapi::Transaction 類別,並確保對 Sandboxed Library 的所有函式呼叫都已完成,且沒有任何 RPC 層級問題,或傳回相關錯誤。

SAPI 交易

SAPI 會將主機程式碼與沙箱程式庫隔離,並讓呼叫端能夠重新啟動或中止有問題的資料處理要求。SAPI 交易更進一步,會自動重複執行失敗的程序。

SAPI 交易可透過兩種不同方式使用:直接從 ::sapi::Transaction 繼承,或使用傳遞至 ::sapi::BasicTransaction 的函式指標。

如要定義 SAPI 交易,請覆寫下列三個函式:

SAPI 交易方法
::sapi::Transaction::Init() 這與呼叫一般 C/C++ 程式庫的初始化方法類似。 除非交易重新啟動,否則在每次與沙箱程式庫的交易中,系統只會呼叫這個方法一次。 如果重新啟動,系統會再次呼叫這個方法,無論先前重新啟動過幾次都一樣。
::sapi::Transaction::Main() 每次呼叫 ::sapi::Transaction::Run() 時,系統都會呼叫這個方法。
::sapi::Transaction::Finish() 這與呼叫一般 C/C++ 程式庫的清除方法類似。 這個方法只會在 SAPI 交易物件毀損期間呼叫一次。

一般圖書館使用

在沒有沙箱程式庫的專案中,處理程式庫時的常見模式如下:

LibInit();
while (data = NextDataToProcess()) {
  result += LibProcessData(data);
}
LibClose();

程式庫會先初始化,然後使用程式庫的匯出函式,最後呼叫結束/關閉函式來清理環境。

沙箱程式庫使用

在含有沙箱化程式庫的專案中,使用含有回呼的交易時,一般程式庫使用中的程式碼會轉譯為下列程式碼片段:

// Overwrite the Init method, passed to BasicTransaction initialization
::absl::Status Init(::sapi::Sandbox* sandbox) {
  // Instantiate the SAPI Object
  LibraryAPI lib(sandbox);
  // Instantiate the Sandboxed Library
  SAPI_RETURN_IF_ERROR(lib.LibInit());
  return ::absl::OkStatus();
}

// Overwrite the Finish method, passed to BasicTransaction initialization
::absl::Status Finish(::sapi::Sandbox *sandbox) {
  // Instantiate the SAPI Object
  LibraryAPI lib(sandbox);
  // Clean-up sandboxed library instance
  SAPI_RETURN_IF_ERROR(lib.LibClose());
  return ::absl::OkStatus();
}

// Wrapper function to process data, passed to Run method call
::absl::Status HandleData(::sapi::Sandbox *sandbox, Data data_to_process,
                           Result *out) {
  // Instantiate the SAPI Object
  LibraryAPI lib(sandbox);
  // Call the sandboxed function LibProcessData
  SAPI_ASSIGN_OR_RETURN(*out, lib.LibProcessData(data_to_process));
  return ::absl::OkStatus();
}

void Handle() {
  // Use SAPI Transactions by passing function pointers to ::sapi::BasicTransaction
  ::sapi::BasicTransaction transaction(Init, Finish);
  while (data = NextDataToProcess()) {
    ::sandbox2::Result result;
    // call the ::sapi::Transaction::Run() method
    transaction.Run(HandleData, data, &result);
    // ...
  }
  // ...
}

如果 handle_data 呼叫期間發生錯誤,交易類別會確保重新初始化程式庫。詳情請參閱下一個章節。

交易重新啟動

如果沙箱化程式庫 API 呼叫在執行 SAPI 交易方法時發生錯誤 (請參閱上表),交易就會重新啟動。預設的重新啟動次數是在 transaction.h 中以 kDefaultRetryCnt 定義。

以下是會觸發重新啟動作業的錯誤範例:

  • 發生沙箱違規情形
  • 沙箱程序異常終止
  • 沙箱函式因程式庫錯誤而傳回錯誤代碼

重新啟動程序會觀察正常的 Init()Main() 流程,如果重複呼叫 ::sapi::Transaction::Run() 方法會傳回錯誤,則整個方法會向呼叫端傳回錯誤

沙箱或 RPC 錯誤處理

自動產生的沙箱化程式庫介面會盡可能接近原始 C/C++ 程式庫函式原型。不過,沙箱化程式庫必須能夠發出任何沙箱或 RPC 錯誤的信號。

這是透過使用 ::sapi::StatusOr<T> 傳回型別 (或傳回 void 的函式使用 ::sapi::Status),而非直接傳回沙箱函式的傳回值來達成。

SAPI 進一步提供一些便利的巨集,可檢查 SAPI 狀態物件並做出反應。這些巨集是在 status_macro.h 標頭檔案中定義。

下列程式碼片段摘錄自 sum 範例,說明如何使用 SAPI 狀態和巨集:

// Instead of void, use ::sapi::Status
::sapi::Status SumTransaction::Main() {
  // Instantiate the SAPI Object
  SumApi f(sandbox());

  // ::sapi::StatusOr<int> sum(int a, int b)
  SAPI_ASSIGN_OR_RETURN(int v, f.sum(1000, 337));
  // ...

  // ::sapi::Status sums(sapi::v::Ptr* params)
  SumParams params;
  params.mutable_data()->a = 1111;
  params.mutable_data()->b = 222;
  params.mutable_data()->ret = 0;
  SAPI_RETURN_IF_ERROR(f.sums(params.PtrBoth()));
  // ...
  // Gets symbol address and prints its value
  int *ssaddr;
  SAPI_RETURN_IF_ERROR(sandbox()->Symbol(
      "sumsymbol", reinterpret_cast<void**>(&ssaddr)));
  ::sapi::v::Int sumsymbol;
  sumsymbol.SetRemote(ssaddr);
  SAPI_RETURN_IF_ERROR(sandbox()->TransferFromSandboxee(&sumsymbol));
  // ...
  return ::sapi::OkStatus();
}

沙箱重新啟動次數

許多沙箱化程式庫都會處理私密的使用者輸入內容。如果沙箱化程式庫在某個時間點損毀,且在執行期間儲存資料,這類私密資料就會面臨風險。舉例來說,如果 Imagemagick 程式庫的沙箱版本開始傳送先前執行的圖片。

為避免這種情況,請勿在多次執行時重複使用沙箱。如要停止重複使用沙箱,主機程式碼可以使用 SAPI 交易,透過 ::sapi::Sandbox::Restart()::sapi::Transaction::Restart() 啟動沙箱化程式庫程序。

重新啟動會使任何對沙箱程式庫程序的參照失效。這表示傳遞的檔案描述元或分配的記憶體將不復存在。