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.