httpd-36090

Version:

2.2.0

Failure report link:

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

How to reproduce:

1. compile apache with proxy mode enabled.

2. create the following perl script (name it test.pl), and put this into cgi-bin directory:

#!/usr/bin/perl
print "Status: 404\n";
print "FOO\n\n";

3. start apache server.

4. use client to access the test.pl in browser.

Symptom:

Apache returns weird webpage body along with “Internal Server Error (500)” error page.

If a CGI script outputs a Status: header with a value other than 200, (404 in our case) and then generates malformed headers (FOO in our case), the message that is returned to the client includes the bogus statement that "Additionally, a 500 Internal Server Error error was encountered while trying to use an ErrorDocument to handle the request" even if no ErrorDocument directives are present in the server configuration files.

In the error log:

[Sun Oct 17 15:02:46 2010] [error] [client XXX.XXX.XXX.XXX] malformed header from script. Bad header=FOO: test2.pl:3


Root cause:

When cgi-script is mal-formed, Apache did not clear the ‘r->status’ set by the script ("Status: 404\n") before calling the library ap_die to return error page.

--- modules/http/http_request.c.orig        2005-11-30 20:40:13.000000000 -0500

+++ modules/http/http_request.c        2005-11-30 20:40:30.000000000 -0500

@@ -268,6 +268,7 @@

ap_process_request () {

       ... ..

  // ap_process_request_internal will call

   // ap_scan_script_header_err_core below with “Status: 404\nFOO\n\n”. In

   // ap_scan_script_header_err_core it will print the error msg and

   // return with HTTP_INTERNAL_SERVER_ERROR. It will also set r->status

   // to 404. This will be used in ap_die, to incorrectly result in

    // an ambiguous message.

   // The fix is to work around ap_die (which is a library function) by

   // setting r->status to HTTP_OK.

   access_status = ap_process_request_internal (r);  

   if (access_status == OK) {        

       ap_finalize_request_protocol(r);

   }

   else {

+      r->status = HTTP_OK;

       ap_die(access_status, r);

   }

/* ap_scan_script_header_err_core is called with w= “Status:404\nFOO\n\n”. */

AP_DECLARE(int) ap_scan_script_header_err_core(w) {

  // parse the output by the cgi script

 .. ..

    /* The loop will iterate twice. First time parsing ‘Status:404’, 2nd

       time will detect the error in FOO\n\n and print error msg before

       return HTTP_INTERNAL_SERVER_ERROR. */

    while (1) {

      ... ...

      /* In the 2nd interation, where w = “FOO\n\n”, it cannot

         locate the ‘:’ in the string thus the ‘if’ below will take

         true branch. */

      if (!(l = strchr(w, ':'))) {

          char malformed[(sizeof MALFORMED_MESSAGE) + 1

                         + MALFORMED_HEADER_LENGTH_TO_SHOW];

          ... ...

          ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_TOCLIENT, 0, r,

                        "%s: %s", malformed,

                        apr_filepath_name_get(r->filename));

          return HTTP_INTERNAL_SERVER_ERROR;

      }

      *l++ = '\0';

      /* In the 1st iteration, w now becomes: “Status\0 404”, where l points

         to ‘404’ */

      ... ...

      if (!strcasecmp(w, "Content-type")) {

           ... ...

      }

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

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

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

      }

    .... ....

  }

  return OK;

}

Here is the call frames from gdb:

#0  ap_scan_script_header_err_core (r=0x1a52df8, buffer=0x4d1b7e50 "", getsfunc=0x4421e2 <getsfunc_BRIGADE>, getsfunc_data=0x1a5a2d0) at util_script.c:408

#1  0x00000000004423c1 in ap_scan_script_header_err_brigade (r=0x1a52df8, bb=0x1a5a2d0, buffer=0x4d1b7e50 "") at util_script.c:649

#2  0x0000000000482e55 in cgid_handler (r=0x1a52df8) at mod_cgid.c:1467

#3  0x000000000043a924 in ap_run_handler (r=0x1a52df8) at config.c:157

#4  0x000000000043b1cd in ap_invoke_handler (r=0x1a52df8) at config.c:371

#5  0x0000000000472ba7 in ap_process_request (r=0x1a52df8) at http_request.c:258

#6  0x000000000046fea2 in ap_process_http_connection (c=0x1a4e9f0) at http_core.c:171

#7  0x00000000004437cd in ap_run_process_connection (c=0x1a4e9f0) at connection.c:43

#8  0x0000000000443c08 in ap_process_connection (c=0x1a4e9f0, csd=0x1a4e7d8) at connection.c:178

#9  0x000000000048bb60 in process_socket (p=0x1a4e768, sock=0x1a4e7d8, my_child_num=0, my_thread_num=24, bucket_alloc=0x1a50d78) at worker.c:531

#10 0x000000000048c3d7 in worker_thread (thd=0x1a05b00, dummy=0x1a4bff0) at worker.c:876

#11 0x00007f0d4a6ebf6e in dummy_worker (opaque=0x1a05b00) at threadproc/unix/thread.c:138

#12 0x00007f0d4a0703ea in start_thread () from /lib/libpthread.so.0

#13 0x00007f0d49bd9cbd in clone () from /lib/libc.so.6

#14 0x0000000000000000 in ?? ()

The “Status: 404\n” in the cgi script will be parsed by the function ap_scan_script_header_err_core, and would correspondingly set r->status (similar to error 38070). Then this “r->status” would be used in ap_die, to incorrectly result in an ambiguous message.

So the key to this bug is: script printed “Status: 404\n”, in other words, the r->status is set in “ap_scan_script_header_err_core”. In other words, the solution in 38070 can also be used in this case!

Is there Error Message?

Yes.

Logging Pattern

Input check (return value of ‘strchr’). Errlog can automatically insert this msg using input check pattern as well!