Przewodnik po transakcjach

Wprowadzenie

W przypadku korzystania z biblioteki C/C++ bez piaskownicy linker dba o to, aby po kompilacji były dostępne wszystkie niezbędne funkcje, więc nie musisz się martwić, czy wywołanie interfejsu API może się nie powieść w czasie działania programu.

W przypadku biblioteki w piaskownicy wykonanie biblioteki odbywa się w osobnym procesie. W przypadku nieudanego wywołania interfejsu API należy sprawdzić wszystkie rodzaje problemów związanych z przekazywaniem wywołania przez warstwę RPC. Czasami błędy warstwy RPC mogą nie być interesujące, na przykład podczas przetwarzania zbiorczego, gdy piaskownica została właśnie ponownie uruchomiona.

Z powodów wymienionych powyżej ważne jest jednak, aby rozszerzyć regularne sprawdzanie błędów zwracanej wartości wywołania interfejsu API w piaskownicy o sprawdzanie, czy na warstwie RPC zwrócono błąd. Dlatego wszystkie prototypy funkcji bibliotecznych zwracają wartość ::sapi::StatusOr<T> zamiast T. Jeśli wywołanie funkcji biblioteki się nie powiedzie (np. z powodu naruszenia zasad piaskownicy), zwracana wartość będzie zawierać szczegóły błędu, który wystąpił.

Obsługa błędów warstwy RPC oznaczałaby, że po każdym wywołaniu biblioteki w piaskownicy następuje dodatkowe sprawdzenie warstwy RPC interfejsu SAPI. Aby poradzić sobie z takimi wyjątkowymi sytuacjami, SAPI udostępnia moduł SAPI Transaction (transaction.h). Ten moduł zawiera klasę ::sapi::Transaction i zapewnia, że wszystkie wywołania funkcji w bibliotece w piaskownicy zostały wykonane bez problemów na poziomie RPC lub zwracają odpowiedni błąd.

Transakcja SAPI

SAPI izoluje kod hosta od biblioteki w piaskownicy i umożliwia wywołującemu ponowne uruchomienie lub przerwanie problematycznego żądania przetwarzania danych. SAPI Transaction idzie o krok dalej i automatycznie powtarza procesy, które zakończyły się niepowodzeniem.

Transakcje SAPI mogą być używane na 2 sposoby: przez bezpośrednie dziedziczenie z ::sapi::Transaction lub przez używanie wskaźników funkcji przekazywanych do ::sapi::BasicTransaction.

Transakcje SAPI są definiowane przez zastąpienie tych 3 funkcji:

Metody transakcji SAPI
::sapi::Transaction::Init() Jest to podobne do wywołania metody inicjowania typowej biblioteki C/C++. Metoda jest wywoływana tylko raz podczas każdej transakcji w bibliotece w piaskownicy, chyba że transakcja zostanie ponownie uruchomiona. W przypadku ponownego uruchomienia metoda jest wywoływana ponownie, niezależnie od tego, ile razy wcześniej nastąpiło ponowne uruchomienie.
::sapi::Transaction::Main() Metoda jest wywoływana dla każdego wywołania ::sapi::Transaction::Run() .
::sapi::Transaction::Finish() Jest to podobne do wywołania metody czyszczenia typowej biblioteki C/C++. Metoda jest wywoływana tylko raz podczas niszczenia obiektu transakcji SAPI.

Normalne korzystanie z biblioteki

W projekcie bez bibliotek w piaskownicy typowy wzorzec postępowania z bibliotekami wygląda mniej więcej tak:

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

Biblioteka jest inicjowana, a następnie używane są jej wyeksportowane funkcje. Na koniec wywoływana jest funkcja zakończenia lub zamknięcia, aby wyczyścić środowisko.

Korzystanie z biblioteki w trybie piaskownicy

W projekcie z bibliotekami w piaskownicy kod z Normal Library Use jest tłumaczony na ten fragment kodu, gdy używasz transakcji z wywołaniami zwrotnymi:

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

Klasa transakcji zapewnia ponowne zainicjowanie biblioteki w przypadku wystąpienia błędu podczas wywołania handle_data – więcej informacji znajdziesz w następnej sekcji.

Ponowne uruchomienia transakcji

Jeśli wywołanie interfejsu API biblioteki w piaskownicy spowoduje błąd podczas wykonywania metod SAPI Transaction (patrz tabela powyżej), transakcja zostanie ponownie uruchomiona. Domyślna liczba ponownych uruchomień jest zdefiniowana przez wartość kDefaultRetryCnt w pliku transaction.h.

Przykłady zgłoszonych błędów, które spowodują ponowne uruchomienie:

  • Wystąpiło naruszenie zasad piaskownicy
  • Proces w piaskownicy uległ awarii
  • Funkcja w piaskownicy zwróciła kod błędu z powodu błędu biblioteki

Procedura ponownego uruchamiania jest zgodna z normalnym przepływem Init()Main(), a jeśli powtarzane wywołania metody ::sapi::Transaction::Run() zwracają błędy, cała metoda zwraca błąd do wywołującego ją kodu.

Obsługa błędów w piaskownicy lub RPC

Automatycznie generowany interfejs biblioteki w piaskownicy jest jak najbardziej zbliżony do prototypu funkcji oryginalnej biblioteki C/C++. Biblioteka w piaskownicy musi jednak mieć możliwość sygnalizowania błędów piaskownicy lub błędów RPC.

Jest to możliwe dzięki użyciu ::sapi::StatusOr<T> typów zwracanych (lub ::sapi::Status w przypadku funkcji zwracających void) zamiast bezpośredniego zwracania wartości zwracanej przez funkcje w piaskownicy.

SAPI udostępnia też wygodne makra do sprawdzania obiektu SAPI Status i reagowania na niego. Te makra są zdefiniowane w pliku nagłówkowym status_macro.h.

Poniższy fragment kodu pochodzi z przykładu sum i pokazuje, jak używać stanu SAPI i makr:

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

Ponowne uruchomienia piaskownicy

Wiele bibliotek działających w piaskownicy przetwarza dane wejściowe użytkownika o charakterze poufnym. Jeśli biblioteka w piaskownicy zostanie w pewnym momencie uszkodzona i będzie przechowywać dane między uruchomieniami, te dane wrażliwe będą zagrożone. Na przykład jeśli wersja biblioteki Imagemagick działająca w piaskownicy zacznie wysyłać zdjęcia z poprzedniego uruchomienia.

Aby uniknąć takiej sytuacji, nie należy ponownie używać piaskownicy do wielu uruchomień. Aby zapobiec ponownemu użyciu piaskownic, kod hosta może zainicjować ponowne uruchomienie procesu biblioteki w piaskownicy za pomocą funkcji ::sapi::Sandbox::Restart() lub ::sapi::Transaction::Restart() podczas korzystania z transakcji SAPI.

Ponowne uruchomienie spowoduje unieważnienie wszystkich odwołań do procesu biblioteki w piaskownicy. Oznacza to, że przekazane deskryptory plików lub przydzielona pamięć nie będą już istnieć.