The cache applet's I/O handler leaves through the "exit" label when it
cannot allocate its input or output buffer, when one of these buffers is
still pending or when the output buffer is full. But "exit" eats the whole
request with b_reset(&appctx->inbuf), and the request can still be needed on
the next call. For instance, in the HTX_CACHE_HEADER state, the handler
reads the request start line to know whether it is a HEAD request.
The input buffer is empty at this point, and appctx_release_buffers() also
frees it while it has no data, so http_get_stline() may return NULL and the
process crashes. This requires the output buffer allocation to fail exactly
after the request was received, which is rare since the buffers reserve
covers most transient failures, hence a latent bug.
Add a dedicated "wait" label for these paths: it flushes the pending HTX
message to the output buffer and returns without touching the request. The
"exit" label is kept for the states where the request must be consumed:
EOS/error, fast-forward blocked by the consumer, and the end of the
response.
This was introduced when the applet got its own buffers by commit
863417292
("MAJOR: cache: Update HTTP cache applet to handle its own buffers") in
3.0-dev3, so the fix should be backported to 3.0.
A debug patch forcing a single output buffer allocation failure on the
first call in the HTX_CACHE_HEADER state reproduced the crash with ASAN
on the htx_sl_req_meth() line; with this fix, the GET and HEAD requests
all succeed and the process stays alive.
size_t ret;
if (applet_fl_test(appctx, APPCTX_FL_INBLK_ALLOC|APPCTX_FL_OUTBLK_ALLOC|APPCTX_FL_OUTBLK_FULL))
- goto exit;
+ goto wait;
if (applet_fl_test(appctx, APPCTX_FL_FASTFWD) && se_fl_test(appctx->sedesc, SE_FL_MAY_FASTFWD_PROD))
goto exit;
}
if (!appctx_get_buf(appctx, &appctx->outbuf)) {
- goto exit;
+ goto wait;
}
if (unlikely(applet_fl_test(appctx, APPCTX_FL_EOS|APPCTX_FL_ERROR))) {
struct ist meth;
if (unlikely(applet_fl_test(appctx, APPCTX_FL_INBLK_ALLOC))) {
- goto exit;
+ goto wait;
}
/* Headers must be dump at once. Otherwise it is an error */
appctx->sedesc->iobuf.flags &= ~IOBUF_FL_FF_BLOCKED;
return;
+ wait:
+ /* The output buffer is not available yet. Leave the request in place:
+ * its start line is read to know whether it is a HEAD request. */
+ if (res_htx)
+ htx_to_buf(res_htx, &appctx->outbuf);
+ return;
+
wait_request:
/* Wait for the request before starting to deliver the response */
applet_need_more_data(appctx);