diff --git a/README.md b/README.md index b70b13f4..73e45f3d 100644 --- a/README.md +++ b/README.md @@ -476,7 +476,11 @@ default port. To run ssh on port 222, and all others on port 22: ``` Compared to Finit v1.12 you must *explicitly deny* access from `eth0`! - + +To protect against looping attacks, the inetd server will refuse UDP +service if the reply port corresponds to any internal service. Similar +to how the FreeBSD inetd operates. + **Internal Services** diff --git a/inetd.c b/inetd.c index 516217e8..43555e18 100755 --- a/inetd.c +++ b/inetd.c @@ -189,6 +189,33 @@ static void socket_cb(uev_t *UNUSED(w), void *arg, int UNUSED(events)) service_step(task); } +/* + * Refuse service if the request specifies a reply port corresponding to any internal service. + * This is done as a defense against looping attacks; the remote IP address is logged. + * http://www.freebsd.org/cgi/man.cgi?inetd(8) + * https://svnweb.freebsd.org/base/head/usr.sbin/inetd/inetd.c?revision=298909&view=markup#l2094 + */ +int inetd_check_loop(struct sockaddr *sa, socklen_t len, char *name) +{ + svc_t *svc; + char pname[NI_MAXHOST]; + + for (svc = svc_inetd_iterator(1); svc; svc = svc_inetd_iterator(0)) { + inetd_t *i = &svc->inetd; + + if (!i->builtin || i->type != SOCK_DGRAM) + continue; + + if (((const struct sockaddr_in *)sa)->sin_port == i->port) { + getnameinfo(sa, len, pname, sizeof(pname), NULL, 0, NI_NUMERICHOST); + FLOG_WARN("%s/%s:%s/%s loop request REFUSED from %s", i->name, "UDP", name, "UDP", pname); + return 1; + } + } + + return 0; +} + /* Launch Inet socket for service. * TODO: Add filtering ALLOW/DENY per interface. */ diff --git a/inetd.h b/inetd.h index 32db966e..d0f326d0 100644 --- a/inetd.h +++ b/inetd.h @@ -46,6 +46,7 @@ typedef struct { int proto; int port; int forking; + int builtin; /* Set by built-in inetd services only */ int next_id; /* Next child job's id */ char name[10]; int (*cmd)(int type); /* internal inetd service, like 'time' */ @@ -53,6 +54,8 @@ typedef struct { TAILQ_HEAD(, inetd_filter) filters; } inetd_t; +int inetd_check_loop (struct sockaddr *sa, socklen_t len, char *name); + int inetd_start (inetd_t *inetd); void inetd_stop (inetd_t *inetd); diff --git a/plugins/chargen.c b/plugins/chargen.c index 30528b51..b3e1febc 100644 --- a/plugins/chargen.c +++ b/plugins/chargen.c @@ -27,6 +27,7 @@ #include "../plugin.h" +#define NAME "chargen" #define PATTERN "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ " static char *generator(char *buf, size_t len) @@ -61,6 +62,9 @@ static int recv_peer(int sd, char *buf, ssize_t len, struct sockaddr *sa, sockle if (-1 == len) return -1; /* On error, close connection. */ + if (inetd_check_loop(sa, *sa_len, NAME)) + return -1; + return 0; } @@ -86,7 +90,7 @@ static int cb(int type) } static plugin_t plugin = { - .name = "chargen", /* Must match the inetd /etc/services entry */ + .name = NAME, /* Must match the inetd /etc/services entry */ .inetd = { .cmd = cb }, diff --git a/plugins/daytime.c b/plugins/daytime.c index 64264176..4ea9ce11 100644 --- a/plugins/daytime.c +++ b/plugins/daytime.c @@ -28,6 +28,9 @@ #include "../plugin.h" +#define NAME "daytime" + + static char *daytime(char *buf, size_t len) { time_t t; @@ -48,6 +51,9 @@ static int recv_peer(int sd, char *buf, ssize_t len, struct sockaddr *sa, sockle if (-1 == len) return -1; /* On error, close connection. */ + if (inetd_check_loop(sa, *sa_len, NAME)) + return -1; + return 0; } @@ -72,7 +78,7 @@ static int cb(int type) } static plugin_t plugin = { - .name = "daytime", /* Must match the inetd /etc/services entry */ + .name = NAME, /* Must match the inetd /etc/services entry */ .inetd = { .cmd = cb } diff --git a/plugins/echo.c b/plugins/echo.c index 1e44f8df..311a2255 100644 --- a/plugins/echo.c +++ b/plugins/echo.c @@ -27,6 +27,20 @@ #include "../plugin.h" +#define NAME "echo" + +static int recv_peer(int sd, char *buf, ssize_t len, struct sockaddr *sa, socklen_t *sa_len) +{ + len = recvfrom(sd, buf, sizeof(buf), MSG_DONTWAIT, sa, sa_len); + if (-1 == len) + return -1; /* On error, close connection. */ + + if (inetd_check_loop(sa, *sa_len, NAME)) + return -1; + + return len; +} + static int cb(int type) { int sd = STDIN_FILENO; @@ -35,7 +49,7 @@ static int cb(int type) struct sockaddr_storage sa; socklen_t sa_len = sizeof(sa); - len = recvfrom(sd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *)&sa, &sa_len); + len = recv_peer(sd, buf, sizeof(buf), (struct sockaddr *)&sa, &sa_len); if (-1 == len) return -1; /* On error, close connection. */ @@ -43,7 +57,7 @@ static int cb(int type) } static plugin_t plugin = { - .name = "echo", /* Must match the inetd /etc/services entry */ + .name = NAME, /* Must match the inetd /etc/services entry */ .inetd = { .cmd = cb }, diff --git a/plugins/time.c b/plugins/time.c index 019e29eb..de81938a 100644 --- a/plugins/time.c +++ b/plugins/time.c @@ -28,6 +28,8 @@ #include "../plugin.h" +#define NAME "time" + /* UNIX epoch starts midnight, 1st Jan, 1970 */ #define EPOCH_OFFSET 2208988800ULL @@ -59,6 +61,9 @@ static int recv_peer(int sd, char *buf, ssize_t len, struct sockaddr *sa, sockle if (-1 == len) return -1; /* On error, close connection. */ + if (inetd_check_loop(sa, *sa_len, NAME)) + return -1; + return 0; } @@ -87,7 +92,7 @@ static int cb(int type) } static plugin_t plugin = { - .name = "time", /* Must match the inetd /etc/services entry */ + .name = NAME, /* Must match the inetd /etc/services entry */ .inetd = { .cmd = cb } diff --git a/service.c b/service.c index a5137d91..afbf4820 100755 --- a/service.c +++ b/service.c @@ -590,6 +590,7 @@ recreate: if (plugin) { /* Internal plugin provides this service */ svc->inetd.cmd = plugin->inetd.cmd; + svc->inetd.builtin = 1; } else { strlcpy(svc->args[i++], cmd, sizeof(svc->args[0])); while ((cmd = strtok(NULL, " ")))