1. Escolher um método de executor do sandbox

O sandbox começa com um executor (consulte Executor de sandbox), responsável por executar o Sandboxee. O arquivo de cabeçalho executor.h contém a API necessária para essa finalidade. A API é muito flexível e permite que você escolha o que funciona melhor para seu caso de uso. As seções a seguir descrevem os três métodos disponíveis.

Método 1: Independente – executa um binário com sandbox já ativado

Essa é a maneira mais simples de usar o sandbox e é o método recomendado quando você quer colocar um binário inteiro no sandbox para o qual não há código-fonte. Essa também é a maneira mais segura de usar o sandbox, já que não há uma inicialização fora do modo sandbox que possa ter efeitos adversos.

No snippet de código a seguir, definimos o caminho do binário que será colocado no sandbox e os argumentos que precisamos transmitir para uma chamada de sistema executiva. Como é possível observar no arquivo de cabeçalho executor.h, não especificamos um valor para envp e, portanto, copiamos o ambiente do processo pai. Lembre-se de que o primeiro argumento é sempre o nome do programa a ser executado. Nosso snippet não define nenhum outro argumento.

Os exemplos desse método de executor são: static e tool.

#include "sandboxed_api/sandbox2/executor.h"

std::string path = "path/to/binary";
std::vector<std::string> args = {path};  // args[0] will become the sandboxed
                                         // process' argv[0], typically the
                                         // path to the binary.
auto executor = absl::make_unique<sandbox2::Executor>(path, args);

Método 2: Sandbox2 Forkserver: informar ao executor quando colocar no sandbox

Esse método oferece a flexibilidade de não estar no sandbox durante a inicialização e, em seguida, escolher quando entrar no sandbox chamando ::sandbox2::Client::SandboxMeHere(). Ele exige que você possa definir no código quando quer iniciar o sandbox e precisa ter uma linha de execução única. Saiba o motivo nas Perguntas frequentes.

No snippet de código a seguir, usamos o mesmo código descrito no método 1 acima. No entanto, para permitir que o programa seja executado sem sandbox durante a inicialização, chamamos set_enable_sandbox_before_exec(false).

#include "sandboxed_api/sandbox2/executor.h"

std::string path = "path/to/binary";
std::vector<std::string> args = {path};
auto executor = absl::make_unique<sandbox2::Executor>(path, args);
executor->set_enable_sandbox_before_exec(false);

Como o executor agora tem um sandbox desativado até ser notificado pelo sandboxee, é necessário criar uma instância ::sandbox2::Client, configurar a comunicação entre o executor e o sandboxee e notificar o executor de que a inicialização foi concluída e que queremos iniciar o sandbox agora chamando sandbox2_client.SandboxMeHere().

// main() of sandboxee
int main(int argc, char** argv) {
  gflags::ParseCommandLineFlags(&argc, &argv, false);

  // Set-up the sandbox2::Client object, using a file descriptor (1023).
  sandbox2::Comms comms(sandbox2::Comms::kSandbox2ClientCommsFD);
  sandbox2::Client sandbox2_client(&comms);
  // Enable sandboxing from here.
  sandbox2_client.SandboxMeHere();
  …

Um exemplo desse método de executor é o crc4, em que crc4bin.cc é o sandboxee e notifica o executor (crc4sandbox.cc) quando ele precisa entrar no sandbox.

Método 3: Forkserver personalizado: prepare um binário, aguarde as solicitações de bifurcação e coloque o sandbox por conta própria

Esse modo permite iniciar um binário, prepará-lo para o sandbox e, em um momento específico do ciclo de vida do binário, disponibilizá-lo para o executor.

O executor vai enviar uma solicitação de bifurcação para o binário, que vai fork() (via ::sandbox2::ForkingClient::WaitAndFork()). O processo recém-criado vai estar pronto para ser colocado no sandbox com ::sandbox2::Client::SandboxMeHere().

#include "sandboxed_api/sandbox2/executor.h"

// Start the custom ForkServer
std::string path = "path/to/binary";
std::vector<std::string> args = {path};
auto fork_executor = absl::make_unique<sandbox2::Executor>(path, args);
fork_executor->StartForkServer();

// Initialize Executor with Comms channel to the ForkServer
auto executor = absl::make_unique<sandbox2::Executor>(
    fork_executor->ipc()->GetComms());

Lembre-se de que esse modo é bastante complicado e se aplica apenas em alguns casos específicos, por exemplo, quando você tem requisitos de memória limitados. Você se beneficiará com o COW, mas a desvantagem é que não há um ASLR real. Outro exemplo de uso típico seria quando o sandboxee tem uma inicialização longa e que usa muita CPU que pode ser executada antes de os dados não confiáveis serem processados.

Para conferir um exemplo desse método de executor, consulte custom_fork.