xsparse cleanup, including integer overflow

* scripts/xsparse.c: Include inttypes.h, for strtoimax.
Don’t include stdint.h, since inttypes.h includes it.
Sort include directives.
Make all extern functions and vars static, except for ‘main’.
(string_to_off): Use strtoimax instead of doing overflow
checking by hand, incorrectly (it relied on undefined behavior).
(string_to_size): New arg MAXSIZE.  All callers changed.
(get_var): Return bool not int.  Fix unlikely integer overflow.
Use strncmp instead of memcmp, to avoid unlikely pointer overflow.
(read_xheader, read_map, main): Avoid unlikely integer overflow.
Check for I/O errors more consistently.
(main): Prefer bool to int, and put vars near use.
This commit is contained in:
Paul Eggert
2024-07-29 20:56:27 -07:00
parent f22b9fe3ce
commit b26e798a0f

View File

@@ -20,16 +20,17 @@
Written by Sergey Poznyakoff */ Written by Sergey Poznyakoff */
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <limits.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
/* Bound on length of the string representing an off_t. /* Bound on length of the string representing an off_t.
See INT_STRLEN_BOUND in intprops.h for explanation */ See INT_STRLEN_BOUND in intprops.h for explanation */
@@ -44,10 +45,10 @@ struct sp_array
off_t numbytes; off_t numbytes;
}; };
char *progname; static char *progname;
int verbose; static bool verbose;
void static void
die (int code, char *fmt, ...) die (int code, char *fmt, ...)
{ {
va_list ap; va_list ap;
@@ -60,7 +61,7 @@ die (int code, char *fmt, ...)
exit (code); exit (code);
} }
void * static void *
emalloc (size_t size) emalloc (size_t size)
{ {
char *p = malloc (size); char *p = malloc (size);
@@ -69,49 +70,33 @@ emalloc (size_t size)
return p; return p;
} }
off_t static off_t
string_to_off (char *p, char **endp) string_to_off (char *p, char **endp)
{ {
off_t v = 0; errno = 0;
intmax_t i = strtoimax (p, endp, 10);
for (; *p; p++) off_t v = i;
{ if (i < 0 || v != i || errno == ERANGE)
int digit = *p - '0'; die (1, "number out of allowed range, near %s", p);
off_t x = v * 10; if (errno || p == *endp)
if (9 < (unsigned) digit) die (1, "number parse error near %s", p);
{
if (endp)
{
*endp = p;
break;
}
die (1, "number parse error near %s", p);
}
else if (x / 10 != v)
die (1, "number out of allowed range, near %s", p);
v = x + digit;
if (v < 0)
die (1, "negative number");
}
if (endp)
*endp = p;
return v; return v;
} }
size_t static size_t
string_to_size (char *p, char **endp) string_to_size (char *p, char **endp, size_t maxsize)
{ {
off_t v = string_to_off (p, endp); off_t v = string_to_off (p, endp);
size_t ret = v; size_t ret = v;
if (ret != v) if (! (ret == v && ret <= maxsize))
die (1, "number too big"); die (1, "number too big");
return ret; return ret;
} }
size_t sparse_map_size; static size_t sparse_map_size;
struct sp_array *sparse_map; static struct sp_array *sparse_map;
void static void
get_line (char *s, int size, FILE *stream) get_line (char *s, int size, FILE *stream)
{ {
char *p = fgets (s, size, stream); char *p = fgets (s, size, stream);
@@ -122,10 +107,10 @@ get_line (char *s, int size, FILE *stream)
len = strlen (p); len = strlen (p);
if (s[len - 1] != '\n') if (s[len - 1] != '\n')
die (1, "buffer overflow"); die (1, "buffer overflow");
s[len - 1] = 0; s[len - 1] = '\0';
} }
int static bool
get_var (FILE *fp, char **name, char **value) get_var (FILE *fp, char **name, char **value)
{ {
static char *buffer; static char *buffer;
@@ -138,12 +123,12 @@ get_var (FILE *fp, char **name, char **value)
size_t len, s; size_t len, s;
if (!fgets (buffer, bufsize, fp)) if (!fgets (buffer, bufsize, fp))
return 0; return false;
len = strlen (buffer); len = strlen (buffer);
if (len == 0) if (len == 0)
return 0; return false;
s = string_to_size (buffer, &p); s = string_to_size (buffer, &p, SIZE_MAX - 1);
if (*p != ' ') if (*p != ' ')
die (1, "malformed header: expected space but found %s", p); die (1, "malformed header: expected space but found %s", p);
if (buffer[len-1] != '\n') if (buffer[len-1] != '\n')
@@ -160,7 +145,7 @@ get_var (FILE *fp, char **name, char **value)
} }
p++; p++;
} }
while (memcmp (p, "GNU.sparse.", 11)); while (strncmp (p, "GNU.sparse.", 11) != 0);
p += 11; p += 11;
q = strchr (p, '='); q = strchr (p, '=');
@@ -170,15 +155,15 @@ get_var (FILE *fp, char **name, char **value)
q[strlen (q) - 1] = 0; q[strlen (q) - 1] = 0;
*name = p; *name = p;
*value = q; *value = q;
return 1; return true;
} }
char *outname; static char *outname;
off_t outsize; static off_t outsize;
unsigned version_major; static unsigned int version_major;
unsigned version_minor; static unsigned int version_minor;
void static void
read_xheader (char *name) read_xheader (char *name)
{ {
char *kw, *val; char *kw, *val;
@@ -205,11 +190,11 @@ read_xheader (char *name)
} }
else if (strcmp (kw, "major") == 0) else if (strcmp (kw, "major") == 0)
{ {
version_major = string_to_size (val, NULL); version_major = string_to_size (val, NULL, SIZE_MAX);
} }
else if (strcmp (kw, "minor") == 0) else if (strcmp (kw, "minor") == 0)
{ {
version_minor = string_to_size (val, NULL); version_minor = string_to_size (val, NULL, SIZE_MAX);
} }
else if (strcmp (kw, "realsize") == 0 else if (strcmp (kw, "realsize") == 0
|| strcmp (kw, "size") == 0) || strcmp (kw, "size") == 0)
@@ -218,7 +203,8 @@ read_xheader (char *name)
} }
else if (strcmp (kw, "numblocks") == 0) else if (strcmp (kw, "numblocks") == 0)
{ {
sparse_map_size = string_to_size (val, NULL); sparse_map_size = string_to_size (val, NULL,
SIZE_MAX / sizeof *sparse_map);
sparse_map = emalloc (sparse_map_size * sizeof *sparse_map); sparse_map = emalloc (sparse_map_size * sizeof *sparse_map);
} }
else if (strcmp (kw, "offset") == 0) else if (strcmp (kw, "offset") == 0)
@@ -258,10 +244,11 @@ read_xheader (char *name)
die (1, "size of the sparse map unknown"); die (1, "size of the sparse map unknown");
if (i != sparse_map_size) if (i != sparse_map_size)
die (1, "not all sparse entries supplied"); die (1, "not all sparse entries supplied");
fclose (fp); if (ferror (fp) || fclose (fp) < 0)
die (1, "read error: %s", name);
} }
void static void
read_map (FILE *ifp) read_map (FILE *ifp)
{ {
size_t i; size_t i;
@@ -271,7 +258,7 @@ read_map (FILE *ifp)
printf ("Reading v.1.0 sparse map\n"); printf ("Reading v.1.0 sparse map\n");
get_line (nbuf, sizeof nbuf, ifp); get_line (nbuf, sizeof nbuf, ifp);
sparse_map_size = string_to_size (nbuf, NULL); sparse_map_size = string_to_size (nbuf, NULL, SIZE_MAX / sizeof *sparse_map);
sparse_map = emalloc (sparse_map_size * sizeof *sparse_map); sparse_map = emalloc (sparse_map_size * sizeof *sparse_map);
for (i = 0; i < sparse_map_size; i++) for (i = 0; i < sparse_map_size; i++)
@@ -282,11 +269,15 @@ read_map (FILE *ifp)
sparse_map[i].numbytes = string_to_off (nbuf, NULL); sparse_map[i].numbytes = string_to_off (nbuf, NULL);
} }
fseeko (ifp, ((ftell (ifp) + BLOCKSIZE - 1) / BLOCKSIZE) * BLOCKSIZE, off_t ifp_offset = ftello (ifp);
SEEK_SET); if (ifp_offset < 0)
die (1, "ftello");
if (ifp_offset % BLOCKSIZE != 0
&& fseeko (ifp, BLOCKSIZE - ifp_offset % BLOCKSIZE, SEEK_CUR) < 0)
die (1, "fseeko");
} }
void static void
expand_sparse (FILE *sfp, int ofd) expand_sparse (FILE *sfp, int ofd)
{ {
size_t i; size_t i;
@@ -327,7 +318,7 @@ expand_sparse (FILE *sfp, int ofd)
free (buffer); free (buffer);
} }
void static void
usage (int code) usage (int code)
{ {
printf ("Usage: %s [OPTIONS] infile [outfile]\n", progname); printf ("Usage: %s [OPTIONS] infile [outfile]\n", progname);
@@ -342,7 +333,7 @@ usage (int code)
exit (code); exit (code);
} }
void static void
guess_outname (char *name) guess_outname (char *name)
{ {
char *p; char *p;
@@ -388,12 +379,8 @@ int
main (int argc, char **argv) main (int argc, char **argv)
{ {
int c; int c;
int dry_run = 0; bool dry_run = false;
char *xheader_file = NULL; char *xheader_file = NULL;
char *inname;
FILE *ifp;
struct stat st;
int ofd;
progname = argv[0]; progname = argv[0];
while ((c = getopt (argc, argv, "hnvx:")) != EOF) while ((c = getopt (argc, argv, "hnvx:")) != EOF)
@@ -409,9 +396,9 @@ main (int argc, char **argv)
break; break;
case 'n': case 'n':
dry_run = 1; dry_run = true;
case 'v': case 'v':
verbose++; verbose = true;
break; break;
default: default:
@@ -428,15 +415,16 @@ main (int argc, char **argv)
if (xheader_file) if (xheader_file)
read_xheader (xheader_file); read_xheader (xheader_file);
inname = argv[0]; char *inname = argv[0];
if (argv[1]) if (argv[1])
outname = argv[1]; outname = argv[1];
if (stat (inname, &st)) struct stat st;
if (stat (inname, &st) < 0)
die (1, "cannot stat %s (%d)", inname, errno); die (1, "cannot stat %s (%d)", inname, errno);
ifp = fopen (inname, "r"); FILE *ifp = fopen (inname, "r");
if (ifp == NULL) if (!ifp)
die (1, "cannot open file %s (%d)", inname, errno); die (1, "cannot open file %s (%d)", inname, errno);
if (!xheader_file || version_major == 1) if (!xheader_file || version_major == 1)
@@ -445,8 +433,8 @@ main (int argc, char **argv)
if (!outname) if (!outname)
guess_outname (inname); guess_outname (inname);
ofd = open (outname, O_RDWR|O_CREAT|O_TRUNC, st.st_mode); int ofd = open (outname, O_RDWR|O_CREAT|O_TRUNC, st.st_mode);
if (ofd == -1) if (ofd < 0)
die (1, "cannot open file %s (%d)", outname, errno); die (1, "cannot open file %s (%d)", outname, errno);
if (verbose) if (verbose)
@@ -460,15 +448,17 @@ main (int argc, char **argv)
expand_sparse (ifp, ofd); expand_sparse (ifp, ofd);
fclose (ifp); if (ferror (ifp) || fclose (ifp) < 0)
close (ofd); die (1, "input error: %s", inname);
if (close (ofd) < 0)
die (1, "output error: %s", outname);
if (verbose) if (verbose)
printf ("Done\n"); printf ("Done\n");
if (outsize) if (outsize)
{ {
if (stat (outname, &st)) if (stat (outname, &st) < 0)
die (1, "cannot stat output file %s (%d)", outname, errno); die (1, "cannot stat output file %s (%d)", outname, errno);
if (st.st_size != outsize) if (st.st_size != outsize)
die (1, "expanded file has wrong size"); die (1, "expanded file has wrong size");