다양한 기록

Race Condition Problem 본문

운영체제보안

Race Condition Problem

라구넹 2024. 12. 6. 15:22

여러 스레드, 프로세스가 공유자원을 업데이트할 때 문제가 발생 (읽기만 하면 그럴 일 없음)

프로세스나 스레드의 실행 순서에 의존적인 결과

ex. 데이터베이스

해결하기 위해 mutex 락, 언락

이런 부분이 어떻게 공격으로 연결되는가 ..

 

#include <stdio.h>
#include <unistd.h>

void main(void)
{
    int pid;
    printf("PID = %d, in BEGIN printf\n", getpid());
    
    pid = fork();
    if( pid == 0 )
    {
        for( int k = 0; k < 25; ++k )
        {
            usleep(50000);
            printf("PID=%d : Child printf *****\n", getpid());
            fflush(stdout);
        }
    }
    else if( pid > 0 )
    {
        for( int n = 0; n < 25; ++n )
        {
            printf("PID=%d : Parent printf\n", getpid());
            fflush(stdout);
            usleep(50000);
        }
    }
    else
    {
        printf("fork() error\n");
    }
    
    printf("PID = %d, in END printf\n", getpid());
}

예시 코드

슬립 시 자동 문맥 교환 (usleep이니 마이크로 단위)

아무튼 출력 결과가 다르게 나옴(순서)

여기서 공유 자원: 모니터

완전히 별개의 프로세스여도 이렇게 될 수 있음

 

#include <stdio.h>
#include <pthread.h>

void *printHello(void *arg)
{
    printf("%s\n", (char*)arg);
}

void main()
{
    pthread_t p1, p2;
    printf("main: begin\n");
    
    pthread_create(&p1, NULL, printHello, "p1");
    pthread_create(&p2, NULL, printHello, "p2");
    
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);
    
    printf("main: end\n");
}

p1이랑 p2가 표준 출력을 두고 경쟁 .. 최종 출력 형태가 매번 달라짐

 

#include <stdio.h>
#include <pthread.h>

static volatile int counter = 0;

void mythread(void *arg)
{
    printf("%s: begin\n", (char*)arg);
    int i;
    for(i = 0; i < 1e7; ++i)
    {
        ++counter;
    }
    printf("%s: done\n", (char*)arg);
}

int main(int argc, char *argv[])
{
    pthread_t p1, p2;
    printf("main: begin (counter = %d)\n", counter);
    pthread_create(&p1, NULL, mythread, "A");
    pthread_create(&p2, NULL, mythread, "B");
    
    pthread_join(p1, NULL);
    pthread_join(p2, NULL);
    
    printf("main: done, counter: %d\n", counter);
    
    return 0;
}

결과: 매번 다름

 

count++

movl	count, %eax
incl	%eax
movl	%eax, count

count++ 이라는 명령은 위와 같이 세 개의 명령으로 구성

만약 두 개의 스레드가 있을 때, 마지막 movl이 실행되기 전에 컨텍스트 스위칭되어 다른 스레드에서 값을 변경하면 문제 발생

* 운영체제 카테고리의 Concurrency 부분에 더 자세히 나옴

 

static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    for(i = 0; i < 1e7; ++i)
    {
        pthread_mutex_lock(&lock);
        ++counter;
        pthread_mutex_unlock(&lock);
    }
    printf("%s: done\n", (char*)arg);
}

락 걸면 해결


Race Condition Vulnerability

//Code for P1
...
balance = balance + amount;
...
movl	balance, %eax
movl	amount, %ebx
add	%ebx, %eax
movl	%eax, balance
//Code for P2
...
balance = balance - amount;
...
movl	balance, %eax
movl	amount, %ebx
sub	%ebx, %eax
movl	%eax, balance

P1은 입금, P2는 인출

가정

초기 balance = 2000

amount = 500

 

P1			P2
movl	balance, %eax
movl	amount, %ebx

			movl	balance, %eax
			movl	amount, %ebx
			sub	%ebx, %eax
			movl	%eax, balance

add	%ebx, %eax
movl	%eax, balance

실행이 이렇게 되면?

sub됐던 거 무시되고 balance는 2500이 됨

 

예시2

account A($500), account B($0)

A -> B 500 송금 .. 스레드 두개

Thread1 Thread2 A + B balance
Check account A balance($500)   500
  Check account A balance($500) 500
Add $500 to account B   1000 (A 500, B 500)
  Add $500 to account B 1500 (A 500, B 1000)
Deduct $500 from account A   1000 (A 0, B 1000)
  Deduct $500 from account A 1000 (A 0, B 1000), 음수는 안될 것
    1000 (A 0, B 1000)

트랜잭션 안되니까 총 balance가 1000으로 불어버림

 

Case Study: Unlimited Starbucks Coffee

기프트 카드 합치는 게 가능한데 레이스 컨디션 문제

Thread1 Thread2 A + B balance
Check card A balance($5)   5
  Check card A balance($5) 5
Add $5 to card B   10 (A 5, B 5)
  Add $5 to card B 15 (A 5, B 10)
Deduct $5 from card A   10 (A 0, B 10)
  Deduct $5 from card A 10 (A 0, B 10), 음수는 안될 것
    10 (A 0, B 10)

위의 계좌 문제랑 사실상 같은 문제임


function withdraw($amount)
{
    $balance = getBalance();
    if( $amount <= $balance )	// check
    {
        $balance = $balance - $amount;	// use
        echo "You have withdrawn: $amount";
        saveBalance($balance);
    }
    else
    {
        echo "Insufficient funds.";
    }
}

check랑 use 사이에 공격이 들어오면 문제가 발생할 수 있음

=> ToCTToU