C 언어에서 시그널 핸들링과 보안 취약점 방지 방법

C 언어에서 시그널은 운영 체제가 프로세스에 특정 이벤트를 알리는 방법 중 하나입니다. 그러나 시그널 핸들링은 잘못 구현될 경우 보안 취약점을 초래할 수 있습니다. 본 기사에서는 시그널의 기본 개념부터 보안 취약점 방지 방법까지 단계별로 살펴봅니다. C 언어 개발자들이 안전한 시그널 처리를 구현할 수 있도록 실용적인 코드 예제와 구체적인 해결책을 제공합니다. 이를 통해 시그널 핸들링의 안정성과 보안성을 모두 확보하는 방법을 배울 수 있습니다.

목차

시그널 핸들링의 기본 개념


시그널은 운영 체제가 프로세스에 특정 이벤트를 알리기 위해 사용하는 메커니즘입니다. 일반적으로 프로세스에 비동기적으로 전달되며, 특정 이벤트(예: Ctrl+C, 특정 하드웨어 신호, 타이머 만료)가 발생했을 때 트리거됩니다.

시그널의 역할

  • 프로세스 중단: SIGINT와 같이 프로세스를 종료시키기 위한 시그널.
  • 알림 전달: 타이머 만료(SIGALRM) 또는 자식 프로세스 종료(SIGCHLD)와 같은 상황 알림.
  • 사용자 정의 이벤트: 프로세스 간 통신 및 사용자 정의 작업 실행.

핸들러 함수


시그널이 발생하면, 운영 체제는 해당 시그널에 등록된 핸들러 함수를 실행합니다. 이를 통해 프로세스가 시그널을 처리하거나 무시할 수 있습니다.

시그널 등록 기본 코드

#include <stdio.h>  
#include <signal.h>  

void signal_handler(int signal) {  
    printf("Received signal: %d\n", signal);  
}  

int main() {  
    signal(SIGINT, signal_handler); // Ctrl+C를 처리  
    while (1) {  
        printf("Running...\n");  
        sleep(1);  
    }  
    return 0;  
}  


위 코드는 SIGINT 시그널(Ctrl+C 입력 시)을 처리하기 위해 간단한 핸들러를 등록하는 방법을 보여줍니다.

핸들러 설정은 시그널을 무시하거나 특정 작업을 수행하도록 프로세스를 제어하는 데 중요한 역할을 합니다.

일반적인 시그널 처리 방법


C 언어에서 시그널을 처리하기 위해 제공되는 주요 함수와 기법은 간단하면서도 강력한 도구를 제공합니다. 다음은 일반적인 시그널 처리 방법과 그 사용법에 대한 설명입니다.

signal 함수


signal 함수는 특정 시그널에 대한 핸들러를 등록하거나 기본 동작을 변경하는 데 사용됩니다.

signal 함수의 기본 사용법

#include <signal.h>

void custom_handler(int signal) {
    printf("Custom handler triggered for signal: %d\n", signal);
}

int main() {
    signal(SIGINT, custom_handler); // SIGINT 시그널에 사용자 정의 핸들러 등록
    while (1) {
        printf("Running...\n");
        sleep(1);
    }
    return 0;
}


위 코드는 SIGINT 시그널(Ctrl+C 입력)을 사용자 정의 핸들러로 처리합니다.

sigaction 함수


sigaction 함수는 signal 함수보다 더 안전하고 유연한 방식으로 시그널을 처리할 수 있게 해줍니다.

sigaction 함수의 기본 사용법

#include <signal.h>
#include <stdio.h>
#include <string.h>

void custom_handler(int signal) {
    printf("Handled signal: %d\n", signal);
}

int main() {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = custom_handler;
    sigaction(SIGINT, &sa, NULL); // SIGINT 시그널에 대해 핸들러 설정

    while (1) {
        printf("Running...\n");
        sleep(1);
    }
    return 0;
}
  • 장점: 핸들러 등록 시 추가 플래그 설정이 가능하고, 더 정밀한 제어가 가능합니다.
  • 추천 이유: 멀티스레드 환경에서의 안전성 확보.

시그널 무시


특정 시그널을 무시해야 할 경우, SIG_IGN 값을 사용하여 해당 시그널의 동작을 비활성화할 수 있습니다.

signal(SIGTERM, SIG_IGN); // SIGTERM 시그널을 무시

기본 동작 복원


기본 동작으로 되돌리려면 SIG_DFL 값을 사용할 수 있습니다.

