blob: 001d9e906e0cc1503fbe764e3750b78bd2efddbc [file] [log] [blame]
Brad Bishop1a4b7ee2018-12-16 17:11:34 -08001From 01a44c96dbd04936e9cb2501745a834a0b09d504 Mon Sep 17 00:00:00 2001
2From: Amos Jeffries <yadij@users.noreply.github.com>
3Date: Sun, 13 May 2018 06:57:41 +0000
4Subject: [PATCH] Bug 4843 pt1: ext_edirectory_userip_acl refactoring for GCC-8
5 (#204)
6
7Proposed changes to this helper to fix strcat / strncat buffer
8overread / overflow issues.
9
10The approach takes three parts:
11
12* adds a makeHexString function to replace many for-loops
13 catenating bits of strings together with hex conversion into a
14 second buffer. Replacing with a snprintf() and buffer overflow
15 handling.
16
17* a copy of Ip::Address::lookupHostIp to convert the input
18 string into IP address binary format, then generate the hex
19 string using the above new hex function instead of looped
20 sub-string concatenations across several buffers.
21 This removes all the "00" and "0000" strncat() calls and
22 allows far simpler code even with added buffer overflow
23 handling.
24
25* replace multiple string part concatenations with a few simpler
26 calls to snprintf() for all the search_ip buffer constructions.
27 Adding buffer overflow handling as needed for the new calls.
28---
29Signed-off-by: Khem Raj <raj.khem@gmail.com>
30Upstream-Status: Backport
31
32 .../ext_edirectory_userip_acl.cc | 376 ++++++------------
33 1 file changed, 120 insertions(+), 256 deletions(-)
34
35diff --git a/helpers/external_acl/eDirectory_userip/ext_edirectory_userip_acl.cc b/helpers/external_acl/eDirectory_userip/ext_edirectory_userip_acl.cc
36index 63609e4..ad16bfd 100644
37--- a/helpers/external_acl/eDirectory_userip/ext_edirectory_userip_acl.cc
38+++ b/helpers/external_acl/eDirectory_userip/ext_edirectory_userip_acl.cc
39@@ -67,6 +67,9 @@
40 #ifdef HAVE_LDAP_H
41 #include <ldap.h>
42 #endif
43+#ifdef HAVE_NETDB_H
44+#include <netdb.h>
45+#endif
46
47 #ifdef HELPER_INPUT_BUFFER
48 #define EDUI_MAXLEN HELPER_INPUT_BUFFER
49@@ -714,11 +717,14 @@ BindLDAP(edui_ldap_t *l, char *dn, char *pw, unsigned int t)
50
51 /* Copy details - dn and pw CAN be NULL for anonymous and/or TLS */
52 if (dn != NULL) {
53+ if (strlen(dn) >= sizeof(l->dn))
54+ return LDAP_ERR_OOB; /* DN too large */
55+
56 if ((l->basedn[0] != '\0') && (strstr(dn, l->basedn) == NULL)) {
57 /* We got a basedn, but it's not part of dn */
58- xstrncpy(l->dn, dn, sizeof(l->dn));
59- strncat(l->dn, ",", 1);
60- strncat(l->dn, l->basedn, strlen(l->basedn));
61+ const int x = snprintf(l->dn, sizeof(l->dn)-1, "%s,%s", dn, l->basedn);
62+ if (x < 0 || static_cast<size_t>(x) >= sizeof(l->dn))
63+ return LDAP_ERR_OOB; /* DN too large */
64 } else
65 xstrncpy(l->dn, dn, sizeof(l->dn));
66 }
67@@ -778,24 +784,73 @@ BindLDAP(edui_ldap_t *l, char *dn, char *pw, unsigned int t)
68 }
69 }
70
71+// XXX: duplicate (partial) of Ip::Address::lookupHostIp
72+/**
73+ * Convert the IP address string representation in src to
74+ * its binary representation.
75+ *
76+ * \return binary representation of the src IP address.
77+ * Must be free'd using freeaddrinfo().
78+ */
79+static struct addrinfo *
80+makeIpBinary(const char *src)
81+{
82+ struct addrinfo want;
83+ memset(&want, 0, sizeof(want));
84+ want.ai_flags = AI_NUMERICHOST; // prevent actual DNS lookups!
85+
86+ struct addrinfo *dst = nullptr;
87+ if (getaddrinfo(src, nullptr, &want, &dst) != 0) {
88+ // not an IP address
89+ /* free any memory getaddrinfo() dynamically allocated. */
90+ if (dst)
91+ freeaddrinfo(dst);
92+ return nullptr;
93+ }
94+
95+ return dst;
96+}
97+
98+/**
99+ * Convert srcLen bytes from src into HEX and store into dst, which
100+ * has a maximum content size of dstSize including c-string terminator.
101+ * The dst value produced will be a 0-terminated c-string.
102+ *
103+ * \retval N length of dst written (excluding c-string terminator)
104+ * \retval -11 (LDAP_ERR_OOB) buffer overflow detected
105+ */
106+static int
107+makeHexString(char *dst, const int dstSize, const char *src, const int srcLen)
108+{
109+ // HEX encoding doubles the amount of bytes/octets copied
110+ if ((srcLen*2) >= dstSize)
111+ return LDAP_ERR_OOB; // cannot copy that many
112+
113+ *dst = 0;
114+
115+ for (int k = 0; k < srcLen; ++k) {
116+ int c = static_cast<int>(src[k]);
117+ if (c < 0)
118+ c = c + 256;
119+ char hexc[4];
120+ const int hlen = snprintf(hexc, sizeof(hexc), "%02X", c);
121+ if (hlen < 0 || static_cast<size_t>(hlen) > sizeof(hexc)) // should be impossible
122+ return LDAP_ERR_OOB;
123+ strcat(dst, hexc);
124+ }
125+ return strlen(dst);
126+}
127+
128 /*
129 * ConvertIP() - <edui_ldap_t> <ip>
130 *
131 * Take an IPv4 address in dot-decimal or IPv6 notation, and convert to 2-digit HEX stored in l->search_ip
132 * This is the networkAddress that we search LDAP for.
133- *
134- * PENDING -- CHANGE OVER TO inet*_pton, but inet6_pton does not provide the correct syntax
135- *
136 */
137 static int
138 ConvertIP(edui_ldap_t *l, char *ip)
139 {
140- char bufa[EDUI_MAXLEN], bufb[EDUI_MAXLEN], obj[EDUI_MAXLEN];
141- char hexc[4], *p;
142 void *y, *z;
143- size_t s;
144- long x;
145- int i, j, t, swi; /* IPv6 "::" cut over toggle */
146 if (l == NULL) return LDAP_ERR_NULL;
147 if (ip == NULL) return LDAP_ERR_PARAM;
148 if (!(l->status & LDAP_INIT_S)) return LDAP_ERR_INIT; /* Not initalized */
149@@ -831,183 +886,22 @@ ConvertIP(edui_ldap_t *l, char *ip)
150 l->status |= (LDAP_IPV4_S);
151 z = NULL;
152 }
153- s = strlen(ip);
154- *(bufa) = '\0';
155- *(bufb) = '\0';
156- *(obj) = '\0';
157- /* StringSplit() will zero out bufa & obj at each call */
158- memset(l->search_ip, '\0', sizeof(l->search_ip));
159- xstrncpy(bufa, ip, sizeof(bufa)); /* To avoid segfaults, use bufa instead of ip */
160- swi = 0;
161- if (l->status & LDAP_IPV6_S) {
162- /* Search for :: in string */
163- if ((bufa[0] == ':') && (bufa[1] == ':')) {
164- /* bufa starts with a ::, so just copy and clear */
165- xstrncpy(bufb, bufa, sizeof(bufb));
166- *(bufa) = '\0';
167- ++swi; /* Indicates that there is a bufb */
168- } else if ((bufa[0] == ':') && (bufa[1] != ':')) {
169- /* bufa starts with a :, a typo so just fill in a ':', cat and clear */
170- bufb[0] = ':';
171- strncat(bufb, bufa, strlen(bufa));
172- *(bufa) = '\0';
173- ++swi; /* Indicates that there is a bufb */
174- } else {
175- p = strstr(bufa, "::");
176- if (p != NULL) {
177- /* Found it, break bufa down and split into bufb here */
178- *(bufb) = '\0';
179- i = strlen(p);
180- memcpy(bufb, p, i);
181- *p = '\0';
182- bufb[i] = '\0';
183- ++swi; /* Indicates that there is a bufb */
184- }
185- }
186- }
187- s = strlen(bufa);
188- if (s < 1)
189- s = strlen(bufb);
190- while (s > 0) {
191- if ((l->status & LDAP_IPV4_S) && (swi == 0)) {
192- /* Break down IPv4 address */
193- t = StringSplit(bufa, '.', obj, sizeof(obj));
194- if (t > 0) {
195- errno = 0;
196- x = strtol(obj, (char **)NULL, 10);
197- if (((x < 0) || (x > 255)) || ((errno != 0) && (x == 0)) || ((obj[0] != '0') && (x == 0)))
198- return LDAP_ERR_OOB; /* Out of bounds -- Invalid address */
199- memset(hexc, '\0', sizeof(hexc));
200- int hlen = snprintf(hexc, sizeof(hexc), "%02X", (int)x);
201- strncat(l->search_ip, hexc, hlen);
202- } else
203- break; /* reached end of octet */
204- } else if (l->status & LDAP_IPV6_S) {
205- /* Break down IPv6 address */
206- if (swi > 1)
207- t = StringSplit(bufb, ':', obj, sizeof(obj)); /* After "::" */
208- else
209- t = StringSplit(bufa, ':', obj, sizeof(obj)); /* Before "::" */
210- /* Convert octet by size (t) - and fill 0's */
211- switch (t) { /* IPv6 is already in HEX, copy contents */
212- case 4:
213- hexc[0] = (char) toupper((int)obj[0]);
214- i = (int)hexc[0];
215- if (!isxdigit(i))
216- return LDAP_ERR_OOB; /* Out of bounds */
217- hexc[1] = (char) toupper((int)obj[1]);
218- i = (int)hexc[1];
219- if (!isxdigit(i))
220- return LDAP_ERR_OOB; /* Out of bounds */
221- hexc[2] = '\0';
222- strncat(l->search_ip, hexc, 2);
223- hexc[0] = (char) toupper((int)obj[2]);
224- i = (int)hexc[0];
225- if (!isxdigit(i))
226- return LDAP_ERR_OOB; /* Out of bounds */
227- hexc[1] = (char) toupper((int)obj[3]);
228- i = (int)hexc[1];
229- if (!isxdigit(i))
230- return LDAP_ERR_OOB; /* Out of bounds */
231- hexc[2] = '\0';
232- strncat(l->search_ip, hexc, 2);
233- break;
234- case 3:
235- hexc[0] = '0';
236- hexc[1] = (char) toupper((int)obj[0]);
237- i = (int)hexc[1];
238- if (!isxdigit(i))
239- return LDAP_ERR_OOB; /* Out of bounds */
240- hexc[2] = '\0';
241- strncat(l->search_ip, hexc, 2);
242- hexc[0] = (char) toupper((int)obj[1]);
243- i = (int)hexc[0];
244- if (!isxdigit(i))
245- return LDAP_ERR_OOB; /* Out of bounds */
246- hexc[1] = (char) toupper((int)obj[2]);
247- i = (int)hexc[1];
248- if (!isxdigit(i))
249- return LDAP_ERR_OOB; /* Out of bounds */
250- hexc[2] = '\0';
251- strncat(l->search_ip, hexc, 2);
252- break;
253- case 2:
254- strncat(l->search_ip, "00", 2);
255- hexc[0] = (char) toupper((int)obj[0]);
256- i = (int)hexc[0];
257- if (!isxdigit(i))
258- return LDAP_ERR_OOB; /* Out of bounds */
259- hexc[1] = (char) toupper((int)obj[1]);
260- i = (int)hexc[1];
261- if (!isxdigit(i))
262- return LDAP_ERR_OOB; /* Out of bounds */
263- hexc[2] = '\0';
264- strncat(l->search_ip, hexc, 2);
265- break;
266- case 1:
267- strncat(l->search_ip, "00", 2);
268- hexc[0] = '0';
269- hexc[1] = (char) toupper((int)obj[0]);
270- i = (int)hexc[1];
271- if (!isxdigit(i))
272- return LDAP_ERR_OOB; /* Out of bounds */
273- hexc[2] = '\0';
274- strncat(l->search_ip, hexc, 2);
275- break;
276- default:
277- if (t > 4)
278- return LDAP_ERR_OOB;
279- break;
280- }
281- /* Code to pad the address with 0's between a '::' */
282- if ((strlen(bufa) == 0) && (swi == 1)) {
283- /* We are *AT* the split, pad in some 0000 */
284- t = strlen(bufb);
285- /* How many ':' exist in bufb ? */
286- j = 0;
287- for (i = 0; i < t; ++i) {
288- if (bufb[i] == ':')
289- ++j;
290- }
291- --j; /* Preceding "::" doesn't count */
292- t = 8 - (strlen(l->search_ip) / 4) - j; /* Remainder */
293- if (t > 0) {
294- for (i = 0; i < t; ++i)
295- strncat(l->search_ip, "0000", 4);
296- }
297- }
298- }
299- if ((bufa[0] == '\0') && (swi > 0)) {
300- s = strlen(bufb);
301- ++swi;
302- } else
303- s = strlen(bufa);
304- }
305- s = strlen(l->search_ip);
306
307- /* CHECK sizes of address, truncate or pad */
308- /* if "::" is at end of ip, then pad another block or two */
309- while ((l->status & LDAP_IPV6_S) && (s < 32)) {
310- strncat(l->search_ip, "0000", 4);
311- s = strlen(l->search_ip);
312- }
313- if ((l->status & LDAP_IPV6_S) && (s > 32)) {
314- /* Too long, truncate */
315- l->search_ip[32] = '\0';
316- s = strlen(l->search_ip);
317- }
318- /* If at end of ip, and its not long enough, then pad another block or two */
319- while ((l->status & LDAP_IPV4_S) && (s < 8)) {
320- strncat(l->search_ip, "00", 2);
321- s = strlen(l->search_ip);
322- }
323- if ((l->status & LDAP_IPV4_S) && (s > 8)) {
324- /* Too long, truncate */
325- l->search_ip[8] = '\0';
326- s = strlen(l->search_ip);
327+ size_t s = LDAP_ERR_INVALID;
328+ if (struct addrinfo *dst = makeIpBinary(ip)) {
329+ if (dst->ai_family == AF_INET6) {
330+ struct sockaddr_in6 *sia = reinterpret_cast<struct sockaddr_in6 *>(dst->ai_addr);
331+ const char *ia = reinterpret_cast<const char *>(sia->sin6_addr.s6_addr);
332+ s = makeHexString(l->search_ip, sizeof(l->search_ip), ia, 16); // IPv6 = 16-byte address
333+
334+ } else if (dst->ai_family == AF_INET) {
335+ struct sockaddr_in *sia = reinterpret_cast<struct sockaddr_in *>(dst->ai_addr);
336+ const char *ia = reinterpret_cast<const char *>(&(sia->sin_addr));
337+ s = makeHexString(l->search_ip, sizeof(l->search_ip), ia, 4); // IPv4 = 4-byte address
338+ } // else leave s with LDAP_ERR_INVALID value
339+ freeaddrinfo(dst);
340 }
341
342- /* Completed, s is length of address in HEX */
343 return s;
344 }
345
346@@ -1099,48 +993,42 @@ SearchFilterLDAP(edui_ldap_t *l, char *group)
347 }
348 if (group == NULL) {
349 /* No groupMembership= to add, yay! */
350- xstrncpy(bufa, "(&", sizeof(bufa));
351- strncat(bufa, edui_conf.search_filter, strlen(edui_conf.search_filter));
352 /* networkAddress */
353- snprintf(bufb, sizeof(bufb), "(|(networkAddress=1\\23%s)", bufc);
354 if (l->status & LDAP_IPV4_S) {
355- int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=8\\23\\00\\00%s)(networkAddress=9\\23\\00\\00%s))", \
356- bufc, bufc);
357- strncat(bufb, bufd, ln);
358+ const int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=8\\23\\00\\00%s)(networkAddress=9\\23\\00\\00%s)", bufc, bufc);
359+ if (ln < 0 || static_cast<size_t>(ln) >= sizeof(bufd))
360+ return LDAP_ERR_OOB;
361+
362 } else if (l->status & LDAP_IPV6_S) {
363- int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=10\\23\\00\\00%s)(networkAddress=11\\23\\00\\00%s))", \
364- bufc, bufc);
365- strncat(bufb, bufd, ln);
366- } else
367- strncat(bufb, ")", 1);
368- strncat(bufa, bufb, strlen(bufb));
369- strncat(bufa, ")", 1);
370+ const int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=10\\23\\00\\00%s)(networkAddress=11\\23\\00\\00%s)", bufc, bufc);
371+ if (ln < 0 || static_cast<size_t>(ln) >= sizeof(bufd))
372+ return LDAP_ERR_OOB;
373+ }
374+ const int x = snprintf(bufa, sizeof(bufa), "(&%s(|(networkAddress=1\\23%s)%s))", edui_conf.search_filter, bufc, bufd);
375+ if (x < 0 || static_cast<size_t>(x) >= sizeof(bufa))
376+ return LDAP_ERR_OOB;
377+
378 } else {
379 /* Needs groupMembership= to add... */
380- xstrncpy(bufa, "(&(&", sizeof(bufa));
381- strncat(bufa, edui_conf.search_filter, strlen(edui_conf.search_filter));
382 /* groupMembership -- NOTE: Squid *MUST* provide "cn=" from squid.conf */
383- snprintf(bufg, sizeof(bufg), "(groupMembership=%s", group);
384 if ((l->basedn[0] != '\0') && (strstr(group, l->basedn) == NULL)) {
385- strncat(bufg, ",", 1);
386- strncat(bufg, l->basedn, strlen(l->basedn));
387+ const int ln = snprintf(bufg, sizeof(bufg), ",%s", l->basedn);
388+ if (ln < 0 || static_cast<size_t>(ln) >= sizeof(bufd))
389+ return LDAP_ERR_OOB;
390 }
391- strncat(bufg, ")", 1);
392- strncat(bufa, bufg, strlen(bufg));
393 /* networkAddress */
394- snprintf(bufb, sizeof(bufb), "(|(networkAddress=1\\23%s)", bufc);
395 if (l->status & LDAP_IPV4_S) {
396- int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=8\\23\\00\\00%s)(networkAddress=9\\23\\00\\00%s))", \
397- bufc, bufc);
398- strncat(bufb, bufd, ln);
399+ const int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=8\\23\\00\\00%s)(networkAddress=9\\23\\00\\00%s)", bufc, bufc);
400+ if (ln < 0 || static_cast<size_t>(ln) >= sizeof(bufd))
401+ return LDAP_ERR_OOB;
402 } else if (l->status & LDAP_IPV6_S) {
403- int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=10\\23\\00\\00%s)(networkAddress=11\\23\\00\\00%s))", \
404- bufc, bufc);
405- strncat(bufb, bufd, ln);
406- } else
407- strncat(bufb, ")", 1);
408- strncat(bufa, bufb, strlen(bufb));
409- strncat(bufa, "))", 2);
410+ const int ln = snprintf(bufd, sizeof(bufd), "(networkAddress=10\\23\\00\\00%s)(networkAddress=11\\23\\00\\00%s)", bufc, bufc);
411+ if (ln < 0 || static_cast<size_t>(ln) >= sizeof(bufd))
412+ return LDAP_ERR_OOB;
413+ }
414+ const int x = snprintf(bufa, sizeof(bufa), "(&(&%s(groupMembership=%s%s)(|(networkAddress=1\\23%s)%s)))", edui_conf.search_filter, group, bufg, bufc, bufd);
415+ if (x < 0 || static_cast<size_t>(x) >= sizeof(bufa))
416+ return LDAP_ERR_OOB;
417 }
418 s = strlen(bufa);
419 xstrncpy(l->search_filter, bufa, sizeof(l->search_filter));
420@@ -1212,10 +1100,10 @@ static int
421 SearchIPLDAP(edui_ldap_t *l)
422 {
423 ber_len_t i, x;
424- ber_len_t j, k;
425- ber_len_t y, z;
426- int c;
427- char bufa[EDUI_MAXLEN], bufb[EDUI_MAXLEN], hexc[4];
428+ ber_len_t j;
429+ ber_len_t z;
430+ char bufa[EDUI_MAXLEN];
431+ char bufb[EDUI_MAXLEN];
432 LDAPMessage *ent;
433 if (l == NULL) return LDAP_ERR_NULL;
434 if (l->lp == NULL) return LDAP_ERR_POINTER;
435@@ -1273,19 +1161,11 @@ SearchIPLDAP(edui_ldap_t *l)
436 /* bufa is the address, just compare it */
437 if (!(l->status & LDAP_IPV4_S) || (l->status & LDAP_IPV6_S))
438 break; /* Not looking for IPv4 */
439- for (k = 0; k < z; ++k) {
440- c = (int) bufa[k];
441- if (c < 0)
442- c = c + 256;
443- int hlen = snprintf(hexc, sizeof(hexc), "%02X", c);
444- if (k == 0)
445- xstrncpy(bufb, hexc, sizeof(bufb));
446- else
447- strncat(bufb, hexc, hlen);
448- }
449- y = strlen(bufb);
450+ const int blen = makeHexString(bufb, sizeof(bufb), bufa, z);
451+ if (blen < 0)
452+ return blen;
453 /* Compare value with IP */
454- if (memcmp(l->search_ip, bufb, y) == 0) {
455+ if (memcmp(l->search_ip, bufb, blen) == 0) {
456 /* We got a match! - Scan 'ber' for 'cn' values */
457 z = ldap_count_values_len(ber);
458 for (j = 0; j < z; ++j) {
459@@ -1308,19 +1188,11 @@ SearchIPLDAP(edui_ldap_t *l)
460 /* bufa + 2 is the address (skip 2 digit port) */
461 if (!(l->status & LDAP_IPV4_S) || (l->status & LDAP_IPV6_S))
462 break; /* Not looking for IPv4 */
463- for (k = 2; k < z; ++k) {
464- c = (int) bufa[k];
465- if (c < 0)
466- c = c + 256;
467- int hlen = snprintf(hexc, sizeof(hexc), "%02X", c);
468- if (k == 2)
469- xstrncpy(bufb, hexc, sizeof(bufb));
470- else
471- strncat(bufb, hexc, hlen);
472- }
473- y = strlen(bufb);
474+ const int blen = makeHexString(bufb, sizeof(bufb), &bufa[2], z);
475+ if (blen < 0)
476+ return blen;
477 /* Compare value with IP */
478- if (memcmp(l->search_ip, bufb, y) == 0) {
479+ if (memcmp(l->search_ip, bufb, blen) == 0) {
480 /* We got a match! - Scan 'ber' for 'cn' values */
481 z = ldap_count_values_len(ber);
482 for (j = 0; j < z; ++j) {
483@@ -1343,19 +1215,11 @@ SearchIPLDAP(edui_ldap_t *l)
484 /* bufa + 2 is the address (skip 2 digit port) */
485 if (!(l->status & LDAP_IPV6_S))
486 break; /* Not looking for IPv6 */
487- for (k = 2; k < z; ++k) {
488- c = (int) bufa[k];
489- if (c < 0)
490- c = c + 256;
491- int hlen = snprintf(hexc, sizeof(hexc), "%02X", c);
492- if (k == 2)
493- xstrncpy(bufb, hexc, sizeof(bufb));
494- else
495- strncat(bufb, hexc, hlen);
496- }
497- y = strlen(bufb);
498+ const int blen = makeHexString(bufb, sizeof(bufb), &bufa[2], z);
499+ if (blen < 0)
500+ return blen;
501 /* Compare value with IP */
502- if (memcmp(l->search_ip, bufb, y) == 0) {
503+ if (memcmp(l->search_ip, bufb, blen) == 0) {
504 /* We got a match! - Scan 'ber' for 'cn' values */
505 z = ldap_count_values_len(ber);
506 for (j = 0; j < z; ++j) {