diff mbox series

[RFC,2/3] i2c: dev: Handle 255 byte blocks for i2c ioctl

Message ID 20210602044113.1581347-3-matt@codeconstruct.com.au
State Accepted
Commit 84a107e68b34217eff536e81a6a6f419ee0d0f7e
Headers show
Series Increase SMBus max block size to 255 | expand

Commit Message

Matt Johnston June 2, 2021, 4:41 a.m. UTC
I2C_SMBUS is limited to 32 bytes due to compatibility with the
32 byte i2c_smbus_data.block

I2C_RDWR allows larger transfers if sufficient sized buffers are passed.

Signed-off-by: Matt Johnston <matt@codeconstruct.com.au>
---
 drivers/i2c/i2c-dev.c        | 92 ++++++++++++++++++++++++++++++------
 include/uapi/linux/i2c-dev.h |  2 +
 include/uapi/linux/i2c.h     |  2 +
 3 files changed, 82 insertions(+), 14 deletions(-)
diff mbox series

Patch

diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c
index 6ef38a8ee95c..b99780c2a55d 100644
--- a/drivers/i2c/i2c-dev.c
+++ b/drivers/i2c/i2c-dev.c
@@ -44,6 +44,24 @@  struct i2c_dev {
 	struct cdev cdev;
 };
 
+/* The userspace union i2c_smbus_data for I2C_SMBUS ioctl is limited
+ * to 32 bytes (I2C_SMBUS_BLOCK_MAX) for compatibility.
+ */
+union compat_i2c_smbus_data {
+	__u8 byte;
+	__u16 word;
+	__u8 block[I2C_SMBUS_BLOCK_MAX + 2]; /* block[0] is used for length */
+			       /* and one more for user-space compatibility */
+};
+
+/* Must match i2c-dev.h definition with compat .data member */
+struct i2c_smbus_ioctl_data {
+	__u8 read_write;
+	__u8 command;
+	__u32 size;
+	union compat_i2c_smbus_data __user *data;
+};
+
 #define I2C_MINORS	(MINORMASK + 1)
 static LIST_HEAD(i2c_dev_list);
 static DEFINE_SPINLOCK(i2c_dev_list_lock);
