Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Fix "out of bounds" undefined behavior #16975

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 30, 2020
Merged

Conversation

vitalybuka
Copy link
Contributor

@vitalybuka vitalybuka commented Mar 31, 2020

This is detected with clang and -fsanitizer=bounds.

Out-of-bound addressing array of fixed size is undefined
behavior in both C and C++. It is so even if underlying
memory is properly allocated.

Easy fix here is to iterate with this pointer instead of
pixel_type::c array.

@QuLogic
Copy link
Member

QuLogic commented Mar 31, 2020

What error is this fixing?

@tacaswell tacaswell added this to the v3.3.0 milestone Mar 31, 2020
@vitalybuka
Copy link
Contributor Author

vitalybuka commented Apr 1, 2020

What error is this fixing?

This fixes undefined behavior which can be "miscompiled" in future
compilers releases (or even now with different settings).

E.g. Here it's perfectly legal for compiler in case of "n = 5" compile as
return (pixel_type*)(c + 4 * pix_step);
or
return (pixel_type*)(c);
because array is just 4 elements
http://eel.is/c++draft/expr.add#4.2

Also it lets users to compile code with -fsanitize=bounds or -fsanitize=bounds-strict.

Copy link
Member

@QuLogic QuLogic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes sense to me in this case, but there appears to be a similar construct in agg_pixfmt_rgb.h and agg_pixfmt_gray.h for which sizeof(class) != step size. I don't think we use rgb, but we do use gray for resampling.

