5. Kommunikation mit dem Sandboxee

Standardmäßig kann der Executor über Dateideskriptoren mit dem Sandboxee kommunizieren. Das kann alles sein, was Sie beispielsweise benötigen, wenn Sie nur eine Datei für den Sandboxee freigeben oder die Standardausgabe des Sandboxee lesen möchten.

Sie benötigen jedoch höchstwahrscheinlich eine komplexere Kommunikationslogik zwischen dem Executor und dem Sandboxee. Die Kommunikations-API (siehe comms.h-Header-Datei) kann zum Senden von Ganzzahlen, Strings, Bytezwischenspeichern, Protokollpuffern oder Dateideskriptoren verwendet werden.

Dateideskriptoren freigeben

Über die Inter-Process Communication API (siehe ipc.h) können Sie MapFd() oder ReceiveFd() verwenden:

  • Verwenden Sie MapFd(), um Dateideskriptoren vom Executor dem Sandboxee zuzuordnen. Dies kann verwendet werden, um eine Datei, die vom Executor geöffnet wurde, für die Verwendung im Sandboxee freizugeben. Ein Anwendungsbeispiel ist in static zu sehen.

    // The executor opened /proc/version and passes it to the sandboxee as stdin
    executor->ipc()->MapFd(proc_version_fd, STDIN_FILENO);
    
  • Verwenden Sie ReceiveFd(), um einen Socket-Pair-Endpunkt zu erstellen. Damit können die Standardausgabe oder Standardfehler von Sandboxee gelesen werden. Ein Anwendungsbeispiel finden Sie im Tool.

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

Kommunikations-API verwenden

Sandbox2 bietet eine praktische Kommunikations-API. Dies ist eine einfache und einfache Möglichkeit, Ganzzahlen, Strings oder Byte-Puffer zwischen dem Executor und dem Sandboxee zu teilen. Im Folgenden finden Sie einige Code-Snippets, die im crc4-Beispiel zu sehen sind.

Um mit der Kommunikations-API zu beginnen, müssen Sie zuerst das Kommunikationsobjekt vom Sandbox2-Objekt abrufen:

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

Sobald das Kommunikationsobjekt verfügbar ist, können Daten mithilfe einer der Funktionen der Send*-Familie an den Sandboxee gesendet werden. Ein Beispiel für die Verwendung der Kommunikations-API finden Sie im crc4-Beispiel. Das folgende Code-Snippet zeigt einen Auszug aus diesem Beispiel. Der Executor sendet ein unsigned char buf[size] mit SendBytes(buf, size):

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

Um Daten vom Sandboxee zu erhalten, verwenden Sie eine der Recv*-Funktionen. Das folgende Code-Snippet ist ein Auszug aus dem crc4-Beispiel. Der Executor empfängt die Prüfsumme in einer vorzeichenlosen 32-Bit-Ganzzahl: uint32_t crc4;

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

Daten für Puffer freigeben

Eine weitere Funktion zur Datenfreigabe besteht darin, die Puffer-API zu verwenden, um große Datenmengen freizugeben und teure Kopien zu vermeiden, die zwischen dem Executor und dem Sandboxee hin- und hergeschickt werden.

Der Executor erstellt einen Puffer – entweder anhand der Größe und der zu übergebenden Daten oder direkt aus einem Dateideskriptor – und übergibt ihn mit comms->SendFD() im Executor und comms->RecvFD() im Sandboxee an den Sandboxee.

Im folgenden Code-Snippet sehen Sie die Seite des Executors. Die Sandbox wird asynchron ausgeführt und gibt Daten über einen Puffer für den Sandboxee frei:

// 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());

Auf der Seite von Sandboxee müssen Sie auch ein Pufferobjekt erstellen und die Daten aus dem Dateideskriptor lesen, der vom Executor gesendet wurde:

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