Project

General

Profile

CGI script/program is not killed after Server Sent events session is terminated

Added by joo almost 4 years ago

Hi,

I use the Firefox browser(76.0.1 64-bit) to start a server sent events session and my CGI program is invoked by the lighttpd server and everything is working good. When I terminate the session from the browser by closing the browser tab or calling close function:

source = new EventSource("/cgi-bin/execgi-bin.cgi?startstreamtag");
source.close();

I see that (wireshark capture) the browser ends the TCP session by sending the FIN flag. However, the server does not end the session and the CGI program is not removed or killed. Do I miss any configuration?

Thanks


Replies (12)

RE: CGI script/program is not killed after Server Sent events session is terminated - Added by gstrauss almost 4 years ago

Please read https://redmine.lighttpd.net/boards/2/topics/5

Your request for assistance is severely lacking in necessary details.

Do I miss any configuration?

Ubuntu and Debian are negligent in keeping stable releases patched to current lighttpd releases. lighttpd 1.4.45 was released over 3 years ago. The latest release is lighttpd 1.4.55 and there have been important bug fixes and security patches made in the past 3+ years

Can you reproduce the behavior you are seeing when using lighttpd 1.4.55?

RE: CGI script/program is not killed after Server Sent events session is terminated - Added by joo almost 4 years ago

Hi,
I am using ROCK Pi S Ubuntu

# uname -a
uname -a
Linux rockpis 4.4.143-34-rockchip-g3c9d2019dba7 #1 SMP PREEMPT Tue Nov 5 01:39:21 UTC 2019 aarch64 aarch64 aarch64 GNU/Linux

# /etc/init.d/lighttpd -v
/etc/init.d/lighttpd -v
lighttpd/1.4.54 - a light and fast webserver

Please find the attached wireshark capture file (line 10: browser terminates the session).

The following screen shows the cgi program (/var/www/cgi-bin/execgi-bin.cgi) is not terminated.

root       364  0.0  0.2   1912  1184 pts/0    Ss   16:12   0:00 /bin/sh -
root       411  0.0  0.0      0     0 ?        S    16:26   0:00 [kworker/3:2]
root       428  0.0  0.0      0     0 ?        S    16:26   0:00 [kworker/1:2]
root       473  0.0  0.0      0     0 ?        S    16:33   0:00 [kworker/1:1]
root       474  0.0  0.0      0     0 ?        S    16:33   0:00 [kworker/u8:1]
root       477  0.0  0.0      0     0 ?        S    16:33   0:00 [kworker/2:0]
www-data   479  0.0  0.6   4192  2900 pts/0    S    16:33   0:00 /etc/init.d/lighttpd -D -f /etc/lighttpd/lighttpd.conf -m /usr/
root       485 82.0  0.8 391500  3584 pts/0    Sl+  16:33   5:47 ./exellrp-new
www-data   498  0.0  0.2   4368  1116 pts/0    S    16:35   0:00 /var/www/cgi-bin/execgi-bin.cgi
root       501  0.2  1.3  11860  6044 ?        Ss   16:40   0:00 sshd: joohong [priv]
joohong    503  0.2  1.4  12232  6428 ?        Ss   16:40   0:00 /lib/systemd/systemd --user
joohong    504  0.0  0.7 162212  3352 ?        S    16:40   0:00 (sd-pam)
joohong    526  0.1  0.8  11860  3848 ?        R    16:40   0:00 sshd: joohong@pts/1
joohong    527  0.0  0.6   3776  3000 pts/1    Ss   16:40   0:00 -bash
joohong    533  0.0  0.5   5288  2388 pts/1    R+   16:41   0:00 ps -aux
joohong@rockpis:~$

I also attached the lighttpd.conf

Regards,

Joo

RE: CGI script/program is not killed after Server Sent events session is terminated - Added by gstrauss almost 4 years ago

Thank you for the additional info.

lighttpd closes the pipe from the CGI program output and sends SIGTERM to the CGI program when lighttpd detects that the client has disconnected. Please strace the lighttpd daemon and/or your CGI program to see if the signal is being sent to the CGI. Check if your CGI is blocking or discarding the signal.

If your CGI program is writing response data, the writes will fail with EPIPE after lighttpd closes the pipe. If your CGI program is not in the process of writing response data, then lighttpd read/write timeouts apply, e.g. server.max-write-idle Please see the documentation

