From f4ca30a3a901a33ab29d2df80e9bc3649385f699 Mon Sep 17 00:00:00 2001
From: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
Date: Fri, 28 Feb 2020 16:11:19 +0100
Subject: Cleanup pax header parser a little

This commit tries to untangle the logic of parsing and sanitizing the
pax header length field and the associated bounds checks.

Signed-off-by: David Oberhollenzer <david.oberhollenzer@sigma-star.at>
---
 lib/tar/read_header.c | 95 ++++++++++++++++++++++-----------------------------
 1 file changed, 40 insertions(+), 55 deletions(-)

diff --git a/lib/tar/read_header.c b/lib/tar/read_header.c
index f82f9a5..de6b0de 100644
--- a/lib/tar/read_header.c
+++ b/lib/tar/read_header.c
@@ -78,108 +78,93 @@ static tar_xattr_t *mkxattr(const char *key, size_t keylen,
 static int read_pax_header(FILE *fp, sqfs_u64 entsize, unsigned int *set_by_pax,
 			   tar_header_decoded_t *out)
 {
+	char *buffer, *line, *key, *ptr, *value, *end;
 	sparse_map_t *sparse_last = NULL, *sparse;
 	sqfs_u64 field, offset = 0, num_bytes = 0;
-	char *buffer, *line, *key, *ptr, *value;
-	sqfs_u64 i, start, len;
 	tar_xattr_t *xattr;
-	int digit;
+	long len;
 
 	buffer = record_to_memory(fp, entsize);
 	if (buffer == NULL)
 		return -1;
 
-	for (i = 0; i < entsize; i += len) {
-		start = i;
+	end = buffer + entsize;
 
-		if (!isdigit(buffer[i]))
+	for (line = buffer; line < end; line += len) {
+		len = strtol(line, &ptr, 10);
+		if (ptr == line || !isspace(*ptr) || len <= 0)
 			goto fail_malformed;
 
-		for (len = 0; i < entsize && isdigit(buffer[i]); ++i) {
-			digit = buffer[i] - '0';
-
-			if (len > (entsize - digit) / 10)
-				goto fail_ov;
-
-			len = len * 10 + digit;
-		}
-
-		if (!isspace(buffer[i]))
-			goto fail_malformed;
-
-		while (i < entsize && isspace(buffer[i]))
-			++i;
-
-		if (i >= entsize || (i - start) >= len)
+		if (len > (end - line))
 			goto fail_ov;
 
-		len -= i - start;
+		line[len - 1] = '\0';
 
-		if (i + len > entsize)
-			goto fail_ov;
+		while (ptr < end && isspace(*ptr))
+			++ptr;
 
-		line = buffer + i;
-		line[len - 1] = '\0';
+		if (ptr >= end || (ptr - line) >= len)
+			goto fail_malformed;
 
-		if (!strncmp(line, "uid=", 4)) {
-			if (pax_read_decimal(line + 4, &field))
+		if (!strncmp(ptr, "uid=", 4)) {
+			if (pax_read_decimal(ptr + 4, &field))
 				goto fail;
 			out->sb.st_uid = field;
 			*set_by_pax |= PAX_UID;
-		} else if (!strncmp(line, "gid=", 4)) {
-			if (pax_read_decimal(line + 4, &field))
+		} else if (!strncmp(ptr, "gid=", 4)) {
+			if (pax_read_decimal(ptr + 4, &field))
 				goto fail;
 			out->sb.st_gid = field;
 			*set_by_pax |= PAX_GID;
-		} else if (!strncmp(line, "path=", 5)) {
+		} else if (!strncmp(ptr, "path=", 5)) {
 			free(out->name);
-			out->name = strdup(line + 5);
+			out->name = strdup(ptr + 5);
 			if (out->name == NULL)
 				goto fail_errno;
 			*set_by_pax |= PAX_NAME;
-		} else if (!strncmp(line, "size=", 5)) {
-			if (pax_read_decimal(line + 5, &out->record_size))
+		} else if (!strncmp(ptr, "size=", 5)) {
+			if (pax_read_decimal(ptr + 5, &out->record_size))
 				goto fail;
 			*set_by_pax |= PAX_SIZE;
-		} else if (!strncmp(line, "linkpath=", 9)) {
+		} else if (!strncmp(ptr, "linkpath=", 9)) {
 			free(out->link_target);
-			out->link_target = strdup(line + 9);
+			out->link_target = strdup(ptr + 9);
 			if (out->link_target == NULL)
 				goto fail_errno;
 			*set_by_pax |= PAX_SLINK_TARGET;
-		} else if (!strncmp(line, "mtime=", 6)) {
-			if (line[6] == '-') {
-				if (pax_read_decimal(line + 7, &field))
+		} else if (!strncmp(ptr, "mtime=", 6)) {
+			if (ptr[6] == '-') {
+				if (pax_read_decimal(ptr + 7, &field))
 					goto fail;
 				out->mtime = -((sqfs_s64)field);
 			} else {
-				if (pax_read_decimal(line + 6, &field))
+				if (pax_read_decimal(ptr + 6, &field))
 					goto fail;
 				out->mtime = field;
 			}
 			*set_by_pax |= PAX_MTIME;
-		} else if (!strncmp(line, "GNU.sparse.name=", 16)) {
+		} else if (!strncmp(ptr, "GNU.sparse.name=", 16)) {
 			free(out->name);
-			out->name = strdup(line + 16);
+			out->name = strdup(ptr + 16);
 			if (out->name == NULL)
 				goto fail_errno;
 			*set_by_pax |= PAX_NAME;
-		} else if (!strncmp(line, "GNU.sparse.map=", 15)) {
+		} else if (!strncmp(ptr, "GNU.sparse.map=", 15)) {
 			free_sparse_list(out->sparse);
 			sparse_last = NULL;
 
-			out->sparse = read_sparse_map(line + 15);
+			out->sparse = read_sparse_map(ptr + 15);
 			if (out->sparse == NULL)
 				goto fail;
-		} else if (!strncmp(line, "GNU.sparse.size=", 16)) {
-			if (pax_read_decimal(line + 16, &out->actual_size))
+		} else if (!strncmp(ptr, "GNU.sparse.size=", 16)) {
+			if (pax_read_decimal(ptr + 16, &out->actual_size))
 				goto fail;
 			*set_by_pax |= PAX_SPARSE_SIZE;
-		} else if (!strncmp(line, "GNU.sparse.offset=", 18)) {
-			if (pax_read_decimal(line + 18, &offset))
+		} else if (!strncmp(ptr, "GNU.sparse.offset=", 18)) {
+			if (pax_read_decimal(ptr + 18, &offset))
 				goto fail;
-		} else if (!strncmp(line, "GNU.sparse.numbytes=", 20)) {
-			if (pax_read_decimal(line + 20, &num_bytes))
+		} else if (!strncmp(ptr, "GNU.sparse.numbytes=", 20)) {
+			if (pax_read_decimal(ptr + 20, &num_bytes))
 				goto fail;
 			sparse = calloc(1, sizeof(*sparse));
 			if (sparse == NULL)
@@ -193,8 +178,8 @@ static int read_pax_header(FILE *fp, sqfs_u64 entsize, unsigned int *set_by_pax,
 				sparse_last->next = sparse;
 				sparse_last = sparse;
 			}
-		} else if (!strncmp(line, "SCHILY.xattr.", 13)) {
-			key = line + 13;
+		} else if (!strncmp(ptr, "SCHILY.xattr.", 13)) {
+			key = ptr + 13;
 
 			ptr = strrchr(key, '=');
 			if (ptr == NULL || ptr == key)
@@ -209,8 +194,8 @@ static int read_pax_header(FILE *fp, sqfs_u64 entsize, unsigned int *set_by_pax,
 
 			xattr->next = out->xattr;
 			out->xattr = xattr;
-		} else if (!strncmp(line, "LIBARCHIVE.xattr.", 17)) {
-			key = line + 17;
+		} else if (!strncmp(ptr, "LIBARCHIVE.xattr.", 17)) {
+			key = ptr + 17;
 
 			ptr = strrchr(key, '=');
 			if (ptr == NULL || ptr == key)
-- 
cgit v1.2.3