Guida alle transazioni

Introduzione

Quando utilizzi una libreria C/C++ non sottoposta a sandbox, il linker garantisce che tutte le funzioni necessarie siano disponibili dopo la compilazione e quindi non è necessario preoccuparsi che una chiamata API possa non riuscire in fase di runtime.

Quando si utilizza una libreria in sandbox, l'esecuzione della libreria avviene in un processo separato. Un errore in una chiamata API richiede il controllo di tutti i tipi di problemi relativi al passaggio della chiamata al livello RPC. A volte, gli errori del livello RPC potrebbero non essere interessanti, ad esempio quando si esegue l'elaborazione collettiva e la sandbox è appena stata riavviata.

Tuttavia, per i motivi sopra menzionati, è importante estendere il controllo regolare degli errori del valore restituito della chiamata API in sandbox per includere il controllo se è stato restituito un errore nel livello RPC. Per questo motivo, tutti i prototipi di funzioni della libreria restituiscono ::sapi::StatusOr<T> anziché T. Nel caso in cui la chiamata della funzione di libreria non vada a buon fine (ad es. a causa di una violazione della sandbox), il valore restituito conterrà i dettagli dell'errore verificatosi.

La gestione degli errori del livello RPC implica che ogni chiamata a una libreria sandbox è seguita da un ulteriore controllo del livello RPC di SAPI. Per gestire queste situazioni eccezionali, SAPI fornisce il modulo SAPI Transaction (transaction.h). Questo modulo contiene la classe ::sapi::Transaction e garantisce che tutte le chiamate di funzione alla libreria in sandbox siano state completate senza problemi a livello di RPC o restituiscano un errore pertinente.

SAPI Transaction

SAPI isola il codice host dalla libreria in sandbox e consente al chiamante di riavviare o interrompere la richiesta di elaborazione dei dati problematica. SAPI Transaction va oltre e ripete automaticamente i processi non riusciti.

Le transazioni SAPI possono essere utilizzate in due modi diversi: ereditando direttamente da ::sapi::Transaction o utilizzando puntatori di funzione passati a ::sapi::BasicTransaction.

Le transazioni SAPI vengono definite sostituendo le seguenti tre funzioni:

Metodi di transazione SAPI
::sapi::Transaction::Init() È simile alla chiamata di un metodo di inizializzazione di una tipica libreria C/C++. Il metodo viene chiamato una sola volta durante ogni transazione nella libreria in sandbox, a meno che la transazione non venga riavviata. In caso di riavvio, il metodo viene chiamato di nuovo, indipendentemente dal numero di riavvii precedenti.
::sapi::Transaction::Main() Il metodo viene chiamato per ogni chiamata a ::sapi::Transaction::Run() .
::sapi::Transaction::Finish() È simile alla chiamata di un metodo di pulizia di una tipica libreria C/C++. Il metodo viene chiamato una sola volta durante l'eliminazione dell'oggetto SAPI Transaction.

Utilizzo normale della biblioteca

In un progetto senza librerie in sandbox, il pattern abituale quando si utilizzano le librerie è simile al seguente:

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

La libreria viene inizializzata, quindi vengono utilizzate le funzioni esportate della libreria e infine viene chiamata una funzione di fine/chiusura per pulire l'ambiente.

Utilizzo della libreria in sandbox

In un progetto con librerie in sandbox, il codice di Normal Library Use viene tradotto nel seguente snippet di codice quando si utilizzano transazioni con callback:

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

La classe di transazione si assicura di reinizializzare la libreria nel caso in cui si sia verificato un errore durante l'invocazione di handle_data. Scopri di più nella sezione successiva.

Riavvii delle transazioni

Se una chiamata API della libreria in sandbox genera un errore durante l'esecuzione dei metodi di transazione SAPI (vedi la tabella sopra), la transazione verrà riavviata. Il numero predefinito di riavvii è definito da kDefaultRetryCnt in transaction.h.

Esempi di errori segnalati che attiveranno un riavvio:

  • Si è verificata una violazione della sandbox
  • Il processo in sandbox è stato interrotto
  • Una funzione in sandbox ha restituito un codice di errore a causa di un errore della libreria

La procedura di riavvio rispetta il normale flusso Init() e Main() e, se chiamate ripetute al metodo ::sapi::Transaction::Run() restituiscono errori, allora l'intero metodo restituisce un errore al chiamante

Gestione degli errori RPC o della sandbox

L'interfaccia della libreria isolata generata automaticamente tenta di avvicinarsi il più possibile al prototipo della funzione della libreria C/C++ originale. Tuttavia, la libreria in sandbox deve essere in grado di segnalare eventuali errori di sandbox o RPC.

Ciò si ottiene utilizzando i tipi restituiti ::sapi::StatusOr<T> (o ::sapi::Status per le funzioni che restituiscono void), anziché restituire direttamente il valore restituito delle funzioni in sandbox.

SAPI fornisce inoltre alcune comode macro per controllare e reagire a un oggetto SAPI Status. Queste macro sono definite nel file di intestazione status_macro.h.

Il seguente snippet di codice è un estratto dell'esempio sum e mostra l'utilizzo di SAPI Status e delle macro:

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

Riavvii della sandbox

Molte librerie in sandbox gestiscono input utente sensibili. Se la libreria in sandbox viene danneggiata a un certo punto e memorizza i dati tra le esecuzioni, questi dati sensibili sono a rischio. Ad esempio, se una versione in sandbox della libreria Imagemagick inizia a inviare immagini dell'esecuzione precedente.

Per evitare questo scenario, la sandbox non deve essere riutilizzata per più esecuzioni. Per interrompere il riutilizzo delle sandbox, il codice host può avviare il riavvio del processo della libreria in sandbox utilizzando ::sapi::Sandbox::Restart() o ::sapi::Transaction::Restart() quando si utilizzano le transazioni SAPI.

Un riavvio invaliderà qualsiasi riferimento al processo della libreria in sandbox. Ciò significa che i descrittori di file passati o la memoria allocata non esisteranno più.