Введение
При использовании неизолированной библиотеки C/C++ компоновщик обеспечивает доступность всех необходимых функций после компиляции, и, таким образом, нет необходимости беспокоиться о том, что вызов API может завершиться ошибкой во время выполнения.
Однако при использовании изолированной библиотеки выполнение библиотеки происходит в отдельном процессе. Сбой в вызове API требует проверки всех видов проблем, связанных с передачей вызова через уровень RPC. Иногда ошибки уровня RPC могут не представлять интереса, например, при выполнении массовой обработки и перезапуске песочницы.
Тем не менее, по указанным выше причинам важно расширить регулярную проверку возвращаемого значения вызова API в изолированной среде, включив в неё проверку на предмет возврата ошибки на уровне RPC. Именно поэтому все прототипы библиотечных функций возвращают ::sapi::StatusOr<T>
вместо T. В случае сбоя вызова библиотечной функции (например, из-за нарушения песочницы) возвращаемое значение будет содержать информацию о произошедшей ошибке.
Обработка ошибок уровня RPC подразумевает, что каждый вызов изолированной библиотеки сопровождается дополнительной проверкой уровня RPC SAPI. Для обработки таких исключительных ситуаций SAPI предоставляет модуль SAPI Transaction ( transaction.h ). Этот модуль содержит класс ::sapi::Transaction
и гарантирует, что все вызовы функций изолированной библиотеки были выполнены без проблем на уровне RPC или возвращают соответствующую ошибку.
SAPI-транзакция
SAPI изолирует код хоста от изолированной библиотеки и предоставляет вызывающему объекту возможность перезапустить или прервать проблемный запрос на обработку данных. SAPI-транзакция идёт ещё дальше и автоматически повторяет неудачные процессы.
Транзакции SAPI можно использовать двумя различными способами: либо напрямую наследуя от ::sapi::Transaction
, либо используя указатели функций, переданные в ::sapi::BasicTransaction
.
Транзакции SAPI определяются путем переопределения следующих трех функций:
Методы транзакций SAPI | |
---|---|
::sapi::Transaction::Init() | Это похоже на вызов метода инициализации типичной библиотеки C/C++. Метод вызывается только один раз во время каждой транзакции в изолированной библиотеке, если только транзакция не перезапускается. В случае перезапуска метод вызывается снова, независимо от того, сколько перезапусков было выполнено ранее. |
::sapi::Transaction::Main() | Метод вызывается при каждом вызове ::sapi::Transaction::Run() . |
::sapi::Transaction::Finish() | Это похоже на вызов метода очистки типичной библиотеки C/C++. Метод вызывается только один раз при уничтожении объекта SAPI-транзакции. |
Обычное использование библиотеки
В проекте без изолированных библиотек обычная схема работы с библиотеками выглядит примерно так:
LibInit();
while (data = NextDataToProcess()) {
result += LibProcessData(data);
}
LibClose();
Библиотека инициализируется, затем используются экспортированные функции библиотеки и, наконец, вызывается функция завершения/закрытия для очистки среды.
Использование изолированной библиотеки
В проекте с изолированными библиотеками код из Normal Library Use транслируется в следующий фрагмент кода при использовании транзакций с обратными вызовами:
// 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);
// ...
}
// ...
}
Класс транзакции обеспечивает повторную инициализацию библиотеки в случае возникновения ошибки во время вызова handle_data
— подробнее об этом в следующем разделе.
Перезапуск транзакций
Если вызов API изолированной библиотеки вызовет ошибку во время выполнения методов транзакции SAPI (см. таблицу выше), транзакция будет перезапущена. Количество перезапусков по умолчанию определяется параметром kDefaultRetryCnt
в файле transaction.h .
Примеры ошибок, которые приведут к перезапуску:
- Произошло нарушение правил песочницы
- Процесс в песочнице дал сбой
- Функция, изолированная в песочнице, вернула код ошибки из-за ошибки библиотеки.
Процедура перезапуска отслеживает обычный поток Init()
и Main()
, и если повторные вызовы метода ::sapi::Transaction::Run()
возвращают ошибки, то весь метод возвращает ошибку вызывающему объекту.
Обработка ошибок в песочнице или RPC
Автоматически сгенерированный интерфейс библиотеки Sandboxed Library стремится быть максимально приближенным к прототипу функции исходной библиотеки C/C++. Однако библиотека Sandboxed Library должна иметь возможность сообщать о любых ошибках песочницы или RPC.
Это достигается за счет использования возвращаемых типов ::sapi::StatusOr<T>
(или ::sapi::Status
для функций, возвращающих void
) вместо непосредственного возврата возвращаемого значения изолированных функций.
SAPI также предоставляет ряд удобных макросов для проверки объекта SAPI Status и реагирования на него. Эти макросы определены в заголовочном файле status_macro.h .
Следующий фрагмент кода представляет собой отрывок из примера суммы и демонстрирует использование SAPI Status и макросов:
// 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();
}
Перезапуски песочницы
Многие изолированные библиотеки обрабатывают конфиденциальные пользовательские данные. Если изолированная библиотека в какой-то момент повреждается и сохраняет данные между запусками, эти конфиденциальные данные подвергаются риску. Например, если изолированная версия библиотеки Imagemagick начинает отправлять изображения предыдущего запуска.
Чтобы избежать такого сценария, песочницу не следует использовать повторно для нескольких запусков. Чтобы предотвратить повторное использование песочниц, код хоста может инициировать перезапуск процесса библиотеки в песочнице с помощью ::sapi::Sandbox::Restart()
или ::sapi::Transaction::Restart()
при использовании SAPI-транзакций.
Перезапуск сделает недействительными все ссылки на процесс изолированной библиотеки. Это означает, что переданные файловые дескрипторы или выделенная память больше не будут существовать.