[PATCH 1 of 1] HTTP: Fix infinite loop on NGX_DECLINED in ngx_http_finalize_request
Will Hawkins
hawkinsw at obs.cr
Sun Sep 15 19:50:42 UTC 2024
On Sun, Sep 15, 2024 at 12:50 AM Maxim Dounin <mdounin at mdounin.ru> wrote:
>
> Hello!
>
> On Sat, Sep 14, 2024 at 11:46:09PM -0400, Will Hawkins wrote:
>
> > On Sat, Sep 14, 2024 at 11:13 PM Maxim Dounin <mdounin at mdounin.ru> wrote:
> > >
> > > Hello!
> > >
> > > On Fri, Sep 13, 2024 at 06:11:31PM -0400, Will Hawkins wrote:
> > >
> > > > On Fri, Sep 13, 2024 at 11:32 AM Maxim Dounin <mdounin at mdounin.ru> wrote:
> > > > >
> > > > > Hello!
> > > > >
> > > > > On Fri, Sep 13, 2024 at 01:20:34AM -0400, Will Hawkins wrote:
> > > > >
> > > > > > # HG changeset patch
> > > > > > # User Will Hawkins <hawkinsw at obs.cr>
> > > > > > # Date 1726202944 14400
> > > > > > # Fri Sep 13 00:49:04 2024 -0400
> > > > > > # Node ID 5bfd931f3b9641b51344d437207134f094012de5
> > > > > > # Parent dbf76fdd109fbbba40a7c5299cc277d180f4bbad
> > > > > > HTTP: Fix infinite loop on NGX_DECLINED in ngx_http_finalize_request
> > > > > >
> > > > > > A handler that invokes ngx_http_finalize_request with NGX_DECLINED
> > > > > > causes an infinite loop because the phase handler index is not
> > > > > > incremented before restarting the processing of phases.
> > > > > >
> > > > > > In (almost) all the other instances where a handler can return
> > > > > > NGX_DECLINED, the phase handler index is incremented before restarting
> > > > > > the processing of phases.
> > > > > >
> > > > > > This change adds that index increment where it was missing.
> > > > > >
> > > > > > diff -r dbf76fdd109f -r 5bfd931f3b96 src/http/ngx_http_request.c
> > > > > > --- a/src/http/ngx_http_request.c Tue Sep 03 13:11:25 2024 +0300
> > > > > > +++ b/src/http/ngx_http_request.c Fri Sep 13 00:49:04 2024 -0400
> > > > > > @@ -2519,6 +2519,7 @@
> > > > > > if (rc == NGX_DECLINED) {
> > > > > > r->content_handler = NULL;
> > > > > > r->write_event_handler = ngx_http_core_run_phases;
> > > > > > + r->phase_handler++;
> > > > > > ngx_http_core_run_phases(r);
> > > > > > return;
> > > > > > }
> > > > > >
> > > > >
> > > > > The ngx_http_finalize_request(NGX_DECLINED) call (or return
> > > > > NGX_DECLINED) is expected to be used by location content handlers,
> > > > > as set by r->content_handler (clcf->handler), to switch back to
> > > > > phase handlers. Note that it clears r->content_handler, so
> > > > > ngx_http_core_content_phase() will resume normal content phase
> > > > > processing.
> > > > >
> > > > > For example, perl module does this when you return NGX_DECLINED
> > > > > from the perl code.
> > > > >
> > > > > And incrementing r->phase_handler will certainly screw up things for
> > > > > this use case.
> > > > >
> > > > > Could you please clarify why you think that calling
> > > > > ngx_http_finalize_request(NGX_DECLINED) in other cases might be
> > > > > needed, and not a bug in the module which does this?
> > > > >
> > > >
> > > > Thank you for the explanation!
> > > >
> > > > After further reflection, I realized that my module was attempting to
> > > > incorrectly participate in the "ecosystem". I was writing my module to
> > > > be a (core) content handler rather than a location content handler. I
> > > > was doing this in an attempt to make it possible for my content
> > > > handler to give an NGX_DECLINED with the effect of giving "other"
> > > > content handlers a chance to respond. As I understand it, with
> > > > location content handlers, only one is allowed. I tried a
> > > > configuration with my module active and 'empty_gif;' and their
> > > > relative order in the location block affected which module was deemed
> > > > the content handler for that block.
> > > >
> > > > It would be cool if there were a way for the module author to chain
> > > > location handlers.
> > > >
> > > > I suppose that is not out of the realm of possibility but each module
> > > > would have to implement it themselves, it seems. A module could store
> > > > the existing value of the location content handler during execution of
> > > > the function for handling configuration information. Then, when their
> > > > handler is invoked, they could immediately dispatch to the stored
> > > > content handler in the case that they wanted to decline
> > > > responsibility.
> > >
> > > Not sure what exactly you are trying to do, but if you consider
> > > chaining your module with other modules, content phase handler
> > > might be more appropriate. Alternatively, a location content
> > > handler which generates an error for requests it cannot handle
> > > might be an option, so it would be possible to configure
> > > error_page processing if needed.
> > >
> > > In general there are two types of content handlers:
> > >
> > > 1. Content phase handlers, such as in the dav, index, autoindex,
> > > and static modules. Such modules are configured globally, and
> > > settings are inherited into more specific contexts, such as
> > > locations or nested locations. These modules can decline the
> > > request by returning NGX_DECLINED from the handler, so the request
> > > processing is passed to the next content phase handler.
> >
> > Thank you for the response! I really appreciate it. This was (is?)
> > exactly what I was trying to do. As I said, however, if the handler
> > cannot make the decision to handle the response until after reading
> > the body, it cannot return NGX_DECLINED from the handler. It would
> > have to return something else (e.g., NGX_OK) while waiting for the
> > callback given to ngx_http_read_client_request_body to execute to
> > examine the body. Then, inside that callback, the only option would be
> > to call ngx_http_finalize_request(NGX_DECLINED). At that point, we
> > would have the infinite loop that I experienced. That was one of the
> > reasons that I thought the fix I submitted would be helpful. In other
> > words, the kind of handler I am writing would not be working in a
> > situation where r->content_handler has a value.
> >
> > So, would it be possible to have a second version of my proposed patch
> > that would change the relevant part of ngx_http_finalize_request to
> > look like:
> >
> > if (rc == NGX_DECLINED) {
> > if (r->content_handler == NULL) {
> > r->phase_handler++;
> > }
> > r->content_handler = NULL;
> > r->write_event_handler = ngx_http_core_run_phases;
> > ngx_http_core_run_phases(r);
> > return;
> > }
> >
> > That might make possible both situations:
> > 1. Preserve the existing behavior for location content handlers (like
> > the perl module the way that you mentioned)
> > 2. Add support for returning NGX_DECLINED in content phase handlers.
> >
> > If that seems reasonable, I would be more than happy to modify the
> > patch and submit!
>
> The specific ngx_http_finalize_request(NGX_DECLINED) handling is
> for location content handlers. While it probably can be extended
> to support content phase (since r->content_handler is expected to
> be NULL at the content phase), it won't work for other phases
> anyway. (And the required code would be more complex, see
> ngx_http_core_content_phase() - it needs to check if there are
> additional content phase handlers, and return 403/404 if not.)
>
> As such, I would rather recommend generic approach to resume phase
> processing, much like it is used in other phase handlers: restore
> ngx_http_core_run_phases() as a write handler, run it, and then
> return NGX_DECLINED from the handler.
>
> (Note that just calling ngx_http_finalize_request(NGX_DECLINED)
> and then returning NGX_DECLINED from the handler would be mostly
> equivalent for a content phase handler, but I would rather
> recommend restoring ngx_http_core_run_phases explicitly.)
>
> Examples can be seen in the limit_req and mirror modules. The
> mirror module specifically reads the request body and can be used
> as a guidance on how to properly restore phase processing after
> reading the request body.
>
> (Note though that the mirror module runs in the precontent phase,
> and therefore needs additional steps to properly pause phase
> processing; for a content phase handler, just
>
> rc = ngx_http_read_client_request_body(r, ngx_http_foo_handler);
>
> if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
> return rc;
> }
>
> return NGX_DONE;
>
> as normally seen in content handlers would be the way to go.)
>
Thank you (again!) for the thorough response. I really appreciate it.
It might take me a day or two, but I would like to experiment with
preparing another version of the patch that incorporates your advice
by creating a function that would take the necessary steps to "fixup"
the request so that returning an NGX_DECLINED triggers processing in
the next phase.
Thank you again for the response!
Will
> --
> Maxim Dounin
> http://mdounin.ru/
More information about the nginx-devel
mailing list