Fix for CVE-2009-2906.
[samba.git] / source / smbd / process.c
index 2a643864fad5aedb15b3e0b10705d5ef19869d8b..c53bfda2219a6be756479b7afb09e1adff590c02 100644 (file)
@@ -120,9 +120,7 @@ static bool valid_packet_size(size_t len)
        if (len > (BUFFER_SIZE + LARGE_WRITEX_HDR_SIZE)) {
                DEBUG(0,("Invalid packet length! (%lu bytes).\n",
                                        (unsigned long)len));
-               if (len > BUFFER_SIZE + (SAFETY_MARGIN/2)) {
-                       return false;
-               }
+               return false;
        }
        return true;
 }
@@ -166,7 +164,7 @@ static NTSTATUS receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx,
        ssize_t toread;
        NTSTATUS status;
 
-       memcpy(writeX_header, lenbuf, sizeof(lenbuf));
+       memcpy(writeX_header, lenbuf, 4);
 
        status = read_socket_with_timeout(
                fd, writeX_header + 4,
@@ -252,6 +250,8 @@ static NTSTATUS receive_smb_raw_talloc_partial_read(TALLOC_CTX *mem_ctx,
                        timeout, toread);
 
                if (!NT_STATUS_IS_OK(status)) {
+                       DEBUG(10, ("receive_smb_raw_talloc_partial_read: %s\n",
+                                  nt_errstr(status)));
                        return status;
                }
        }
@@ -279,17 +279,11 @@ static NTSTATUS receive_smb_raw_talloc(TALLOC_CTX *mem_ctx, int fd,
 
        if (CVAL(lenbuf,0) == 0 &&
                        min_recv_size &&
-                       smb_len_large(lenbuf) > min_recv_size && /* Could be a UNIX large writeX. */
+                       smb_len_large(lenbuf) > (min_recv_size + STANDARD_WRITE_AND_X_HEADER_SIZE) && /* Could be a UNIX large writeX. */
                        !srv_is_signing_active()) {
 
-               status = receive_smb_raw_talloc_partial_read(
-                       mem_ctx, lenbuf, fd, buffer, timeout, p_unread, &len);
-
-               if (!NT_STATUS_IS_OK(status)) {
-                       DEBUG(10, ("receive_smb_raw: %s\n",
-                                  nt_errstr(status)));
-                       return status;
-               }
+               return receive_smb_raw_talloc_partial_read(
+                       mem_ctx, lenbuf, fd, buffer, timeout, p_unread, plen);
        }
 
        if (!valid_packet_size(len)) {
@@ -324,7 +318,7 @@ static NTSTATUS receive_smb_talloc(TALLOC_CTX *mem_ctx,     int fd,
                                   size_t *p_unread, bool *p_encrypted,
                                   size_t *p_len)
 {
-       size_t len;
+       size_t len = 0;
        NTSTATUS status;
 
        *p_encrypted = false;
@@ -440,6 +434,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,
@@ -495,6 +490,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;
@@ -509,7 +514,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)
@@ -517,7 +522,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;
                }
        }
@@ -719,7 +724,7 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
        int selrtn;
        struct timeval to;
        int maxfd = 0;
-       size_t len;
+       size_t len = 0;
        NTSTATUS status;
 
        *p_unread = 0;
@@ -784,6 +789,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;
                }
        }
