android: binder: support for scatter-gather.
authorMartijn Coenen <maco@google.com>
Fri, 30 Sep 2016 12:10:07 +0000 (14:10 +0200)
committerMartijn Coenen <maco@android.com>
Mon, 31 Oct 2016 14:14:59 +0000 (15:14 +0100)
Previously all data passed over binder needed
to be serialized, with the exception of Binder
objects and file descriptors.

This patchs adds support for scatter-gathering raw
memory buffers into a binder transaction, avoiding
the need to first serialize them into a Parcel.

To remain backwards compatibile with existing
binder clients, it introduces two new command
ioctls for this purpose - BC_TRANSACTION_SG and
BC_REPLY_SG. These commands may only be used with
the new binder_transaction_data_sg structure,
which adds a field for the total size of the
buffers we are scatter-gathering.

Because memory buffers may contain pointers to
other buffers, we allow callers to specify
a parent buffer and an offset into it, to indicate
this is a location pointing to the buffer that
we are fixing up. The kernel will then take care
of fixing up the pointer to that buffer as well.

Change-Id: I02417f28cff14688f2e1d6fcb959438fd96566cc
Signed-off-by: Martijn Coenen <maco@google.com>
drivers/android/binder.c
include/uapi/linux/android/binder.h

index 83a6c1bbbad59ff17c369a508eb618c873b204cf..a0f4d29e514888c74cfbd03b726192b185d4ab0a 100644 (file)
@@ -153,6 +153,9 @@ module_param_call(stop_on_user_error, binder_set_stop_on_user_error,
 
 #define to_binder_fd_object(hdr) container_of(hdr, struct binder_fd_object, hdr)
 
+#define to_binder_buffer_object(hdr) \
+       container_of(hdr, struct binder_buffer_object, hdr)
+
 enum binder_stat_types {
        BINDER_STAT_PROC,
        BINDER_STAT_THREAD,
@@ -166,7 +169,7 @@ enum binder_stat_types {
 
 struct binder_stats {
        int br[_IOC_NR(BR_FAILED_REPLY) + 1];
-       int bc[_IOC_NR(BC_DEAD_BINDER_DONE) + 1];
+       int bc[_IOC_NR(BC_REPLY_SG) + 1];
        int obj_created[BINDER_STAT_COUNT];
        int obj_deleted[BINDER_STAT_COUNT];
 };
@@ -1306,6 +1309,9 @@ static size_t binder_validate_object(struct binder_buffer *buffer, u64 offset)
        case BINDER_TYPE_FD:
                object_size = sizeof(struct binder_fd_object);
                break;
+       case BINDER_TYPE_PTR:
+               object_size = sizeof(struct binder_buffer_object);
+               break;
        default:
                return 0;
        }
@@ -1316,11 +1322,111 @@ static size_t binder_validate_object(struct binder_buffer *buffer, u64 offset)
                return 0;
 }
 
+/**
+ * binder_validate_ptr() - validates binder_buffer_object in a binder_buffer.
+ * @b:         binder_buffer containing the object
+ * @index:     index in offset array at which the binder_buffer_object is
+ *             located
+ * @start:     points to the start of the offset array
+ * @num_valid: the number of valid offsets in the offset array
+ *
+ * Return:     If @index is within the valid range of the offset array
+ *             described by @start and @num_valid, and if there's a valid
+ *             binder_buffer_object at the offset found in index @index
+ *             of the offset array, that object is returned. Otherwise,
+ *             %NULL is returned.
+ *             Note that the offset found in index @index itself is not
+ *             verified; this function assumes that @num_valid elements
+ *             from @start were previously verified to have valid offsets.
+ */
+static struct binder_buffer_object *binder_validate_ptr(struct binder_buffer *b,
+                                                       binder_size_t index,
+                                                       binder_size_t *start,
+                                                       binder_size_t num_valid)
+{
+       struct binder_buffer_object *buffer_obj;
+       binder_size_t *offp;
+
+       if (index >= num_valid)
+               return NULL;
+
+       offp = start + index;
+       buffer_obj = (struct binder_buffer_object *)(b->data + *offp);
+       if (buffer_obj->hdr.type != BINDER_TYPE_PTR)
+               return NULL;
+
+       return buffer_obj;
+}
+
+/**
+ * binder_validate_fixup() - validates pointer/fd fixups happen in order.
+ * @b:                 transaction buffer
+ * @objects_start      start of objects buffer
+ * @buffer:            binder_buffer_object in which to fix up
+ * @offset:            start offset in @buffer to fix up
+ * @last_obj:          last binder_buffer_object that we fixed up in
+ * @last_min_offset:   minimum fixup offset in @last_obj
+ *
+ * Return:             %true if a fixup in buffer @buffer at offset @offset is
+ *                     allowed.
+ *
+ * For safety reasons, we only allow fixups inside a buffer to happen
+ * at increasing offsets; additionally, we only allow fixup on the last
+ * buffer object that was verified, or one of its parents.
+ *
+ * Example of what is allowed:
+ *
+ * A
+ *   B (parent = A, offset = 0)
+ *   C (parent = A, offset = 16)
+ *     D (parent = C, offset = 0)
+ *   E (parent = A, offset = 32) // min_offset is 16 (C.parent_offset)
+ *
+ * Examples of what is not allowed:
+ *
+ * Decreasing offsets within the same parent:
+ * A
+ *   C (parent = A, offset = 16)
+ *   B (parent = A, offset = 0) // decreasing offset within A
+ *
+ * Referring to a parent that wasn't the last object or any of its parents:
+ * A
+ *   B (parent = A, offset = 0)
+ *   C (parent = A, offset = 0)
+ *   C (parent = A, offset = 16)
+ *     D (parent = B, offset = 0) // B is not A or any of A's parents
+ */
+static bool binder_validate_fixup(struct binder_buffer *b,
+                                 binder_size_t *objects_start,
+                                 struct binder_buffer_object *buffer,
+                                 binder_size_t fixup_offset,
+                                 struct binder_buffer_object *last_obj,
+                                 binder_size_t last_min_offset)
+{
+       if (!last_obj) {
+               /* Nothing to fix up in */
+               return false;
+       }
+
+       while (last_obj != buffer) {
+               /*
+                * Safe to retrieve the parent of last_obj, since it
+                * was already previously verified by the driver.
+                */
+               if ((last_obj->flags & BINDER_BUFFER_FLAG_HAS_PARENT) == 0)
+                       return false;
+               last_min_offset = last_obj->parent_offset + sizeof(uintptr_t);
+               last_obj = (struct binder_buffer_object *)
+                       (b->data + *(objects_start + last_obj->parent));
+       }
+       return (fixup_offset >= last_min_offset);
+}
+
 static void binder_transaction_buffer_release(struct binder_proc *proc,
                                              struct binder_buffer *buffer,
                                              binder_size_t *failed_at)
 {
-       binder_size_t *offp, *off_end;
+       binder_size_t *offp, *off_start, *off_end;
        int debug_id = buffer->debug_id;
 
        binder_debug(BINDER_DEBUG_TRANSACTION,
@@ -1331,13 +1437,13 @@ static void binder_transaction_buffer_release(struct binder_proc *proc,
        if (buffer->target_node)
                binder_dec_node(buffer->target_node, 1, 0);
 
-       offp = (binder_size_t *)(buffer->data +
-                                ALIGN(buffer->data_size, sizeof(void *)));
+       off_start = (binder_size_t *)(buffer->data +
+                                     ALIGN(buffer->data_size, sizeof(void *)));
        if (failed_at)
                off_end = failed_at;
        else
-               off_end = (void *)offp + buffer->offsets_size;
-       for (; offp < off_end; offp++) {
+               off_end = (void *)off_start + buffer->offsets_size;
+       for (offp = off_start; offp < off_end; offp++) {
                struct binder_object_header *hdr;
                size_t object_size = binder_validate_object(buffer, *offp);
 
@@ -1393,7 +1499,12 @@ static void binder_transaction_buffer_release(struct binder_proc *proc,
                        if (failed_at)
                                task_close_fd(proc, fp->fd);
                } break;
-
+               case BINDER_TYPE_PTR:
+                       /*
+                        * Nothing to do here, this will get cleaned up when the
+                        * transaction buffer gets freed
+                        */
+                       break;
                default:
                        pr_err("transaction release %d bad object type %x\n",
                                debug_id, hdr->type);
@@ -1563,6 +1674,53 @@ err_fd_not_accepted:
        return ret;
 }
 
+static int binder_fixup_parent(struct binder_transaction *t,
+                              struct binder_thread *thread,
+                              struct binder_buffer_object *bp,
+                              binder_size_t *off_start,
+                              binder_size_t num_valid,
+                              struct binder_buffer_object *last_fixup_obj,
+                              binder_size_t last_fixup_min_off)
+{
+       struct binder_buffer_object *parent;
+       u8 *parent_buffer;
+       struct binder_buffer *b = t->buffer;
+       struct binder_proc *proc = thread->proc;
+       struct binder_proc *target_proc = t->to_proc;
+
+       if (!(bp->flags & BINDER_BUFFER_FLAG_HAS_PARENT))
+               return 0;
+
+       parent = binder_validate_ptr(b, bp->parent, off_start, num_valid);
+       if (!parent) {
+               binder_user_error("%d:%d got transaction with invalid parent offset or type\n",
+                                 proc->pid, thread->pid);
+               return -EINVAL;
+       }
+
+       if (!binder_validate_fixup(b, off_start,
+                                  parent, bp->parent_offset,
+                                  last_fixup_obj,
+                                  last_fixup_min_off)) {
+               binder_user_error("%d:%d got transaction with out-of-order buffer fixup\n",
+                                 proc->pid, thread->pid);
+               return -EINVAL;
+       }
+
+       if (parent->length < sizeof(binder_uintptr_t) ||
+           bp->parent_offset > parent->length - sizeof(binder_uintptr_t)) {
+               /* No space for a pointer here! */
+               binder_user_error("%d:%d got transaction with invalid parent offset\n",
+                                 proc->pid, thread->pid);
+               return -EINVAL;
+       }
+       parent_buffer = (u8 *)(parent->buffer -
+                              target_proc->user_buffer_offset);
+       *(binder_uintptr_t *)(parent_buffer + bp->parent_offset) = bp->buffer;
+
+       return 0;
+}
+
 static void binder_transaction(struct binder_proc *proc,
                               struct binder_thread *thread,
                               struct binder_transaction_data *tr, int reply,
@@ -1571,8 +1729,9 @@ static void binder_transaction(struct binder_proc *proc,
        int ret;
        struct binder_transaction *t;
        struct binder_work *tcomplete;
-       binder_size_t *offp, *off_end;
+       binder_size_t *offp, *off_end, *off_start;
        binder_size_t off_min;
+       u8 *sg_bufp, *sg_buf_end;
        struct binder_proc *target_proc;
        struct binder_thread *target_thread = NULL;
        struct binder_node *target_node = NULL;
@@ -1581,6 +1740,8 @@ static void binder_transaction(struct binder_proc *proc,
        struct binder_transaction *in_reply_to = NULL;
        struct binder_transaction_log_entry *e;
        uint32_t return_error;
+       struct binder_buffer_object *last_fixup_obj = NULL;
+       binder_size_t last_fixup_min_off = 0;
        struct binder_context *context = proc->context;
 
        e = binder_transaction_log_add(&binder_transaction_log);
@@ -1755,8 +1916,9 @@ static void binder_transaction(struct binder_proc *proc,
        if (target_node)
                binder_inc_node(target_node, 1, 0, NULL);
 
-       offp = (binder_size_t *)(t->buffer->data +
-                                ALIGN(tr->data_size, sizeof(void *)));
+       off_start = (binder_size_t *)(t->buffer->data +
+                                     ALIGN(tr->data_size, sizeof(void *)));
+       offp = off_start;
 
        if (copy_from_user(t->buffer->data, (const void __user *)(uintptr_t)
                           tr->data.ptr.buffer, tr->data_size)) {
@@ -1778,7 +1940,16 @@ static void binder_transaction(struct binder_proc *proc,
                return_error = BR_FAILED_REPLY;
                goto err_bad_offset;
        }
-       off_end = (void *)offp + tr->offsets_size;
+       if (!IS_ALIGNED(extra_buffers_size, sizeof(u64))) {
+               binder_user_error("%d:%d got transaction with unaligned buffers size, %lld\n",
+                                 proc->pid, thread->pid,
+                                 extra_buffers_size);
+               return_error = BR_FAILED_REPLY;
+               goto err_bad_offset;
+       }
+       off_end = (void *)off_start + tr->offsets_size;
+       sg_bufp = (u8 *)(PTR_ALIGN(off_end, sizeof(void *)));
+       sg_buf_end = sg_bufp + extra_buffers_size;
        off_min = 0;
        for (; offp < off_end; offp++) {
                struct binder_object_header *hdr;
@@ -1831,7 +2002,41 @@ static void binder_transaction(struct binder_proc *proc,
                        fp->pad_binder = 0;
                        fp->fd = target_fd;
                } break;
-
+               case BINDER_TYPE_PTR: {
+                       struct binder_buffer_object *bp =
+                               to_binder_buffer_object(hdr);
+                       size_t buf_left = sg_buf_end - sg_bufp;
+
+                       if (bp->length > buf_left) {
+                               binder_user_error("%d:%d got transaction with too large buffer\n",
+                                                 proc->pid, thread->pid);
+                               return_error = BR_FAILED_REPLY;
+                               goto err_bad_offset;
+                       }
+                       if (copy_from_user(sg_bufp,
+                                          (const void __user *)(uintptr_t)
+                                          bp->buffer, bp->length)) {
+                               binder_user_error("%d:%d got transaction with invalid offsets ptr\n",
+                                                 proc->pid, thread->pid);
+                               return_error = BR_FAILED_REPLY;
+                               goto err_copy_data_failed;
+                       }
+                       /* Fixup buffer pointer to target proc address space */
+                       bp->buffer = (uintptr_t)sg_bufp +
+                               target_proc->user_buffer_offset;
+                       sg_bufp += ALIGN(bp->length, sizeof(u64));
+
+                       ret = binder_fixup_parent(t, thread, bp, off_start,
+                                                 offp - off_start,
+                                                 last_fixup_obj,
+                                                 last_fixup_min_off);
+                       if (ret < 0) {
+                               return_error = BR_FAILED_REPLY;
+                               goto err_translate_failed;
+                       }
+                       last_fixup_obj = bp;
+                       last_fixup_min_off = 0;
+               } break;
                default:
                        binder_user_error("%d:%d got transaction with invalid object type, %x\n",
                                proc->pid, thread->pid, hdr->type);
@@ -2085,6 +2290,17 @@ static int binder_thread_write(struct binder_proc *proc,
                        break;
                }
 
+               case BC_TRANSACTION_SG:
+               case BC_REPLY_SG: {
+                       struct binder_transaction_data_sg tr;
+
+                       if (copy_from_user(&tr, ptr, sizeof(tr)))
+                               return -EFAULT;
+                       ptr += sizeof(tr);
+                       binder_transaction(proc, thread, &tr.transaction_data,
+                                          cmd == BC_REPLY_SG, tr.buffers_size);
+                       break;
+               }
                case BC_TRANSACTION:
                case BC_REPLY: {
                        struct binder_transaction_data tr;
@@ -3606,7 +3822,9 @@ static const char * const binder_command_strings[] = {
        "BC_EXIT_LOOPER",
        "BC_REQUEST_DEATH_NOTIFICATION",
        "BC_CLEAR_DEATH_NOTIFICATION",
-       "BC_DEAD_BINDER_DONE"
+       "BC_DEAD_BINDER_DONE",
+       "BC_TRANSACTION_SG",
+       "BC_REPLY_SG",
 };
 
 static const char * const binder_objstat_strings[] = {
index f67c2b1c0713799f51fd4524daa3ae48e27f268c..f3ef6e2634ba9f360171f28cb62dc8f2fbe5f8f8 100644 (file)
@@ -33,6 +33,7 @@ enum {
        BINDER_TYPE_HANDLE      = B_PACK_CHARS('s', 'h', '*', B_TYPE_LARGE),
        BINDER_TYPE_WEAK_HANDLE = B_PACK_CHARS('w', 'h', '*', B_TYPE_LARGE),
        BINDER_TYPE_FD          = B_PACK_CHARS('f', 'd', '*', B_TYPE_LARGE),
+       BINDER_TYPE_PTR         = B_PACK_CHARS('p', 't', '*', B_TYPE_LARGE),
 };
 
 enum {
@@ -95,6 +96,39 @@ struct binder_fd_object {
 
        binder_uintptr_t                cookie;
 };
+
+/* struct binder_buffer_object - object describing a userspace buffer
+ * @hdr:               common header structure
+ * @flags:             one or more BINDER_BUFFER_* flags
+ * @buffer:            address of the buffer
+ * @length:            length of the buffer
+ * @parent:            index in offset array pointing to parent buffer
+ * @parent_offset:     offset in @parent pointing to this buffer
+ *
+ * A binder_buffer object represents an object that the
+ * binder kernel driver can copy verbatim to the target
+ * address space. A buffer itself may be pointed to from
+ * within another buffer, meaning that the pointer inside
+ * that other buffer needs to be fixed up as well. This
+ * can be done by setting the BINDER_BUFFER_FLAG_HAS_PARENT
+ * flag in @flags, by setting @parent buffer to the index
+ * in the offset array pointing to the parent binder_buffer_object,
+ * and by setting @parent_offset to the offset in the parent buffer
+ * at which the pointer to this buffer is located.
+ */
+struct binder_buffer_object {
+       struct binder_object_header     hdr;
+       __u32                           flags;
+       binder_uintptr_t                buffer;
+       binder_size_t                   length;
+       binder_size_t                   parent;
+       binder_size_t                   parent_offset;
+};
+
+enum {
+       BINDER_BUFFER_FLAG_HAS_PARENT = 0x01,
+};
+
 /*
  * On 64-bit platforms where user code may run in 32-bits the driver must
  * translate the buffer (and local binder) addresses appropriately.
@@ -187,6 +221,11 @@ struct binder_transaction_data {
        } data;
 };
 
+struct binder_transaction_data_sg {
+       struct binder_transaction_data transaction_data;
+       binder_size_t buffers_size;
+};
+
 struct binder_ptr_cookie {
        binder_uintptr_t ptr;
        binder_uintptr_t cookie;
@@ -371,6 +410,12 @@ enum binder_driver_command_protocol {
        /*
         * void *: cookie
         */
+
+       BC_TRANSACTION_SG = _IOW('c', 17, struct binder_transaction_data_sg),
+       BC_REPLY_SG = _IOW('c', 18, struct binder_transaction_data_sg),
+       /*
+        * binder_transaction_data_sg: the sent command.
+        */
 };
 
 #endif /* _UAPI_LINUX_BINDER_H */