Blame SOURCES/squid-4.15-CVE-2021-28116.patch

4f9141
commit b003a0da7865caa25b5d1e70c79329b32409b02a (HEAD -> refs/heads/v4, refs/remotes/origin/v4)
4f9141
Author: Amos Jeffries <yadij@users.noreply.github.com>
4f9141
Date:   2021-09-24 21:53:11 +0000
4f9141
4f9141
    WCCP: Validate packets better (#899)
4f9141
    
4f9141
    Update WCCP to support exception based error handling for
4f9141
    parsing and processing we are moving Squid to for protocol
4f9141
    handling.
4f9141
    
4f9141
    Update the main WCCPv2 parsing checks to throw meaningful
4f9141
    exceptions when detected.
4f9141
4f9141
diff --git a/src/wccp2.cc b/src/wccp2.cc
4f9141
index ee592449c..6ef469e91 100644
4f9141
--- a/src/wccp2.cc
4f9141
+++ b/src/wccp2.cc
4f9141
@@ -1108,6 +1108,59 @@ wccp2ConnectionClose(void)
4f9141
  * Functions for handling the requests.
4f9141
  */
4f9141
 
4f9141
+/// Checks that the given area section ends inside the given (whole) area.
4f9141
+/// \param error the message to throw when the section does not fit
4f9141
+static void
4f9141
+CheckSectionLength(const void *sectionStart, const size_t sectionLength, const void *wholeStart, const size_t wholeSize, const char *error)
4f9141
+{
4f9141
+    assert(sectionStart);
4f9141
+    assert(wholeStart);
4f9141
+
4f9141
+    const auto wholeEnd = static_cast<const char*>(wholeStart) + wholeSize;
4f9141
+    assert(sectionStart >= wholeStart && "we never go backwards");
4f9141
+    assert(sectionStart <= wholeEnd && "we never go beyond our whole (but zero-sized fields are OK)");
4f9141
+    static_assert(sizeof(wccp2_i_see_you_t) <= PTRDIFF_MAX, "paranoid: no UB when subtracting in-whole pointers");
4f9141
+    // subtraction safe due to the three assertions above
4f9141
+    const auto remainderDiff = wholeEnd - static_cast<const char*>(sectionStart);
4f9141
+
4f9141
+    // casting safe due to the assertions above (and size_t definition)
4f9141
+    assert(remainderDiff >= 0);
4f9141
+    const auto remainderSize = static_cast<size_t>(remainderDiff);
4f9141
+
4f9141
+    if (sectionLength <= remainderSize)
4f9141
+        return;
4f9141
+
4f9141
+    throw TextException(error, Here());
4f9141
+}
4f9141
+
4f9141
+/// Checks that the area contains at least dataLength bytes after the header.
4f9141
+/// The size of the field header itself is not included in dataLength.
4f9141
+/// \returns the total field size -- the field header and field data combined
4f9141
+template<class FieldHeader>
4f9141
+static size_t
4f9141
+CheckFieldDataLength(const FieldHeader *header, const size_t dataLength, const void *areaStart, const size_t areaSize, const char *error)
4f9141
+{
4f9141
+    assert(header);
4f9141
+    const auto dataStart = reinterpret_cast<const char*>(header) + sizeof(header);
4f9141
+    CheckSectionLength(dataStart, dataLength, areaStart, areaSize, error);
4f9141
+    return sizeof(header) + dataLength; // no overflow after CheckSectionLength()
4f9141
+}
4f9141
+
4f9141
+/// Positions the given field at a given start within a given packet area.
4f9141
+/// The Field type determines the correct field size (used for bounds checking).
4f9141
+/// \param field the field pointer the function should set
4f9141
+/// \param areaStart the start of a packet (sub)structure containing the field
4f9141
+/// \param areaSize the size of the packet (sub)structure starting at areaStart
4f9141
+/// \param fieldStart the start of a field within the given area
4f9141
+/// \param error the message to throw when the field does not fit the area
4f9141
+template<class Field>
4f9141
+static void
4f9141
+SetField(Field *&field, const void *fieldStart, const void *areaStart, const size_t areaSize, const char *error)
4f9141
+{
4f9141
+    CheckSectionLength(fieldStart, sizeof(Field), areaStart, areaSize, error);
4f9141
+    field = static_cast<Field*>(const_cast<void*>(fieldStart));
4f9141
+}
4f9141
+
4f9141
 /*
4f9141
  * Accept the UDP packet
4f9141
  */
4f9141
@@ -1124,8 +1177,6 @@ wccp2HandleUdp(int sock, void *)
4f9141
 
4f9141
     /* These structs form the parts of the packet */
4f9141
 
4f9141
-    struct wccp2_item_header_t *header = NULL;
4f9141
-
4f9141
     struct wccp2_security_none_t *security_info = NULL;
4f9141
 
4f9141
     struct wccp2_service_info_t *service_info = NULL;
4f9141
@@ -1141,14 +1192,13 @@ wccp2HandleUdp(int sock, void *)
4f9141
     struct wccp2_cache_identity_info_t *cache_identity = NULL;
4f9141
 
4f9141
     struct wccp2_capability_info_header_t *router_capability_header = NULL;
4f9141
+    char *router_capability_data_start = nullptr;
4f9141
 
4f9141
     struct wccp2_capability_element_t *router_capability_element;
4f9141
 
4f9141
     struct sockaddr_in from;
4f9141
 
4f9141
     struct in_addr cache_address;
4f9141
-    int len, found;
4f9141
-    short int data_length, offset;
4f9141
     uint32_t tmp;
4f9141
     char *ptr;
4f9141
     int num_caches;
4f9141
@@ -1161,20 +1211,18 @@ wccp2HandleUdp(int sock, void *)
4f9141
     Ip::Address from_tmp;
4f9141
     from_tmp.setIPv4();
4f9141
 
4f9141
-    len = comm_udp_recvfrom(sock,
4f9141
-                            &wccp2_i_see_you,
4f9141
-                            WCCP_RESPONSE_SIZE,
4f9141
-                            0,
4f9141
-                            from_tmp);
4f9141
+    const auto lenOrError = comm_udp_recvfrom(sock, &wccp2_i_see_you, WCCP_RESPONSE_SIZE, 0, from_tmp);
4f9141
 
4f9141
-    if (len < 0)
4f9141
+    if (lenOrError < 0)
4f9141
         return;
4f9141
+    const auto len = static_cast<size_t>(lenOrError);
4f9141
 
4f9141
-    if (ntohs(wccp2_i_see_you.version) != WCCP2_VERSION)
4f9141
-        return;
4f9141
-
4f9141
-    if (ntohl(wccp2_i_see_you.type) != WCCP2_I_SEE_YOU)
4f9141
-        return;
4f9141
+    try {
4f9141
+        // TODO: Remove wccp2_i_see_you.data and use a buffer to read messages.
4f9141
+        const auto message_header_size = sizeof(wccp2_i_see_you) - sizeof(wccp2_i_see_you.data);
4f9141
+        Must2(len >= message_header_size, "incomplete WCCP message header");
4f9141
+        Must2(ntohs(wccp2_i_see_you.version) == WCCP2_VERSION, "WCCP version unsupported");
4f9141
+        Must2(ntohl(wccp2_i_see_you.type) == WCCP2_I_SEE_YOU, "WCCP packet type unsupported");
4f9141
 
4f9141
     /* FIXME INET6 : drop conversion boundary */
4f9141
     from_tmp.getSockAddr(from);
4f9141
@@ -1182,73 +1230,60 @@ wccp2HandleUdp(int sock, void *)
4f9141
     debugs(80, 3, "Incoming WCCPv2 I_SEE_YOU length " << ntohs(wccp2_i_see_you.length) << ".");
4f9141
 
4f9141
     /* Record the total data length */
4f9141
-    data_length = ntohs(wccp2_i_see_you.length);
4f9141
+    const auto data_length = ntohs(wccp2_i_see_you.length);
4f9141
+    Must2(data_length <= len - message_header_size,
4f9141
+          "malformed packet claiming it's bigger than received data");
4f9141
 
4f9141
-    offset = 0;
4f9141
-
4f9141
-    if (data_length > len) {
4f9141
-        debugs(80, DBG_IMPORTANT, "ERROR: Malformed WCCPv2 packet claiming it's bigger than received data");
4f9141
-        return;
4f9141
-    }
4f9141
+    size_t offset = 0;
4f9141
 
4f9141
     /* Go through the data structure */
4f9141
-    while (data_length > offset) {
4f9141
+    while (offset + sizeof(struct wccp2_item_header_t) <= data_length) {
4f9141
 
4f9141
         char *data = wccp2_i_see_you.data;
4f9141
 
4f9141
-        header = (struct wccp2_item_header_t *) &data[offset];
4f9141
+        const auto itemHeader = reinterpret_cast<const wccp2_item_header_t*>(&data[offset]);
4f9141
+        const auto itemSize = CheckFieldDataLength(itemHeader, ntohs(itemHeader->length),
4f9141
+                              data, data_length, "truncated record");
4f9141
+        // XXX: Check "The specified length must be a multiple of 4 octets"
4f9141
+        // requirement to avoid unaligned memory reads after the first item.
4f9141
 
4f9141
-        switch (ntohs(header->type)) {
4f9141
+        switch (ntohs(itemHeader->type)) {
4f9141
 
4f9141
         case WCCP2_SECURITY_INFO:
4f9141
-
4f9141
-            if (security_info != NULL) {
4f9141
-                debugs(80, DBG_IMPORTANT, "Duplicate security definition");
4f9141
-                return;
4f9141
-            }
4f9141
-
4f9141
-            security_info = (struct wccp2_security_none_t *) &wccp2_i_see_you.data[offset];
4f9141
+            Must2(!security_info, "duplicate security definition");
4f9141
+            SetField(security_info, itemHeader, itemHeader, itemSize,
4f9141
+                     "security definition truncated");
4f9141
             break;
4f9141
 
4f9141
         case WCCP2_SERVICE_INFO:
4f9141
-
4f9141
-            if (service_info != NULL) {
4f9141
-                debugs(80, DBG_IMPORTANT, "Duplicate service_info definition");
4f9141
-                return;
4f9141
-            }
4f9141
-
4f9141
-            service_info = (struct wccp2_service_info_t *) &wccp2_i_see_you.data[offset];
4f9141
+            Must2(!service_info, "duplicate service_info definition");
4f9141
+            SetField(service_info, itemHeader, itemHeader, itemSize,
4f9141
+                     "service_info definition truncated");
4f9141
             break;
4f9141
 
4f9141
         case WCCP2_ROUTER_ID_INFO:
4f9141
-
4f9141
-            if (router_identity_info != NULL) {
4f9141
-                debugs(80, DBG_IMPORTANT, "Duplicate router_identity_info definition");
4f9141
-                return;
4f9141
-            }
4f9141
-
4f9141
-            router_identity_info = (struct router_identity_info_t *) &wccp2_i_see_you.data[offset];
4f9141
+            Must2(!router_identity_info, "duplicate router_identity_info definition");
4f9141
+            SetField(router_identity_info, itemHeader, itemHeader, itemSize,
4f9141
+                     "router_identity_info definition truncated");
4f9141
             break;
4f9141
 
4f9141
         case WCCP2_RTR_VIEW_INFO:
4f9141
-
4f9141
-            if (router_view_header != NULL) {
4f9141
-                debugs(80, DBG_IMPORTANT, "Duplicate router_view definition");
4f9141
-                return;
4f9141
-            }
4f9141
-
4f9141
-            router_view_header = (struct router_view_t *) &wccp2_i_see_you.data[offset];
4f9141
+            Must2(!router_view_header, "duplicate router_view definition");
4f9141
+            SetField(router_view_header, itemHeader, itemHeader, itemSize,
4f9141
+                     "router_view definition truncated");
4f9141
             break;
4f9141
 
4f9141
-        case WCCP2_CAPABILITY_INFO:
4f9141
-
4f9141
-            if (router_capability_header != NULL) {
4f9141
-                debugs(80, DBG_IMPORTANT, "Duplicate router_capability definition");
4f9141
-                return;
4f9141
-            }
4f9141
+        case WCCP2_CAPABILITY_INFO: {
4f9141
+            Must2(!router_capability_header, "duplicate router_capability definition");
4f9141
+            SetField(router_capability_header, itemHeader, itemHeader, itemSize,
4f9141
+                     "router_capability definition truncated");
4f9141
 
4f9141
-            router_capability_header = (struct wccp2_capability_info_header_t *) &wccp2_i_see_you.data[offset];
4f9141
+            CheckFieldDataLength(router_capability_header, ntohs(router_capability_header->capability_info_length),
4f9141
+                                 itemHeader, itemSize, "capability info truncated");
4f9141
+            router_capability_data_start = reinterpret_cast<char*>(router_capability_header) +
4f9141
+                                           sizeof(*router_capability_header);
4f9141
             break;
4f9141
+        }
4f9141
 
4f9141
         /* Nothing to do for the types below */
4f9141
 
4f9141
@@ -1257,22 +1292,17 @@ wccp2HandleUdp(int sock, void *)
4f9141
             break;
4f9141
 
4f9141
         default:
4f9141
-            debugs(80, DBG_IMPORTANT, "Unknown record type in WCCPv2 Packet (" << ntohs(header->type) << ").");
4f9141
+            debugs(80, DBG_IMPORTANT, "Unknown record type in WCCPv2 Packet (" << ntohs(itemHeader->type) << ").");
4f9141
         }
4f9141
 
4f9141
-        offset += sizeof(struct wccp2_item_header_t);
4f9141
-        offset += ntohs(header->length);
4f9141
-
4f9141
-        if (offset > data_length) {
4f9141
-            debugs(80, DBG_IMPORTANT, "Error: WCCPv2 packet tried to tell us there is data beyond the end of the packet");
4f9141
-            return;
4f9141
-        }
4f9141
+        offset += itemSize;
4f9141
+        assert(offset <= data_length && "CheckFieldDataLength(itemHeader...) established that");
4f9141
     }
4f9141
 
4f9141
-    if ((security_info == NULL) || (service_info == NULL) || (router_identity_info == NULL) || (router_view_header == NULL)) {
4f9141
-        debugs(80, DBG_IMPORTANT, "Incomplete WCCPv2 Packet");
4f9141
-        return;
4f9141
-    }
4f9141
+    Must2(security_info, "packet missing security definition");
4f9141
+    Must2(service_info, "packet missing service_info definition");
4f9141
+    Must2(router_identity_info, "packet missing router_identity_info definition");
4f9141
+    Must2(router_view_header, "packet missing router_view definition");
4f9141
 
4f9141
     debugs(80, 5, "Complete packet received");
4f9141
 
4f9141
@@ -1308,10 +1338,7 @@ wccp2HandleUdp(int sock, void *)
4f9141
             break;
4f9141
     }
4f9141
 
4f9141
-    if (router_list_ptr->next == NULL) {
4f9141
-        debugs(80, DBG_IMPORTANT, "WCCPv2 Packet received from unknown router");
4f9141
-        return;
4f9141
-    }
4f9141
+    Must2(router_list_ptr->next, "packet received from unknown router");
4f9141
 
4f9141
     /* Set the router id */
4f9141
     router_list_ptr->info->router_address = router_identity_info->router_id_element.router_address;
4f9141
@@ -1331,11 +1358,20 @@ wccp2HandleUdp(int sock, void *)
4f9141
         }
4f9141
     } else {
4f9141
 
4f9141
-        char *end = ((char *) router_capability_header) + sizeof(*router_capability_header) + ntohs(router_capability_header->capability_info_length) - sizeof(struct wccp2_capability_info_header_t);
4f9141
-
4f9141
-        router_capability_element = (struct wccp2_capability_element_t *) (((char *) router_capability_header) + sizeof(*router_capability_header));
4f9141
-
4f9141
-        while ((char *) router_capability_element <= end) {
4f9141
+        const auto router_capability_data_length = ntohs(router_capability_header->capability_info_length);
4f9141
+        assert(router_capability_data_start);
4f9141
+        const auto router_capability_data_end = router_capability_data_start +
4f9141
+                                                router_capability_data_length;
4f9141
+        for (auto router_capability_data_current = router_capability_data_start;
4f9141
+                router_capability_data_current < router_capability_data_end;) {
4f9141
+
4f9141
+            SetField(router_capability_element, router_capability_data_current,
4f9141
+                     router_capability_data_start, router_capability_data_length,
4f9141
+                     "capability element header truncated");
4f9141
+            const auto elementSize = CheckFieldDataLength(
4f9141
+                                         router_capability_element, ntohs(router_capability_element->capability_length),
4f9141
+                                         router_capability_data_start, router_capability_data_length,
4f9141
+                                         "capability element truncated");
4f9141
 
4f9141
             switch (ntohs(router_capability_element->capability_type)) {
4f9141
 
4f9141
@@ -1377,7 +1413,7 @@ wccp2HandleUdp(int sock, void *)
4f9141
                 debugs(80, DBG_IMPORTANT, "Unknown capability type in WCCPv2 Packet (" << ntohs(router_capability_element->capability_type) << ").");
4f9141
             }
4f9141
 
4f9141
-            router_capability_element = (struct wccp2_capability_element_t *) (((char *) router_capability_element) + sizeof(struct wccp2_item_header_t) + ntohs(router_capability_element->capability_length));
4f9141
+            router_capability_data_current += elementSize;
4f9141
         }
4f9141
     }
4f9141
 
4f9141
@@ -1396,23 +1432,34 @@ wccp2HandleUdp(int sock, void *)
4f9141
     num_caches = 0;
4f9141
 
4f9141
     /* Check to see if we're the master cache and update the cache list */
4f9141
-    found = 0;
4f9141
+    bool found = false;
4f9141
     service_list_ptr->lowest_ip = 1;
4f9141
     cache_list_ptr = &router_list_ptr->cache_list_head;
4f9141
 
4f9141
     /* to find the list of caches, we start at the end of the router view header */
4f9141
 
4f9141
     ptr = (char *) (router_view_header) + sizeof(struct router_view_t);
4f9141
+    const auto router_view_size = sizeof(struct router_view_t) +
4f9141
+                                  ntohs(router_view_header->header.length);
4f9141
 
4f9141
     /* Then we read the number of routers */
4f9141
-    memcpy(&tmp, ptr, sizeof(tmp));
4f9141
+    const uint32_t *routerCountRaw = nullptr;
4f9141
+    SetField(routerCountRaw, ptr, router_view_header, router_view_size,
4f9141
+             "malformed packet (truncated router view info w/o number of routers)");
4f9141
 
4f9141
     /* skip the number plus all the ip's */
4f9141
-
4f9141
-    ptr += sizeof(tmp) + (ntohl(tmp) * sizeof(struct in_addr));
4f9141
+    ptr += sizeof(*routerCountRaw);
4f9141
+    const auto ipCount = ntohl(*routerCountRaw);
4f9141
+    const auto ipsSize = ipCount * sizeof(struct in_addr); // we check for unsigned overflow below
4f9141
+    Must2(ipsSize / sizeof(struct in_addr) != ipCount, "huge IP address count");
4f9141
+    CheckSectionLength(ptr, ipsSize, router_view_header, router_view_size, "invalid IP address count");
4f9141
+    ptr += ipsSize;
4f9141
 
4f9141
     /* Then read the number of caches */
4f9141
-    memcpy(&tmp, ptr, sizeof(tmp));
4f9141
+    const uint32_t *cacheCountRaw = nullptr;
4f9141
+    SetField(cacheCountRaw, ptr, router_view_header, router_view_size,
4f9141
+             "malformed packet (truncated router view info w/o cache count)");
4f9141
+    memcpy(&tmp, cacheCountRaw, sizeof(tmp)); // TODO: Replace tmp with cacheCount
4f9141
     ptr += sizeof(tmp);
4f9141
 
4f9141
     if (ntohl(tmp) != 0) {
4f9141
@@ -1426,7 +1473,8 @@ wccp2HandleUdp(int sock, void *)
4f9141
 
4f9141
             case WCCP2_ASSIGNMENT_METHOD_HASH:
4f9141
 
4f9141
-                cache_identity = (struct wccp2_cache_identity_info_t *) ptr;
4f9141
+                SetField(cache_identity, ptr, router_view_header, router_view_size,
4f9141
+                         "malformed packet (truncated router view info cache w/o assignment hash)");
4f9141
 
4f9141
                 ptr += sizeof(struct wccp2_cache_identity_info_t);
4f9141
 
4f9141
@@ -1437,13 +1485,15 @@ wccp2HandleUdp(int sock, void *)
4f9141
 
4f9141
             case WCCP2_ASSIGNMENT_METHOD_MASK:
4f9141
 
4f9141
-                cache_mask_info = (struct cache_mask_info_t *) ptr;
4f9141
+                SetField(cache_mask_info, ptr, router_view_header, router_view_size,
4f9141
+                         "malformed packet (truncated router view info cache w/o assignment mask)");
4f9141
 
4f9141
                 /* The mask assignment has an undocumented variable length entry here */
4f9141
 
4f9141
                 if (ntohl(cache_mask_info->num1) == 3) {
4f9141
 
4f9141
-                    cache_mask_identity = (struct wccp2_cache_mask_identity_info_t *) ptr;
4f9141
+                    SetField(cache_mask_identity, ptr, router_view_header, router_view_size,
4f9141
+                             "malformed packet (truncated router view info cache w/o assignment mask identity)");
4f9141
 
4f9141
                     ptr += sizeof(struct wccp2_cache_mask_identity_info_t);
4f9141
 
4f9141
@@ -1474,10 +1524,7 @@ wccp2HandleUdp(int sock, void *)
4f9141
             debugs (80, 5,  "checking cache list: (" << std::hex << cache_address.s_addr << ":" <<  router_list_ptr->local_ip.s_addr << ")");
4f9141
 
4f9141
             /* Check to see if it's the master, or us */
4f9141
-
4f9141
-            if (cache_address.s_addr == router_list_ptr->local_ip.s_addr) {
4f9141
-                found = 1;
4f9141
-            }
4f9141
+            found = found || (cache_address.s_addr == router_list_ptr->local_ip.s_addr);
4f9141
 
4f9141
             if (cache_address.s_addr < router_list_ptr->local_ip.s_addr) {
4f9141
                 service_list_ptr->lowest_ip = 0;
4f9141
@@ -1494,7 +1541,7 @@ wccp2HandleUdp(int sock, void *)
4f9141
         cache_list_ptr->next = NULL;
4f9141
 
4f9141
         service_list_ptr->lowest_ip = 1;
4f9141
-        found = 1;
4f9141
+        found = true;
4f9141
         num_caches = 1;
4f9141
     }
4f9141
 
4f9141
@@ -1502,7 +1549,7 @@ wccp2HandleUdp(int sock, void *)
4f9141
 
4f9141
     router_list_ptr->num_caches = htonl(num_caches);
4f9141
 
4f9141
-    if ((found == 1) && (service_list_ptr->lowest_ip == 1)) {
4f9141
+    if (found && (service_list_ptr->lowest_ip == 1)) {
4f9141
         if (ntohl(router_view_header->change_number) != router_list_ptr->member_change) {
4f9141
             debugs(80, 4, "Change detected - queueing up new assignment");
4f9141
             router_list_ptr->member_change = ntohl(router_view_header->change_number);
4f9141
@@ -1515,6 +1562,10 @@ wccp2HandleUdp(int sock, void *)
4f9141
         eventDelete(wccp2AssignBuckets, NULL);
4f9141
         debugs(80, 5, "I am not the lowest ip cache - not assigning buckets");
4f9141
     }
4f9141
+
4f9141
+    } catch (...) {
4f9141
+        debugs(80, DBG_IMPORTANT, "ERROR: Ignoring WCCPv2 message: " << CurrentException);
4f9141
+    }
4f9141
 }
4f9141
 
4f9141
 static void