환경 변수 상속 (Environment Variable Inheritance)

Ⅰ. 환경 변수의 개념

1. 정의

환경 변수(Environment Variable)는 운영체제에서 프로세스가 실행될 때 참조할 수 있는 키-값(Key-Value) 쌍으로 이루어진 문자열이다. 프로세스는 부모 프로세스(Parent Process)로부터 환경 변수를 상속(Inherit)받으며, 이를 통해 실행 환경 설정을 자식 프로세스(Child Process)에 전달한다.

비유: 환경 변수는 "가족의 전통 레시피"다. 부모가 아이에게 전달하며, 아이는 그대로 사용하거나 자신만의 재료를 추가할 수 있다.

2. 프로세스 메모리에서의 환경 변수

프로세스 메모리 레이아웃 상 환경 변수의 위치

High Address
+---------------------------+
|         Stack             |
|   argv[0], argv[1], ...   |
|   envp[0], envp[1], ...  | <-- 환경 변수 포인터 배열
|   argc                     |
+---------------------------+
|          |                |  Stack grows downward
|          v                |
+---------------------------+
|        Heap               |  Heap grows upward
+---------------------------+
|       BSS                 |
|       Data                |
|       Text (Code)         |
+---------------------------+
Low Address

환경 변수 블록 (Environ Block):
+--------------------------------------+
| PATH=/usr/bin:/bin                   |
| HOME=/home/user                      |
| LANG=ko_KR.UTF-8                     |
| LD_LIBRARY_PATH=/opt/lib             |
| SHELL=/bin/bash                      |
| TERM=xterm-256color                  |
| NULL (terminator)                    |
+--------------------------------------+

비유: 환경 변수는 프로세스라는 "가방" 주머니에 넣어둔 "메모 쪽지"들이다. 프로그램은 언제든 주머니에서 꺼내 읽을 수 있다.


Ⅱ. 주요 환경 변수

1. 시스템 핵심 환경 변수

환경 변수설명예시
PATH실행 파일 검색 경로/usr/bin:/bin:/usr/local/bin
HOME사용자 홈 디렉토리/home/user
LANG로케일 및 언어 설정ko_KR.UTF-8
SHELL로그인 셸 경로/bin/bash
TERM터미널 유형xterm-256color
USER현재 사용자 이름ubuntu
PWD현재 작업 디렉토리/home/user/project
LD_LIBRARY_PATH동적 라이브러리 검색 경로/opt/oracle/lib
LD_PRELOAD사전 로드할 라이브러리/tmp/malloc_hook.so
TMPDIR임시 파일 디렉토리/tmp

2. PATH 환경 변수

  • PATH (Path): 셸이 명령어를 입력받았을 때 실행 파일을 검색하는 디렉토리 목록이다. 콜론(:)으로 구분되며, 왼쪽부터 순서대로 검색한다.
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# 현재 디렉토리를 PATH에 추가 (보안 주의)
$ export PATH=.:$PATH   # 위험: 현재 디렉토리의 악성 파일 실행 가능

3. 환경 변수 설정 방법

# 임시 설정 (현재 셸만)
export MY_VAR="hello"

# 영구 설정
echo 'export MY_VAR="hello"' >> ~/.bashrc

# 자식 프로세스에만 전달 (현재 셸에는 미설정)
MY_VAR="hello" /bin/child_program

# unset으로 제거
unset MY_VAR

Ⅲ. 환경 변수 API

1. C 표준 라이브러리 함수

함수원형설명
getenv()char *getenv(const char *name)환경 변수 값 조회
setenv()int setenv(const char *name, const char *value, int overwrite)환경 변수 설정 (overwrite=1이면 덮어씀)
putenv()int putenv(char *string)"NAME=VALUE" 형식 문자열로 설정 (버퍼 소유권 이전)
unsetenv()int unsetenv(const char *name)환경 변수 제거
environextern char **environ전체 환경 변수 배열에 대한 전역 포인터

