[PATCH v1 2/5] sbm: sandbox input and output buffers

From: Petr Tesarik
Date: Wed Feb 14 2024 - 06:32:24 EST


From: Petr Tesarik <petr.tesarik1@xxxxxxxxxxxxxxxxxxx>

Provide SBM_COPY_IN(), SBM_COPY_OUT() and SBM_COPY_INOUT() macros to
allocate sandbox mode buffers for input/output data. Input data is copied
from kernel mode to the allocated buffer before calling the target
function. Output data is copied from the allocated buffer to kernel mode
after the target function returns.

Define two new arch hooks to map input/output buffers:

* arch_sbm_map_readonly()
* arch_sbm_map_writable()

Before calling the target function, use these hooks to create mappings for
all buffers, read-only or writable as appropriate. Provide a fallback no-op
implementation.

Upon expansion, the SBM_COPY_xxx() macros evaluate to the address of the
buffer in sandbox mode, cast back to the original type. This pointer should
be used by code running in the sandbox. It should not be used in kernel
mode; although the address is valid, the buffers are overwritten by
sbm_exec().

To do the typecast, prefer typeof(({x;})) over typeof(x). The statement
expression forces array-to-pointer decay, which allows to pass an array as
an argument to these macros.

Signed-off-by: Petr Tesarik <petr.tesarik1@xxxxxxxxxxxxxxxxxxx>
---
include/linux/sbm.h | 154 ++++++++++++++++++++++++++++++++++++++++++++
kernel/sbm.c | 88 +++++++++++++++++++++++++
2 files changed, 242 insertions(+)

diff --git a/include/linux/sbm.h b/include/linux/sbm.h
index 8e0c63fb9fb2..9671b3c556c7 100644
--- a/include/linux/sbm.h
+++ b/include/linux/sbm.h
@@ -9,16 +9,27 @@
#ifndef __LINUX_SBM_H
#define __LINUX_SBM_H

+struct sbm_buf;
+
/**
* struct sbm - SandBox Mode instance.
* @error: Error code. Initialized to zero by sbm_init() and updated when
* a SBM operation fails.
* @private: Arch-specific private data.
+ * @input: Input data. Copied to a temporary buffer before starting sandbox
+ * mode.
+ * @output: Output data. Copied from a temporary buffer after return from
+ * sandbox mode.
+ * @io: Input and output data. Copied to a temporary buffer before
+ * starting sandbox mode and copied back after return.
*/
struct sbm {
#ifdef CONFIG_SANDBOX_MODE
int error;
void *private;
+ struct sbm_buf *input;
+ struct sbm_buf *output;
+ struct sbm_buf *io;
#endif
};

@@ -73,6 +84,103 @@ static inline int sbm_error(const struct sbm *sbm)
*/
int sbm_exec(struct sbm *sbm, sbm_func func, void *data);

