Author: pfg
Date: Mon Dec  8 15:10:48 2014
New Revision: 275612
URL: https://svnweb.freebsd.org/changeset/base/275612

Log:
  patch(1): avoid line number overflows
  
  Introduce strtolinenum to properly check line numbers while parsing:
  no signs, no spaces, just digits, 0 <= x <= LONG_MAX
  
  Properly validate line ranges supplied in diff file to prevent overflows.
  Also fixes an out of boundary memory access because the resulting values
  are used as array indices.
  
  PR:   195436
  Obtained from:        OpenBSD (CVS pch.c rev 1.45, 1,46, common.h rev 1.28)
  MFC after:    1 week

Modified:
  head/usr.bin/patch/common.h
  head/usr.bin/patch/pch.c

Modified: head/usr.bin/patch/common.h
==============================================================================
--- head/usr.bin/patch/common.h Mon Dec  8 13:19:29 2014        (r275611)
+++ head/usr.bin/patch/common.h Mon Dec  8 15:10:48 2014        (r275612)
@@ -40,6 +40,7 @@
 #define        INITHUNKMAX 125         /* initial dynamic allocation size */
 #define        INITLINELEN 4096
 #define        BUFFERSIZE 4096
+#define        LINENUM_MAX LONG_MAX
 
 #define        SCCSPREFIX "s."
 #define        GET "get -e %s"

Modified: head/usr.bin/patch/pch.c
==============================================================================
--- head/usr.bin/patch/pch.c    Mon Dec  8 13:19:29 2014        (r275611)
+++ head/usr.bin/patch/pch.c    Mon Dec  8 15:10:48 2014        (r275612)
@@ -78,6 +78,7 @@ static size_t pgets(bool _do_indent);
 static char    *best_name(const struct file_name *, bool);
 static char    *posix_name(const struct file_name *, bool);
 static size_t  num_components(const char *);
+static LINENUM strtolinenum(char *, char **);
 
 /*
  * Prepare to look for the next patch in the patch file.
@@ -354,7 +355,7 @@ intuit_diff_type(void)
                stars_this_line = strnEQ(s, "********", 8);
                if ((!diff_type || diff_type == CONTEXT_DIFF) && 
stars_last_line &&
                    strnEQ(s, "*** ", 4)) {
-                       if (atol(s + 4) == 0)
+                       if (strtolinenum(s + 4, &s) == 0)
                                ok_to_create_file = true;
                        /*
                         * If this is a new context diff the character just
@@ -593,22 +594,25 @@ another_hunk(void)
                                        malformed();
                                if (strnEQ(s, "0,0", 3))
                                        memmove(s, s + 2, strlen(s + 2) + 1);
-                               p_first = (LINENUM) atol(s);
-                               while (isdigit((unsigned char)*s))
-                                       s++;
+                               p_first = strtolinenum(s, &s);
                                if (*s == ',') {
                                        for (;
                                             *s && !isdigit((unsigned char)*s); 
s++)
                                                ;
                                        if (!*s)
                                                malformed();
-                                       p_ptrn_lines = ((LINENUM) atol(s)) - 
p_first + 1;
+                                       p_ptrn_lines = strtolinenum(s, &s) - 
p_first + 1;
+                                       if (p_ptrn_lines < 0)
+                                               malformed();
                                } else if (p_first)
                                        p_ptrn_lines = 1;
                                else {
                                        p_ptrn_lines = 0;
                                        p_first = 1;
                                }
+                               if (p_first >= LINENUM_MAX - p_ptrn_lines ||
+                                   p_ptrn_lines >= LINENUM_MAX - 6)
+                                       malformed();
 
                                /* we need this much at least */
                                p_max = p_ptrn_lines + 6;
@@ -661,22 +665,25 @@ another_hunk(void)
                                                ;
                                        if (!*s)
                                                malformed();
-                                       p_newfirst = (LINENUM) atol(s);
-                                       while (isdigit((unsigned char)*s))
-                                               s++;
+                                       p_newfirst = strtolinenum(s, &s);
                                        if (*s == ',') {
                                                for (; *s && !isdigit((unsigned 
char)*s); s++)
                                                        ;
                                                if (!*s)
                                                        malformed();
-                                               p_repl_lines = ((LINENUM) 
atol(s)) -
+                                               p_repl_lines = strtolinenum(s, 
&s) -
                                                    p_newfirst + 1;
+                                               if (p_repl_lines < 0)
+                                                       malformed();
                                        } else if (p_newfirst)
                                                p_repl_lines = 1;
                                        else {
                                                p_repl_lines = 0;
                                                p_newfirst = 1;
                                        }
+                                       if (p_newfirst >= LINENUM_MAX - 
p_repl_lines ||
+                                           p_repl_lines >= LINENUM_MAX - p_end)
+                                               malformed();
                                        p_max = p_repl_lines + p_end;
                                        if (p_max > MAXHUNKSIZE)
                                                fatal("hunk too large (%ld 
lines) at line %ld: %s",
@@ -869,32 +876,28 @@ hunk_done:
                s = buf + 4;
                if (!*s)
                        malformed();
