Squid is able to process Edge Side Includes (ESI) pages when it acts as a reverse proxy. By default, this processing is enabled by default, and can be ‘triggered’ by any page whose response can be manipulated to return a single response header, and content. ESI is an xml-based markup language which, in layman terms, can be used to cache specific parts of a single webpage, thus allowing for the caching of static content on an otherwise dynamically created page.
The ESI syntax includes a directive called <esi:when>
. This directive is used to test for boolean conditions. For example, <esi:when test="$(HTTP_USER_AGENT{'os'})=='iPhone'">
could be used to check whether a request’s user agent is identifying itself as an iPhone.
The issue is that when the condition 0
is tested, a discrepancy in the ESI code handles the character differently. Squid uses the getsymbol
function to parse the test condition, and determine what type of test is being conducted, such as a logical AND, NOT, LESS THAN, MORE THAN, etc. For the character 0
, Squid interprets this as an integer to be tested against something. However, it is eventually passed to the function ESIExpression::Evaluate
, which in some cases, expects a string literal (which is converted to a ESI_EXPR_EXPR type) (rather than an integer). In this specific case of a single 0
, an assertion will occur. The code for ESIExpression::Evaluate
is:
int
ESIExpression::Evaluate(char const *s)
{
stackmember stack[ESI_STACK_DEPTH_LIMIT];
int stackdepth = 0;
char const *end;
if (stackdepth > 1) {
stackmember rv;
rv.valuetype = ESI_EXPR_INVALID;
rv.precedence = 0;
if (stack[stackdepth - 2].
eval(stack, &stackdepth, stackdepth - 2, &rv)) {
/* special case - leading operator failed */
debugs(86, DBG_IMPORTANT, "invalid expression");
PROF_stop(esiExpressionEval);
return 0;
}
}
if (stackdepth == 0) {
/* Empty expression - evaluate to false */
PROF_stop(esiExpressionEval);
return 0;
}
/* if we hit here, we think we have a valid result */
assert(stackdepth == 1);
assert(stack[0].valuetype == ESI_EXPR_EXPR);
return stack[0].value.integral ? 1 : 0;
Here, we can see that if stackdepth == 1
to begin with, which is the case for the simple case of test="0"
, nearly none of this function will actually execute. However, because 0
is interpreted as a an integer, the assertion assert(stack[0].valuetype == ESI_EXPR_EXPR);
will fail.
A simple reproducer is a reverse-proxied page with the following response:
<l xmlns:esi="http://www.edge-delivery.org/esi/1.0"><esi:when test="0">
Perhaps the bigger issue here is that testing for 0 is (and should be) a completely valid test: it just simply means always false. This could be for debugging reasons, or there could be some other script which is setting this to 0 upstream.