Skip to contentSkip to conversation

firmware: dmi: Respect buffer size in get_modalias

PATCH Superseded Cyrill Gorcunov 2 participants 5 messages Feb 20, 2025 – Mar 29, 2025 Dmi/smbios support

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 firmware: dmi: Respect buffer size in get_modalias

When we collect data from DMI data the result is accumulated either in a page buffer from sysfs entry or from uevent environment buffer. Both are big enough (4K and 2K) and it is hard to imagine that we overflow 4K page with the data, still the situation is different for uevent buffer where buffer may be already filled with data and we possibly may overflow it.

Thus lets respect buffer size given by a caller and never write data unconditionally.

---
 drivers/firmware/dmi-id.c |   12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
drivers/firmware/dmi-id.c +8 -4
Index: linux-tip.git/drivers/firmware/dmi-id.c
===================================================================
@@ -103,8 +103,12 @@ static ssize_t get_modalias(char *buffer
 	char *p;
 	const struct mafield *f;
 
-	strcpy(buffer, "dmi");
-	p = buffer + 3; left = buffer_size - 4;
+	l = strscpy(buffer, "dmi", buffer_size);
+	if (l < 0)
+		return 0;
+	p = buffer + l; left = buffer_size - l - 1;
+	if (left < 0)
+		return 0;
 
 	for (f = fields; f->prefix && left > 0; f++) {
 		const char *c;
@@ -135,7 +139,7 @@ static ssize_t sys_dmi_modalias_show(str
 				     struct device_attribute *attr, char *page)
 {
 	ssize_t r;
-	r = get_modalias(page, PAGE_SIZE-1);
+	r = get_modalias(page, PAGE_SIZE-2);
 	page[r] = '\n';
 	page[r+1] = 0;
 	return r+1;
@@ -163,7 +167,7 @@ static int dmi_dev_uevent(const struct d
 		return -ENOMEM;
 	len = get_modalias(&env->buf[env->buflen - 1],
 			   sizeof(env->buf) - env->buflen);
-	if (len >= (sizeof(env->buf) - env->buflen))
+	if (!len || len >= (sizeof(env->buf) - env->buflen))
 		return -ENOMEM;
 	env->buflen += len;
 	return 0;
#1 lore raw
Quoted 7 lines · Cyrill Gorcunov

When we collect data from DMI data the result is accumulated either in a page buffer from sysfs entry or from uevent environment buffer. Both are big enough (4K and 2K) and it is hard to imagine that we overflow 4K page with the data, still the situation is different for uevent buffer where buffer may be already filled with data and we possibly may overflow it.

Quoted 5 lines

Thus lets respect buffer size given by a caller and never write data unconditionally.

Ping?

#2 lore raw
In reply to Cyrill Gorcunov

Hi Cyrill,

Quoted 6 lines · Cyrill Gorcunov

When we collect data from DMI data the result is accumulated either in a page buffer from sysfs entry or from uevent environment buffer. Both are big enough (4K and 2K) and it is hard to imagine that we overflow 4K page with the data, still the situation is different for uevent buffer where buffer may be already filled with data and we possibly may overflow it.

Would it not be a concern if that ever happened?

Quoted 2 lines

Thus lets respect buffer size given by a caller and never write data unconditionally.

On the principle I agree. On the implementation, not quite so.

Quoted 11 lines
---
 drivers/firmware/dmi-id.c |   12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

Index: linux-tip.git/drivers/firmware/dmi-id.c
===================================================================

drivers/firmware/dmi-id.c static ssize_t get_modalias(char *buffer · line 108

Quoted 8 lines
drivers/firmware/dmi-id.c +3 -2
@@ -103,8 +103,12 @@ static ssize_t get_modalias(char *buffer
 	char *p;
 	const struct mafield *f;
 
-	strcpy(buffer, "dmi");
-	p = buffer + 3; left = buffer_size - 4;
+	l = strscpy(buffer, "dmi", buffer_size);
+	if (l < 0)
+		return 0;

If function get_modalias() now has a return convention, it should be documented. But I see a problem which is that the return convention isn't clear. It *may* now return 0 on buffer overrun, but not necessarily. The rest of the function is best-effort mode and will silently drop a part of the modalias string if it doesn't fit. This is not consistent.

This is not caused by your patch, but this could actually cause false positive matches, so it may be a good idea to fix it while you're here. And in my opinion the best thing to do is to return an error rather than an half-baked modalias string. If the string doesn't fit as a whole, it's going to cause trouble at some point anyway, so we better learn about it early and do something about it. And that would be consistent.

I'm also curious why you chose to return 0 on error, rather than the more conventional -1 or -ENOMEM?

drivers/firmware/dmi-id.c static ssize_t get_modalias(char *buffer · line 109

Quoted 1 line
drivers/firmware/dmi-id.c +1 -0
+	p = buffer + l; left = buffer_size - l - 1;

Please split on separate lines for readability. I would also appreciate a comment explaining that the "- 1" is to leave room for the trailing ":" (if I understand that right).

drivers/firmware/dmi-id.c static ssize_t sys_dmi_modalias_show(str · line 142

Quoted 11 lines
drivers/firmware/dmi-id.c +3 -1
+	if (left < 0)
+		return 0;
 
 	for (f = fields; f->prefix && left > 0; f++) {
 		const char *c;
@@ -135,7 +139,7 @@ static ssize_t sys_dmi_modalias_show(str
 				     struct device_attribute *attr, char *page)
 {
 	ssize_t r;
-	r = get_modalias(page, PAGE_SIZE-1);
+	r = get_modalias(page, PAGE_SIZE-2);

Why? As I read the code, get_modalias() returns the length of the string, excluding the trailing '\0'. So it will be, at most, one less than the buffer size we passed. So if we pass PAGE_SIZE-1, r is at most PAGE_SIZE-2, which leaves exactly the 2 bytes we need for the two lines of code below. Am I missing something?

(The last line of get_modalias would probably be clearer as:

	return (p + 1) - buffer;

or if p was increased beforehand to actually point to the end of the string.)

drivers/firmware/dmi-id.c static int dmi_dev_uevent(const struct d · line 171

Quoted 10 lines
drivers/firmware/dmi-id.c +1 -1
 	page[r] = '\n';
 	page[r+1] = 0;
 	return r+1;
@@ -163,7 +167,7 @@ static int dmi_dev_uevent(const struct d
 		return -ENOMEM;
 	len = get_modalias(&env->buf[env->buflen - 1],
 			   sizeof(env->buf) - env->buflen);
-	if (len >= (sizeof(env->buf) - env->buflen))
+	if (!len || len >= (sizeof(env->buf) - env->buflen))
 		return -ENOMEM;

I do not like the fact that we check whether get_modalias() returns an error here, and not in sys_dmi_modalias_show(). This is inconsistent. IMHO both functions should check the return value and return an error code on failure.

As a side note, I can't see how the second condition could be true. We pass the buffer size to get_modalias() exactly to make sure that it won't write past the buffer's end.

drivers/firmware/dmi-id.c static int dmi_dev_uevent(const struct d · line 173

Quoted 2 lines
drivers/firmware/dmi-id.c +0 -0
 	env->buflen += len;
 	return 0;

Thanks,

Signature · 2 lines
Jean Delvare
SUSE L3 Support
#3 lore raw
Quoted 11 lines · Jean Delvare

Hi Cyrill,

Quoted 6 lines · Cyrill Gorcunov

When we collect data from DMI data the result is accumulated either in a page buffer from sysfs entry or from uevent environment buffer. Both are big enough (4K and 2K) and it is hard to imagine that we overflow 4K page with the data, still the situation is different for uevent buffer where buffer may be already filled with data and we possibly may overflow it.

Would it not be a concern if that ever happened?

Sure it would. Still at the moment a "page" for sysfs buffer is rather hardcoded and a huge amount of other code relies on this size, some brave soul need to spend vast slab of time to review each sysfs writer. Actually I would not touch this code if get_modalias been used by sysfs only.

Quoted 4 lines
Quoted 2 lines

Thus lets respect buffer size given by a caller and never write data unconditionally.

On the principle I agree. On the implementation, not quite so.

...

Quoted 19 lines

drivers/firmware/dmi-id.c static ssize_t get_modalias(char *buffer · line 108

Quoted 11 lines
drivers/firmware/dmi-id.c +3 -2
@@ -103,8 +103,12 @@ static ssize_t get_modalias(char *buffer
 	char *p;
 	const struct mafield *f;
 
-	strcpy(buffer, "dmi");
-	p = buffer + 3; left = buffer_size - 4;
+	l = strscpy(buffer, "dmi", buffer_size);
+	if (l < 0)
+		return 0;

If function get_modalias() now has a return convention, it should be documented. But I see a problem which is that the return convention isn't clear. It *may* now return 0 on buffer overrun, but not necessarily. The rest of the function is best-effort mode and will silently drop a part of the modalias string if it doesn't fit. This is not consistent.

Quoted 7 lines

This is not caused by your patch, but this could actually cause false positive matches, so it may be a good idea to fix it while you're here. And in my opinion the best thing to do is to return an error rather than an half-baked modalias string. If the string doesn't fit as a whole, it's going to cause trouble at some point anyway, so we better learn about it early and do something about it. And that would be consistent.

The problem is... userspace. I'm not sure what is better -- return an error and empty data or trimmed strings. If you prefer error instead of course I can make it so.

Quoted 3 lines

I'm also curious why you chose to return 0 on error, rather than the more conventional -1 or -ENOMEM?

To match snprintf api when zero size is passed ('cause for udev we will pass 0 if buffer is already exhausted).

Quoted 6 lines

drivers/firmware/dmi-id.c static ssize_t get_modalias(char *buffer · line 109

Quoted 2 lines
drivers/firmware/dmi-id.c +1 -0
+	p = buffer + l; left = buffer_size - l - 1;

Please split on separate lines for readability. I would also appreciate a comment explaining that the "- 1" is to leave room for the trailing ":" (if I understand that right).

Exactly, I was scratching my head first too figuring out why additional char is needed here.

Quoted 4 lines
Quoted 4 lines
drivers/firmware/dmi-id.c +2 -0
+	if (left < 0)
+		return 0;
 
Quoted 14 lines

drivers/firmware/dmi-id.c static ssize_t sys_dmi_modalias_show(str · line 142

Quoted 8 lines
drivers/firmware/dmi-id.c +1 -1
 	for (f = fields; f->prefix && left > 0; f++) {
 		const char *c;
@@ -135,7 +139,7 @@ static ssize_t sys_dmi_modalias_show(str
 				     struct device_attribute *attr, char *page)
 {
 	ssize_t r;
-	r = get_modalias(page, PAGE_SIZE-1);
+	r = get_modalias(page, PAGE_SIZE-2);

Why? As I read the code, get_modalias() returns the length of the string, excluding the trailing '\0'. So it will be, at most, one less than the buffer size we passed. So if we pass PAGE_SIZE-1, r is at most PAGE_SIZE-2, which leaves exactly the 2 bytes we need for the two lines of code below. Am I missing something?

Yeah, I managed to overcounting here, page_size-1 should be enough.

Quoted 22 lines

(The last line of get_modalias would probably be clearer as:

	return (p + 1) - buffer;

or if p was increased beforehand to actually point to the end of the string.)

drivers/firmware/dmi-id.c static int dmi_dev_uevent(const struct d · line 171

Quoted 10 lines
drivers/firmware/dmi-id.c +1 -1
 	page[r] = '\n';
 	page[r+1] = 0;
 	return r+1;
@@ -163,7 +167,7 @@ static int dmi_dev_uevent(const struct d
 		return -ENOMEM;
 	len = get_modalias(&env->buf[env->buflen - 1],
 			   sizeof(env->buf) - env->buflen);
-	if (len >= (sizeof(env->buf) - env->buflen))
+	if (!len || len >= (sizeof(env->buf) - env->buflen))
 		return -ENOMEM;

I do not like the fact that we check whether get_modalias() returns an error here, and not in sys_dmi_modalias_show(). This is inconsistent. IMHO both functions should check the return value and return an error code on failure.

Quoted 3 lines

As a side note, I can't see how the second condition could be true. We pass the buffer size to get_modalias() exactly to make sure that it won't write past the buffer's end.

I just added !len here, which didn't make code anyhow better, good point!

Quoted 3 lines

drivers/firmware/dmi-id.c static int dmi_dev_uevent(const struct d · line 173

Quoted 3 lines
drivers/firmware/dmi-id.c +0 -0
 	env->buflen += len;
 	return 0;

So Jean, do you think it worth to rework this patch and add an error in case of overflow? To be honest I made this patch simply because I miscounted PAGE_SIZE-1 case here (probably should stop reading code at deep nights :) Since we never ever hit buffer overflow so far neither in sysfs or udev it obviously not critical. Still if you think it would worth to address a potential problem I'll rework it and resend addressing your comments.

Cyrill

#4 lore raw

PATCH v2 1/1 firmware: dmi: Respect buffer size in get_modalias

When we collect data from DMI info the "dmi" prefix is copied unconditionally which may result in buffer overflow in case of filling uevent environment. Thus lets use strscpy() helper instead. Same time make all get_modalias() callers to handler error.

---
v2:
- add comment about reserving space for suffix
- check for error in callers

 drivers/firmware/dmi-id.c |   30 +++++++++++++++++++-----------
 1 file changed, 19 insertions(+), 11 deletions(-)
drivers/firmware/dmi-id.c +19 -11
Index: linux-tip.git/drivers/firmware/dmi-id.c
===================================================================
@@ -103,8 +103,15 @@ static ssize_t get_modalias(char *buffer
 	char *p;
 	const struct mafield *f;
 
-	strcpy(buffer, "dmi");
-	p = buffer + 3; left = buffer_size - 4;
+	l = strscpy(buffer, "dmi", buffer_size);
+	if (l < 0)
+		return -ENOMEM;
+	p = buffer + l;
+
+	/* Reserve place for suffix */
+	left = buffer_size - l - 1;
+	if (left < 0)
+		return -ENOMEM;
 
 	for (f = fields; f->prefix && left > 0; f++) {
 		const char *c;
@@ -125,20 +132,21 @@ static ssize_t get_modalias(char *buffer
 		left -= l;
 	}
 
-	p[0] = ':';
-	p[1] = 0;
+	*p++ = ':';
+	*p = 0;
 
-	return p - buffer + 1;
+	return p - buffer;
 }
 
 static ssize_t sys_dmi_modalias_show(struct device *dev,
 				     struct device_attribute *attr, char *page)
 {
-	ssize_t r;
-	r = get_modalias(page, PAGE_SIZE-1);
-	page[r] = '\n';
-	page[r+1] = 0;
-	return r+1;
+	ssize_t r = get_modalias(page, PAGE_SIZE-1);
+	if (r > 0) {
+		page[r++] = '\n';
+		page[r] = 0;
+	}
+	return r;
 }
 
 static struct device_attribute sys_dmi_modalias_attr =
@@ -163,7 +171,7 @@ static int dmi_dev_uevent(const struct d
 		return -ENOMEM;
 	len = get_modalias(&env->buf[env->buflen - 1],
 			   sizeof(env->buf) - env->buflen);
-	if (len >= (sizeof(env->buf) - env->buflen))
+	if (len < 0)
 		return -ENOMEM;
 	env->buflen += len;
 	return 0;