取引ガイド

はじめに

サンドボックス化されていない C/C++ ライブラリを使用する場合、リンカーはコンパイル後に必要なすべての関数が利用可能であることを保証するため、実行時に API 呼び出しが失敗する可能性を心配する必要はありません。

ただし、サンドボックス化されたライブラリを使用する場合、ライブラリの実行は別のプロセスで行われます。API 呼び出しが失敗した場合は、RPC レイヤでの呼び出しの受け渡しに関連するあらゆる種類の問題を確認する必要があります。RPC レイヤのエラーが重要でない場合があります。たとえば、一括処理を行っているときにサンドボックスが再起動された場合などです。

ただし、上記の理由から、サンドボックス化された API 呼び出しの戻り値の定期的なエラー チェックを拡張して、RPC レイヤでエラーが返されたかどうかを確認することが重要です。そのため、すべてのライブラリ関数プロトタイプは T ではなく ::sapi::StatusOr<T> を返します。ライブラリ関数の呼び出しが失敗した場合(サンドボックス違反など)、戻り値には発生したエラーの詳細が含まれます。

RPC レイヤのエラーを処理するということは、サンドボックス化されたライブラリへの各呼び出しの後に、SAPI の RPC レイヤの追加チェックが続くことを意味します。このような例外的な状況に対処するため、SAPI には SAPI トランザクション モジュール(transaction.h)が用意されています。このモジュールには ::sapi::Transaction クラスが含まれており、サンドボックス ライブラリへのすべての関数呼び出しが RPC レベルの問題なく完了したことを確認するか、関連するエラーを返します。

SAPI トランザクション

SAPI は、ホストコードをサンドボックス化されたライブラリから分離し、呼び出し元に問題のあるデータ処理リクエストを再開または中止する機能を提供します。SAPI トランザクションは、さらに一歩進んで、失敗したプロセスを自動的に繰り返します。

SAPI トランザクションは、::sapi::Transaction から直接継承するか、::sapi::BasicTransaction に渡された関数ポインタを使用するかの 2 つの方法で使用できます。

SAPI トランザクションは、次の 3 つの関数をオーバーライドすることで定義されます。

SAPI トランザクション メソッド
::sapi::Transaction::Init() これは、一般的な C/C++ ライブラリの初期化メソッドを呼び出すのと同様です。このメソッドは、トランザクションが再開されない限り、サンドボックス化されたライブラリへの各トランザクション中に 1 回だけ呼び出されます。再起動の場合、それまでの再起動の回数に関係なく、メソッドが再び呼び出されます。
::sapi::Transaction::Main() このメソッドは、::sapi::Transaction::Run() が呼び出されるたびに呼び出されます。
::sapi::Transaction::Finish() これは、一般的な C/C++ ライブラリのクリーンアップ メソッドを呼び出すのと同様です。このメソッドは、SAPI トランザクション オブジェクトの破棄時に 1 回だけ呼び出されます。

通常のライブラリの使用

サンドボックス化されたライブラリのないプロジェクトでライブラリを扱う場合の一般的なパターンは次のようになります。

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 メソッドの実行中にエラーを発生させた場合(上の表を参照)、トランザクションは再開されます。デフォルトの再起動回数は、transaction.hkDefaultRetryCnt で定義されています。

再起動をトリガーするエラーの例は次のとおりです。

  • サンドボックス違反が発生しました
  • サンドボックス化されたプロセスがクラッシュした
  • ライブラリ エラーのため、サンドボックス化された関数がエラーコードを返した

再起動手順では、通常の Init()Main() のフローが監視されます。::sapi::Transaction::Run() メソッドの呼び出しが繰り返されてエラーが返された場合、メソッド全体が呼び出し元にエラーを返します。

サンドボックスまたは RPC エラー処理

自動生成されたサンドボックス ライブラリ インターフェースは、元の C/C++ ライブラリ関数プロトタイプにできるだけ近いものになるように試みます。ただし、サンドボックス化されたライブラリは、サンドボックスまたは RPC エラーを通知できる必要があります。

これは、サンドボックス化された関数の戻り値を直接返すのではなく、::sapi::StatusOr<T> 戻り値の型(void を返す関数の場合は ::sapi::Status)を利用することで実現されます。

SAPI は、SAPI ステータス オブジェクトをチェックして反応するための便利なマクロも提供します。これらのマクロは、status_macro.h ヘッダー ファイルで定義されています。

次のコード スニペットは、合計の例からの抜粋で、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() を使用してサンドボックス化されたライブラリ プロセスの再起動を開始できます。

再起動すると、サンドボックス化されたライブラリ プロセスへの参照が無効になります。つまり、渡されたファイル記述子や割り当てられたメモリは存在しなくなります。