give each server process its own socket for the executor

this fixes a bug introduced with the prefork mechanics: every server
process shared the same socket, and this would cause a race condition
when multiple server processes asked for a script cgi being executed.

This gives each server process its own socket to talk to the executor,
so the race cannot happen.
This commit is contained in:
Omar Polo 2021-03-03 17:22:01 +00:00
parent fda7b99fc7
commit 2c3e53dac6
5 changed files with 81 additions and 93 deletions

78
ex.c
View File

@ -19,6 +19,7 @@
#include <err.h> #include <err.h>
#include <errno.h> #include <errno.h>
#include <event.h>
#include <fcntl.h> #include <fcntl.h>
#include <libgen.h> #include <libgen.h>
#include <limits.h> #include <limits.h>
@ -397,15 +398,50 @@ childerr:
_exit(1); _exit(1);
} }
int static void
executor_main() handle_fork_req(int fd, short ev, void *data)
{ {
char *spath, *relpath, *addr, *ruser, *cissuer, *chash; char *spath, *relpath, *addr, *ruser, *cissuer, *chash;
struct vhost *vhost; struct vhost *vhost;
struct iri iri; struct iri iri;
time_t notbefore, notafter; time_t notbefore, notafter;
int d; int d;
if (!recv_iri(fd, &iri)
|| !recv_string(fd, &spath)
|| !recv_string(fd, &relpath)
|| !recv_string(fd, &addr)
|| !recv_string(fd, &ruser)
|| !recv_string(fd, &cissuer)
|| !recv_string(fd, &chash)
|| !recv_time(fd, &notbefore)
|| !recv_time(fd, &notafter)
|| !recv_vhost(fd, &vhost))
fatal("failure in handling fork request");
d = launch_cgi(&iri, spath, relpath, addr, ruser, cissuer, chash,
notbefore, notafter, vhost);
if (!send_fd(fd, d))
fatal("failure in sending the fd to the server: %s",
strerror(errno));
close(d);
free_recvd_iri(&iri);
free(spath);
free(relpath);
free(addr);
free(ruser);
free(cissuer);
free(chash);
}
int
executor_main(void)
{
struct vhost *vhost;
struct event evs[PROC_MAX];
int i;
#ifdef __OpenBSD__ #ifdef __OpenBSD__
for (vhost = hosts; vhost->domain != NULL; ++vhost) { for (vhost = hosts; vhost->domain != NULL; ++vhost) {
/* r so we can chdir into the correct directory */ /* r so we can chdir into the correct directory */
@ -419,39 +455,15 @@ executor_main()
err(1, "pledge"); err(1, "pledge");
#endif #endif
while (!hupped) { event_init();
if (!recv_iri(exfd, &iri)
|| !recv_string(exfd, &spath)
|| !recv_string(exfd, &relpath)
|| !recv_string(exfd, &addr)
|| !recv_string(exfd, &ruser)
|| !recv_string(exfd, &cissuer)
|| !recv_string(exfd, &chash)
|| !recv_time(exfd, &notbefore)
|| !recv_time(exfd, &notafter)
|| !recv_vhost(exfd, &vhost))
break;
d = launch_cgi(&iri, spath, relpath, addr, ruser, cissuer, chash, for (i = 0; i < conf.prefork; ++i) {
notbefore, notafter, vhost); event_set(&evs[i], servpipes[i], EV_READ | EV_PERSIST,
if (!send_fd(exfd, d)) handle_fork_req, NULL);
break; event_add(&evs[i], NULL);
close(d);
free_recvd_iri(&iri);
free(spath);
free(relpath);
free(addr);
free(ruser);
free(cissuer);
free(chash);
} }
if (hupped) event_dispatch();
_exit(0);
/* kill all process in my group. This means the listener and
* every pending CGI script. */
kill(0, SIGINT);
return 1; return 1;
} }

1
gmid.1
View File

@ -159,6 +159,7 @@ This increases the performance and prevents delays when connecting to
a server. a server.
.Nm .Nm
runs 3 server processes by default, when not in config-less mode. runs 3 server processes by default, when not in config-less mode.
The maximum number allowed is 16.
.It Ic protocols Ar string .It Ic protocols Ar string
Specify the TLS protocols to enable. Specify the TLS protocols to enable.
Refer to Refer to

89
gmid.c
View File

@ -29,7 +29,7 @@ volatile sig_atomic_t hupped;
struct vhost hosts[HOSTSLEN]; struct vhost hosts[HOSTSLEN];
int exfd, logfd, sock4, sock6; int exfd, logfd, sock4, sock6, servpipes[PROC_MAX];
struct imsgbuf logpibuf, logcibuf; struct imsgbuf logpibuf, logcibuf;
@ -299,31 +299,6 @@ drop_priv(void)
log_warn(NULL, "not a good idea to run a network daemon as root"); log_warn(NULL, "not a good idea to run a network daemon as root");
} }
static int
spawn_listeners(int *p)
{
int i;
close(p[0]);
exfd = p[1];
for (i = 0; i < conf.prefork -1; ++i) {
switch (fork()) {
case -1:
fatal("fork: %s", strerror(errno));
case 0: /* child */
setproctitle("server(%d)", i+1);
return listener_main();
}
}
if (conf.prefork == 0)
setproctitle("server");
else
setproctitle("server(%d)", conf.prefork);
return listener_main();
}
static void static void
usage(const char *me) usage(const char *me)
{ {
@ -359,13 +334,13 @@ logger_init(void)
} }
} }
static int static int
serve(int argc, char **argv, int *p) serve(int argc, char **argv)
{ {
char path[PATH_MAX]; char path[PATH_MAX];
int i, p[2];
if (config_path == NULL) { if (config_path == NULL) {
if (hostname == NULL) if (hostname == NULL)
hostname = "localhost"; hostname = "localhost";
if (certs_dir == NULL) if (certs_dir == NULL)
@ -395,28 +370,35 @@ serve(int argc, char **argv, int *p)
* to put private certs inside the chroot. */ * to put private certs inside the chroot. */
setup_tls(); setup_tls();
switch (fork()) { for (i = 0; i < conf.prefork; ++i) {
case -1: if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC,
fatal("fork: %s", strerror(errno)); PF_UNSPEC, p) == -1)
fatal("socketpair: %s", strerror(errno));
case 0: /* child */ switch (fork()) {
return spawn_listeners(p); case -1:
fatal("fork: %s", strerror(errno));
default: /* parent */ case 0: /* child */
setproctitle("executor"); close(p[0]);
close(p[1]); exfd = p[1];
exfd = p[0]; setproctitle("server");
drop_priv(); _exit(listener_main());
unblock_signals(); default:
return executor_main(); servpipes[i] = p[0];
close(p[1]);
}
} }
setproctitle("executor");
drop_priv();
unblock_signals();
_exit(executor_main());
} }
int int
main(int argc, char **argv) main(int argc, char **argv)
{ {
int ch, p[2]; int ch, conftest = 0, configless = 0;
int conftest = 0, configless = 0;
int old_ipv6, old_port; int old_ipv6, old_port;
init_config(); init_config();
@ -482,7 +464,7 @@ main(int argc, char **argv)
if (config_path == NULL) { if (config_path == NULL) {
configless = 1; configless = 1;
conf.foreground = 1; conf.foreground = 1;
conf.prefork = 0; conf.prefork = 1;
conf.verbose++; conf.verbose++;
} }
@ -509,10 +491,6 @@ main(int argc, char **argv)
err(1, "daemon"); err(1, "daemon");
} }
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC,
PF_UNSPEC, p) == -1)
err(1, "socketpair");
if (config_path != NULL) if (config_path != NULL)
parse_conf(config_path); parse_conf(config_path);
@ -524,7 +502,7 @@ main(int argc, char **argv)
sock6 = make_socket(conf.port, AF_INET6); sock6 = make_socket(conf.port, AF_INET6);
if (configless) if (configless)
return serve(argc, argv, p); return serve(argc, argv);
/* wait a sighup and reload the daemon */ /* wait a sighup and reload the daemon */
for (;;) { for (;;) {
@ -535,12 +513,9 @@ main(int argc, char **argv)
case -1: case -1:
fatal("fork: %s", strerror(errno)); fatal("fork: %s", strerror(errno));
case 0: case 0:
return serve(argc, argv, p); return serve(argc, argv);
} }
close(p[0]);
close(p[1]);
wait_sighup(); wait_sighup();
unblock_signals(); unblock_signals();
log_info(NULL, "reloading configuration %s", config_path); log_info(NULL, "reloading configuration %s", config_path);
@ -568,9 +543,5 @@ main(int argc, char **argv)
sock4 = make_socket(conf.port, AF_INET); sock4 = make_socket(conf.port, AF_INET);
if (sock6 == -1 && conf.ipv6) if (sock6 == -1 && conf.ipv6)
sock6 = make_socket(conf.port, AF_INET6); sock6 = make_socket(conf.port, AF_INET6);
if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC,
PF_UNSPEC, p) == -1)
fatal("socketpair: %s", strerror(errno));
} }
} }

4
gmid.h
View File

@ -56,6 +56,8 @@
#define DOMAIN_NAME_LEN (253+1) #define DOMAIN_NAME_LEN (253+1)
#define LABEL_LEN (63+1) #define LABEL_LEN (63+1)
#define PROC_MAX 16
struct location { struct location {
const char *match; const char *match;
const char *lang; const char *lang;
@ -120,6 +122,8 @@ extern struct imsgbuf logpibuf, logcibuf;
extern volatile sig_atomic_t hupped; extern volatile sig_atomic_t hupped;
extern int servpipes[PROC_MAX];
struct iri { struct iri {
char *schema; char *schema;
char *host; char *host;

View File

@ -299,7 +299,7 @@ check_strip_no(int n)
int int
check_prefork_num(int n) check_prefork_num(int n)
{ {
if (n <= 0) if (n <= 0 || n >= PROC_MAX)
yyerror("invalid prefork number %d", n); yyerror("invalid prefork number %d", n);
return n; return n;
} }