Introduction
Lorsque vous utilisez une bibliothèque C/C++ non sandboxée, 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'éventualité d'un échec d'appel d'API au moment de l'exécution.
Toutefois, lorsque vous utilisez une bibliothèque sandboxée, l'exécution de la bibliothèque se fait dans un processus distinct. En cas d'échec d'un appel d'API, vous devez vérifier tous les types de problèmes liés à la transmission de l'appel sur la couche RPC. Parfois, les erreurs de couche RPC peuvent ne pas être intéressantes, par exemple lors du traitement par lot et lorsque le bac à sable vient d'être redémarré.
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 pour inclure la vérification de la présence d'une erreur renvoyée sur la couche RPC. C'est pourquoi tous les prototypes de fonction de bibliothèque renvoient ::sapi::StatusOr<T>
au lieu de T. Si l'appel de la fonction de bibliothèque échoue (par exemple, en raison d'une violation du bac à sable), la valeur renvoyée contient des informations sur l'erreur qui s'est produite.
La gestion des erreurs de la couche RPC signifie que chaque appel à une bibliothèque en bac à sable est suivi d'une vérification supplémentaire de la couche RPC de SAPI. Pour faire face à ces situations exceptionnelles, SAPI fournit le module de transaction SAPI (transaction.h).
Ce module contient la classe ::sapi::Transaction
et s'assure que tous les appels de fonction à la bibliothèque bac à sable ont été effectués sans aucun problème au niveau RPC ou renvoient une erreur pertinente.
Transaction SAPI
SAPI isole le code hôte de la bibliothèque en bac à sable et permet à l'appelant de redémarrer ou d'abandonner la requête de traitement de données problématique. SAPI Transaction 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() |
Cela revient à appeler 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 sandbox, sauf si la transaction est redémarrée. En cas de redémarrage, la méthode est appelée à nouveau, quel que soit le nombre de redémarrages précédents. |
::sapi::Transaction::Main() |
La méthode est appelée pour chaque appel à ::sapi::Transaction::Run() . |
::sapi::Transaction::Finish() |
Cela revient à appeler 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 de transaction SAPI. |
Utilisation normale de la bibliothèque
Dans un projet sans bibliothèques mises en bac à sable, le schéma habituel de gestion des bibliothèques ressemble à ceci :
LibInit();
while (data = NextDataToProcess()) {
result += LibProcessData(data);
}
LibClose();
La bibliothèque est initialisée, puis les fonctions exportées de la bibliothèque sont utilisées. Enfin, une fonction de fin/fermeture est appelée pour nettoyer l'environnement.
Utilisation de la bibliothèque dans un bac à sable
Dans un projet avec des bibliothèques mises en bac à sable, le code de Utilisation normale de la bibliothèque se traduit par l'extrait de code suivant lors de l'utilisation 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 s'assure de réinitialiser la bibliothèque en cas d'erreur lors de l'appel handle_data
. Nous y reviendrons dans la section suivante.
Redémarrages de 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 SAPI Transaction (voir le 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 des exemples d'erreurs déclenchant un redémarrage :
- Une violation du bac à sable s'est produite
- Le processus isolé dans un bac à sable a planté
- Une fonction sandboxée a renvoyé un code d'erreur en raison d'une erreur de bibliothèque.
La procédure de redémarrage respecte le flux normal Init()
et Main()
. 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 bibliothèque sandboxée générée automatiquement tente de se rapprocher le plus possible du prototype de fonction de la bibliothèque C/C++ d'origine. Toutefois, la bibliothèque sandboxée doit pouvoir signaler toute erreur de sandbox ou de RPC.
Pour ce faire, il utilise les types de retour ::sapi::StatusOr<T>
(ou ::sapi::Status
pour les fonctions renvoyant void
), au lieu de renvoyer directement la valeur de retour des fonctions en bac à sable.
SAPI fournit également des macros pratiques pour vérifier un objet d'état SAPI 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 un extrait de l'exemple de somme et montre 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 sandbox traitent des entrées utilisateur sensibles. Si la bibliothèque sandbox est corrompue à un moment donné et stocke des données entre les exécutions, ces données sensibles sont à risque. Par exemple, si une version sandboxée de la bibliothèque Imagemagick commence à envoyer des images de l'exécution précédente.
Pour éviter ce 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 la bibliothèque en bac à sable à l'aide de ::sapi::Sandbox::Restart()
ou ::sapi::Transaction::Restart()
lors de l'utilisation des transactions SAPI.
Un redémarrage invalidera toute référence au processus de la bibliothèque sandboxée. Cela signifie que les descripteurs de fichier transmis ou la mémoire allouée n'existeront plus.