[PATCH] QUIC: fixed post-close use-after-free
Sergey Kandaurov
pluknet at nginx.com
Mon May 22 12:45:16 UTC 2023
> On 22 May 2023, at 16:00, Roman Arutyunyan <arut at nginx.com> wrote:
>
> On Mon, May 22, 2023 at 03:28:24PM +0400, Sergey Kandaurov wrote:
>>
>>> On 21 May 2023, at 13:20, Roman Arutyunyan <arut at nginx.com> wrote:
>>>
>>> # HG changeset patch
>>> # User Roman Arutyunyan <arut at nginx.com>
>>> # Date 1684659758 -14400
>>> # Sun May 21 13:02:38 2023 +0400
>>> # Node ID 321b69f47a0a9874a063e464d13706339a706bb8
>>> # Parent 235d482ef6bc8c40a956b2413865d42c94e0fc05
>>> QUIC: fixed post-close use-after-free.
>>>
>>> Previously, ngx_quic_close_connection() could be called in a way that QUIC
>>> connection was accessed after the call. In most cases the connection is not
>>> closed right away, but close timeout is scheduled. However, it's not always
>>> the case. Also, if the close process started earlier for a different reason,
>>> calling ngx_quic_close_connection() may actually close the connection. The
>>> connection object should not be accessed after that. Particularly, accessing
>>> c->pool can result in null pointer dereference.
>>
>> The last sentence doesn't seem correct.
>> For that, c->pool should be NULL on destroy; not sure if we do this.
>
> Indeed. Removed this sentence.
>
>>> Now, when possible, return statement is added to eliminate post-close connection
>>> object access. In other places ngx_quic_close_connection() is substituted with
>>> posting close event.
>>>
>>> Prodded by Coverity (CID 1530402).
>>>
>>> diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c
>>> --- a/src/event/quic/ngx_event_quic.c
>>> +++ b/src/event/quic/ngx_event_quic.c
>>> @@ -844,7 +844,7 @@ ngx_quic_handle_packet(ngx_connection_t
>>> "quic stateless reset packet detected");
>>>
>>> qc->draining = 1;
>>> - ngx_quic_close_connection(c, NGX_OK);
>>> + ngx_post_event(&qc->close, &ngx_posted_events);
>>>
>>> return NGX_OK;
>>> }
>>
>> Stateless reset mimics a short packet and uses an entire datagram.
>> We handle short packets accordingly, expecting no further packets.
>> So there should be no issues with synchronous connection close,
>> except perhaps that we return NGX_OK here for seemingly no reason.
>> This theoretically may involve additional actions leading to post-close
>> use-after-free.
>
> We can return NGX_DONE, but it's still not good for calling functions to
> reference connection that's already freed. In current code this will
> result in logging packet handling result in a freed c->log. But even if
> we fix this, having a freed connection will one day lead to a problem.
>
>>> @@ -1390,7 +1390,7 @@ ngx_quic_handle_frames(ngx_connection_t
>>>
>>> if (do_close) {
>>> qc->draining = 1;
>>> - ngx_quic_close_connection(c, NGX_OK);
>>> + ngx_post_event(&qc->close, &ngx_posted_events);
>>> }
>>>
>>> if (pkt->path != qc->path && nonprobing) {
>>
>> Not sure if this one is practicable (but I don't object).
>> To make this call close connection immediately, qc->closing
>> should be already set but close timer unset. To speculate,
>> ngx_quic_close_connection() could be previously called with
>> some other rc, and ngx_quic_close_streams() returned NGX_AGAIN.
>
> Yes, that's unlikely, but possible.
>
>>> diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c
>>> --- a/src/event/quic/ngx_event_quic_ack.c
>>> +++ b/src/event/quic/ngx_event_quic_ack.c
>>> @@ -806,6 +806,7 @@ void ngx_quic_lost_handler(ngx_event_t *
>>>
>>> if (ngx_quic_detect_lost(c, NULL) != NGX_OK) {
>>> ngx_quic_close_connection(c, NGX_ERROR);
>>> + return;
>>> }
>>>
>>> ngx_quic_connstate_dbg(c);
>>> diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c
>>> --- a/src/event/quic/ngx_event_quic_streams.c
>>> +++ b/src/event/quic/ngx_event_quic_streams.c
>>> @@ -1084,7 +1084,8 @@ ngx_quic_stream_cleanup_handler(void *da
>>> {
>>> ngx_connection_t *c = data;
>>>
>>> - ngx_quic_stream_t *qs;
>>> + ngx_quic_stream_t *qs;
>>> + ngx_quic_connection_t *qc;
>>>
>>> qs = c->quic;
>>>
>>> @@ -1092,16 +1093,23 @@ ngx_quic_stream_cleanup_handler(void *da
>>> "quic stream id:0x%xL cleanup", qs->id);
>>>
>>> if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) {
>>> - ngx_quic_close_connection(c, NGX_ERROR);
>>> - return;
>>> + goto failed;
>>> }
>>>
>>> qs->connection = NULL;
>>>
>>> if (ngx_quic_close_stream(qs) != NGX_OK) {
>>> - ngx_quic_close_connection(c, NGX_ERROR);
>>> - return;
>>> + goto failed;
>>> }
>>> +
>>> + return;
>>> +
>>> +failed:
>>> +
>>> + qc = ngx_quic_get_connection(qs->parent);
>>> + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR;
>>> +
>>> + ngx_post_event(&qc->close, &ngx_posted_events);
>>> }
>>>
>>>
>>
>> Another problem here is that ngx_quic_close_connection() was
>> called with stream connection, with unpleasant consequences.
>> You might want to reflect this in the changelog.
>
> Thanks for noticing this. The full commit log:
>
> QUIC: fixed post-close use-after-free.
>
> Previously, ngx_quic_close_connection() could be called in a way that QUIC
> connection was accessed after the call. In most cases the connection is not
> closed right away, but close timeout is scheduled. However, it's not always
> the case. Also, if the close process started earlier for a different reason,
> calling ngx_quic_close_connection() may actually close the connection. The
> connection object should not be accessed after that.
>
> Now, when possible, return statement is added to eliminate post-close connection
> object access. In other places ngx_quic_close_connection() is substituted with
> posting close event.
>
> Also, the new way of closing connection in ngx_quic_stream_cleanup_handler()
> fixes another problem in this function. Previously it passed stream connection
> instead of QUIC connection to ngx_quic_close_connection(). This could result
> in incomplete connection shutdown. One consequence of that could be that QUIC
> streams were freed without shutting down their application contexts. This could
> result in another use-after-free.
>
> Found by Coverity (CID 1530402).
>
Good for me.
--
Sergey Kandaurov
More information about the nginx-devel
mailing list