]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: connection: Generic, list-based allocation and look-up of PPv2 TLVs
authorAlexander Stephan <alexander.stephan@sap.com>
Wed, 16 Aug 2023 13:35:04 +0000 (15:35 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 29 Aug 2023 13:15:47 +0000 (15:15 +0200)
In order to be able to implement fetches in the future that allow
retrieval of any TLVs, a new generic data structure for TLVs is introduced.

Existing TLV fetches for PP2_TYPE_AUTHORITY and PP2_TYPE_UNIQUE_ID are
migrated to use this new data structure. TLV related pools are updated
to not rely on type, but only on size. Pools accomodate the TLV list
element with their associated value. For now, two pools for 128 B and
256 B values are introduced. More fine-grained solutions are possible
in the future, if necessary.

include/haproxy/connection-t.h
include/haproxy/connection.h
src/connection.c

index bb6edb1c10d89d766b8025cd27270ddc6e4c23dd..b41663ce27e15d12130313a9d1af496946fe561b 100644 (file)
@@ -503,6 +503,19 @@ struct conn_hash_params {
        struct sockaddr_storage *dst_addr;
 };
 
+/*
+ * This structure describes an TLV entry consisting of its type
+ * and corresponding payload. This can be used to construct a list
+ * from which arbitrary TLV payloads can be fetched.
+ * It might be possible to embed the 'tlv struct' here in the future.
+ */
+struct conn_tlv_list {
+       struct list list;
+       unsigned short len; // 65535 should be more than enough!
+       unsigned char type;
+       char value[0];
+} __attribute__((packed));
+
 /* This structure describes a connection with its methods and data.
  * A connection may be performed to proxy or server via a local or remote
  * socket, and can also be made to an internal applet. It can support
@@ -541,8 +554,7 @@ struct connection {
        void (*destroy_cb)(struct connection *conn);  /* callback to notify of imminent death of the connection */
        struct sockaddr_storage *src; /* source address (pool), when known, otherwise NULL */
        struct sockaddr_storage *dst; /* destination address (pool), when known, otherwise NULL */
-       struct ist proxy_authority;   /* Value of the authority TLV received via PROXYv2 */
-       struct ist proxy_unique_id;   /* Value of the unique ID TLV received via PROXYv2 */
+       struct list tlv_list;         /* list of TLVs received via PROXYv2 */
 
        /* used to identify a backend connection for http-reuse,
         * thus only present if conn.target is of type OBJ_TYPE_SERVER
@@ -630,7 +642,10 @@ struct mux_proto_list {
 
 #define TLV_HEADER_SIZE 3
 
-#define HA_PP2_AUTHORITY_MAX 255 /* Maximum length of an authority TLV */
+#define HA_PP2_AUTHORITY_MAX 255  /* Maximum length of an authority TLV */
+#define HA_PP2_TLV_VALUE_128 128  /* E.g., accomodate unique IDs (128 B) */
+#define HA_PP2_TLV_VALUE_256 256  /* E.g., accomodate authority TLVs (currently, <= 255 B) */
+#define HA_PP2_MAX_ALLOC     1024 /* Maximum TLV value for PPv2 to prevent DoS */
 
 struct proxy_hdr_v2 {
        uint8_t sig[12];   /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */
index 1d216571288660c58fe351c72b242777cf54885e..e21d8294dbb68129484d1a7fba51e40d6a70a71f 100644 (file)
@@ -40,7 +40,8 @@
 extern struct pool_head *pool_head_connection;
 extern struct pool_head *pool_head_conn_hash_node;
 extern struct pool_head *pool_head_sockaddr;
-extern struct pool_head *pool_head_authority;
+extern struct pool_head *pool_head_pp_tlv_128;
+extern struct pool_head *pool_head_pp_tlv_256;
 extern struct pool_head *pool_head_uniqueid;
 extern struct xprt_ops *registered_xprt[XPRT_ENTRIES];
 extern struct mux_proto_list mux_proto_list;
@@ -52,6 +53,7 @@ extern struct mux_stopping_data mux_stopping_data[MAX_THREADS];
 int conn_recv_proxy(struct connection *conn, int flag);
 int conn_send_proxy(struct connection *conn, unsigned int flag);
 int make_proxy_line(char *buf, int buf_len, struct server *srv, struct connection *remote, struct stream *strm);
+struct conn_tlv_list *conn_get_tlv(struct connection *conn, int type);
 
 int conn_append_debug_info(struct buffer *buf, const struct connection *conn, const char *pfx);
 
index 101ac4f596bb6ddd03bf6e8e699bd8622b4c92c7..c7403b5829f963e78c96a6faf7401f5ab02ad207 100644 (file)
@@ -37,7 +37,8 @@
 DECLARE_POOL(pool_head_connection,     "connection",     sizeof(struct connection));
 DECLARE_POOL(pool_head_conn_hash_node, "conn_hash_node", sizeof(struct conn_hash_node));
 DECLARE_POOL(pool_head_sockaddr,       "sockaddr",       sizeof(struct sockaddr_storage));
-DECLARE_POOL(pool_head_authority,      "authority",      HA_PP2_AUTHORITY_MAX);
+DECLARE_POOL(pool_head_pp_tlv_128,     "pp_tlv_128",     sizeof(struct conn_tlv_list) + HA_PP2_TLV_VALUE_128);
+DECLARE_POOL(pool_head_pp_tlv_256,     "pp_tlv_256",     sizeof(struct conn_tlv_list) + HA_PP2_TLV_VALUE_256);
 
 struct idle_conns idle_conns[MAX_THREADS] = { };
 struct xprt_ops *registered_xprt[XPRT_ENTRIES] = { NULL, };
@@ -52,6 +53,22 @@ struct mux_stopping_data mux_stopping_data[MAX_THREADS];
 /* disables sending of proxy-protocol-v2's LOCAL command */
 static int pp2_never_send_local;
 
+/* find the value of a received TLV for a given type */
+struct conn_tlv_list *conn_get_tlv(struct connection *conn, int type)
+{
+       struct conn_tlv_list *tlv = NULL;
+
+       if (!conn)
+               return NULL;
+
+       list_for_each_entry(tlv, &conn->tlv_list, list) {
+               if (tlv->type == type)
+                       return tlv;
+       }
+
+       return NULL;
+}
+
 /* Remove <conn> idle connection from its attached tree (idle, safe or avail).
  * If also present in the secondary server idle list, conn is removed from it.
  *
@@ -447,11 +464,10 @@ void conn_init(struct connection *conn, void *target)
                LIST_INIT(&conn->session_list);
        else
                LIST_INIT(&conn->stopping_list);
+       LIST_INIT(&conn->tlv_list);
        conn->subs = NULL;
        conn->src = NULL;
        conn->dst = NULL;
-       conn->proxy_authority = IST_NULL;
-       conn->proxy_unique_id = IST_NULL;
        conn->hash_node = NULL;
        conn->xprt = NULL;
        conn->reverse.target = NULL;
@@ -531,6 +547,8 @@ struct connection *conn_new(void *target)
 /* Releases a connection previously allocated by conn_new() */
 void conn_free(struct connection *conn)
 {
+       struct conn_tlv_list *tlv, *tlv_back = NULL;
+
        if (conn_is_back(conn))
                conn_backend_deinit(conn);
 
@@ -545,11 +563,16 @@ void conn_free(struct connection *conn)
        sockaddr_free(&conn->src);
        sockaddr_free(&conn->dst);
 
-       pool_free(pool_head_authority, istptr(conn->proxy_authority));
-       conn->proxy_authority = IST_NULL;
-
-       pool_free(pool_head_uniqueid, istptr(conn->proxy_unique_id));
-       conn->proxy_unique_id = IST_NULL;
+       /* Free all previously allocated TLVs */
+       list_for_each_entry_safe(tlv, tlv_back, &conn->tlv_list, list) {
+               LIST_DELETE(&tlv->list);
+               if (tlv->len > HA_PP2_TLV_VALUE_256)
+                       free(tlv);
+               else if (tlv->len < HA_PP2_TLV_VALUE_128)
+                       pool_free(pool_head_pp_tlv_128, tlv);
+               else
+                       pool_free(pool_head_pp_tlv_256, tlv);
+       }
 
        ha_free(&conn->reverse.name.area);
 
@@ -1080,8 +1103,10 @@ int conn_recv_proxy(struct connection *conn, int flag)
 
                /* TLV parsing */
                while (tlv_offset < total_v2_len) {
-                       struct tlv *tlv_packet;
                        struct ist tlv;
+                       struct tlv *tlv_packet = NULL;
+                       struct conn_tlv_list *new_tlv = NULL;
+                       size_t data_len = 0;
 
                        /* Verify that we have at least TLV_HEADER_SIZE bytes left */
                        if (tlv_offset + TLV_HEADER_SIZE > total_v2_len)
@@ -1095,6 +1120,7 @@ int conn_recv_proxy(struct connection *conn, int flag)
                        if (tlv_offset > total_v2_len)
                                goto bad_header;
 
+                       /* Prepare known TLV types */
                        switch (tlv_packet->type) {
                        case PP2_TYPE_CRC32C: {
                                uint32_t n_crc32c;
@@ -1121,36 +1147,49 @@ int conn_recv_proxy(struct connection *conn, int flag)
                        }
 #endif
                        case PP2_TYPE_AUTHORITY: {
+                               /* For now, keep the length restriction by HAProxy */
                                if (istlen(tlv) > HA_PP2_AUTHORITY_MAX)
                                        goto bad_header;
-                               conn->proxy_authority = ist2(pool_alloc(pool_head_authority), 0);
-                               if (!isttest(conn->proxy_authority))
-                                       goto fail;
-                               if (istcpy(&conn->proxy_authority, tlv, HA_PP2_AUTHORITY_MAX) < 0) {
-                                       /* This is impossible, because we verified that the TLV value fits. */
-                                       my_unreachable();
-                                       goto fail;
-                               }
+
                                break;
                        }
                        case PP2_TYPE_UNIQUE_ID: {
                                if (istlen(tlv) > UNIQUEID_LEN)
                                        goto bad_header;
-                               conn->proxy_unique_id = ist2(pool_alloc(pool_head_uniqueid), 0);
-                               if (!isttest(conn->proxy_unique_id))
-                                       goto fail;
-                               if (istcpy(&conn->proxy_unique_id, tlv, UNIQUEID_LEN) < 0) {
-                                       /* This is impossible, because we verified that the TLV value fits. */
-                                       my_unreachable();
-                                       goto fail;
-                               }
                                break;
                        }
                        default:
                                break;
                        }
+
+                       /* If we did not find a known TLV type that we can optimize for, we generically allocate it */
+                       data_len = get_tlv_length(tlv_packet);
+
+                       /* Prevent attackers from allocating too much memory */
+                       if (unlikely(data_len > HA_PP2_MAX_ALLOC))
+                               goto fail;
+
+                       /* Alloc memory based on data_len */
+                       if (data_len > HA_PP2_TLV_VALUE_256)
+                               new_tlv = malloc(get_tlv_length(tlv_packet) + sizeof(struct conn_tlv_list));
+                       else if (data_len <= HA_PP2_TLV_VALUE_128)
+                               new_tlv = pool_alloc(pool_head_pp_tlv_128);
+                       else
+                               new_tlv = pool_alloc(pool_head_pp_tlv_256);
+
+                       if (unlikely(!new_tlv))
+                               goto fail;
+
+                       new_tlv->type = tlv_packet->type;
+
+                       /* Save TLV to make it accessible via sample fetch */
+                       memcpy(new_tlv->value, tlv.ptr, data_len);
+                       new_tlv->len = data_len;
+
+                       LIST_APPEND(&conn->tlv_list, &new_tlv->list);
                }
 
+
                /* Verify that the PROXYv2 header ends at a TLV boundary.
                 * This is can not be true, because the TLV parsing already
                 * verifies that a TLV does not exceed the total length and
@@ -1969,10 +2008,12 @@ static int make_proxy_line_v2(char *buf, int buf_len, struct server *srv, struct
        }
 
        if (srv->pp_opts & SRV_PP_V2_AUTHORITY) {
+               struct conn_tlv_list *tlv = conn_get_tlv(remote, PP2_TYPE_AUTHORITY);
+
                value = NULL;
-               if (remote && isttest(remote->proxy_authority)) {
-                       value = istptr(remote->proxy_authority);
-                       value_len = istlen(remote->proxy_authority);
+               if (tlv) {
+                       value_len = tlv->len;
+                       value = tlv->value;
                }
 #ifdef USE_OPENSSL
                else {
@@ -2220,7 +2261,8 @@ int smp_fetch_fc_rcvd_proxy(const struct arg *args, struct sample *smp, const ch
 /* fetch the authority TLV from a PROXY protocol header */
 int smp_fetch_fc_pp_authority(const struct arg *args, struct sample *smp, const char *kw, void *private)
 {
-       struct connection *conn;
+       struct connection *conn = NULL;
+       struct conn_tlv_list *conn_tlv;
 
        conn = objt_conn(smp->sess->origin);
        if (!conn)
@@ -2231,21 +2273,29 @@ int smp_fetch_fc_pp_authority(const struct arg *args, struct sample *smp, const
                return 0;
        }
 
-       if (!isttest(conn->proxy_authority))
-               return 0;
+       conn_tlv = smp->ctx.p ? smp->ctx.p : LIST_ELEM(conn->tlv_list.n, struct conn_tlv_list *, list);
+       list_for_each_entry_from(conn_tlv, &conn->tlv_list, list) {
+               if (conn_tlv->type == PP2_TYPE_AUTHORITY) {
+                       smp->flags |= SMP_F_NOT_LAST;
+                       smp->data.type = SMP_T_STR;
+                       smp->data.u.str.area = conn_tlv->value;
+                       smp->data.u.str.data = conn_tlv->len;
+                       smp->ctx.p = conn_tlv;
 
-       smp->flags = 0;
-       smp->data.type = SMP_T_STR;
-       smp->data.u.str.area = istptr(conn->proxy_authority);
-       smp->data.u.str.data = istlen(conn->proxy_authority);
+                       return 1;
+               }
+       }
 
-       return 1;
+       smp->flags &= ~SMP_F_NOT_LAST;
+
+       return 0;
 }
 
 /* fetch the unique ID TLV from a PROXY protocol header */
 int smp_fetch_fc_pp_unique_id(const struct arg *args, struct sample *smp, const char *kw, void *private)
 {
-       struct connection *conn;
+       struct connection *conn = NULL;
+       struct conn_tlv_list *conn_tlv;
 
        conn = objt_conn(smp->sess->origin);
        if (!conn)
@@ -2256,15 +2306,22 @@ int smp_fetch_fc_pp_unique_id(const struct arg *args, struct sample *smp, const
                return 0;
        }
 
-       if (!isttest(conn->proxy_unique_id))
-               return 0;
+       conn_tlv = smp->ctx.p ? smp->ctx.p : LIST_ELEM(conn->tlv_list.n, struct conn_tlv_list *, list);
+       list_for_each_entry_from(conn_tlv, &conn->tlv_list, list) {
+               if (conn_tlv->type == PP2_TYPE_UNIQUE_ID) {
+                       smp->flags |= SMP_F_NOT_LAST;
+                       smp->data.type = SMP_T_STR;
+                       smp->data.u.str.area = conn_tlv->value;
+                       smp->data.u.str.data = conn_tlv->len;
+                       smp->ctx.p = conn_tlv;
 
-       smp->flags = 0;
-       smp->data.type = SMP_T_STR;
-       smp->data.u.str.area = istptr(conn->proxy_unique_id);
-       smp->data.u.str.data = istlen(conn->proxy_unique_id);
+                       return 1;
+               }
+       }
 
-       return 1;
+       smp->flags &= ~SMP_F_NOT_LAST;
+
+       return 0;
 }
 
 /* fetch the error code of a connection */