[Date Prev][Date Next] [Chronological] [Thread] [Top]

SASL bind memory bug



Hello, I have a memory bug to report. We are using openldap client side
libraries, binding to openldap server using SASL, GSSAPI Kerberos 5.

Client environment:
SunOS zver.Stanford.EDU 5.8 Generic_108528-17 sun4u sparc SUNW,Ultra-5_10
openldap-2.1.20
cyrus-sasl-2.1.13
krb5-1.2.8

In long-running client services we like to renew the kerberos ticket on
the fly, as a failback from "Local Error". So the logic is:

ldap_init
rc = ldap_sasl_interactive_bind_s
if (rc == LDAP_LOCAL_ERROR) // means TGT expired or missing
then
    [krb5 code to get new TGT]
    ldap_unbind                 <------- memory error, memory freed twice
    ldap_sasl_interactive_bind_s // try again

The problem is that during the first ldap_sasl_interactive_bind_s, when
the LDAP_LOCAL_ERROR is generated, ldap_free_connection is called, and as
you can see from the purify trace below, that eventually enters sasl code
and frees ld->ld_error, but it is never set to NULL afterwards. Then
ldap_unbind goes thru and tries to free ld->ld_error AGAIN, since it's
not NULL, and that's a double free(), folks.

I attach a small program that was used to generate the following Purify
output, illustrating the double bind problem. The test program needs
libldif.a and ldap include files. It was built like so:

gcc -g -I/usr/local/include -I./ -I/usr/local/openldap-2.1.20/include -c
-o ldap.o ldap.c
purify gcc -g -o ldap ldap.o \
-R/usr/local/lib \
libldif.a \
-lresolv \
-lsasl2 \
-lkrb4 -ldes425 -lkrb5 -lk5crypto -lcom_err \
-lssl \
-lcrypto \
-lnsl -lsocket \
-lldap -llber

Purify output:

      FUM: Freeing unallocated memory
      This is occurring while in:
            free           [rtlib.o]
            ber_memfree    [memory.c:143]
                               memset( mh, 0xff, mh->bm_length +
sizeof(struct ber_mem_hdr) + sizeof(ber_int_t));
                               free( mh );
               #else
            =>                 free( p );
               #endif
                               return;
                       }
            ldap_ld_free   [unbind.c:98]
                       if ( ld->ld_error != NULL ) {
            =>                 LDAP_FREE( ld->ld_error );
                               ld->ld_error = NULL;
                       }

            ldap_unbind_ext [unbind.c:46]
                       rc = ldap_int_client_controls( ld, cctrls );
                       if( rc != LDAP_SUCCESS ) return rc;

            =>         return ldap_ld_free( ld, 1, sctrls, cctrls );
               }

               int
            ldap_unbind    [unbind.c:67]
                       Debug( LDAP_DEBUG_TRACE, "ldap_unbind\n", 0, 0, 0
);
               #endif

            =>         return( ldap_unbind_ext( ld, NULL, NULL ) );
               }

            main           [ldap.c:350]
                       }
               fprintf( stderr, "second ldap_init:\n");

            => ldap_unbind( ld );
               ld = NULL;
      Attempting to free block at 0xaf2a0 already freed.
      This block was allocated from:
            malloc         [rtlib.o]
            _buf_alloc     [libsasl2.so.2.0.13]
            _sasl_conn_init [libsasl2.so.2.0.13]
            sasl_client_new [libsasl2.so.2.0.13]
            ldap_int_sasl_open [cyrus.c:477]
            ldap_int_open_connection [open.c:348]
      There have been 8 frees since this block was freed from:
            free           [rtlib.o]
            _sasl_conn_dispose [libsasl2.so.2.0.13]
            client_dispose [libsasl2.so.2.0.13]
            sasl_dispose   [libsasl2.so.2.0.13]
            ldap_int_sasl_close [cyrus.c:507]
            ldap_free_connection [request.c:472]

