Vulnerability: Perl before 5.26.3 and 5.28.x before 5.28.1 has a buffer overflow via a crafted regular expression that triggers invalid write operations.
Vulnerability ID: CVE-2018-18311
CWE: CWE-787
Severity score: 6.4
Project: https://github.com/Perl/perl5
Published: 2018-12-07
File: util.c
Commit message: Perl_my_setenv(); handle integer wrap

RT #133204

Wean this function off int/I32 and onto UV/Size_t.
Also, replace all malloc-ish calls with a wrapper that does
overflow checks,

In particular, it was doing (nlen + vlen + 2) which could wrap when
the combined length of the environment variable name and value
exceeded around 0x7fffffff.

The wrapper check function is probably overkill, but belt and braces...

NB this function has several variant parts, #ifdef'ed by platform
type; I have blindly changed the parts that aren't compiled under linux.
Commit author: David Mitchell

Vulnerable version (before fix):
 Perl_new_warnings_bitfield(pTHX_ STRLEN *buffer, const char *const bits,    *(s+(nlen+1+vlen)) = '\0'  #ifdef USE_ENVIRON_ARRAY        /* VMS' my_setenv() is in vms.c */ #if !defined(WIN32) && !defined(NETWARE) void Perl_my_setenv(pTHX_ const char *nam, const char *val) { Perl_my_setenv(pTHX_ const char *nam, const char *val) #ifndef PERL_USE_SAFE_PUTENV     if (!PL_use_safe_putenv) {         /* most putenv()s leak, so we manipulate environ directly */         I32 i;         const I32 len = strlen(nam);         int nlen, vlen;          /* where does it go? */         for (i = 0; environ[i]; i++) {             if (strnEQ(environ[i],nam,len) && environ[i][len] == '=')                 break;         }          if (environ == PL_origenviron) {   /* need we copy environment? */             I32 j;             I32 max;             char **tmpenv;              max = i;             while (environ[max])                 max++;             tmpenv = (char**)safesysmalloc((max+2) * sizeof(char*));             for (j=0; j<max; j++) {         /* copy environment */                 const int len = strlen(environ[j]);                 tmpenv[j] = (char*)safesysmalloc((len+1)*sizeof(char));                 Copy(environ[j], tmpenv[j], len+1, char);             }             tmpenv[max] = NULL; Perl_my_setenv(pTHX_ const char *nam, const char *val) #endif         }         if (!environ[i]) {                 /* does not exist yet */             environ = (char**)safesysrealloc(environ, (i+2) * sizeof(char*));             environ[i+1] = NULL;    /* make sure it's null terminated */         }         else             safesysfree(environ[i]);         nlen = strlen(nam);         vlen = strlen(val);          environ[i] = (char*)safesysmalloc((nlen+vlen+2) * sizeof(char));         /* all that work just for this */         my_setenv_format(environ[i], nam, nlen, val, vlen);     } else { Perl_my_setenv(pTHX_ const char *nam, const char *val)             if (environ) /* old glibc can crash with null environ */                 (void)unsetenv(nam);         } else { 	    const int nlen = strlen(nam); 	    const int vlen = strlen(val); 	    char * const new_env =                 (char*)safesysmalloc((nlen + vlen + 2) * sizeof(char));             my_setenv_format(new_env, nam, nlen, val, vlen);             (void)putenv(new_env);         } #   else /* ! HAS_UNSETENV */         char *new_env; 	const int nlen = strlen(nam); 	int vlen;         if (!val) { 	   val = "";         }         vlen = strlen(val);         new_env = (char*)safesysmalloc((nlen + vlen + 2) * sizeof(char));         /* all that work just for this */         my_setenv_format(new_env, nam, nlen, val, vlen);         (void)putenv(new_env); Perl_my_setenv(pTHX_ const char *nam, const char *val) {     dVAR;     char *envstr;     const int nlen = strlen(nam);     int vlen;      if (!val) {        val = "";     }     vlen = strlen(val);     Newx(envstr, nlen+vlen+2, char);     my_setenv_format(envstr, nam, nlen, val, vlen);     (void)PerlEnv_putenv(envstr);     Safefree(envstr);

Fixed version (after fix):
 Perl_new_warnings_bitfield(pTHX_ STRLEN *buffer, const char *const bits,    *(s+(nlen+1+vlen)) = '\0'  #ifdef USE_ENVIRON_ARRAY  /* small wrapper for use by Perl_my_setenv that mallocs, or reallocs if  * 'current' is non-null, with up to three sizes that are added together.  * It handles integer overflow.  */ static char * S_env_alloc(void *current, Size_t l1, Size_t l2, Size_t l3, Size_t size) {     void *p;     Size_t sl, l = l1 + l2;      if (l < l2)         goto panic;     l += l3;     if (l < l3)         goto panic;     sl = l * size;     if (sl < l)         goto panic;      p = current             ? safesysrealloc(current, sl)             : safesysmalloc(sl);     if (p)         return (char*)p;    panic:     croak_memory_wrap(); }   /* VMS' my_setenv() is in vms.c */ #if !defined(WIN32) && !defined(NETWARE)  void Perl_my_setenv(pTHX_ const char *nam, const char *val) { Perl_my_setenv(pTHX_ const char *nam, const char *val) #ifndef PERL_USE_SAFE_PUTENV     if (!PL_use_safe_putenv) {         /* most putenv()s leak, so we manipulate environ directly */         UV i;         Size_t vlen, nlen = strlen(nam);          /* where does it go? */         for (i = 0; environ[i]; i++) {             if (strnEQ(environ[i], nam, nlen) && environ[i][nlen] == '=')                 break;         }          if (environ == PL_origenviron) {   /* need we copy environment? */             UV j, max;             char **tmpenv;              max = i;             while (environ[max])                 max++;             /* XXX shouldn't that be max+1 rather than max+2 ??? - DAPM */             tmpenv = (char**)S_env_alloc(NULL, max, 2, 0, sizeof(char*));             for (j=0; j<max; j++) {         /* copy environment */                 const Size_t len = strlen(environ[j]);                 tmpenv[j] = S_env_alloc(NULL, len, 1, 0, 1);                 Copy(environ[j], tmpenv[j], len+1, char);             }             tmpenv[max] = NULL; Perl_my_setenv(pTHX_ const char *nam, const char *val) #endif         }         if (!environ[i]) {                 /* does not exist yet */             environ = (char**)S_env_alloc(environ, i, 2, 0, sizeof(char*));             environ[i+1] = NULL;    /* make sure it's null terminated */         }         else             safesysfree(environ[i]);          vlen = strlen(val);          environ[i] = S_env_alloc(NULL, nlen, vlen, 2, 1);         /* all that work just for this */         my_setenv_format(environ[i], nam, nlen, val, vlen);     } else { Perl_my_setenv(pTHX_ const char *nam, const char *val)             if (environ) /* old glibc can crash with null environ */                 (void)unsetenv(nam);         } else { 	    const Size_t nlen = strlen(nam); 	    const Size_t vlen = strlen(val); 	    char * const new_env = S_env_alloc(NULL, nlen, vlen, 2, 1);             my_setenv_format(new_env, nam, nlen, val, vlen);             (void)putenv(new_env);         } #   else /* ! HAS_UNSETENV */         char *new_env; 	const Size_t nlen = strlen(nam); 	Size_t vlen;         if (!val) { 	   val = "";         }         vlen = strlen(val);         new_env = S_env_alloc(NULL, nlen, vlen, 2, 1);         /* all that work just for this */         my_setenv_format(new_env, nam, nlen, val, vlen);         (void)putenv(new_env); Perl_my_setenv(pTHX_ const char *nam, const char *val) {     dVAR;     char *envstr;     const Size_t nlen = strlen(nam);     Size_t vlen;      if (!val) {        val = "";     }     vlen = strlen(val);     envstr = S_env_alloc(NULL, nlen, vlen, 2, 1);     my_setenv_format(envstr, nam, nlen, val, vlen);     (void)PerlEnv_putenv(envstr);     Safefree(envstr);

Patch:
patch: @@ -2061,8 +2061,40 @@ Perl_new_warnings_bitfield(pTHX_ STRLEN *buffer, const char *const bits,
    *(s+(nlen+1+vlen)) = '\0'
 
 #ifdef USE_ENVIRON_ARRAY
-       /* VMS' my_setenv() is in vms.c */
+
+/* small wrapper for use by Perl_my_setenv that mallocs, or reallocs if
+ * 'current' is non-null, with up to three sizes that are added together.
+ * It handles integer overflow.
+ */
+static char *
+S_env_alloc(void *current, Size_t l1, Size_t l2, Size_t l3, Size_t size)
+{
+    void *p;
+    Size_t sl, l = l1 + l2;
+
+    if (l < l2)
+        goto panic;
+    l += l3;
+    if (l < l3)
+        goto panic;
+    sl = l * size;
+    if (sl < l)
+        goto panic;
+
+    p = current
+            ? safesysrealloc(current, sl)
+            : safesysmalloc(sl);
+    if (p)
+        return (char*)p;
+
+  panic:
+    croak_memory_wrap();
+}
+
+
+/* VMS' my_setenv() is in vms.c */
 #if !defined(WIN32) && !defined(NETWARE)
+
 void
 Perl_my_setenv(pTHX_ const char *nam, const char *val)
 {
@@ -2078,28 +2110,27 @@ Perl_my_setenv(pTHX_ const char *nam, const char *val)
 #ifndef PERL_USE_SAFE_PUTENV
     if (!PL_use_safe_putenv) {
         /* most putenv()s leak, so we manipulate environ directly */
-        I32 i;
-        const I32 len = strlen(nam);
-        int nlen, vlen;
+        UV i;
+        Size_t vlen, nlen = strlen(nam);
 
         /* where does it go? */
         for (i = 0; environ[i]; i++) {
-            if (strnEQ(environ[i],nam,len) && environ[i][len] == '=')
+            if (strnEQ(environ[i], nam, nlen) && environ[i][nlen] == '=')
                 break;
         }
 
         if (environ == PL_origenviron) {   /* need we copy environment? */
-            I32 j;
-            I32 max;
+            UV j, max;
             char **tmpenv;
 
             max = i;
             while (environ[max])
                 max++;
-            tmpenv = (char**)safesysmalloc((max+2) * sizeof(char*));
+            /* XXX shouldn't that be max+1 rather than max+2 ??? - DAPM */
+            tmpenv = (char**)S_env_alloc(NULL, max, 2, 0, sizeof(char*));
             for (j=0; j<max; j++) {         /* copy environment */
-                const int len = strlen(environ[j]);
-                tmpenv[j] = (char*)safesysmalloc((len+1)*sizeof(char));
+                const Size_t len = strlen(environ[j]);
+                tmpenv[j] = S_env_alloc(NULL, len, 1, 0, 1);
                 Copy(environ[j], tmpenv[j], len+1, char);
             }
             tmpenv[max] = NULL;
@@ -2118,15 +2149,15 @@ Perl_my_setenv(pTHX_ const char *nam, const char *val)
 #endif
         }
         if (!environ[i]) {                 /* does not exist yet */
-            environ = (char**)safesysrealloc(environ, (i+2) * sizeof(char*));
+            environ = (char**)S_env_alloc(environ, i, 2, 0, sizeof(char*));
             environ[i+1] = NULL;    /* make sure it's null terminated */
         }
         else
             safesysfree(environ[i]);
-        nlen = strlen(nam);
+
         vlen = strlen(val);
 
-        environ[i] = (char*)safesysmalloc((nlen+vlen+2) * sizeof(char));
+        environ[i] = S_env_alloc(NULL, nlen, vlen, 2, 1);
         /* all that work just for this */
         my_setenv_format(environ[i], nam, nlen, val, vlen);
     } else {
@@ -2150,22 +2181,21 @@ Perl_my_setenv(pTHX_ const char *nam, const char *val)
             if (environ) /* old glibc can crash with null environ */
                 (void)unsetenv(nam);
         } else {
-	    const int nlen = strlen(nam);
-	    const int vlen = strlen(val);
-	    char * const new_env =
-                (char*)safesysmalloc((nlen + vlen + 2) * sizeof(char));
+	    const Size_t nlen = strlen(nam);
+	    const Size_t vlen = strlen(val);
+	    char * const new_env = S_env_alloc(NULL, nlen, vlen, 2, 1);
             my_setenv_format(new_env, nam, nlen, val, vlen);
             (void)putenv(new_env);
         }
 #   else /* ! HAS_UNSETENV */
         char *new_env;
-	const int nlen = strlen(nam);
-	int vlen;
+	const Size_t nlen = strlen(nam);
+	Size_t vlen;
         if (!val) {
 	   val = "";
         }
         vlen = strlen(val);
-        new_env = (char*)safesysmalloc((nlen + vlen + 2) * sizeof(char));
+        new_env = S_env_alloc(NULL, nlen, vlen, 2, 1);
         /* all that work just for this */
         my_setenv_format(new_env, nam, nlen, val, vlen);
         (void)putenv(new_env);
@@ -2187,14 +2217,14 @@ Perl_my_setenv(pTHX_ const char *nam, const char *val)
 {
     dVAR;
     char *envstr;
-    const int nlen = strlen(nam);
-    int vlen;
+    const Size_t nlen = strlen(nam);
+    Size_t vlen;
 
     if (!val) {
        val = "";
     }
     vlen = strlen(val);
-    Newx(envstr, nlen+vlen+2, char);
+    envstr = S_env_alloc(NULL, nlen, vlen, 2, 1);
     my_setenv_format(envstr, nam, nlen, val, vlen);
     (void)PerlEnv_putenv(envstr);
     Safefree(envstr);
path: util.c