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