(BTW, my mention of old Ubuntu systems stemmed from your January post of running lighttpd 1.4.45 https://redmine.lighttpd.net/boards/2/topics/8900)

RE: CGI script/program is not killed after Server Sent events session is terminated - Added by joo almost 4 years ago

It seems like I do not have "strace" command in my embedded linux environment.

I put some debug code (log) in my CGI program to check if it does receive the SIGTERM from the server:

volatile sig_atomic_t done = 0;
static FILE *fp;

void term(int signum)
{
        fp = fopen("/home/rock/upload/llrp.log", "a");
    if (fp == NULL)
    {
        exit(-1);
    }
    fprintf(fp, "receive signal SIG NUM = %d\n", signum);
    fclose(fp);
    //fprintf(fp, "receive signal SIGTERM\n");
    IReaderApiClose(CCLI::handle);
        done = 1;
}

int main(void) 
{
    IReader *handle;
    int ret;
    int region;
    const char *cgi_env = getenv("QUERY_STRING");

    char cgi_buffer[128];

    sprintf(cgi_buffer, "%s", cgi_env);

    for (unsigned int i = 0; i < strlen(cgi_buffer); i++)
    {
        if (cgi_buffer[i] == '&')
        {
            cgi_buffer[i] = ' ';
        }
    }
    std::string cgi_string(cgi_buffer);

    fp = fopen("/home/rock/upload/llrp.log", "a");
    if (fp == NULL)
    {
        exit(-1);
    }
    fprintf(fp, "Open log...\n");
    fclose(fp);
        struct sigaction action;
        memset(&action, 0, sizeof(struct sigaction));
        action.sa_handler = term;
        sigaction(SIGTERM, &action, NULL);
        sigaction(SIGHUP, &action, NULL);
        sigaction(SIGUSR1, &action, NULL);
        sigaction(SIGKILL, &action, NULL);
        sigaction(SIGINT, &action, NULL);

    CCLI::handle = IReaderApiInit();

    if (NULL == CCLI::handle)
    {
        exit(-1);
    }
    //printf("connecting...\n");
    ret = IReaderApiConnect(CCLI::handle, (char *)"127.0.0.1");
    if (IREADER_SUCCESS != ret)
    {
        // printf("Connect IReader Fails");
        IReaderApiClose(CCLI::handle);
        exit(-1);
    }

    CCLI::process_cli_command(cgi_string);

    if (done == 0)
    {
        IReaderApiClose(CCLI::handle);
    }

    return 0;
}

When the browser terminates the session or the browser tab is closed, I double check to make sure the FIN flag is sent (wireshark).

I open the log file (llrp.log) and see no signal is caught. Then from the shell I issue a kill command to kill the CGI PID and I check the llrp.log file again and it shows a signal 15 is caught. This indicates my CGI program does not block or discard the signal...

Currently, my CGI program does not write any data. When It does, as you have pointed out, the CGI program will be closed because of the broken PIPE.

Thank for your help.

Regards,

Joo

RE: CGI script/program is not killed after Server Sent events session is terminated - Added by gstrauss almost 4 years ago

    const char *cgi_env = getenv("QUERY_STRING");

    char cgi_buffer[128];

    sprintf(cgi_buffer, "%s", cgi_env);

The above code is vulnerable to buffer overflow if the query string is longer than 127 characters. NEVER use sprintf(). ALWAYS distrust data from external sources.

You should copy into std::string and modify '&' to ' ' in the std::string
std::string cgi_string(cgi_env);

RE: CGI script/program is not killed after Server Sent events session is terminated - Added by gstrauss almost 4 years ago

It appears that lighttpd is receiving POLLRDHUP event on client fd. When lighttpd goes to check if the client socket is half-closed or fully-closed, lighttpd finds SOL_TCP TCP_INFO tcpi.tcpi_state == TCP_CLOSE_WAIT, which lighttpd interprets as half-closed. I need to look into this further.

RE: CGI script/program is not killed after Server Sent events session is terminated - Added by gstrauss almost 4 years ago

For all socket-based lighttpd backends, lighttpd propagates the TCP_FIN. All dynamic lighttpd backends besides mod_cgi use sockets. (mod_fastcgi, mod_proxy, mod_scgi, mod_sockproxy, mod_wstunnel)

However, for mod_cgi, lighttpd uses pipes, and the stdin pipe of the CGI script might be connected to a temporary file or /dev/null if there was no client request body. In both cases, the stdin pipe of the CGI is no longer connected to lighttpd. Even if it were, we would want to send any pending input to the pipe before signalling the CGI of the receipt of POLLRDHUP.

How might lighttpd safely signal the equivalent of a socket TCP_FIN to a process (executable) running the CGI? There is no one obviously right answer which is why lighttpd does not implement one. POLLRDHUP from client may or may not have any meaning to the specific CGI, and some people might want the CGI killed upon receipt of POLLRDHUP, and others might not.

I am not a huge fan of infinite configuration knobs, but am willing to be convinced that I should add a config option to lighttpd -- cgi.kill-on-rdhup or something like that -- to send the CGI a TERM signal when a POLLRDHUP is received from the client. (Hint: asking nicely will not be sufficient to convince me)

Another alternative is to write your C/C++ backend as an SCGI script and use mod_scgi instead of mod_cgi. The code to write an SCGI backend in C is fairly straightforward and I wrote a simple SCGI backend scgi_responder.c that is used for testing lighttpd. lighttpd will propagate the TCP_FIN to the SCGI backend.

[Edit] clarification: lighttpd will propagate TCP_FIN to socket-based backends only if enabled "tcp-fin-propagate" => "enable" in the backend host definition. Please refer to the generic options in the mod_fastcgi options fastcgi.server. These options apply to all lighttpd socket-based dynamic backends.

RE: CGI script/program is not killed after Server Sent events session is terminated - Added by joo almost 4 years ago

I am trying if I can do a quick patch with bo luck.

When the browser closes the session (TCP_FIN is sent), I do not see the function cgi_handle_fdevent is called (I added some debug log).
I am wondering where is the right place to look at?

Thanks

Joo

RE: CGI script/program is not killed after Server Sent events session is terminated - Added by gstrauss almost 4 years ago

In gw_backend.c:gw_handle_subrequest() is the shared code for lighttpd dynamic socket backends. It contains

    if (r->conf.stream_request_body & FDEVENT_STREAM_REQUEST_TCP_FIN)
        gw_conditional_tcp_fin(hctx, r);

Near the end of mod_cgi.c:mod_cgi_handle_subrequest(), you would do something like the following (untested)

    if (r->conf.stream_request_body & FDEVENT_STREAM_REQUEST_TCP_FIN) {
        /* XXX: TODO: check if any cached request body has been sent to CGI; see gw_conditional_tcp_fin() (for sockets) */
        if (hctx->pid > 0) {
                cgi_pid_kill(p, hctx->pid);
        }
        /* above will send kill but otherwise continue.  To send kill and stop reading from CGI
         *   cgi_connection_close(hctx);
         * which includes the cgi_pid_kill() */
    }
Something similar may be appropriate in cgi_write_request(), to match gw_conditional_tcp_fin() in gw_write_request()

The behavior in lighttpd dynamic socket backends sends any data received from client before propagating TCP_FIN. The sample (untested) code above, tries to do similar for CGI. If you want to give up immediately, use cgi_connection_close(hctx);

Again, if you're going to spend time on this, I would recommend using mod_scgi and settings "tcp-fin-propagate" => "enable" for the scgi.server host, as the functionality you seek is already available there.

RE: CGI script/program is not killed after Server Sent events session is terminated - Added by joo almost 4 years ago

Thank you so much for the help. I will use mod_scgi at later time.
I ran some test with the patch listed above, the flow does not seem to enter mod_cgi_handle_subrequest when TCP_FIN is received. Are there any server settings that need to be configured?

Thanks again.

Joo

RE: CGI script/program is not killed after Server Sent events session is terminated - Added by gstrauss almost 4 years ago

the flow does not seem to enter mod_cgi_handle_subrequest when TCP_FIN is received

You are incorrect.

I have pointed you at the correct place in the code.

The (untested) code snippet I posted is triggered on TCP_FIN, but what it does when it receives the TCP_FIN needs additional work. As you haven't made any attempt to convince me why mod_cgi should support this feature in a flexible way, I am done here.

I will use mod_scgi at later time.

That time has arrived. Good luck.

RE: CGI script/program is not killed after Server Sent events session is terminated - Added by joo almost 4 years ago

"As you haven't made any attempt to convince me why mod_cgi should support this feature in a flexible way, I am done here."

Our code is currently based on the CGI model. It works stable and is tested except this little issue. Intuitively, this feature should be there by default or in a flexible way. I believe other designs that use the CGI model will also love to have this feature supported. (it's kind of weird to have the backend session still open while the front end session is gone).

Anyway, I ran more testing again using the patch. It seems like it works the first time. After the first time, I create the session again and then kill, that function is not called.

Thanks,

Joo

    (1-12/12)