signal(SIGINT, SIG_DFL); // SIGINT 시그널의 기본 동작 복원

시그널 처리 시 주의 사항

  • 비동기적으로 호출되므로 시그널 핸들러 내부에서 비안전한 함수(예: printf)를 사용하는 것은 권장되지 않습니다.
  • 멀티스레드 환경에서 정확한 동작을 보장하기 위해 반드시 sigaction을 사용할 것을 권장합니다.

위의 기법들을 조합하면 다양한 시그널 처리 요구사항을 만족시킬 수 있습니다.

시그널 관련 보안 취약점 사례


시그널 처리의 구현이 부적절하면 프로그램에 심각한 보안 취약점을 초래할 수 있습니다. 이 섹션에서는 시그널 처리와 관련된 일반적인 보안 취약점과 실무 사례를 소개합니다.

1. 경쟁 상태(Race Condition)


시그널 핸들러가 실행되는 동안 동일한 데이터나 리소스를 다른 코드에서 동시에 접근하면 경쟁 상태가 발생할 수 있습니다.

사례


시그널 핸들러가 파일을 열거나 닫는 작업을 수행하는 도중, 다른 스레드가 동일한 파일에 접근하여 데이터 손상 또는 보안 침해가 발생할 수 있습니다.

해결 방안

  • 시그널 핸들러 내부에서는 최소한의 작업만 수행하고, 리소스 접근은 주 프로세스에서 처리하도록 설계합니다.
  • 멀티스레드 환경에서는 sigaction과 같은 안전한 API를 사용하고, 동기화를 위한 락(lock) 메커니즘을 활용합니다.

2. 비안전한 함수 호출


시그널 핸들러 내부에서 비안전한 함수(예: printf, malloc)를 호출하면, 예상치 못한 동작이 발생할 수 있습니다.

사례


시그널 핸들러에서 printf를 호출할 경우, 다른 코드가 동시에 printf를 호출하면 출력이 손상되거나 프로그램이 비정상 종료될 수 있습니다.

해결 방안

  • 시그널 핸들러에서는 write와 같은 안전한 함수만 사용합니다.
#include <unistd.h>
void signal_handler(int signal) {
    const char *message = "Signal received\n";
    write(STDOUT_FILENO, message, strlen(message));
}

3. 시그널 재진입(Reentrancy) 문제


시그널 핸들러가 실행되는 동안 동일한 시그널이 다시 발생하면, 핸들러가 재진입하여 충돌이나 무한 루프가 발생할 수 있습니다.

사례


SIGALRM 핸들러가 실행 중인데, 또 다른 타이머에 의해 SIGALRM이 발생하여 스택 오버플로우가 발생할 위험이 있습니다.

해결 방안

  • sigaction을 사용하여 시그널이 처리 중일 때 해당 시그널을 블록(block)합니다.
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGALRM); // SIGALRM 블록
sa.sa_handler = custom_handler;
sigaction(SIGALRM, &sa, NULL);

4. 데이터 조작 공격


시그널 처리 과정에서 잘못된 데이터 검증이나 유효성 검사가 부족하면 공격자가 데이터 조작을 통해 시스템을 침해할 수 있습니다.

사례


핸들러 내부에서 외부 입력 값을 처리하는 경우, 공격자가 악의적인 값을 입력하여 취약점을 유발할 수 있습니다.

해결 방안

  • 입력 값에 대한 엄격한 유효성 검사를 수행합니다.
  • 핸들러 내부에서는 최소한의 작업만 수행하고, 검증 작업은 주 프로세스에서 처리합니다.

요약


시그널 처리에서 발생할 수 있는 보안 취약점을 방지하려면, 경쟁 상태, 비안전한 함수 호출, 재진입 문제, 데이터 검증 부족 등을 철저히 고려해야 합니다. 안전한 코드 설계와 시그널 핸들링의 원칙을 준수하면 이러한 취약점을 효과적으로 방지할 수 있습니다.

시그널 안전성 확보를 위한 전략


시그널 핸들링은 비동기적으로 실행되므로, 코드 안전성을 확보하기 위해 신중한 설계가 필요합니다. 다음은 안전한 시그널 처리를 위한 주요 전략입니다.

1. 최소한의 작업 수행


시그널 핸들러는 가능한 한 간단하게 유지해야 합니다. 핸들러 내부에서 리소스를 수정하거나 복잡한 작업을 수행하면 예기치 않은 동작을 초래할 수 있습니다.

실행 예


