ChangeSet 1.1123.18.2, 2003/08/11 14:36:29-07:00, khali@linux-fr.org [PATCH] i2c: user/kernel bug and memory leak in i2c-dev drivers/i2c/i2c-dev.c | 41 ++++++++++++++++++++++++++--------------- 1 files changed, 26 insertions(+), 15 deletions(-) diff -Nru a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c --- a/drivers/i2c/i2c-dev.c Fri Aug 15 11:27:09 2003 +++ b/drivers/i2c/i2c-dev.c Fri Aug 15 11:27:09 2003 @@ -181,6 +181,7 @@ struct i2c_smbus_ioctl_data data_arg; union i2c_smbus_data temp; struct i2c_msg *rdwr_pa; + u8 **data_ptrs; int i,datasize,res; unsigned long funcs; @@ -214,7 +215,7 @@ return (copy_to_user((unsigned long __user *)arg, &funcs, sizeof(unsigned long)))?-EFAULT:0; - case I2C_RDWR: + case I2C_RDWR: if (copy_from_user(&rdwr_arg, (struct i2c_rdwr_ioctl_data __user *)arg, sizeof(rdwr_arg))) @@ -231,28 +232,37 @@ if (rdwr_pa == NULL) return -ENOMEM; + if (copy_from_user(rdwr_pa, rdwr_arg.msgs, + rdwr_arg.nmsgs * sizeof(struct i2c_msg))) { + kfree(rdwr_pa); + return -EFAULT; + } + + data_ptrs = (u8 **) kmalloc(rdwr_arg.nmsgs * sizeof(u8 *), + GFP_KERNEL); + if (data_ptrs == NULL) { + kfree(rdwr_pa); + return -ENOMEM; + } + res = 0; for( i=0; i 8192) { res = -EINVAL; break; } + data_ptrs[i] = rdwr_pa[i].buf; rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL); if(rdwr_pa[i].buf == NULL) { res = -ENOMEM; break; } if(copy_from_user(rdwr_pa[i].buf, - rdwr_arg.msgs[i].buf, + data_ptrs[i], rdwr_pa[i].len)) { - res = -EFAULT; + ++i; /* Needs to be kfreed too */ + res = -EFAULT; break; } } @@ -260,18 +270,18 @@ int j; for (j = 0; j < i; ++j) kfree(rdwr_pa[j].buf); + kfree(data_ptrs); kfree(rdwr_pa); return res; } - if (!res) { - res = i2c_transfer(client->adapter, - rdwr_pa, - rdwr_arg.nmsgs); - } + + res = i2c_transfer(client->adapter, + rdwr_pa, + rdwr_arg.nmsgs); while(i-- > 0) { if( res>=0 && (rdwr_pa[i].flags & I2C_M_RD)) { if(copy_to_user( - rdwr_arg.msgs[i].buf, + data_ptrs[i], rdwr_pa[i].buf, rdwr_pa[i].len)) { res = -EFAULT; @@ -279,6 +289,7 @@ } kfree(rdwr_pa[i].buf); } + kfree(data_ptrs); kfree(rdwr_pa); return res;