]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: config: prevent communication with privileged ports
authorAmaury Denoyelle <adenoyelle@haproxy.com>
Wed, 22 May 2024 12:21:16 +0000 (14:21 +0200)
committerAmaury Denoyelle <adenoyelle@haproxy.com>
Fri, 24 May 2024 12:36:31 +0000 (14:36 +0200)
This commit introduces a new global setting named
harden.reject_privileged_ports.{tcp|quic}. When active, communications
with clients which use privileged source ports are forbidden. Such
behavior is considered suspicious as it can be used as spoofing or
DNS/NTP amplication attack.

Value is configured per transport protocol. For each TCP and QUIC
distinct code locations are impacted by this setting. The first one is
in sock_accept_conn() which acts as a filter for all TCP based
communications just after accept() returns a new connection. The second
one is dedicated for QUIC communication in quic_recv(). In both cases,
if a privileged source port is used and setting is disabled, received
message is silently dropped.

By default, protection are disabled for both protocols. This is to be
able to backport it without breaking changes on stable release.

This should be backported as it is an interesting security feature yet
relatively simple to implement.

doc/configuration.txt
include/haproxy/global-t.h
include/haproxy/protocol-t.h
include/haproxy/tools.h
src/cfgparse-global.c
src/haproxy.c
src/quic_sock.c
src/sock.c

index 804a77e98be1196340c421a41af2f1ddddf72d3c..ef0cf8ea26bfe3234f972c35d88249ae4b439872 100644 (file)
@@ -1276,6 +1276,8 @@ The following keywords are supported in the "global" section :
    - h1-case-adjust-file
    - h2-workaround-bogus-websocket-clients
    - hard-stop-after
+   - harden.reject-privileged-ports.tcp
+   - harden.reject-privileged-ports.quic
    - insecure-fork-wanted
    - insecure-setuid-wanted
    - issuers-chain-path
@@ -1944,6 +1946,12 @@ hard-stop-after <time>
 
   See also: grace
 
+harden.reject-privileged-ports.tcp { on | off }
+harden.reject-privileged-ports.quic { on | off }
+  Toggle per protocol protection which forbid communication with clients which
+  use privileged ports as their source port. This range of ports is defined
+  according to RFC 6335. Protection is inactive by default on both protocols.
+
 http-err-codes [+-]<range>[,...] [...]
   Replace, reduce or extend the list of status codes that define an error as
   considered by the termination codes and the "http_err_cnt" counter in stick
