Bug 229661 - [WebRTC] Leak or over-release of CFPixelBufferRef returned from webrtc::createPixelBufferFromFrame()
Summary: [WebRTC] Leak or over-release of CFPixelBufferRef returned from webrtc::creat...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Media (show other bugs)
Version: WebKit Nightly Build
Hardware: Unspecified Unspecified
: P2 Normal
Assignee: David Kilzer (:ddkilzer)
URL:
Keywords: InRadar
Depends on:
Blocks: 229806
  Show dependency treegraph
 
Reported: 2021-08-29 19:57 PDT by David Kilzer (:ddkilzer)
Modified: 2021-11-10 12:56 PST (History)
11 users (show)

See Also:


Attachments
Patch v1 (13.20 KB, patch)
2021-08-29 20:04 PDT, David Kilzer (:ddkilzer)
no flags Details | Formatted Diff | Diff
Patch v2 (30.04 KB, patch)
2021-08-30 07:49 PDT, David Kilzer (:ddkilzer)
no flags Details | Formatted Diff | Diff
Patch v3 (30.05 KB, patch)
2021-08-30 09:17 PDT, David Kilzer (:ddkilzer)
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Patch v4 (30.11 KB, patch)
2021-08-30 10:21 PDT, David Kilzer (:ddkilzer)
ews-feeder: commit-queue-
Details | Formatted Diff | Diff
Alternative approach (2.04 KB, patch)
2021-08-31 01:10 PDT, youenn fablet
no flags Details | Formatted Diff | Diff
Patch v5 (30.12 KB, patch)
2021-08-31 19:27 PDT, David Kilzer (:ddkilzer)
no flags 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) 2021-08-29 19:57:52 PDT
Leak or over-release of CFPixelBufferRef returned from webrtc::createPixelBufferFromFrame().

Found by the clang static analyzer:

OpenSource/Source/ThirdParty/libwebrtc/Source/webrtc/sdk/WebKit/WebKitDecoderReceiver.cpp:177:9: warning: Incorrect decrement of the reference count of an object that is not owned at this point by the caller
        CFRelease(pixelBuffer);
        ^~~~~~~~~~~~~~~~~~~~~~
OpenSource/Source/ThirdParty/libwebrtc/Source/webrtc/sdk/WebKit/WebKitDecoderReceiver.cpp:177:9: warning: Incorrect decrement of the reference count of an object that is not owned at this point by the caller
        CFRelease(pixelBuffer);
        ^~~~~~~~~~~~~~~~~~~~~~
OpenSource/Source/ThirdParty/libwebrtc/Source/webrtc/sdk/WebKit/WebKitDecoderReceiver.cpp:177:9: warning: Incorrect decrement of the reference count of an object that is not owned at this point by the caller
        CFRelease(pixelBuffer);
        ^~~~~~~~~~~~~~~~~~~~~~
3 warnings generated.
Comment 1 Radar WebKit Bug Importer 2021-08-29 19:58:21 PDT
<rdar://problem/82507827>
Comment 2 David Kilzer (:ddkilzer) 2021-08-29 20:04:13 PDT
Created attachment 436754 [details]
Patch v1
Comment 3 David Kilzer (:ddkilzer) 2021-08-30 07:49:12 PDT
Created attachment 436771 [details]
Patch v2
Comment 4 David Kilzer (:ddkilzer) 2021-08-30 09:17:27 PDT
Created attachment 436778 [details]
Patch v3
Comment 5 David Kilzer (:ddkilzer) 2021-08-30 10:21:56 PDT
Created attachment 436790 [details]
Patch v4
Comment 6 Eric Carlson 2021-08-30 11:26:39 PDT
Comment on attachment 436790 [details]
Patch v4

