[PATCH] connection: Detect overflows in length field.

Michal Srb msrb at suse.com
Mon Jul 30 15:45:17 UTC 2018


The length field can be any uint32 value. Two kinds of overflows may
happen on 32 bit systems:

1) If the value is in range [UINT32_MAX-3, UINT32_MAX], the DIV_ROUNDUP
will turn it into 0. Then `next` equals `p` and so the big `length` is not
detected. But the wl_array will contain the original big value. Probably
leading to crashes it is used later.

2) The pointer may overflow if the `length` is sufficiently big. In that
case `next` will point to some memory before the beginning of the buffer
and the the check `next > end` is not triggered. Using `next` later can
crash.

Signed-off-by: Michal Srb <msrb at suse.com>
---

Note that the problem happens only on 32bit systems.

 src/connection.c        | 19 +++++++++++++++++--
 tests/connection-test.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 63 insertions(+), 2 deletions(-)

diff --git a/src/connection.c b/src/connection.c
index 294c521..b48f3a4 100644
--- a/src/connection.c
+++ b/src/connection.c
@@ -46,6 +46,21 @@
 
 #define DIV_ROUNDUP(n, a) ( ((n) + ((a) - 1)) / (a) )
 
+inline uint32_t *
+ptr_add_saturating(uint32_t *p, uint32_t length) {
+	// Make sure that rounding length up will not overflow.
+	if (length > UINT32_MAX - sizeof(uint32_t))
+		return (uint32_t*) INTPTR_MAX;
+
+	length = DIV_ROUNDUP(length, sizeof(uint32_t));
+
+	// Make sure that adding length to p will not overflow.
+	if (length * sizeof(uint32_t) > UINTPTR_MAX - (uintptr_t) p)
+		return (uint32_t*) INTPTR_MAX;
+
+	return p + length;
+}
+
 struct wl_buffer {
 	char data[4096];
 	uint32_t head, tail;
@@ -734,7 +749,7 @@ wl_connection_demarshal(struct wl_connection *connection,
 				break;
 			}
 
-			next = p + DIV_ROUNDUP(length, sizeof *p);
+			next = ptr_add_saturating(p, length);
 			if (next > end) {
 				wl_log("message too short, "
 				       "object (%d), message %s(%s)\n",
@@ -793,7 +808,7 @@ wl_connection_demarshal(struct wl_connection *connection,
 		case 'a':
 			length = *p++;
 
-			next = p + DIV_ROUNDUP(length, sizeof *p);
+			next = ptr_add_saturating(p, length);
 			if (next > end) {
 				wl_log("message too short, "
 				       "object (%d), message %s(%s)\n",
diff --git a/tests/connection-test.c b/tests/connection-test.c
index 157e1bc..ff064cf 100644
--- a/tests/connection-test.c
+++ b/tests/connection-test.c
@@ -456,6 +456,52 @@ TEST(connection_demarshal)
 	release_marshal_data(&data);
 }
 
+static void
+expected_fail_demarshal(struct marshal_data *data, const char *format, uint32_t *msg, int expected_error)
+{
+	struct wl_message message = { "test", format, NULL };
+	struct wl_closure *closure;
+	struct wl_map objects;
+	int size = msg[1];
+
+	assert(write(data->s[1], msg, size) == size);
+	assert(wl_connection_read(data->read_connection) == size);
+
+	wl_map_init(&objects, WL_MAP_SERVER_SIDE);
+	closure = wl_connection_demarshal(data->read_connection,
+					  size, &objects, &message);
+
+	assert(closure == NULL);
+	assert(errno == expected_error);
+}
+
+TEST(connection_demarshal_failures)
+{
+	struct marshal_data data;
+	uint32_t msg[10];
+
+	setup_marshal_data(&data);
+
+	// These need careful handling on 32bit systems.
+	uint32_t overflowing_values[] = {
+		0xffffffff, 0xfffffffe, 0xfffffffd, 0xfffffffc,
+		0xfffff000, 0xffffd000, 0xffffc000, 0xffffb000
+	};
+	for (unsigned int i = 0; i < ARRAY_LENGTH(overflowing_values); i++) {
+		msg[0] = 400200;
+		msg[1] = 24;
+		msg[2] = overflowing_values[i];
+		expected_fail_demarshal(&data, "s", msg, EINVAL);
+
+		msg[0] = 400200;
+		msg[1] = 24;
+		msg[2] = overflowing_values[i];
+		expected_fail_demarshal(&data, "a", msg, EINVAL);
+	}
+
+	release_marshal_data(&data);
+}
+
 static void
 marshal_demarshal(struct marshal_data *data,
 		  void (*func)(void), int size, const char *format, ...)
-- 
2.16.4



More information about the wayland-devel mailing list