From a10c0ce76098857b899505d05de9f2e13ddf7a7a Mon Sep 17 00:00:00 2001
From: Clemens Ladisch <clemens@ladisch.de>
Date: Wed, 19 May 2010 08:28:32 +0200
Subject: [PATCH] firewire: check cdev response length

Add a check that the data length in the SEND_RESPONSE ioctl is correct.
Incidentally, this also fixes the previously wrong response length of
software-handled lock requests.

Signed-off-by: Clemens Ladisch <clemens@ladisch.de>
Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
---
 drivers/firewire/core-cdev.c        |  9 ++++---
 drivers/firewire/core-transaction.c | 38 ++++++++++++++++++++++++++++-
 drivers/firewire/core.h             |  1 +
 3 files changed, 44 insertions(+), 4 deletions(-)

diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c
index 9d1a1a1a83c9..50332b84f49a 100644
--- a/drivers/firewire/core-cdev.c
+++ b/drivers/firewire/core-cdev.c
@@ -756,9 +756,12 @@ static int ioctl_send_response(struct client *client, union ioctl_arg *arg)
 	if (is_fcp_request(r->request))
 		goto out;
 
-	if (a->length < r->length)
-		r->length = a->length;
-	if (copy_from_user(r->data, u64_to_uptr(a->data), r->length)) {
+	if (a->length != fw_get_response_length(r->request)) {
+		ret = -EINVAL;
+		kfree(r->request);
+		goto out;
+	}
+	if (copy_from_user(r->data, u64_to_uptr(a->data), a->length)) {
 		ret = -EFAULT;
 		kfree(r->request);
 		goto out;
diff --git a/drivers/firewire/core-transaction.c b/drivers/firewire/core-transaction.c
index fdc33ff06dc1..4fd5c3b2128e 100644
--- a/drivers/firewire/core-transaction.c
+++ b/drivers/firewire/core-transaction.c
@@ -580,6 +580,41 @@ static void free_response_callback(struct fw_packet *packet,
 	kfree(request);
 }
 
+int fw_get_response_length(struct fw_request *r)
+{
+	int tcode, ext_tcode, data_length;
+
+	tcode = HEADER_GET_TCODE(r->request_header[0]);
+
+	switch (tcode) {
+	case TCODE_WRITE_QUADLET_REQUEST:
+	case TCODE_WRITE_BLOCK_REQUEST:
+		return 0;
+
+	case TCODE_READ_QUADLET_REQUEST:
+		return 4;
+
+	case TCODE_READ_BLOCK_REQUEST:
+		data_length = HEADER_GET_DATA_LENGTH(r->request_header[3]);
+		return data_length;
+
+	case TCODE_LOCK_REQUEST:
+		ext_tcode = HEADER_GET_EXTENDED_TCODE(r->request_header[3]);
+		data_length = HEADER_GET_DATA_LENGTH(r->request_header[3]);
+		switch (ext_tcode) {
+		case EXTCODE_FETCH_ADD:
+		case EXTCODE_LITTLE_ADD:
+			return data_length;
+		default:
+			return data_length / 2;
+		}
+
+	default:
+		WARN(1, KERN_ERR "wrong tcode %d", tcode);
+		return 0;
+	}
+}
+
 void fw_fill_response(struct fw_packet *response, u32 *request_header,
 		      int rcode, void *payload, size_t length)
 {
@@ -713,7 +748,8 @@ void fw_send_response(struct fw_card *card,
 
 	if (rcode == RCODE_COMPLETE)
 		fw_fill_response(&request->response, request->request_header,
-				 rcode, request->data, request->length);
+				 rcode, request->data,
+				 fw_get_response_length(request));
 	else
 		fw_fill_response(&request->response, request->request_header,
 				 rcode, NULL, 0);
diff --git a/drivers/firewire/core.h b/drivers/firewire/core.h
index 0ecfcd95f4c5..25a72e57a0cd 100644
--- a/drivers/firewire/core.h
+++ b/drivers/firewire/core.h
@@ -218,6 +218,7 @@ static inline bool is_next_generation(int new_generation, int old_generation)
 
 void fw_core_handle_request(struct fw_card *card, struct fw_packet *request);
 void fw_core_handle_response(struct fw_card *card, struct fw_packet *packet);
+int fw_get_response_length(struct fw_request *request);
 void fw_fill_response(struct fw_packet *response, u32 *request_header,
 		      int rcode, void *payload, size_t length);
 void fw_send_phy_config(struct fw_card *card,
-- 
GitLab