Leitfaden zu Transaktionen

Einführung

Wenn Sie eine nicht in einer Sandbox ausgeführte C/C++-Bibliothek verwenden, sorgt der Linker dafür, dass alle erforderlichen Funktionen nach der Kompilierung verfügbar sind. Sie müssen sich also keine Sorgen machen, dass ein API-Aufruf zur Laufzeit fehlschlägt.

Bei Verwendung einer Sandboxed Library wird die Ausführung der Bibliothek jedoch in einem separaten Prozess ausgeführt. Bei einem Fehler in einem API-Aufruf müssen alle Arten von Problemen im Zusammenhang mit der Übergabe des Aufrufs über die RPC-Ebene geprüft werden. Manchmal sind die Fehler der RPC-Ebene nicht relevant, z. B. bei der Bulk-Verarbeitung und wenn die Sandbox gerade neu gestartet wurde.

Aus den oben genannten Gründen ist es jedoch wichtig, die regelmäßige Fehlerprüfung des Rückgabewerts des Sandboxed API-Aufrufs so zu erweitern, dass geprüft wird, ob auf der RPC-Ebene ein Fehler zurückgegeben wurde. Aus diesem Grund geben alle Prototypen von Bibliotheksfunktionen ::sapi::StatusOr<T> anstelle von T zurück. Falls der Aufruf der Bibliotheksfunktion fehlschlägt (z.B. aufgrund eines Sandbox-Verstoßes), enthält der Rückgabewert Details zum aufgetretenen Fehler.

Die Verarbeitung der Fehler der RPC-Ebene würde bedeuten, dass jedem Aufruf einer Sandboxed Library eine zusätzliche Prüfung der RPC-Ebene von SAPI folgt. Um diese Ausnahmesituationen zu bewältigen, bietet SAPI das SAPI-Transaktionsmodul (transaction.h). Dieses Modul enthält die Klasse ::sapi::Transaction und sorgt dafür, dass alle Funktionsaufrufe an die Sandboxed Library ohne RPC-Probleme abgeschlossen werden oder ein relevanter Fehler zurückgegeben wird.

SAPI-Transaktion

SAPI isoliert den Hostcode von der Sandbox-Bibliothek und gibt dem Aufrufer die Möglichkeit, die problematische Datenverarbeitungsanfrage neu zu starten oder abzubrechen. SAPI-Transaktionen gehen noch einen Schritt weiter und wiederholen fehlgeschlagene Prozesse automatisch.

SAPI-Transaktionen können auf zwei verschiedene Arten verwendet werden: entweder direkt durch Vererbung von ::sapi::Transaction oder durch Verwendung von Funktionszeigern, die an ::sapi::BasicTransaction übergeben werden.

SAPI-Transaktionen werden durch Überschreiben der folgenden drei Funktionen definiert:

SAPI-Transaktionsmethoden
::sapi::Transaction::Init() Dies ähnelt dem Aufrufen einer Initialisierungsmethode einer typischen C/C++-Bibliothek. Die Methode wird während jeder Transaktion in der Sandboxed Library nur einmal aufgerufen, es sei denn, die Transaktion wird neu gestartet. Im Falle eines Neustarts wird die Methode noch einmal aufgerufen, unabhängig davon, wie viele Neustarts zuvor stattgefunden haben.
::sapi::Transaction::Main() Die Methode wird für jeden Aufruf von ::sapi::Transaction::Run() aufgerufen.
::sapi::Transaction::Finish() Dies ähnelt dem Aufrufen einer Bereinigungsmethode einer typischen C/C++-Bibliothek. Die Methode wird nur einmal während der Zerstörung des SAPI-Transaktionsobjekts aufgerufen.

Normale Nutzung der Bibliothek

In einem Projekt ohne Sandbox-Bibliotheken sieht das übliche Muster für den Umgang mit Bibliotheken so aus:

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

Die Bibliothek wird initialisiert, dann werden exportierte Funktionen der Bibliothek verwendet und schließlich wird eine End-/Schließfunktion aufgerufen, um die Umgebung zu bereinigen.

Verwendung von Bibliotheken in der Sandbox

In einem Projekt mit Sandbox-Bibliotheken wird der Code aus Normal Library Use (Verwendung normaler Bibliotheken) in das folgende Code-Snippet übersetzt, wenn Transaktionen mit Callbacks verwendet werden:

// 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);
    // ...
  }
  // ...
}

Die Transaktionsklasse sorgt dafür, dass die Bibliothek neu initialisiert wird, falls während des handle_data-Aufrufs ein Fehler aufgetreten ist. Weitere Informationen dazu finden Sie im nächsten Abschnitt.

Neustarts von Transaktionen

Wenn bei einem API-Aufruf einer Sandbox-Bibliothek während der Ausführung der SAPI-Transaktionsmethoden (siehe Tabelle oben) ein Fehler auftritt, wird die Transaktion neu gestartet. Die Standardanzahl der Neustarts wird durch kDefaultRetryCnt in transaction.h definiert.

Beispiele für Fehler, die einen Neustart auslösen:

  • Es ist ein Sandbox-Verstoß aufgetreten
  • Der Sandbox-Prozess ist abgestürzt
  • Eine Sandbox-Funktion hat aufgrund eines Bibliotheksfehlers einen Fehlercode zurückgegeben.

Beim Neustart wird der normale Init()- und Main()-Ablauf eingehalten. Wenn bei wiederholten Aufrufen der ::sapi::Transaction::Run()-Methode Fehler zurückgegeben werden, gibt die gesamte Methode einen Fehler an den Aufrufer zurück.

Sandbox- oder RPC-Fehlerbehandlung

Die automatisch generierte Sandboxed Library-Schnittstelle versucht, so nah wie möglich am ursprünglichen C/C++-Bibliotheksfunktionsprototyp zu sein. Die Sandboxed Library muss jedoch alle Sandbox- oder RPC-Fehler signalisieren können.

Dies wird erreicht, indem ::sapi::StatusOr<T>-Rückgabetypen (oder ::sapi::Status für Funktionen, die void zurückgeben) verwendet werden, anstatt den Rückgabewert der Sandbox-Funktionen direkt zurückzugeben.

SAPI bietet außerdem einige praktische Makros, mit denen Sie ein SAPI-Statusobjekt prüfen und darauf reagieren können. Diese Makros sind in der Headerdatei status_macro.h definiert.

Das folgende Code-Snippet ist ein Auszug aus dem Summenbeispiel und veranschaulicht die Verwendung von SAPI-Status und den Makros:

// 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();
}

Sandbox-Neustarts

Viele Sandboxed-Bibliotheken verarbeiten vertrauliche Nutzereingaben. Wenn die Sandbox-Bibliothek irgendwann beschädigt wird und Daten zwischen Ausführungen speichert, sind diese sensiblen Daten gefährdet. Das kann beispielsweise passieren, wenn eine Sandbox-Version der Imagemagick-Bibliothek Bilder des vorherigen Laufs sendet.

Um ein solches Szenario zu vermeiden, sollte die Sandbox nicht für mehrere Läufe wiederverwendet werden. Um die Wiederverwendung von Sandboxes zu verhindern, kann der Hostcode einen Neustart des Sandbox-Bibliotheksprozesses mit ::sapi::Sandbox::Restart() oder ::sapi::Transaction::Restart() initiieren, wenn SAPI-Transaktionen verwendet werden.

Durch einen Neustart werden alle Verweise auf den Prozess der Sandbox-Bibliothek ungültig. Das bedeutet, dass übergebene Dateideskriptoren oder zugewiesener Speicher nicht mehr vorhanden sind.