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()
i 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ć.