summaryrefslogtreecommitdiffstats
path: root/debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch
diff options
context:
space:
mode:
Diffstat (limited to 'debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch')
-rw-r--r--debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch309
1 files changed, 309 insertions, 0 deletions
diff --git a/debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch b/debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch
new file mode 100644
index 0000000..cbddde0
--- /dev/null
+++ b/debian/patches/76-01-fix-string_is_ip_address-CVE-2023-42117-Bug-3031.patch
@@ -0,0 +1,309 @@
+From a95acb1c19c2e3600ef327c71318e33316d34440 Mon Sep 17 00:00:00 2001
+From: "Heiko Schlittermann (HS12-RIPE)" <hs@schlittermann.de>
+Date: Thu, 5 Oct 2023 22:49:57 +0200
+Subject: [PATCH 1/3] fix: string_is_ip_address (CVE-2023-42117) Bug 3031
+
+---
+ doc/ChangeLog | 206 ++++++++++++++++++++++++++++++++++++++++++
+ src/expand.c | 14 ++-
+ src/functions.h | 1 +
+ src/string.c | 200 +++++++++++++++++++++-------------------
+ 4 files changed, 323 insertions(+), 98 deletions(-)
+
+diff --git a/src/expand.c b/src/expand.c
+index 36c9f423b..4986e4657 100644
+--- a/src/expand.c
++++ b/src/expand.c
+@@ -2646,17 +2646,25 @@ switch(cond_type = identify_operator(&s, &opname))
+ }
+ *yield = (Ustat(sub[0], &statbuf) == 0) == testfor;
+ break;
+
+ case ECOND_ISIP:
+ case ECOND_ISIP4:
+ case ECOND_ISIP6:
+- rc = string_is_ip_address(sub[0], NULL);
+- *yield = ((cond_type == ECOND_ISIP)? (rc != 0) :
+- (cond_type == ECOND_ISIP4)? (rc == 4) : (rc == 6)) == testfor;
++ {
++ const uschar *errp;
++ const uschar **errpp;
++ DEBUG(D_expand) errpp = &errp; else errpp = 0;
++ if (0 == (rc = string_is_ip_addressX(sub[0], NULL, errpp)))
++ DEBUG(D_expand) debug_printf("failed: %s\n", errp);
++
++ *yield = ( cond_type == ECOND_ISIP ? rc != 0 :
++ cond_type == ECOND_ISIP4 ? rc == 4 : rc == 6) == testfor;
++ }
++
+ break;
+
+ /* Various authentication tests - all optionally compiled */
+
+ case ECOND_PAM:
+ #ifdef SUPPORT_PAM
+ rc = auth_call_pam(sub[0], &expand_string_message);
+diff --git a/src/functions.h b/src/functions.h
+index 224666cb1..3c8104d25 100644
+--- a/src/functions.h
++++ b/src/functions.h
+@@ -552,14 +552,15 @@ extern gstring *string_catn(gstring *, const uschar *, int) WARN_UNUSED_RESULT;
+ extern int string_compare_by_pointer(const void *, const void *);
+ extern uschar *string_copy_dnsdomain(uschar *);
+ extern uschar *string_copy_malloc(const uschar *);
+ extern uschar *string_dequote(const uschar **);
+ extern uschar *string_format_size(int, uschar *);
+ extern int string_interpret_escape(const uschar **);
+ extern int string_is_ip_address(const uschar *, int *);
++extern int string_is_ip_addressX(const uschar *, int *, const uschar **);
+ #ifdef SUPPORT_I18N
+ extern BOOL string_is_utf8(const uschar *);
+ #endif
+ extern const uschar *string_printing2(const uschar *, int);
+ extern uschar *string_split_message(uschar *);
+ extern uschar *string_unprinting(uschar *);
+ #ifdef SUPPORT_I18N
+diff --git a/src/string.c b/src/string.c
+index a5161bb31..9aefc2b58 100644
+--- a/src/string.c
++++ b/src/string.c
+@@ -25,131 +25,141 @@ address (assuming HAVE_IPV6 is set). If a mask is permitted and one is present,
+ and maskptr is not NULL, its offset is placed there.
+
+ Arguments:
+ s a string
+ maskptr NULL if no mask is permitted to follow
+ otherwise, points to an int where the offset of '/' is placed
+ if there is no / followed by trailing digits, *maskptr is set 0
++ errp NULL if no diagnostic information is required, and if the netmask
++ length should not be checked. Otherwise it is set pointing to a short
++ descriptive text.
+
+ Returns: 0 if the string is not a textual representation of an IP address
+ 4 if it is an IPv4 address
+ 6 if it is an IPv6 address
+-*/
+
++The legacy string_is_ip_address() function follows below.
++*/
+ int
+-string_is_ip_address(const uschar *s, int *maskptr)
+-{
+-int yield = 4;
++string_is_ip_addressX(const uschar *ip_addr, int *maskptr, const uschar **errp) {
++ struct addrinfo hints;
++ struct addrinfo *res;
+
+-/* If an optional mask is permitted, check for it. If found, pass back the
+-offset. */
++ uschar *slash, *percent;
+
+-if (maskptr)
++ uschar *endp = 0;
++ long int mask = 0;
++ const uschar *addr = 0;
++
++ /* If there is a slash, but we didn't request a (optional) netmask,
++ we return failure, as we do if the mask isn't a pure numerical value,
++ or if it is negative. The actual length is checked later, once we know
++ the address family. */
++ if (slash = Ustrchr(ip_addr, '/'))
+ {
+- const uschar *ss = s + Ustrlen(s);
+- *maskptr = 0;
+- if (s != ss && isdigit(*(--ss)))
++ if (!maskptr)
+ {
+- while (ss > s && isdigit(ss[-1])) ss--;
+- if (ss > s && *(--ss) == '/') *maskptr = ss - s;
++ if (errp) *errp = "netmask found, but not requested";
++ return 0;
+ }
+- }
+-
+-/* A colon anywhere in the string => IPv6 address */
+-
+-if (Ustrchr(s, ':') != NULL)
+- {
+- BOOL had_double_colon = FALSE;
+- BOOL v4end = FALSE;
+-
+- yield = 6;
+-
+- /* An IPv6 address must start with hex digit or double colon. A single
+- colon is invalid. */
+-
+- if (*s == ':' && *(++s) != ':') return 0;
+-
+- /* Now read up to 8 components consisting of up to 4 hex digits each. There
+- may be one and only one appearance of double colon, which implies any number
+- of binary zero bits. The number of preceding components is held in count. */
+
+- for (int count = 0; count < 8; count++)
++ uschar *rest;
++ mask = Ustrtol(slash+1, &rest, 10);
++ if (*rest || mask < 0)
+ {
+- /* If the end of the string is reached before reading 8 components, the
+- address is valid provided a double colon has been read. This also applies
+- if we hit the / that introduces a mask or the % that introduces the
+- interface specifier (scope id) of a link-local address. */
+-
+- if (*s == 0 || *s == '%' || *s == '/') return had_double_colon ? yield : 0;
+-
+- /* If a component starts with an additional colon, we have hit a double
+- colon. This is permitted to appear once only, and counts as at least
+- one component. The final component may be of this form. */
+-
+- if (*s == ':')
+- {
+- if (had_double_colon) return 0;
+- had_double_colon = TRUE;
+- s++;
+- continue;
+- }
+-
+- /* If the remainder of the string contains a dot but no colons, we
+- can expect a trailing IPv4 address. This is valid if either there has
+- been no double-colon and this is the 7th component (with the IPv4 address
+- being the 7th & 8th components), OR if there has been a double-colon
+- and fewer than 6 components. */
+-
+- if (Ustrchr(s, ':') == NULL && Ustrchr(s, '.') != NULL)
+- {
+- if ((!had_double_colon && count != 6) ||
+- (had_double_colon && count > 6)) return 0;
+- v4end = TRUE;
+- yield = 6;
+- break;
+- }
+-
+- /* Check for at least one and not more than 4 hex digits for this
+- component. */
+-
+- if (!isxdigit(*s++)) return 0;
+- if (isxdigit(*s) && isxdigit(*(++s)) && isxdigit(*(++s))) s++;
+-
+- /* If the component is terminated by colon and there is more to
+- follow, skip over the colon. If there is no more to follow the address is
+- invalid. */
+-
+- if (*s == ':' && *(++s) == 0) return 0;
++ if (errp) *errp = "netmask not numeric or <0";
++ return 0;
+ }
+
+- /* If about to handle a trailing IPv4 address, drop through. Otherwise
+- all is well if we are at the end of the string or at the mask or at a percent
+- sign, which introduces the interface specifier (scope id) of a link local
+- address. */
++ *maskptr = slash - ip_addr; /* offset of the slash */
++ endp = slash;
++ } else if (maskptr) *maskptr = 0; /* no slash found */
+
+- if (!v4end)
+- return (*s == 0 || *s == '%' ||
+- (*s == '/' && maskptr != NULL && *maskptr != 0))? yield : 0;
++ /* The interface-ID suffix (%<id>) is optional (for IPv6). If it
++ exists, we check it syntactically. Later, if we know the address
++ family is IPv4, we might reject it.
++ The interface-ID is mutually exclusive with the netmask, to the
++ best of my knowledge. */
++ if (percent = Ustrchr(ip_addr, '%'))
++ {
++ if (slash)
++ {
++ if (errp) *errp = "interface-ID and netmask are mutually exclusive";
++ return 0;
++ }
++ for (uschar *p = percent+1; *p; p++)
++ if (!isalnum(*p) && !ispunct(*p))
++ {
++ if (errp) *errp = "interface-ID must match [[:alnum:][:punct:]]";
++ return 0;
++ }
++ endp = percent;
+ }
+
+-/* Test for IPv4 address, which may be the tail-end of an IPv6 address. */
+-
+-for (int i = 0; i < 4; i++)
++ /* inet_pton() can't parse netmasks and interface IDs, so work on a shortened copy
++ allocated on the current stack */
++ if (endp) {
++ ptrdiff_t l = endp - ip_addr;
++ if (l > 255)
++ {
++ if (errp) *errp = "rudiculous long ip address string";
++ return 0;
++ }
++ addr = alloca(l+1); /* *BSD does not have strndupa() */
++ Ustrncpy((uschar *)addr, ip_addr, l);
++ ((uschar*)addr)[l] = '\0';
++ } else addr = ip_addr;
++
++ int af;
++ union { /* we do not need this, but inet_pton() needs a place for storage */
++ struct in_addr sa4;
++ struct in6_addr sa6;
++ } sa;
++
++ af = Ustrchr(addr, ':') ? AF_INET6 : AF_INET;
++ if (!inet_pton(af, addr, &sa))
+ {
+- long n;
+- uschar * end;
+-
+- if (i != 0 && *s++ != '.') return 0;
+- n = strtol(CCS s, CSS &end, 10);
+- if (n > 255 || n < 0 || end <= s || end > s+3) return 0;
+- s = end;
++ if (errp) *errp = af == AF_INET6 ? "IP address string not parsable as IPv6"
++ : "IP address string not parsable IPv4";
++ return 0;
+ }
++ /* we do not check the values of the mask here, as
++ this is done on the callers side (but I don't understand why), so
++ actually I'd like to do it here, but it breaks at least 0002 */
++ switch (af)
++ {
++ case AF_INET6:
++ if (errp && mask > 128)
++ {
++ *errp = "IPv6 netmask value must not be >128";
++ return 0;
++ }
++ return 6;
++ case AF_INET:
++ if (percent)
++ {
++ if (errp) *errp = "IPv4 address string must not have an interface-ID";
++ return 0;
++ }
++ if (errp && mask > 32) {
++ *errp = "IPv4 netmask value must not be >32";
++ return 0;
++ }
++ return 4;
++ default:
++ if (errp) *errp = "unknown address family (should not happen)";
++ return 0;
++ }
++}
+
+-return !*s || (*s == '/' && maskptr && *maskptr != 0) ? yield : 0;
++int
++string_is_ip_address(const uschar *ip_addr, int *maskptr) {
++ return string_is_ip_addressX(ip_addr, maskptr, 0);
+ }
++
+ #endif /* COMPILE_UTILITY */
+
+
+ /*************************************************
+ * Format message size *
+ *************************************************/
+
+--
+2.42.0
+