핸들러 내부에서 플래그를 설정하고, 주요 작업은 주 프로세스에서 수행합니다.

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

volatile sig_atomic_t signal_flag = 0;

void signal_handler(int signal) {
    signal_flag = 1; // 플래그 설정
}

int main() {
    signal(SIGINT, signal_handler);  
    while (1) {
        if (signal_flag) {
            printf("Signal received, handling it now.\n");
            signal_flag = 0;
        }
        sleep(1);
    }
    return 0;
}

2. 시그널 블록 설정


특정 시그널이 처리 중일 때 동일한 시그널이 중복 실행되지 않도록 블록을 설정해야 합니다.

코드 예시


sigaction을 사용하여 블록 리스트를 설정합니다.

#include <signal.h>
#include <stdio.h>

void signal_handler(int signal) {
    printf("Handling signal: %d\n", signal);
}

int main() {
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGINT); // SIGINT 블록 추가
    sa.sa_flags = 0;

    sigaction(SIGINT, &sa, NULL);

    while (1) {
        printf("Running...\n");
        sleep(1);
    }
    return 0;
}

3. 재진입 안전 함수 사용


시그널 핸들러에서 호출되는 함수는 반드시 재진입 안전(reentrant-safe)이어야 합니다. 재진입 안전하지 않은 함수(예: printf, malloc)는 충돌을 유발할 수 있습니다.

안전한 대안

  • write를 사용하여 메시지를 출력합니다.
  • 글로벌 상태를 변경하지 않고, 불변 데이터만 활용합니다.

4. 멀티스레드 환경에서의 주의


멀티스레드 환경에서는 각 스레드에 독립적으로 시그널을 전달하거나 전체 스레드에 블록 설정을 해야 합니다.

스레드별 시그널 처리

pthread_sigmask(SIG_BLOCK, &sigset, NULL); // 스레드별 시그널 블록 설정

5. 디버깅 및 로깅


시그널 핸들링이 예상대로 동작하는지 확인하기 위해 디버깅과 로깅을 철저히 수행해야 합니다.

로깅 방법

  • 간단한 메시지 출력: write 사용.
  • 복잡한 로그 저장: 주 프로세스에서 처리.

6. 비동기 작업 분리


핸들러는 작업의 트리거 역할만 수행하고, 실제 작업은 주 프로세스에서 처리하도록 설계합니다.

요약


안전한 시그널 처리를 위해 핸들러를 간단히 유지하고, 재진입 안전 함수만 사용하며, 블록 설정 및 멀티스레드 환경에서의 적절한 관리를 준수해야 합니다. 이를 통해 경쟁 상태와 예기치 않은 동작을 방지할 수 있습니다.

sigaction 함수 활용


sigaction 함수는 C 언어에서 시그널을 처리하는 데 있어 가장 안전하고 유연한 방법 중 하나입니다. 기존의 signal 함수보다 더 많은 옵션과 제어 기능을 제공하며, 멀티스레드 환경에서도 안전하게 사용할 수 있습니다.

sigaction 함수의 기본 구조


sigaction 함수는 다음과 같은 형식으로 사용됩니다:

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
  • signum: 처리할 시그널의 번호입니다(예: SIGINT, SIGTERM).
  • act: 새로운 시그널 핸들러와 설정 정보를 포함하는 구조체입니다.
  • oldact: 이전 시그널 핸들러 정보를 저장하는 데 사용됩니다(필요하지 않으면 NULL로 설정).

sigaction 구조체


sigaction 구조체의 주요 필드는 다음과 같습니다:

  • sa_handler: 시그널 핸들러 함수 포인터.
  • sa_mask: 해당 시그널을 처리하는 동안 블록할 시그널의 집합.
  • sa_flags: 시그널 처리 동작을 변경하는 플래그(예: SA_RESTART, SA_SIGINFO).

sigaction 함수 사용 예시


아래는 sigaction을 사용하여 SIGINT 시그널을 처리하는 예제입니다.

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

void custom_handler(int signal) {
    printf("Received signal: %d\n", signal);
}

int main() {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));  // 구조체 초기화
    sa.sa_handler = custom_handler;  // 핸들러 함수 설정
    sigemptyset(&sa.sa_mask);  // 추가 블록할 시그널 없음
    sa.sa_flags = 0;  // 기본 동작 설정

    // SIGINT에 대해 sigaction 설정
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    while (1) {
        printf("Running...\n");
        sleep(1);
    }

    return 0;
}

sigaction의 주요 옵션

