summaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorMatt Johnston <matt@codeconstruct.com.au>2021-11-15 03:49:22 +0100
committerDavid S. Miller <davem@davemloft.net>2021-11-15 15:11:24 +0100
commit84a107e68b34217eff536e81a6a6f419ee0d0f7e (patch)
tree3beaf1ebf78e6a4330445b81b4dba7b78dc8b453 /drivers
parenti2c: core: Allow 255 byte transfers for SMBus 3.x (diff)
downloadlinux-84a107e68b34217eff536e81a6a6f419ee0d0f7e.tar.xz
linux-84a107e68b34217eff536e81a6a6f419ee0d0f7e.zip
i2c: dev: Handle 255 byte blocks for i2c ioctl
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> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/i2c/i2c-dev.c93
1 files changed, 79 insertions, 14 deletions
diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c
index bce0e8bb7852..5ee9118c0407 100644
--- a/drivers/i2c/i2c-dev.c
+++ b/drivers/i2c/i2c-dev.c
@@ -46,6 +46,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,30 @@ 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 +313,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 +336,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 +353,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 +414,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 +433,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;
}