New type sockaddr_in46 has been recently introduced. It serves as a
union which can store either an IPv4 or IPv6 address. The objective is
to reduce the storage size for QUIC datagrams which previously uses a
sockaddr_storage field.
On qc_new_conn(), source and destination addresses from the datagram are
passed to the function as sockaddr_storage so that they are copied into
the newly built quic_conn instance. However, the involved memcpy() is
producing a buffer overflow as sockaddr_in46 is smaller than
sockaddr_storage type.
This patch fixes this by defining a new helper function
in46un_to_addr(). This allows to convert safely sockaddr_in46 to a plain
sockaddr type. The function is now used before invoking qc_new_conn().
Note that there is still other several places where union sockaddr_in46
is casted as sockaddr_storage type. However, these should be safe as in
these cases sockaddr fields are accessed individually after checking
ss_family. The memory issue only exists when plain memcpy is used.
This bug was detected using ASAN. It generates the following traces when
a QUIC connection is instantiated.
==37474==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7c3bb9a61100 at pc 0x5631f52c3946 bp 0x7ffc83e45b50 sp 0x7ffc83e45310
READ of size 128 at 0x7c3bb9a61100 thread T0
#0 0x5631f52c3945 in __asan_memcpy (/home/amaury/work/haproxy-quic-dev/haproxy+0x3ae945) (BuildId:
a7ccfd74b7a71a869b8ff8d13f6dcde8c82c1487)
#1 0x5631f55f9e34 in qc_new_conn /home/amaury/work/haproxy-quic-dev/src/quic_conn.c:1311:2
#2 0x5631f558d5c3 in quic_rx_pkt_retrieve_conn /home/amaury/work/haproxy-quic-dev/src/quic_rx.c:1875:10
#3 0x5631f558330b in quic_dgram_parse /home/amaury/work/haproxy-quic-dev/src/quic_rx.c:2463:29
#4 0x5631f5625da6 in quic_lstnr_dghdlr /home/amaury/work/haproxy-quic-dev/src/quic_sock.c:206:3
#5 0x5631f6a64173 in run_tasks_from_lists /home/amaury/work/haproxy-quic-dev/src/task.c:660:26
#6 0x5631f6a6ba1e in process_runnable_tasks /home/amaury/work/haproxy-quic-dev/src/task.c:913:9
#7 0x5631f5e984c3 in run_poll_loop /home/amaury/work/haproxy-quic-dev/src/haproxy.c:2982:3
#8 0x5631f5e9a715 in run_thread_poll_loop /home/amaury/work/haproxy-quic-dev/src/haproxy.c:3212:2
#9 0x5631f5e9f732 in main /home/amaury/work/haproxy-quic-dev/src/haproxy.c:3853:2
#10 0x7f2bba8276c0 (/usr/lib/libc.so.6+0x276c0) (BuildId:
ca0db5ab57a36507d61bbcf4988d344974331f19)
#11 0x7f2bba8277f8 in __libc_start_main (/usr/lib/libc.so.6+0x277f8) (BuildId:
ca0db5ab57a36507d61bbcf4988d344974331f19)
#12 0x5631f51be594 in _start (/home/amaury/work/haproxy-quic-dev/haproxy+0x2a9594) (BuildId:
a7ccfd74b7a71a869b8ff8d13f6dcde8c82c1487)
No need to backport.
return next;
}
+/* Initializes <dst> sockaddr_storage from <src> union sockaddr_in46 type.
+ * Only unspec, IPv4 and IPv6 addresses are supported.
+ */
+static inline void in46un_to_addr(const union sockaddr_in46 *src,
+ struct sockaddr_storage *dst)
+{
+ struct sockaddr_in *in;
+ struct sockaddr_in6 *in6;
+
+ switch (((struct sockaddr_storage *)src)->ss_family) {
+ case AF_UNSPEC:
+ memset(dst, 0, sizeof(*dst));
+ break;
+
+ case AF_INET:
+ in = (struct sockaddr_in *)dst;
+ in->sin_family = AF_INET;
+ in->sin_addr = src->in4.sin_addr;
+ in->sin_port = src->in4.sin_port;
+ break;
+
+ case AF_INET6:
+ in6 = (struct sockaddr_in6 *)dst;
+ in6->sin6_family = AF_INET6;
+ in6->sin6_addr = src->in6.sin6_addr;
+ in6->sin6_port = src->in6.sin6_port;
+ break;
+
+ default:
+ ABORT_NOW();
+ break;
+ }
+}
+
#endif /* USE_QUIC */
#endif /* _HAPROXY_QUIC_SOCK_H */
pool_free(pool_head_quic_connection_id, conn_id);
}
else {
+ struct sockaddr_storage saddr, daddr;
+ in46un_to_addr(&pkt->saddr, &saddr);
+ in46un_to_addr(&dgram->daddr, &daddr);
+
qc = qc_new_conn(l, pkt, &token_odcid, NULL, conn_id,
- (struct sockaddr_storage *)&dgram->daddr,
- (struct sockaddr_storage *)&pkt->saddr);
+ &daddr, &saddr);
if (qc == NULL) {
quic_cid_delete(conn_id); /* Removes CID from global tree as it points to a NULL qc. */
pool_free(pool_head_quic_connection_id, conn_id);