Score:0

Issues with cgi-bin scripts using uWSGI and nginx

id flag

I am trying to get cgi-bin scripts running on an nginx system using uWSGI and I am having issues with this, and with debugging this to figure out what is going wrong. The system is a Raspberry Pi running Rasbian bookworm, and I have added an nginx location block for cgi-bin to the default site in /etc/nginx/sites-available/default as follows...

location ^~ /cgi-bin/ {
   root /var/www/cgi-bin;
   include uwsgi_params;
   uwsgi_pass unix:/tmp/uwsgi.sock;
}

As far as I understand, that location block defines an expression so that anything beginning with cgi-bin is then handled within. The barebones configuration for uWSGI includes a file provided with my system that sets some defaults, /etc/nginx/uwsgi_params which is here...

uwsgi_param  QUERY_STRING       $query_string;
uwsgi_param  REQUEST_METHOD     $request_method;
uwsgi_param  CONTENT_TYPE       $content_type;
uwsgi_param  CONTENT_LENGTH     $content_length;

uwsgi_param  REQUEST_URI        $request_uri;
uwsgi_param  PATH_INFO          $document_uri;
uwsgi_param  DOCUMENT_ROOT      $document_root;
uwsgi_param  SERVER_PROTOCOL    $server_protocol;
uwsgi_param  REQUEST_SCHEME     $scheme;
uwsgi_param  HTTPS              $https if_not_empty;

uwsgi_param  REMOTE_ADDR        $remote_addr;
uwsgi_param  REMOTE_PORT        $remote_port;
uwsgi_param  SERVER_PORT        $server_port;
uwsgi_param  SERVER_NAME        $server_name;

The above is simple enough, and I have experimented with a few things there when doing that. I have created a systemd unit which runs uWSGI with a specific configuration, which you can see here...

[uwsgi]
uid = uwsgi
gid = uwsgi
plugins = cgi,logfile
socket = /tmp/uwsgi.sock
cgi = /cgi-bin=/var/www/cgi-bin
chmod-socket = 666
chown-socket = www-data:www-data
req-logger = file:/var/log/uwsgi/errors.log
logger = file:/var/log/uwsgi/errors.log
chdir = /var/www/cgi-bin

I have tried some other variations with the above configuration regarding paths. It was after I had a look at things with the methods below, that I decided to give up on my concern with these paths and these sort of details.

Things start up, permissions on the cgi-bin directory are set properly, and things are running normal for me here. When I try to run a script with the URL http://this.system/cgi-bin/nph-proxy.cgi I get a 502 error in nginx, and then this error appears in the log I set for uWSGI...

-- unavailable modifier requested: 0 --

The log does not show much detail on what has gone wrong here, and that is my main problem. I could not find any way to get more detailed logs or more context about this error, and so I went on to do some further investigations. There were some threads where some Django users were having issues due to missing plugins. I have verified that the plugins for CGI and these error logs exist and are being loaded.

This corresponds with this error for the request in the nginx error log...

2023/07/24 18:02:18 [error] 32579#32579: *5 upstream prematurely closed connection while reading response header from upstream, client: 192.168.1.4, server: _, request: "GET /cgi-bin/test.cgi HTTP/1.1", upstream: "uwsgi://unix:/tmp/uwsgi.sock:", host: "redacted.home"

Because there were no options to get more detailed logs, I decided to have a look some other ways. The first was to get a GPT-4 instance a friend lends to me to create a simple CGI Perl script that simply creates a file when it is ran and writes it's environment variables into it...

#!/usr/bin/perl

use CGI;
my $cgi = CGI->new;

# Open the debug file
open(my $fh, '>', '/var/www/cgi-bin/test.log') or die "Could not open file 'debug_file.txt' $!";

# Print the environment variables to the file
foreach my $key (sort keys %ENV) {
    print $fh "$key=$ENV{$key}\n";
}

# Close the file
close $fh;

# Generate a simple HTTP response
print $cgi->header('text/plain');
print "Debug information written to '/path/to/debug_file.txt'.\n";

exit;

No file was ever created by this simple Perl script here, and so I thought I would try a couple of other things to see what was happening.