pix_step = 4,
pix_width = sizeof(value_type) * pix_step,
};
struct pixel_type
{
value_type c[num_components];
value_type c[pix_step];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should change, as it makes the rgba file inconsistent with the rgb one, and semantically these are different things.

Copy link
Contributor Author

@vitalybuka vitalybuka Apr 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if we change them as well? Looks like this way code is simpler and compliant.
Then num_components is not used anywhere, but I can return it back if you like.
PTAL

Copy link

@tkoeppe tkoeppe Apr 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@QuLogic : Has this point been resolved? (I don't have any opinion either way, but I wanted to make sure this isn't a blocker.)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(For what it's worth, if it is easier to land this overall fix without chaning the bounds here, I'd welcome that as a simpler first step; the deduplication of these two redundant enumerators could be a separate PR.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like as small as possible patches as well. Still here to use "this + 1" we need array size to be the "pix_step" not "num_components". Then num_components is unused. So the best I can do is moving lines with "- num_components = ..." into a separate PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure who you're tagging there; as you can see I've already approved.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vitalybuka: Ah of course, Step could be not 1... say, is the current code correct when Step is not 1? In that case the current code's next would skip over some array elements, right?

This is detected with clang and -fsanitizer=bounds.

Out-of-bound addressing array of fixed size is undefined
behavior in both C and C++. It is so even if underlying
memory is properly allocated.

Easy fix here is to iterate with this pointer instead of
pixel_type::c array.
@vitalybuka
Copy link
Contributor Author

ping

Copy link
Member

@QuLogic QuLogic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semantically, I understand what it was trying to do, even if it's not up to the standard, but in practice, dropping the different sizes shouldn't be a problem.

@vitalybuka
Copy link
Contributor Author

Semantically, I understand what it was trying to do, even if it's not up to the standard, but in practice, dropping the different sizes shouldn't be a problem.

In practice it's inconvenience for those who compile with -fsanitize=bounds. We will have to
disable the check for the project or maintain a local patch. Both are invenveniece.

This check finds real buffer overflow which can't be detected by other means. E.g. -fsanitize=address will not report buffer overflow reads from a different field of the same structure.

Also I think my last patch makes code a little bit nice even regardless to the bounds :)

@QuLogic
Copy link
Member

QuLogic commented Apr 18, 2020

Yes, it would break -fsanitize=bounds, but I don't think we ever use num_components != pix_step, since we don't use the rgb version, never template Step in gray, and rgba has them equal.

@vitalybuka
Copy link
Contributor Author

Can someone merge this please?

@QuLogic
Copy link
Member

QuLogic commented Apr 22, 2020

Merging requires 2 approvals. Please be patient.

@jklymak
Copy link
Member

jklymak commented Apr 27, 2020

I would need far more context to approve. What is this c code used by, and how do we know that this change works?

@tkoeppe
Copy link

tkoeppe commented Apr 27, 2020

@jklymak: Is there anything I could do to help? I'm quite keen on having this fixed. So... first off, is it contentious whether the status quo is buggy? (It is accessing an array out of bounds.) Is the fix correct? If you consider why the buggy code "seems to work", it's because it is using the address of the first subobject and casting it to the address of the object itself, so we can replace the cast by just this The load-bearing question is how the stride changes; this is about the relation between the size of value_type and the size of (the type of) *this.

It seems that what is meant here is to itereate through an array of pixel_type, and it so happens that that type is exactly as large as its contents (i.e. there's no padding), namely 4 * sizeof(value_type) for "rgba" and 1 * sizeof(value_type) for "rgb" and "gray". If we're just iterating over pixel_types, then simple pointer arithmetic on this will do just that.

I've applied this change internally (minus the change of the array bound), and some of our tests continue to pass, and the previous bug (as reported by sanitizers) is fixed. (I'm not sure how much coverage we have, though. I can run some more tests.)

@tkoeppe
Copy link

tkoeppe commented Apr 28, 2020

A correction to my previous comment: The stride logic is only correct for the "rgba" version, where num_components and pix_step are both hard-coded to 4. In the "rgb" and "gray" versions, the status quo is questionable, see other comment thread above. That other code might warrant a separate analysis and fix; it's not just about an "out of bounds UB", but it actually seems questionable whether the class layout is currently correct in those cases: e.g. in "rgb", c is an array value_type[3], but next computes (pixel_type*)(c + Step), so this would be outright invalid whenever Step is not a multiple of 3 even if you believe flattened pointer arithmetic through arrays of structs. The first thing to decide here would be if c was really intended to be an array of value_type[Step] (as is currently proposed).

@QuLogic
Copy link
Member

QuLogic commented Apr 28, 2020

Step must only be >= 3 to be correct as far as the intention of the original code.

@tkoeppe
Copy link

tkoeppe commented Apr 28, 2020

@QuLogic: If Step is not a multiple of 3, then c + Step does not point at a pixel_type object, though, does it? So the pointer cast would be invalid?

@QuLogic
Copy link
Member

QuLogic commented Apr 28, 2020

Why do you keep pinging a random other person? I don't think they appreciate it.

The point of Step is to be a stride; it is intentionally not related to the number of components. The buffer is not pixel_type[]; it's (pixel_type *)char[]; (playing a bit loose with syntax there).

@tkoeppe
Copy link

tkoeppe commented Apr 28, 2020

@QuLogic: Oh, so sorry, I had been completely blind to the mixup! I used the auto completion and selected the wrong user! Hopefully fixed now.

@tkoeppe
Copy link

tkoeppe commented Apr 28, 2020

@QuLogic: aha -- so do you mean that you really have a large array char[N] and inside that you freely want to pick out chunks of size 3 (at arbitrary offsets) and treat those as pixel_types?

@QuLogic
Copy link
Member

QuLogic commented Apr 28, 2020

Yes, it's somewhat like BMP (maybe JPEG too?) where every row must be on a 4-byte boundary, even if width is not divisible by 4.

@QuLogic
Copy link
Member

QuLogic commented Apr 28, 2020

But note this is mostly academic, as we never use rgb, and always use Step=1(=num_components) for gray.

@tkoeppe
Copy link

tkoeppe commented Apr 28, 2020

Oh, interesting -- could you maybe back up a bit and explain the intended layout from scratch? So, let's say for example for RGB, we have a contiguous buffer RGBRGBRGBRGB... -- and one pixel_type contains one chunk RGB, is that right? It never starts on, say, a G? And there's no padding? But then you still want to make larger, aligned steps to somewhere that's a multiple of 4 from the beginning of the buffer? If the step is unrelated to the number of components, where does any eventual padding go? (In the BMP example, say, there'd be padding at the end of each row, so there's a "row pitch" to allow random access to the beginning of each row.)

@tkoeppe
Copy link

tkoeppe commented Apr 28, 2020

@QuLogic: Oh, is that right? Could we maybe enshrine this in code and remove the Step template parameter from "gray"?

@QuLogic
Copy link
Member

QuLogic commented Apr 28, 2020

Well, we aren't upstream, and while upstream is somewhat dormant, it's fine to fix bugs, but changing semantics seems unnecessary.

@tkoeppe
Copy link

tkoeppe commented Apr 29, 2020

OK, so if we're not changing that, but only the pointer arithmetic, then I suppose the next implementation for "gray" should say this + pix_step and for "rgba" it should say this + 1. But what should it be for "rgb"?

@QuLogic
Copy link
Member

QuLogic commented Apr 30, 2020

No, this + pix_step is definitely wrong, as pointer arithmetic would turn that into (typeof this)((char*)this + sizeof(*this) * pix_step). That's why it's always this + 1.

@story645 story645 merged commit 1ca210c into matplotlib:master Apr 30, 2020
Copy link
Member

@tacaswell tacaswell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to make the biggest actually difference when we are laying this on top of a buffer with a pixel stride that is bigger than the pixel size so

'RGBxRGBx' (for an 4byte aligned array with 3byte actual data payload or 'WxxxWxxx' for 4byte aligned pixels with 1byte data) where previously the c component of the struct would be the size of the actual payload, where as now it will be the full size of stride.

I think any further discussion about overhauling this code should be pushed to the apparently revived Agg upstream.

@story645
Copy link
Member

Thank you for the contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants