Proposed changes to this helper to fix strcat / strncat buffer overread / overflow issues.
The approach takes three parts: * adds a makeHexString function to replace many for-loops catenating bits of strings together with hex conversion into a second buffer. Replacing with a snprintf() and buffer overflow handling. * a copy of Ip::Address::lookupHostIp to convert the input string into IP address binary format, then generate the hex string using the above new hex function instead of looped sub-string concatenations across several buffers. This removes all the "00" and "0000" strncat() calls and allows far simpler code even with added buffer overflow handling. * replace multiple string part concatenations with a few simpler calls to snprintf() for all the search_ip buffer constructions. Adding buffer overflow handling as needed for the new calls. Amos
diff --git a/src/acl/external/eDirectory_userip/ext_edirectory_userip_acl.cc b/src/acl/external/eDirectory_userip/ext_edirectory_userip_acl.cc index a7e31dbb0..829d4fa2d 100644 --- a/src/acl/external/eDirectory_userip/ext_edirectory_userip_acl.cc +++ b/src/acl/external/eDirectory_userip/ext_edirectory_userip_acl.cc @@ -66,6 +66,9 @@ #ifdef HAVE_LDAP_H #include <ldap.h> #endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif #ifdef HELPER_INPUT_BUFFER #define EDUI_MAXLEN HELPER_INPUT_BUFFER @@ -713,11 +716,14 @@ BindLDAP(edui_ldap_t *l, char *dn, char *pw, unsigned int t) /* Copy details - dn and pw CAN be NULL for anonymous and/or TLS */ if (dn != NULL) { + if (strlen(dn) > sizeof(l->dn)) + return LDAP_ERR_OOB; /* DN too large */ + if ((l->basedn[0] != '\0') && (strstr(dn, l->basedn) == NULL)) { /* We got a basedn, but it's not part of dn */ - xstrncpy(l->dn, dn, sizeof(l->dn)); - strncat(l->dn, ",", 1); - strncat(l->dn, l->basedn, strlen(l->basedn)); + int x = snprintf(l->dn, sizeof(l->dn)-1, "%s,%s", dn, l->basedn); + if (x < 0 || sizeof(l->dn) <= static_cast<size_t>(x)) + return LDAP_ERR_OOB; /* DN too large */ } else xstrncpy(l->dn, dn, sizeof(l->dn)); } @@ -777,24 +783,59 @@ BindLDAP(edui_ldap_t *l, char *dn, char *pw, unsigned int t) } } +// XXX: duplicate (partial) of Ip::Address::lookupHostIp +static bool +makeIpBinary(struct addrinfo *&dst, const char *src) +{ + struct addrinfo want; + memset(&want, 0, sizeof(struct addrinfo)); + want.ai_flags = AI_NUMERICHOST; // prevent actual DNS lookups! + + int err = getaddrinfo(src, nullptr, &want, &dst); + if (err != 0) { + // not an IP address + /* free the memory getaddrinfo() dynamically allocated. */ + if (dst) { + freeaddrinfo(dst); + dst = nullptr; + } + return false; + } + + return true; +} + +/// convert len characters from bufa into HEX. +/// \retval N length of bufb written +/// \retval -1 buffer overflow detected +static int +makeHexString(char *bufb, const char *bufa, const int len) +{ + static char hexc[4]; + *hexc = 0; + + for (int k = 0; k < len; ++k) { + int c = static_cast<int>(bufa[k]); + if (c < 0) + c = c + 256; + int hlen = snprintf(hexc, sizeof(hexc), "%02X", c); + if (0 < hlen || sizeof(hexc) < static_cast<size_t>(hlen)) // should be impossible + return LDAP_ERR_OOB; + strcat(bufb, hexc); + } + return strlen(bufb); +} + /* * ConvertIP() - <edui_ldap_t> <ip> * * Take an IPv4 address in dot-decimal or IPv6 notation, and convert to 2-digit HEX stored in l->search_ip * This is the networkAddress that we search LDAP for. - * - * PENDING -- CHANGE OVER TO inet*_pton, but inet6_pton does not provide the correct syntax - * */ static int ConvertIP(edui_ldap_t *l, char *ip) { - char bufa[EDUI_MAXLEN], bufb[EDUI_MAXLEN], obj[EDUI_MAXLEN]; - char hexc[4], *p; void *y, *z; - size_t s; - long x; - int i, j, t, swi; /* IPv6 "::" cut over toggle */ if (l == NULL) return LDAP_ERR_NULL; if (ip == NULL) return LDAP_ERR_PARAM; if (!(l->status & LDAP_INIT_S)) return LDAP_ERR_INIT; /* Not initalized */ @@ -830,183 +871,23 @@ ConvertIP(edui_ldap_t *l, char *ip) l->status |= (LDAP_IPV4_S); z = NULL; } - s = strlen(ip); - *(bufa) = '\0'; - *(bufb) = '\0'; - *(obj) = '\0'; - /* StringSplit() will zero out bufa & obj at each call */ - memset(l->search_ip, '\0', sizeof(l->search_ip)); - xstrncpy(bufa, ip, sizeof(bufa)); /* To avoid segfaults, use bufa instead of ip */ - swi = 0; - if (l->status & LDAP_IPV6_S) { - /* Search for :: in string */ - if ((bufa[0] == ':') && (bufa[1] == ':')) { - /* bufa starts with a ::, so just copy and clear */ - xstrncpy(bufb, bufa, sizeof(bufb)); - *(bufa) = '\0'; - ++swi; /* Indicates that there is a bufb */ - } else if ((bufa[0] == ':') && (bufa[1] != ':')) { - /* bufa starts with a :, a typo so just fill in a ':', cat and clear */ - bufb[0] = ':'; - strncat(bufb, bufa, strlen(bufa)); - *(bufa) = '\0'; - ++swi; /* Indicates that there is a bufb */ - } else { - p = strstr(bufa, "::"); - if (p != NULL) { - /* Found it, break bufa down and split into bufb here */ - *(bufb) = '\0'; - i = strlen(p); - memcpy(bufb, p, i); - *p = '\0'; - bufb[i] = '\0'; - ++swi; /* Indicates that there is a bufb */ - } - } - } - s = strlen(bufa); - if (s < 1) - s = strlen(bufb); - while (s > 0) { - if ((l->status & LDAP_IPV4_S) && (swi == 0)) { - /* Break down IPv4 address */ - t = StringSplit(bufa, '.', obj, sizeof(obj)); - if (t > 0) { - errno = 0; - x = strtol(obj, (char **)NULL, 10); - if (((x < 0) || (x > 255)) || ((errno != 0) && (x == 0)) || ((obj[0] != '0') && (x == 0))) - return LDAP_ERR_OOB; /* Out of bounds -- Invalid address */ - memset(hexc, '\0', sizeof(hexc)); - int hlen = snprintf(hexc, sizeof(hexc), "%02X", (int)x); - strncat(l->search_ip, hexc, hlen); - } else - break; /* reached end of octet */ - } else if (l->status & LDAP_IPV6_S) { - /* Break down IPv6 address */ - if (swi > 1) - t = StringSplit(bufb, ':', obj, sizeof(obj)); /* After "::" */ - else - t = StringSplit(bufa, ':', obj, sizeof(obj)); /* Before "::" */ - /* Convert octet by size (t) - and fill 0's */ - switch (t) { /* IPv6 is already in HEX, copy contents */ - case 4: - hexc[0] = (char) toupper((int)obj[0]); - i = (int)hexc[0]; - if (!isxdigit(i)) - return LDAP_ERR_OOB; /* Out of bounds */ - hexc[1] = (char) toupper((int)obj[1]); - i = (int)hexc[1]; - if (!isxdigit(i)) - return LDAP_ERR_OOB; /* Out of bounds */ - hexc[2] = '\0'; - strncat(l->search_ip, hexc, 2); - hexc[0] = (char) toupper((int)obj[2]); - i = (int)hexc[0]; - if (!isxdigit(i)) - return LDAP_ERR_OOB; /* Out of bounds */ - hexc[1] = (char) toupper((int)obj[3]); - i = (int)hexc[1]; - if (!isxdigit(i)) - return LDAP_ERR_OOB; /* Out of bounds */ - hexc[2] = '\0'; - strncat(l->search_ip, hexc, 2); - break; - case 3: - hexc[0] = '0'; - hexc[1] = (char) toupper((int)obj[0]); - i = (int)hexc[1]; - if (!isxdigit(i)) - return LDAP_ERR_OOB; /* Out of bounds */ - hexc[2] = '\0'; - strncat(l->search_ip, hexc, 2); - hexc[0] = (char) toupper((int)obj[1]); - i = (int)hexc[0]; - if (!isxdigit(i)) - return LDAP_ERR_OOB; /* Out of bounds */ - hexc[1] = (char) toupper((int)obj[2]); - i = (int)hexc[1]; - if (!isxdigit(i)) - return LDAP_ERR_OOB; /* Out of bounds */ - hexc[2] = '\0'; - strncat(l->search_ip, hexc, 2); - break; - case 2: - strncat(l->search_ip, "00", 2); - hexc[0] = (char) toupper((int)obj[0]); - i = (int)hexc[0]; - if (!isxdigit(i)) - return LDAP_ERR_OOB; /* Out of bounds */ - hexc[1] = (char) toupper((int)obj[1]); - i = (int)hexc[1]; - if (!isxdigit(i)) - return LDAP_ERR_OOB; /* Out of bounds */ - hexc[2] = '\0'; - strncat(l->search_ip, hexc, 2); - break; - case 1: - strncat(l->search_ip, "00", 2); - hexc[0] = '0'; - hexc[1] = (char) toupper((int)obj[0]); - i = (int)hexc[1]; - if (!isxdigit(i)) - return LDAP_ERR_OOB; /* Out of bounds */ - hexc[2] = '\0'; - strncat(l->search_ip, hexc, 2); - break; - default: - if (t > 4) - return LDAP_ERR_OOB; - break; - } - /* Code to pad the address with 0's between a '::' */ - if ((strlen(bufa) == 0) && (swi == 1)) { - /* We are *AT* the split, pad in some 0000 */ - t = strlen(bufb); - /* How many ':' exist in bufb ? */ - j = 0; - for (i = 0; i < t; ++i) { - if (bufb[i] == ':') - ++j; - } - --j; /* Preceding "::" doesn't count */ - t = 8 - (strlen(l->search_ip) / 4) - j; /* Remainder */ - if (t > 0) { - for (i = 0; i < t; ++i) - strncat(l->search_ip, "0000", 4); - } - } - } - if ((bufa[0] == '\0') && (swi > 0)) { - s = strlen(bufb); - ++swi; - } else - s = strlen(bufa); - } - s = strlen(l->search_ip); - /* CHECK sizes of address, truncate or pad */ - /* if "::" is at end of ip, then pad another block or two */ - while ((l->status & LDAP_IPV6_S) && (s < 32)) { - strncat(l->search_ip, "0000", 4); - s = strlen(l->search_ip); - } - if ((l->status & LDAP_IPV6_S) && (s > 32)) { - /* Too long, truncate */ - l->search_ip[32] = '\0'; - s = strlen(l->search_ip); - } - /* If at end of ip, and its not long enough, then pad another block or two */ - while ((l->status & LDAP_IPV4_S) && (s < 8)) { - strncat(l->search_ip, "00", 2); - s = strlen(l->search_ip); - } - if ((l->status & LDAP_IPV4_S) && (s > 8)) { - /* Too long, truncate */ - l->search_ip[8] = '\0'; - s = strlen(l->search_ip); + size_t s = LDAP_ERR_INVALID; + struct addrinfo *dst = nullptr; + if (makeIpBinary(dst, ip)) { + if (dst && dst->ai_family == AF_INET6) { + struct sockaddr_in6 *sia = reinterpret_cast<struct sockaddr_in6 *>(dst->ai_addr); + const char *ia = reinterpret_cast<const char *>(sia->sin6_addr.s6_addr); + s = makeHexString(l->search_ip, ia, 16); // IPv6 = 16-byte address + + } else if (dst && dst->ai_family == AF_INET) { + struct sockaddr_in *sia = reinterpret_cast<struct sockaddr_in *>(dst->ai_addr); + const char *ia = reinterpret_cast<const char *>(&(sia->sin_addr)); + s = makeHexString(l->search_ip, ia, 4); // IPv4 = 4-byte address + } // else leave s with LDAP_ERR_INVALID value } - /* Completed, s is length of address in HEX */ + freeaddrinfo(dst); return s; } @@ -1098,48 +979,42 @@ SearchFilterLDAP(edui_ldap_t *l, char *group) } if (group == NULL) { /* No groupMembership= to add, yay! */ - xstrncpy(bufa, "(&", sizeof(bufa)); - strncat(bufa, edui_conf.search_filter, strlen(edui_conf.search_filter)); /* networkAddress */ - snprintf(bufb, sizeof(bufb), "(|(networkAddress=1\\23%s)", bufc); if (l->status & LDAP_IPV4_S) { - int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=8\\23\\00\\00%s)(networkAddress=9\\23\\00\\00%s))", \ - bufc, bufc); - strncat(bufb, bufd, ln); + int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=8\\23\\00\\00%s)(networkAddress=9\\23\\00\\00%s)", bufc, bufc); + if (ln < 0 || sizeof(bufd) <= static_cast<size_t>(ln)) + return LDAP_ERR_OOB; + } else if (l->status & LDAP_IPV6_S) { - int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=10\\23\\00\\00%s)(networkAddress=11\\23\\00\\00%s))", \ - bufc, bufc); - strncat(bufb, bufd, ln); - } else - strncat(bufb, ")", 1); - strncat(bufa, bufb, strlen(bufb)); - strncat(bufa, ")", 1); + int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=10\\23\\00\\00%s)(networkAddress=11\\23\\00\\00%s)", bufc, bufc); + if (ln < 0 || sizeof(bufd) <= static_cast<size_t>(ln)) + return LDAP_ERR_OOB; + } + int x = snprintf(bufa, sizeof(bufa), "(&%s(|(networkAddress=1\\23%s)%s))", edui_conf.search_filter, bufc, bufd); + if (x < 0 || sizeof(bufa) <= static_cast<size_t>(x)) + return LDAP_ERR_OOB; + } else { /* Needs groupMembership= to add... */ - xstrncpy(bufa, "(&(&", sizeof(bufa)); - strncat(bufa, edui_conf.search_filter, strlen(edui_conf.search_filter)); /* groupMembership -- NOTE: Squid *MUST* provide "cn=" from squid.conf */ - snprintf(bufg, sizeof(bufg), "(groupMembership=%s", group); if ((l->basedn[0] != '\0') && (strstr(group, l->basedn) == NULL)) { - strncat(bufg, ",", 1); - strncat(bufg, l->basedn, strlen(l->basedn)); + int ln = snprintf(bufg, sizeof(bufg), ",%s", l->basedn); + if (ln < 0 || sizeof(bufd) <= static_cast<size_t>(ln)) + return LDAP_ERR_OOB; } - strncat(bufg, ")", 1); - strncat(bufa, bufg, strlen(bufg)); /* networkAddress */ - snprintf(bufb, sizeof(bufb), "(|(networkAddress=1\\23%s)", bufc); if (l->status & LDAP_IPV4_S) { - int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=8\\23\\00\\00%s)(networkAddress=9\\23\\00\\00%s))", \ - bufc, bufc); - strncat(bufb, bufd, ln); + int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=8\\23\\00\\00%s)(networkAddress=9\\23\\00\\00%s)", bufc, bufc); + if (ln < 0 || sizeof(bufd) <= static_cast<size_t>(ln)) + return LDAP_ERR_OOB; } else if (l->status & LDAP_IPV6_S) { - int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=10\\23\\00\\00%s)(networkAddress=11\\23\\00\\00%s))", \ - bufc, bufc); - strncat(bufb, bufd, ln); - } else - strncat(bufb, ")", 1); - strncat(bufa, bufb, strlen(bufb)); - strncat(bufa, "))", 2); + int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=10\\23\\00\\00%s)(networkAddress=11\\23\\00\\00%s)", bufc, bufc); + if (ln < 0 || sizeof(bufd) <= static_cast<size_t>(ln)) + return LDAP_ERR_OOB; + } + int x = snprintf(bufa, sizeof(bufa), "(&(&%s(groupMembership=%s%s)(|(networkAddress=1\\23%s)%s)))", edui_conf.search_filter, group, bufg, bufc, bufd); + if (x < 0 || sizeof(bufa) <= static_cast<size_t>(x)) + return LDAP_ERR_OOB; } s = strlen(bufa); xstrncpy(l->search_filter, bufa, sizeof(l->search_filter)); @@ -1211,10 +1086,9 @@ static int SearchIPLDAP(edui_ldap_t *l) { ber_len_t i, x; - ber_len_t j, k; + ber_len_t j; ber_len_t y, z; - int c; - char bufa[EDUI_MAXLEN], bufb[EDUI_MAXLEN], hexc[4]; + char bufa[EDUI_MAXLEN], bufb[EDUI_MAXLEN]; LDAPMessage *ent; if (l == NULL) return LDAP_ERR_NULL; if (l->lp == NULL) return LDAP_ERR_POINTER; @@ -1272,17 +1146,9 @@ SearchIPLDAP(edui_ldap_t *l) /* bufa is the address, just compare it */ if (!(l->status & LDAP_IPV4_S) || (l->status & LDAP_IPV6_S)) break; /* Not looking for IPv4 */ - for (k = 0; k < z; ++k) { - c = (int) bufa[k]; - if (c < 0) - c = c + 256; - int hlen = snprintf(hexc, sizeof(hexc), "%02X", c); - if (k == 0) - xstrncpy(bufb, hexc, sizeof(bufb)); - else - strncat(bufb, hexc, hlen); - } - y = strlen(bufb); + y = makeHexString(bufb, bufa, z); + if (y < 0) + return LDAP_ERR_OOB; /* Compare value with IP */ if (memcmp(l->search_ip, bufb, y) == 0) { /* We got a match! - Scan 'ber' for 'cn' values */ @@ -1307,17 +1173,9 @@ SearchIPLDAP(edui_ldap_t *l) /* bufa + 2 is the address (skip 2 digit port) */ if (!(l->status & LDAP_IPV4_S) || (l->status & LDAP_IPV6_S)) break; /* Not looking for IPv4 */ - for (k = 2; k < z; ++k) { - c = (int) bufa[k]; - if (c < 0) - c = c + 256; - int hlen = snprintf(hexc, sizeof(hexc), "%02X", c); - if (k == 2) - xstrncpy(bufb, hexc, sizeof(bufb)); - else - strncat(bufb, hexc, hlen); - } - y = strlen(bufb); + y = makeHexString(bufb, &bufa[2], z); + if (y < 0) + return LDAP_ERR_OOB; /* Compare value with IP */ if (memcmp(l->search_ip, bufb, y) == 0) { /* We got a match! - Scan 'ber' for 'cn' values */ @@ -1342,17 +1200,9 @@ SearchIPLDAP(edui_ldap_t *l) /* bufa + 2 is the address (skip 2 digit port) */ if (!(l->status & LDAP_IPV6_S)) break; /* Not looking for IPv6 */ - for (k = 2; k < z; ++k) { - c = (int) bufa[k]; - if (c < 0) - c = c + 256; - int hlen = snprintf(hexc, sizeof(hexc), "%02X", c); - if (k == 2) - xstrncpy(bufb, hexc, sizeof(bufb)); - else - strncat(bufb, hexc, hlen); - } - y = strlen(bufb); + y = makeHexString(bufb, &bufa[2], z); + if (y < 0) + return LDAP_ERR_OOB; /* Compare value with IP */ if (memcmp(l->search_ip, bufb, y) == 0) { /* We got a match! - Scan 'ber' for 'cn' values */
_______________________________________________ squid-dev mailing list squid-dev@lists.squid-cache.org http://lists.squid-cache.org/listinfo/squid-dev