+/**
+ * struct sbm_buf - Description of an input/output buffer.
+ * @next: Pointer to the next buffer in the list.
+ * @kern_ptr: Buffer address in kernel mode.
+ * @sbm_ptr: Buffer address in sandbox mode.
+ * @size: Size of the buffer.
+ */
+struct sbm_buf {
+ struct sbm_buf *next;
+ void *kern_ptr;
+ void *sbm_ptr;
+ size_t size;
+};
+
+/**
+ * sbm_alloc_buf() - Allocate a new input/output buffer.
+ * @sbm: SBM instance.
+ * @size: Size of the buffer.
+ *
+ * Allocate a new &struct sbm_buf and the corresponding sandbox mode
+ * input/output buffer. If either allocation fails, update &sbm->error.
+ *
+ * Return: New buffer descriptor, or %NULL on allocation failure.
+ */
+struct sbm_buf *sbm_alloc_buf(struct sbm *sbm, size_t size);
+
+/**
+ * sbm_add_buf() - Add a new I/O buffer to the SBM instance.
+ * @sbm: SBM instance.
+ * @list: Target argument buffer list.
+ * @buf: Buffer virtual address.
+ * @size: Size of the buffer.
+ *
+ * Add a new buffer to @list.
+ *
+ * Return: SBM address of the buffer, or %NULL on error.
+ */
+static inline void *sbm_add_buf(struct sbm *sbm, struct sbm_buf **list,
+ const void *buf, size_t size)
+{
+ struct sbm_buf *io;
+
+ io = sbm_alloc_buf(sbm, size);
+ if (!io)
+ return NULL;
+
+ io->kern_ptr = (void *)buf;
+ io->next = *list;
+ *list = io;
+ return io->sbm_ptr;
+}
+
+/**
+ * SBM_COPY_IN() - Mark an input buffer for copying into SBM.
+ * @sbm: SBM instance.
+ * @buf: Buffer virtual address.
+ * @size: Size of the buffer.
+ *
+ * Add a buffer to the input buffer list for @sbm. The content of the
+ * buffer is copied to sandbox mode before calling the target function.
+ *
+ * It is OK to modify the input buffer after invoking this macro.
+ *
+ * Return: Buffer address in sandbox mode.
+ */
+#define SBM_COPY_IN(sbm, buf, size) \
+ ((typeof(({buf; })))sbm_add_buf((sbm), &(sbm)->input, (buf), (size)))
+
+/**
+ * SBM_COPY_OUT() - Mark an output buffer for copying out of SBM.
+ * @sbm: SBM instance.
+ * @buf: Buffer virtual address.
+ * @size: Size of the buffer.
+ *
+ * Add a buffer to the output buffer list for @sbm. The content of the
+ * buffer is copied to kernel mode after calling the target function.
+ *
+ * Return: Buffer address in sandbox mode.
+ */
+#define SBM_COPY_OUT(sbm, buf, size) \
+ ((typeof(({buf; })))sbm_add_buf((sbm), &(sbm)->output, (buf), (size)))
+
+/**
+ * SBM_COPY_INOUT() - Mark an input buffer for copying into SBM and out of SBM.
+ * @sbm: SBM instance.
+ * @buf: Buffer virtual address.
+ * @size: Size of the buffer.
+ *
+ * Add a buffer to the input and output buffer list for @sbm. The content
+ * of the buffer is copied to sandbox mode before calling the target function
+ * and copied back to kernel mode after the call.
+ *
+ * Return: Buffer address in sandbox mode.
+ */
+#define SBM_COPY_INOUT(sbm, buf, size) \
+ ((typeof(({buf; })))sbm_add_buf((sbm), &(sbm)->io, (buf), (size)))
+
#ifdef CONFIG_HAVE_ARCH_SBM

/**
@@ -95,6 +203,30 @@ int arch_sbm_init(struct sbm *sbm);
*/
void arch_sbm_destroy(struct sbm *sbm);

+/**
+ * arch_sbm_map_readonly() - Arch hook to map a buffer for reading.
+ * @sbm: SBM instance.
+ * @buf: Buffer to be mapped.
+ *
+ * Make the specified buffer readable by sandbox code. See also
+ * arch_sbm_map_writable().
+ *
+ * Return: Zero on success, negative on error.
+ */
+int arch_sbm_map_readonly(struct sbm *sbm, const struct sbm_buf *buf);
+
+/**
+ * arch_sbm_map_writable() - Arch hook to map a buffer for reading and writing.
+ * @sbm: SBM instance.
+ * @buf: Buffer to be mapped.
+ *
+ * Make the specified buffer readable and writable by sandbox code.
+ * See also arch_sbm_map_readonly().
+ *
+ * Return: Zero on success, negative on error.
+ */
+int arch_sbm_map_writable(struct sbm *sbm, const struct sbm_buf *buf);
+
/**
* arch_sbm_exec() - Arch hook to execute code in a sandbox.
* @sbm: SBM instance.
@@ -121,6 +253,18 @@ static inline void arch_sbm_destroy(struct sbm *sbm)
{
}

+static inline int arch_sbm_map_readonly(struct sbm *sbm,
+ const struct sbm_buf *buf)
+{
+ return 0;
+}
+
+static inline int arch_sbm_map_writable(struct sbm *sbm,
+ const struct sbm_buf *buf)
+{
+ return 0;
+}
+
static inline int arch_sbm_exec(struct sbm *sbm, sbm_func func, void *data)
{
return func(data);
@@ -149,6 +293,16 @@ static inline int sbm_exec(struct sbm *sbm, sbm_func func, void *data)
return func(data);
}

+/* Evaluate expression exactly once, avoiding warnings about a "statement
+ * with no effect". GCC doesn't issue this warning for the return value
+ * of a statement expression.
+ */
+#define __SBM_EVAL(x) ({ typeof(({x; })) __tmp = (x); __tmp; })
+
+#define SBM_COPY_IN(sbm, buf, size) __SBM_EVAL(buf)
+#define SBM_COPY_OUT(sbm, buf, size) __SBM_EVAL(buf)
+#define SBM_COPY_INOUT(sbm, buf, size) __SBM_EVAL(buf)
+
#endif /* CONFIG_SANDBOX_MODE */

#endif /* __LINUX_SBM_H */
diff --git a/kernel/sbm.c b/kernel/sbm.c
index 9a5b89a71a23..df57184f5d87 100644
--- a/kernel/sbm.c
+++ b/kernel/sbm.c
@@ -9,7 +9,46 @@

#include <linux/export.h>
#include <linux/sbm.h>
+#include <linux/slab.h>
#include <linux/string.h>
+#include <linux/vmalloc.h>
+
+struct sbm_buf *sbm_alloc_buf(struct sbm *sbm, size_t size)
+{
+ struct sbm_buf *buf;
+
+ if (sbm->error)
+ return NULL;
+
+ buf = kzalloc(sizeof(*buf), GFP_KERNEL);
+ if (!buf) {
+ sbm->error = -ENOMEM;
+ goto out;
+ }
+ buf->sbm_ptr = vzalloc(size);
+ if (!buf->sbm_ptr) {
+ sbm->error = -ENOMEM;
+ goto out;
+ }
+ buf->size = size;
+
+out:
+ return buf;
+}
+EXPORT_SYMBOL(sbm_alloc_buf);
+
+/* Free a buffer list. */
+static void sbm_free_buf_list(const struct sbm_buf *buf)
+{
+ const struct sbm_buf *nextbuf;
+
+ while (buf) {
+ vfree(buf->sbm_ptr);
+ nextbuf = buf->next;
+ kfree(buf);
+ buf = nextbuf;
+ }
+}

int sbm_init(struct sbm *sbm)
{
@@ -25,14 +64,61 @@ EXPORT_SYMBOL(sbm_init);

void sbm_destroy(struct sbm *sbm)
{
+ sbm_free_buf_list(sbm->input);
+ sbm_free_buf_list(sbm->output);
+ sbm_free_buf_list(sbm->io);
arch_sbm_destroy(sbm);
}
EXPORT_SYMBOL(sbm_destroy);

+/* Copy input buffers into a sandbox. */
+static int sbm_copy_in(struct sbm *sbm)
+{
+ const struct sbm_buf *buf;
+ int err = 0;
+
+ for (buf = sbm->input; buf; buf = buf->next) {
+ err = arch_sbm_map_readonly(sbm, buf);
+ if (err)
+ return err;
+ memcpy(buf->sbm_ptr, buf->kern_ptr, buf->size);
+ }
+
+ for (buf = sbm->io; buf; buf = buf->next) {
+ err = arch_sbm_map_writable(sbm, buf);
+ if (err)
+ return err;
+ memcpy(buf->sbm_ptr, buf->kern_ptr, buf->size);
+ }
+
+ for (buf = sbm->output; buf; buf = buf->next) {
+ err = arch_sbm_map_writable(sbm, buf);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+/* Copy output buffers out of a sandbox. */
+static void sbm_copy_out(struct sbm *sbm)
+{
+ const struct sbm_buf *buf;
+
+ for (buf = sbm->output; buf; buf = buf->next)
+ memcpy(buf->kern_ptr, buf->sbm_ptr, buf->size);
+ for (buf = sbm->io; buf; buf = buf->next)
+ memcpy(buf->kern_ptr, buf->sbm_ptr, buf->size);
+}
+
int sbm_exec(struct sbm *sbm, sbm_func func, void *args)
{
int ret;

+ if (sbm->error)
+ return sbm->error;
+
+ sbm->error = sbm_copy_in(sbm);
if (sbm->error)
return sbm->error;

@@ -40,6 +126,8 @@ int sbm_exec(struct sbm *sbm, sbm_func func, void *args)
if (sbm->error)
return sbm->error;

+ sbm_copy_out(sbm);
+
return ret;
}
EXPORT_SYMBOL(sbm_exec);
--
2.34.1