diff mbox

rtc: ds1307: long block operations bugfix

Message ID 8a33d5e0f1f90b42ccd14bcdf89d8348.squirrel@ssl0.ovh.net
State Accepted
Headers show

Commit Message

Bertrand Achard Nov. 12, 2012, 5:58 p.m. UTC
Hello,

The rtc-ds1307 driver does not properly handle block operations bigger
than 32 bytes in either of the two modes supported (SMbus native, or
emulated if not supported by the SMbus platform driver).

It also does not properly handle userland-supplied input (block operation
length) through sysfs and may suffer a type of buffer overrun.

The driver has been modified with proper input validation, buffer sizes,
and now splits block transfers bigger than 32 bytes into separate
transfers.

Explanation : Buffer size allocated is I2C_SMBUS_BLOCK_MAX which equals to
32 as per the SMbus spec. Reads and write may be up to 56 bytes (to the
NVRAM). This patch allocated a 255 byte buffer, the maximum allowable
(address is an u8).
It's not only a buffer problem, SMbus only supports up to 32 bytes
transfer at once, so it's needed to split bigger transfers.

Patch successfully tested on 3.2.27; cleanly applies on 3.7-rc4.

Signed-off-by: Bertrand Achard <ba@cykian.net>

---

@@ -208,7 +209,7 @@ static s32 ds1307_read_block_data(const
 static s32 ds1307_write_block_data(const struct i2c_client *client, u8
command,
 				   u8 length, const u8 *values)
 {
-	u8 currvalues[I2C_SMBUS_BLOCK_MAX];
+	u8 currvalues[255];
 	int tries = 0;

 	dev_dbg(&client->dev, "ds1307_write_block_data (length=%d)\n", length);
@@ -236,6 +237,57 @@ static s32 ds1307_write_block_data(const

 /*----------------------------------------------------------------------*/

+/* These RTC devices are not designed to be connected to a SMbus adapter.
+   SMbus limits block operations length to 32 bytes, whereas it's not
+   limited on I2C buses. As a result, accesses may exceed 32 bytes;
+   in that case, split them into smaller blocks */
+
+static s32 ds1307_native_smbus_write_block_data(const struct i2c_client
*client,
+				u8 command, u8 length, const u8 *values)
+{
+	if (length <= I2C_SMBUS_BLOCK_MAX) {
+		return i2c_smbus_write_i2c_block_data(client,
+					command, length, values);
+	} else {
+		u8 suboffset = 0;
+		while (suboffset < length) {
+			s32 retval = i2c_smbus_write_i2c_block_data(client,
+						command+suboffset,
+						min(I2C_SMBUS_BLOCK_MAX, length-suboffset),
+						values+suboffset);
+			if (retval < 0)
+				return retval;
+
+			suboffset += I2C_SMBUS_BLOCK_MAX;
+		}
+		return length;
+	}
+}
+
+static s32 ds1307_native_smbus_read_block_data(const struct i2c_client
*client,
+				u8 command, u8 length, u8 *values)
+{
+	if (length <= I2C_SMBUS_BLOCK_MAX) {
+		return i2c_smbus_read_i2c_block_data(client,
+					command, length, values);
+	} else {
+		u8 suboffset = 0;
+		while (suboffset < length) {
+			s32 retval = i2c_smbus_read_i2c_block_data(client,
+						command+suboffset,
+						min(I2C_SMBUS_BLOCK_MAX, length-suboffset),
+						values+suboffset);
+			if (retval < 0)
+				return retval;
+
+			suboffset += I2C_SMBUS_BLOCK_MAX;
+		}
+		return length;
+	}
+}
+
+/*----------------------------------------------------------------------*/
+
 /*
  * The IRQ logic includes a "real" handler running in IRQ context just
  * long enough to schedule this workqueue entry.   We need a task context
@@ -641,8 +693,8 @@ static int __devinit ds1307_probe(struct

 	buf = ds1307->regs;
 	if (i2c_check_functionality(adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) {
-		ds1307->read_block_data = i2c_smbus_read_i2c_block_data;
-		ds1307->write_block_data = i2c_smbus_write_i2c_block_data;
+		ds1307->read_block_data = ds1307_native_smbus_read_block_data;
+		ds1307->write_block_data = ds1307_native_smbus_write_block_data;
 	} else {
 		ds1307->read_block_data = ds1307_read_block_data;
 		ds1307->write_block_data = ds1307_write_block_data;

Comments

Andrew Morton Dec. 19, 2012, 12:17 a.m. UTC | #1
On Mon, 12 Nov 2012 18:58:58 +0100
"Bertrand Achard" <ba@cykian.net> wrote:

> Hello,
> 
> The rtc-ds1307 driver does not properly handle block operations bigger
> than 32 bytes in either of the two modes supported (SMbus native, or
> emulated if not supported by the SMbus platform driver).
> 
> It also does not properly handle userland-supplied input (block operation
> length) through sysfs and may suffer a type of buffer overrun.
> 
> The driver has been modified with proper input validation, buffer sizes,
> and now splits block transfers bigger than 32 bytes into separate
> transfers.
> 
> Explanation : Buffer size allocated is I2C_SMBUS_BLOCK_MAX which equals to
> 32 as per the SMbus spec. Reads and write may be up to 56 bytes (to the
> NVRAM). This patch allocated a 255 byte buffer, the maximum allowable
> (address is an u8).
> It's not only a buffer problem, SMbus only supports up to 32 bytes
> transfer at once, so it's needed to split bigger transfers.
> 
> Patch successfully tested on 3.2.27; cleanly applies on 3.7-rc4.
> 
> ....
>
> @@ -182,7 +183,7 @@ static s32 ds1307_read_block_data_once(c
>  static s32 ds1307_read_block_data(const struct i2c_client *client, u8
> command,

The patch is wordwrapped - please fix your email client for next time.

>  {
> -	u8 oldvalues[I2C_SMBUS_BLOCK_MAX];
> +	u8 oldvalues[255];

255 bytes is rather a lot of stack.  Is it really necessary?  From your
description it sounds like we can get by with 56 bytes here?
diff mbox

Patch

--- linux/drivers/rtc/rtc-ds1307.c.orig	2012-11-11 02:03:14.960707406 +0000
+++ linux/drivers/rtc/rtc-ds1307.c	2012-11-11 20:38:49.648717002 +0000
@@ -4,6 +4,7 @@ 
  *  Copyright (C) 2005 James Chapman (ds1337 core)
  *  Copyright (C) 2006 David Brownell
  *  Copyright (C) 2009 Matthias Fuchs (rx8025 support)
+ *  Copyright (C) 2012 Bertrand Achard (nvram access fixes)
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License version 2 as
@@ -182,7 +183,7 @@  static s32 ds1307_read_block_data_once(c
 static s32 ds1307_read_block_data(const struct i2c_client *client, u8
command,
 				  u8 length, u8 *values)
 {
-	u8 oldvalues[I2C_SMBUS_BLOCK_MAX];
+	u8 oldvalues[255];
 	s32 ret;
 	int tries = 0;