From patchwork Wed Jun 2 04:41:12 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matt Johnston X-Patchwork-Id: 452824 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 93234C47083 for ; Wed, 2 Jun 2021 04:42:13 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 7431B610E7 for ; Wed, 2 Jun 2021 04:42:13 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229571AbhFBEnz (ORCPT ); Wed, 2 Jun 2021 00:43:55 -0400 Received: from pi.codeconstruct.com.au ([203.29.241.158]:46388 "EHLO codeconstruct.com.au" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229653AbhFBEny (ORCPT ); Wed, 2 Jun 2021 00:43:54 -0400 Received: by codeconstruct.com.au (Postfix, from userid 10001) id B5C19219D8; Wed, 2 Jun 2021 12:42:02 +0800 (AWST) From: Matt Johnston To: linux-i2c@vger.kernel.org, matt@codeconstruct.com.au Cc: linux-aspeed@lists.ozlabs.org, jk@codeconstruct.com.au Subject: [RFC PATCH 2/3] i2c: dev: Handle 255 byte blocks for i2c ioctl Date: Wed, 2 Jun 2021 12:41:12 +0800 Message-Id: <20210602044113.1581347-3-matt@codeconstruct.com.au> X-Mailer: git-send-email 2.30.2 In-Reply-To: <20210602044113.1581347-1-matt@codeconstruct.com.au> References: <20210602044113.1581347-1-matt@codeconstruct.com.au> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-i2c@vger.kernel.org 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 --- 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 --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)