Skip to contentSkip to conversation

i2c-tools: i2cbusses: Avoid buffer overflows in sysfs paths

PATCH Jean Delvare 2 participants 5 messages Oct 31, 2017 – Nov 8, 2017 I2C subsystem host drivers

lore
Share & tools
lore
#0 lore raw

sprintf isn't safe, use snprintf instead.

---
 tools/i2cbusses.c |   10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
tools/i2cbusses.c +5 -5
@@ -220,18 +220,18 @@ struct i2c_adap *gather_i2c_busses(void)
 
 		/* this should work for kernels 2.6.5 or higher and */
 		/* is preferred because is unambiguous */
-		sprintf(n, "%s/%s/name", sysfs, de->d_name);
+		snprintf(n, NAME_MAX, "%s/%s/name", sysfs, de->d_name);
 		f = fopen(n, "r");
 		/* this seems to work for ISA */
 		if(f == NULL) {
-			sprintf(n, "%s/%s/device/name", sysfs, de->d_name);
+			snprintf(n, NAME_MAX, "%s/%s/device/name", sysfs, de->d_name);
 			f = fopen(n, "r");
 		}
 		/* non-ISA is much harder */
 		/* and this won't find the correct bus name if a driver
 		   has more than one bus */
 		if(f == NULL) {
-			sprintf(n, "%s/%s/device", sysfs, de->d_name);
+			snprintf(n, NAME_MAX, "%s/%s/device", sysfs, de->d_name);
 			if(!(ddir = opendir(n)))
 				continue;
 			while ((dde = readdir(ddir)) != NULL) {
@@ -240,8 +240,8 @@ struct i2c_adap *gather_i2c_busses(void)
 				if (!strcmp(dde->d_name, ".."))
 					continue;
 				if ((!strncmp(dde->d_name, "i2c-", 4))) {
-					sprintf(n, "%s/%s/device/%s/name",
-						sysfs, de->d_name, dde->d_name);
+					snprintf(n, NAME_MAX, "%s/%s/device/%s/name",
+						 sysfs, de->d_name, dde->d_name);
 					if((f = fopen(n, "r")))
 						goto found;
 				}
Signature · 2 lines
Jean Delvare
SUSE L3 Support
#1 lore raw

Hello Jean,

tools/i2cbusses.c struct i2c_adap *gather_i2c_busses(void) · line 223

Quoted 13 lines · Jean Delvare

sprintf isn't safe, use snprintf instead.

---
 tools/i2cbusses.c |   10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
tools/i2cbusses.c +1 -1
@@ -220,18 +220,18 @@ struct i2c_adap *gather_i2c_busses(void)
 
 		/* this should work for kernels 2.6.5 or higher and */
 		/* is preferred because is unambiguous */
-		sprintf(n, "%s/%s/name", sysfs, de->d_name);
+		snprintf(n, NAME_MAX, "%s/%s/name", sysfs, de->d_name);

OK, now instead of running in a buffer overflow in sprintf you might call fopen with a partial (maybe unterminated?) filename. While this is definitively better, you should check the return value of snprintf to be completely safe here.

Best regards
Uwe

Signature · 2 lines
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
#2 lore raw

Hi Uwe,

Thanks for the review, very appreciated.

Quoted 12 lines · Uwe Kleine-König

Hello Jean,

Quoted 9 lines · Jean Delvare

sprintf isn't safe, use snprintf instead.

---
 tools/i2cbusses.c |   10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)
tools/i2cbusses.c +0 -0
@@ -220,18 +220,18 @@ struct i2c_adap *gather_i2c_busses(void)
 
Quoted 9 lines

tools/i2cbusses.c struct i2c_adap *gather_i2c_busses(void) · line 223

Quoted 4 lines
tools/i2cbusses.c +1 -1
 		/* this should work for kernels 2.6.5 or higher and */
 		/* is preferred because is unambiguous */
-		sprintf(n, "%s/%s/name", sysfs, de->d_name);
+		snprintf(n, NAME_MAX, "%s/%s/name", sysfs, de->d_name);  

OK, now instead of running in a buffer overflow in sprintf you might call fopen with a partial (maybe unterminated?) filename. While this is definitively better, you should check the return value of snprintf to be completely safe here.

To be honest, I never thought the buffer overflows could ever happen, my motivation to fix them was to allow the code to build in OBS, where FORTIFY_SOURCE is enabled. So I went for the most simple change that made gcc happy.

That being said, I have no problem additionally checking the value returned by snprintf. Something like this?

From: Jean Delvare
Subject: i2cbusses: Check the return value of snprintf

It's very unlikely that these paths will ever be truncated, but better safe than sorry.

---
 tools/i2cbusses.c |   34 ++++++++++++++++++++++++++++------
 1 file changed, 28 insertions(+), 6 deletions(-)
tools/i2cbusses.c +28 -6
@@ -137,7 +137,7 @@ struct i2c_adap *gather_i2c_busses(void)
 	FILE *f;
 	char fstype[NAME_MAX], sysfs[NAME_MAX], n[NAME_MAX];
 	int foundsysfs = 0;
-	int count=0;
+	int len, count = 0;
 	struct i2c_adap *adapters;
 
 	adapters = calloc(BUNCH, sizeof(struct i2c_adap));
@@ -220,18 +220,32 @@ struct i2c_adap *gather_i2c_busses(void)
 
 		/* this should work for kernels 2.6.5 or higher and */
 		/* is preferred because is unambiguous */
-		snprintf(n, NAME_MAX, "%s/%s/name", sysfs, de->d_name);
+		len = snprintf(n, NAME_MAX, "%s/%s/name", sysfs, de->d_name);
+		if (len >= NAME_MAX) {
+			fprintf(stderr, "%s: path truncated\n", n);
+			continue;
+		}
 		f = fopen(n, "r");
 		/* this seems to work for ISA */
 		if(f == NULL) {
-			snprintf(n, NAME_MAX, "%s/%s/device/name", sysfs, de->d_name);
+			len = snprintf(n, NAME_MAX, "%s/%s/device/name", sysfs,
+				       de->d_name);
+			if (len >= NAME_MAX) {
+				fprintf(stderr, "%s: path truncated\n", n);
+				continue;
+			}
 			f = fopen(n, "r");
 		}
 		/* non-ISA is much harder */
 		/* and this won't find the correct bus name if a driver
 		   has more than one bus */
 		if(f == NULL) {
-			snprintf(n, NAME_MAX, "%s/%s/device", sysfs, de->d_name);
+			len = snprintf(n, NAME_MAX, "%s/%s/device", sysfs,
+				       de->d_name);
+			if (len >= NAME_MAX) {
+				fprintf(stderr, "%s: path truncated\n", n);
+				continue;
+			}
 			if(!(ddir = opendir(n)))
 				continue;
 			while ((dde = readdir(ddir)) != NULL) {
@@ -240,8 +254,16 @@ struct i2c_adap *gather_i2c_busses(void)
 				if (!strcmp(dde->d_name, ".."))
 					continue;
 				if ((!strncmp(dde->d_name, "i2c-", 4))) {
-					snprintf(n, NAME_MAX, "%s/%s/device/%s/name",
-						 sysfs, de->d_name, dde->d_name);
+					len = snprintf(n, NAME_MAX,
+						       "%s/%s/device/%s/name",
+						       sysfs, de->d_name,
+						       dde->d_name);
+					if (len >= NAME_MAX) {
+						fprintf(stderr,
+							"%s: path truncated\n",
+							n);
+						continue;
+					}
 					if((f = fopen(n, "r")))
 						goto found;
 				}
Signature · 2 lines
Jean Delvare
SUSE L3 Support
#3 lore raw

Hello Jean,

Quoted 43 lines · Jean Delvare
Quoted 4 lines
Quoted 4 lines
tools/i2cbusses.c +0 -0
@@ -220,18 +220,18 @@ struct i2c_adap *gather_i2c_busses(void)
 
Quoted 9 lines

tools/i2cbusses.c struct i2c_adap *gather_i2c_busses(void)

Quoted 4 lines
tools/i2cbusses.c +1 -1
 		/* this should work for kernels 2.6.5 or higher and */
 		/* is preferred because is unambiguous */
-		sprintf(n, "%s/%s/name", sysfs, de->d_name);
+		snprintf(n, NAME_MAX, "%s/%s/name", sysfs, de->d_name);  

OK, now instead of running in a buffer overflow in sprintf you might call fopen with a partial (maybe unterminated?) filename. While this is definitively better, you should check the return value of snprintf to be completely safe here.

To be honest, I never thought the buffer overflows could ever happen, my motivation to fix them was to allow the code to build in OBS, where FORTIFY_SOURCE is enabled. So I went for the most simple change that made gcc happy.

That being said, I have no problem additionally checking the value returned by snprintf. Something like this?

From: Jean Delvare
Subject: i2cbusses: Check the return value of snprintf

It's very unlikely that these paths will ever be truncated, but better safe than sorry.

---
 tools/i2cbusses.c |   34 ++++++++++++++++++++++++++++------
 1 file changed, 28 insertions(+), 6 deletions(-)
tools/i2cbusses.c +1 -1
@@ -137,7 +137,7 @@ struct i2c_adap *gather_i2c_busses(void)
 	FILE *f;
 	char fstype[NAME_MAX], sysfs[NAME_MAX], n[NAME_MAX];
 	int foundsysfs = 0;
-	int count=0;
+	int len, count = 0;
 	struct i2c_adap *adapters;
 
 	adapters = calloc(BUNCH, sizeof(struct i2c_adap));

tools/i2cbusses.c struct i2c_adap *gather_i2c_busses(void) · line 227

Quoted 9 lines
tools/i2cbusses.c +5 -1
@@ -220,18 +220,32 @@ struct i2c_adap *gather_i2c_busses(void)
 
 		/* this should work for kernels 2.6.5 or higher and */
 		/* is preferred because is unambiguous */
-		snprintf(n, NAME_MAX, "%s/%s/name", sysfs, de->d_name);
+		len = snprintf(n, NAME_MAX, "%s/%s/name", sysfs, de->d_name);
+		if (len >= NAME_MAX) {
+			fprintf(stderr, "%s: path truncated\n", n);
+			continue;
+		}

According to C99 snprintf et al return "the number of characters which would have been written to the final string if enough space had been available". Up to glibc 2.0.6 -1 was returned though if the output was truncated. Does one still have to show consideration for a libc that old? Hmm, probably not.

Then your change looks fine.

Best regards
Uwe

Signature · 2 lines
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |
#4 lore raw
Quoted 47 lines · Uwe Kleine-König

Hello Jean,

Quoted 44 lines · Jean Delvare
Quoted 4 lines
Quoted 4 lines
tools/i2cbusses.c +0 -0
@@ -220,18 +220,18 @@ struct i2c_adap *gather_i2c_busses(void)
 
Quoted 9 lines

tools/i2cbusses.c struct i2c_adap *gather_i2c_busses(void)

Quoted 4 lines
tools/i2cbusses.c +1 -1
 		/* this should work for kernels 2.6.5 or higher and */
 		/* is preferred because is unambiguous */
-		sprintf(n, "%s/%s/name", sysfs, de->d_name);
+		snprintf(n, NAME_MAX, "%s/%s/name", sysfs, de->d_name);    

OK, now instead of running in a buffer overflow in sprintf you might call fopen with a partial (maybe unterminated?) filename. While this is definitively better, you should check the return value of snprintf to be completely safe here.

To be honest, I never thought the buffer overflows could ever happen, my motivation to fix them was to allow the code to build in OBS, where FORTIFY_SOURCE is enabled. So I went for the most simple change that made gcc happy.

That being said, I have no problem additionally checking the value returned by snprintf. Something like this?

From: Jean Delvare
Subject: i2cbusses: Check the return value of snprintf

It's very unlikely that these paths will ever be truncated, but better safe than sorry.

---
 tools/i2cbusses.c |   34 ++++++++++++++++++++++++++++------
 1 file changed, 28 insertions(+), 6 deletions(-)
tools/i2cbusses.c +1 -1
@@ -137,7 +137,7 @@ struct i2c_adap *gather_i2c_busses(void)
 	FILE *f;
 	char fstype[NAME_MAX], sysfs[NAME_MAX], n[NAME_MAX];
 	int foundsysfs = 0;
-	int count=0;
+	int len, count = 0;
 	struct i2c_adap *adapters;
 
 	adapters = calloc(BUNCH, sizeof(struct i2c_adap));
@@ -220,18 +220,32 @@ struct i2c_adap *gather_i2c_busses(void)
 
Quoted 14 lines

tools/i2cbusses.c struct i2c_adap *gather_i2c_busses(void) · line 227

Quoted 8 lines
tools/i2cbusses.c +5 -1
 		/* this should work for kernels 2.6.5 or higher and */
 		/* is preferred because is unambiguous */
-		snprintf(n, NAME_MAX, "%s/%s/name", sysfs, de->d_name);
+		len = snprintf(n, NAME_MAX, "%s/%s/name", sysfs, de->d_name);
+		if (len >= NAME_MAX) {
+			fprintf(stderr, "%s: path truncated\n", n);
+			continue;
+		}  

According to C99 snprintf et al return "the number of characters which would have been written to the final string if enough space had been available". Up to glibc 2.0.6 -1 was returned though if the output was truncated. Does one still have to show consideration for a libc that old? Hmm, probably not.

I think we don't care, especially when nothing terribly bad would happen then - simply back to the old behavior.

Quoted 1 line

Then your change looks fine.

Thanks for the review, I'll commit it.

Signature · 2 lines
Jean Delvare
SUSE L3 Support