summaryrefslogtreecommitdiff
path: root/drivers/i2c/i2c-dev.c
diff options
context:
space:
mode:
authorDavid S. Miller <davem@davemloft.net>2021-11-15 14:11:25 +0000
committerDavid S. Miller <davem@davemloft.net>2021-11-15 14:11:25 +0000
commit71812af7234f30362b43ccff33f93890ae4c0655 (patch)
tree92a7bce858d15b20b9c7b7fff83fca3fe3a225ef /drivers/i2c/i2c-dev.c
parentcc0be1ad686fb29a4d127948486f40b17fb34b50 (diff)
parent80be9b2c0d93dabe5dc0b4ee41da12422ed25d71 (diff)
downloadlinux-71812af7234f30362b43ccff33f93890ae4c0655.tar.gz
linux-71812af7234f30362b43ccff33f93890ae4c0655.tar.bz2
linux-71812af7234f30362b43ccff33f93890ae4c0655.zip
Merge branch 'mctp-i2c-driver'
Matt Johnston says: ==================== MCTP I2C driver This patch series adds a netdev driver providing MCTP transport over I2C. It applies against net-next using recent MCTP changes there, though also has I2C core changes for review. I'll leave it to maintainers where it should be applied - please let me know if it needs to be submitted differently. The I2C patches were previously sent as RFC though the only feedback there was an ack to 255 bytes for aspeed. The dt-bindings patch went through review on the list. ==================== Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers/i2c/i2c-dev.c')
-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;
}