libipt: check section boundaries in pt_section_read()

When reading from a mapped section, we perform all kinds of overflow checks in
the various read functions.  And we still missed an overflow on systems with
32-bit pointers.

Move the check to pt_section_read() where we check the requested offset against
the section size.  Also truncate the read request there, if necessary.

This fixes the overflow on systems with 32-bit pointers.

Change-Id: I3c4591dcde113e2696347298af5ad3671ef0e41c
Signed-off-by: Markus Metzger <markus.t.metzger@intel.com>
diff --git a/libipt/src/posix/pt_section_posix.c b/libipt/src/posix/pt_section_posix.c
index 8da97e0..1b1ce05 100644
--- a/libipt/src/posix/pt_section_posix.c
+++ b/libipt/src/posix/pt_section_posix.c
@@ -246,8 +246,7 @@
 		      uint16_t size, uint64_t offset)
 {
 	struct pt_sec_posix_mapping *mapping;
-	const uint8_t *begin, *end;
-	int bytes;
+	const uint8_t *begin;
 
 	if (!buffer || !section)
 		return -pte_invalid;
@@ -256,23 +255,14 @@
 	if (!mapping)
 		return -pte_internal;
 
+	/* We already checked in pt_section_read() that the requested memory
+	 * lies within the section's boundaries.
+	 *
+	 * And we checked that the entire section was mapped.  There's no need
+	 * to check for overflows, again.
+	 */
 	begin = mapping->begin + offset;
-	end = begin + size;
 
-	if (end < begin)
-		return -pte_nomap;
-
-	if (mapping->end <= begin)
-		return -pte_nomap;
-
-	if (begin < mapping->begin)
-		return -pte_nomap;
-
-	if (mapping->end < end)
-		end = mapping->end;
-
-	bytes = (int) (end - begin);
-
-	memcpy(buffer, begin, bytes);
-	return bytes;
+	memcpy(buffer, begin, size);
+	return (int) size;
 }
diff --git a/libipt/src/pt_section.c b/libipt/src/pt_section.c
index 87d242e..e04275a 100644
--- a/libipt/src/pt_section.c
+++ b/libipt/src/pt_section.c
@@ -265,11 +265,22 @@
 int pt_section_read(const struct pt_section *section, uint8_t *buffer,
 		    uint16_t size, uint64_t offset)
 {
+	uint64_t limit, space;
+
 	if (!section)
 		return -pte_internal;
 
 	if (!section->read)
 		return -pte_nomap;
 
+	limit = section->size;
+	if (limit <= offset)
+		return -pte_nomap;
+
+	/* Truncate if we try to read past the end of the section. */
+	space = limit - offset;
+	if (space < size)
+		size = (uint16_t) space;
+
 	return section->read(section, buffer, size, offset);
 }
diff --git a/libipt/src/pt_section_file.c b/libipt/src/pt_section_file.c
index 0ae13f8..034352c 100644
--- a/libipt/src/pt_section_file.c
+++ b/libipt/src/pt_section_file.c
@@ -195,7 +195,7 @@
 {
 	struct pt_sec_file_mapping *mapping;
 	FILE *file;
-	long begin, end, fbegin, fend;
+	long begin;
 	size_t read;
 	int errcode;
 
@@ -207,30 +207,20 @@
 		return -pte_internal;
 
 	file = mapping->file;
-	begin = mapping->begin;
-	end = mapping->end;
 
-	fbegin = (long) offset;
-	if (((uint64_t) fbegin) != offset)
-		return -pte_nomap;
-
-	fbegin += begin;
-	fend = fbegin + size;
-
-	if (fend < fbegin)
-		return -pte_nomap;
-
-	if (end <= fbegin)
-		return -pte_nomap;
-
-	if (fbegin < begin)
-		return -pte_nomap;
+	/* We already checked in pt_section_read() that the requested memory
+	 * lies within the section's boundaries.
+	 *
+	 * And we checked that the file covers the entire section in
+	 * pt_sec_file_map().  There's no need to check for overflows, again.
+	 */
+	begin = mapping->begin + (long) offset;
 
 	errcode = fmap_lock(mapping);
 	if (errcode < 0)
 		return errcode;
 
-	errcode = fseek(file, fbegin, SEEK_SET);
+	errcode = fseek(file, begin, SEEK_SET);
 	if (errcode)
 		goto out_unlock;
 
diff --git a/libipt/src/windows/pt_section_windows.c b/libipt/src/windows/pt_section_windows.c
index 02dcb06..2fc6000 100644
--- a/libipt/src/windows/pt_section_windows.c
+++ b/libipt/src/windows/pt_section_windows.c
@@ -321,8 +321,7 @@
 		      uint16_t size, uint64_t offset)
 {
 	struct pt_sec_windows_mapping *mapping;
-	const uint8_t *begin, *end;
-	int bytes;
+	const uint8_t *begin;
 
 	if (!buffer || !section)
 		return -pte_invalid;
@@ -331,23 +330,14 @@
 	if (!mapping)
 		return -pte_internal;
 
+	/* We already checked in pt_section_read() that the requested memory
+	 * lies within the section's boundaries.
+	 *
+	 * And we checked that the entire section was mapped.  There's no need
+	 * to check for overflows, again.
+	 */
 	begin = mapping->begin + offset;
-	end = begin + size;
 
-	if (end < begin)
-		return -pte_nomap;
-
-	if (mapping->end <= begin)
-		return -pte_nomap;
-
-	if (begin < mapping->begin)
-		return -pte_nomap;
-
-	if (mapping->end < end)
-		end = mapping->end;
-
-	bytes = (int) (end - begin);
-
-	memcpy(buffer, begin, bytes);
-	return bytes;
+	memcpy(buffer, begin, size);
+	return (int) size;
 }
diff --git a/libipt/test/src/ptunit-section.c b/libipt/test/src/ptunit-section.c
index cb9ce20..38f676f 100644
--- a/libipt/test/src/ptunit-section.c
+++ b/libipt/test/src/ptunit-section.c
@@ -491,6 +491,30 @@
 	return ptu_passed();
 }
 
+static struct ptunit_result read_overflow_32bit(struct section_fixture *sfix)
+{
+	uint8_t bytes[] = { 0xcc, 0x2, 0x4, 0x6 }, buffer[] = { 0xcc };
+	int status;
+
+	sfix_write(sfix, bytes);
+
+	sfix->section = pt_mk_section(sfix->name, 0x1ull, 0x3ull);
+	ptu_ptr(sfix->section);
+
+	status = pt_section_map(sfix->section);
+	ptu_int_eq(status, 0);
+
+	status = pt_section_read(sfix->section, buffer, 1,
+				 0xff00000000ull);
+	ptu_int_eq(status, -pte_nomap);
+	ptu_uint_eq(buffer[0], 0xcc);
+
+	status = pt_section_unmap(sfix->section);
+	ptu_int_eq(status, 0);
+
+	return ptu_passed();
+}
+
 static struct ptunit_result read_nomap(struct section_fixture *sfix)
 {
 	uint8_t bytes[] = { 0xcc, 0x2, 0x4, 0x6 }, buffer[] = { 0xcc };
@@ -706,6 +730,7 @@
 	ptu_run_f(suite, read_from_truncated, sfix);
 	ptu_run_f(suite, read_nomem, sfix);
 	ptu_run_f(suite, read_overflow, sfix);
+	ptu_run_f(suite, read_overflow_32bit, sfix);
 	ptu_run_f(suite, read_nomap, sfix);
 	ptu_run_f(suite, read_unmap_map, sfix);
 	ptu_run_f(suite, stress, sfix);