1. `SA_RESTART` 플래그


시그널 처리 후 중단된 시스템 호출을 자동으로 재시작합니다.

sa.sa_flags = SA_RESTART;

2. `SA_SIGINFO` 플래그


핸들러 함수가 추가 정보를 받을 수 있도록 합니다.

void custom_handler(int signal, siginfo_t *info, void *context) {
    printf("Signal: %d, PID: %d\n", signal, info->si_pid);
}
sa.sa_flags = SA_SIGINFO;
sa.sa_sigaction = custom_handler;

sigaction 함수의 장점

  1. 안전성: signal 함수보다 멀티스레드 환경에서 더 안전합니다.
  2. 정밀한 제어: 블록할 시그널과 플래그를 통해 시그널 처리 동작을 세밀하게 설정할 수 있습니다.
  3. 추가 정보: SA_SIGINFO를 사용하여 시그널 관련 추가 정보를 받을 수 있습니다.

시그널 블록 설정 예시


핸들러 실행 중 특정 시그널을 블록하여 재진입 문제를 방지할 수 있습니다.

sigaddset(&sa.sa_mask, SIGTERM);  // SIGTERM 블록 추가

요약


sigaction 함수는 C 언어에서 시그널을 처리하는 가장 강력한 도구로, 안전성과 유연성을 제공합니다. 이를 활용하여 멀티스레드 환경에서도 안전하고 정교한 시그널 처리를 구현할 수 있습니다.

비동기적 함수 호출의 문제점


시그널 핸들링은 본질적으로 비동기적입니다. 이는 시그널 핸들러가 실행되는 동안 프로그램의 다른 코드와 동시에 실행될 수 있음을 의미합니다. 이로 인해 특정 함수 호출 시 예상치 못한 문제나 충돌이 발생할 수 있습니다.

비동기적 함수 호출에서의 주요 문제점

1. 재진입 문제


재진입 문제는 시그널 핸들러가 실행되는 동안 동일한 함수가 다시 호출될 때 발생합니다. 이로 인해 데이터 손상, 충돌, 또는 예기치 않은 동작이 발생할 수 있습니다.

사례


핸들러 내부에서 malloc 또는 free와 같은 메모리 관리 함수를 호출하면, 프로그램의 다른 부분에서도 이 함수들을 사용할 경우 충돌이 발생할 수 있습니다.

해결 방안

  • 시그널 핸들러에서는 재진입이 안전한 함수만 호출합니다.
  • 비안전한 함수 목록은 시스템 문서(man 7 signal)에서 확인할 수 있습니다.

안전한 대안 예시

#include <signal.h>
#include <unistd.h>

void signal_handler(int signal) {
    const char *message = "Signal received\n";
    write(STDOUT_FILENO, message, 16); // 재진입 안전한 함수 사용
}

2. 데이터 경합(Race Condition)


시그널 핸들러가 실행되는 동안 프로그램의 다른 코드가 동일한 데이터에 접근하면 경합이 발생하여 데이터 손상이나 충돌이 발생할 수 있습니다.

사례


핸들러와 주 프로세스가 동시에 동일한 전역 변수를 수정하려고 시도할 때 데이터가 손상될 수 있습니다.

해결 방안

  • volatile sig_atomic_t 변수를 사용하여 경합을 방지합니다.
#include <signal.h>
#include <stdio.h>

volatile sig_atomic_t signal_flag = 0;

void signal_handler(int signal) {
    signal_flag = 1;
}

int main() {
    signal(SIGINT, signal_handler);
    while (1) {
        if (signal_flag) {
            printf("Signal received\n");
            signal_flag = 0;
        }
    }
    return 0;
}

3. 스택 오버플로우


핸들러가 과도하게 호출되거나, 재귀적으로 호출될 경우 스택 오버플로우가 발생할 수 있습니다.

해결 방안

  • 시그널 핸들러 실행 중 동일한 시그널을 블록하여 재귀 호출을 방지합니다.
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sigaddset(&sa.sa_mask, SIGINT); // SIGINT 블록
sa.sa_handler = signal_handler;
sigaction(SIGINT, &sa, NULL);

시그널 핸들러 설계 원칙

  • 핸들러 내부 작업 최소화: 시그널 핸들러에서는 간단한 플래그 설정 또는 로깅만 수행하고, 주요 작업은 주 프로세스에서 처리합니다.
  • 안전한 함수만 호출: 비재진입 함수는 핸들러에서 사용하지 않습니다.
  • 데이터 동기화: volatile sig_atomic_t 또는 뮤텍스를 사용해 동기화 문제를 방지합니다.