2. 사용 예제

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

int main() {
    // 환경 변수 조회
    char *path = getenv("PATH");
    if (path) {
        printf("PATH: %s\n", path);
    }

    // 환경 변수 설정 (기존 값 있으면 덮어씀)
    setenv("MY_APP_DIR", "/opt/myapp", 1);

    // putenv: 문자열 포인터를 직접 전달 (주의: 버퍼 수정 시 문제)
    putenv("MY_TEMP_VAR=temp_value");

    // 환경 변수 제거
    unsetenv("MY_TEMP_VAR");

    // 전체 환경 변수 순회
    extern char **environ;
    for (char **env = environ; *env != NULL; env++) {
        printf("%s\n", *env);
    }

    return 0;
}
환경 변수 API 동작 흐름

[getenv("PATH")]
    |
    v
environ 배열에서 "PATH="로 시작하는 항목 검색
    |
    v
"PATH=/usr/bin:/bin"에서 '=' 이후 문자열 반환
    |
    v
포인터 반환 (복사본 아님, 수정하면 안 됨)

[setenv("KEY", "VALUE", 1)]
    |
    v
environ 배열에서 "KEY=" 검색
    |
    +-- 있고 overwrite=1 --> 기존 항목 대체
    |
    +-- 없음 --> environ 배열 확장 후 새 항목 추가

비유: getenv()는 "전화번호부에서 이름 찾기", setenv()는 "전화번호부에 새 번호 등록하기"와 같다. putenv()는 "직접 전화번호부 책갈피에 메모지 꽂기"라서 메모지를 분실하면 번호도 사라진다.


Ⅳ. fork()와 exec()에서의 환경 변수 상속

1. fork() 시 환경 변수 복사

fork() 시스템 콜은 부모 프로세스의 환경 변수 블록(Environ Block)을 자식 프로세스로 **복사(Copy)**한다. 부모와 자식은 독립적인 복사본을 가지므로, 한쪽에서 setenv()를 호출해도 다른 쪽에 영향을 주지 않는다.

fork() 시 환경 변수 복사

[Parent Process]                    [Child Process]
environ:                            environ:
  PATH=/usr/bin                       PATH=/usr/bin       (복사본)
  HOME=/home/user                     HOME=/home/user     (복사본)
  MY_VAR=parent                       MY_VAR=parent       (복사본)
        |                                    |
        | fork()                             |
        v                                    v
  setenv("MY_VAR", "changed", 1)       // 영향 없음
  MY_VAR=changed                      MY_VAR=parent (그대로)

2. exec() 시 환경 변수 대체

exec() 계열 함수는 현재 프로세스의 이미지를 새 프로그램으로 완전히 교체한다. 이때 환경 변수 처리는 함수에 따라 다르다.

함수환경 변수 동작
execl(path, arg...)기존 환경 변수 유지
execv(path, argv)기존 환경 변수 유지
execle(path, arg..., envp)새 envp로 대체
execve(path, argv, envp)새 envp로 대체
execlp(file, arg...)기존 환경 변수 유지 + PATH 검색
execvp(file, argv)기존 환경 변수 유지 + PATH 검색
exec() 계열의 환경 변수 처리

[execl("/bin/ls", "ls", NULL)]
    기존 environ 블록 그대로 유지
    --> ls가 부모와 동일한 환경 변수로 실행됨

[execle("/bin/ls", "ls", NULL, new_envp)]
    기존 environ 폐기
    --> new_envp 배열이 새 environ이 됨
    --> 부모의 환경 변수 완전히 사라짐

[execve("/bin/ls", argv, new_envp)]
    execle와 동일 (배열 형식)

비유: fork()는 "부모의 레시피 노트를 복사해서 아이에게 주는 것"이고, execle()는 "아이가 자기만의 새 레시피 노트로 교체하는 것"이다.


Ⅴ. 보안: LD_PRELOAD 공격

