Wstęp
W przypadku biblioteki C/C++ poza piaskownicą linker zapewnia, że wszystkie niezbędne funkcje są dostępne po kompilacji, dzięki czemu nie trzeba się martwić, czy wywołanie interfejsu API może zakończyć się niepowodzeniem w czasie działania.
Jeśli jednak używasz biblioteki w trybie piaskownicy, jej uruchomienie odbywa się w osobnym procesie. Niepowodzenie wywołania interfejsu API wymaga sprawdzenia wszelkich problemów związanych z przekazywaniem wywołania przez warstwę RPC. Czasami błędy warstwy RPC mogą nie być interesujące, np. podczas przetwarzania zbiorczego, gdy piaskownica właśnie została ponownie uruchomiona.
Z powodów wymienionych powyżej warto jednak rozszerzyć regularne sprawdzanie wartości zwracanego wywołania interfejsu API w trybie piaskownicy, tak aby obejmowało sprawdzenie, czy w warstwie RPC wystąpił błąd. Dlatego wszystkie prototypy funkcji biblioteki zwracają wartość ::sapi::StatusOr<T>
zamiast T. Jeśli nie uda się wywołać funkcji biblioteki (np. z powodu naruszenia zasad piaskownicy), zwracana wartość będzie zawierać szczegóły błędu.
Obsługa błędów warstwy RPC oznaczałaby, że po każdym wywołaniu biblioteki w trybie piaskownicy następuje dodatkowa kontrola warstwy RPC interfejsu SAPI. Na potrzeby takich wyjątkowych sytuacji interfejs SAPI udostępnia moduł transakcji SAPI (transaction.h). Ten moduł zawiera klasę ::sapi::Transaction
i pilnuje, by wszystkie wywołania funkcji biblioteki piaskownicy w trybie piaskownicy zostały ukończone bez problemów na poziomie RPC lub zwracały odpowiedni błąd.
Transakcja SAPI
Interfejs SAPI izoluje kod hosta od biblioteki w trybie piaskownicy i daje elementowi wywołującemu możliwość ponownego uruchomienia lub przerwania problematycznego żądania przetwarzania danych. Transakcja SAPI idzie o krok dalej i automatycznie powtarza nieudane procesy.
Transakcjach SAPI może być używana na 2 sposoby: przez bezpośrednie dziedziczenie z elementu ::sapi::Transaction
lub za pomocą wskaźników funkcji przekazanych do ::sapi::BasicTransaction
.
Transakcje SAPI definiuje się przez zastąpienie tych 3 funkcji:
Metody transakcji SAPI | |
---|---|
::sapi::Transaction::Init() |
Przypomina to wywoływanie metody inicjowania w typowej bibliotece C/C++. Metoda jest wywoływana tylko raz podczas każdej transakcji w Bibliotece w trybie piaskownicy, chyba że transakcja zostanie ponownie uruchomiona. W przypadku ponownego uruchomienia metoda jest wywoływana ponownie niezależnie od tego, ile ponownych uruchomień miało miejsce wcześniej. |
::sapi::Transaction::Main() |
Metoda jest wywoływana przy każdym wywołaniu funkcji ::sapi::Transaction::Run() . |
::sapi::Transaction::Finish() |
Przypomina to wywołanie metody czyszczenia w typowej bibliotece C/C++. Podczas niszczenia obiektu transakcji SAPI metoda jest wywoływana tylko raz. |
Zwykłe korzystanie z biblioteki
W projekcie bez bibliotek w trybie piaskownicy zwykły wzorzec obsługi bibliotek wygląda mniej więcej tak:
LibInit();
while (data = NextDataToProcess()) {
result += LibProcessData(data);
}
LibClose();
Biblioteka jest inicjowana, następnie używane są wyeksportowane funkcje biblioteki, a na koniec wywoływana jest funkcja end/close w celu oczyszczenia środowiska.
Korzystanie z biblioteki w trybie piaskownicy
Podczas korzystania z transakcji z wywołaniami zwrotnymi w projekcie zawierającym biblioteki w piaskownicy kod z normalnego użycia biblioteki jest zamieniany na ten fragment kodu:
// 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 dba o ponowne zainicjowanie biblioteki, jeśli podczas wywołania handle_data
wystąpił błąd. Więcej informacji na ten temat znajdziesz w następnej sekcji.
Ponowne uruchomienie transakcji
Jeśli wywołanie interfejsu API biblioteki w trybie piaskownicy zwróci błąd podczas wykonywania metod transakcji SAPI (patrz tabela powyżej), transakcja zostanie uruchomiona ponownie. Domyślna liczba ponownych uruchomień jest określana przez kDefaultRetryCnt
w tagu transaction.h.
Przykłady zgłoszonych błędów, które spowodują ponowne uruchomienie:
- Wystąpiło naruszenie zasad piaskownicy
- Proces w trybie piaskownicy uległ awarii
- Funkcja w trybie piaskownicy zwróciła kod błędu z powodu błędu biblioteki
Procedura ponownego uruchamiania obserwuje normalny przepływ Init()
i Main()
. Jeśli wielokrotne wywołania metody ::sapi::Transaction::Run()
zwracają błędy, cała metoda zwraca błąd do elementu wywołującego.
Obsługa błędów piaskownicy lub RPC
Automatycznie generowany interfejs biblioteki w trybie piaskownicy jest jak najbardziej zbliżony do pierwotnego prototypu funkcji biblioteki C/C++. Biblioteka w trybie piaskownicy musi jednak mieć możliwość sygnalizowania błędów piaskownicy lub RPC.
Można to osiągnąć przez stosowanie zwracanych typów ::sapi::StatusOr<T>
(lub ::sapi::Status
w przypadku funkcji zwracających void
), zamiast bezpośredniego zwracania wartości zwracanej przez funkcje piaskownicy.
Interfejs SAPI udostępnia też przydatne makra, które umożliwiają sprawdzanie obiektu SAPI Status i reagowanie na nie. Te makra są zdefiniowane w pliku nagłówka status_macro.h.
Ten fragment kodu stanowi fragment przykładu z sumą i przedstawia użycie makr 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 w trybie piaskownicy obsługuje poufne dane wejściowe użytkownika. Jeśli biblioteka w trybie piaskownicy zostanie w pewnym momencie uszkodzona i będzie przechowywać dane pomiędzy uruchomieniami, te dane wrażliwe są zagrożone. Dzieje się tak na przykład wtedy, gdy z biblioteki Imagemagick w piaskownicy zacznie wysyłać zdjęcia z poprzedniego uruchomienia.
Aby uniknąć takiej sytuacji, nie należy używać piaskownicy do wielu uruchomień. Aby zatrzymać ponowne używanie piaskownicy, kod hosta może zainicjować ponowne uruchomienie procesu biblioteki w piaskownicy za pomocą polecenia ::sapi::Sandbox::Restart()
lub ::sapi::Transaction::Restart()
podczas korzystania z transakcji SAPI.
Ponowne uruchomienie unieważni każde odwołanie do procesu biblioteki w trybie piaskownicy. Oznacza to, że przekazane deskryptory plików lub przydzielona pamięć przestaną istnieć.