Hi,

the numbers in patch files are parsed with atol(), which is not properly
checked for all cases:

- atol() accepts leading spaces, whereas the surrounding code will
  iterate through digits to skip that number... If there is a plus or
  minus sign in front, this for-loop would fail, too.
- Ranges are not properly checked, allowing negative values
  and overflowing numbers.

I have introduced the method "strtolinenum", because the internal format
of line numbers is LINENUM (typedef of long).  It behaves like strtol
but uses our strtonum internally.  In case of an unparsable file, it
bails out with a fatal.  Either it writes the offending number on
terminal or uses the generic "malformed" function to bail out.

If you want to play around with the number parsing, take this example:

$ echo a > a
$ echo b > b
$ diff -u a b > a.diff
$ cat a.diff
--- a   Tue Nov 18 19:57:47 2014
+++ b   Tue Nov 18 19:57:49 2014
@@ -1 +1 @@
-a
+b

You can adjust the @@ numbers.  Remove one, make it too large, etc.


Tobias

Index: common.h
===================================================================
RCS file: /cvs/src/usr.bin/patch/common.h,v
retrieving revision 1.26
diff -u -p -r1.26 common.h
--- common.h    11 Mar 2006 19:41:30 -0000      1.26
+++ common.h    18 Nov 2014 18:54:42 -0000
@@ -28,6 +28,7 @@
 
 #include <sys/types.h>
 
+#include <limits.h>
 #include <stdbool.h>
 
 #define DEBUGGING
@@ -38,6 +39,7 @@
 #define INITHUNKMAX 125                /* initial dynamic allocation size */
 #define MAXLINELEN 8192
 #define BUFFERSIZE 1024
+#define LINENUM_MAX LONG_MAX
 
 #define SCCSPREFIX "s."
 #define GET "get -e %s"
Index: pch.c
===================================================================
RCS file: /cvs/src/usr.bin/patch/pch.c,v
retrieving revision 1.43
diff -u -p -r1.43 pch.c
--- pch.c       18 Nov 2014 17:03:35 -0000      1.43
+++ pch.c       18 Nov 2014 18:54:43 -0000
@@ -76,6 +76,7 @@ static char   *pgets(char *, int, FILE *);
 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.
@@ -340,7 +341,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
@@ -577,15 +578,13 @@ 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;
                                } else if (p_first)
                                        p_ptrn_lines = 1;
                                else {
@@ -645,15 +644,13 @@ 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;
                                        } else if (p_newfirst)
                                                p_repl_lines = 1;
@@ -853,26 +850,18 @@ 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 == ' ')
@@ -1018,23 +1007,17 @@ 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;
                } 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 (hunk_type == 'd')
@@ -1546,4 +1529,37 @@ num_components(const char *path)
                        cp++;           /* skip consecutive slashes */
        }
        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;
 }

Reply via email to