Introdução
Ao usar uma biblioteca C/C++ sem sandbox, o vinculador garante que todas as funções necessárias estejam disponíveis após a compilação. Assim, não é preciso se preocupar se uma chamada de API pode falhar durante a execução.
No entanto, ao usar uma biblioteca em sandbox, a execução dela fica em um processo separado. Uma falha em uma chamada de API exige a verificação de todos os tipos de problemas relacionados à transmissão da chamada pela camada RPC. Às vezes, os erros da camada RPC podem não ser interessantes, por exemplo, ao fazer o processamento em lote e o sandbox acabou de ser reiniciado.
No entanto, pelos motivos mencionados acima, é importante estender a verificação regular de erros do valor de retorno da chamada de API em sandbox para incluir a verificação se um erro foi retornado na camada RPC. Por isso, todos os protótipos de função da biblioteca retornam ::sapi::StatusOr<T>
em vez de T. Se a invocação da função de biblioteca falhar (por exemplo, devido a uma violação da sandbox), o valor de retorno vai conter detalhes sobre o erro ocorrido.
O processamento dos erros da camada RPC significa que cada chamada para uma biblioteca em sandbox é seguida por uma verificação adicional da camada RPC da SAPI. Para lidar com essas situações excepcionais, a SAPI fornece o módulo de transação da SAPI (transaction.h).
Esse módulo contém a classe ::sapi::Transaction
e garante que todas as
chamadas de função para a biblioteca em sandbox foram concluídas sem problemas
no nível de RPC ou retornam um erro relevante.
Transação da SAPI
A SAPI isola o código do host da biblioteca em sandbox e permite que o autor da chamada reinicie ou aborte a solicitação de processamento de dados problemática. A transação SAPI vai um passo além e repete automaticamente os processos com falha.
As transações da SAPI podem ser usadas de duas maneiras diferentes: herdando diretamente de ::sapi::Transaction
ou usando ponteiros de função transmitidos para ::sapi::BasicTransaction
.
As transações da SAPI são definidas substituindo estas três funções:
Métodos de transação da SAPI | |
---|---|
::sapi::Transaction::Init() |
Isso é semelhante a chamar um método de inicialização de uma biblioteca C/C++ típica. O método é chamado apenas uma vez durante cada transação para a biblioteca em sandbox, a menos que a transação seja reiniciada. Em caso de reinicialização, o método é chamado novamente, não importa quantas reinicializações ocorreram antes. |
::sapi::Transaction::Main() |
O método é chamado para cada chamada de ::sapi::Transaction::Run() . |
::sapi::Transaction::Finish() |
Isso é semelhante a chamar um método de limpeza de uma biblioteca C/C++ típica. O método é chamado apenas uma vez durante a destruição do objeto de transação da SAPI. |
Uso normal da biblioteca
Em um projeto sem bibliotecas em sandbox, o padrão usual ao lidar com bibliotecas é semelhante a este:
LibInit();
while (data = NextDataToProcess()) {
result += LibProcessData(data);
}
LibClose();
A biblioteca é inicializada, as funções exportadas dela são usadas e, por fim, uma função de encerramento/fechamento é chamada para limpar o ambiente.
Uso de biblioteca em sandbox
Em um projeto com bibliotecas em sandbox, o código de Uso normal da biblioteca é traduzido para o seguinte snippet de código ao usar transações com callbacks:
// 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);
// ...
}
// ...
}
A classe de transação garante a reinicialização da biblioteca caso ocorra um erro
durante a invocação de handle_data
. Saiba mais sobre isso na próxima
seção.
Reinicializações de transações
Se uma chamada de API de biblioteca em sandbox gerar um erro durante a execução dos métodos de transação da SAPI (consulte a tabela acima), a transação será reiniciada. O número padrão de reinicializações é definido por kDefaultRetryCnt
em transaction.h.
Exemplos de erros gerados que vão acionar uma reinicialização:
- Ocorreu uma violação do sandbox
- O processo em sandbox falhou
- Uma função em sandbox retornou um código de erro devido a um erro de biblioteca
O procedimento de reinicialização segue o fluxo normal de Init()
e Main()
. Se
chamadas repetidas ao método ::sapi::Transaction::Run()
retornarem erros, então
o método inteiro vai retornar um erro para o chamador.
Tratamento de erros de sandbox ou RPC
A interface da biblioteca em sandbox gerada automaticamente tenta ser o mais próxima possível do protótipo da função da biblioteca C/C++ original. No entanto, a biblioteca em sandbox precisa ser capaz de sinalizar erros de sandbox ou RPC.
Isso é feito usando tipos de retorno ::sapi::StatusOr<T>
(ou
::sapi::Status
para funções que retornam void
), em vez de retornar o
valor de retorno das funções em sandbox diretamente.
A SAPI também oferece algumas macros convenientes para verificar e reagir a um objeto de status da SAPI. Essas macros são definidas no arquivo de cabeçalho status_macro.h.
O snippet de código a seguir é um trecho do exemplo sum e demonstra o uso do status da SAPI e das macros:
// 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();
}
Reinicializações do sandbox
Muitas bibliotecas em sandbox processam entradas sensíveis do usuário. Se a biblioteca em sandbox for corrompida em algum momento e armazenar dados entre execuções, esses dados sensíveis estarão em risco. Por exemplo, se uma versão em sandbox da biblioteca Imagemagick começar a enviar fotos da execução anterior.
Para evitar essa situação, a sandbox não pode ser reutilizada para várias execuções. Para
interromper a reutilização de sandboxes, o código do host pode iniciar uma reinicialização do
processo da biblioteca em sandbox usando ::sapi::Sandbox::Restart()
ou
::sapi::Transaction::Restart()
ao usar transações SAPI.
Uma reinicialização vai invalidar qualquer referência ao processo da biblioteca em sandbox. Isso significa que os descritores de arquivo transmitidos ou a memória alocada não vão mais existir.