Bug 163762 - IntSize::area() should used checked arithmetic
Summary: IntSize::area() should used checked arithmetic
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Platform (show other bugs)
Version: Safari 10
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: David Kilzer (:ddkilzer)
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2016-10-20 15:31 PDT by David Kilzer (:ddkilzer)
Modified: 2016-10-21 23:42 PDT (History)
11 users (show)

See Also:


Attachments
Patch v1 (23.81 KB, patch)
2016-10-20 20:04 PDT, David Kilzer (:ddkilzer)
buildbot: commit-queue-
Details | Formatted Diff | Diff
Archive of layout-test-results from ews106 for mac-yosemite-wk2 (901.53 KB, application/zip)
2016-10-20 21:19 PDT, Build Bot
no flags Details
Patch v2 (27.73 KB, patch)
2016-10-21 00:53 PDT, David Kilzer (:ddkilzer)
no flags Details | Formatted Diff | Diff
Patch v3 (30.99 KB, patch)
2016-10-21 15:36 PDT, David Kilzer (:ddkilzer)
darin: review+
ddkilzer: commit-queue?
Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description David Kilzer (:ddkilzer) 2016-10-20 15:31:34 PDT
InSize::area() should used checked arithmetic since m_width and m_height are integers, and area() currently returns an unsigned value, and area() is frequently multiplied by 4 when used.
Comment 1 David Kilzer (:ddkilzer) 2016-10-20 20:04:11 PDT
Created attachment 292317 [details]
Patch v1
Comment 2 Build Bot 2016-10-20 21:19:13 PDT
Comment on attachment 292317 [details]
Patch v1

Attachment 292317 [details] did not pass mac-wk2-ews (mac-wk2):
Output: http://webkit-queues.webkit.org/results/2335403

New failing tests:
plugins/large-plugin-crash.html
Comment 3 Build Bot 2016-10-20 21:19:17 PDT
Created attachment 292321 [details]
Archive of layout-test-results from ews106 for mac-yosemite-wk2

The attached test failures were seen while running run-webkit-tests on the mac-wk2-ews.
Bot: ews106  Port: mac-yosemite-wk2  Platform: Mac OS X 10.10.5
Comment 4 Radar WebKit Bug Importer 2016-10-20 23:46:31 PDT
<rdar://problem/28885875>
Comment 5 David Kilzer (:ddkilzer) 2016-10-21 00:53:30 PDT
Created attachment 292327 [details]
Patch v2
Comment 6 Brent Fulgham 2016-10-21 09:11:56 PDT
Comment on attachment 292327 [details]
Patch v2

View in context: https://bugs.webkit.org/attachment.cgi?id=292327&action=review

I think these changes look good. We need a WK2 reviewer to complete. r=me for non-WK2 parts.

> Source/WebCore/html/ImageData.cpp:116
> +    , m_data(Uint8ClampedArray::createUninitialized((size.area() * 4).unsafeGet()))

I wonder if it would be better to say "sizeof(RGBA32)" instead of "4" in all of these places.

> Source/WebCore/platform/graphics/win/ImageBufferDataDirect2D.cpp:87
> +    memcpy(result->data(), pixels, area);

Funny that we were doing this calculation twice before!
Comment 7 Darin Adler 2016-10-21 09:36:11 PDT
Comment on attachment 292327 [details]
Patch v2

View in context: https://bugs.webkit.org/attachment.cgi?id=292327&action=review

> Source/WebCore/html/MediaElementSession.cpp:681
> +    return elementRectInMainFrame.area().unsafeGet() > totalElementArea / 2;

Looking at all this code using Checked makes it clear that unsafeGet is the wrong name for that function. That function terminates the process if there was overflow. That’s not “unsafe” in the sense we normally mean it. In fact, we recently named a function safeCast and we gave it the same behavior as unsafeGet, abort the process if something is wrong.

> Source/WebCore/platform/graphics/IntSize.h:134
> +    Checked<unsigned> area() const

Using checked arithmetic to compute the area is clearly the right thing to do. Having a function that is named simply area, and returns a Checked<>, when we have almost no other functions like that in all of WebKit, is not an obvious pattern. I would suggest renaming it checkedArea instead.

> Source/WebCore/platform/graphics/win/ImageBufferDataDirect2D.cpp:50
> -    Checked<unsigned, RecordOverflow> area = 4 * rect.area();
> -    if (area.hasOverflowed())
> +    if (rect.unclampedArea() > std::numeric_limits<unsigned>::max() / 4)
>          return nullptr;
> +    unsigned area = (rect.area() * 4).unsafeGet();

This is inelegant. Checked is doing overflow checking, and here we do the same overflow checking a second time so that we can detect that case and return nullptr. Is there a way to write this without having to do our own hand check that exactly matches what Checked is doing?

> Source/WebKit2/WebProcess/Plugins/PluginProxy.cpp:613
> +    if (backingStoreSize.unclampedArea() > std::numeric_limits<unsigned>::max() / 4) {
> +        m_backingStore = nullptr;
> +        return false;
> +    }

Same comment as above. Can we find a way to use Checked to do this rather than writing code to do same check? Also, I think this code needs a comment. It’s not at all clear why this check is the correct one.
Comment 8 David Kilzer (:ddkilzer) 2016-10-21 11:00:20 PDT
Comment on attachment 292327 [details]
Patch v2

View in context: https://bugs.webkit.org/attachment.cgi?id=292327&action=review

>> Source/WebCore/html/ImageData.cpp:116
>> +    , m_data(Uint8ClampedArray::createUninitialized((size.area() * 4).unsafeGet()))
> 
> I wonder if it would be better to say "sizeof(RGBA32)" instead of "4" in all of these places.

I inferred from Simon's review of the first patch that these "4" values aren't all necessarily used for the same reason.  Is that not the case?

Also, "typedef unsigned RGBA32;" has a comment that's it's deprecated in Source/WebCore/platform/graphics/Color.h, so I'm not sure if we should be adding more uses of it?

>> Source/WebCore/html/MediaElementSession.cpp:681
>> +    return elementRectInMainFrame.area().unsafeGet() > totalElementArea / 2;
> 
> Looking at all this code using Checked makes it clear that unsafeGet is the wrong name for that function. That function terminates the process if there was overflow. That’s not “unsafe” in the sense we normally mean it. In fact, we recently named a function safeCast and we gave it the same behavior as unsafeGet, abort the process if something is wrong.

I think the mismatch comes from the difference between Checked<unsigned, CrashOnOverflow> (which is the default for Checked<unsigned>) and Checked<unsigned, RecordOverflow>.

The Checked<unsigned>)::unsafeGet() method is poorly named since it's always safe (since we would have crashed before it was called if there was overflow), but Checked<unsigned, RecordOverflow>::unsafeGet() might actually crash if one didn't check the value of Checked<unsigned, RecordOverflow>::hasOverflowed() first.

Do you have a proposal for fixing this mismatch?

>> Source/WebCore/platform/graphics/IntSize.h:134
>> +    Checked<unsigned> area() const
> 
> Using checked arithmetic to compute the area is clearly the right thing to do. Having a function that is named simply area, and returns a Checked<>, when we have almost no other functions like that in all of WebKit, is not an obvious pattern. I would suggest renaming it checkedArea instead.

I will rename it in a new patch, but I can see three reasons for not naming it differently:

1. You can't make a mistake because the compiler will tell you that you didn't call a method to get the value out of the Checked<> object.
2. I foresee a future where we have many more methods that return Checked<>, and so adding a "checked" prefix won't be that useful.
3. It forces the caller to think about overflow if they hit a compiler error with #1.  (This is the weakest argument because the "checked" prefix would do the same.)

>> Source/WebCore/platform/graphics/win/ImageBufferDataDirect2D.cpp:50
>> +    unsigned area = (rect.area() * 4).unsafeGet();
> 
> This is inelegant. Checked is doing overflow checking, and here we do the same overflow checking a second time so that we can detect that case and return nullptr. Is there a way to write this without having to do our own hand check that exactly matches what Checked is doing?

I think the way to do this is to templatize checkedArea() to handle both <unsigned, CrashOnOverflow> and Checked<unsigned, RecordOverflow> variants, then chose the variant at the call site.

I will do this in the next patch.

>> Source/WebKit2/WebProcess/Plugins/PluginProxy.cpp:613
>> +    }
> 
> Same comment as above. Can we find a way to use Checked to do this rather than writing code to do same check? Also, I think this code needs a comment. It’s not at all clear why this check is the correct one.

Ditto.
Comment 9 Simon Fraser (smfr) 2016-10-21 11:10:06 PDT
(In reply to comment #8)
> Comment on attachment 292327 [details]
> Patch v2
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=292327&action=review
> 
> >> Source/WebCore/html/ImageData.cpp:116
> >> +    , m_data(Uint8ClampedArray::createUninitialized((size.area() * 4).unsafeGet()))
> > 
> > I wonder if it would be better to say "sizeof(RGBA32)" instead of "4" in all of these places.
> 
> I inferred from Simon's review of the first patch that these "4" values
> aren't all necessarily used for the same reason.  Is that not the case?

Right now that's true, but soon we'll be making ImageBuffers with different pixel sizes, so you'll have to ask the image buffer for its "bytes per pixel".
Comment 10 Darin Adler 2016-10-21 12:12:22 PDT
Comment on attachment 292327 [details]
Patch v2

View in context: https://bugs.webkit.org/attachment.cgi?id=292327&action=review

>>> Source/WebCore/html/MediaElementSession.cpp:681
>>> +    return elementRectInMainFrame.area().unsafeGet() > totalElementArea / 2;
>> 
>> Looking at all this code using Checked makes it clear that unsafeGet is the wrong name for that function. That function terminates the process if there was overflow. That’s not “unsafe” in the sense we normally mean it. In fact, we recently named a function safeCast and we gave it the same behavior as unsafeGet, abort the process if something is wrong.
> 
> I think the mismatch comes from the difference between Checked<unsigned, CrashOnOverflow> (which is the default for Checked<unsigned>) and Checked<unsigned, RecordOverflow>.
> 
> The Checked<unsigned>)::unsafeGet() method is poorly named since it's always safe (since we would have crashed before it was called if there was overflow), but Checked<unsigned, RecordOverflow>::unsafeGet() might actually crash if one didn't check the value of Checked<unsigned, RecordOverflow>::hasOverflowed() first.
> 
> Do you have a proposal for fixing this mismatch?

No, no proposal right now.

>>> Source/WebCore/platform/graphics/IntSize.h:134
>>> +    Checked<unsigned> area() const
>> 
>> Using checked arithmetic to compute the area is clearly the right thing to do. Having a function that is named simply area, and returns a Checked<>, when we have almost no other functions like that in all of WebKit, is not an obvious pattern. I would suggest renaming it checkedArea instead.
> 
> I will rename it in a new patch, but I can see three reasons for not naming it differently:
> 
> 1. You can't make a mistake because the compiler will tell you that you didn't call a method to get the value out of the Checked<> object.
> 2. I foresee a future where we have many more methods that return Checked<>, and so adding a "checked" prefix won't be that useful.
> 3. It forces the caller to think about overflow if they hit a compiler error with #1.  (This is the weakest argument because the "checked" prefix would do the same.)

The value of the name with "checked" in it is primarily for people reading code, not writing code. I agree that when writing code the compiler will help you understand it’s checked.