@@ -871,8 +880,8 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
        if (selrtn == -1) {
                /* something is wrong. Maybe the socket is dead? */
                return map_nt_error_from_unix(errno);
-       } 
-    
+       }
+
        /* Did we timeout ? */
        if (selrtn == 0) {
                return NT_STATUS_IO_TIMEOUT;
@@ -894,6 +903,15 @@ static NTSTATUS receive_message_or_smb(TALLOC_CTX *mem_ctx, char **buffer,
                goto again;
        }
 
+       /*
+        * We've just woken up from a protentially long select sleep.
+        * Ensure we process local messages as we need to synchronously
+        * process any messages from other smbd's to avoid file rename race
+        * conditions. This call is cheap if there are no messages waiting.
+        * JRA.
+        */
+       message_dispatch(smbd_messaging_context());
+
        status = receive_smb_talloc(mem_ctx, smbd_server_fd(), buffer, 0,
                                    p_unread, p_encrypted, &len);
 
@@ -1030,7 +1048,7 @@ static const struct smb_message_struct {
 /* 0x30 */ { NULL, NULL, 0 },
 /* 0x31 */ { NULL, NULL, 0 },
 /* 0x32 */ { "SMBtrans2",reply_trans2, AS_USER | CAN_IPC },
-/* 0x33 */ { "SMBtranss2",reply_transs2, AS_USER},
+/* 0x33 */ { "SMBtranss2",reply_transs2, AS_USER | CAN_IPC},
 /* 0x34 */ { "SMBfindclose",reply_findclose,AS_USER},
 /* 0x35 */ { "SMBfindnclose",reply_findnclose,AS_USER},
 /* 0x36 */ { NULL, NULL, 0 },
@@ -1250,8 +1268,10 @@ void reply_outbuf(struct smb_request *req, uint8 num_words, uint32 num_bytes)
        if ((num_bytes > 0xffffff)
            || ((num_bytes + smb_size + num_words*2) > 0xffffff)) {
                char *msg;
-               asprintf(&msg, "num_bytes too large: %u",
-                        (unsigned)num_bytes);
+               if (asprintf(&msg, "num_bytes too large: %u",
+                            (unsigned)num_bytes) == -1) {
+                       msg = CONST_DISCARD(char *, "num_bytes too large");
+               }
                smb_panic(msg);
        }
 
@@ -1289,9 +1309,8 @@ static void smb_dump(const char *name, int type, const char *data, ssize_t len)
 
        if (len < 4) len = smb_len(data)+4;
        for (i=1;i<100;i++) {
-               asprintf(&fname, "/tmp/%s.%d.%s", name, i,
-                               type ? "req" : "resp");
-               if (!fname) {
+               if (asprintf(&fname, "/tmp/%s.%d.%s", name, i,
+                            type ? "req" : "resp") == -1) {
                        return;
                }
                fd = open(fname, O_WRONLY|O_CREAT|O_EXCL, 0644);
@@ -1465,6 +1484,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;
@@ -1480,6 +1500,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) !=
@@ -1632,6 +1659,7 @@ void chain_reply(struct smb_request *req)
        char *outbuf = (char *)req->outbuf;
        size_t outsize = smb_len(outbuf) + 4;
        size_t outsize_padded;
+       size_t padding;
        size_t ofs, to_move;
 
        struct smb_request *req2;
@@ -1670,12 +1698,13 @@ void chain_reply(struct smb_request *req)
         */
 
        outsize_padded = (outsize + 3) & ~3;
+       padding = outsize_padded - outsize;
 
        /*
         * remember how much the caller added to the chain, only counting
         * stuff after the parameter words
         */
-       chain_size += outsize_padded - smb_wct;
+       chain_size += (outsize_padded - smb_wct);
 
        /*
         * work out pointers into the original packets. The
@@ -1783,17 +1812,17 @@ void chain_reply(struct smb_request *req)
        SCVAL(outbuf, smb_vwv0, smb_com2);
        SSVAL(outbuf, smb_vwv1, chain_size + smb_wct - 4);
 
-       if (outsize_padded > outsize) {
+       if (padding != 0) {
 
                /*
                 * Due to padding we have some uninitialized bytes after the
                 * caller's output
                 */
 
-               memset(outbuf + outsize, 0, outsize_padded - outsize);
+               memset(outbuf + outsize, 0, padding);
        }
 
-       smb_setlen(outbuf, outsize2 + chain_size - 4);
+       smb_setlen(outbuf, outsize2 + caller_outputlen + padding - 4);
 
        /*
         * restore the saved data, being careful not to overwrite any data
@@ -1804,6 +1833,12 @@ void chain_reply(struct smb_request *req)
        SAFE_FREE(caller_output);
        TALLOC_FREE(req2);
 
+       /*
+        * Reset the chain_size for our caller's offset calculations
+        */
+
+       chain_size -= (outsize_padded - smb_wct);
+
        return;
 }
 
@@ -1901,6 +1936,7 @@ static void timeout_processing(int *select_timeout,
 
                unsigned char trust_passwd_hash[16];
                time_t lct;
+               void *lock;
 
                /*
                 * We're in domain level security, and the code that
@@ -1912,7 +1948,9 @@ static void timeout_processing(int *select_timeout,
                 * First, open the machine password file with an exclusive lock.
                 */
 
-               if (secrets_lock_trust_account_password(lp_workgroup(), True) == False) {
+               lock = secrets_get_trust_account_lock(NULL, lp_workgroup());
+
+               if (lock == NULL) {
                        DEBUG(0,("process: unable to lock the machine account password for \
 machine %s in domain %s.\n", global_myname(), lp_workgroup() ));
                        return;
@@ -1921,7 +1959,7 @@ machine %s in domain %s.\n", global_myname(), lp_workgroup() ));
                if(!secrets_fetch_trust_account_password(lp_workgroup(), trust_passwd_hash, &lct, NULL)) {
                        DEBUG(0,("process: unable to read the machine account password for \
 machine %s in domain %s.\n", global_myname(), lp_workgroup()));
-                       secrets_lock_trust_account_password(lp_workgroup(), False);
+                       TALLOC_FREE(lock);
                        return;
                }
 
@@ -1931,7 +1969,7 @@ machine %s in domain %s.\n", global_myname(), lp_workgroup()));
 
                if(t < lct + lp_machine_password_timeout()) {
                        global_machine_password_needs_changing = False;
-                       secrets_lock_trust_account_password(lp_workgroup(), False);
+                       TALLOC_FREE(lock);
                        return;
                }
 
@@ -1939,7 +1977,7 @@ machine %s in domain %s.\n", global_myname(), lp_workgroup()));
     
                change_trust_account_password( lp_workgroup(), NULL);
                global_machine_password_needs_changing = False;
-               secrets_lock_trust_account_password(lp_workgroup(), False);
+               TALLOC_FREE(lock);
        }
 
        /* update printer queue caches if necessary */