Please let me know if you need more info on this. Our current workaround
is to not unbind at all, avoiding the double free but leaking 500 bytes a
pop.

thank you for looking at this

-anton

---
Anton Ushakov
Infrastructure Services, ITSS, Stanford University
#+--+#+--+#+--+#+--+#+--+#+--+#+--+#+--+#+--+#+--+#+--+#+--+#+--+#

#include "portable.h"

#include <stdio.h>

#include <ac/stdlib.h>

#include <ac/ctype.h>
#include <ac/signal.h>
#include <ac/string.h>
#include <ac/unistd.h>
#include <ac/errno.h>
#include <sys/stat.h>

#include <krb5.h>

#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_IO_H
#include <io.h>
#endif

#include <ldap.h>

#include "ldif.h"
#include "lutil.h"
#include "lutil_ldap.h"
#include "ldap_defaults.h"
#include "ldap_log.h"

static char *def_tmpdir;
static char *def_urlpre;

static void print_entry LDAP_P((
        LDAP    *ld,
        LDAPMessage     *entry,
        int             attrsonly));

static int write_ldif LDAP_P((
        int type,
        char *name,
        char *value,
        ber_len_t vallen ));


static int dosearch LDAP_P((
        LDAP    *ld,
        char    *base,
        int             scope,
        char    *value,
        char    **attrs,
        int             attrsonly,
        int     sizelimit ));

static char *tmpdir = NULL;
static char *urlpre = NULL;
static char *prog = NULL;
static char     *binddn = NULL;
static struct berval passwd = { 0, NULL };
static char     *base = NULL;
static char     *ldaphost = NULL;
static char *ldapuri = NULL;
static int      ldapport = 0;
#ifdef HAVE_CYRUS_SASL
static unsigned sasl_flags = LDAP_SASL_AUTOMATIC;
/*
static char     *sasl_realm = NULL;
static char     *sasl_authc_id = NULL;
static char     *sasl_authz_id = NULL;
static char     *sasl_mech = NULL;
static char     *sasl_secprops = NULL;
*/
#endif
static int      use_tls = 0;
static char     *sortattr = NULL;
static int      verbose, not, includeufn, vals2tmp, ldif;

typedef struct sasl_defaults {
    char *mech;
    char *realm;
    char *authcid;
    char *passwd;
    char *authzid;
} sasl_defaults;

int sasl_interact_stub(LDAP *ld,
                       unsigned flags,
                       void *defaults,
                       void *in )
{
    // we are using sasl_flags = LDAP_SASL_AUTOMATIC;
    // therefore this should never be called, but just in case
    return LDAP_SUCCESS;
}

static void
urlize(char *url)
{
        char *p;

        if (*LDAP_DIRSEP != '/') {
                for (p = url; *p; p++) {
                        if (*p == *LDAP_DIRSEP)
                                *p = '/';
                }
        }
}

