[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 );
}