httpd-38070

BUG link:

https://issues.apache.org/bugzilla/show_bug.cgi?id=38070

Version:

2.2.0

How to reproduce?

See symptom below:

Symptom:
Use Apache to serve a cgi script (perl) as below:

#!/usr/bin/perl

print "Status: 200\n";

print "Last-Modified: Tue, 15 Feb 2005 15:00:00 GMT\n";

print "Content-Type: text/html\n\n";

print "Hello world\n"

When browser access the this cgi program multiple times,

this script should always print “Hello world” on the browser.

However, in the incorrect execution:
1st time, everything is good.


2nd time, httpd returns status code 200 without BODY and logged
status 304 in accesslog.  Blank screen is displayed.

Root cause:

The problem is that the script contains the following two lines:

print "Status: 200\n";

print "Last-Modified: Tue, 15 Feb 2005 15:00:00 GMT\n";

This would trick httpd in the 2nd time to think the script is ‘304’ and should return an empty response body.

Here is the logic in “ap_scan_script_header_err_core”, which is to parse the output of cgi script:

It meant to call “ap_meets_condition” only if “cgi_status” is not explicitly set in the header. Otherwise, if the “Status: XXX” is contained in the header, then we will set the cgi_status on later line “r->status=cgi_status=atoi(l)”.

So the key is to know:

1. the highlighted condition passed the check, and ap_meets_conditions is called, with ‘cond_status = 304’.

2. that the “cgi_status == HTTP_OK” is set at line X1.

patch:

--- httpd/httpd/branches/2.2.x/server/util_script.c        374894
+++ httpd/httpd/branches/2.2.x/server/util_script.c        374895
AP_DECLARE(int) ap_scan_script_header_err_core(... ...) {
@@ -405,7 +407,7 @@ AP_DECLARE(int) ap_scan_script_header_er

-    int cgi_status = HTTP_OK;
+    int cgi_status = HTTP_UNSET;
     apr_table_t *merge;
    apr_table_t *cookie_table;
   

     … …

    while (1) {

        … ...
@@ -466,7 +468,18 @@ AP_DECLARE(int) ap_scan_script_header_er
        if (w[0] == '\0') {
            int cond_status = OK;

-            if ((cgi_status == HTTP_OK) && (r->method_number == M_GET)) {
+           /* PR#38070: This fails because it gets confused when a
+            * CGI Status header overrides ap_meets_conditions.
+            *
+            * We can fix that by dropping ap_meets_conditions when
+            * Status has been set.  Since this is the only place
+            * cgi_status gets used, let's test it explicitly.
+            *
+            * The alternative would be to ignore CGI Status when
+            * ap_meets_conditions returns anything interesting.
+            * That would be safer wrt HTTP, but would break CGI.
+            */
+            if ((cgi_status == HTTP_UNSET) && (r->method_number == M_GET)) {
                 cond_status = ap_meets_conditions(r);

        // ap_meets_conditions will set cond_status to ‘304’ the 2nd time,

        // because the HTTP header contains “Last-Modified: a-past-date”.

        // It should return cond_status ‘200’ since set explicitly in script!

                …

480:                 return cond_status;
            }
       }

       … ...
       else if (!strcasecmp(w, "Status")) {

           r->status = cgi_status = atoi(l);

         // Here will set cgi_status -> HTTP_OK (200), from script output.

           r->status_line = apr_pstrdup(r->pool, l);

       }

   }

}

No any message!

Can we anticipate the error?

Yes. They are performing input check on the httpd header.