binder: Add support for scatter-gather

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.

Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Martijn Coenen <maco@google.com>
Cc: Arve Hjønnevåg <arve@android.com>
Cc: Amit Pundir <amit.pundir@linaro.org>
Cc: Serban Constantinescu <serban.constantinescu@arm.com>
Cc: Dmitry Shmidt <dimitrysh@google.com>
Cc: Rom Lemarchand <romlem@google.com>
Cc: Android Kernel Team <kernel-team@android.com>
Signed-off-by: Martijn Coenen <maco@google.com>
[jstultz: Fold in small fix from Amit Pundir <amit.pundir@linaro.org>]
Signed-off-by: John Stultz <john.stultz@linaro.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Martijn Coenen 2017-02-03 14:40:51 -08:00 committed by Greg Kroah-Hartman
parent 4bfac80af3
commit 7980240b6d
2 changed files with 275 additions and 12 deletions

View File

@ -152,6 +152,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,
@ -165,7 +168,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];
};
@ -1304,6 +1307,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;
}
@ -1314,11 +1320,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,
@ -1329,13 +1435,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);
@ -1391,7 +1497,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);
@ -1561,6 +1672,53 @@ static int binder_translate_fd(int fd,
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,
@ -1569,8 +1727,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;
@ -1579,6 +1738,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);
@ -1753,8 +1914,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)) {
@ -1776,7 +1938,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,
(u64)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;
@ -1829,7 +2000,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);
@ -2083,6 +2288,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;
@ -3609,7 +3825,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[] = {

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