diff mbox series

[v4] EDAC/mc: Prefer strscpy over strcpy

Message ID 20210814075527.5999-1-len.baker@gmx.com (mailing list archive)
State New, archived
Headers show
Series [v4] EDAC/mc: Prefer strscpy over strcpy | expand

Commit Message

Len Baker Aug. 14, 2021, 7:55 a.m. UTC
strcpy() performs no bounds checking on the destination buffer. This
could result in linear overflows beyond the end of the buffer, leading
to all kinds of misbehaviors. The safe replacement is strscpy().

This is a previous step in the path to remove the strcpy() function
entirely from the kernel.

Signed-off-by: Len Baker <len.baker@gmx.com>
---
Hi,

Following the comments in the previous version I don't use the scnprintf
function avoiding speed penalties. Instead I use the strscpy.

Thanks,
Len

This is a task of the KSPP [1]

[1] https://github.com/KSPP/linux/issues/88

Changelog v1 -> v2
- Use the strscpy() instead of scnprintf() to add labels and follow a
  code pattern more similar to the current one (advance "p" and
  decrement "left") (Robert Richter).

Changelog v2 -> v3
- Rename the "left" variable to "len" (Robert Richter).
- Use strlen(p) instead of strlen(OTHER_LABEL) to decrement "len" and
  increment "p" as otherwise "left" could underflow and p overflow
  (Robert Richter).

Changelog v3 -> v4
- Change the commit subject (Joe Perches).
- Fix broken logic (Robert Richter).
- Rebase against v5.14-rc5.

Previous versions:

v1
https://lore.kernel.org/linux-hardening/20210725162954.9861-1-len.baker@gmx.com/

v2
https://lore.kernel.org/linux-hardening/20210801143558.12674-1-len.baker@gmx.com/

v3
https://lore.kernel.org/linux-hardening/20210807155957.10069-1-len.baker@gmx.com/

 drivers/edac/edac_mc.c | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

--
2.25.1

Comments

Borislav Petkov Aug. 23, 2021, 5:30 p.m. UTC | #1
On Sat, Aug 14, 2021 at 09:55:27AM +0200, Len Baker wrote:
> strcpy() performs no bounds checking on the destination buffer. This
> could result in linear overflows beyond the end of the buffer, leading
> to all kinds of misbehaviors. The safe replacement is strscpy().
> 
> This is a previous step in the path to remove the strcpy() function

"previous step"?

> entirely from the kernel.
> 
> Signed-off-by: Len Baker <len.baker@gmx.com>

...

> diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c
> index f6d462d0be2d..7aea6c502316 100644
> --- a/drivers/edac/edac_mc.c
> +++ b/drivers/edac/edac_mc.c
> @@ -1032,6 +1032,7 @@ void edac_mc_handle_error(const enum hw_event_mc_err_type type,
>  	int i, n_labels = 0;
>  	struct edac_raw_error_desc *e = &mci->error_desc;
>  	bool any_memory = true;
> +	size_t len;
> 
>  	edac_dbg(3, "MC%d\n", mci->mc_idx);
> 
> @@ -1086,6 +1087,7 @@ void edac_mc_handle_error(const enum hw_event_mc_err_type type,
>  	 */
>  	p = e->label;
>  	*p = '\0';
> +	len = sizeof(e->label);
> 
>  	mci_for_each_dimm(mci, dimm) {
>  		if (top_layer >= 0 && top_layer != dimm->location[0])
> @@ -1114,10 +1116,12 @@ void edac_mc_handle_error(const enum hw_event_mc_err_type type,
>  			*p = '\0';
>  		} else {
>  			if (p != e->label) {
> -				strcpy(p, OTHER_LABEL);
> -				p += strlen(OTHER_LABEL);
> +				strscpy(p, OTHER_LABEL, len);

Hm, maybe I'm missing something but looking at that strscpy()
definition, why aren't you doing:

				num = strscpy(p, OTHER_LABEL, len);
				if (num < 0)
					/* just in case */
					break;

				len -= num;
				p   += num;

since that function supposedly returns the number of chars copied.

> +				len -= strlen(p);
> +				p += strlen(p);
>  			}
> -			strcpy(p, dimm->label);
> +			strscpy(p, dimm->label, len);
> +			len -= strlen(p);
>  			p += strlen(p);

Ditto.

Thx.
Len Baker Aug. 24, 2021, 10:28 a.m. UTC | #2
Hi Borislav,

On Mon, Aug 23, 2021 at 07:30:34PM +0200, Borislav Petkov wrote:
> On Sat, Aug 14, 2021 at 09:55:27AM +0200, Len Baker wrote:
> > strcpy() performs no bounds checking on the destination buffer. This
> > could result in linear overflows beyond the end of the buffer, leading
> > to all kinds of misbehaviors. The safe replacement is strscpy().
> >
> > This is a previous step in the path to remove the strcpy() function
>
> "previous step"?

This is a task of the KSPP [1] and the main reason is to clean up the
proliferation of str*cpy functions in the kernel.

[1] https://github.com/KSPP/linux/issues/88

> > entirely from the kernel.
> >
> > Signed-off-by: Len Baker <len.baker@gmx.com>
>
> ...
>
> > diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c
> > index f6d462d0be2d..7aea6c502316 100644
> > --- a/drivers/edac/edac_mc.c
> > +++ b/drivers/edac/edac_mc.c
> > @@ -1032,6 +1032,7 @@ void edac_mc_handle_error(const enum hw_event_mc_err_type type,
> >  	int i, n_labels = 0;
> >  	struct edac_raw_error_desc *e = &mci->error_desc;
> >  	bool any_memory = true;
> > +	size_t len;
> >
> >  	edac_dbg(3, "MC%d\n", mci->mc_idx);
> >
> > @@ -1086,6 +1087,7 @@ void edac_mc_handle_error(const enum hw_event_mc_err_type type,
> >  	 */
> >  	p = e->label;
> >  	*p = '\0';
> > +	len = sizeof(e->label);
> >
> >  	mci_for_each_dimm(mci, dimm) {
> >  		if (top_layer >= 0 && top_layer != dimm->location[0])
> > @@ -1114,10 +1116,12 @@ void edac_mc_handle_error(const enum hw_event_mc_err_type type,
> >  			*p = '\0';
> >  		} else {
> >  			if (p != e->label) {
> > -				strcpy(p, OTHER_LABEL);
> > -				p += strlen(OTHER_LABEL);
> > +				strscpy(p, OTHER_LABEL, len);
>
> Hm, maybe I'm missing something but looking at that strscpy()
> definition, why aren't you doing:
>
> 				num = strscpy(p, OTHER_LABEL, len);
> 				if (num < 0)
> 					/* just in case */
> 					break;
>
> 				len -= num;
> 				p   += num;
>
> since that function supposedly returns the number of chars copied.

Yes, you are right. The same discussion happened in the v3 review [2] and
I agree with the reasons that Robert Richter exposed. Using the strlen()
implementation it is not necessary to check the return code of strcpy and
we can assume a silent truncation.

[2] https://lore.kernel.org/linux-hardening/YRN+8u59lJ6MWsOL@rric.localdomain/

Regards,
Len

> > +				len -= strlen(p);
> > +				p += strlen(p);
> >  			}
> > -			strcpy(p, dimm->label);
> > +			strscpy(p, dimm->label, len);
> > +			len -= strlen(p);
> >  			p += strlen(p);
>
> Ditto.
>
> Thx.
>
> --
> Regards/Gruss,
>     Boris.
>
> https://people.kernel.org/tglx/notes-about-netiquette
Borislav Petkov Aug. 24, 2021, 6:26 p.m. UTC | #3
On Tue, Aug 24, 2021 at 12:28:07PM +0200, Len Baker wrote:
> This is a task of the KSPP [1] and the main reason is to clean up the
> proliferation of str*cpy functions in the kernel.

That I understood - you prominently explain where the patches stem from.

What I can't parse is that formulation "previous step". What previous
step do you mean?

> Yes, you are right. The same discussion happened in the v3 review [2] and
> I agree with the reasons that Robert Richter exposed. Using the strlen()
> implementation it is not necessary to check the return code of strcpy and
> we can assume a silent truncation.
> 
> [2] https://lore.kernel.org/linux-hardening/YRN+8u59lJ6MWsOL@rric.localdomain/

Ok, looking at the asm, gcc is actually smart enough not to call
strlen() twice on the same buffer.

But then there's this in the strscpy() kernel-doc comment:

"The destination buffer is always NUL terminated, unless it's
zero-sized."

so looking at the code, we're merrily decrementing len but nothing's
checking whether len can become 0. Because if it does, strscpy() will
do:

	if (count == 0 || WARN_ON_ONCE(count > INT_MAX))
		return -E2BIG;

so if p ends up pointing to something which is *not* '\0', strlen() will
go off into the weeds.

So I don't care if it doesn't look just as nice - it better be correct
in all cases first.

Thx.
Joe Perches Aug. 24, 2021, 7:05 p.m. UTC | #4
On Tue, 2021-08-24 at 20:26 +0200, Borislav Petkov wrote:
> On Tue, Aug 24, 2021 at 12:28:07PM +0200, Len Baker wrote:
> > This is a task of the KSPP [1] and the main reason is to clean up the
> > proliferation of str*cpy functions in the kernel.
> 
> That I understood - you prominently explain where the patches stem from.
> 
> What I can't parse is that formulation "previous step". What previous
> step do you mean?
> 
> > Yes, you are right. The same discussion happened in the v3 review [2] and
> > I agree with the reasons that Robert Richter exposed. Using the strlen()
> > implementation it is not necessary to check the return code of strcpy and
> > we can assume a silent truncation.
> > 
> > [2] https://lore.kernel.org/linux-hardening/YRN+8u59lJ6MWsOL@rric.localdomain/
> 
> Ok, looking at the asm, gcc is actually smart enough not to call
> strlen() twice on the same buffer.
> 
> But then there's this in the strscpy() kernel-doc comment:
> 
> "The destination buffer is always NUL terminated, unless it's
> zero-sized."
> 
> so looking at the code, we're merrily decrementing len but nothing's
> checking whether len can become 0. Because if it does, strscpy() will
> do:
> 
> 	if (count == 0 || WARN_ON_ONCE(count > INT_MAX))
> 		return -E2BIG;
> 
> so if p ends up pointing to something which is *not* '\0', strlen() will
> go off into the weeds.
> 
> So I don't care if it doesn't look just as nice - it better be correct
> in all cases first.

It's all somehat unnecessary as it seems it's guaranteed not to overflow.

$ git grep -n -w OTHER_LABEL next-20210820
next-20210820:drivers/edac/edac_mc.c:1118:                              strcpy(p, OTHER_LABEL);
next-20210820:drivers/edac/edac_mc.c:1119:                              p += strlen(OTHER_LABEL);
next-20210820:include/linux/edac.h:57:#define OTHER_LABEL " or "
next-20210820:include/linux/edac.h:470: char label[(EDAC_MC_LABEL_LEN + 1 + sizeof(OTHER_LABEL)) * EDAC_MAX_LABELS];

Also the array size of char label is too large.

First by 1 * EDAC_MAX_LABELS as sizeof(OTHER_LABEL) is 5 not 4 and
second there can only be EDAC_MAX_LABELS - 1 uses of OTHER_LABEL.

And I would remove the define for OTHER_LABEL and use " or " directly.

Lastly, this could be easier to understand using stpcpy and/or scnprintf.
David Laight Aug. 25, 2021, 8:48 a.m. UTC | #5
From: Borislav Petkov
> Sent: 24 August 2021 19:26
..
> so looking at the code, we're merrily decrementing len but nothing's
> checking whether len can become 0. Because if it does, strscpy() will
> do:
> 
> 	if (count == 0 || WARN_ON_ONCE(count > INT_MAX))
> 		return -E2BIG;

That -E2BIG is going to break something.
It means that you always have to do an error check whenever
you use the return value of strscpy().

Anything that does:
	offset += strscpy(...)
is broken.

It really wasn't a good idea for reporting 'truncated'.

	David

-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK
Registration No: 1397386 (Wales)
Len Baker Aug. 27, 2021, 5:36 p.m. UTC | #6
Hi,

On Tue, Aug 24, 2021 at 08:26:10PM +0200, Borislav Petkov wrote:
> On Tue, Aug 24, 2021 at 12:28:07PM +0200, Len Baker wrote:
> > This is a task of the KSPP [1] and the main reason is to clean up the
> > proliferation of str*cpy functions in the kernel.
>
> That I understood - you prominently explain where the patches stem from.
>
> What I can't parse is that formulation "previous step". What previous
> step do you mean?

Well, the main purpose is to clean up the proliferation of str*cpy functions.
One task is to remove the strcpy uses: The first step (previous step) would
be to remove all the strcpy uses. Then, as a second step remove all the
strcpy implementations.

I hope that this clarify your question.

Regards,
Len
Borislav Petkov Aug. 27, 2021, 5:54 p.m. UTC | #7
On Fri, Aug 27, 2021 at 07:36:33PM +0200, Len Baker wrote:
> Well, the main purpose is to clean up the proliferation of str*cpy functions.
> One task is to remove the strcpy uses: The first step (previous step) would
> be to remove all the strcpy uses. Then, as a second step remove all the
> strcpy implementations.
> 
> I hope that this clarify your question.

Yes, it does.

Now lemme clarify why I'm asking: when your patch is committed to the
kernel tree and someone reads its commit message months or even years
from now - and those who do that are mostly maintainers trying to figure
out why stuff was done the way it was - they will read:

"This is a previous step in the path to remove the strcpy() function
entirely from the kernel."

and wonder what previous step that is what the following step is...

So, long story short, your commit message should be complete on its own
and understandable without any references to things which might not be
as clear and self-evident in the future as they are now.

Makes sense?

Also, if you're wondering if you should send the patch with the error
checking of strscpy() added, as I requested, even if it might look
superfluous now, yes you should.

Even if it looks impossible now, we might change some of those defines
in the future and forget to touch the logic which generates e->label and
we might end up exhausting that string.

So it would be a lot more robust if something would catch that change,
albeit seemingly redundant now.

I sincerely hope that clears up things.

Thx.
Joe Perches Aug. 27, 2021, 7:08 p.m. UTC | #8
On Fri, 2021-08-27 at 19:54 +0200, Borislav Petkov wrote:
> So it would be a lot more robust if something would catch that change,
> albeit seemingly redundant now.

I still think scnprintf is _way_ more common and intelligible as
a construct than this odd strscpy with required error checking.
Len Baker Aug. 29, 2021, 4:14 p.m. UTC | #9
Hi,

On Fri, Aug 27, 2021 at 07:54:07PM +0200, Borislav Petkov wrote:
> On Fri, Aug 27, 2021 at 07:36:33PM +0200, Len Baker wrote:
> > Well, the main purpose is to clean up the proliferation of str*cpy functions.
> > One task is to remove the strcpy uses: The first step (previous step) would
> > be to remove all the strcpy uses. Then, as a second step remove all the
> > strcpy implementations.
> >
> > I hope that this clarify your question.
>
> Yes, it does.
>
> Now lemme clarify why I'm asking: when your patch is committed to the
> kernel tree and someone reads its commit message months or even years
> from now - and those who do that are mostly maintainers trying to figure
> out why stuff was done the way it was - they will read:
>
> "This is a previous step in the path to remove the strcpy() function
> entirely from the kernel."
>
> and wonder what previous step that is what the following step is...
>
> So, long story short, your commit message should be complete on its own
> and understandable without any references to things which might not be
> as clear and self-evident in the future as they are now.
>
> Makes sense?

Ok, understood. Thanks for the advise and guidance.

>
> Also, if you're wondering if you should send the patch with the error
> checking of strscpy() added, as I requested, even if it might look
> superfluous now, yes you should.
>
> Even if it looks impossible now, we might change some of those defines
> in the future and forget to touch the logic which generates e->label and
> we might end up exhausting that string.
>
> So it would be a lot more robust if something would catch that change,
> albeit seemingly redundant now.
>
> I sincerely hope that clears up things.

Yes, it clears up things. However I think the same that Joe:

From Joe Perches:

[...]
I still think scnprintf is _way_ more common and intelligible as
a construct than this odd strscpy with required error checking.
[...]

So, I will send a new version for review with the commit message updated
and using the scnprintf. This way we can discuss using a real patch.
Anyway thanks for the review.

Regards,
Len
diff mbox series

Patch

diff --git a/drivers/edac/edac_mc.c b/drivers/edac/edac_mc.c
index f6d462d0be2d..7aea6c502316 100644
--- a/drivers/edac/edac_mc.c
+++ b/drivers/edac/edac_mc.c
@@ -1032,6 +1032,7 @@  void edac_mc_handle_error(const enum hw_event_mc_err_type type,
 	int i, n_labels = 0;
 	struct edac_raw_error_desc *e = &mci->error_desc;
 	bool any_memory = true;
+	size_t len;

 	edac_dbg(3, "MC%d\n", mci->mc_idx);

@@ -1086,6 +1087,7 @@  void edac_mc_handle_error(const enum hw_event_mc_err_type type,
 	 */
 	p = e->label;
 	*p = '\0';
+	len = sizeof(e->label);

 	mci_for_each_dimm(mci, dimm) {
 		if (top_layer >= 0 && top_layer != dimm->location[0])
@@ -1114,10 +1116,12 @@  void edac_mc_handle_error(const enum hw_event_mc_err_type type,
 			*p = '\0';
 		} else {
 			if (p != e->label) {
-				strcpy(p, OTHER_LABEL);
-				p += strlen(OTHER_LABEL);
+				strscpy(p, OTHER_LABEL, len);
+				len -= strlen(p);
+				p += strlen(p);
 			}
-			strcpy(p, dimm->label);
+			strscpy(p, dimm->label, len);
+			len -= strlen(p);
 			p += strlen(p);
 		}

@@ -1140,9 +1144,9 @@  void edac_mc_handle_error(const enum hw_event_mc_err_type type,
 	}

 	if (any_memory)
-		strcpy(e->label, "any memory");
+		strscpy(e->label, "any memory", sizeof(e->label));
 	else if (!*e->label)
-		strcpy(e->label, "unknown memory");
+		strscpy(e->label, "unknown memory", sizeof(e->label));

 	edac_inc_csrow(e, row, chan);