비동기적 함수 호출을 피하기 위한 권장 전략

  1. 핸들러 내부에서는 전역 변수를 사용하되, 동기화가 필요한 데이터는 반드시 보호합니다.
  2. 멀티스레드 환경에서는 시그널 마스크를 설정하여 특정 시그널이 선택된 스레드에서만 처리되도록 합니다.

요약


비동기적 함수 호출로 인한 문제를 방지하려면 시그널 핸들러에서 재진입 안전한 함수만 사용하고, 데이터 경합 및 스택 오버플로우를 방지하는 설계를 채택해야 합니다. 안전한 설계를 통해 비동기적 환경에서도 프로그램의 안정성을 유지할 수 있습니다.

보안 취약점 방지를 위한 코드 예제


시그널 처리와 관련된 보안 취약점을 방지하려면 안전한 코딩 관행과 검증된 설계를 적용해야 합니다. 아래는 시그널 핸들링에서 발생할 수 있는 보안 문제를 예방하기 위한 실용적인 코드 예제입니다.

1. 안전한 시그널 핸들러 구현


핸들러는 최소한의 작업만 수행하고, 재진입 안전한 함수만 호출해야 합니다.

코드 예제: 안전한 핸들러

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

volatile sig_atomic_t signal_flag = 0; // 안전한 플래그

void signal_handler(int signal) {
    signal_flag = 1; // 플래그 설정
}

int main() {
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = signal_handler; // 안전한 핸들러 등록
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    while (1) {
        if (signal_flag) {
            write(STDOUT_FILENO, "Signal received\n", 17); // 재진입 안전한 함수 호출
            signal_flag = 0;
        }
        sleep(1);
    }

    return 0;
}

2. 데이터 경합 방지


핸들러와 메인 프로세스 간 데이터 경합을 방지하려면 volatile sig_atomic_t를 사용하거나 보호 메커니즘을 도입해야 합니다.

코드 예제: 데이터 보호

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

volatile sig_atomic_t shared_data = 0; // 전역 변수

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void signal_handler(int signal) {
    pthread_mutex_lock(&mutex); // 보호 시작
    shared_data++;
    pthread_mutex_unlock(&mutex); // 보호 종료
}

int main() {
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;

    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    while (1) {
        pthread_mutex_lock(&mutex);
        printf("Shared data: %d\n", shared_data);
        pthread_mutex_unlock(&mutex);
        sleep(1);
    }

    return 0;
}

3. 시그널 중첩 방지


중첩된 시그널 처리를 방지하려면 특정 시그널이 처리 중일 때 블록할 수 있습니다.

코드 예제: 중첩 방지

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

void signal_handler(int signal) {
    write(STDOUT_FILENO, "Handling signal\n", 16);
}

int main() {
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sigaddset(&sa.sa_mask, SIGINT); // SIGINT 중첩 방지
    sa.sa_flags = 0;

    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        return 1;
    }

    while (1) {
        printf("Running...\n");
        sleep(1);
    }

    return 0;
}

4. 입력 검증


시그널 핸들러가 사용자 입력을 처리해야 한다면, 입력 값의 유효성을 철저히 검증해야 합니다.

코드 예제: 입력 값 검증

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void signal_handler(int signal) {
    int input = -1;
    printf("Enter a number: ");
    if (scanf("%d", &input) != 1 || input < 0) {
        printf("Invalid input\n");
        return;
    }
    printf("You entered: %d\n", input);
}

int main() {
    signal(SIGINT, signal_handler);

    while (1) {
        printf("Running...\n");
        sleep(1);
    }

    return 0;
}

요약


위 예제들은 시그널 처리에서 발생할 수 있는 보안 취약점을 예방하는 주요 전략을 다룹니다. 데이터 경합 방지, 중첩 방지, 안전한 함수 사용, 그리고 입력 검증을 통해 안정적이고 보안성이 높은 프로그램을 구현할 수 있습니다.

시그널 핸들링과 보안 테스트


시그널 핸들링이 제대로 동작하고 보안 취약점을 방지하기 위해서는 철저한 테스트가 필수입니다. 이 섹션에서는 시그널 핸들링의 정확성과 보안성을 확인하기 위한 다양한 테스트 기법을 소개합니다.

1. 기본 동작 테스트


시그널 핸들러가 예상대로 동작하는지 확인하기 위해 기본적인 테스트를 수행합니다.