Your argument that we will return checked from lots more functions in the future is an interesting one. I still think there will be *many* functions that return values that are not checked since the values are not the result of a computation. I think we need to discuss how good a pattern that is. It’s good to return a checked value if the caller is highly likely to do additional calculations based on that value. Otherwise the use of Checked can be inside the function and need not affect the return type.
Comment 11 David Kilzer (:ddkilzer) 2016-10-21 12:26:26 PDT
Comment on attachment 292327 [details]
Patch v2

View in context: https://bugs.webkit.org/attachment.cgi?id=292327&action=review

>>>> Source/WebCore/platform/graphics/IntSize.h:134
>>>> +    Checked<unsigned> area() const
>>> 
>>> Using checked arithmetic to compute the area is clearly the right thing to do. Having a function that is named simply area, and returns a Checked<>, when we have almost no other functions like that in all of WebKit, is not an obvious pattern. I would suggest renaming it checkedArea instead.
>> 
>> I will rename it in a new patch, but I can see three reasons for not naming it differently:
>> 
>> 1. You can't make a mistake because the compiler will tell you that you didn't call a method to get the value out of the Checked<> object.
>> 2. I foresee a future where we have many more methods that return Checked<>, and so adding a "checked" prefix won't be that useful.
>> 3. It forces the caller to think about overflow if they hit a compiler error with #1.  (This is the weakest argument because the "checked" prefix would do the same.)
> 
> The value of the name with "checked" in it is primarily for people reading code, not writing code. I agree that when writing code the compiler will help you understand it’s checked.
> 
> Your argument that we will return checked from lots more functions in the future is an interesting one. I still think there will be *many* functions that return values that are not checked since the values are not the result of a computation. I think we need to discuss how good a pattern that is. It’s good to return a checked value if the caller is highly likely to do additional calculations based on that value. Otherwise the use of Checked can be inside the function and need not affect the return type.

Actually, what I wanted to write instead of #3 above (but forgot while typing this up waiting outside the new Firenza Pizza place in Los Gatos for lunch) is:

#4. I don't think we want to ever provide an IntSize::area() method that doesn't use checked arithmetic due to the possibility of overflow, so this will become the "default" method for getting the area of an IntSize, so why not call it just area()?

I understand the argument to make it easier for the reader, although it's also obvious where the object is used later that's it's Checked<> with methods like unsafeGet() being used.  (In a similar way, the use of 'auto' obscures the exact type of the returned value, but using clues later in the method usually make it obvious whether it's a RetainPtr<> or a Ref<> or a RefPtr<>, etc.)

Hmm...maybe change unsafeGet() into checkedGet() or getChecked() or getCheckedValue() or checkedValue()?  (I like the last suggestion the best.)

Then expressions like (size.area() * 4).checkedValue() would make it obvious you're dealing with Checked<> objects without having to rename area() to checkedArea().
Comment 12 David Kilzer (:ddkilzer) 2016-10-21 15:36:09 PDT
Created attachment 292422 [details]
Patch v3
Comment 13 Darin Adler 2016-10-21 22:38:00 PDT
Comment on attachment 292422 [details]
Patch v3

View in context: https://bugs.webkit.org/attachment.cgi?id=292422&action=review

> Source/WebCore/platform/graphics/win/ImageBufferDataDirect2D.cpp:48
> +    Checked<unsigned, RecordOverflow> numBytes = rect.area<RecordOverflow>() * 4;

This should use auto, I think, rather than writing out the type.

> Source/WebKit2/Shared/ShareableBitmap.cpp:69
> +    Checked<unsigned, RecordOverflow> numBytes = numBytesForSize(size);

Ditto.

> Source/WebKit2/Shared/ShareableBitmap.cpp:82
> +    Checked<unsigned, RecordOverflow> numBytes = numBytesForSize(size);

Ditto.

> Source/WebKit2/Shared/ShareableBitmap.cpp:97
> +    Checked<unsigned, RecordOverflow> numBytes = numBytesForSize(size);

Ditto.
Comment 14 David Kilzer (:ddkilzer) 2016-10-21 23:42:00 PDT
Committed r207708: <http://trac.webkit.org/changeset/207708>