diff mbox series

staging: vchiq: Avoid mixing bulk_userdata kernel and userspace pointer

Message ID 20240730170840.1603752-1-umang.jain@ideasonboard.com (mailing list archive)
State New, archived
Headers show
Series staging: vchiq: Avoid mixing bulk_userdata kernel and userspace pointer | expand

Commit Message

Umang Jain July 30, 2024, 5:08 p.m. UTC
In vchiq_dev.c, there are two places where the __user bulk_userdata
pointer to set to a kernel-space pointer which then gives relevant
Sparse warnings as below:

vchiq_dev.c:328:26: warning: incorrect type in assignment (different address spaces)
vchiq_dev.c:328:26:    expected void *[assigned] userdata
vchiq_dev.c:328:26:    got void [noderef] __user *userdata
vchiq_dev.c:543:47: warning: incorrect type in assignment (different address spaces)
vchiq_dev.c:543:47:    expected void [noderef] __user *[addressable] [assigned] bulk_userdata
vchiq_dev.c:543:47:    got void *bulk_userdata

This is solved by adding additional functional argument to track the
userspace bulk_userdata separately and passing it accordingly to
completion handlers.

This patch is inspired by commit
1c954540c0eb ("staging: vchiq: avoid mixing kernel and user pointers").

There are no Sparse warnings left to be fixed in vc04_services,
hence drop the relevant TODO entry as well.

Signed-off-by: Umang Jain <umang.jain@ideasonboard.com>
---
 .../bcm2835-audio/bcm2835-vchiq.c             |  3 ++-
 .../include/linux/raspberrypi/vchiq.h         |  7 +++--
 drivers/staging/vc04_services/interface/TODO  |  4 ---
 .../interface/vchiq_arm/vchiq_arm.c           | 26 +++++++++++--------
 .../interface/vchiq_arm/vchiq_arm.h           |  3 ++-
 .../interface/vchiq_arm/vchiq_core.c          | 21 ++++++++-------
 .../interface/vchiq_arm/vchiq_core.h          |  5 ++--
 .../interface/vchiq_arm/vchiq_dev.c           | 10 ++-----
 .../vc04_services/vchiq-mmal/mmal-vchiq.c     |  2 +-
 9 files changed, 42 insertions(+), 39 deletions(-)

Comments

Stefan Wahren July 30, 2024, 6:05 p.m. UTC | #1
Hi Umang,

Am 30.07.24 um 19:08 schrieb Umang Jain:
> In vchiq_dev.c, there are two places where the __user bulk_userdata
> pointer to set to a kernel-space pointer which then gives relevant
> Sparse warnings as below:
>
> vchiq_dev.c:328:26: warning: incorrect type in assignment (different address spaces)
> vchiq_dev.c:328:26:    expected void *[assigned] userdata
> vchiq_dev.c:328:26:    got void [noderef] __user *userdata
> vchiq_dev.c:543:47: warning: incorrect type in assignment (different address spaces)
> vchiq_dev.c:543:47:    expected void [noderef] __user *[addressable] [assigned] bulk_userdata
> vchiq_dev.c:543:47:    got void *bulk_userdata
>
> This is solved by adding additional functional argument to track the
> userspace bulk_userdata separately and passing it accordingly to
> completion handlers.
IMO this patch fixes the issue for spare, but don't address the
confusing member naming for humans. It's not clear that "userdata" is a
kernel pointer and "uuserdata" is a pointer to userspace. It would be
nice to avoid the word "user" for kernel pointer in this case.

Thanks
Arnd Bergmann July 30, 2024, 9:28 p.m. UTC | #2
On Tue, Jul 30, 2024, at 20:05, Stefan Wahren wrote:
> Hi Umang,
>
> Am 30.07.24 um 19:08 schrieb Umang Jain:
>> In vchiq_dev.c, there are two places where the __user bulk_userdata
>> pointer to set to a kernel-space pointer which then gives relevant
>> Sparse warnings as below:
>>
>> vchiq_dev.c:328:26: warning: incorrect type in assignment (different address spaces)
>> vchiq_dev.c:328:26:    expected void *[assigned] userdata
>> vchiq_dev.c:328:26:    got void [noderef] __user *userdata
>> vchiq_dev.c:543:47: warning: incorrect type in assignment (different address spaces)
>> vchiq_dev.c:543:47:    expected void [noderef] __user *[addressable] [assigned] bulk_userdata
>> vchiq_dev.c:543:47:    got void *bulk_userdata
>>
>> This is solved by adding additional functional argument to track the
>> userspace bulk_userdata separately and passing it accordingly to
>> completion handlers.
> IMO this patch fixes the issue for spare, but don't address the
> confusing member naming for humans. It's not clear that "userdata" is a
> kernel pointer and "uuserdata" is a pointer to userspace. It would be
> nice to avoid the word "user" for kernel pointer in this case.

Right, also you need to provide a much better explanation about
how the code is meant to work, and what this opaque pointer
is meant to do.

Ideally this should be cleaned up in a way that completely
avoids passing both user and kernel data at the same time.

A small step would be to separate out the "struct bulk_waiter
*bulk_waiter" argument and make that typesafe.

You can also wrap vchiq_bulk_transfer() in order to have
four separate functions based on the different 'mode'
values and have them only take the arguments they actually
need.

    Arnd
diff mbox series

Patch

diff --git a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c
index 133ed15f3dbc..c44f3d5cca70 100644
--- a/drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c
+++ b/drivers/staging/vc04_services/bcm2835-audio/bcm2835-vchiq.c
@@ -96,7 +96,8 @@  static int bcm2835_audio_send_simple(struct bcm2835_audio_instance *instance,
 static int audio_vchi_callback(struct vchiq_instance *vchiq_instance,
 			       enum vchiq_reason reason,
 			       struct vchiq_header *header,
-			       unsigned int handle, void *userdata)
+			       unsigned int handle, void *userdata,
+			       void __user *uuserdata)
 {
 	struct bcm2835_audio_instance *instance = vchiq_get_service_userdata(vchiq_instance,
 									     handle);
diff --git a/drivers/staging/vc04_services/include/linux/raspberrypi/vchiq.h b/drivers/staging/vc04_services/include/linux/raspberrypi/vchiq.h
index 6c40d8c1dde6..c777952dd9d9 100644
--- a/drivers/staging/vc04_services/include/linux/raspberrypi/vchiq.h
+++ b/drivers/staging/vc04_services/include/linux/raspberrypi/vchiq.h
@@ -56,7 +56,8 @@  struct vchiq_service_base {
 			enum vchiq_reason reason,
 			struct vchiq_header *header,
 			unsigned int handle,
-			void *bulk_userdata);
+			void *bulk_userdata,
+			void __user *ubulk_userdata);
 	void *userdata;
 };
 
@@ -65,6 +66,7 @@  struct vchiq_completion_data_kernel {
 	struct vchiq_header *header;
 	void *service_userdata;
 	void *bulk_userdata;
+	void __user *ubulk_userdata;
 };
 
 struct vchiq_service_params_kernel {
@@ -73,7 +75,8 @@  struct vchiq_service_params_kernel {
 			enum vchiq_reason reason,
 			struct vchiq_header *header,
 			unsigned int handle,
-			void *bulk_userdata);
+			void *bulk_userdata,
+			void __user *ubulk_userdata);
 	void *userdata;
 	short version;       /* Increment for non-trivial changes */
 	short version_min;   /* Update for incompatible changes */
diff --git a/drivers/staging/vc04_services/interface/TODO b/drivers/staging/vc04_services/interface/TODO
index dfb1ee49633f..2ae75362421b 100644
--- a/drivers/staging/vc04_services/interface/TODO
+++ b/drivers/staging/vc04_services/interface/TODO
@@ -27,10 +27,6 @@  The code follows the 80 characters limitation yet tends to go 3 or 4 levels of
 indentation deep making it very unpleasant to read. This is specially relevant
 in the character driver ioctl code and in the core thread functions.
 
-* Clean up Sparse warnings from __user annotations. See
-vchiq_irq_queue_bulk_tx_rx(). Ensure that the address of "&waiter->bulk_waiter"
-is never disclosed to userspace.
-
 * Fix behavior of message handling
 
 The polling behavior of vchiq_bulk_transmit(), vchiq_bulk_receive() and