r=me once the bots are happy
Comment 7 David Kilzer (:ddkilzer) 2021-08-30 13:03:17 PDT
(In reply to Eric Carlson from comment #6)
> Comment on attachment 436790 [details]
> Patch v4
> 
> r=me once the bots are happy

Failures reproduce on macOS 12 Monterey as well.
Comment 8 David Kilzer (:ddkilzer) 2021-08-30 13:14:42 PDT
I am probably going to need help figuring out why these two tests are failing, and only on macOS.  There are a lot of moving parts I'm not familiar with here.
Comment 9 David Kilzer (:ddkilzer) 2021-08-30 15:27:53 PDT
Comment on attachment 436790 [details]
Patch v4

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

> Source/WebKit/WebProcess/GPU/webrtc/LibWebRTCCodecs.cpp:434
> -    RetainPtr<CVPixelBufferRef> newPixelBuffer;
> -    auto pixelBuffer = webrtc::pixelBufferFromFrame(frame, [&newPixelBuffer](size_t width, size_t height, webrtc::BufferType bufferType) -> CVPixelBufferRef {
> +    auto pixelBuffer = adoptCF(webrtc::createPixelBufferFromFrame(frame, [](size_t width, size_t height, webrtc::BufferType bufferType) -> CVPixelBufferRef {

I think I was assuming that `newPixelBuffer` and `pixelBuffer` would be the same here, but they may not be.

This may be the cause of the regression.  Need to check into this.
Comment 10 youenn fablet 2021-08-31 01:01:48 PDT
Comment on attachment 436790 [details]
Patch v4

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

>> Source/WebKit/WebProcess/GPU/webrtc/LibWebRTCCodecs.cpp:434
>> +    auto pixelBuffer = adoptCF(webrtc::createPixelBufferFromFrame(frame, [](size_t width, size_t height, webrtc::BufferType bufferType) -> CVPixelBufferRef {
> 
> I think I was assuming that `newPixelBuffer` and `pixelBuffer` would be the same here, but they may not be.
> 
> This may be the cause of the regression.  Need to check into this.

The idea of pixelBufferFromFrame is the following:
- In most cases, there is no need to do any conversion/allocation, pixelBuffer is directly retrieved from frame which keeps ownership of the underlying pixel buffer.
- In some cases, in particular for muted frames, the pixel buffer is not I420, in which case we do need a conversion. The idea is that pixelBuffer will not own ownership on the new pixel buffer but newPixelBuffer will actually own it since it is a RetainPtr.

The easiest way to fix this would be to apply the same strategy in WebKitDecoderReceiver::Decoded as in LibWebRTCCodecs::encodeFrame or RealtimeIncomingVideoSourceCocoa.
Comment 11 youenn fablet 2021-08-31 01:10:52 PDT
Created attachment 436852 [details]
Alternative approach
Comment 12 youenn fablet 2021-08-31 01:12:26 PDT
I uploaded the alternative approach.
I am fine with your approach since that might be easier to maintain.
Comment 13 David Kilzer (:ddkilzer) 2021-08-31 11:07:54 PDT
(In reply to youenn fablet from comment #12)
> I uploaded the alternative approach.
> I am fine with your approach since that might be easier to maintain.

Thanks!  The tests clearly say that I made a logic error (logic change) in my patch, but I can't see what that is!  I may need to step away from the patch for a few days if I can't find it.
Comment 14 David Kilzer (:ddkilzer) 2021-08-31 17:48:39 PDT
(In reply to David Kilzer (:ddkilzer) from comment #13)
> (In reply to youenn fablet from comment #12)
> > I uploaded the alternative approach.
> > I am fine with your approach since that might be easier to maintain.
> 
> Thanks!  The tests clearly say that I made a logic error (logic change) in
> my patch, but I can't see what that is!  I may need to step away from the
> patch for a few days if I can't find it.

I think I see the inadvertent logic change that I made in LibWebRTCCodecs.cpp.

Testing incremental changes locally now to verify.
Comment 15 David Kilzer (:ddkilzer) 2021-08-31 19:23:59 PDT
Comment on attachment 436790 [details]
Patch v4

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

> Source/WebKit/WebProcess/GPU/webrtc/LibWebRTCCodecs.cpp:460
> -    auto sample = RemoteVideoSample::create(pixelBuffer, MediaTime(frame.timestamp_us() * 1000, 1000000), toMediaSampleVideoRotation(frame.rotation()));
> +    auto sample = RemoteVideoSample::create(pixelBuffer.get(), MediaTime(frame.timestamp_us() * 1000, 1000000), toMediaSampleVideoRotation(frame.rotation()));
>      if (!sample) {
>          // FIXME: Optimize this code path, currently we have non BGRA for muted frames at least.
> -        newPixelBuffer = convertToBGRA(pixelBuffer);
> -        sample = RemoteVideoSample::create(newPixelBuffer.get(), MediaTime(frame.timestamp_us() * 1000, 1000000), toMediaSampleVideoRotation(frame.rotation()));
> +        auto convertedPixelBuffer = convertToBGRA(pixelBuffer.get());
> +        sample = RemoteVideoSample::create(convertedPixelBuffer.get(), MediaTime(frame.timestamp_us() * 1000, 1000000), toMediaSampleVideoRotation(frame.rotation()));
>      }
>  
>      encoder.connection->send(Messages::LibWebRTCCodecsProxy::EncodeFrame { encoder.identifier, *sample, frame.timestamp(), shouldEncodeAsKeyFrame }, 0);

The logic bug I was missing was that the CVPixelBufferRef needed to be retained until after send() is called at the end of the method.

By creating a local `convertedPixelBuffer` variable, the CVPixelBufferRef is released before send() is called, causing the bug.

The fix is to reuse `pixelBuffer` to store the converted CVPixelBufferRef since its scope is the same as the send() method.
Comment 16 David Kilzer (:ddkilzer) 2021-08-31 19:27:28 PDT
Created attachment 436986 [details]
Patch v5
Comment 17 EWS 2021-09-01 13:02:12 PDT
Committed r281868 (241198@main): <https://commits.webkit.org/241198@main>

All reviewed patches have been landed. Closing bug and clearing flags on attachment 436986 [details].
Comment 18 Darin Adler 2021-09-01 13:07:06 PDT
Comment on attachment 436986 [details]
Patch v5

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

> Source/WebKit/WebProcess/GPU/webrtc/LibWebRTCCodecs.cpp:457
> +        pixelBuffer = convertToBGRA(pixelBuffer.get());
> +        sample = RemoteVideoSample::create(pixelBuffer.get(), MediaTime(frame.timestamp_us() * 1000, 1000000), toMediaSampleVideoRotation(frame.rotation()));

For future reference, don’t need a local variable for this. Intermediates are kept alive until the full expression they are part of is done, in this case until the function returns, so this would be correct:

    sample = RemoteVideoSample::create(convertToBGRA(pixelBuffer.get()).get(), MediaTime(frame.timestamp_us() * 1000, 1000000), toMediaSampleVideoRotation(frame.rotation()));

But also, is it important to reuse the variable? Could just use a new one:

    auto convertedBuffer = convertToBGRA(pixelBuffer.get());
Comment 19 David Kilzer (:ddkilzer) 2021-09-01 13:11:27 PDT
Comment on attachment 436986 [details]
Patch v5

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

>> Source/WebKit/WebProcess/GPU/webrtc/LibWebRTCCodecs.cpp:457
>> +        sample = RemoteVideoSample::create(pixelBuffer.get(), MediaTime(frame.timestamp_us() * 1000, 1000000), toMediaSampleVideoRotation(frame.rotation()));
> 
> For future reference, don’t need a local variable for this. Intermediates are kept alive until the full expression they are part of is done, in this case until the function returns, so this would be correct:
> 
>     sample = RemoteVideoSample::create(convertToBGRA(pixelBuffer.get()).get(), MediaTime(frame.timestamp_us() * 1000, 1000000), toMediaSampleVideoRotation(frame.rotation()));
> 
> But also, is it important to reuse the variable? Could just use a new one:
> 
>     auto convertedBuffer = convertToBGRA(pixelBuffer.get());

It is important to have a variable outside the scope of the `if` block to keep the CVPixelBufferRef alive until `send()` is called later in the method.  If this isn't done, it causes two test failures.

See Comment #15 for details.
Comment 20 Darin Adler 2021-09-01 13:49:40 PDT
Comment on attachment 436986 [details]
Patch v5

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

>>> Source/WebKit/WebProcess/GPU/webrtc/LibWebRTCCodecs.cpp:457
>>> +        sample = RemoteVideoSample::create(pixelBuffer.get(), MediaTime(frame.timestamp_us() * 1000, 1000000), toMediaSampleVideoRotation(frame.rotation()));
>> 
>> For future reference, don’t need a local variable for this. Intermediates are kept alive until the full expression they are part of is done, in this case until the function returns, so this would be correct:
>> 
>>     sample = RemoteVideoSample::create(convertToBGRA(pixelBuffer.get()).get(), MediaTime(frame.timestamp_us() * 1000, 1000000), toMediaSampleVideoRotation(frame.rotation()));
>> 
>> But also, is it important to reuse the variable? Could just use a new one:
>> 
>>     auto convertedBuffer = convertToBGRA(pixelBuffer.get());
> 
> It is important to have a variable outside the scope of the `if` block to keep the CVPixelBufferRef alive until `send()` is called later in the method.  If this isn't done, it causes two test failures.
> 
> See Comment #15 for details.

Makes sense. Seems like we have a small design problem that we should return and fix. RemoteVideoSample has a dangerous interface if it takes a raw pointer and the caller needs to keep it alive.
Comment 21 David Kilzer (:ddkilzer) 2021-09-02 08:50:22 PDT
(In reply to Darin Adler from comment #20)
> Makes sense. Seems like we have a small design problem that we should return
> and fix. RemoteVideoSample has a dangerous interface if it takes a raw
> pointer and the caller needs to keep it alive.

Bug 229806: RemoteVideoSample needs CVPixelBufferRef to be kept alive, but relies on caller to retain it