테스트 예제

  • 목표: SIGINT 시그널이 발생했을 때 핸들러가 호출되는지 확인.
  • 방법: 프로그램 실행 중 Ctrl+C를 입력하여 핸들러가 실행되는지 확인.
#include <signal.h>
#include <stdio.h>

void signal_handler(int signal) {
    printf("Handled signal: %d\n", signal);
}

int main() {
    signal(SIGINT, signal_handler);
    while (1) {
        printf("Waiting for signal...\n");
        sleep(1);
    }
    return 0;
}
  • 결과 확인: Handled signal: 2와 같은 출력이 표시되면 테스트 성공.

2. 재진입 문제 테스트


재진입 문제가 발생하지 않는지 확인하기 위해 동시에 여러 시그널을 발생시켜 테스트합니다.

테스트 예제

  • 목표: 동일한 시그널이 중첩되더라도 프로그램이 안정적으로 동작하는지 확인.
  • 방법: kill 명령어를 사용하여 동일한 시그널을 연속적으로 발생시킴.
kill -SIGINT <프로세스 ID>
kill -SIGINT <프로세스 ID>
  • 코드 예시:
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void signal_handler(int signal) {
    printf("Signal %d handled safely.\n", signal);
    sleep(2); // 의도적으로 지연
}

int main() {
    signal(SIGINT, signal_handler);
    while (1) {
        printf("Running...\n");
        sleep(1);
    }
    return 0;
}
  • 결과 확인: 한 번의 시그널 처리 후 중복 호출 없이 안정적으로 동작해야 합니다.

3. 경쟁 상태 테스트


시그널 처리 중 데이터 경합이 발생하지 않는지 확인합니다.

테스트 예제

  • 목표: 핸들러와 메인 프로세스가 동일한 데이터에 동시에 접근해도 데이터 손상이 발생하지 않음.
  • 방법: 전역 변수에 대한 핸들러와 메인 프로세스의 동시 접근 테스트.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

volatile sig_atomic_t counter = 0;

void signal_handler(int signal) {
    counter++;
}

int main() {
    signal(SIGINT, signal_handler);
    while (1) {
        printf("Counter value: %d\n", counter);
        sleep(1);
    }
    return 0;
}
  • 결과 확인: 핸들러와 메인 프로세스가 변경하는 데이터가 손상되지 않으면 성공.

4. 보안 테스트


입력 검증과 시그널 처리의 보안성을 확인하기 위해 악의적인 입력을 시뮬레이션합니다.

테스트 예제

  • 목표: 핸들러가 의도치 않은 입력으로 인해 비정상 종료되지 않는지 확인.
  • 방법: 입력 값으로 NULL 포인터, 매우 큰 값, 또는 비ASCII 문자 등을 제공.
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void signal_handler(int signal) {
    printf("Signal received: %d\n", signal);
}

int main() {
    signal(SIGUSR1, signal_handler);
    while (1) {
        printf("Waiting for SIGUSR1...\n");
        sleep(1);
    }
    return 0;
}
  • 결과 확인: 프로그램이 비정상 종료되지 않고 정상적으로 동작해야 합니다.

5. 스트레스 테스트


시그널이 빈번하게 발생할 경우에도 핸들러가 안정적으로 동작하는지 확인합니다.

테스트 방법

  • 목표: 짧은 시간 동안 다수의 시그널이 발생하더라도 프로그램이 중단되지 않는지 확인.
  • 방법: 스크립트를 사용하여 짧은 간격으로 여러 시그널을 발생시킵니다.
for i in {1..100}; do kill -SIGUSR1 <프로세스 ID>; done

요약


시그널 핸들링과 보안 테스트는 기본 동작 확인, 재진입 문제 방지, 데이터 경합 방지, 보안성 검증, 스트레스 테스트를 포함해야 합니다. 철저한 테스트를 통해 안정적이고 안전한 시그널 처리를 구현할 수 있습니다.

요약


본 기사에서는 C 언어에서 시그널 핸들링의 기본 개념과 방법, 그리고 보안 취약점을 방지하기 위한 전략과 코드 예제를 다뤘습니다. 또한, 재진입 문제, 데이터 경합, 중첩 방지와 같은 주요 이슈와 이를 해결하는 방법을 제시했습니다. 철저한 보안 테스트와 안전한 설계를 통해 C 언어 기반 애플리케이션에서 안정성과 보안성을 모두 확보할 수 있습니다.

목차