5. Communicating with the Sandboxee
By default, the executor can communicate with the Sandboxee through file descriptors. This might be all you need, for example if you just want to share a file with the Sandboxee, or read the Sandboxee's standard output.
However, you most likely have the need for more complex communication logic between the executor and Sandboxee. The comms API (see the comms.h header file) can be used to send integers, strings, byte buffers, protobufs, or file descriptors.
Sharing File Descriptors
Using the Inter-Process Communication API (see
ipc.h),
you can use MapFd()
or ReceiveFd()
:
Use
MapFd()
to map file descriptors from the executor to the Sandboxee. This can be used to share a file opened from the executor for use in the Sandboxee. An example use can be seen in static.// The executor opened /proc/version and passes it to the sandboxee as stdin executor->ipc()->MapFd(proc_version_fd, STDIN_FILENO);
Use
ReceiveFd()
to create a socketpair endpoint. This can be used to read the Sandboxee's standard output or standard errors. An example use can be seen in the tool.// The executor receives a file descriptor of the sandboxee stdout int recv_fd1 = executor->ipc())->ReceiveFd(STDOUT_FILENO);
Using the comms API
Sandbox2 provides a convenient comms API. This is a simple and easy way to share integers, strings, or byte buffers between the executor and Sandboxee. Below are some code snippets that you can find in the crc4 example.
To get started with the comms API, you first have to get the comms object from the Sandbox2 object:
sandbox2::Comms* comms = s2.comms();
Once the comms object is available, data can be sent to the Sandboxee using one
of the Send* family of functions. You can find an example use of the comms API
in the crc4 example. The code snippet
below shows an excerpt from that example. The executor sends an unsigned char
buf[size]
with SendBytes(buf, size)
:
if (!(comms->SendBytes(static_cast<const uint8_t*>(buf), sz))) {
/* handle error */
}
To receive data from the Sandboxee, use one of the Recv*
functions. The code
snippet below is an excerpt from the
crc4 example. The executor receives
the checksum in a 32-bit unsigned integer: uint32_t crc4;
if (!(comms->RecvUint32(&crc4))) {
/* handle error */
}
Sharing Data with Buffers
Another data sharing functionality is to use the buffer API to share large amounts of data and to avoid expensive copies that are sent back and forth between the executor and Sandboxee.
The executor creates a Buffer, either by size and data to be passed, or directly
from a file descriptor, and passes it to the Sandboxee using comms->SendFD()
in the executor and comms->RecvFD()
in the Sandboxee.
In the code snippet below, you can see the executor's side. The sandbox runs asynchronously and shares data via a buffer with the 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());
On the Sandboxee's side, you also have to create a buffer object and read the data from the file descriptor sent by the 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 */