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:
parent
4bfac80af3
commit
7980240b6d
|
@ -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 @@ 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,
|
||||
|
@ -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[] = {
|
||||
|
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in New Issue