I decided to use the tool socat with command socat UNIX-LISTEN:/tmp/uwsgi.sock,user=www-data STDOUT to listen on the socket that uWSGI uses, to see requests myself. The output was slightly garbled and would not paste well here, but I was able to see some of the details nginx was passing on in this request...

SERVER_PROTOCO HTTP/1.1
REQUEST_SCHEME http
REQUEST_URI /cgi-bin/test.cgi
PATH_INFO /cgi-bin/test.cgi

There were other things in the output from socat here, but overall nothing really stood out here either that was obvious or that I could change. However, I thought the paths that nginx was providing here might be a culprit. Tweaking with paths and such changed nothing, as my next investigations into what is going on reveal.

I also decided to use strace to monitor the uWSGI worker process, strace allows one to look at system calls made by a process, which although is a bit limiting can give some insights. I obtained the process ID from the log file when starting up the service. I used this strace command strace -fvv -s 9999 -p 32699 which follows any forks, is very verbose, and does not truncate strings that are less than 9999 bytes. Once running, it waits like this...

strace: Process 32699 attached
epoll_wait(10,

When I make the HTTP request to nginx for my testing script in the cgi-bin directory, we can see the worker process do some work. We can see the uWSGI worker ask the operating system for the request here, and some information about it. The next thing is for the script to make a system call related to writing the error into the log.

epoll_wait(10, [{EPOLLIN, {u32=9, u64=9}}], 1, -1) = 1
accept4(9, {sa_family=AF_UNIX}, [110->2], SOCK_NONBLOCK) = 12
read(12, "\0v\1\0\f\0QUERY_STRING\0\0\16\0REQUEST_METHOD\3\0GET\f\0CONTENT_TYPE\0\0\16\0CONTENT_LENGTH\0\0\v\0REQUEST_URI\26\0/cgi-bin/nph-proxy.cgi\t\0PATH_INFO\26\0/cgi-bin/nph-proxy.cgi\r\0DOCUMENT_ROOT\20\0/var/www/cgi-bin\17\0SERVER_PROTOCOL\10\0HTTP/1.1\16\0REQUEST_SCHEME\4\0http\v\0REMOTE_ADDR\v\000192.168.1.4\v\0REMOTE_PORT\5\00040434\v\0SERVER_PORT\2\080\v\0SERVER_NAME\1\0_\t\0HTTP_HOST\n\0pidns.home\17\0HTTP_USER_AGENT\n\0curl/8.0.1\v\0HTTP_ACCEPT\3\0*/*", 4100) = 378
write(2, "-- unavailable modifier requested: 0 --\n", 40) = 40
close(12)                               = 0

Here I break down the above to something easier to read...

REQUEST_URI /cgi-bin/nph-proxy.cgi
PATH_INFO /cgi-bin/nph-proxy.cgi
DOCUMENT_ROOT /var/www/cgi-bin
SERVER_PROTOCOL HTTP/1.1
REQUEST_SCHEME http
SERVER_PORT 80
SERVER_NAME (possibly empty)

It comes to my attention that some some paths don't look quite right in the request as seen by strace. The proc filesystem also indicated some issues with the working directory being used, which was set to / - which I fixed and also updated in the configuration files I have shown above for both nginx and uWSGI.

I think it says a lot here about what strace isn't showing us here which is file operations, or any system calls to run the script. Despite various tweaks to nginx and the configuration for the uWSGI service, there are never any attempts to open any files or to execute anything.

Documentation shows that users having this error had to install and enable certain plugins for the error to go away. These people were running Python/Django applications however, and I am just using CGI and the logfiles extension. The documentation for running CGI applications mentions nothing beyond enabling the CGI plugin and having a cgi variable in the configuration file.

Given that the error happens without the process doing anything more than getting a request, the process never attempts anything with details in the request, and that some users get this error when plugins and such are missing - I think one can assume that the unsupported modifiers are basically variables provided by nginx. The defaults here would have everything needed by modern web applications, for things like clean-urls and other things which don't exist in CGI.

I suspect what might fix this is some reduction in what is being passed by nginx here, but I cannot find anything about this in the documentation. If someone is more familiar with the workings of CGI, maybe something in my configuration or debugging above stands out.

I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.