[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]

Memory leaks in PAM



(Second attempt;  first attempt was not recognized as originating
 from list member.  List moderator, please disregard previous post)


I'm writing a program that will link with libpam.  It will
have a "bool authenticate(char* name, char* password)" function
that will simply return true or false based on the results of
pam_authenticate() and pam_acct_mgmt().

I understand from reading through the pam-list archives that I
can't use pam_set_item() for the AUTHTOK, but instead need
to create a conversation function to feed the cached password
into PAM.

I started with misc_conv.c, and created a conversation function
that has the password as it's appdata_ptr.  The program works,
but leaks memory.

I've attached code that can be used to reproduce the problem.
You can use it like this:
./pamstandalone username password 10000

This will attempt to authenticate "username" with "password" 10000
times.  In order to try this, you will obviously not want to use
an account and password that you care about, since the password will
show up in a ps and in .bash_history.

If you run the program as listed above, and monitor it in top
or something, you will see it's memory footprint continually grow.
It's best to use a username and password that will return true, so
that you don't have to deal with the fail delay and thus the memory
usage will grow fast enough to be perceptible.

As my program will stay in memory and authenticate() will be called
multiple times, this memory leakage is not acceptable.

There are two places in this sample code where memory is dynamically
allocated, and not freed (at least not by me).  The first is in
authenticate() where pPasswd is created with strdup() and then
given as the second argument of the pam_conv (thus allowing it to
be passed to the conversation function as appdata_ptr).  I've tried
to free this after calling pam_end.  You can see that I free pUserName
in this manner.  If I try to free pPasswd also, the program segfaults.

The other dynamic allocation is in MY_PAM_conv where
  struct pam_response* reply
is allocated with calloc().  This also is never freed unless an
error is encountered in the conversation function.  This cannot
be freed within the conversation function, because **response is
assigned it's memory (this is how the password, among other things
is passed back to PAM).

I tried making appdata_ptr a struct { pam_response*; char*};
instead of just a char*.  This way I could have a handle on the
pam_response that is allocated from within the conversation function,
and free it once pam_end is complete.  This also resulted in a segfault
(not too surprisingly).

Is there any way this sample code could be changed so that it
does not leak memory?  Or, are the problems within PAM itself?

Thank you.


-- 
Bart
~~~~
J. Bart Whiteley  <bart@caldera.com>  (801)765-4999
Caldera International, Inc. Orem, UT, USA -- http://www.caldera.com/
/**
 *
 *
 */

#include <iostream.h>
#include <security/pam_appl.h>
#include <security/pam_misc.h>
#include <iostream.h>
#include <stdio.h>



//////////////////////////////////////////////////////////////////////////////
int
MY_PAM_conv(int num_msg, const struct pam_message **msgm,
				struct pam_response **response, void *appdata_ptr)
{
	int count=0;
	struct pam_response *reply;

	if (num_msg <= 0)
		return PAM_CONV_ERR;

	//D(("allocating empty response structure array."));

	reply = (struct pam_response *) calloc(num_msg,
														sizeof(struct pam_response));
	if (reply == NULL)
	{
		//D(("no memory for responses"));
		return PAM_CONV_ERR;
	}
	bool failed = false;

	//D(("entering conversation function."));

	for (count=0; count < num_msg; ++count)
	{
		char *string=NULL;

		if (failed == true)
		{
			break;
		}
		switch (msgm[count]->msg_style)
		{
			case PAM_PROMPT_ECHO_OFF:
				string = reinterpret_cast<char*>(appdata_ptr);
				if (string == NULL)
				{
					failed = true;
				}
				break;
				/*case PAM_PROMPT_ECHO_ON:
					string = read_string(CONV_ECHO_ON,msgm[count]->msg);
					if (string == NULL) {
						goto failed_conversation;
					}
					break;
				case PAM_ERROR_MSG:
					if (fprintf(stderr,"%s\n",msgm[count]->msg) < 0) {
						goto failed_conversation;
					}
					break;
				case PAM_TEXT_INFO:
					if (fprintf(stdout,"%s\n",msgm[count]->msg) < 0) {
						goto failed_conversation;
					}
					break;
				case PAM_BINARY_PROMPT:
					{
						void *pack_out=NULL;
						const void *pack_in = msgm[count]->msg;
	
						if (!pam_binary_handler_fn
								|| pam_binary_handler_fn(pack_in, &pack_out) != PAM_SUCCESS
								|| pack_out == NULL) {
							goto failed_conversation;
						}
						string = (char *) pack_out;
						pack_out = NULL;
	
						break;
					}*/
			default:
				fprintf(stderr, "erroneous conversation (%d)\n"
						  ,msgm[count]->msg_style);
				failed = true;
		}

		if (string)
		{								  /* must add to reply array */
			/* add string to list of responses */

			reply[count].resp_retcode = 0;
			reply[count].resp = string;
			string = NULL;
		}
	}

	/* New (0.59+) behavior is to always have a reply - this is
		compatable with the X/Open (March 1997) spec. */
	if (!failed)
	{
		*response = reply;
		reply = NULL;

	}
	else
	{
		if (reply)
		{
			for (count=0; count<num_msg; ++count)
			{
				if (reply[count].resp == NULL)
				{
					continue;
				}
				switch (msgm[count]->msg_style)
				{
					/*case PAM_PROMPT_ECHO_ON:*/
					case PAM_PROMPT_ECHO_OFF:
						_pam_overwrite(reply[count].resp);
						free(reply[count].resp);
						break;
					/*case PAM_BINARY_PROMPT:
						pam_binary_handler_free((void **) &reply[count].resp);
						break;
					case PAM_ERROR_MSG:
					case PAM_TEXT_INFO:
						// should not actually be able to get here... 
						free(reply[count].resp);*/
				} // switch
				reply[count].resp = NULL;
			} // for
			free(reply);
			reply = NULL;
		} // if (reply)
		return PAM_CONV_ERR;
	} // else
	return PAM_SUCCESS;
}
	
//////////////////////////////////////////////////////////////////////////////
bool
authenticate(const char* userName,
					const char* password)
{

	char* pPasswd = strdup(password);
	char* pUserName = strdup(userName);

	struct pam_conv conv = {
		MY_PAM_conv,
		pPasswd
	};

	pam_handle_t *pamh=NULL;
	int rval;

	rval = pam_start("kde", pUserName, &conv, &pamh);

	if (rval == PAM_SUCCESS)
		rval = pam_authenticate(pamh, 0);	 /* is user really user? */

	if (rval == PAM_SUCCESS)
		rval = pam_acct_mgmt(pamh, 0);		 /* permitted access? */

	if (rval == PAM_CONV_ERR)
	{
		pam_end(pamh, rval);
		free(pUserName);
		exit(1);
	}


	if (pam_end(pamh,rval) != PAM_SUCCESS)
	{		// close Linux-PAM 
		pamh = NULL;
		exit(1);
	}

	free(pUserName);
	return( rval == PAM_SUCCESS ? true : false );		 /* indicate success */
}


//////////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[])
{
	if (argc != 4)
	{
		cerr << "Usage: " << argv[0] << " <username> <password> <count>" << endl;
		return 1;
	}
	for(int i = 0; i < atoi(argv[3]); i++)
	{
		if (authenticate(argv[1], argv[2]))
		{
			cout << "True!" << endl;
		}
		else
		{
			cout << "False!" << endl;
		}
	}

	return 0;
}



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index] []