Guide sur les transactions

Introduction

Lorsque vous utilisez une bibliothèque C/C++ hors bac à sable, l'éditeur de liens s'assure que toutes les fonctions nécessaires sont disponibles après la compilation. Vous n'avez donc pas à vous soucier de l'échec d'un appel d'API au moment de l'exécution.

Cependant, lorsque vous utilisez une bibliothèque en bac à sable, son exécution se fait dans un processus distinct. L'échec d'un appel d'API nécessite la vérification de toutes sortes de problèmes liés au transfert de l'appel sur la couche RPC. Parfois, les erreurs de couche RPC ne sont pas intéressantes, par exemple lors du traitement groupé et du redémarrage du bac à sable.

Néanmoins, pour les raisons mentionnées ci-dessus, il est important d'étendre la vérification régulière des erreurs de la valeur renvoyée par l'appel d'API en bac à sable afin d'inclure la vérification si une erreur a été renvoyée sur la couche RPC. C'est pourquoi tous les prototypes de fonctions de bibliothèque renvoient ::sapi::StatusOr<T> au lieu de T. En cas d'échec de l'appel de la fonction de bibliothèque (en raison d'un cas de non-respect du bac à sable, par exemple), la valeur renvoyée contient des informations sur l'erreur qui s'est produite.

Le traitement des erreurs de couche RPC signifie que chaque appel vers une bibliothèque en bac à sable est suivi d'une vérification supplémentaire de la couche RPC de SAPI. Afin de gérer ces situations exceptionnelles, SAPI propose le module SAPI Transaction (transaction.h). Ce module contient la classe ::sapi::Transaction et garantit que tous les appels de fonction à la bibliothèque en bac à sable ont été effectués sans problème au niveau du RPC ou qu'ils renvoient une erreur pertinente.

Transaction SAPI

SAPI isole le code hôte de la bibliothèque en bac à sable et donne à l'appelant la possibilité de redémarrer ou d'annuler la requête de traitement de données qui pose problème. La transaction SAPI va encore plus loin et répète automatiquement les processus ayant échoué.

Les transactions SAPI peuvent être utilisées de deux manières différentes: soit en héritant directement de ::sapi::Transaction, soit en utilisant des pointeurs de fonction transmis à ::sapi::BasicTransaction.

Les transactions SAPI sont définies en remplaçant les trois fonctions suivantes:

Méthodes de transaction SAPI
::sapi::Transaction::Init() Cette opération s'apparente à l'appel d'une méthode d'initialisation d'une bibliothèque C/C++ classique. La méthode n'est appelée qu'une seule fois lors de chaque transaction vers la bibliothèque en bac à sable, sauf si la transaction est redémarrée. En cas de redémarrage, la méthode est rappelée, quel que soit le nombre de redémarrages précédents.
::sapi::Transaction::Main() La méthode est appelée à chaque appel de ::sapi::Transaction::Run() .
::sapi::Transaction::Finish() Cette opération s'apparente à l'appel d'une méthode de nettoyage d'une bibliothèque C/C++ classique. La méthode n'est appelée qu'une seule fois lors de la destruction de l'objet Transaction SAPI.

Utilisation normale de la bibliothèque

Dans un projet sans bibliothèques en bac à sable, le modèle habituel pour traiter les bibliothèques se présente comme suit:

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

La bibliothèque est initialisée, ses fonctions exportées sont utilisées et, enfin, une fonction de fin/fermeture est appelée pour nettoyer l'environnement.

Utilisation de la bibliothèque en bac à sable

Dans un projet comportant des bibliothèques en bac à sable, le code de l'utilisation normale des bibliothèques se traduit par l'extrait de code suivant en cas de transactions avec des rappels:

// 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 de transaction veille à réinitialiser la bibliothèque en cas d'erreur lors de l'appel de handle_data. Vous trouverez plus d'informations à ce sujet dans la section suivante.

Redémarrages des transactions

Si un appel d'API de bibliothèque en bac à sable génère une erreur lors de l'exécution des méthodes de transaction SAPI (voir tableau ci-dessus), la transaction est redémarrée. Le nombre de redémarrages par défaut est défini par kDefaultRetryCnt dans transaction.h.

Voici quelques exemples d'erreurs qui déclenchent un redémarrage:

  • Une violation du bac à sable s'est produite
  • Le processus en bac à sable a planté
  • Une fonction en bac à sable a renvoyé un code d'erreur en raison d'une erreur de bibliothèque

La procédure de redémarrage observe le flux Init() et Main() normal. Si des appels répétés à la méthode ::sapi::Transaction::Run() renvoient des erreurs, la méthode entière renvoie une erreur à son appelant

Gestion des erreurs de bac à sable ou RPC

L'interface de la bibliothèque en bac à sable générée automatiquement tente de se rapprocher le plus possible du prototype de fonction de la bibliothèque C/C++ d'origine. Cependant, la bibliothèque en bac à sable doit pouvoir signaler toute erreur de bac à sable ou de RPC.

Pour ce faire, utilisez des types renvoyés ::sapi::StatusOr<T> (ou ::sapi::Status pour les fonctions renvoyant void), au lieu de renvoyer directement la valeur renvoyée par les fonctions en bac à sable.

SAPI fournit également des macros pratiques pour vérifier un objet SAPI Status et y réagir. Ces macros sont définies dans le fichier d'en-tête status_macro.h.

L'extrait de code suivant est tiré de l'exemple de somme. Il illustre l'utilisation de l'état SAPI et des 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();
}

Redémarrages du bac à sable

De nombreuses bibliothèques en bac à sable gèrent les entrées utilisateur sensibles. Si la bibliothèque en bac à sable est corrompue et stocke des données entre les exécutions, ces données sensibles sont menacées. Par exemple, si une version en bac à sable de la bibliothèque Imagemagick commence à envoyer des photos de l'exécution précédente.

Pour éviter un tel scénario, le bac à sable ne doit pas être réutilisé pour plusieurs exécutions. Pour arrêter la réutilisation des bacs à sable, le code hôte peut lancer un redémarrage du processus de bibliothèque en bac à sable à l'aide de ::sapi::Sandbox::Restart() ou de ::sapi::Transaction::Restart() lors de l'utilisation de transactions SAPI.

Un redémarrage invalidera toute référence au processus de bibliothèque en bac à sable. Cela signifie que les descripteurs de fichier transmis ou la mémoire allouée n'existent plus.