diff --git a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
index c4d97dbf6ba8..fae939f35642 100644
--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
+++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.c
@@ -859,7 +859,7 @@  vchiq_bulk_transmit(struct vchiq_instance *instance, unsigned int handle, const
 		case VCHIQ_BULK_MODE_CALLBACK:
 			ret = vchiq_bulk_transfer(instance, handle,
 						  (void *)data, NULL,
-						  size, userdata, mode,
+						  size, userdata, NULL, mode,
 						  VCHIQ_BULK_TRANSMIT);
 			break;
 		case VCHIQ_BULK_MODE_BLOCKING:
@@ -896,7 +896,7 @@  int vchiq_bulk_receive(struct vchiq_instance *instance, unsigned int handle,
 		case VCHIQ_BULK_MODE_NOCALLBACK:
 		case VCHIQ_BULK_MODE_CALLBACK:
 			ret = vchiq_bulk_transfer(instance, handle, data, NULL,
-						  size, userdata,
+						  size, userdata, NULL,
 						  mode, VCHIQ_BULK_RECEIVE);
 			break;
 		case VCHIQ_BULK_MODE_BLOCKING:
@@ -969,7 +969,7 @@  vchiq_blocking_bulk_transfer(struct vchiq_instance *instance, unsigned int handl
 	}
 
 	ret = vchiq_bulk_transfer(instance, handle, data, NULL, size,
-				  &waiter->bulk_waiter,
+				  &waiter->bulk_waiter, NULL,
 				  VCHIQ_BULK_MODE_BLOCKING, dir);
 	if ((ret != -EAGAIN) || fatal_signal_pending(current) || !waiter->bulk_waiter.bulk) {
 		struct vchiq_bulk *bulk = waiter->bulk_waiter.bulk;
@@ -996,7 +996,7 @@  vchiq_blocking_bulk_transfer(struct vchiq_instance *instance, unsigned int handl
 static int
 add_completion(struct vchiq_instance *instance, enum vchiq_reason reason,
 	       struct vchiq_header *header, struct user_service *user_service,
-	       void *bulk_userdata)
+	       void *bulk_userdata, void __user *ubulk_userdata)
 {
 	struct vchiq_completion_data_kernel *completion;
 	struct vchiq_drv_mgmt *mgmt = dev_get_drvdata(instance->state->dev);
@@ -1027,6 +1027,7 @@  add_completion(struct vchiq_instance *instance, enum vchiq_reason reason,
 	/* N.B. service_userdata is updated while processing AWAIT_COMPLETION */
 	completion->service_userdata = user_service->service;
 	completion->bulk_userdata = bulk_userdata;
+	completion->ubulk_userdata = ubulk_userdata;
 
 	if (reason == VCHIQ_SERVICE_CLOSED) {
 		/*
@@ -1058,7 +1059,8 @@  add_completion(struct vchiq_instance *instance, enum vchiq_reason reason,
 static int
 service_single_message(struct vchiq_instance *instance,
 		       enum vchiq_reason reason,
-		       struct vchiq_service *service, void *bulk_userdata)
+		       struct vchiq_service *service,
+		       void *bulk_userdata, void __user *ubulk_userdata)
 {
 	struct user_service *user_service;
 
@@ -1076,7 +1078,7 @@  service_single_message(struct vchiq_instance *instance,
 		dev_dbg(instance->state->dev,
 			"arm: Inserting extra MESSAGE_AVAILABLE\n");
 		ret = add_completion(instance, reason, NULL, user_service,
-				     bulk_userdata);
+				     bulk_userdata, ubulk_userdata);
 		if (ret)
 			return ret;
 	}
@@ -1094,7 +1096,8 @@  service_single_message(struct vchiq_instance *instance,
 
 int
 service_callback(struct vchiq_instance *instance, enum vchiq_reason reason,
-		 struct vchiq_header *header, unsigned int handle, void *bulk_userdata)
+		 struct vchiq_header *header, unsigned int handle,
+		 void *bulk_userdata, void __user *ubulk_userdata)
 {
 	/*
 	 * How do we ensure the callback goes to the right client?
@@ -1147,8 +1150,8 @@  service_callback(struct vchiq_instance *instance, enum vchiq_reason reason,
 			DEBUG_TRACE(SERVICE_CALLBACK_LINE);
 			DEBUG_COUNT(MSG_QUEUE_FULL_COUNT);
 
-			ret = service_single_message(instance, reason,
-						     service, bulk_userdata);
+			ret = service_single_message(instance, reason, service,
+						     bulk_userdata, ubulk_userdata);
 			if (ret) {
 				DEBUG_TRACE(SERVICE_CALLBACK_LINE);
 				vchiq_service_put(service);
@@ -1186,7 +1189,7 @@  service_callback(struct vchiq_instance *instance, enum vchiq_reason reason,
 		return 0;
 
 	return add_completion(instance, reason, header, user_service,
-		bulk_userdata);
+		bulk_userdata, ubulk_userdata);
 }
 
 void vchiq_dump_platform_instances(struct vchiq_state *state, struct seq_file *f)
@@ -1273,7 +1276,8 @@  static int
 vchiq_keepalive_vchiq_callback(struct vchiq_instance *instance,
 			       enum vchiq_reason reason,
 			       struct vchiq_header *header,
-			       unsigned int service_user, void *bulk_user)
+			       unsigned int service_user,
+			       void *bulk_userdata, void __user *ubulk_userdata)
 {
 	dev_err(instance->state->dev, "suspend: %s: callback reason %d\n",
 		__func__, reason);
diff --git a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.h b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.h
index b402aac333d9..43c73e986779 100644
--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.h
+++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_arm.h
@@ -155,7 +155,8 @@  static inline int vchiq_register_chrdev(struct device *parent) { return 0; }
 
 extern int
 service_callback(struct vchiq_instance *vchiq_instance, enum vchiq_reason reason,
-		 struct vchiq_header *header, unsigned int handle, void *bulk_userdata);
+		 struct vchiq_header *header, unsigned int handle,
+		 void *bulk_userdata, void __user *ubulk_userdata);
 
 extern void
 free_bulk_waiter(struct vchiq_instance *instance);
diff --git a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_core.c b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_core.c
index 50af04b217f4..b24a27a46074 100644
--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_core.c
+++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_core.c
@@ -449,7 +449,8 @@  mark_service_closing(struct vchiq_service *service)
 
 static inline int
 make_service_callback(struct vchiq_service *service, enum vchiq_reason reason,
-		      struct vchiq_header *header, void *bulk_userdata)
+		      struct vchiq_header *header,
+		      void *bulk_userdata, void __user *ubulk_userdata)
 {
 	int status;
 
@@ -457,7 +458,7 @@  make_service_callback(struct vchiq_service *service, enum vchiq_reason reason,
 		service->state->id, service->localport, reason_names[reason],
 		header, bulk_userdata);
 	status = service->base.callback(service->instance, reason, header, service->handle,
-					bulk_userdata);
+					bulk_userdata, ubulk_userdata);
 	if (status && (status != -EAGAIN)) {
 		dev_warn(service->state->dev,
 			 "core: %d: ignoring ERROR from callback to service %x\n",
@@ -1339,7 +1340,7 @@  notify_bulks(struct vchiq_service *service, struct vchiq_bulk_queue *queue,
 				enum vchiq_reason reason =
 						get_bulk_reason(bulk);
 				status = make_service_callback(service, reason,	NULL,
-							       bulk->userdata);
+							       bulk->userdata, bulk->uuserdata);
 				if (status == -EAGAIN)
 					break;
 			}
@@ -1689,7 +1690,7 @@  parse_message(struct vchiq_state *state, struct vchiq_header *header)
 			claim_slot(state->rx_info);
 			DEBUG_TRACE(PARSE_LINE);
 			if (make_service_callback(service, VCHIQ_MESSAGE_AVAILABLE, header,
-						  NULL) == -EAGAIN) {
+						  NULL, NULL) == -EAGAIN) {
 				DEBUG_TRACE(PARSE_LINE);
 				goto bail_not_ready;
 			}
@@ -2072,7 +2073,7 @@  sync_func(void *v)
 			if ((service->remoteport == remoteport) &&
 			    (service->srvstate == VCHIQ_SRVSTATE_OPENSYNC)) {
 				if (make_service_callback(service, VCHIQ_MESSAGE_AVAILABLE, header,
-							  NULL) == -EAGAIN)
+							  NULL, NULL) == -EAGAIN)
 					dev_err(state->dev,
 						"sync: error: synchronous callback to service %d returns -EAGAIN\n",
 						localport);
@@ -2624,7 +2625,7 @@  close_service_complete(struct vchiq_service *service, int failstate)
 		return -EINVAL;
 	}
 
-	status = make_service_callback(service, VCHIQ_SERVICE_CLOSED, NULL, NULL);
+	status = make_service_callback(service, VCHIQ_SERVICE_CLOSED, NULL, NULL, NULL);
 
 	if (status != -EAGAIN) {
 		int uc = service->service_use_count;
@@ -2987,7 +2988,8 @@  vchiq_remove_service(struct vchiq_instance *instance, unsigned int handle)
  * structure.
  */
 int vchiq_bulk_transfer(struct vchiq_instance *instance, unsigned int handle,
-			void *offset, void __user *uoffset, int size, void *userdata,
+			void *offset, void __user *uoffset, int size,
+			void *userdata, void __user *uuserdata,
 			enum vchiq_bulk_mode mode, enum vchiq_bulk_dir dir)
 {
 	struct vchiq_service *service = find_service_by_handle(instance, handle);
@@ -3062,6 +3064,7 @@  int vchiq_bulk_transfer(struct vchiq_instance *instance, unsigned int handle,
 	bulk->mode = mode;
 	bulk->dir = dir;
 	bulk->userdata = userdata;
+	bulk->uuserdata = uuserdata;
 	bulk->size = size;
 	bulk->actual = VCHIQ_BULK_ACTUAL_ABORTED;
 
@@ -3074,9 +3077,9 @@  int vchiq_bulk_transfer(struct vchiq_instance *instance, unsigned int handle,
 	 */
 	wmb();
 
-	dev_dbg(state->dev, "core: %d: bt (%d->%d) %cx %x@%pad %pK\n",
+	dev_dbg(state->dev, "core: %d: bt (%d->%d) %cx %x@%pad %pK %p\n",
 		state->id, service->localport, service->remoteport,
-		dir_char, size, &bulk->data, userdata);
+		dir_char, size, &bulk->data, userdata, uuserdata);
 
 	/*
 	 * The slot mutex must be held when the service is being closed, so
diff --git a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_core.h b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_core.h
index 77cc4d7ac077..6d915aeeae7f 100644
--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_core.h
+++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_core.h
@@ -114,6 +114,7 @@  struct vchiq_bulk {
 	short mode;
 	short dir;
 	void *userdata;
+	void __user *uuserdata;
 	dma_addr_t data;
 	int size;
 	void *remote_data;
@@ -472,8 +473,8 @@  remote_event_pollall(struct vchiq_state *state);
 
 extern int
 vchiq_bulk_transfer(struct vchiq_instance *instance, unsigned int handle, void *offset,
-		    void __user *uoffset, int size, void *userdata, enum vchiq_bulk_mode mode,
-		    enum vchiq_bulk_dir dir);
+		    void __user *uoffset, int size, void *userdata, void __user *uuserdata,
+		    enum vchiq_bulk_mode mode, enum vchiq_bulk_dir dir);
 
 extern void
 vchiq_dump_state(struct seq_file *f, struct vchiq_state *state);
diff --git a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_dev.c b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_dev.c
index 9cd2a64dce5e..3bb45342e89e 100644
--- a/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_dev.c
+++ b/drivers/staging/vc04_services/interface/vchiq_arm/vchiq_dev.c
@@ -324,12 +324,10 @@  static int vchiq_irq_queue_bulk_tx_rx(struct vchiq_instance *instance,
 		dev_dbg(service->state->dev, "arm: found bulk_waiter %pK for pid %d\n",
 			waiter, current->pid);
 		userdata = &waiter->bulk_waiter;
-	} else {
-		userdata = args->userdata;
 	}
 
 	status = vchiq_bulk_transfer(instance, args->handle, NULL, args->data, args->size,
-				     userdata, args->mode, dir);
+				     userdata, args->userdata, args->mode, dir);
 
 	if (!waiter) {
 		ret = 0;
@@ -536,11 +534,7 @@  static int vchiq_ioc_await_completion(struct vchiq_instance *instance,
 		    !instance->use_close_delivered)
 			vchiq_service_put(service);
 
-		/*
-		 * FIXME: address space mismatch, does bulk_userdata
-		 * actually point to user or kernel memory?
-		 */
-		user_completion.bulk_userdata = completion->bulk_userdata;
+		user_completion.bulk_userdata = completion->ubulk_userdata;
 
 		if (vchiq_put_completion(args->buf, &user_completion, ret)) {
 			if (ret == 0)
diff --git a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c
index 67489c334f7b..24777e570ad5 100644
--- a/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c
+++ b/drivers/staging/vc04_services/vchiq-mmal/mmal-vchiq.c
@@ -551,7 +551,7 @@  static void bulk_abort_cb(struct vchiq_mmal_instance *instance,
 /* incoming event service callback */
 static int mmal_service_callback(struct vchiq_instance *vchiq_instance,
 				 enum vchiq_reason reason, struct vchiq_header *header,
-				 unsigned int handle, void *bulk_ctx)
+				 unsigned int handle, void *bulk_ctx, void __user *bulk_uctx)
 {
 	struct vchiq_mmal_instance *instance = vchiq_get_service_userdata(vchiq_instance, handle);
 	u32 msg_len;