[libcamera-devel] [PATCH] libcamera: gst: Fix double-free when acquire_buffer fails
Marian Cichy
mci at pengutronix.de
Wed Mar 10 19:12:45 CET 2021
On 3/10/21 6:53 PM, Marian Cichy wrote:
>
> On 3/10/21 6:30 PM, Laurent Pinchart wrote:
>> Hi Marian
>>
>> Thank you for the patch.
>>
>> On Wed, Mar 10, 2021 at 08:35:31PM +0530, Umang Jain wrote:
>>> On 3/9/21 8:05 PM, Marian Cichy wrote:
>>>> If gst_buffer_pool_acquire_buffer in gst_libcamera_task_run fails, the
>>>> unique_ptr to the request-object gets reset and hence, its destructor
>>>> is called. However, the wrap-object points to the same object and is
>>>> still alive at this moment. When the task_run-function is finished,
>>>> the
>>>> destructor of the wrap-object is called, which in return calls the
>>>> destructor of the request-object again.
>>>>
>>>> Also note the wrong comment, which claims that WrapRequest does not
>>>> take ownership of the request, however, actually it already has
>>>> ownership.
>>>>
>>>> Replacing request.reset() with request.release() doesn't call the
>>>> destructor on the request-object and only one free happens at the end.
>>>>
>>>> Signed-off-by: Marian Cichy <m.cichy at pengutronix.de>
>>>> ---
>>>> src/gstreamer/gstlibcamerasrc.cpp | 6 ++++--
>>>> 1 file changed, 4 insertions(+), 2 deletions(-)
>>>>
>>>> diff --git a/src/gstreamer/gstlibcamerasrc.cpp
>>>> b/src/gstreamer/gstlibcamerasrc.cpp
>>>> index a8ed7652..b0194c2f 100644
>>>> --- a/src/gstreamer/gstlibcamerasrc.cpp
>>>> +++ b/src/gstreamer/gstlibcamerasrc.cpp
>>>> @@ -279,10 +279,12 @@ gst_libcamera_src_task_run(gpointer user_data)
>>>> &buffer, nullptr);
>>>> if (ret != GST_FLOW_OK) {
>>>> /*
>>>> - * RequestWrap does not take ownership, and we won't be
>>>> + * RequestWrap has ownership, and we won't be
>>>> * queueing this one due to lack of buffers.
>>>> + * So the request will be freed when RequestWrap
>>>> + * goes out of scope.
>>>> */
>>>> - request.reset();
>>>> + request.release();
>>> This is a bit problematic as `request` is still in-used beyond this
>>> line(there is `if (request)` block below) and here it will set it as
>>> NULL. You will need to take care of this situation using the
>>> RequestWrap
>>> probably. However, one thing I still am not sure of ...
>>>
>>>> break;
>>>> }
>>> I am not sure if why `RequestWrap` is written intentionally in a
>>> way, to
>>> transfer the ownership of the `Request`. This fact is quite evident
>>> looking at the ~RequestWrap(). The larger picture here is (which is
>>> why
>>> you hit this double-free issue) that, whenever RequestWrap is used -
>>> always transfer ownership of Request. So, that's a 2-step manual in
>>> order to use the wrap; if the latter is forgotten - we will always hit
>>> double free issues.
>>>
>>> Can you please look how the RequestWrap is used elsewhere in the
>>> gstreamer plugin and try to see why transfer of ownership is
>>> intented in
>>> RequestWrap? If it's not essential, we should probably re-fine
>>> RequestWrap to really be a "wrapper" and not try to steal the reference
>>> - and take the additional responsibility of free-ing it.
>> The request belongs to the gstreamer element, so we need to keep track
>> of it and free it. Request instances are managed with std::unique_ptr<>,
>> so the best option I think would be to simple move the request to the
>> wrap, using a unique pointer there.
>>
>> How about this untested change ?
>>
>> diff --git a/src/gstreamer/gstlibcamerasrc.cpp
>> b/src/gstreamer/gstlibcamerasrc.cpp
>> index 636c14dff64e..e86c3d7f74c7 100644
>> --- a/src/gstreamer/gstlibcamerasrc.cpp
>> +++ b/src/gstreamer/gstlibcamerasrc.cpp
>> @@ -52,19 +52,19 @@ GST_DEBUG_CATEGORY_STATIC(source_debug);
>> #define GST_CAT_DEFAULT source_debug
>> struct RequestWrap {
>> - RequestWrap(Request *request);
>> + RequestWrap(std::unique_ptr<Request> request);
>> ~RequestWrap();
>> void attachBuffer(GstBuffer *buffer);
>> GstBuffer *detachBuffer(Stream *stream);
>> /* For ptr comparison only. */
>> - Request *request_;
>> + std::unique_ptr<Request> request_;
>> std::map<Stream *, GstBuffer *> buffers_;
>> };
>> -RequestWrap::RequestWrap(Request *request)
>> - : request_(request)
>> +RequestWrap::RequestWrap(std::unique_ptr<Request> request)
>> + : request_(std::move(request))
>> {
>> }
>> @@ -74,8 +74,6 @@ RequestWrap::~RequestWrap()
>> if (item.second)
>> gst_buffer_unref(item.second);
>> }
>> -
>> - delete request_;
>> }
>> void RequestWrap::attachBuffer(GstBuffer *buffer)
>> @@ -164,7 +162,7 @@ GstLibcameraSrcState::requestCompleted(Request
>> *request)
>> std::unique_ptr<RequestWrap> wrap = std::move(requests_.front());
>> requests_.pop();
>> - g_return_if_fail(wrap->request_ == request);
>> + g_return_if_fail(wrap->request_.get() == request);
>> if ((request->status() == Request::RequestCancelled)) {
>> GST_DEBUG_OBJECT(src_, "Request was cancelled");
>> @@ -268,8 +266,7 @@ gst_libcamera_src_task_run(gpointer user_data)
>> GstLibcameraSrc *self = GST_LIBCAMERA_SRC(user_data);
>> GstLibcameraSrcState *state = self->state;
>> - std::unique_ptr<Request> request = state->cam_->createRequest();
>> - auto wrap = std::make_unique<RequestWrap>(request.get());
>> + auto wrap =
>> std::make_unique<RequestWrap>(state->cam_->createRequest());
>> for (GstPad *srcpad : state->srcpads_) {
>> GstLibcameraPool *pool = gst_libcamera_pad_get_pool(srcpad);
>> GstBuffer *buffer;
>> @@ -279,24 +276,23 @@ gst_libcamera_src_task_run(gpointer user_data)
>> &buffer, nullptr);
>> if (ret != GST_FLOW_OK) {
>> /*
>> - * RequestWrap does not take ownership, and we won't be
>> - * queueing this one due to lack of buffers.
>> + * RequestWrap has ownership of the rquest, and we
>> + * won't be queueing this one due to lack of buffers.
>> */
>> - request.reset();
>> + wrap.release();
>> break;
>> }
>> wrap->attachBuffer(buffer);
>> }
>> - if (request) {
>> + if (wrap) {
>> GLibLocker lock(GST_OBJECT(self));
>> GST_TRACE_OBJECT(self, "Requesting buffers");
>> - state->cam_->queueRequest(request.get());
>> + state->cam_->queueRequest(wrap->request_.get());
>
>
> Did you mean
>
> state->cam_->queueRequest(wrap->request_)
>
> the wrapped request is not a unique_ptr here.
>
>
> With this change, I tested both the success and the error path and it
> looks fine. However, I think there should be a printed message (maybe
> GST_WARNING) when we get in the error path, as there is no indicator
> now that buffer acquistion failed.
>
>
> Regards,
>
> Marian
>
>
Forget about this. My Mail reader decided to clip half of the message.
Works fine, but there should still be an Error message if buffer
acquisition failed.
Regards,
Marian
>> state->requests_.push(std::move(wrap));
>> - /* The request will be deleted in the completion handler. */
>> - request.release();
>> + /* The RequestWrap will be deleted in the completion
>> handler. */
>> }
>> GstFlowReturn ret = GST_FLOW_OK;
>>
>>
More information about the libcamera-devel
mailing list