From f77bdcf6c71a9d0274d96298b136300d5bb05bb1 Mon Sep 17 00:00:00 2001 From: Jeremy Allison Date: Tue, 20 Feb 2007 06:22:20 +0000 Subject: r21460: Fix for server-side processing of SPNEGO auth fragmented into "max xmit" size security blob chunks. Bug #4400. Needs limits adding, and also a client-side version. Jeremy. (This used to be commit aa69f2481aafee5dccc3783b8a6e23ca4eb0dbfa) --- source3/include/smb.h | 10 ++- source3/libsmb/asn1.c | 8 ++ source3/smbd/password.c | 1 + source3/smbd/sesssetup.c | 226 ++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 234 insertions(+), 11 deletions(-) diff --git a/source3/include/smb.h b/source3/include/smb.h index cc03722c2b..cea7638d79 100644 --- a/source3/include/smb.h +++ b/source3/include/smb.h @@ -1719,6 +1719,15 @@ struct pwd_info { fstring password; }; +/* For split krb5 SPNEGO blobs. */ +struct pending_auth_data { + struct pending_auth_data *prev, *next; + uint16 vuid; /* Tag for this entry. */ + uint16 smbpid; /* Alternate tag for this entry. */ + size_t needed_len; + DATA_BLOB partial_data; +}; + typedef struct user_struct { struct user_struct *next, *prev; uint16 vuid; /* Tag for this entry. */ @@ -1748,7 +1757,6 @@ typedef struct user_struct { struct auth_serversupplied_info *server_info; struct auth_ntlmssp_state *auth_ntlmssp_state; - } user_struct; struct unix_error_map { diff --git a/source3/libsmb/asn1.c b/source3/libsmb/asn1.c index 937e063c62..6ebe9ab62c 100644 --- a/source3/libsmb/asn1.c +++ b/source3/libsmb/asn1.c @@ -23,6 +23,14 @@ /* free an asn1 structure */ void asn1_free(ASN1_DATA *data) { + struct nesting *nesting = data->nesting; + + while (nesting) { + struct nesting *nnext = nesting->next; + free(nesting); + nesting = nnext; + }; + data->nesting = NULL; SAFE_FREE(data->data); } diff --git a/source3/smbd/password.c b/source3/smbd/password.c index 10cb920237..bf4e9258ff 100644 --- a/source3/smbd/password.c +++ b/source3/smbd/password.c @@ -111,6 +111,7 @@ void invalidate_vuid(uint16 vuid) SAFE_FREE(vuser->groups); TALLOC_FREE(vuser->nt_user_token); + SAFE_FREE(vuser); num_validated_vuids--; } diff --git a/source3/smbd/sesssetup.c b/source3/smbd/sesssetup.c index 6c5e8f678f..9b9ae4c353 100644 --- a/source3/smbd/sesssetup.c +++ b/source3/smbd/sesssetup.c @@ -658,6 +658,183 @@ static int reply_spnego_auth(connection_struct *conn, char *inbuf, char *outbuf, return -1; } +/**************************************************************************** + List to store partial SPNEGO auth fragments. +****************************************************************************/ + +static struct pending_auth_data *pd_list; + +/**************************************************************************** + Delete an entry on the list. +****************************************************************************/ + +static void delete_partial_auth(struct pending_auth_data *pad) +{ + DLIST_REMOVE(pd_list, pad); + data_blob_free(&pad->partial_data); + SAFE_FREE(pad); +} + +/**************************************************************************** + Search for a partial SPNEGO auth fragment matching an smbpid. +****************************************************************************/ + +static struct pending_auth_data *get_pending_auth_data(uint16 smbpid) +{ + struct pending_auth_data *pad; + + for (pad = pd_list; pad; pad = pad->next) { + if (pad->smbpid == smbpid) { + break; + } + } + return pad; +} + +/**************************************************************************** + Check the size of an SPNEGO blob. If we need more return NT_STATUS_MORE_PROCESSING_REQUIRED, + else return NT_STATUS_OK. +****************************************************************************/ + +static NTSTATUS check_spnego_blob_complete(uint16 smbpid, uint16 vuid, DATA_BLOB *pblob) +{ + struct pending_auth_data *pad; + ASN1_DATA data; + size_t needed_len = 0; + + /* Ensure we have some data. */ + if (pblob->length == 0) { + /* Caller can cope. */ + DEBUG(2,("check_spnego_blob_complete: zero blob length !\n")); + delete_partial_auth(pad); + return NT_STATUS_OK; + } + + pad = get_pending_auth_data(smbpid); + + /* Were we waiting for more data ? */ + if (pad) { + DATA_BLOB tmp_blob; + + /* Integer wrap paranoia.... */ + + if (pad->partial_data.length + pblob->length < pad->partial_data.length || + pad->partial_data.length + pblob->length < pblob->length) { + + DEBUG(2,("check_spnego_blob_complete: integer wrap " + "pad->partial_data.length = %u, " + "pblob->length = %u\n", + (unsigned int)pad->partial_data.length, + (unsigned int)pblob->length )); + + delete_partial_auth(pad); + return NT_STATUS_INVALID_PARAMETER; + } + + DEBUG(10,("check_spnego_blob_complete: " + "pad->partial_data.length = %u, " + "pad->needed_len = %u, " + "pblob->length = %u,\n", + (unsigned int)pad->partial_data.length, + (unsigned int)pad->needed_len, + (unsigned int)pblob->length )); + + tmp_blob = data_blob(NULL, + pad->partial_data.length + pblob->length); + + /* Concatenate the two. */ + memcpy(tmp_blob.data, + pad->partial_data.data, + pad->partial_data.length); + memcpy(tmp_blob.data + pad->partial_data.length, + pblob->data, + pblob->length); + + /* Replace the partial data. */ + data_blob_free(&pad->partial_data); + pad->partial_data = tmp_blob; + ZERO_STRUCT(tmp_blob); + + /* Are we done ? */ + if (pblob->length >= pad->needed_len) { + /* Yes, replace pblob. */ + data_blob_free(pblob); + *pblob = pad->partial_data; + ZERO_STRUCT(pad->partial_data); + delete_partial_auth(pad); + return NT_STATUS_OK; + } + + /* Still need more data. */ + pad->needed_len -= pblob->length; + return NT_STATUS_MORE_PROCESSING_REQUIRED; + } + + if ((pblob->data[0] != ASN1_APPLICATION(0)) && + (pblob->data[0] != ASN1_CONTEXT(1))) { + /* Not something we can determine the + * length of. + */ + return NT_STATUS_OK; + } + + /* This is a new SPNEGO sessionsetup - see if + * the data given in this blob is enough. + */ + + asn1_load(&data, *pblob); + asn1_start_tag(&data, pblob->data[0]); + if (data.has_error || data.nesting == NULL) { + asn1_free(&data); + /* Let caller catch. */ + return NT_STATUS_OK; + } + + /* Integer wrap paranoia.... */ + + if (data.nesting->taglen + data.nesting->start < data.nesting->taglen || + data.nesting->taglen + data.nesting->start < data.nesting->start) { + + DEBUG(2,("check_spnego_blob_complete: integer wrap " + "data.nesting->taglen = %u, " + "data.nesting->start = %u\n", + (unsigned int)data.nesting->taglen, + (unsigned int)data.nesting->start )); + + asn1_free(&data); + return NT_STATUS_INVALID_PARAMETER; + } + + /* Total length of the needed asn1 is the tag length + * plus the current offset. */ + + needed_len = data.nesting->taglen + data.nesting->start; + asn1_free(&data); + + DEBUG(10,("check_spnego_blob_complete: needed_len = %u, " + "pblob->length = %u\n", + (unsigned int)needed_len, + (unsigned int)pblob->length )); + + if (needed_len <= pblob->length) { + /* Nothing to do - blob is complete. */ + return NT_STATUS_OK; + } + + /* We must store this blob until complete. */ + pad = SMB_MALLOC(sizeof(struct pending_auth_data)); + if (!pad) { + return NT_STATUS_NO_MEMORY; + } + pad->needed_len = needed_len - pblob->length; + pad->partial_data = data_blob(pblob->data, pblob->length); + pad->smbpid = smbpid; + pad->vuid = vuid; + DLIST_ADD(pd_list, pad); + + return NT_STATUS_MORE_PROCESSING_REQUIRED; +} + /**************************************************************************** Reply to a session setup command. conn POINTER CAN BE NULL HERE ! @@ -677,6 +854,8 @@ static int reply_sesssetup_and_X_spnego(connection_struct *conn, char *inbuf, enum remote_arch_types ra_type = get_remote_arch(); int vuid = SVAL(inbuf,smb_uid); user_struct *vuser = NULL; + NTSTATUS status = NT_STATUS_OK; + uint16 smbpid = SVAL(inbuf,smb_pid); DEBUG(3,("Doing spnego session setup\n")); @@ -715,16 +894,28 @@ static int reply_sesssetup_and_X_spnego(connection_struct *conn, char *inbuf, /* Windows 2003 doesn't set the native lanman string, but does set primary domain which is a bug I think */ - if ( !strlen(native_lanman) ) + if ( !strlen(native_lanman) ) { ra_lanman_string( primary_domain ); - else + } else { ra_lanman_string( native_lanman ); + } } vuser = get_partial_auth_user_struct(vuid); + if (!vuser) { + struct pending_auth_data *pad = get_pending_auth_data(smbpid); + if (pad) { + DEBUG(10,("reply_sesssetup_and_X_spnego: found pending vuid %u\n", + (unsigned int)pad->vuid )); + vuid = pad->vuid; + vuser = get_partial_auth_user_struct(vuid); + } + } + if (!vuser) { vuid = register_vuid(NULL, data_blob(NULL, 0), data_blob(NULL, 0), NULL); if (vuid == UID_FIELD_INVALID ) { + data_blob_free(&blob1); return ERROR_NT(nt_status_squash(NT_STATUS_INVALID_PARAMETER)); } @@ -732,11 +923,27 @@ static int reply_sesssetup_and_X_spnego(connection_struct *conn, char *inbuf, } if (!vuser) { + data_blob_free(&blob1); return ERROR_NT(nt_status_squash(NT_STATUS_INVALID_PARAMETER)); } SSVAL(outbuf,smb_uid,vuid); - + + /* Large (greater than 4k) SPNEGO blobs are split into multiple + * sessionsetup requests as the Windows limit on the security blob + * field is 4k. Bug #4400. JRA. + */ + + status = check_spnego_blob_complete(smbpid, vuid, &blob1); + if (!NT_STATUS_IS_OK(status)) { + if (!NT_STATUS_EQUAL(status, NT_STATUS_MORE_PROCESSING_REQUIRED)) { + /* Real error - kill the intermediate vuid */ + invalidate_vuid(vuid); + } + data_blob_free(&blob1); + return ERROR_NT(nt_status_squash(status)); + } + if (blob1.data[0] == ASN1_APPLICATION(0)) { /* its a negTokenTarg packet */ ret = reply_spnego_negotiate(conn, inbuf, outbuf, vuid, length, bufsize, blob1, @@ -755,25 +962,24 @@ static int reply_sesssetup_and_X_spnego(connection_struct *conn, char *inbuf, if (strncmp((char *)(blob1.data), "NTLMSSP", 7) == 0) { DATA_BLOB chal; - NTSTATUS nt_status; if (!vuser->auth_ntlmssp_state) { - nt_status = auth_ntlmssp_start(&vuser->auth_ntlmssp_state); - if (!NT_STATUS_IS_OK(nt_status)) { + status = auth_ntlmssp_start(&vuser->auth_ntlmssp_state); + if (!NT_STATUS_IS_OK(status)) { /* Kill the intermediate vuid */ invalidate_vuid(vuid); - - return ERROR_NT(nt_status_squash(nt_status)); + data_blob_free(&blob1); + return ERROR_NT(nt_status_squash(status)); } } - nt_status = auth_ntlmssp_update(vuser->auth_ntlmssp_state, + status = auth_ntlmssp_update(vuser->auth_ntlmssp_state, blob1, &chal); data_blob_free(&blob1); reply_spnego_ntlmssp(conn, inbuf, outbuf, vuid, &vuser->auth_ntlmssp_state, - &chal, nt_status, False); + &chal, status, False); data_blob_free(&chal); return -1; } -- cgit