index f38c28eaeaceb88d90a1d45a3ebee4a47f18ff2c..7665ef24d4c8fed742b3d0c92104dcef0e81876a 100644 (file)
@@ -216,6 +216,8 @@ struct global {
        int numa_cpu_mapping;
        int thread_limit;               /* hard limit on the number of threads */
        int prealloc_fd;
+       uchar clt_privileged_ports;     /* bitmask to allow client privileged ports exchanges per protocol */
+       /* 3-bytes hole */
        int cfg_curr_line;              /* line number currently being parsed */
        const char *cfg_curr_file;      /* config file currently being parsed or NULL */
        char *cfg_curr_section;         /* config section name currently being parsed or NULL */
index b85f29cc03b5b54d461eccfdf30dd88c860c06c8..0c5bd9e1cbc7ec3332ce8f3d6039f05bbbdeba11 100644 (file)
@@ -138,6 +138,17 @@ struct protocol {
        struct list list;                               /* list of registered protocols (under proto_lock) */
 };
 
+/* Transport protocol identifiers which can be used as masked values. */
+enum ha_proto {
+       HA_PROTO_NONE = 0x00,
+
+       HA_PROTO_TCP  = 0x01,
+       HA_PROTO_UDP  = 0x02,
+       HA_PROTO_QUIC = 0x04,
+
+       HA_PROTO_ANY  = 0xff,
+};
+
 #endif /* _HAPROXY_PROTOCOL_T_H */
 
 /*
index fc1b576f394e4beee67c907c9a20c3508a60b7dc..937adaaf7b704cb802fc084b753202097603febb 100644 (file)
@@ -42,6 +42,7 @@
 #include <haproxy/api.h>
 #include <haproxy/chunk.h>
 #include <haproxy/intops.h>
+#include <haproxy/global.h>
 #include <haproxy/namespace-t.h>
 #include <haproxy/protocol-t.h>
 #include <haproxy/tools-t.h>
@@ -781,6 +782,21 @@ static inline int set_host_port(struct sockaddr_storage *addr, int port)
        return 0;
 }
 
+/* Returns true if <addr> port is forbidden as client source using <proto>. */
+static inline int port_is_restricted(const struct sockaddr_storage *addr,
+                                     enum ha_proto proto)
+{
+       const uint16_t port = get_host_port(addr);
+
+       BUG_ON_HOT(proto != HA_PROTO_TCP && proto != HA_PROTO_QUIC);
+
+       /* RFC 6335 6. Port Number Ranges */
+       if (unlikely(port < 1024 && port > 0))
+               return !(global.clt_privileged_ports & proto);
+
+       return 0;
+}
+
 /* Convert mask from bit length form to in_addr form.
  * This function never fails.
  */
index d93d5ffb4b00231ce136acac05c30546c2364ae8..452c0e54536bb8894b77dd9bc0a2d19282197925 100644 (file)
@@ -1376,8 +1376,59 @@ static int cfg_parse_prealloc_fd(char **args, int section_type, struct proxy *cu
        return 0;
 }
 
+/* Parser for harden.reject-privileged-ports.{tcp|quic}. */
+static int cfg_parse_reject_privileged_ports(char **args, int section_type,
+                                             struct proxy *curpx,
+                                             const struct proxy *defpx,
+                                             const char *file, int line, char **err)
+{
+       struct ist proto;
+       char onoff;
+
+       if (!*(args[1])) {
+               memprintf(err, "'%s' expects either 'on' or 'off'.", args[0]);
+               return -1;
+       }
+
+       proto = ist(args[0]);
+       while (istlen(istfind(proto, '.')))
+               proto = istadv(istfind(proto, '.'), 1);
+
+       if (strcmp(args[1], "on") == 0) {
+               onoff = 1;
+       }
+       else if (strcmp(args[1], "off") == 0) {
+               onoff = 0;
+       }
+       else {
+               memprintf(err, "'%s' expects either 'on' or 'off'.", args[0]);
+               return -1;
+       }
+
+       if (istmatch(proto, ist("tcp"))) {
+               if (!onoff)
+                       global.clt_privileged_ports |= HA_PROTO_TCP;
+               else
+                       global.clt_privileged_ports &= ~HA_PROTO_TCP;
+       }
+       else if (istmatch(proto, ist("quic"))) {
+               if (!onoff)
+                       global.clt_privileged_ports |= HA_PROTO_QUIC;
+               else
+                       global.clt_privileged_ports &= ~HA_PROTO_QUIC;
+       }
+       else {
+               memprintf(err, "invalid protocol for '%s'.", args[0]);
+               return -1;
+       }
+
+       return 0;
+}
+
 static struct cfg_kw_list cfg_kws = {ILH, {
        { CFG_GLOBAL, "prealloc-fd", cfg_parse_prealloc_fd },
+       { CFG_GLOBAL, "harden.reject-privileged-ports.tcp",  cfg_parse_reject_privileged_ports },
+       { CFG_GLOBAL, "harden.reject-privileged-ports.quic", cfg_parse_reject_privileged_ports },
        { 0, NULL, NULL },
 }};
 
index 90db4e798353beeaf25120259d18c7e435d54734..30df816ff8018ff59b4c78efc22d1463f6fd0abc 100644 (file)
@@ -210,6 +210,8 @@ struct global global = {
        .maxsslconn = DEFAULT_MAXSSLCONN,
 #endif
 #endif
+       /* by default do not protect against clients using privileged port */
+       .clt_privileged_ports = HA_PROTO_ANY,
        /* others NULL OK */
 };
 
index 31ba2497d67d4f34b2a7e8e6297e24c6789eebcf..b21fe8cac8a4b714714c0a3be5ffb501fcbe2406 100644 (file)
@@ -29,6 +29,7 @@
 #include <haproxy/listener.h>
 #include <haproxy/log.h>
 #include <haproxy/pool.h>
+#include <haproxy/protocol-t.h>
 #include <haproxy/proto_quic.h>
 #include <haproxy/proxy-t.h>
 #include <haproxy/quic_cid.h>
@@ -393,6 +394,11 @@ static ssize_t quic_recv(int fd, void *out, size_t len,
        if (ret < 0)
                goto end;
 
+       if (unlikely(port_is_restricted((struct sockaddr_storage *)from, HA_PROTO_QUIC))) {
+               ret = -1;
+               goto end;
+       }
+
        for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
                switch (cmsg->cmsg_level) {
                case IPPROTO_IP:
index fba92bd7cf3ee2801ab44a48c41c782f2843422f..df82c6ea7b309fac442c2bbbf79574d794ed1038 100644 (file)
@@ -30,6 +30,7 @@
 #include <haproxy/listener.h>
 #include <haproxy/log.h>
 #include <haproxy/namespace.h>
+#include <haproxy/protocol-t.h>
 #include <haproxy/proto_sockpair.h>
 #include <haproxy/sock.h>
 #include <haproxy/sock_inet.h>
@@ -109,6 +110,9 @@ struct connection *sock_accept_conn(struct listener *l, int *status)
                        goto fail_conn;
                }
 
+               if (unlikely(port_is_restricted(addr, HA_PROTO_TCP)))
+                       goto fail_conn;
+
                /* Perfect, the connection was accepted */
                conn = conn_new(&l->obj_type);
                if (!conn)