diff options
Diffstat (limited to 'drivers/android/binder.c')
| -rw-r--r-- | drivers/android/binder.c | 437 | 
1 files changed, 379 insertions, 58 deletions
| diff --git a/drivers/android/binder.c b/drivers/android/binder.c index c75fb600740c..8351c5638880 100644 --- a/drivers/android/binder.c +++ b/drivers/android/binder.c @@ -69,7 +69,7 @@  #include <uapi/linux/android/binder.h> -#include <asm/cacheflush.h> +#include <linux/cacheflush.h>  #include "binder_internal.h"  #include "binder_trace.h" @@ -1608,15 +1608,21 @@ static void binder_cleanup_transaction(struct binder_transaction *t,  /**   * binder_get_object() - gets object and checks for valid metadata   * @proc:	binder_proc owning the buffer + * @u:		sender's user pointer to base of buffer   * @buffer:	binder_buffer that we're parsing.   * @offset:	offset in the @buffer at which to validate an object.   * @object:	struct binder_object to read into   * - * Return:	If there's a valid metadata object at @offset in @buffer, the + * Copy the binder object at the given offset into @object. If @u is + * provided then the copy is from the sender's buffer. If not, then + * it is copied from the target's @buffer. + * + * Return:	If there's a valid metadata object at @offset, the   *		size of that object. Otherwise, it returns zero. The object   *		is read into the struct binder_object pointed to by @object.   */  static size_t binder_get_object(struct binder_proc *proc, +				const void __user *u,  				struct binder_buffer *buffer,  				unsigned long offset,  				struct binder_object *object) @@ -1626,10 +1632,16 @@ static size_t binder_get_object(struct binder_proc *proc,  	size_t object_size = 0;  	read_size = min_t(size_t, sizeof(*object), buffer->data_size - offset); -	if (offset > buffer->data_size || read_size < sizeof(*hdr) || -	    binder_alloc_copy_from_buffer(&proc->alloc, object, buffer, -					  offset, read_size)) +	if (offset > buffer->data_size || read_size < sizeof(*hdr))  		return 0; +	if (u) { +		if (copy_from_user(object, u + offset, read_size)) +			return 0; +	} else { +		if (binder_alloc_copy_from_buffer(&proc->alloc, object, buffer, +						  offset, read_size)) +			return 0; +	}  	/* Ok, now see if we read a complete object. */  	hdr = &object->hdr; @@ -1702,7 +1714,7 @@ static struct binder_buffer_object *binder_validate_ptr(  					  b, buffer_offset,  					  sizeof(object_offset)))  		return NULL; -	object_size = binder_get_object(proc, b, object_offset, object); +	object_size = binder_get_object(proc, NULL, b, object_offset, object);  	if (!object_size || object->hdr.type != BINDER_TYPE_PTR)  		return NULL;  	if (object_offsetp) @@ -1767,7 +1779,8 @@ static bool binder_validate_fixup(struct binder_proc *proc,  		unsigned long buffer_offset;  		struct binder_object last_object;  		struct binder_buffer_object *last_bbo; -		size_t object_size = binder_get_object(proc, b, last_obj_offset, +		size_t object_size = binder_get_object(proc, NULL, b, +						       last_obj_offset,  						       &last_object);  		if (object_size != sizeof(*last_bbo))  			return false; @@ -1882,7 +1895,7 @@ static void binder_transaction_buffer_release(struct binder_proc *proc,  		if (!binder_alloc_copy_from_buffer(&proc->alloc, &object_offset,  						   buffer, buffer_offset,  						   sizeof(object_offset))) -			object_size = binder_get_object(proc, buffer, +			object_size = binder_get_object(proc, NULL, buffer,  							object_offset, &object);  		if (object_size == 0) {  			pr_err("transaction release %d bad object at offset %lld, size %zd\n", @@ -1933,7 +1946,7 @@ static void binder_transaction_buffer_release(struct binder_proc *proc,  		case BINDER_TYPE_FD: {  			/*  			 * No need to close the file here since user-space -			 * closes it for for successfully delivered +			 * closes it for successfully delivered  			 * transactions. For transactions that weren't  			 * delivered, the new fd was never allocated so  			 * there is no need to close and the fput on the @@ -2220,16 +2233,258 @@ err_fd_not_accepted:  	return ret;  } -static int binder_translate_fd_array(struct binder_fd_array_object *fda, +/** + * struct binder_ptr_fixup - data to be fixed-up in target buffer + * @offset	offset in target buffer to fixup + * @skip_size	bytes to skip in copy (fixup will be written later) + * @fixup_data	data to write at fixup offset + * @node	list node + * + * This is used for the pointer fixup list (pf) which is created and consumed + * during binder_transaction() and is only accessed locally. No + * locking is necessary. + * + * The list is ordered by @offset. + */ +struct binder_ptr_fixup { +	binder_size_t offset; +	size_t skip_size; +	binder_uintptr_t fixup_data; +	struct list_head node; +}; + +/** + * struct binder_sg_copy - scatter-gather data to be copied + * @offset		offset in target buffer + * @sender_uaddr	user address in source buffer + * @length		bytes to copy + * @node		list node + * + * This is used for the sg copy list (sgc) which is created and consumed + * during binder_transaction() and is only accessed locally. No + * locking is necessary. + * + * The list is ordered by @offset. + */ +struct binder_sg_copy { +	binder_size_t offset; +	const void __user *sender_uaddr; +	size_t length; +	struct list_head node; +}; + +/** + * binder_do_deferred_txn_copies() - copy and fixup scatter-gather data + * @alloc:	binder_alloc associated with @buffer + * @buffer:	binder buffer in target process + * @sgc_head:	list_head of scatter-gather copy list + * @pf_head:	list_head of pointer fixup list + * + * Processes all elements of @sgc_head, applying fixups from @pf_head + * and copying the scatter-gather data from the source process' user + * buffer to the target's buffer. It is expected that the list creation + * and processing all occurs during binder_transaction() so these lists + * are only accessed in local context. + * + * Return: 0=success, else -errno + */ +static int binder_do_deferred_txn_copies(struct binder_alloc *alloc, +					 struct binder_buffer *buffer, +					 struct list_head *sgc_head, +					 struct list_head *pf_head) +{ +	int ret = 0; +	struct binder_sg_copy *sgc, *tmpsgc; +	struct binder_ptr_fixup *pf = +		list_first_entry_or_null(pf_head, struct binder_ptr_fixup, +					 node); + +	list_for_each_entry_safe(sgc, tmpsgc, sgc_head, node) { +		size_t bytes_copied = 0; + +		while (bytes_copied < sgc->length) { +			size_t copy_size; +			size_t bytes_left = sgc->length - bytes_copied; +			size_t offset = sgc->offset + bytes_copied; + +			/* +			 * We copy up to the fixup (pointed to by pf) +			 */ +			copy_size = pf ? min(bytes_left, (size_t)pf->offset - offset) +				       : bytes_left; +			if (!ret && copy_size) +				ret = binder_alloc_copy_user_to_buffer( +						alloc, buffer, +						offset, +						sgc->sender_uaddr + bytes_copied, +						copy_size); +			bytes_copied += copy_size; +			if (copy_size != bytes_left) { +				BUG_ON(!pf); +				/* we stopped at a fixup offset */ +				if (pf->skip_size) { +					/* +					 * we are just skipping. This is for +					 * BINDER_TYPE_FDA where the translated +					 * fds will be fixed up when we get +					 * to target context. +					 */ +					bytes_copied += pf->skip_size; +				} else { +					/* apply the fixup indicated by pf */ +					if (!ret) +						ret = binder_alloc_copy_to_buffer( +							alloc, buffer, +							pf->offset, +							&pf->fixup_data, +							sizeof(pf->fixup_data)); +					bytes_copied += sizeof(pf->fixup_data); +				} +				list_del(&pf->node); +				kfree(pf); +				pf = list_first_entry_or_null(pf_head, +						struct binder_ptr_fixup, node); +			} +		} +		list_del(&sgc->node); +		kfree(sgc); +	} +	BUG_ON(!list_empty(pf_head)); +	BUG_ON(!list_empty(sgc_head)); + +	return ret > 0 ? -EINVAL : ret; +} + +/** + * binder_cleanup_deferred_txn_lists() - free specified lists + * @sgc_head:	list_head of scatter-gather copy list + * @pf_head:	list_head of pointer fixup list + * + * Called to clean up @sgc_head and @pf_head if there is an + * error. + */ +static void binder_cleanup_deferred_txn_lists(struct list_head *sgc_head, +					      struct list_head *pf_head) +{ +	struct binder_sg_copy *sgc, *tmpsgc; +	struct binder_ptr_fixup *pf, *tmppf; + +	list_for_each_entry_safe(sgc, tmpsgc, sgc_head, node) { +		list_del(&sgc->node); +		kfree(sgc); +	} +	list_for_each_entry_safe(pf, tmppf, pf_head, node) { +		list_del(&pf->node); +		kfree(pf); +	} +} + +/** + * binder_defer_copy() - queue a scatter-gather buffer for copy + * @sgc_head:		list_head of scatter-gather copy list + * @offset:		binder buffer offset in target process + * @sender_uaddr:	user address in source process + * @length:		bytes to copy + * + * Specify a scatter-gather block to be copied. The actual copy must + * be deferred until all the needed fixups are identified and queued. + * Then the copy and fixups are done together so un-translated values + * from the source are never visible in the target buffer. + * + * We are guaranteed that repeated calls to this function will have + * monotonically increasing @offset values so the list will naturally + * be ordered. + * + * Return: 0=success, else -errno + */ +static int binder_defer_copy(struct list_head *sgc_head, binder_size_t offset, +			     const void __user *sender_uaddr, size_t length) +{ +	struct binder_sg_copy *bc = kzalloc(sizeof(*bc), GFP_KERNEL); + +	if (!bc) +		return -ENOMEM; + +	bc->offset = offset; +	bc->sender_uaddr = sender_uaddr; +	bc->length = length; +	INIT_LIST_HEAD(&bc->node); + +	/* +	 * We are guaranteed that the deferred copies are in-order +	 * so just add to the tail. +	 */ +	list_add_tail(&bc->node, sgc_head); + +	return 0; +} + +/** + * binder_add_fixup() - queue a fixup to be applied to sg copy + * @pf_head:	list_head of binder ptr fixup list + * @offset:	binder buffer offset in target process + * @fixup:	bytes to be copied for fixup + * @skip_size:	bytes to skip when copying (fixup will be applied later) + * + * Add the specified fixup to a list ordered by @offset. When copying + * the scatter-gather buffers, the fixup will be copied instead of + * data from the source buffer. For BINDER_TYPE_FDA fixups, the fixup + * will be applied later (in target process context), so we just skip + * the bytes specified by @skip_size. If @skip_size is 0, we copy the + * value in @fixup. + * + * This function is called *mostly* in @offset order, but there are + * exceptions. Since out-of-order inserts are relatively uncommon, + * we insert the new element by searching backward from the tail of + * the list. + * + * Return: 0=success, else -errno + */ +static int binder_add_fixup(struct list_head *pf_head, binder_size_t offset, +			    binder_uintptr_t fixup, size_t skip_size) +{ +	struct binder_ptr_fixup *pf = kzalloc(sizeof(*pf), GFP_KERNEL); +	struct binder_ptr_fixup *tmppf; + +	if (!pf) +		return -ENOMEM; + +	pf->offset = offset; +	pf->fixup_data = fixup; +	pf->skip_size = skip_size; +	INIT_LIST_HEAD(&pf->node); + +	/* Fixups are *mostly* added in-order, but there are some +	 * exceptions. Look backwards through list for insertion point. +	 */ +	list_for_each_entry_reverse(tmppf, pf_head, node) { +		if (tmppf->offset < pf->offset) { +			list_add(&pf->node, &tmppf->node); +			return 0; +		} +	} +	/* +	 * if we get here, then the new offset is the lowest so +	 * insert at the head +	 */ +	list_add(&pf->node, pf_head); +	return 0; +} + +static int binder_translate_fd_array(struct list_head *pf_head, +				     struct binder_fd_array_object *fda, +				     const void __user *sender_ubuffer,  				     struct binder_buffer_object *parent, +				     struct binder_buffer_object *sender_uparent,  				     struct binder_transaction *t,  				     struct binder_thread *thread,  				     struct binder_transaction *in_reply_to)  {  	binder_size_t fdi, fd_buf_size;  	binder_size_t fda_offset; +	const void __user *sender_ufda_base;  	struct binder_proc *proc = thread->proc; -	struct binder_proc *target_proc = t->to_proc; +	int ret;  	fd_buf_size = sizeof(u32) * fda->num_fds;  	if (fda->num_fds >= SIZE_MAX / sizeof(u32)) { @@ -2253,29 +2508,36 @@ static int binder_translate_fd_array(struct binder_fd_array_object *fda,  	 */  	fda_offset = (parent->buffer - (uintptr_t)t->buffer->user_data) +  		fda->parent_offset; -	if (!IS_ALIGNED((unsigned long)fda_offset, sizeof(u32))) { +	sender_ufda_base = (void __user *)(uintptr_t)sender_uparent->buffer + +				fda->parent_offset; + +	if (!IS_ALIGNED((unsigned long)fda_offset, sizeof(u32)) || +	    !IS_ALIGNED((unsigned long)sender_ufda_base, sizeof(u32))) {  		binder_user_error("%d:%d parent offset not aligned correctly.\n",  				  proc->pid, thread->pid);  		return -EINVAL;  	} +	ret = binder_add_fixup(pf_head, fda_offset, 0, fda->num_fds * sizeof(u32)); +	if (ret) +		return ret; +  	for (fdi = 0; fdi < fda->num_fds; fdi++) {  		u32 fd; -		int ret;  		binder_size_t offset = fda_offset + fdi * sizeof(fd); +		binder_size_t sender_uoffset = fdi * sizeof(fd); -		ret = binder_alloc_copy_from_buffer(&target_proc->alloc, -						    &fd, t->buffer, -						    offset, sizeof(fd)); +		ret = copy_from_user(&fd, sender_ufda_base + sender_uoffset, sizeof(fd));  		if (!ret)  			ret = binder_translate_fd(fd, offset, t, thread,  						  in_reply_to); -		if (ret < 0) -			return ret; +		if (ret) +			return ret > 0 ? -EINVAL : ret;  	}  	return 0;  } -static int binder_fixup_parent(struct binder_transaction *t, +static int binder_fixup_parent(struct list_head *pf_head, +			       struct binder_transaction *t,  			       struct binder_thread *thread,  			       struct binder_buffer_object *bp,  			       binder_size_t off_start_offset, @@ -2321,14 +2583,7 @@ static int binder_fixup_parent(struct binder_transaction *t,  	}  	buffer_offset = bp->parent_offset +  			(uintptr_t)parent->buffer - (uintptr_t)b->user_data; -	if (binder_alloc_copy_to_buffer(&target_proc->alloc, b, buffer_offset, -					&bp->buffer, sizeof(bp->buffer))) { -		binder_user_error("%d:%d got transaction with invalid parent offset\n", -				  proc->pid, thread->pid); -		return -EINVAL; -	} - -	return 0; +	return binder_add_fixup(pf_head, buffer_offset, bp->buffer, 0);  }  /** @@ -2455,6 +2710,7 @@ static void binder_transaction(struct binder_proc *proc,  	binder_size_t off_start_offset, off_end_offset;  	binder_size_t off_min;  	binder_size_t sg_buf_offset, sg_buf_end_offset; +	binder_size_t user_offset = 0;  	struct binder_proc *target_proc = NULL;  	struct binder_thread *target_thread = NULL;  	struct binder_node *target_node = NULL; @@ -2469,6 +2725,12 @@ static void binder_transaction(struct binder_proc *proc,  	int t_debug_id = atomic_inc_return(&binder_last_id);  	char *secctx = NULL;  	u32 secctx_sz = 0; +	struct list_head sgc_head; +	struct list_head pf_head; +	const void __user *user_buffer = (const void __user *) +				(uintptr_t)tr->data.ptr.buffer; +	INIT_LIST_HEAD(&sgc_head); +	INIT_LIST_HEAD(&pf_head);  	e = binder_transaction_log_add(&binder_transaction_log);  	e->debug_id = t_debug_id; @@ -2782,19 +3044,6 @@ static void binder_transaction(struct binder_proc *proc,  	if (binder_alloc_copy_user_to_buffer(  				&target_proc->alloc, -				t->buffer, 0, -				(const void __user *) -					(uintptr_t)tr->data.ptr.buffer, -				tr->data_size)) { -		binder_user_error("%d:%d got transaction with invalid data ptr\n", -				proc->pid, thread->pid); -		return_error = BR_FAILED_REPLY; -		return_error_param = -EFAULT; -		return_error_line = __LINE__; -		goto err_copy_data_failed; -	} -	if (binder_alloc_copy_user_to_buffer( -				&target_proc->alloc,  				t->buffer,  				ALIGN(tr->data_size, sizeof(void *)),  				(const void __user *) @@ -2837,6 +3086,7 @@ static void binder_transaction(struct binder_proc *proc,  		size_t object_size;  		struct binder_object object;  		binder_size_t object_offset; +		binder_size_t copy_size;  		if (binder_alloc_copy_from_buffer(&target_proc->alloc,  						  &object_offset, @@ -2848,8 +3098,27 @@ static void binder_transaction(struct binder_proc *proc,  			return_error_line = __LINE__;  			goto err_bad_offset;  		} -		object_size = binder_get_object(target_proc, t->buffer, -						object_offset, &object); + +		/* +		 * Copy the source user buffer up to the next object +		 * that will be processed. +		 */ +		copy_size = object_offset - user_offset; +		if (copy_size && (user_offset > object_offset || +				binder_alloc_copy_user_to_buffer( +					&target_proc->alloc, +					t->buffer, user_offset, +					user_buffer + user_offset, +					copy_size))) { +			binder_user_error("%d:%d got transaction with invalid data ptr\n", +					proc->pid, thread->pid); +			return_error = BR_FAILED_REPLY; +			return_error_param = -EFAULT; +			return_error_line = __LINE__; +			goto err_copy_data_failed; +		} +		object_size = binder_get_object(target_proc, user_buffer, +				t->buffer, object_offset, &object);  		if (object_size == 0 || object_offset < off_min) {  			binder_user_error("%d:%d got transaction with invalid offset (%lld, min %lld max %lld) or object.\n",  					  proc->pid, thread->pid, @@ -2861,6 +3130,11 @@ static void binder_transaction(struct binder_proc *proc,  			return_error_line = __LINE__;  			goto err_bad_offset;  		} +		/* +		 * Set offset to the next buffer fragment to be +		 * copied +		 */ +		user_offset = object_offset + object_size;  		hdr = &object.hdr;  		off_min = object_offset + object_size; @@ -2923,6 +3197,8 @@ static void binder_transaction(struct binder_proc *proc,  		case BINDER_TYPE_FDA: {  			struct binder_object ptr_object;  			binder_size_t parent_offset; +			struct binder_object user_object; +			size_t user_parent_size;  			struct binder_fd_array_object *fda =  				to_binder_fd_array_object(hdr);  			size_t num_valid = (buffer_offset - off_start_offset) / @@ -2954,11 +3230,35 @@ static void binder_transaction(struct binder_proc *proc,  				return_error_line = __LINE__;  				goto err_bad_parent;  			} -			ret = binder_translate_fd_array(fda, parent, t, thread, -							in_reply_to); -			if (ret < 0) { +			/* +			 * We need to read the user version of the parent +			 * object to get the original user offset +			 */ +			user_parent_size = +				binder_get_object(proc, user_buffer, t->buffer, +						  parent_offset, &user_object); +			if (user_parent_size != sizeof(user_object.bbo)) { +				binder_user_error("%d:%d invalid ptr object size: %zd vs %zd\n", +						  proc->pid, thread->pid, +						  user_parent_size, +						  sizeof(user_object.bbo));  				return_error = BR_FAILED_REPLY; -				return_error_param = ret; +				return_error_param = -EINVAL; +				return_error_line = __LINE__; +				goto err_bad_parent; +			} +			ret = binder_translate_fd_array(&pf_head, fda, +							user_buffer, parent, +							&user_object.bbo, t, +							thread, in_reply_to); +			if (!ret) +				ret = binder_alloc_copy_to_buffer(&target_proc->alloc, +								  t->buffer, +								  object_offset, +								  fda, sizeof(*fda)); +			if (ret) { +				return_error = BR_FAILED_REPLY; +				return_error_param = ret > 0 ? -EINVAL : ret;  				return_error_line = __LINE__;  				goto err_translate_failed;  			} @@ -2980,19 +3280,14 @@ static void binder_transaction(struct binder_proc *proc,  				return_error_line = __LINE__;  				goto err_bad_offset;  			} -			if (binder_alloc_copy_user_to_buffer( -						&target_proc->alloc, -						t->buffer, -						sg_buf_offset, -						(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_param = -EFAULT; +			ret = binder_defer_copy(&sgc_head, sg_buf_offset, +				(const void __user *)(uintptr_t)bp->buffer, +				bp->length); +			if (ret) {  				return_error = BR_FAILED_REPLY; +				return_error_param = ret;  				return_error_line = __LINE__; -				goto err_copy_data_failed; +				goto err_translate_failed;  			}  			/* Fixup buffer pointer to target proc address space */  			bp->buffer = (uintptr_t) @@ -3001,7 +3296,8 @@ static void binder_transaction(struct binder_proc *proc,  			num_valid = (buffer_offset - off_start_offset) /  					sizeof(binder_size_t); -			ret = binder_fixup_parent(t, thread, bp, +			ret = binder_fixup_parent(&pf_head, t, +						  thread, bp,  						  off_start_offset,  						  num_valid,  						  last_fixup_obj_off, @@ -3028,6 +3324,30 @@ static void binder_transaction(struct binder_proc *proc,  			goto err_bad_object_type;  		}  	} +	/* Done processing objects, copy the rest of the buffer */ +	if (binder_alloc_copy_user_to_buffer( +				&target_proc->alloc, +				t->buffer, user_offset, +				user_buffer + user_offset, +				tr->data_size - user_offset)) { +		binder_user_error("%d:%d got transaction with invalid data ptr\n", +				proc->pid, thread->pid); +		return_error = BR_FAILED_REPLY; +		return_error_param = -EFAULT; +		return_error_line = __LINE__; +		goto err_copy_data_failed; +	} + +	ret = binder_do_deferred_txn_copies(&target_proc->alloc, t->buffer, +					    &sgc_head, &pf_head); +	if (ret) { +		binder_user_error("%d:%d got transaction with invalid offsets ptr\n", +				  proc->pid, thread->pid); +		return_error = BR_FAILED_REPLY; +		return_error_param = ret; +		return_error_line = __LINE__; +		goto err_copy_data_failed; +	}  	if (t->buffer->oneway_spam_suspect)  		tcomplete->type = BINDER_WORK_TRANSACTION_ONEWAY_SPAM_SUSPECT;  	else @@ -3101,6 +3421,7 @@ err_bad_object_type:  err_bad_offset:  err_bad_parent:  err_copy_data_failed: +	binder_cleanup_deferred_txn_lists(&sgc_head, &pf_head);  	binder_free_txn_fixups(t);  	trace_binder_transaction_failed_buffer_release(t->buffer);  	binder_transaction_buffer_release(target_proc, NULL, t->buffer, |