int 
webauthldap_get_ticket(char* keytabfile, char* principal)
{
    krb5_context ctx;
    krb5_creds creds;
    krb5_kt_cursor cursor;
    krb5_keytab_entry entry;
    krb5_get_init_creds_opt opts;
    krb5_keytab keytab;
    krb5_ccache cc;
    krb5_principal princ;
    krb5_error_code code;
    krb5_error_code tcode;

    // initialize the main struct that holds kerberos context
    if ((code = krb5_init_context(&ctx)) != 0)
        return code;

    // locate, open, and read the keytab
    if ((code = krb5_kt_resolve(ctx, keytabfile, &keytab)) != 0)
        return code;

    // if the principal has been specified via directives, use it, 
    // otherwise just read the first entry out of the keytab.
    if (principal) {
        code = krb5_parse_name(ctx, principal, &princ);
    } else {
        if ((code = krb5_kt_start_seq_get(ctx, keytab, &cursor)) != 0) {
            tcode = krb5_kt_close(ctx, keytab);
            return code;
        }

        if ((code = krb5_kt_next_entry(ctx, keytab, &entry, &cursor)) == 0) {
            code = krb5_copy_principal(ctx, entry.principal, &princ);
            tcode = krb5_kt_free_entry(ctx, &entry);
        }
        tcode = krb5_kt_end_seq_get(ctx, keytab, &cursor);
    }

    if (code != 0) {
        tcode = krb5_kt_close(ctx, keytab);
        krb5_free_principal(ctx, princ);
        return code;
    }

    // locate and open the creadentials cache file
    if ((code = krb5_cc_resolve(ctx, "/tmp/ldap.tkt", &cc)) != 0) {
       krb5_kt_close(ctx, keytab);
       krb5_free_principal(ctx, princ);
       return code;
    }
    
    // initialize it if necessary
    if ((code != krb5_cc_initialize(ctx, cc, princ)) != 0) {
        krb5_kt_close(ctx, keytab);
        krb5_free_principal(ctx, princ);
        return code;
    }
    
    krb5_get_init_creds_opt_init(&opts);

    // get the tgt for this principal
    code = krb5_get_init_creds_keytab(ctx,
                                      &creds,
                                      princ,
                                      keytab,
                                      0, /* start_time */
                                      NULL, /* in_tkt_service */
                                      &opts);

    krb5_kt_close(ctx, keytab);
    krb5_free_principal(ctx, princ);

    if (code == 0) {
        /* add the creds to the cache */
        code = krb5_cc_store_cred(ctx, cc, &creds);
        krb5_free_cred_contents(ctx, &creds);
        krb5_cc_close(ctx, cc);
    }

    krb5_free_context(ctx);

    return code;
}


