bin/115946: [libpam] not thread-safe

Oleg Oleg.Dolgov at gmail.com
Thu Aug 30 08:40:02 PDT 2007


>Number:         115946
>Category:       bin
>Synopsis:       [libpam] not thread-safe
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Thu Aug 30 15:40:01 GMT 2007
>Closed-Date:
>Last-Modified:
>Originator:     Oleg
>Release:        6.2-RELEASE 7-CURRENT
>Organization:
Sunbay
>Environment:
FreeBSD netsnapper.domain.com 6.2-RELEASE FreeBSD 6.2-RELEASE #0: Tue Aug 14 15:14:19 EEST 2007     root at netsnapper.domain.com:/usr/src/sys/i386/compile/NETSNAPPER  i386
(SMP + SCHED_ULE)
>Description:
openpam_load_module/openpam_release_module (/usr/src/contrib/openpam/lib/openpam_load.c) called from pam_start/pam_end not thread safe.
They use global static variable for list of previously found modules.
Easly reproduceable with attached program (only on true 2-x CPU machine).
>How-To-Repeat:
Steps:
# cc -Wall -D_UNIX -D_DEBUG -std=c99 -I/usr/local/include -I/usr/include/security -O0 -g -fno-inline   -L/usr/local/lib -o pthreads_and_pam main.c -Xlinker -Bdynamic -lpam -lpthread
# echo "auth required pam_permit.so" >/usr/local/etc/pam.d/pthreads_and_pam
# ./pthreads_and_pam
....................
(after 4 sec - 5 min)
pthreads_and_pam in free(): error: chunk is already free
Abort trap: 6 (core dumped)
# grep openpam /var/log/messages
Aug 30 17:33:57 netsnapper pthreads_and_pam: in openpam_release_module(): module (null) has negative refcount

>Fix:
Add synchronization code around access to global variable
'modules' in /usr/src/contrib/openpam/lib/openpam_load.c.

Can take from /usr/src/libexec/rtld-elf/rtld_lock.c or add new library depend from libpthread and use their function (ugly?)

Patch attached with submission follows:


#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <pthread.h>

#include <pam_appl.h>

typedef struct _auth_subject_t {
    const char *username;
    const char *password;
    const char *service;
} auth_subject_t;

#define NUM_THREADS 50 
volatile int num_threads;
pthread_mutex_t num_thr_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t last_thread_fini;

int pam_auth_conv(int NumOfMsgs, const struct pam_message **pMsgs, struct pam_response **pResponces, void *pContext)
{
	int i = 0;

	*pResponces = (struct pam_response *) malloc(NumOfMsgs * sizeof(struct pam_response));
	if (*pResponces == NULL) {
		return 0;
	}
	bzero(*pResponces, NumOfMsgs * sizeof(struct pam_response));

	for (i = 0; i < NumOfMsgs; i++) {
		switch(pMsgs[i]->msg_style) {
			case PAM_PROMPT_ECHO_OFF:
				(*pResponces)[i].resp = strdup(((auth_subject_t*)pContext)->password);
				(*pResponces)[i].resp_retcode = 0;
				break;
			case PAM_PROMPT_ECHO_ON:
				(*pResponces)[i].resp = strdup(((auth_subject_t*)pContext)->username);
				(*pResponces)[i].resp_retcode = 0;
				break;
			case PAM_ERROR_MSG:
				break;
			case PAM_TEXT_INFO:
				break;
			default:
				break;
		}
	}
	return PAM_SUCCESS;
}

void* test(void* arg)
{
	struct pam_conv conv = {0};
	
	auth_subject_t auth_subject = { /*login*/"agile", /*password*/"agile", "pthreads_and_pam" };

	conv.conv = pam_auth_conv;
	conv.appdata_ptr = &auth_subject;

	int				result = 0;
	pam_handle_t	*pam_handle = NULL;
	const char		*pam_service_name = auth_subject.service;

	result = pam_start(pam_service_name, NULL, &conv, &pam_handle);

	if (result != PAM_SUCCESS) {
		fprintf(stderr, "pam_start failed, %s\n", pam_strerror(pam_handle, result));
		goto failed;
	}

	result = pam_authenticate(pam_handle, 0);
	if (result != PAM_SUCCESS) {
		fprintf(stderr, "* %s\n", pam_strerror(pam_handle, result));
		goto failed;
	}

	putc('.', stderr);

failed:
	if (pam_handle) {
		pam_end(pam_handle, 0);
	}

	pthread_mutex_lock(&num_thr_mutex);
		num_threads--;
		// last thread?
		if (num_threads == 0) {
			pthread_cond_signal(&last_thread_fini);
		}
	pthread_mutex_unlock(&num_thr_mutex);

	return NULL;
}

int main(int argc, char *argv[])
{
	pthread_cond_init(&last_thread_fini, NULL);
	pthread_t thr;

	//for(int x=0; x<1; x++)
	while(1)
	{

		pthread_mutex_lock(&num_thr_mutex);
		num_threads = NUM_THREADS;

		for (int i=0; i<num_threads; i++) {
			if (pthread_create(&thr, NULL, test, (void*)i+1)) {
				fprintf(stderr, "can't create thread");
			} else {
				pthread_detach(thr);
			}
		}

		// wait
		pthread_cond_wait(&last_thread_fini, &num_thr_mutex);
		putc('\n', stderr);
		pthread_mutex_unlock(&num_thr_mutex);
	}

	return 0;
}



>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list