5. Kommunikation mit dem Sandboxee

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

Wahrscheinlich benötigen Sie jedoch eine komplexere Kommunikationslogik zwischen dem Executor und dem Sandboxee. Mit der Comms API (siehe die Headerdatei comms.h) können Sie Ganzzahlen, Strings, Byte-Puffer, Protobufs oder Dateideskriptoren senden.

Dateideskriptoren freigeben

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

  • Verwenden Sie MapFd(), um Dateideskriptoren vom Executor zum Sandboxee zuzuordnen. Damit kann eine Datei, die vom Executor geöffnet wurde, für die Verwendung in der Sandboxee freigegeben werden. Ein Beispiel für die Verwendung finden Sie unter static.

    // The executor opened /proc/version and passes it to the sandboxee as stdin
    executor->ipc()->MapFd(proc_version_fd, STDIN_FILENO);
    
  • Verwenden SieReceiveFd(), um einen Socketpair-Endpunkt zu erstellen. Damit kann die Standardausgabe oder die Standardfehler des Sandboxee gelesen werden. Ein Beispiel für die Verwendung finden Sie im Tool.

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

Comms API verwenden

Sandbox2 bietet eine praktische Comms-API. So lassen sich Ganzzahlen, Strings oder Byte-Puffer einfach zwischen dem Executor und dem Sandboxee-Prozess austauschen. Im Folgenden finden Sie einige Code-Snippets aus dem Beispiel crc4.

Wenn Sie die Comms API verwenden möchten, müssen Sie zuerst das Comms-Objekt aus dem Sandbox2-Objekt abrufen:

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

Sobald das Kommunikationsobjekt verfügbar ist, können Daten mit einer der Send*-Funktionen an die Sandboxee gesendet werden. Ein Beispiel für die Verwendung der Comms API finden Sie im crc4-Beispiel. Das Code-Snippet unten 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 */
}

Wenn Sie Daten vom Sandboxee empfangen möchten, verwenden Sie eine der Recv*-Funktionen. Das folgende Code-Snippet ist ein Auszug aus dem Beispiel crc4. Der Executor empfängt die Prüfsumme als vorzeichenlose 32-Bit-Ganzzahl: uint32_t crc4;

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

Daten mit Buffers teilen

Eine weitere Funktion für die gemeinsame Nutzung von Daten ist die Verwendung der Buffer API, um große Datenmengen freizugeben und teure Kopien zu vermeiden, die zwischen dem Executor und dem Sandboxee hin- und hergesendet 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 Code-Snippet unten sehen Sie die Seite des Executors. Die Sandbox wird asynchron ausgeführt und gibt Daten über einen Puffer an die Sandboxee weiter:

// 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 Sandboxee-Seite müssen Sie außerdem ein Pufferobjekt erstellen und die Daten aus dem vom Executor gesendeten Dateideskriptor lesen:

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