-               p_first = (LINENUM) atol(s);
-               while (isdigit((unsigned char)*s))
-                       s++;
+               p_first = strtolinenum(s, &s);
                if (*s == ',') {
-                       p_ptrn_lines = (LINENUM) atol(++s);
-                       while (isdigit((unsigned char)*s))
-                               s++;
+                       p_ptrn_lines = strtolinenum(s + 1, &s);
                } else
                        p_ptrn_lines = 1;
                if (*s == ' ')
                        s++;
                if (*s != '+' || !*++s)
                        malformed();
-               p_newfirst = (LINENUM) atol(s);
-               while (isdigit((unsigned char)*s))
-                       s++;
+               p_newfirst = strtolinenum(s, &s);
                if (*s == ',') {
-                       p_repl_lines = (LINENUM) atol(++s);
-                       while (isdigit((unsigned char)*s))
-                               s++;
+                       p_repl_lines = strtolinenum(s + 1, &s);
                } else
                        p_repl_lines = 1;
                if (*s == ' ')
                        s++;
                if (*s != '@')
                        malformed();
+               if (p_first >= LINENUM_MAX - p_ptrn_lines ||
+                   p_newfirst > LINENUM_MAX - p_repl_lines ||
+                   p_ptrn_lines >= LINENUM_MAX - p_repl_lines - 1)
+                       malformed();
                if (!p_ptrn_lines)
                        p_first++;      /* do append rather than insert */
                p_max = p_ptrn_lines + p_repl_lines + 1;
@@ -1034,35 +1037,36 @@ hunk_done:
                        next_intuit_at(line_beginning, p_input_line);
                        return false;
                }
-               p_first = (LINENUM) atol(buf);
-               for (s = buf; isdigit((unsigned char)*s); s++)
-                       ;
+               p_first = strtolinenum(buf, &s);
                if (*s == ',') {
-                       p_ptrn_lines = (LINENUM) atol(++s) - p_first + 1;
-                       while (isdigit((unsigned char)*s))
-                               s++;
+                       p_ptrn_lines = strtolinenum(s + 1, &s) - p_first + 1;
+                       if (p_ptrn_lines < 0)
+                               malformed();
                } else
                        p_ptrn_lines = (*s != 'a');
                hunk_type = *s;
                if (hunk_type == 'a')
                        p_first++;      /* do append rather than insert */
-               min = (LINENUM) atol(++s);
-               for (; isdigit((unsigned char)*s); s++)
-                       ;
+               min = strtolinenum(s + 1, &s);
                if (*s == ',')
-                       max = (LINENUM) atol(++s);
+                       max = strtolinenum(s + 1, &s);
                else
                        max = min;
+               if (min < 0 || min > max || max - min == LINENUM_MAX)
+                       malformed();
                if (hunk_type == 'd')
                        min++;
-               p_end = p_ptrn_lines + 1 + max - min + 1;
+               p_newfirst = min;
+               p_repl_lines = max - min + 1;
+               if (p_newfirst > LINENUM_MAX - p_repl_lines ||
+                   p_ptrn_lines >= LINENUM_MAX - p_repl_lines - 1)
+                       malformed();
+               p_end = p_ptrn_lines + p_repl_lines + 1;
                if (p_end > MAXHUNKSIZE)
                        fatal("hunk too large (%ld lines) at line %ld: %s",
                            p_end, p_input_line, buf);
                while (p_end >= hunkmax)
                        grow_hunkmax();
-               p_newfirst = min;
-               p_repl_lines = max - min + 1;
                snprintf(buf, buf_size, "*** %ld,%ld\n", p_first,
                    p_first + p_ptrn_lines - 1);
                p_line[0] = savestr(buf);
@@ -1582,3 +1586,36 @@ num_components(const char *path)
        }
        return n;
 }
+
+/*
+ * Convert number at NPTR into LINENUM and save address of first
+ * character that is not a digit in ENDPTR.  If conversion is not
+ * possible, call fatal.
+ */
+static LINENUM
+strtolinenum(char *nptr, char **endptr)
+{
+       LINENUM rv;
+       char c;
+       char *p;
+       const char *errstr;
+
+       for (p = nptr; isdigit((unsigned char)*p); p++)
+               ;
+
+       if (p == nptr)
+               malformed();
+
+       c = *p;
+       *p = '\0';
+
+       rv = strtonum(nptr, 0, LINENUM_MAX, &errstr);
+       if (errstr != NULL)
+               fatal("invalid line number at line %ld: `%s' is %s\n",
+                   p_input_line, nptr, errstr);
+ 
+       *p = c;
+       *endptr = p;
+
+       return rv;
+}
_______________________________________________
[email protected] mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "[email protected]"

Reply via email to