From 5f1f1c47623f846909481073d56bc909d13e5e37 Mon Sep 17 00:00:00 2001
From: Jeremy Allison <jra@samba.org>
Date: Wed, 30 Sep 2009 14:27:26 +0200
Subject: [PATCH] Fix for CVE-2009-2906.

Summary:
Specially crafted SMB requests on
authenticated SMB connections can send smbd
into a 100% CPU loop, causing a DoS on the
Samba server.
---
 source/include/smb.h  |    1 +
 source/smbd/process.c |   28 +++++++++++++++++++++++++---
 2 files changed, 26 insertions(+), 3 deletions(-)

diff --git a/source/include/smb.h b/source/include/smb.h
index 56d9461..327f212 100644
--- a/source/include/smb.h
+++ b/source/include/smb.h
@@ -715,6 +715,7 @@ struct pending_message_list {
 	struct timeval request_time; /* When was this first issued? */
 	struct timeval end_time; /* When does this time out? */
 	bool encrypted;
+	bool processed;
 	DATA_BLOB buf;
 	DATA_BLOB private_data;
 };
diff --git a/source/smbd/process.c b/source/smbd/process.c
index 365c972..446b868 100644
--- a/source/smbd/process.c
+++ b/source/smbd/process.c
@@ -438,6 +438,7 @@ static bool push_queued_message(struct smb_request *req,
 	msg->request_time = request_time;
 	msg->end_time = end_time;
 	msg->encrypted = req->encrypted;
+	msg->processed = false;
 
 	if (private_data) {
 		msg->private_data = data_blob_talloc(msg, private_data,
@@ -493,6 +494,16 @@ void schedule_deferred_open_smb_message(uint16 mid)
 		DEBUG(10,("schedule_deferred_open_smb_message: [%d] msg_mid = %u\n", i++,
 			(unsigned int)msg_mid ));
 		if (mid == msg_mid) {
+
+			if (pml->processed) {
+				/* A processed message should not be
+				 * rescheduled. */
+				DEBUG(0,("schedule_deferred_open_smb_message: LOGIC ERROR "
+					"message mid %u was already processed\n",
+					(unsigned int)msg_mid ));
+				continue;
+			}
+
 			DEBUG(10,("schedule_deferred_open_smb_message: scheduling mid %u\n",
 				mid ));
 			pml->end_time.tv_sec = 0;
@@ -507,7 +518,7 @@ void schedule_deferred_open_smb_message(uint16 mid)
 }
 
 /****************************************************************************
- Return true if this mid is on the deferred queue.
+ Return true if this mid is on the deferred queue and was not yet processed.
 ****************************************************************************/
 
 bool open_was_deferred(uint16 mid)
@@ -515,7 +526,7 @@ bool open_was_deferred(uint16 mid)
 	struct pending_message_list *pml;
 
 	for (pml = deferred_open_queue; pml; pml = pml->next) {
-		if (SVAL(pml->buf.data,smb_mid) == mid) {
+		if (SVAL(pml->buf.data,smb_mid) == mid && !pml->processed) {
 			return True;
 		}
 	}
@@ -784,6 +795,10 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
 			/* We leave this message on the queue so the open code can
 			   know this is a retry. */
 			DEBUG(5,("receive_message_or_smb: returning deferred open smb message.\n"));
+
+			/* Mark the message as processed so this is not
+			 * re-processed in error. */
+			msg->processed = true;
 			return NT_STATUS_OK;
 		}
 	}
@@ -1428,7 +1443,6 @@ static connection_struct *switch_message(uint8 type, struct smb_request *req, in
 
 		if (!change_to_user(conn,session_tag)) {
 			reply_nterror(req, NT_STATUS_DOS(ERRSRV, ERRbaduid));
-			remove_deferred_open_smb_message(req->mid);
 			return conn;
 		}
 
@@ -1493,6 +1507,7 @@ static connection_struct *switch_message(uint8 type, struct smb_request *req, in
 
 static void construct_reply(char *inbuf, int size, size_t unread_bytes, bool encrypted)
 {
+	struct pending_message_list *pml = NULL;
 	uint8 type = CVAL(inbuf,smb_com);
 	connection_struct *conn;
 	struct smb_request *req;
@@ -1508,6 +1523,13 @@ static void construct_reply(char *inbuf, int size, size_t unread_bytes, bool enc
 
 	conn = switch_message(type, req, size);
 
+	/* If this was a deferred message and it's still there and
+	 * was processed, remove it. */
+	pml = get_open_deferred_message(req->mid);
+	if (pml && pml->processed) {
+		remove_deferred_open_smb_message(req->mid);
+	}
+
 	if (req->unread_bytes) {
 		/* writeX failed. drain socket. */
 		if (drain_socket(smbd_server_fd(), req->unread_bytes) !=
-- 
1.6.0.2