[libcamera-devel] [PATCH v3 05/11] libcamera: request: A request canary

Laurent Pinchart laurent.pinchart at ideasonboard.com
Fri Mar 26 03:51:41 CET 2021


Hi Kieran,

Thank you for the patch.

On Thu, Mar 25, 2021 at 01:42:25PM +0000, Kieran Bingham wrote:
> Request objects are created and owned by the application, but are of
> course utilised widely throughout the internals of libcamera.
> 
> If the application free's the requests while they are still active
> within libcamera a use after free will occur. While this can be trapped
> by tools such as valgrind, given the importance of this object and the
> relationship of external ownership, it may have some value to provide
> Debug build (disabled at Release build) assertions on the condition of
> these objects.
> 
> Make sure the request fails an assertion immediately if used after free.
> 
> Signed-off-by: Kieran Bingham <kieran.bingham at ideasonboard.com>
> 
> ---
> I've removed the RFC from this, and I actually even more so believe this
> is a good facility to provide on the Request object.
> 
> Mostly because the Request object is the only object which is given to
> libcamera from the application (yes, created by libcamera, but that's
> separate), and is expected to be free'd by the application.
> 
> If an application free's requests while they are still in use within
> libcamera, the symptoms can be distinctly misleading and lead to rabbit
> holes.
> 
> Therefore, I think the Request object is the one place where extra
> safety checking (in debug builds) is worth while.

Should the canary_ member be compiled out in non-debug builds ?

> Yes, of course if an application free's a request while it's in use with
> libcamera - then it's the applications fault, not ours - but because of
> the nature of requests, this could be an easy trap to fall into - and I
> don't want to find that reported as bugs in libcamera.

When Request will become Extensible, do you think the canary_ field
could be moved to the Private class ? That would make me feel better
about this feature :-)

Actually, maybe we could use the d_ pointer and o_ pointer to replace
the canary, but checking the point to each other ?

> ---
>  include/libcamera/request.h |  2 ++
>  src/libcamera/request.cpp   | 18 ++++++++++++++++++
>  2 files changed, 20 insertions(+)
> 
> diff --git a/include/libcamera/request.h b/include/libcamera/request.h
> index 4cf5ff3f7d3b..965ffa6b45b2 100644
> --- a/include/libcamera/request.h
> +++ b/include/libcamera/request.h
> @@ -79,6 +79,8 @@ private:
>  	const uint64_t cookie_;
>  	Status status_;
>  	bool cancelled_;
> +
> +	uint32_t canary_;
>  };
>  
>  } /* namespace libcamera */
> diff --git a/src/libcamera/request.cpp b/src/libcamera/request.cpp
> index 7b7ef6814686..c4258480b12b 100644
> --- a/src/libcamera/request.cpp
> +++ b/src/libcamera/request.cpp
> @@ -19,6 +19,8 @@
>  #include "libcamera/internal/log.h"
>  #include "libcamera/internal/tracepoints.h"
>  
> +#define REQUEST_CANARY 0x1F2E3D4C
> +
>  /**
>   * \file request.h
>   * \brief Describes a frame capture request to be processed by a camera
> @@ -90,6 +92,8 @@ Request::Request(Camera *camera, uint64_t cookie)
>  
>  	LIBCAMERA_TRACEPOINT(request_construct, this);
>  
> +	canary_ = REQUEST_CANARY;

You can move this to the member initializer list.

> +
>  	LOG(Request, Debug) << "Created request - cookie: " << cookie_;
>  }
>  
> @@ -100,6 +104,8 @@ Request::~Request()
>  	delete metadata_;
>  	delete controls_;
>  	delete validator_;
> +
> +	canary_ = 0;
>  }
>  
>  /**
> @@ -114,6 +120,8 @@ Request::~Request()
>   */
>  void Request::reuse(ReuseFlag flags)
>  {
> +	ASSERT(canary_ == REQUEST_CANARY);
> +
>  	LIBCAMERA_TRACEPOINT(request_reuse, this);
>  
>  	pending_.clear();
> @@ -179,6 +187,8 @@ void Request::reuse(ReuseFlag flags)
>   */
>  int Request::addBuffer(const Stream *stream, FrameBuffer *buffer)
>  {
> +	ASSERT(canary_ == REQUEST_CANARY);
> +
>  	if (!stream) {
>  		LOG(Request, Error) << "Invalid stream reference";
>  		return -EINVAL;
> @@ -214,6 +224,8 @@ int Request::addBuffer(const Stream *stream, FrameBuffer *buffer)
>   */
>  FrameBuffer *Request::findBuffer(const Stream *stream) const
>  {
> +	ASSERT(canary_ == REQUEST_CANARY);
> +
>  	const auto it = bufferMap_.find(stream);
>  	if (it == bufferMap_.end())
>  		return nullptr;
> @@ -281,6 +293,7 @@ FrameBuffer *Request::findBuffer(const Stream *stream) const
>   */
>  void Request::complete()
>  {
> +	ASSERT(canary_ == REQUEST_CANARY);
>  	ASSERT(status_ == RequestPending);
>  	ASSERT(!hasPendingBuffers());
>  
> @@ -306,6 +319,8 @@ void Request::complete()
>   */
>  bool Request::completeBuffer(FrameBuffer *buffer)
>  {
> +	ASSERT(canary_ == REQUEST_CANARY);
> +
>  	LIBCAMERA_TRACEPOINT(request_complete_buffer, this, buffer);
>  
>  	int ret = pending_.erase(buffer);
> @@ -326,6 +341,9 @@ std::string Request::toString() const
>  	/* Pending, Completed, Cancelled(X). */
>  	static const char *statuses = "PCX";
>  
> +	if (canary_ != REQUEST_CANARY)
> +		return "Invalid Canary on Request";

As all bets are off in this case, should we ASSERT() to catch this
condition as early as possible ?

> +
>  	/* Example Output: Request(55:P:1/2:6523524) */
>  	ss << "Request (" << sequence_ << ":" << statuses[status_] << ":"
>  	   << pending_.size() << "/" << bufferMap_.size() << ":"

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list