...
Code Block | ||
---|---|---|
| ||
#include <stdio.h> #include <pthread.h> #include <stdlib.h> typedef struct { int balance; void *thread1(void *ptr); void *thread2(void *ptr); pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t m2 = PTHREAD_MUTEX_INITIALIZER; void *thread1 pthread_mutex_t balance_mutex; } bank_account; typedef struct { bank_account *from; bank_account *to; int amount; } deposit_thr_args; /* return negative on error */ int create_bank_account(bank_account **ba, int initial_amount) { bank_account *nba = malloc(sizeof(bank_account)); if (nba == NULL) { return -1; } nba->balance = initial_amount; pthread_mutex_init(&nba->balance_mutex, NULL); *ba = nba; return 0; } void *deposit(void *ptr) { deposit_thr_args *args = (deposit_thr_args *)ptr; pthread_mutex_lock(&m1(args->from->balance_mutex)); /* donot someenough stuffbalance thatto require lockingtransfer mutex1 */ if (args->from->balance < args->amount) { pthread_mutex_unlock(&(args->from->balance_mutex)); return NULL; } pthread_mutex_lock(&m2(args->to->balance_mutex)); args->from->balance /* do some stuff that require locking mutex2 */ -= args->amount; args->to->balance += args->amount; pthread_mutex_unlock(&m2(args->from->balance_mutex)); pthread_mutex_unlock(&m1(args->to->balance_mutex)); return NULL; } voidint *thread2(void *ptr) { pthread_mutex_lock(&m2); /* do some stuff that require locking mutex2 */ pthread_mutex_lock(&m1); /* do some stuff that require locking mutex1 */ pthread_mutex_unlock(&m1); pthread_mutex_unlock(&m2); return NULLmain() { pthread_t thr1, thr2; int err; bank_account *ba1, *ba2; err = create_bank_account(&ba1, 1000); if (err < 0) exit(err); err = create_bank_account(&ba2, 1000); if (err < 0) exit(err); deposit_thr_args *arg1 = malloc(sizeof(deposit_thr_args)); deposit_thr_args *arg2 = malloc(sizeof(deposit_thr_args)); arg1->from = ba1; arg1->to = ba2; arg1->amount = 100; arg2->from = ba2; arg2->to = ba1; arg2->amount = 100; /* perform the deposit */ pthread_create(&thr1, NULL, deposit, (void *)arg1); pthread_create(&thr2, NULL, deposit, (void *)arg2); pthread_exit(NULL); return 0; } |
Compliant Solution
The solution to the deadlock problem is to lock in predefined order. In the following example, each thread will lock m1 first then m2. This way circular wait problem is avoided and when one thread requires a lock will guarantee it will require the next lock.
Code Block | ||
---|---|---|
| ||
#include <stdio.h> #include <pthread.h> #include <stdlib.h> typedef struct { int balance; pthread_mutex_t balance_mutex; unsigned int id; /* read only and should never be changed */ void *thread1(void *ptr); void *thread2(void *ptr); pthread_mutex_t m1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t m2 = PTHREAD_MUTEX_INITIALIZER; void *thread1} bank_account; typedef struct { bank_account *from; bank_account *to; int amount; } deposit_thr_args; unsigned int global_id = 1; /* return negative on error */ int create_bank_account(bank_account **ba, int initial_amount) { bank_account *nba = malloc(sizeof(bank_account)); if (nba == NULL) { return -1; } nba->balance = initial_amount; pthread_mutex_init(&nba->balance_mutex, NULL); nba->id = global_id++; *ba = nba; return 0; } void *deposit(void *ptr) { deposit_thr_args *args = (deposit_thr_args *)ptr; if (args->from->id == args->to->id) return NULL; /* ensure proper ordering for unlocking */ if (args->from->id < args->to->id) { pthread_mutex_lock(&(args->from->balance_mutex)); pthread_mutex_lock(&m1(args->to->balance_mutex)); } else { pthread_mutex_lock(&m2); /* do some stuff that require locking mutex1(args->to->balance_mutex)); pthread_mutex_lock(&(args->from->balance_mutex)); } /* not enough balance to transfer */ if /* do some stuff that require locking mutex2 */ (args->from->balance < args->amount) { pthread_mutex_unlock(&(args->from->balance_mutex)); pthread_mutex_unlock(&(args->to->balance_mutex)); return NULL; } args->from->balance -= args->amount; args->to->balance += args->amount; pthread_mutex_unlock(&m2(args->from->balance_mutex)); pthread_mutex_unlock(&m1(args->to->balance_mutex)); return NULL; } voidint *thread2(void *ptr) { pthread_mutex_lock(&m1); pthread_mutex_lock(&m2); /* do some stuff that require locking mutex1 */ /* do some stuff that require locking mutex2 */ pthread_mutex_unlock(&m1); pthread_mutex_unlock(&m2); return NULLmain() { pthread_t thr1, thr2; int err; bank_account *ba1, *ba2; err = create_bank_account(&ba1, 1000); if (err < 0) exit(err); err = create_bank_account(&ba2, 1000); if (err < 0) exit(err); deposit_thr_args *arg1 = malloc(sizeof(deposit_thr_args)); deposit_thr_args *arg2 = malloc(sizeof(deposit_thr_args)); arg1->from = ba1; arg1->to = ba2; arg1->amount = 100; arg2->from = ba2; arg2->to = ba1; arg2->amount = 100; /* perform the deposit */ pthread_create(&thr1, NULL, deposit, (void *)arg1); pthread_create(&thr2, NULL, deposit, (void *)arg2); pthread_exit(NULL); return 0; } |
Risk Assessment
Deadlock causes multiple threads to not be able to progress and thus halt the executing program. This is a potential denial-of-service attack when the attacker can force deadlock situations. It's probable that deadlock will occur in multi-thread programs that manage multiple resources. Some automation for detecting deadlock can be implemented in which the detector can try different inputs and wait for a timeout. The fixes can be done automatically using some graph algorithm like Dijkstra, but most like be manual.
...