5. Como se comunicar com o sandboxee

Por padrão, o executor pode se comunicar com o sandboxee usando descritores de arquivo. Isso pode ser tudo que você precisa, por exemplo, se quiser apenas compartilhar um arquivo com o sandboxee ou ler a saída padrão dele.

No entanto, você provavelmente vai precisar de uma lógica de comunicação mais complexa entre o executor e o sandboxee. A API comms (consulte o arquivo de cabeçalho comms.h) pode ser usada para enviar números inteiros, strings, buffers de byte, protobufs ou descritores de arquivos.

Como compartilhar descritores de arquivos

Com a API Inter-Process Communication (consulte ipc.h), é possível usar MapFd() ou ReceiveFd():

  • Use MapFd() para mapear descritores de arquivos do executor para o sandboxee. Isso pode ser usado para compartilhar um arquivo aberto pelo executor para uso no Sandboxee. Um exemplo de uso pode ser visto em estático.

    // The executor opened /proc/version and passes it to the sandboxee as stdin
    executor->ipc()->MapFd(proc_version_fd, STDIN_FILENO);
    
  • Use ReceiveFd() para criar um endpoint de socketpair. Pode ser usado para ler a saída padrão ou os erros padrão do sandboxee. Veja um exemplo de uso na ferramenta.

    // The executor receives a file descriptor of the sandboxee stdout
    int recv_fd1 = executor->ipc())->ReceiveFd(STDOUT_FILENO);
    

Como usar a API comms

O Sandbox2 oferece uma API de comunicações conveniente. Essa é uma maneira simples e fácil de compartilhar números inteiros, strings ou buffers de byte entre o executor e o sandboxee. Confira abaixo alguns snippets de código que podem ser encontrados no exemplo do crc4.

Para começar a usar a API comms, primeiro você precisa obter o objeto comms do objeto Sandbox2:

sandbox2::Comms* comms = s2.comms();

Quando o objeto de comunicação estiver disponível, os dados poderão ser enviados para o Sandboxee usando uma da família de funções Enviar*. Confira um exemplo de uso da API comms no crc4. O snippet de código abaixo mostra um trecho desse exemplo. O executor envia um unsigned char buf[size] com SendBytes(buf, size):

if (!(comms->SendBytes(static_cast<const uint8_t*>(buf), sz))) {
  /* handle error */
}

Para receber dados do sandboxee, use uma das funções Recv*. O snippet de código abaixo é um trecho do exemplo do crc4. O executor recebe a soma de verificação em um número inteiro não assinado de 32 bits: uint32_t crc4;

if (!(comms->RecvUint32(&crc4))) {
  /* handle error */
}

Compartilhamento de dados com buffers

Outra funcionalidade de compartilhamento de dados é usar a API de buffer para compartilhar grandes quantidades de dados e evitar cópias caras entre o executor e o sandboxee.

O executor cria um buffer, por tamanho e dados a serem transmitidos, ou diretamente de um descritor de arquivo, e o transmite para o sandboxee usando comms->SendFD() no executor e comms->RecvFD() no sandboxee.

No snippet de código abaixo, é possível observar o lado do executor. O sandbox é executado de maneira assíncrona e compartilha dados por meio de um buffer com o sandboxee:

// start the sandbox asynchronously
s2.RunAsync();

// instantiate the comms object
sandbox2::Comms* comms = s2.comms();

// random buffer data we want to send
constexpr unsigned char buffer_data[] = /* random data */;
constexpr unsigned int buffer_dataLen = 34;

// create sandbox2 buffer
absl::StatusOr<std::unique_ptr<sandbox2::Buffer>> buffer =
     sandbox2::Buffer::CreateWithSize(1ULL << 20 /* 1Mib */);
std::unique_ptr<sandbox2::Buffer> buffer_ptr = std::move(buffer).value();

// point to the sandbox2 buffer and fill with data
uint8_t* buf = buffer_ptr‑>data();
memcpy(buf, buffer_data, buffer_data_len);

// send the data to the sandboxee
comms‑>SendFd(buffer_ptr‑>fd());

No lado do sandboxee, você também precisa criar um objeto de buffer e ler os dados do descritor de arquivo enviado pelo executor:

// establish the communication with the executor
int fd;
comms.RecvFD(&fd);

// create the buffer
absl::StatusOr<std::unique_ptr<sandbox2::Buffer>> buffer =
     sandbox2::Buffer::createFromFd(fd);

// get the data
auto buffer_ptr = std::move(buffer).value();
uint8_t* buf = buffer_ptr‑>data();

/* work with the buf object */