int
main( int argc, char **argv )
{
        char            *infile, *filtpattern, line[BUFSIZ];
        FILE            *fp = NULL;
        int                     rc, i, first, scope, deref, attrsonly, manageDSAit, noop, crit;
        int                     referrals, timelimit, sizelimit, debug;
        int             authmethod, version, want_bindpw;
        LDAP            *ld = NULL;
        int             valuesReturnFilter;
        BerElement      *ber = NULL;
        struct berval   *bvalp = NULL;
        char    *vrFilter  = NULL, *control = NULL, *cvalue;
        char    *pw_file = NULL;
        sasl_defaults *defaults;
	char ** attrs = NULL; 
    struct stat keytab_stat;
	int fd;
	int princ_specified;
    char* keytabfile;
    char* principal;

        infile = NULL;
        debug = verbose = not = vals2tmp = referrals = valuesReturnFilter =
                attrsonly = manageDSAit = noop = ldif = want_bindpw = 0;

scope = LDAP_SCOPE_SUBTREE;
ldaphost = strdup("ldap-test1.stanford.edu");
base = strdup("cn=accounts,dc=stanford,dc=edu");
authmethod = LDAP_AUTH_SASL;
version = LDAP_VERSION3;
filtpattern = "uid=antonu";
debug = 0;
keytabfile = "/afs/ir/users/a/n/antonu/projects/webauthldaptest.keytab";
principal = "service/webauthldaptest";

 ber_set_option( NULL, LBER_OPT_DEBUG_LEVEL, &debug ) ;
 ldap_set_option( NULL, LDAP_OPT_DEBUG_LEVEL, &debug ) ;

ld = ldap_init( ldaphost, ldapport );
if (ld == NULL) {
	fprintf( stderr, "ldap_init returned NULL\n");
	return -1;
}

        /* referrals */
        if (ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF) 
                != LDAP_OPT_SUCCESS)
        {
                fprintf( stderr, "Could not set LDAP_OPT_REFERRALS %s\n",
                        referrals ? "on" : "off" );
                return EXIT_FAILURE;
        }

        if( ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version )
                != LDAP_OPT_SUCCESS )
        {
                fprintf( stderr, "Could not set LDAP_OPT_PROTOCOL_VERSION %d\n",
                        version );
                return EXIT_FAILURE;
        }

        if ( use_tls && ( ldap_start_tls_s( ld, NULL, NULL ) != LDAP_SUCCESS )) 
{
                ldap_perror( ld, "ldap_start_tls" );
                if ( use_tls > 1 ) {
                        return EXIT_FAILURE;
                }
        }



		defaults = ber_memalloc( sizeof( sasl_defaults ) );
                bzero(defaults, sizeof( sasl_defaults ));
		if (defaults != NULL) {
		ldap_get_option( ld, LDAP_OPT_X_SASL_MECH, &defaults->mech );
    		ldap_get_option( ld, LDAP_OPT_X_SASL_REALM, &defaults->realm );
    		ldap_get_option( ld, LDAP_OPT_X_SASL_AUTHCID, &defaults->authcid );
    		ldap_get_option( ld, LDAP_OPT_X_SASL_AUTHZID, &defaults->authzid );

		}

		defaults->mech = "GSSAPI";

    // since SASL will look there, lets put the ticket location into env
    if (putenv("KRB5CCNAME=/tmp/ldap.tkt") != 0) {
	fprintf(stderr, 
          "webauthldap: cannot set ticket cache env var\n");
        return -1;
    }
		fprintf(stderr,
                     "webauthldap: set ticket to /tmp/ldap.tkt\n");

                rc = ldap_sasl_interactive_bind_s( ld, binddn,
                        defaults->mech, NULL, NULL,
                        sasl_flags, sasl_interact_stub, defaults );

		if (defaults->authcid != NULL) {
			ldap_memfree (defaults->authcid);
			defaults->authcid = NULL;
		}

    // this likely means the ticket is missing or expired
    if (rc == LDAP_LOCAL_ERROR) {

        fprintf( stderr, "webauthldap: getting new ticket\n");

        // so let's get a new ticket
        if (stat(keytabfile, &keytab_stat) < 0) {
		fprintf(stderr, 
                         "webauthldap: cannot stat the keytab %s: %s (%d)\n",
                         keytabfile, strerror(errno), errno);
            return -1;
        }

        if ((fd = open(keytabfile, O_RDONLY, 0)) < 0) {
		fprintf(stderr,
                         "webauthldap: cannot read the keytab %s: %s (%d)\n", 
                         keytabfile, 
                         strerror(errno), errno);
            close(fd);
            return -1;
        }
        close(fd);

        princ_specified = principal? 1:0;

        rc = webauthldap_get_ticket(keytabfile, principal);

        if (rc == KRB5_REALM_CANT_RESOLVE) {
            if (princ_specified)
			fprintf( stderr,   
                             "webauthldap(%s): cannot get ticket: %s %s",
                             "check if the keytab", 
                             keytabfile,
                             "is valid for the specified principal\n");
            else
		fprintf( stderr,   
                             "webauthldap(%s): cannot get ticket: %s %s",
                             "check if the keytab", 
                             keytabfile,
                             "is valid and only contains the right principal\n");

            return -1;
        } if (rc != 0) {
             fprintf( stderr,   "webauthldap: cannot get ticket: %s (%d)\n", 
                         error_message(rc), rc);
            return -1;
        }
fprintf( stderr, "second ldap_init:\n");

ldap_unbind( ld );
ld = NULL;
ld = ldap_init( ldaphost, ldapport );
if (ld == NULL) {
        fprintf( stderr, "second ldap_init returned NULL\n");
        return -1;
}

        if (ldap_set_option(ld, LDAP_OPT_REFERRALS, LDAP_OPT_OFF)
                != LDAP_OPT_SUCCESS)
        {
                fprintf( stderr, "Could not set LDAP_OPT_REFERRALS %s\n",
                        referrals ? "on" : "off" );
                return EXIT_FAILURE;
        }
                                                                                
        if( ldap_set_option( ld, LDAP_OPT_PROTOCOL_VERSION, &version )
                != LDAP_OPT_SUCCESS )
        {
                fprintf( stderr, "Could not set LDAP_OPT_PROTOCOL_VERSION %d\n",                        version );
                return EXIT_FAILURE;
        }
                                                                                
        if ( use_tls && ( ldap_start_tls_s( ld, NULL, NULL ) != LDAP_SUCCESS )) {
                ldap_perror( ld, "ldap_start_tls" );
                if ( use_tls > 1 ) {
                        return EXIT_FAILURE;
                }
        }

/*
		ldap_memfree (defaults);
                defaults = ber_memalloc( sizeof( sasl_defaults ) );
                bzero(defaults, sizeof( sasl_defaults ));
                if (defaults != NULL) {
                ldap_get_option( ld, LDAP_OPT_X_SASL_MECH, &defaults->mech );
                ldap_get_option( ld, LDAP_OPT_X_SASL_REALM, &defaults->realm );
                ldap_get_option( ld, LDAP_OPT_X_SASL_AUTHCID, &defaults->authcid );
                ldap_get_option( ld, LDAP_OPT_X_SASL_AUTHZID, &defaults->authzid );
                                                                                    
                }
                defaults->mech = "GSSAPI";
*/

        // and try to bind one more time
                rc = ldap_sasl_interactive_bind_s( ld, binddn,
                        defaults->mech, NULL, NULL,
                        sasl_flags, sasl_interact_stub, defaults );

        if (defaults->authcid != NULL)
            ldap_memfree (defaults->authcid);

    } else {
		fprintf( stderr,
                         "webauthldap: using existing ticket\n");
    }

		ldap_memfree (defaults);

                if( rc != LDAP_SUCCESS ) {
                fprintf( stderr,
                         "webauthldap: second bind failed.\n");
                        ldap_perror( ld, "ldap_sasl_interactive_bind_s" );
                        return( EXIT_FAILURE );
                }

                rc = dosearch( ld, base, scope, filtpattern,
                        attrs, attrsonly, -1 );

        ldap_unbind( ld );
        return( rc );
}

