ldap/servers/slapd/ldaputil.c | 39 +++++++
ldap/servers/slapd/sasl_io.c | 186 +++++++++++++++++++++++++++++++++-----
ldap/servers/slapd/slapi-plugin.h | 10 ++
3 files changed, 211 insertions(+), 24 deletions(-)
New commits:
commit b4cdebbef35fe413dc4d853256536e96ff3ee313
Author: Mark Reynolds <mreynolds(a)redhat.com>
Date: Wed Jul 17 23:57:04 2013 -0400
Ticket 47416 - SASL encrypted packet length exceeds maximum allowed limit
Bug Description: If an error occurs while establishing the secure layer during
a bind operation, the client will send an unbind request that
is not encrypted. The sasl code expects encrypted values
and it incorrectly parses the message for its length, but since
it's not encrypted the length is wrong, and exceeds the saslio
limit.
Fix Description: Check if there is data in the encrypted buffer before the secure
layer is established. Check if the first byte in the buffer is an
LDAP_TAG_MESSAGE. Then we get the legnth of the LDAP message, and
compare
it to maxbersize. Then attmept to read in the rest of the
packet(if necessary). If we fail to read in the LDAP msg length,
then return PR_WOULD_BLOCK_ERROR. Otherwise parse the berElement,
and
check if it is an UNBIND operation. If it is then copy it to the
socket
buffer, and let the server process the operation. Any other valid
LDAP operation is still treated as an error.
https://fedorahosted.org/389/ticket/47416
Reviewed by: richm (Thanks!!!)
diff --git a/ldap/servers/slapd/ldaputil.c b/ldap/servers/slapd/ldaputil.c
index f753570..ae07c44 100644
--- a/ldap/servers/slapd/ldaputil.c
+++ b/ldap/servers/slapd/ldaputil.c
@@ -2331,3 +2331,42 @@ slapi_is_ipv6_addr( const char *hostname ){
}
return 0;
}
+
+/*
+ * Get the length of the ber-encoded ldap message. Note, only the length of
+ * the LDAP operation is returned, not the length of the entire berval.
+ * Add 2 to the length for the entire PDU size. If "strict" is set then
+ * the entire LDAP PDU must be in the berval.
+ */
+ber_len_t
+slapi_berval_get_msg_len(struct berval *bv, int strict)
+{
+ ber_len_t len, rest;
+ unsigned char *ptr;
+ int i;
+
+ /* Get the ldap operation length */
+ rest = bv->bv_len - 1;
+ ptr = (unsigned char *)bv->bv_val ;
+ ptr++; /* skip the tag and get right to the length */
+ len = *ptr++;
+ rest--;
+
+ if ( len & 0x80U ) {
+ len &= 0x7fU;
+ if ( len - 1U > sizeof(ber_len_t) - 1U || rest < len ) {
+ /* Indefinite-length/too long length/not enough data */
+ return -1;
+ }
+ rest -= len;
+ i = len;
+ for( len = *ptr++ & 0xffU; --i; len |= *ptr++ & 0xffU ) {
+ len <<= 8;
+ }
+ }
+ if( strict && len > rest ) {
+ return -1;
+ }
+
+ return len;
+}
diff --git a/ldap/servers/slapd/sasl_io.c b/ldap/servers/slapd/sasl_io.c
index 52d6506..de9bea6 100644
--- a/ldap/servers/slapd/sasl_io.c
+++ b/ldap/servers/slapd/sasl_io.c
@@ -52,7 +52,9 @@
*/
#define SASL_IO_BUFFER_SIZE 1024
-
+#define SASL_IO_BUFFER_NOT_ENCRYPTED -99
+#define SASL_IO_BUFFER_START_SIZE 7
+
/*
* SASL sends its encrypted PDU's with an embedded 4-byte length
* at the beginning (in network byte order). We peek inside the
@@ -204,56 +206,184 @@ sasl_get_io_private(PRFileDesc *fd)
static PRInt32
sasl_io_start_packet(PRFileDesc *fd, PRIntn flags, PRIntervalTime timeout, PRInt32 *err)
{
- PRInt32 ret = 0;
- unsigned char buffer[sizeof(PRInt32)];
- size_t packet_length = 0;
- size_t saslio_limit;
+ unsigned char buffer[SASL_IO_BUFFER_START_SIZE];
sasl_io_private *sp = sasl_get_io_private(fd);
Connection *c = sp->conn;
PRInt32 amount = sizeof(buffer);
+ PRInt32 ret = 0;
+ size_t packet_length = 0;
+ size_t saslio_limit;
*err = 0;
debug_print_layers(fd);
- amount -= sp->encrypted_buffer_offset;
/* first we need the length bytes */
ret = PR_Recv(fd->lower, buffer, amount, flags, timeout);
LDAPDebug( LDAP_DEBUG_CONNS,
- "read sasl packet length returned %d on connection %" NSPRIu64
"\n", ret, c->c_connid, 0 );
+ "sasl_io_start_packet: read sasl packet length returned %d on connection
%" NSPRIu64 "\n",
+ ret, c->c_connid, 0 );
if (ret <= 0) {
*err = PR_GetError();
if (ret == 0) {
LDAPDebug1Arg( LDAP_DEBUG_CONNS,
- "sasl_io_start_packet: connection closed while reading sasl
packet length on connection %" NSPRIu64 "\n", c->c_connid );
+ "sasl_io_start_packet: connection closed while reading sasl
packet length on connection %" NSPRIu64 "\n",
+ c->c_connid );
} else {
LDAPDebug( LDAP_DEBUG_CONNS,
- "sasl_io_start_packet: error reading sasl packet length on
connection %" NSPRIu64 " %d:%s\n", c->c_connid, *err,
slapd_pr_strerror(*err) );
+ "sasl_io_start_packet: error reading sasl packet length on
connection %" NSPRIu64 " %d:%s\n",
+ c->c_connid, *err, slapd_pr_strerror(*err) );
}
return ret;
}
/*
* Read the bytes and add them to sp->encrypted_buffer
- * - if offset < 4, tell caller we didn't read enough bytes yet
- * - if offset >= 4, decode the length and proceed.
+ * - if offset < 7, tell caller we didn't read enough bytes yet
+ * - if offset >= 7, decode the length and proceed.
*/
- if (ret < sizeof(buffer)) {
- memcpy(sp->encrypted_buffer + sp->encrypted_buffer_offset, buffer, ret);
- sp->encrypted_buffer_offset += ret;
- if (sp->encrypted_buffer_offset < sizeof(buffer)) {
- LDAPDebug2Args( LDAP_DEBUG_CONNS,
- "sasl_io_start_packet: read only %d bytes of sasl packet "
- "length on connection %" NSPRIu64 "\n", ret,
c->c_connid );
+ if((ret + sp->encrypted_buffer_offset) > sp->encrypted_buffer_size){
+ sasl_io_resize_encrypted_buffer(sp, ret + sp->encrypted_buffer_offset);
+ }
+ memcpy(sp->encrypted_buffer + sp->encrypted_buffer_offset, buffer, ret);
+ sp->encrypted_buffer_offset += ret;
+ if (sp->encrypted_buffer_offset < sizeof(buffer)) {
+ LDAPDebug2Args( LDAP_DEBUG_CONNS,
+ "sasl_io_start_packet: read only %d bytes of sasl packet "
+ "length on connection %" NSPRIu64 "\n", ret,
c->c_connid );
#if defined(EWOULDBLOCK)
- errno = EWOULDBLOCK;
+ errno = EWOULDBLOCK;
#elif defined(EAGAIN)
- errno = EAGAIN;
+ errno = EAGAIN;
#endif
- PR_SetError(PR_WOULD_BLOCK_ERROR, errno);
+ PR_SetError(PR_WOULD_BLOCK_ERROR, errno);
+ return PR_FAILURE;
+ }
+
+ /*
+ * Check if an LDAP operation was sent unencrypted
+ */
+ if(!sp->send_encrypted && *sp->encrypted_buffer == LDAP_TAG_MESSAGE){
+ struct berval bv, tmp_bv;
+ BerElement *ber = NULL;
+ ber_len_t maxbersize = config_get_maxbersize();
+ ber_len_t ber_len = 0;
+ ber_tag_t tag;
+
+ slapi_log_error( SLAPI_LOG_CONNS, "sasl_io_start_packet",
"conn=%" NSPRIu64 " fd=%d "
+ "Sent an LDAP message that was not encrypted.\n",
c->c_connid, c->c_sd);
+
+ /* Build a berval so we can get the length before reading in the entire packet
*/
+ bv.bv_val = sp->encrypted_buffer;
+ bv.bv_len = sp->encrypted_buffer_offset;
+ if((ber_len = slapi_berval_get_msg_len(&bv, 0)) == -1){
+ goto done;
+ }
+
+ /* Is the ldap operation too large? */
+ if(ber_len > maxbersize){
+ slapi_log_error( SLAPI_LOG_FATAL, "connection",
+ "conn=%" NSPRIu64 " fd=%d Incoming BER Element was too
long, max allowable "
+ "is %" BERLEN_T " bytes. Change the nsslapd-maxbersize
attribute in "
+ "cn=config to increase.\n",
+ c->c_connid, c->c_sd, maxbersize );
+ PR_SetError(PR_IO_ERROR, 0);
return PR_FAILURE;
}
- } else {
- memcpy(sp->encrypted_buffer, buffer, sizeof(buffer));
- sp->encrypted_buffer_offset = sizeof(buffer);
+ /*
+ * Bump the ber length by 2 for the tag/length we skipped over when calculating
the berval length.
+ * We now have the total "packet" size, so we know exactly what is left
to read in.
+ */
+ ber_len += 2;
+
+ /*
+ * Read in the rest of the packet.
+ *
+ * sp->encrypted_buffer_offset is the total number of bytes that have been
written
+ * to the buffer. Once we have the complete LDAP packet we'll set it back to
zero,
+ * and adjust the sp->encrypted_buffer_count.
+ */
+ while(sp->encrypted_buffer_offset < ber_len){
+ unsigned char mybuf[SASL_IO_BUFFER_SIZE];
+
+ ret = PR_Recv(fd->lower, mybuf, SASL_IO_BUFFER_SIZE, flags, timeout);
+ if (ret == PR_WOULD_BLOCK_ERROR || (ret == 0 &&
sp->encrypted_buffer_offset < ber_len)){
+ /*
+ * Need more data, go back and try to get more data from
connection_read_operation()
+ * We can return and continue to update sp->encrypted_buffer because
we have
+ * maintained the current size in encrypted_buffer_offset.
+ */
+#if defined(EWOULDBLOCK)
+ errno = EWOULDBLOCK;
+#elif defined(EAGAIN)
+ errno = EAGAIN;
+#endif
+ PR_SetError(PR_WOULD_BLOCK_ERROR, errno);
+ return PR_FAILURE;
+ } else if (ret > 0) {
+ LDAPDebug( LDAP_DEBUG_CONNS,
+ "Continued: read sasl packet length returned %d on
connection %" NSPRIu64 "\n",
+ ret, c->c_connid, 0 );
+ if((ret + sp->encrypted_buffer_offset) >
sp->encrypted_buffer_size){
+ sasl_io_resize_encrypted_buffer(sp, ret +
sp->encrypted_buffer_offset);
+ }
+ memcpy(sp->encrypted_buffer + sp->encrypted_buffer_offset, mybuf,
ret );
+ sp->encrypted_buffer_offset += ret;
+ } else if (ret < 0){
+ *err = PR_GetError();
+ LDAPDebug( LDAP_DEBUG_CONNS, "sasl_io_start_packet: error reading
sasl packet length on connection "
+ "%" NSPRIu64 " %d:%s\n", c->c_connid,
*err, slapd_pr_strerror(*err) );
+ return ret;
+ }
+ }
+
+ /*
+ * Reset the berval with the updated buffer, and create the berElement
+ */
+ bv.bv_val = sp->encrypted_buffer;
+ bv.bv_len = sp->encrypted_buffer_offset;
+ if ( (ber = ber_init(&bv)) == NULL){
+ goto done;
+ }
+
+ /*
+ * Start parsing the berElement. First skip this tag, and move on to the
+ * tag msgid
+ */
+ ber_skip_tag(ber, &ber_len);
+ if( ber_peek_tag( ber, &ber_len ) == LDAP_TAG_MSGID) {
+ /*
+ * Skip the entire msgid element, so we can get to the LDAP op tag
+ */
+ if(ber_skip_element(ber, &tmp_bv) == LDAP_TAG_MSGID) {
+ /*
+ * We only allow unbind operations to be processed for unencrypted
operations
+ */
+ if (( tag = ber_peek_tag( ber, &ber_len )) == LDAP_REQ_UNBIND ) {
+ slapi_log_error( SLAPI_LOG_CONNS, "sasl_io_start_packet",
"conn=%" NSPRIu64 " fd=%d "
+ "Received unencrypted UNBIND operation.\n",
c->c_connid, c->c_sd);
+ sp->encrypted_buffer_count = sp->encrypted_buffer_offset;
+ sp->encrypted_buffer_offset = 0;
+ ber_free(ber, 1);
+ return SASL_IO_BUFFER_NOT_ENCRYPTED;
+ }
+ slapi_log_error( SLAPI_LOG_CONNS, "sasl_io_start_packet",
"conn=%" NSPRIu64 " fd=%d "
+ "Error: received an LDAP message (tag 0x%lx) that was not
encrypted.\n",
+ c->c_connid, c->c_sd, tag);
+ }
+ }
+
+done:
+ /* If we got here we have garbage, or a denied LDAP operation */
+ slapi_log_error( SLAPI_LOG_CONNS, "sasl_io_start_packet",
"conn=%" NSPRIu64 " fd=%d "
+ "Error: received an invalid message that was not
encrypted.\n",
+ c->c_connid, c->c_sd);
+
+ if (NULL != ber){
+ ber_free(ber, 1);
+ }
+ PR_SetError(PR_IO_ERROR, 0);
+
+ return PR_FAILURE;
}
+
/* At this point, sp->encrypted_buffer_offset == sizeof(buffer) */
/* Decode the length */
packet_length = ntohl(*(uint32_t *)sp->encrypted_buffer);
@@ -334,6 +464,14 @@ sasl_io_recv(PRFileDesc *fd, void *buf, PRInt32 len, PRIntn flags,
if (!sasl_io_reading_packet(sp)) {
/* First read the packet length and so on */
ret = sasl_io_start_packet(fd, flags, timeout, &err);
+ if (SASL_IO_BUFFER_NOT_ENCRYPTED == ret) {
+ /*
+ * Special case: we received unencrypted data that was actually
+ * an unbind. Copy it to the buffer and return its length.
+ */
+ memcpy(buf, sp->encrypted_buffer, sp->encrypted_buffer_count);
+ return sp->encrypted_buffer_count;
+ }
if (0 >= ret) {
/* timeout, connection closed, or error */
return ret;
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
index d548baf..a22ef29 100644
--- a/ldap/servers/slapd/slapi-plugin.h
+++ b/ldap/servers/slapd/slapi-plugin.h
@@ -2998,6 +2998,16 @@ void slapi_rdn_free(Slapi_RDN **rdn);
int slapi_is_ipv6_addr( const char *ipAddress);
/**
+ * Returns the length of a ber-encoded ldap operation
+ *
+ * \param bv is the berval of the encoded ldap operation
+ * \param strict set to 0 if "bv" does not contain the entire LDAP encoding
+ * \return -1 if the length can not be determined
+ * \return length
+ */
+ber_len_t slapi_berval_get_msg_len(struct berval *bv, int strict);
+
+/**
* Frees and clears the contents of a \c Slapi_RDN structure from memory.
*
* Both the RDN value and the array of split RDNs are freed. Those pointers