@@ -235,14 +253,17 @@  static int i2cdev_check_addr(struct i2c_adapter *adapter, unsigned int addr)
 static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
 		unsigned nmsgs, struct i2c_msg *msgs)
 {
-	u8 __user **data_ptrs;
+	u8 __user **data_ptrs = NULL;
+	u16 *orig_lens = NULL;
 	int i, res;
 
+	res = -ENOMEM;
 	data_ptrs = kmalloc_array(nmsgs, sizeof(u8 __user *), GFP_KERNEL);
-	if (data_ptrs == NULL) {
-		kfree(msgs);
-		return -ENOMEM;
-	}
+	if (data_ptrs == NULL)
+		goto out;
+	orig_lens = kmalloc_array(nmsgs, sizeof(u16), GFP_KERNEL);
+	if (orig_lens == NULL)
+		goto out;
 
 	res = 0;
 	for (i = 0; i < nmsgs; i++) {
@@ -253,12 +274,29 @@  static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
 		}
 
 		data_ptrs[i] = (u8 __user *)msgs[i].buf;
-		msgs[i].buf = memdup_user(data_ptrs[i], msgs[i].len);
+		msgs[i].buf = NULL;
+		if (msgs[i].len < 1) {
+			/* Sanity check */
+			res = -EINVAL;
+			break;
+
+		}
+		/* Allocate a larger buffer to accommodate possible 255 byte
+		 * blocks. Read results will be dropped later
+		 * if they are too large for the original length. */
+		orig_lens[i] = msgs[i].len;
+		msgs[i].buf = kmalloc(msgs[i].len + I2C_SMBUS_V3_BLOCK_MAX,
+			GFP_USER | __GFP_NOWARN);
 		if (IS_ERR(msgs[i].buf)) {
 			res = PTR_ERR(msgs[i].buf);
 			break;
 		}
-		/* memdup_user allocates with GFP_KERNEL, so DMA is ok */
+		if (copy_from_user(msgs[i].buf, data_ptrs[i], msgs[i].len)) {
+			kfree(msgs[i].buf);
+			res = -EFAULT;
+			break;
+		}
+		/* Buffer from kmalloc, so DMA is ok */
 		msgs[i].flags |= I2C_M_DMA_SAFE;
 
 		/*
@@ -274,7 +312,7 @@  static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
 		 */
 		if (msgs[i].flags & I2C_M_RECV_LEN) {
 			if (!(msgs[i].flags & I2C_M_RD) ||
-			    msgs[i].len < 1 || msgs[i].buf[0] < 1 ||
+			    msgs[i].buf[0] < 1 ||
 			    msgs[i].len < msgs[i].buf[0] +
 					     I2C_SMBUS_BLOCK_MAX) {
 				i++;
@@ -297,12 +335,16 @@  static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
 	res = i2c_transfer(client->adapter, msgs, nmsgs);
 	while (i-- > 0) {
 		if (res >= 0 && (msgs[i].flags & I2C_M_RD)) {
-			if (copy_to_user(data_ptrs[i], msgs[i].buf,
-					 msgs[i].len))
+			if (orig_lens[i] < msgs[i].len)
+				res = -EINVAL;
+			else if (copy_to_user(data_ptrs[i], msgs[i].buf,
+						 msgs[i].len))
 				res = -EFAULT;
 		}
 		kfree(msgs[i].buf);
 	}
+out:
+	kfree(orig_lens);
 	kfree(data_ptrs);
 	kfree(msgs);
 	return res;
@@ -310,7 +352,7 @@  static noinline int i2cdev_ioctl_rdwr(struct i2c_client *client,
 
 static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
 		u8 read_write, u8 command, u32 size,
-		union i2c_smbus_data __user *data)
+		union compat_i2c_smbus_data __user *data)
 {
 	union i2c_smbus_data temp = {};
 	int datasize, res;
@@ -371,6 +413,16 @@  static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
 		if (copy_from_user(&temp, data, datasize))
 			return -EFAULT;
 	}
+	if ((size == I2C_SMBUS_BLOCK_PROC_CALL ||
+	    size == I2C_SMBUS_I2C_BLOCK_DATA ||
+	    size == I2C_SMBUS_BLOCK_DATA) &&
+	    read_write == I2C_SMBUS_WRITE &&
+	    temp.block[0] > I2C_SMBUS_BLOCK_MAX) {
+		/* Don't accept writes larger than the buffer size */
+		dev_dbg(&client->adapter->dev, "block write is too large");
+		return -EINVAL;
+
+	}
 	if (size == I2C_SMBUS_I2C_BLOCK_BROKEN) {
 		/* Convert old I2C block commands to the new
 		   convention. This preserves binary compatibility. */
@@ -380,9 +432,21 @@  static noinline int i2cdev_ioctl_smbus(struct i2c_client *client,
 	}
 	res = i2c_smbus_xfer(client->adapter, client->addr, client->flags,
 	      read_write, command, size, &temp);
-	if (!res && ((size == I2C_SMBUS_PROC_CALL) ||
-		     (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
-		     (read_write == I2C_SMBUS_READ))) {
+	if (res)
+		return res;
+	if ((size == I2C_SMBUS_BLOCK_PROC_CALL ||
+	    size == I2C_SMBUS_I2C_BLOCK_DATA ||
+	    size == I2C_SMBUS_BLOCK_DATA) &&
+	    read_write == I2C_SMBUS_READ &&
+	    temp.block[0] > I2C_SMBUS_BLOCK_MAX) {
+		/* Don't accept reads larger than the buffer size */
+		dev_dbg(&client->adapter->dev, "block read is too large");
+		return -EINVAL;
+
+	}
+	if ((size == I2C_SMBUS_PROC_CALL) ||
+	    (size == I2C_SMBUS_BLOCK_PROC_CALL) ||
+	    (read_write == I2C_SMBUS_READ)) {
 		if (copy_to_user(data, &temp, datasize))
 			return -EFAULT;
 	}
diff --git a/include/uapi/linux/i2c-dev.h b/include/uapi/linux/i2c-dev.h
index 1c4cec4ddd84..46ce31d42f7d 100644
--- a/include/uapi/linux/i2c-dev.h
+++ b/include/uapi/linux/i2c-dev.h
@@ -39,12 +39,14 @@ 
 
 
 /* This is the structure as used in the I2C_SMBUS ioctl call */
+#ifndef __KERNEL__
 struct i2c_smbus_ioctl_data {
 	__u8 read_write;
 	__u8 command;
 	__u32 size;
 	union i2c_smbus_data __user *data;
 };
+#endif
 
 /* This is the structure as used in the I2C_RDWR ioctl call */
 struct i2c_rdwr_ioctl_data {
diff --git a/include/uapi/linux/i2c.h b/include/uapi/linux/i2c.h
index 7b7d90b50cf0..c3534ab1ae53 100644
--- a/include/uapi/linux/i2c.h
+++ b/include/uapi/linux/i2c.h
@@ -109,6 +109,8 @@  struct i2c_msg {
 #define I2C_FUNC_SMBUS_WRITE_I2C_BLOCK	0x08000000 /* w/ 1-byte reg. addr. */
 #define I2C_FUNC_SMBUS_HOST_NOTIFY	0x10000000 /* SMBus 2.0 or later */
 #define I2C_FUNC_SMBUS_V3_BLOCK		0x20000000 /* Device supports 255 byte block */
+						   /* Note that I2C_SMBUS ioctl only */
+						   /* supports a 32 byte block */
 
 #define I2C_FUNC_SMBUS_BYTE		(I2C_FUNC_SMBUS_READ_BYTE | \
 					 I2C_FUNC_SMBUS_WRITE_BYTE)