static int dosearch(
        LDAP    *ld,
        char    *base,
        int             scope,
        char    *value,
        char    **attrs,
        int             attrsonly,
        int sizelimit )
{
        char                    *filter;
        int                     rc;
        int                     nresponses;
        int                     nentries;
        int                     nreferences;
        int                     nextended;
        int                     npartial;
        LDAPMessage             *res, *msg;
        ber_int_t       msgid;

                filter = value;

        rc = ldap_search_ext( ld, base, scope, filter, attrs, attrsonly,
                NULL, NULL, NULL, sizelimit, &msgid );

        if( rc != LDAP_SUCCESS ) {
                fprintf( stderr, "%s: ldap_search_ext: %s (%d)\n",
                        prog, ldap_err2string( rc ), rc );
                return( rc );
        }

        nresponses = nentries = nreferences = nextended = npartial = 0;

        res = NULL;
        rc = ldap_result( ld, LDAP_RES_ANY,
                LDAP_MSG_ALL,
                NULL, &res );
        if (rc > 0) { 

                for ( msg = ldap_first_message( ld, res );
                        msg != NULL;
                        msg = ldap_next_message( ld, msg ) )
                {
                        if( nresponses++ ) putchar('\n');

                        switch( ldap_msgtype( msg ) ) {
                        case LDAP_RES_SEARCH_ENTRY:
                                nentries++;
                                print_entry( ld, msg, attrsonly );
                                break;

                       case LDAP_RES_SEARCH_RESULT:
				break;

                        }
                }

                ldap_msgfree( res );
        }

        if ( rc == -1 ) {
                ldap_perror( ld, "ldap_result" );
                return( rc );
        }

done:
        if ( ldif < 2 ) {
                printf( "\n# numResponses: %d\n", nresponses );
                if( nentries ) printf( "# numEntries: %d\n", nentries );
;
        }

        return( rc );
}


