Руководство по транзакциям

Введение

При использовании библиотеки 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 Transaction делает еще один шаг вперед и автоматически повторяет неудачные процессы.

Транзакции SAPI можно использовать двумя разными способами: либо напрямую наследовать от ::sapi::Transaction , либо использовать указатели функций, передаваемые в ::sapi::BasicTransaction .

Транзакции SAPI определяются путем переопределения следующих трех функций:

Методы транзакций SAPI
::sapi::Transaction::Init() Это похоже на вызов метода инициализации типичной библиотеки C/C++. Этот метод вызывается только один раз во время каждой транзакции в изолированной библиотеке, если транзакция не перезапускается. В случае перезапуска метод вызывается повторно, независимо от того, сколько перезапусков произошло до этого.
::sapi::Transaction::Main() Этот метод вызывается при каждом вызове ::sapi::Transaction::Run() .
::sapi::Transaction::Finish() Это похоже на вызов метода очистки типичной библиотеки C/C++. Метод вызывается только один раз во время уничтожения объекта SAPI Transaction.

Обычное использование библиотеки

В проекте без изолированных библиотек обычная схема работы с библиотеками выглядит примерно так:

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

Библиотека инициализируется, затем используются экспортированные функции библиотеки и, наконец, вызывается функция завершения/закрытия для очистки среды.

Использование изолированной библиотеки

В проекте с изолированными библиотеками код из раздела «Обычное использование библиотеки» преобразуется в следующий фрагмент кода при использовании транзакций с обратными вызовами:

// 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 в файлетранзакции.h .

Примеры возникающих ошибок, которые вызывают перезагрузку:

  • Произошло нарушение песочницы
  • Процесс в песочнице завершился сбоем
  • Функция в песочнице вернула код ошибки из-за ошибки библиотеки.

Процедура перезапуска наблюдает за обычным потоком Init() и Main() , и если повторные вызовы метода ::sapi::Transaction::Run() возвращают ошибки, то весь метод возвращает ошибку вызывающему объекту.

Песочница или обработка ошибок RPC

Автоматически создаваемый интерфейс изолированной библиотеки пытается быть как можно ближе к исходному прототипу функции библиотеки C/C++. Однако изолированная библиотека должна иметь возможность сигнализировать о любых ошибках песочницы или RPC.

Это достигается за счет использования возвращаемых типов ::sapi::StatusOr<T> (или ::sapi::Status для функций, возвращающих void ) вместо прямого возврата возвращаемого значения изолированных функций.

SAPI также предоставляет несколько удобных макросов для проверки и реагирования на объект SAPI Status. Эти макросы определены в заголовочном файле status_macro.h .

Следующий фрагмент кода представляет собой выдержку из примера суммы и демонстрирует использование статуса SAPI и макросов:

// 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.

Перезапуск сделает недействительной любую ссылку на процесс изолированной библиотеки. Это означает, что переданные файловые дескрипторы или выделенная память больше не будут существовать.

,

Введение

При использовании библиотеки 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 Transaction делает еще один шаг вперед и автоматически повторяет неудачные процессы.

Транзакции SAPI можно использовать двумя разными способами: либо напрямую наследовать от ::sapi::Transaction , либо использовать указатели функций, передаваемые в ::sapi::BasicTransaction .

Транзакции SAPI определяются путем переопределения следующих трех функций:

Методы транзакций SAPI
::sapi::Transaction::Init() Это похоже на вызов метода инициализации типичной библиотеки C/C++. Этот метод вызывается только один раз во время каждой транзакции в изолированной библиотеке, если транзакция не перезапускается. В случае перезапуска метод вызывается повторно, независимо от того, сколько перезапусков произошло до этого.
::sapi::Transaction::Main() Этот метод вызывается при каждом вызове ::sapi::Transaction::Run() .
::sapi::Transaction::Finish() Это похоже на вызов метода очистки типичной библиотеки C/C++. Метод вызывается только один раз во время уничтожения объекта SAPI Transaction.

Обычное использование библиотеки

В проекте без изолированных библиотек обычная схема работы с библиотеками выглядит примерно так:

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

Библиотека инициализируется, затем используются экспортированные функции библиотеки и, наконец, вызывается функция завершения/закрытия для очистки среды.

Использование изолированной библиотеки

В проекте с изолированными библиотеками код из раздела «Обычное использование библиотеки» преобразуется в следующий фрагмент кода при использовании транзакций с обратными вызовами:

// 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 в файлетранзакции.h .

Примеры возникающих ошибок, которые вызывают перезагрузку:

  • Произошло нарушение песочницы
  • Процесс в песочнице завершился сбоем
  • Функция в песочнице вернула код ошибки из-за ошибки библиотеки.

Процедура перезапуска наблюдает за обычным потоком Init() и Main() , и если повторные вызовы метода ::sapi::Transaction::Run() возвращают ошибки, то весь метод возвращает ошибку вызывающему объекту.

Песочница или обработка ошибок RPC

Автоматически создаваемый интерфейс изолированной библиотеки пытается быть как можно ближе к исходному прототипу функции библиотеки C/C++. Однако изолированная библиотека должна иметь возможность сигнализировать о любых ошибках песочницы или RPC.

Это достигается за счет использования возвращаемых типов ::sapi::StatusOr<T> (или ::sapi::Status для функций, возвращающих void ) вместо прямого возврата возвращаемого значения изолированных функций.

SAPI также предоставляет несколько удобных макросов для проверки и реагирования на объект SAPI Status. Эти макросы определены в заголовочном файле status_macro.h .

Следующий фрагмент кода представляет собой выдержку из примера суммы и демонстрирует использование статуса SAPI и макросов:

// 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.

Перезапуск сделает недействительной любую ссылку на процесс изолированной библиотеки. Это означает, что переданные файловые дескрипторы или выделенная память больше не будут существовать.