환경 변수 상속 (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) | 환경 변수 제거 |
| environ | extern 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 바이너리 안전
└── 컨테이너 격리
세 줄 설명 (어린이용)
- 환경 변수는 컴퓨터 프로그램에게 "어디에서 무엇을 찾아야 하는지" 알려주는 메모장이에요.
- 부모 프로그램은 이 메모장을 복사해서 자식 프로그램에게 주고, 자식은 필요한 내용을 바꿀 수 있어요.
- LD_PRELOAD는 나쁜 사람이 가짜 도구를 먼저 끼워 넣어서 프로그램을 속이는 나쁜 짓이에요, 그래서 방어가 필요해요.
약어 정리
| 약어 | Full Name |
|---|---|
| PATH | Pathname Variable |
| HOME | Home Directory |
| LANG | Language and Locale |
| API | Application Programming Interface |
| SUID | Set Owner User ID |
| SGID | Set Group ID |