static void
print_entry(
        LDAP    *ld,
        LDAPMessage     *entry,
        int             attrsonly)
{
        char            *a, *dn, *ufn;
        char    tmpfname[ 256 ];
        char    url[ 256 ];
        int                     i, rc;
        BerElement              *ber = NULL;
        struct berval   **bvals;
        LDAPControl **ctrls = NULL;
        FILE            *tmpfp;

        dn = ldap_get_dn( ld, entry );
        ufn = NULL;

        if ( ldif < 2 ) {
                ufn = ldap_dn2ufn( dn );
                write_ldif( LDIF_PUT_COMMENT, NULL, ufn, ufn ? strlen( ufn ) : 0 );
        }
        write_ldif( LDIF_PUT_VALUE, "dn", dn, dn ? strlen( dn ) : 0);

        rc = ldap_get_entry_controls( ld, entry, &ctrls );

        if( rc != LDAP_SUCCESS ) {
                fprintf(stderr, "print_entry: %d\n", rc );
                ldap_perror( ld, "ldap_get_entry_controls" );
                exit( EXIT_FAILURE );
        }

        if ( includeufn ) {
                if( ufn == NULL ) {
                        ufn = ldap_dn2ufn( dn );
                }
                write_ldif( LDIF_PUT_VALUE, "ufn", ufn, ufn ? strlen( ufn ) : 0 );
        }

        if( ufn != NULL ) ldap_memfree( ufn );
        ldap_memfree( dn );

        for ( a = ldap_first_attribute( ld, entry, &ber ); a != NULL;
                a = ldap_next_attribute( ld, entry, ber ) )
        {
                if ( attrsonly ) {
                        write_ldif( LDIF_PUT_NOVALUE, a, NULL, 0 );

                } else if (( bvals = ldap_get_values_len( ld, entry, a )) != NULL ) {
                        for ( i = 0; bvals[i] != NULL; i++ ) {
                                if ( vals2tmp > 1 || ( vals2tmp
                                        && ldif_is_not_printable( bvals[i]->bv_val, bvals[i]->bv_len ) ))
                                {
                                        int tmpfd;
                                        /* write value to file */
                                        snprintf( tmpfname, sizeof tmpfname,
                                                "%s" LDAP_DIRSEP "ldapsearch-%s-XXXXXX",
                                                tmpdir, a );
                                        tmpfp = NULL;

                                        tmpfd = mkstemp( tmpfname );

                                        if ( tmpfd < 0  ) {
                                                perror( tmpfname );
                                                continue;
                                        }

                                        if (( tmpfp = fdopen( tmpfd, "w")) == NULL ) {
                                                perror( tmpfname );
                                                continue;
                                        }

                                        if ( fwrite( bvals[ i ]->bv_val,
                                                bvals[ i ]->bv_len, 1, tmpfp ) == 0 )
                                        {
                                                perror( tmpfname );
                                                fclose( tmpfp );
                                                continue;
                                        }

                                        fclose( tmpfp );

                                        snprintf( url, sizeof url, "%s%s", urlpre,
                                                &tmpfname[strlen(tmpdir) + sizeof(LDAP_DIRSEP) - 1] );

                                        urlize( url );
                                        write_ldif( LDIF_PUT_URL, a, url, strlen( url ));

                                } else {
                                        write_ldif( LDIF_PUT_VALUE, a,
                                                bvals[ i ]->bv_val, bvals[ i ]->bv_len );
                                }
                        }
                        ber_bvecfree( bvals );
                }
            ldap_memfree(a);
        }

        if( ber != NULL ) {
                ber_free( ber, 0 );
        }
}

static int
write_ldif( int type, char *name, char *value, ber_len_t vallen )
{
        char    *ldif;

        if (( ldif = ldif_put( type, name, value, vallen )) == NULL ) {
                return( -1 );
        }

        fputs( ldif, stdout );
        ber_memfree( ldif );

        return( 0 );
}