1. LD_PRELOAD의 동작 원리

  • LD_PRELOAD: 동적 링커가 다른 모든 라이브러리보다 먼저 로드하는 라이브러리 경로를 지정하는 환경 변수이다. 이를 이용해 표준 라이브러리 함수를 가로채거나(Hooking) 대체할 수 있다.

2. 공격 시나리오

// malicious.c - malloc을 가로채는 악성 라이브러리
#include <stdio.h>

void *malloc(size_t size) {
    printf("[ATTACK] malloc(%zu) intercepted!\n", size);
    // 원래 malloc 대신 임의 동작 수행
    return NULL;  // DoS 공격
}
# 악성 라이브러리 컴파일
gcc -shared -fPIC -o /tmp/malicious.so malicious.c -ldl

# 공격 실행
LD_PRELOAD=/tmp/malicious.so /usr/bin/victim_program
# victim_program의 모든 malloc 호출이 가로채짐!

3. 방어 수단

LD_PRELOAD 공격 방어 레이어

+------------------------------------------+
| 1. SUID/SGID 바이너리                     |
|    -> LD_PRELOAD 무시 (보안 설정)          |
+------------------------------------------+
| 2. /etc/ld.so.preload (관리자 전용)       |
|    -> 시스템 수준 preloading만 허용        |
+------------------------------------------+
| 3. setenv("LD_PRELOAD", "", 1)           |
|    -> 프로그램 시작 시 명시적 초기화        |
+------------------------------------------+
| 4. Docker/Container 격리                  |
|    -> 호스트 환경 변수 격리                |
+------------------------------------------+
| 5. Capabilities / SELinux                 |
|    -> 라이브러리 로딩 권한 제한            |
+------------------------------------------+

비유: LD_PRELOAD 공격은 "수업 전에 선생님의 교과서를 가짜 교과서로 몰래 바꿔치기하는 것"이다. SUID는 "출입증을 확인하는 교실 문지기" 역할을 한다.


요약

지식 그래프

환경 변수 상속
├── 기본 개념
│   ├── 키-값(Key-Value) 쌍
│   ├── 부모-자식 상속 (Inheritance)
│   └── environ 블록 (메모리의 Stack 영역)
├── 주요 환경 변수
│   ├── PATH (실행 파일 검색 경로)
│   ├── HOME (사용자 홈 디렉토리)
│   ├── LANG (로케일 설정)
│   ├── LD_LIBRARY_PATH (동적 라이브러리 경로)
│   └── LD_PRELOAD (사전 로드 라이브러리)
├── C API
│   ├── getenv() (조회)
│   ├── setenv() (설정)
│   ├── putenv() (포인터 직접 설정)
│   └── unsetenv() (제거)
├── 프로세스 생성 시 동작
│   ├── fork() -> 환경 변수 복사
│   ├── exec() -> 유지 또는 대체
│   ├── execl/execv -> 기존 환경 유지
│   └── execle/execve -> 새 envp로 대체
└── 보안
    ├── LD_PRELOAD 공격 (함수 가로채기)
    ├── SUID/SGID 바이너리 안전
    └── 컨테이너 격리

세 줄 설명 (어린이용)

  1. 환경 변수는 컴퓨터 프로그램에게 "어디에서 무엇을 찾아야 하는지" 알려주는 메모장이에요.
  2. 부모 프로그램은 이 메모장을 복사해서 자식 프로그램에게 주고, 자식은 필요한 내용을 바꿀 수 있어요.
  3. LD_PRELOAD는 나쁜 사람이 가짜 도구를 먼저 끼워 넣어서 프로그램을 속이는 나쁜 짓이에요, 그래서 방어가 필요해요.

약어 정리

약어Full Name
PATHPathname Variable
HOMEHome Directory
LANGLanguage and Locale
APIApplication Programming Interface
SUIDSet Owner User ID
SGIDSet Group ID