- ASCII Code의 CRLF 제거 방법 (0)2009/09/04
- Linux / Unix Command: signal (0)2007/07/18
- 시스템 관리자를 위한 기초 명령어 활용법 (0)2007/06/22
- 프로세스정보 얻어오기 (0)2007/05/14
- 여러 가지 설정으로 공격으로부터 시스템을 안전하... (0)2007/05/10
34 Articles, Search for 'Unix & Linux/System'
- 2009/09/04 ASCII Code의 CRLF 제거 방법
- 2007/07/18 Linux / Unix Command: signal
- 2007/06/22 시스템 관리자를 위한 기초 명령어 활용법
- 2007/05/14 프로세스정보 얻어오기
- 2007/05/10 여러 가지 설정으로 공격으로부터 시스템을 안전하게 지키는 방법
- 2007/05/10 데이터 문제에 대한 차세대 NFS 계열 파일 시스템 대안
- 2007/05/10 inotify를 이용한 리눅스 파일 시스템 감시
- 2007/05/04 Secure programmer: 컴포넌트를 안전하게 호출하기
- 2007/05/04 리눅스에서의 메모리관리
- 2007/05/04 리눅스 시스템 콜 레퍼런스
NAME
signal - list of available signalsDESCRIPTION
Linux supports both POSIX reliable signals (hereinafter "standard signals") and POSIX real-time signals.Standard Signals
Linux supports the standard signals listed below. Several signal numbers are architecture dependent, as indicated in the "Value" column. (Where three values are given, the first one is usually valid for alpha and sparc, the middle one for i386, ppc and sh, and the last one for mips. A - denotes that a signal is absent on the corresponding architecture.)The entries in the "Action" column of the table specify the default action for the signal, as follows:
- Term
- Default action is to terminate the process.
- Ign
- Default action is to ignore the signal.
- Core
- Default action is to terminate the process and dump core.
- Stop
- Default action is to stop the process.
First the signals described in the original POSIX.1 standard.
| Signal | Value | Action | Comment |
| or death of controlling process | |||
| SIGINT | 2 | Term | Interrupt from keyboard |
| SIGQUIT | 3 | Core | Quit from keyboard |
| SIGILL | 4 | Core | Illegal Instruction |
| SIGABRT | 6 | Core | Abort signal from abort(3) |
| SIGFPE | 8 | Core | Floating point exception |
| SIGKILL | 9 | Term | Kill signal |
| SIGSEGV | 11 | Core | Invalid memory reference |
| SIGPIPE | 13 | Term | Broken pipe: write to pipe with no readers |
| SIGALRM | 14 | Term | Timer signal from alarm(2) |
| SIGTERM | 15 | Term | Termination signal |
| SIGUSR1 | 30,10,16 | Term | User-defined signal 1 |
| SIGUSR2 | 31,12,17 | Term | User-defined signal 2 |
| SIGCHLD | 20,17,18 | Ign | Child stopped or terminated |
| SIGCONT | 19,18,25 | Continue if stopped | |
| SIGSTOP | 17,19,23 | Stop | Stop process |
| SIGTSTP | 18,20,24 | Stop | Stop typed at tty |
| SIGTTIN | 21,21,26 | Stop | tty input for background process |
| SIGTTOU | 22,22,27 | Stop | tty output for background process |
The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
Next the signals not in the POSIX.1 standard but described in SUSv2 and SUSv3 / POSIX 1003.1-2001.
| Signal | Value | Action | Comment |
| SIGPOLL | Term | Pollable event (Sys V). Synonym of SIGIO | |
| SIGPROF | 27,27,29 | Term | Profiling timer expired |
| SIGSYS | 12,-,12 | Core | Bad argument to routine (SVID) |
| SIGTRAP | 5 | Core | Trace/breakpoint trap |
| SIGURG | 16,23,21 | Ign | Urgent condition on socket (4.2 BSD) |
| SIGVTALRM | 26,26,28 | Term | Virtual alarm clock (4.2 BSD) |
| SIGXCPU | 24,24,30 | Core | CPU time limit exceeded (4.2 BSD) |
| SIGXFSZ | 25,25,31 | Core | File size limit exceeded (4.2 BSD) |
Up to and including Linux 2.2, the default behaviour for SIGSYS, SIGXCPU, SIGXFSZ, and (on architectures other than SPARC and MIPS) SIGBUS was to terminate the process (without a core dump). (On some other Unices the default action for SIGXCPU and SIGXFSZ is to terminate the process without a core dump.) Linux 2.4 conforms to the POSIX 1003.1-2001 requirements for these signals, terminating the process with a core dump.
Next various other signals.
| Signal | Value | Action | Comment |
| SIGEMT | 7,-,7 | Term | |
| SIGSTKFLT | -,16,- | Term | Stack fault on coprocessor (unused) |
| SIGIO | 23,29,22 | Term | I/O now possible (4.2 BSD) |
| SIGCLD | -,-,18 | Ign | A synonym for SIGCHLD |
| SIGPWR | 29,30,19 | Term | Power failure (System V) |
| SIGINFO | 29,-,- | A synonym for SIGPWR | |
| SIGLOST | -,-,- | Term | File lock lost |
| SIGWINCH | 28,28,20 | Ign | Window resize signal (4.3 BSD, Sun) |
| SIGUNUSED | -,31,- | Term | Unused signal (will be SIGSYS) |
(Signal 29 is SIGINFO / SIGPWR on an alpha but SIGLOST on a sparc.)
SIGEMT is not specified in POSIX 1003.1-2001, but neverthless appears on most other Unices, where its default action is typically to terminate the process with a core dump.
SIGPWR (which is not specified in POSIX 1003.1-2001) is typically ignored by default on those other Unices where it appears.
SIGIO (which is not specified in POSIX 1003.1-2001) is ignored by default on several other Unices.
Real-time Signals
Linux supports real-time signals as originally defined in the POSIX.4 real-time extensions (and now included in POSIX 1003.1-2001). Linux supports 32 real-time signals, numbered from 32 (SIGRTMIN) to 63 (SIGRTMAX). (Programs should always refer to real-time signals using notation SIGRTMIN+n, since the range of real-time signal numbers varies across Unices.)Unlike standard signals, real-time signals have no predefined meanings: the entire set of real-time signals can be used for application-defined purposes. (Note, however, that the LinuxThreads implementation uses the first three real-time signals.)
The default action for an unhandled real-time signal is to terminate the receiving process.
Real-time signals are distinguished by the following:
- 1.
- Multiple instances of real-time signals can be queued. By contrast, if multiple instances of a standard signal are delivered while that signal is currently blocked, then only one instance is queued.
- 2.
- If the signal is sent using sigqueue(2), an accompanying value (either an integer or a pointer) can be sent with the signal. If the receiving process establishes a handler for this signal using the SA_SIGACTION flag to sigaction(2) then it can obtain this data via the si_value field of the siginfo_t structure passed as the second argument to the handler. Furthermore, the si_pid and si_uid fields of this structure can be used to obtain the PID and real user ID of the process sending the signal.
- 3.
- Real-time signals are delivered in a guaranteed order. Multiple real-time signals of the same type are delivered in the order they were sent. If different real-time signals are sent to a process, they are delivered starting with the lowest-numbered signal. (I.e., low-numbered signals have highest priority.)
If both standard and real-time signals are pending for a process, POSIX leaves it unspecified which is delivered first. Linux, like many other implementations, gives priority to standard signals in this case.
According to POSIX, an implementation should permit at least _POSIX_SIGQUEUE_MAX (32) real-time signals to be queued to a process. However, rather than placing a per-process limit, Linux imposes a system-wide limit on the number of queued real-time signals for all processes. This limit can be viewed (and with privilege) changed via the /proc/sys/kernel/rtsig-max file. A related file, /proc/sys/kernel/rtsig-max, can be used to find out how many real-time signals are currently queued.
CONFORMING TO
POSIX.1SEE ALSO
kill(1), kill(2), setitimer(2), sigaction(2), signal(2), sigprocmask(2), sigqueue(2)- ASCII Code의 CRLF 제거 방법 (0)2009/09/04
- Linux / Unix Command: signal (0)2007/07/18
- 시스템 관리자를 위한 기초 명령어 활용법 (0)2007/06/22
- 프로세스정보 얻어오기 (0)2007/05/14
- 여러 가지 설정으로 공격으로부터 시스템을 안전하... (0)2007/05/10
Leave your greetings.
01. 패턴을 찾아주는 grep(egrep, fgrep) 명령
grep은 입력에서 주어진 패턴을 포함하고 있는 줄을 찾아주는 명령이다. 많은 시스템 관리 명령들과 파이프(pipe)를 이용해서 사용할 수 있다. 흔히 사용하는 것은 프로세스 확인이다.
위와 같이 현재 시스템의 프로세스 중 httpd만 검색해 낸다. 만일 pipe를 이용하여 검색할 경우 검색 패턴이 한개 이상일 경우엔 egrep을 이용하여 검색할 수 있다.
이 밖에 grep는 file 내용 중에 특정 패턴이 들어 있는 행을 찾아서 출력해준다. option을 이용하여 보다 다양한 패턴을 선택하여 검색할 수 있다.
-v : 패턴을 포함하지 않는 행만 출력한다.
-n : 행 번호를 출력한다.
-l : 파일명만 출력한다.
-c : 패턴과 일치하는 라인의 갯수만 보여준다.
몇 가지 예를 들어 보자.
대표적인 사용 방법이다. 파일에 pattern이란 단어를 포함하는 행을 출력한다.
위는 공백을 제거한 파일 내용 살펴보기
위는 주석을 제거한 파일 내용 살펴보기를 의미한다. 이 밖에 패턴과 정확히 일치하는 것만을 찾아주는 fgrep가 있다. grep, egrep, fgrep의 옵션으로는 위의 옵션을 공통적으로 사용한다.
유용한 팁 하나를 소개해 보겠다. grep는 현재 디렉토리에 존재하는 파일만을 검색한다. 만일 현재 디렉토리와 그 하위 디렉토리까지 grep 패턴 검색을 하고자 할 땐 아래와 같이 find 명령을 이용하면 된다.
02 필터 역할을 하는 awk 명령
awk는 grep과 같이 출력된 문장에서 필요한 부분만 걸러내는 필터 역할을 하는 명령어의 일종이다. 사용하기에 따라서 매우 유용하다.

위의 출력문은 Tab(공백)으로 각 컬럼을 구분하는데 다음과 같이 하면 원하는 컬럼값만을 선택해서 출력이 가능하다.

위와 같이 1, 5, 9번째 컬럼값만 출력이 된다. 이 밖에 awk는 연산 기능을 가지고 있다.

이와 같이 webdizen 권한의 파일들의 크기를 합한 값을 보여준다.
이 명령 라인은 find로 검색한 파일을 ls -al 형식으로 출력한 후 여기서 바이트 수를 가지고 있는 7번째 열의 합계를 누적한다. 그리고 마지막 줄에 최종 결과값을 출력한다. 이 밖에 awk는 평균값도 구할 수 있다. 방법은 의외로 간단하여 END 절의 sum을 sum/NR로 대체하면 평균값이 나온다. NR은 awk내부 변수로서 현재까지의 입력 라인수를 갖는다.
03. 가장 많이 쓰이는 find 명령
find 명령어는 시스템 관리 명령 중 가장 많이 사용되는 명령어 중 하나이다. 이 명령어의 다양한 기능을 많이 알수록 관리자의 불필요한 작업량을 최대한 줄일 수 있을 것이다.
-mtime n : 정확히 n일 전에 수정된 파일
-newer [file] : file 보다 최근에 수정된 파일
-size n : 정확히 n x 512byte의 길이를 갖는 파일
-type c : 파일의 종류를 기술. f : 파일 d : 디렉토리
-fstype [filesystme] : 파일 시스템 종류
-name [pattern] : 파일 이름 검색
-perm p : 파일 접근 퍼미션이 p인 경우
-user [user] : 파일 소유권이 user인 파일
-group [group] : 파일 그룹이 group인 파일
-nouser : 파일 소유자가 /etc/passwd에 없는 경우
-nogroup : 파일 소유 그룹이 /etc/group에 없는 경우
-uid n : 파일 uid가 n인 경우
-gid n : 파일 gid가 n인 경우
단순히 위의 옵션대로 사용하면 그렇게 유용해 보이지 않을 수 있을 것이다. 예를 들어 누가 정확히 3일 전에 접근한 파일들으 검색할 것인가? 하지만 +, - 기호를 이용하여 기간, 시간, 크기와 같은 수치들의 범위를 지정할 수 있다.
-mtime +7 : 수정된 지 7일이 지난 파일
-mtime -7 : 수정된 지 7일이 안된 파일
-size +100 : 50kbyte 보다 큰 파일
-name 뒤엔 인용 부호(와일드 카드 문자)를 같이 사용할 수 있다.
-name *.dat : 확장자가 dat인 모든 파일
이런 옵션들을 차례로 기술하여 파일을 찾을 경우 AND 연산이 적용된다. 그리고 -o 옵션을 이용하여 OR을 적용할 수도 있고 ( )를 이용하여 그룹을 지어 적용할 수도 있다. 이 밖에 NOT 연산도 가능한데 옵션 앞에 ! 부호를 달아 주면 된다. 밑의 몇 가지 예를 살펴보자.
(AND 논리 연산으로 접근한지 60일 지난 파일 중 수정한 지 120일 지난 파일)
(OR 논리 연산으로 소유자나 그룹이 webdizen인 경우)

(NOT 논리 연산으로 소유자나 그룹이 webdizen이 아닌 경우)

옵션별 사용 예를 들어보자.
-perm 75 : permission = 755
-perm -002 : 모든 사람들이 기록할 수 있는 파일
-perm -4000 : SUID 엑세스 설정
-perm -2000 : SGID 엑세스 설정
그 외에도 지원되는 내부 옵션들이 있다.
-ls : 대응되는 파일에 대한 긴 디렉토리 목록을 출력
-exec commands : 대응되는 파일에 대해 commands 명령 수행
-ok commands : 파일에 대한 commands 명령 수행 전에 입력 대기 상태
-xdev : 검색 시작 디렉토리가 속해 있는 파일 시스템에 대해서만 검색 제한
-mount : IRIX와 SCO UNIX에서 -xdev 옵션
-prune : 서브 디렉토리는 검색을 하지 않음
-print 옵션은 근래는 기본으로 들어간다. 굳이 붙일 필요는 없고, -exec, -ok 옵션 사용 시에는 반드시 마지막에 \; 으로 구문을 마감해야 한다.
예를 들어서 find로 검색한 파일을 지우기 위해서는 다음과 같이 한다.
find 명령어를 이용한 시스템 관리 방법 예를 몇 가지 더 들어 보자.
파일 크기가 20M 이상이며 30일 이상 동안 수정되지 않은 파일을 찾는다.
find / -type f -size +20480 -mtime +30 -exec rm -f {} \; (대응되는 파일 삭제)
보안에 관련된 팁으로서, 모든 setuid, setgid를 검색한다.
find / -type f \( -perm -4000 -o -perm -2000 \) | diff -setuidlist
(검색된 setuid, setgid를 기존에 작성한 목록과 비교하여 새로 추가 된 것이 있는지를 확인한다.)
- ASCII Code의 CRLF 제거 방법 (0)2009/09/04
- Linux / Unix Command: signal (0)2007/07/18
- 시스템 관리자를 위한 기초 명령어 활용법 (0)2007/06/22
- 프로세스정보 얻어오기 (0)2007/05/14
- 여러 가지 설정으로 공격으로부터 시스템을 안전하... (0)2007/05/10
Leave your greetings.
오늘은 proc파일시스템을 이용해서 프로세스정보를 가져오는 프로그램을 만들어보도록하겠다.
1절. 소개
2절. proc 파일시스템
2.1절. proc 파일시스템에 대하여
2.2절. 프로세스 정보가져오기
3절. 나만의 ps제작
3.1절. 예제코드
3.1.1절. Makefile
3.1.2절. proc.h, proc.cc
3.1.3절. qps.h, qps.cc
3.1.4절. main.cc
3.2절. 테스트
4절. 결론
--------------------------------------------------------------------------------
1절. 소개
대부분의 Unix운영체제는 proc파일시템을 제공한다. proc는 process infomation pseudo의 줄임말이다. 즉 proc파일시스템은 커널에서제공하는 프로세스정보를 저장하고 있는 파일시스템이라고 정리할수 있다.
이 문서는 proc파일시스템을 이용해서 실제 프로세스목록을 출력하는 프로그램제작과 관련된 내용을 담고 있다. 문서는 리눅스운영체제(kernel 2.4.x)를 기준으로 작성되었다.
--------------------------------------------------------------------------------
2절. proc 파일시스템
2.1절. proc 파일시스템에 대하여
리눅스 커널의 주된임무중 하나는 프로세스를 실행시키고 이들을 유지하는 일이며, 이를 위해서 커널데이타구조체를 내부적으로 사용하게 된다. 그런데 이런 프로세스정보는 커널에게만 필요한게 아니고 시스템 관리자혹은 프로그래머들에게도 절대적으로 필요한 정보이다.
그렇다면 필요한 정보를 얻어내기 위해서 어떻게 해야할까? 직접 커널에게 커널데이타구조체를 요청해서 그걸 일일이 분석해야 할까 ? 물론 그렇게도 할수 있겠지만, 이것은 매우 복잡한 작업이며, 또한 (커널모드에서 직접이루어지는 작업임으로)위험한작업이기도 하다. 그래서 리눅스시스템은 사용자레벨에서 프로세스의 상태를 간단하게 확인가능하도록 하기위해서 proc파일시스템을 제공한다.
우리는 복잡하게 커널로부터 여러가지 커널데이타구조체를 요청할필요 없이 proc파일시스템에서 제공하는 정보들을 읽어들이는 정도로 간단하게 프로세스 상태를 얻어올수 있다.
작은 정보: proc파일시스템은 대부분의 유닉스운영체제에서 채택되어지고 있지만, 데이타를 저장하는 범위와 포맷에 있어서 운영체제간 차이점을 보인다. 저장하는 범위의 경우 대부분의 유닉스운영체제는 단지 프로세스 정보만을 제공하는 반면, 리눅스는 프로세스 정보뿐만 아니라 네트웍정보, 파일시스템, 디바이스 정보, 메모리정보, CPU정보등 다양한 정보들을 제공한다.
특히 리눅스의 경우 몇몇정보들에 대해서는 단지 열람만 가능한 수준이 아닌 직접수정을 통해서 커널의 행동을 변경시켜줄수도 있다. 이것은 다른 유닉스에 비해서 매우 확장된 부분이라고 할수 있다.
예를 들어 ICMP요청에 대한 응답을 막고 싶다면 "echo 0 /proc/sys/net/ipv4/icmp_echo_ignore_all"하는 정도로 간단하게 커널의 행동을 변경시켜줄수 있다. 다른운영체제에서의 이러한 작업은 전용관리도구를 사용하든지 리붓팅을 시키든지 해야한다.
저장되는 포맷을 보자면 리눅스는 일반 ASCII문자로 이루어진 반면 다른 유닉스들은 구조체로 정보가 이루어져 있다. 리눅스의 경우 프로세스 정보가 일반문자로 이루어져 있어서 직관적으로 확인하기에 좋기는 하지만 프로그래밍을 할경우 이를 파싱해야되기 때문에 다른 유닉스들에 비해서 좀 불편한점이 되기도 한다.
--------------------------------------------------------------------------------
2.2절. 프로세스 정보가져오기
리눅스의 경우 기능이 확장되긴 했지만 proc파일시스템의 가장큰 사용목적은 뭐니뭐니 해도 프로세스정보를 얻어오는 일이다.
기본적으로 proc파일시스템은 /proc디렉토리안에서 이루어지며, 프로세스정보는 /proc디렉토리밑에 각 프로세스의 PID를 이름으로하는 서브디렉토리 밑에 위치하게 된다. 예를들어 PID가 912인 프로세스라면, 912 프로세스의 정보는 /proc/912(이하 /proc/[PID])밑에 위치하게 된다.
/proc/[PID] 디렉토리밑에는 다시 몇개의 디렉토리와 몇개의 파일들로 이루어져 있다. /proc/[PID]/ -+-- cmdline
|
+-- cwd
|
+-- environ
|
+-- exe
|
+-- fd -------+-- 0
| |
+-- maps +-- 1
|
+-- root
|
+-- stat
|
+-- statm
|
+-- status
리눅스의 경우 위와 같은 파일들로 이루어져 있다. 각 파일이 가지고 있는 자세한 정보들에 대해서는 proc의 man페이지를 참고하기 바란다.
리눅스의 경우 각각의 정보들은 일반 ASCII텍스트문자로 이루어져있고, 대부분의 경우 공백문자(' ')로 필드의 구분이 되어있음으로, 쉽게 원하는 정보들을 얻어올수 있다.
--------------------------------------------------------------------------------
3절. 나만의 ps제작
프로세스정보를 확인하기 위해서 리눅스는 ps라는 도구를 제공한다. ps를 사용함으로써, 우리는 프로세스의 각종중요한 정보들을 얻어오고, 얻어온 정보는 시스템관리와 프로그래밍을 위한 중요한 데이타로 사용한다.

우리가 얻고자하는 프로세스데이타는 다음과 같다.
프로세스의 실행유저
프로세스 아이디(PID)
부모프로세스 아이디(PPID)
부모프로세스 상태(Zombie, Run, Sleep 등)
프로세스 이름
CPU 사용율(%)
VMEM(가상메모리) 사용율
ps에 비해서 몇가지 빠진것들이 있긴하지만 프로세스를 관리하는데 필요한 최소한의 정보는 가져온다.
이 ps도 기본적으로 proc파일시스템에있는 프로세스정보를 이용해서 가져온다. 더 정확히 말하자면 stats에서 필요한 정보를 가져온다. 다음은 실제 stats의 파일내용이다. 원래는 하나의 행으로 되어있으나 출력하기 쉽게 여러개의 행으로 분리했다. [root@localhost 2489]# cat stat
2489 (vi) T 2251 2489 2251 772 2581 0 187 0 455 0 12 4 0 0 9 0 0 0 181334
6950912 0 4294967295 134512640 136413760 3221223720 3221222316
1074893473 0 0 12288 1333808895 3222310480 0 0 17 0
우리가 만들고자하는 프로그램은 위의 stat 정보를 분석하게 될것이다.
--------------------------------------------------------------------------------
3.1절. 예제코드
프로그램의 이름은 qps로 하도록 하겠다.
이프로그램의 쏘쓰는 몇개의 모듈로 이루어져 있으며, 쏘쓰관리를 위해서 Makefile을 사용할것이다. 다음은 만들고자 하는 qps의 쏘쓰트리 구조이다. -+-- Makefile
|
+-- main.cc
|
+-- proc.cc
|
+-- qps.cc
|
+-- include ----+-- proc.h
|
+-- qps.h
다음은 각 파일들에 대한 설명이다.
표 1. qps 쏘쓰파일 설명
Makefile make에서 사용할 make rule 파일
main.cc main함수를 포함하는 코드, 최소한의 코드만을 가진다
proc.cc 실제 proc파일시스템을 참조해서 각종 프로세스정보를 얻어온다.
qps.cc proc.cc에 정의된 함수를 호출하여 프로세스정보를 얻어오고 이를 화면에 보기좋게 출력한다.
include/proc.h proc.cc에서 사용될 함수선언
include/qps.h qps.cc에서 사용될 함수선언
--------------------------------------------------------------------------------
3.1.1절. Makefile
쏘쓰코드들을 관리하기 위한 Makefile이다. 이해하는데 별다른 어려움은 없을것이다.
--------------------------------------------------------------------------------
3.1.2절. proc.h, proc.cc
실질적으로 stat를 분석해서 프로세스데이타를 얻어오는 함수들을 포함한다. opendir(2)함수를 이용해서 /proc 디렉토리밑에 있는 파일들의 목록을 얻어오고, 만약 얻어온 파일이 디렉토리이면서 숫자로되어있을경우 프로세스정보 디렉토리라고 판단하고, 서브디렉토리에 있는 stat 파일을 읽어들인다.
읽어들인 stat정보는 " "를 기준으로 파싱해서 배열(vector)에 집어넣는다. 더불어 우리가 만들고자하는 qps프로그램은 해당프로세스의 유저이름도 가져와야 한다. /proc/[PID]/stat 파일은 프로세스소유자의 권한으로 만들어진다. 우리는 stat(2)계열함수를 사용하면 해당 파일의 UID를 얻어올수 있다는걸 알수있다. 또한 getpwuid(3)를 이용하면 해당 UID에 대한 유저이름도 얻어올수 있다.
이렇게 해서 하나의 프로세스에 대한정보가 만들어졌다. 그런데 우리는 프로세스의 목록을 가져와야 함으로 이들 정보는 다시 배열의 원소로 들어가야 할것이다. 이러한 자료구조(배열의 배열)를 위해서 필자는 (속편하게)vector를 사용했다.
다음은 실제 코드들이다. 위의 내용들은 코드를 통해서 이해하기 바란다. 그리어려운 코드들은 아님으로 주석만으로도 충분히 이해가능할것이다.
예제 : include/proc.h
--------------------------------------------------------------------------------
3.1.3절. qps.h, qps.cc
위에서 설명한 proc.cc를 통해서 stat를 분석한 프로세스데이타가 만들어졌음으로 이제 이것을 가지고 와서 화면에 적당히 뿌려줘야 할것이다. 다음은 이와 관련된 함수들이다. 프로세세의 CPU사용율을 가져오기 위한 getcputime()함수외에는 별특별한건 없을것이다.
include/qps.h
qps.cc // Local 헤더파일
--------------------------------------------------------------------------------
3.1.4절. main.cc
main함수다. 더이상 설명할 필요도 없는 간단한 코드이다.
예제 main.cc
--------------------------------------------------------------------------------
3.2절. 테스트
컴파일은 make를 이용하면 된다. 다음은 우리가 만든 프로그램을 실행시킨 화면이다.

그럭저럭 잘돌아가는걸 확인할수 있을것이다.
--------------------------------------------------------------------------------
4절. 결론
이상 proc파일시스템을 이용해서 어떻게 프로세스정보를 얻어오는지에 대해서 알아고, 이 얻어온정보를 가공해서 실제 관리자나, 프로그래머에게 유용한 정보로 만드는 방법을 알아보았다.
시간이 남는다면 몇가지 다른 부가적인 시스템정보까지 포함시켜서 Top와 같은 좀더 강력한 프로그램을 만드는것도 재미있을것이다. 혹은 QT, GTK등을 이용해서 GUI환경에서 작동하는 시스템프로세스 모니터링 프로그램을 만들수도 있을것이다.
출처 : http://joinc.co.kr/modules.php?name=new ··· 3Dnested
- Linux / Unix Command: signal (0)2007/07/18
- 시스템 관리자를 위한 기초 명령어 활용법 (0)2007/06/22
- 프로세스정보 얻어오기 (0)2007/05/14
- 여러 가지 설정으로 공격으로부터 시스템을 안전하... (0)2007/05/10
- 데이터 문제에 대한 차세대 NFS 계열 파일 시스템... (0)2007/05/10
Leave your greetings.
난이도 : 초급
Mario Eberlein, IT 아키텍트, IBM Global Services
Rene Auberger, 소프트웨어 IT 아키텍트, IBM Software Group
Wolfram Andreas Richter, IT Architect, IBM Software Group
2005년 4 월 14 일
공격에 대한 저항력이 강한 리눅스 시스템을 만드는 기술을 배워보자. 부트 프로세스와 로컬 파일시스템 보안, 서비스와 데몬 잠금, 할당량과 한계 적용, Mandatory Access Control 실행, 새로운 소프트웨어로 보안을 업데이트 할 때 발생할 수 있는 보안 취약성 인식 등을 다룬다. Part 1에서는 보안 개념과 잠재적 위험을 Part 2에서는 보안 설치를 계획할 때 고려해야 할 사항들을 설명한다.
이 시리즈를 통해서, 리눅스를 실행하는 시스템의 계획 수립, 디자인, 설치, 설정, 관리 방법을 설명한다. 보안 개념, 설치 문제, 잠재적 위협과 남용 등에 대한 이론적인 설명 외에도 리눅스 시스템의 보안과 하드닝 등 실용적인 방법도 제시한다. 최소한의 설치, 리눅스 설치 하드닝, 권한/인증, 로컬 및 네트워크 보안, 공격과 방어 방법, 데이터 보안, 바이러스, 악성 프로그램 등을 논할 것이다.
Part 1은 보안 개념과 잠재적 위협에 대한 문제로 시작한다. Part 2에서는 보안 설치를 계획할 때 고려해야 할 사항들을 설명한다. 상세한 액션 플랜도 포함된다.
이번 시간에는 리눅스 하드닝 단계를 설명하겠다.
하드닝(hardening)에 대하여
성공적인 하드닝을 위해서는 다음과 같이 해야 한다:
공격을 피하기 위해서 시스템이 네트워크로 연결되기 전에 하드닝을 수행한다.
최소 권한 모델(least-privilege model)설정:시스템은 해당 기능에 필요한 정도로만 액세스를 허용해야 한다. 마찬가지로, 사용자들은 최소한의 접근 권한만 가져야 한다..
사전 플래닝과 준비를 완료하고 최소한의 설치(Part 2 참조)를 마친 후에는 여러 가지 설정 단계들을 고려해야 한다. 다음 단계는 리눅스 하드닝에 일반적으로 참조된다:
부트(boot) 프로세스 보안
서비스와 데몬 보안
로컬 파일시스템 보안
할당량과 한계 적용
Mandatory Access Control 실행
보안 패치 업데이트 및 추가
부트(boot) 프로세스 보안
부트 로더(LILO 또는 Grub)를 설정하여 부팅 시 어떤 사용자의 개입도 허용되지 않도록 한다. 다시 말해서, 사용자가 부트 프롬프트에서 커널 매개변수를 패스하지 못하도록 한다. (원격 데이터센터 같은 곳에서) 원격 재부팅을 수행하는 것이 아닌 이상 패스워드를 넣도록 설정한다. 이는 머신에 물리적으로 접근하는 사람들에게 주는 사전 경고이다. 루트 쉘을 얻기 위해 single 또는 init=/bin/sh를 이용하는 부팅을 방지한다. (하드 드라이브를 제거하거나 다른 시스템에 하드 드라이브를 마운트 하는 등의) 추가적인 시도를 하면, 파일 시스템을 암호화 하지 않은 한 쉽게 피할 수 있다.
LILO의 경우 매개변수 prompt를 설정 파일(일반적으로 /etc에 있음)에 있는 password로 대체시켜라. Grub의 경우, 관련 매개변수는 Grub 설정 파일(/boot/grub/grub/conf)에 있는 hiddenmenu, default 0, password이다.
실행 레벨 설정에는 sp:S:respawn:/sbin/sulogin을 /etc/inittab에 추가하여 단일 사용자 모드로 전환할 때 루트 패스워드가 필요하다.
사용자가 Ctrl-Alt-Del을 사용하여 재부팅을 하지 못하도록 한다: ctrlaltdel주석을 달아 /etc/inittab에 ctrlaltdel 엔트리가 실행되지 못하도록 한다. #ca::ctrlaltdel:/sbin/shutdown -t5 -rf now 처럼 해시(#) 표시를 추가하여 키 조합을 이용한 재부팅을 방지할 수 있다.
서비스와 데몬 보안
서비스의 보안 설정의 첫 단계는 필요하지 않는 모든 서비스들을 실행불가 상태로 만드는 것이다. 제공되지 않는 서비스들은 침입자들이 악용할 수 없기 때문에 위험을 줄일 수 있다.
모든 실행 가능한 서비스들을 감추려면 여러 위치를 검사하면 된다. 불안정한 서비스들의 기능을 억제하고 보다 안전한 옵션으로 대체한다. 예를 들어, telnet은 암호화 되지 않기 때문에 암호화된 ssh 서비스를 사용한다. (Part 2 참조)
서비스 보안을 위해서는 다음 요소들을 고려해야 한다:
/etc/inittab
/etc/init.d의 부트 스크립트
inetd/xinetd 데몬
TCP 래퍼
방화벽
/etc/inittab
부트 프로세스 동안 init 프로세스는 /etc/inittab 파일에 있는 엔트리들을 읽는다. 라인 당 한 개씩 있는 각 엔트리는 특정 상황에서 어떤 프로그램이 실행될 것인지를 정의한다. 그와 같은 프로그램은 서비스 그 자체 이거나 서비스를 시작 및 종료하는데 사용된다.
init 프로세스는 하나의 문자로 구별된 여러 상태들(소위 말해 실행 레벨(run level), 을 구별한다. 실행 레벨이 나타나거나 (전원 오류 같은) 특정 이벤트가 발생하면 엔트리들은 평가되고 해당 명령어들이 실행된다.
/etc/inittab에 있는 엔트리 포맷은 이 엔트리용 레이블이고, 엔트리가 처리되는 실행 레벨,그 다음에는 액션 키워드, 명령행 매개변수를 포함하여 실행될 명령어 순으로 된다. 이 모든 필드는 콜론으로 구별되며 전형적인 엔트리는 다음과 같다:
my_service:35:once:/usr/local/bin/my_service someparameter
(inittab의 맨 페이지(man-page)에서 액션 키워드의 리스트를 참조하라.)
이 예제에서, 이 엔트리의 레이블은 my_service이다. 실행 레벨3 또는 5가 되면 매개변수 someparameter를 가진 /usr/local/bin/my_service 프로그램이 실행된다. 일단 프로그램이 종료되면 다시 시작되지 않는다. (액션 키워드 “once”)
안전한 리눅스 시스템을 위해 /etc/inittab의 모든 엔트리 기능을 이해해야 하며, 엔트리를 삭제하거나 라인 앞에 해시 사인을 사용하여 주석을 달아 원치 않는 서비스 기능을 없앤다.
모든 리눅스 시스템에 공통적인 두 가지 유형의 엔트리가 있다. 하나는 /sbin/getty 라고 하는 프로그램을 시작하는데 사용된다. 이것은 리눅스 가상 콘솔이나 시리얼 라인상에서 로그인을 허용하는데 사용된다. 또 다른 유형은 매개 변수로서 현재 실행 레벨을 갖고 있는 /etc/rc.d 디렉토리의 rc 라는 스크립트를 시작한다. 이 스크립트는 서비스의 시작과 종료를 제어한다. (다음에 설명하겠다.)
/etc/init.d의 부트 스크립트
/etc/init.d에 있는 부트 스크립트는 시스템 서비스를 시작 및 정지하는데 사용된다. 각 실행 레벨의 경우 실행 레벨 변경 시 호출될 부트 스크립트로의 소프트 링크가 포함되어 있는 /etc/rcN.d 디렉토리가 있다. (“N”은 실행 레벨을 나타낸다.)
링크 이름이 “S”로 시작한다면 이 스크립트는 상응하는 서비스를 시작하기 위해 실행 레벨을 입력할 때 실행된다; “K”로 시작하면 서비스 중지를 위해 실행 레벨을 종료할 때 스크립트가 실행된다.
대부분, 부트 스크립트의 이름을 보면 어떤 서비스가 제어되는지 알 수 있다. 서비스가 특정 실행 레벨에서 시작되지 않도록 하려면 실행 레벨 하위 디렉토리에서 상응하는 부트 스크립트 까지의 링크를 삭제하고 아무것도 수행하지 않는 더미 스크립트로 /etc/init.d에 있는 원래 부트 스크립트로 대체한다.
inetd/xinetd 데몬
클라이언트가 요청하면 서비스들은 호출될 수 있다. 이 요청들은 수퍼 데몬인 inetd 또는 xinetd로 간다. 수퍼 데몬은 어떤 서비스를 시작할 것인지를 결정하고 요청을 상응하는 데몬에 보낸다. 일반적으로 telnet, ftp, rlogin 같은 서비스들은 inetd 또는 xinetd를 사용하여 시작된다.
inetd 데몬은 에서 설정된다. 여기에는 수퍼 데몬에서 제공하는 각 서비스에 대한 엔트리가 포함된다. FTP 서버를 설정하는 엔트리는 ftp stream tcp nowait root /usr/bin/ftpd in.ftpd -el과 같다. 그리고 마찬가지로 해시 사인을 사용하여 주석을 달아 기능을 못하도록 한다.
보안을 위해 xinetd를 사용하라. inetd와 달리 xinetd는 rpc 기반 서비스들을 시작할 수 있고 액세스 제어도 제공한다. xinetd는 인커밍 커넥션의 비율, 특정 호스트로 부터의 인커밍 커넥션의 수, 한 서비스를 위한 총 커넥션의 수 등을 제한할 수 있다.
xinetd는 각 하위 데몬에 대한 개별 설정 파일에 의해 설정된다. 이 파일들은 /etc/xinetd.d/에 위치해 있다. 위 FTP 서버용 설정 파일 예제는 /etc/xinetd.d/ftp 일 것이고 다음과 같다:
Listing 1. 설정 파일, /etc/xinetd.d/ftp
service ftp
{
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/bin/ftpd
server_args = -el
disable = yes
}
서비스 기능을 없애려면 disable 매개변수를 이전 예제에서 yes로 설정한다.
보다 세련된 액세스 제어를 위해 xinetd는 다음 세 개의 매개변수들을 지원한다:
only_from
no_access
access_time
ftp 데몬 기능을 완전히 억제하지 않고 액세스만 제한하려면 다음과 같이 /etc/xinetd.d/ftp config 파일을 변경하면 된다:
Listing 2. 액세스를 제한하도록 변경된 /etc/xinetd.d/ftp 설정 파일
service ftp
{
socket_type = stream
protocol = tcp
wait = no
user = root
server = /usr/bin/ftpd
server_args = -el
disable = no
only-from = 192.168.200.3 192.168.200.7 192.168.200.9
only-from += 192.168.200.10 192.168.200.12 172.16.0.0
no_access = 172.16.{1,2,3,10}
access_times = 07:00-21:00
}
only-from과 no_access는 숫자로 된 IP 주소(맨 오른쪽에 있는 0은 와일드카드로 간주됨), IP 주소/넷마스크 범위, 호스트 네임, /etc/networks의 네트워크 이름을 허용한다. only-from과 no_access의 조합이 사용되면 xinetd는 각 호스트 연결과 가장 근접한 매치를 찾는다.
이전 코드 예제의 경우, IP 주소 172.16.x.x를 가진 호스트는 172.16.1.x, 172.16.2.x, 172.16.3.x, 172.16.10.x에 있는 주소를 가진 호스트를 제외하고는 연결될 수 있음을 의미한다. no_access에서 처럼 인자 표시를 사용하면 네 개의 모든 조수의 컴포넌트를 지정할 필요가 없다. 인자로 된 부분은 주소의 가장 오른쪽 엘리먼트가 되어야 한다. (xinetd와 설정은 참고자료 참조)
TCP 래퍼
xinetd 대신 inted를 사용하기로 결정했다면 TCP 래퍼를 사용하여 특정 네트워크를 수락/거절하고, 요청을 기록할 수 있다. TCP 래퍼는 권한 부여와 로깅을 위해 /etc/hosts.allow와 /etc/hosts.deny를 평가하고 이것에 직접 응답하지 않고 클라이언트 요청을 래핑한다. 일단 성공적인 권한 부여가 되면 요청은 원리 요청된 서비스로 포워딩 된다.
TCP 래퍼를 사용하면 inetd를 사용할 때 보다 더 나은 점이 있다:
요청 클라이언트는 TCP 래퍼를 인식하지 못한다. 따라서 운이 좋은 사람들은 어떤 차이도 느끼지 못하고 운이 나쁜 사람들은 왜 요청이 실패했는지에 대한 아무런 정보도 받지 못한다.
TCP 래퍼는 래핑된 서비스와 상관없이 작동하면서 애플리케이션들이 설정 파일들을 공유하게 하여 관리 작업을 용이하게 한다.
TCP 래퍼 설정 파일 관련 문서는 Red Hat Linux Reference Guide를 참조하라. (참고자료)
world-writable 파일 찾는 방법
world-writable 파일을 찾으려면 다음 명령어를 사용한다:
find / -perm -002 \( -type f -o -type d \) -ls
풀이:
/는 검색의 출발선이다.
-perm은 허가 여부를 검사한다.
002는 (8진법 표기) "other"용 쓰기 비트 설정이다.
002 모드 앞에 있는 -는 모든 허가 비트가 설정됨을 의미한다. (zero 비트는 무시된다).
-type f 또는 -type d는 정식 파일 또는 디렉토리를 찾는다
-ls는 -ls 포맷에서 찾은 파일들을 나열한다.
방화벽
실행되지 않는 서비스나, 인터넷 같은 특정 네트워크로 접근 될 수 없는 서비스들과의 원치 않는 통신을 막으려면 방화벽 설치가 권장된다. 방화벽은 신용 레벨에 기반하여 네트워크들 간 통신을 제어하고, 최소 권한 원리를 적용하여 규칙 기반 보안 정책을 사용하는 특정 서비스들로의 액세스를 허용 또는 거부한다.
방화벽 설치와 설정은 매우 복잡한 주제이기 때문에 이 글에서는 다루지 않겠다.
로컬 파일시스템 보안
로컬 파일시스템의 보안은 파일과 디렉토리의 소유권과 접근 권한을 다루는 문제이다. 파일시스템을 보안화 하려면 파일과 디렉토리의 보호 비트(protection bit)가 최소 접근만 허용하도록 설정되어야 한다.
world-writable 파일과 시스템 디렉토리 그리고 setuid 또는 setgid명렁어와 관련된 부적절한 허가는 특별한 주의가 필요하다. 이 명령어를 실행하는 사용자가 실제로 갖고 있는 것 보다 더 높은 사용자 권한으로 실행된다. 오직 루트만 접근 할 수 있는 파일에 접근해야 할 수도 있다. (/bin/passwd가 /etc/passwd에 접근해야 하는 것 처럼). 각 명령어들은 setuid/setgid 비트 세트가 반드시 필요하다. 그런 경우가 아니면 사용하지 말라
파티션 상의 모든 파일들이 setuid/setgid비트를 필요로 하지 않을 때, /etc/fstab의 nosuid 옵션은 상응하는 파일 시스템상의 각 파일들을 위해 이를 정지시킨다. 다음 예제의 경우 (/dev/hdc1):
#device mountpoint filesystemtype options dump fsckorder
/dev/hda1 / ext2 defaults 1 1
...
/dev/hdc1 /mnt/cdrom iso9660 nosuid,user 1 2
더욱이, 민감한 데이터의 경우 그 데이터를 암호화하고 패스워드로 보호해야 함은 더 말할 나위 없다. GnuPG는 이 용도에 맞는 패키지이다.
할당량과 한계 적용
Linux PAM (Pluggable Authentication Modules)은 어느 정도 유용한 제한 기능이 있다. /etc/security/limits.conf 파일에서 설정될 수 있다. 이 제한은 싱글 세션에 적용된다는 것을 염두해야 한다. maxlogins옵션으로 전체적인 제한을 관리할 수 있다. limits.conf 내의 엔트리들은 다음의 구조를 갖는다: username|@groupname type resource limit
그룹 이름 앞에는 반드시 @이 선행되어야 한다. 사용자 이름과 구별하기 위해서이다. 유형은 soft 또는 hard가 되어야 한다. soft-limit은 초과될 수 있고 일반적으로 경고 표시인 반면, hard-limit은 초과될 수 없다. resource는 이들 키워드들 중 하나이다:
core - 핵심 파일의 사이즈 제한(KB).
data - 최대 데이터 사이즈(KB).
fsize - 최대 파일 사이즈 (KB).
memlock - 최대 고정 메모리 주소 공간 (KB).
nofile - 최대 개방 파일의 수.
rss - 레지던트 세트의 최대 사이즈 (KB).
stack - 최대 스택 사이즈 (KB).
cpu - 최대 CPU 시간 (분).
nproc - 최대 프로세스 수.
as - 주소 공간 한계.
maxlogins - 사용자에게 허용된 최대 로그인 수.
다음 코드 예제에서, 모든 사용자들은 세션 당 10 MB로 제한되고 총 4개의 동시 로그인이 허용된다. 세 번째 줄은 모든 사람들에게 core 덤프를 불가능하게 한다. 네 번째 줄은 사용자 bin에 대한 모든 제한을 제거한다. ftp는 10 개의 동시 세션을 가질 수 있다. (이는 익명의 ftp어카운트에 특히 유용하다); managers그룹의 멤버들은 40 프로세스로 제한된다. developers는 64 MB의 memlock으로 제한되며 wwwusers의 모든 멤버들은 50 MB 보다 큰 파일을 만들 수 없다.
Listing 3. 할당과 한계 설정
* hard rss 10000
* hard maxlogins 4
* hard core 0
bin -
ftp hard maxlogins 10
@managers hard nproc 40
@developers hard memlock 64000
@wwwusers hard fsize 50000
할당 예제
모든 파티션 사용자들을 위한 할당은 쓰기가 허용되어야 한다. 시스템이 인간 사용자 대신 애플리케이션에 대한 사용자 ID를 갖는 것도 고려해야 한다. 그러한 ID들은 사람들이 접근할 수 없는 디렉토리에 쓰기 접근을 갖고 있다.
/sbin/quotacheck -avug를 cronjobs에 추가하여 할당 파일과 커널에서 현재 사용하고 있는 테이블을 자동으로 업데이트 하도록 한다.
이 한계를 활성화하기 위해 다음 라인을 /etc/pam.d/login의 밑에 추가해야 한다: session required /lib/security/pam_limits.so.
할당은 inode의 수와 사용자 및 그룹의 소비 공간을 제한한다. 할당은 마운트 포인트 별로 정의되어 사용자가 여러 파티션에 쓰기 권한을 가지면 이들 각자에 대한 할당을 정의해야 한다.
할당은 하드 드라이브의 모든 사용 공간을 채우는 DoS 어택의 위험을 최소화하는 관리자들의 방식이다. (다른 프로세스들이 임시 파일을 만들지 못하게 하여 실패하게끔 한다). 사용하고 있는 배포판에 따라 내장된 할당 툴을 설치할 수 있고 직접 다운로드, 컴파일, 설치할 수 있다. (참고자료)
할당은 커널에서 실행되어야 한다. 오늘날 대부분의 배포판들은 할당 지원이 된다. 자신의 배포판에서 할당을 실행할 수 없다면 참고자료를 참조하라.
파일시스템용 할당을 실행하려면 /etc/fstab의 상응하는 라인에 옵션을 추가해야 한다. usrquota와 grpquota를 사용하여 사용자 할당과 그룹 할당을 실행한다. (Listing 4):
Listing 4. 사용자 할당과 그룹 할당 실행하기
/dev/hda1 / ext3 defaults 1 1
/dev/hda2 /home ext3 defaults,usrquota 1 1
/dev/hda3 /tmp ext3 defaults,usrquota,grpquota 1 1
/dev/hda4 /shared ext3 defaults,grpquota 1 1
/dev/hdc1 /mnt/cdrom iso9660 nosuid,user 1 2
그런 다음, 상응하는 파일시스템들을 다시 마운트하여 mount -a -o remount로 새롭게 추가된 옵션을 활성화한다; 그런 다음 quotacheck -cugvm명령어를 사용하여 바이너리 할당 파일을 만든다. 여기에는 머신이 읽을 수 있는 포맷에 할당 설정이 포함된다.
할당 설정은 edquota툴을 사용하여 수행된다. 사용자 alice용 제한을 설정하려면, edquota -u alice로 이것을 호출한다. 환경변수 EDITOR(디폴트는 vi)로 정의된 에디터는 비슷한 콘텐트로 연다:
Quotas for user alice:
/dev/hda2: blocks in use: 3567, limits (soft = 5500, hard = 6500)
inodes in use: 412, limits (soft = 1000, hard = 1500)
"in use" 값은 정보를 목적으로 있는 것이기 때문에 변경될 수 없다. soft와 hard 한계가 변경할 수 있는 유일한 값이다. 에디터를 저장하고 종료한 후에 edquota는 편집했던 임시 파일을 읽고 그 값을 바이너리 할당 파일로 변환하여 변경사항을 적용한다. 그룹 할당을 편집은 -u 대신 -g옵션을 사용해야 한다는 것을 제외하고는 작동은 같다.
soft 한계는 초과될 수 있는 경고 레벨인 반면 hard 한계는 엄격하게 실행된다. soft 한계는 grace period이다. (가끔 soft time 한계라고 불린다); 이는 사용자에게 허용된 시간 길이가 시스템에 의해 실행될 때 까지 soft 한계를 초과할 수 있다.);
grace period를 edquota -t로 설정할 수 있다. 사용할 수 있는 단위는 초, 분, 시간, 일, 주, 달 등이다. 할당을 관리하는 기타 유용한 툴들로는 repquota (파일시스템용 할당 요약), quotaon, quotaoff (할당을 on/off로 변환)등이 있다.
Mandatory Access Control 실행
Mandatory Access Control 또는 MAC으로 SELinux를 구현하여 추가적인 보안 문제를 해결할 수 있다. MAC을 사용하면 OS의 허가가 사용자/그룹 ID에 의해 제어된다. 또한 MAC을 사용하여 각 개별 프로세스는 리눅스 커널에서 실행되고 프로세스가 수행하는 것을 제어한다.
MAC으로 올바르게 설정된 시스템에서 강탈 또는 해킹당헌 서비스는 시스템으로 넘어올 수 없다. 서비스 프로세스가 실행되는 곳의 사용자 또는 그룹 ID (최악의 시나리오: root)가 /etc/passwd 같은 중요한 시스템 파일의 파일 허가와 맞더라도 이 정책은 접근을 허용하지 않는다.
SELinux의 효과는 모든 사람들이 로그인 할 수 있는 인터넷 상의 테스트 시스템에서 잘 보여준다; 제어 방식은 모든 악의적인 작업을 막는다. 심지어 root로서 로그인 할 수 있는 사용자라도 막는다.
GnuPG 키 추가하기
배포판 벤더의 GnuPG 키는 이미 기본 설정의 일부가 되어있어야 한다: $ rpm -import
커넥션 인증이 입증된 HTTPS를 통해 벤더의 웹 사이트에서 다운로드를 받는 안전한 방식으로 keyfile을 받아야 한다.
SELinux에는 몇 가지 문제가 있다. 우선 배포판에서 MAC을 실행할 수 없다면 설정은 매우 힘들다. 커널의 패치와 재컴파일이 필요하고 특정 시스템 관리 툴을 대체해야 한다. (이 모든 것이 배포판 벤더의 지원 정책을 위반하는 것이다). 둘째, 적당한 정책을 정의하는 것은 매우 복잡한 일이다. 선택한 애플리케이션에 사용할 수 있는 정책 정의가 없다면 MAC 환경에서 실행하기 위해 많은 어려움을 겪어야 한다. 광범위한 패키지들이 지원되는 데스크탑 워크스테이션에서 특히 그렇다.
보안 패치 업데이트 및 추가
시스템을 가능한 한 안전하게 유지하려면 소프트웨어에 대한 새로운 픽스와 패치에 대한 정보를 지속적으로 받아야 한다. 이 정보에는 여러 소스들이 있지만, 일반적으로 소프트웨어 벤더와 리눅스 배포자들은 적시에 이 정보를 제공해야 한다. 또한 CERT (Computer Emergency Response Team)의 서비스를 사용할 수 있다. (거의 무료이다). 또한 최신 보고, 취약성 등에 대한 정보를 담고 있는 메일링 리스트들을 관리한다.
새로운 업데이트를 적용하려면 자신의 시스템과 보안 요구사항에 적용될 수 있는지를 확인해야 한다. 업데이트 설치는 그 자체가 보안 문제의 원인이 될 수 있다. 또한 각 업데이트로 인해 취약점이 생기거나 업데이트가 실패하면 시스템은 사용불가 상태로 될 것이다.
큰 규모의 시스템에 업데이트를 적용해야 할 때, 모두를 동시에 업데이트 할 수 없을 때가 종종 있다. 이로 인해 시스템 상호 비호환 문제가 생긴다.
여러분도 알다시피 시스템 업데이트에는 많은 위험이 잠재되어 있다. 다음은 이러한 위험들을 줄이기 위한 권고 사항들이다:
처음 설치 후에 곧바로 시스템을 네트워크에 연결하지 말라. 개별 머신에서 모든 관련 업데이트를 다운로드하여 이들을 직접 전송하여 시스템의 현재 상태를 확인하라.
사용하고 있는 시스템을 항상 백업하라.
비즈니스에 중요한 모든 시스템들의 경우, 하드웨어와 소프트웨어 모두, 실제 제품 환경과 동일한 고립된 테스트 환경을 갖고 있어야 한다. 테스트 환경에서 먼저 업데이트를 하여 제품 환경에는 차질이 없도록 하라.
이상적으로는 시스템을 구성하고 있는 모든 프로그램의 업데이트 전후에 해당 기능과 퍼포먼스를 비교하기 위한 회귀 테스트 수트를 갖고 있어야 한다. 최소한, 제품 시스템을 변경하기 전에 테스트 환경에서 영향을 주지 않는 기능과 서비스인지를 확인하는 반복적인 품질 검사를 해야 한다.
직접 업데이트를 적용하는 것은 작은 네트워크에는 알맞지만 규모가 클 경우 제어가 불가능하다. 이로 인해 업데이트가 설치되지 않을 때도 있다. 상용 또는 오픈 소스 시스템 관리 또는 소프트웨어 배포 툴을 이용하여 업데이트를 쉽게 전개하라.
백업을 자주 하라고 언급한적이 있다. 다시 한번 강조하겠다.
업데이트 적용 계획을 수립하라. 다음은 고려 사항이다:
시스템의 업데이트 순서
비즈니스에 중요한 시스템 결정
시스템의 의존 방식r
기밀 데이터를 포함하는 시스템 확인
무결성 체크 툴을 사용할 때, 알려진 보안 상태에서 취해진 시스템의 기본 특징을 업데이트 하여 예기치 않은 변경을 확인할 수 있도록 하라.
픽스를 적용하기 전에, 암호화 체크섬 툴을 사용하여 (웹 사이트나 ftp 서버에서 다운로드 할 때) 소프트웨어의 무결성과 인증을 확인하라. 리눅스에서는 일반적으로 MD5 또는 SHA-1 체크섬을 사용한다. RPM 패키지를 사용하여 소프트웨어가 제공된다면 벤더는 GnuPG 서명을 제공해야 한다. $ rpm -v --checksig
MD5 또는 SHA-1 체크섬을 확인하려면, $ md5sum
그 무엇보다도, 백업하라.
보안 플랜 수행하기
Part 2에서 논의한 것 처럼, 설치된 시스템과 문서화된 보안 계획을 맞춰보라. 어떤 프로세스가 시스템상에서 실제로 실행되는지를 파악하고 불필요한 프로세스를 제거한다. 이 같은 작업을 정기적으로 수행하여 정상적이지 않은 액티비티를 검사한다; 알려지지 않은 프로세스는 원치 않은 프로세스를 제공할 수도 있고 시스템을 위험에 노출시키게 된다.
이 섹션에서는 불필요한 (잠재적인 위험이 있는) 프로세스를 찾아 기능을 정지시키는 방법과 시스템의 정기적인 감사를 준비하는 방법을 설명한다.
불필요한 프로세스의 파악 및 기능 정지
이상적으로는, 시스템 상에서 실행중인 모든 프로세스를 알아야 한다. 모든 프로세스 리스트를 파악하려면 ps -ef (POSIX style) 또는 ps ax (BSD style)를 실행한다. 이름이 대괄호([])로 둘러 쌓인 프로세스는 도움말 기능(캐시를 디스크에 플러시하는 등..)을 수행하는 커널 레벨의 프로세스이다; 모든 다른 프로세스들은 userland 프로세스이다. 새롭게 설치된 (최소) 시스템상에서 조차도 많은 프로세스들이 실행되고 있다는 것을 알게 될 것이다. 여기에 익숙해지고 이를 문서에 추가하라.
네트워크 모니터링 툴
다음은 네트워크 모니터링 툴들이다:
Nmap (Network Mapper)는 무료 오픈 소스 유틸리티로서 네트워크 탐색 또는 보안 감사에 사용된다. 이것을 사용하여 설정을 끝마친 후에 시스템을 검사한다.
IPTraf는 리눅스용 콘솔 기반의 네트워크 통계 유틸리티이다. TCP 커넥션 패킷과 바이트 카운트, 인터페이스 통계, 액티비티 인디케이터, TCP/UDP 트래픽 브레이크다운, LAN 스테이션 패킷, 바이트 카운트 같은 다양한 그림을 모은다.
Multi Router Traffic Grapher는 네트워크 링크 상의 트래픽 부하를 감시하는 툴이다. MRTG는 이 트래픽의 그래픽 이미지를 포함한 HTML을 생성한다. MRTG index page참조.
이제, 네트워크 연결을 여는 프로세스를 살펴보자; 이 프로세스는 잠재적인 공격 위험이 크다. 모든 TCP 또는 UDP 연결 리스트를 알려면 netstat -atu (name resolution으로 읽기 쉬움) 또는 netstat -atun (name resolution이 없으며 빠르다) 명령어를 실행한다. 이 리스트 안에서 LISTEN 상태를 가진 TCP 연결과 모든 UDP 연결에 주의를 기울여라. 이들은 인커밍 연결을 수락하는 서버들이다.
서버가 127.0.0.1/localhost를 리스닝하면 시스템 자체에 의해 접근될 수 있다. (loopback 인터페이스). 따라서 외부에서 접근 가능한 인터페이스나, 어떤 네트워크 인터페이스를 통해서든 접근할 수 있는 0.0.0.0 (= * name-resolution 켜진 상태)을 리스닝 하는 것보다 노출이 훨씬 덜 된다.
netstat -atun을 사용했다면 포트 넘버를 변환해야 한다. /etc/services 에서 검색할 수 있다. 추가 매개변수인 -p를 사용하여 상응하는 프로세스를 디스플레이 한다. (Listing 5)
이 예제에서 포트매퍼(portmapper)와 그래픽 유저 인터페이스(X)는 특정 서버에는 필요하지 않다는 결론이 나왔다. 포트매퍼는 NFS 같은 다양한 RPC 기반 서비스에 표준 엔드포인트를 제공한다. 시스템은 NFS 공유를 제공하지 않는다. X 윈도우 디스플레이는 시스템이 워크스테이션으로서 사용될 때 유용하지만 서버로 사용될 때는 제한이 많다.
이 프로세스들이 (/etc/inittab, boot scripts 등을 통해) 어떻게 시작되었는지를 확인하고 앞에서 설명한 대로 기능을 정지한다. 이 작업은 프로그램이 다른 프로그램에 의해 시작되었다면 매우 힘들다; X 서버는 거의 대부분 xdm, kdm, 또는 gdm 같은 디스플레이 매니저로 시작되고 inittab 또는 부트 스크립트 디렉토리에는 보이지 않는다.
netstat으로 열거된 연결은 넷 상의 모든 컴퓨터에서 사용될 필요가 없다: 리눅스의 빌트인 기능에 근거한 방화벽은 패킷이 개방 커넥션에 연결되기 전에 액세스를 조정한다.
감사 준비
일단 기본적인 시스템이 설치되고 안전하게 설정되면 시스템을 안전하게 지키는 것이 궁극적인 목표이다. 시스템에 원치 않은 변경을 확인하려면 시스템의 핑거프린트를 기록하는 감사 툴을 사용한다.
http://www-128.ibm.com/developerworks/k ··· clnx3%2F
- 시스템 관리자를 위한 기초 명령어 활용법 (0)2007/06/22
- 프로세스정보 얻어오기 (0)2007/05/14
- 여러 가지 설정으로 공격으로부터 시스템을 안전하... (0)2007/05/10
- 데이터 문제에 대한 차세대 NFS 계열 파일 시스템... (0)2007/05/10
- inotify를 이용한 리눅스 파일 시스템 감시 (0)2007/05/10
Leave your greetings.
난이도 : 초급
Frank Pohlmann
U.K. Technical 편집자 및 개발자, LinuxUser and Developer
2005년 5 월 17 일
분산 파일 시스템은 최근까지 많은 이슈는 없었다. 이를 사용하는 것이 대개는 기업과 교육 네트워크이기 때문이다. 시스템이 오픈 소스 파일 시스템 퍼즐에 어떻게 맞는지는 개념상으로 언제나 명확한 것은 아니다. Open Andrew File System (OpenAFS)은 Network File System (NFS)의 성숙한 대안이다.
사용자들은 파일 시스템의 개념을 두 가지 방식으로 이해한다. 첫 번째는 파일과, 그 파일을 포함하고 있는 디렉토리, 그리고 디렉토리 구조를 보유한 파티션으로 구성하는 방식이다. 그리고 두 번째는, 파일 시스템은 파일들이 미가공 금속으로 조직화 또는 매핑되는 방식이다. 원래는 가상 파일 시스템(VFS) 레이어와 실제 메모리 관리 루틴 처럼 중간에 존재하는 레이어이지만 사용자가 접근할 수 있는 구조화된 정보를 관리하는 것과 관련해서는 파워 유저가 파일 시스템 내부를 들여다 보고 커널의 후미진 곳 까지 파악하도록 하는 것이 이치에 맞는다.
메탈은 RAM 또는 하드 디스크로 구성되지만, 어떤 경우에는 파일 시스템 데이터 구조는 하드웨어 제조자들이 만든 섹터와 바이트로 구성된다. 보다 원시적인 경우는, 사용자들이 스스로 개념상 이를 쪼갤 수도 있다. 큰 사이즈의 파일에 액세스 할 때 속도를 높일 수 있는 툴도 있다. 또한 디렉토리와 파일을 재구성할 수 있는 툴도 있는데 이 툴들은 비트, 바이트, 섹터로부터 안전하다.
파일 시스템 개념
이러한 개념적 정의의 고전적인 경우는 FreeBSD—유닉스의 BSD—가 UNIX File System V2 (UFS2)를 사용하여 디스크 상의 데이터를 구성하고, Flash File System (FFS)을 사용하여 파일을 디렉토리로 구성하고 디렉토리 액세스를 최적화하던 방식이다. 리눅스는 원래 두개 이상의 파일 시스템 보다 더 많은 것을 허용하기 때문에 약간 다르게 작동한다. 따라서 VFS 레이어에서는 리눅스 사용자가 리눅스가 메모리를 관리하는 방식에 구애 받지 않고 새로운 파일 시스템 지원을 추가할 수 있다.
정적 그리고 저널 파일 시스템에 대서 나중에 이야기 할 때, 영속성과 더 나아가서 파일 시스템 콘텐츠의 보안에 대해 강조할 것이다. BSD UNIX 관점에서 보면 정적 저널 파일 시스템들은 UNIX File System (UFS)이 파일을 구성하고 보안화 하는 방식과 관련이 있다. 비록 리눅스 파일 시스템들이 Journal File System (JFS), 차세대 파일 시스템 (XFS), 초기 ReiserFS들을 사용할 수 있을 때부터 저널 파일 시스템들을 포함하긴 했지만 기술적 저널리즘 이나 기업 공공성이 빛을 발하지 않은 또 다른 영역은 분산 파일 시스템이다.
NFS에서 내가 배운 것
대규모의 사용자들에게 TCP 또는 User Datagram Protocol (UDP)을 통해 사용할 수 있는 네트워크 중심의 파일 시스템 레이어를 만드는 것은 신중하지 못한 것처럼 보인다. pre-V3 NFS 관련 공포 시나리오는 10명 남짓의 사용자들로 네트워크를 관리하는 많은 관리자들을 고생시킨다. 게다가 매우 빠른 마더보드 아키텍쳐로 지원되는 다중 프로세서 아키텍쳐의 출현은 분산 파일 시스템 문제를 우선순위 밖으로 밀어낸다. 속도는 지능적으로 구현된 분산 시스템들이 아닌 하드웨어로 보장된다. 분산 파일 시스템들이 기저의 파일 시스템 구현- ext2, ext3, ReiserFS 파일 시스템 드라이버-들에 의존한다면 분산 파일 시스템들은 큰 대학 네트워크와 과학 또는 기업 네트워크로 제한될 것이다.
그렇다면 분산 파일 시스템은 우리가 언급했던 두 개 중 상단에 있는 제 3의 레이어인가? 현대 네트워킹의 큰 문제 중 하나는 이종의 네트워크를 결합해야 한다는 점이다. (Samba가 대표적인 예이다.) 하지만 파일 시스템 퍼즐에 세 개의 주요한 플레이어가 있다는 것을 이해해야 한다: Microsoft® Windows® 파일 시스템 군 (FAT16, FAT32, NTFS 파일 시스템); Apple Mac OS X (HFS+); 그리고 원시 리눅스 저널 파일 시스템(대게ReiserFS와 ext3). Samba는 Windows와 리눅스 파일 시스템의 협업을 돕지만 모든 주요 파일 시스템에 접근하여 관리할 수 있다는 것을 의미하지는 않는다.
어떤 사람들은 NFS V4를 이 문제를 해결 때 시도하지만 NFS V4를 다루는 Request for Comments (RFC) 3530은 아직 2년 밖에 되지 않았고, 2.6 커널용 NFS V4도 너무 어리기 때문에 제품 서버에 쓸 것을 권하기가 망설여진다. Fedora cores 2와 3은 NFS V4 패치와 NFS V4 유틸리티를 제공한다. 이것은 NFS가 더 많은 포트를 열고 사용자들에게 반출된 각 네임스페이스에 개별 클라이언트를 설정하는 것으로 네트워크 관리자들을 애를 먹인 이후로 개발자가 만들어 낸 현상이다. RFC 3530은 대부분 보안 문제만 언급한다. 아직도 NFS 디렉토리는 개별적으로 마운트되어야 한다. 통합 sign-on과 Kerberos를 사용하여 보안화 할 수 있지만 손질이 필요하다.
OpenAFS 논거
OpenAFS는 소프트웨어의 설치와 관리 고통을 제거한다. OpenAFS는 다른 파일 시스템들 끼리 효율적으로 협업하도록 돕는다. 비록 유닉스용 원래의 메타포와 이것의 매력적인 후계자 Plan 9는 파일이었지만 현대의 네트워크로 된 파일 시스템들을 완전히 다시 만들기 보다는 또 다른 분산 파일 시스템 레이어가 추가되었어야 했다.
카네기 멜론 대학교 프로그래머들은 1983년에 AFS를 개발했다. 그 뒤 바로, 이 대학은 Transarc라는 회사를 설립하여 AFS 기반 서비스를 팔았다. IBM은 1998년에 Transarc를 인수하여 AFS를 OpenAFS란 이름으로 오픈 소스 제품으로서 사용할 수 있도록 했다. 하지만 OpenAFS가 Coda와 Arla 같은 다른 분산 파일 시스템들을 만들어냈기 때문에 무용담은 여기가 끝이 아니다. OS 클라이언트들이 존재했고 문서도 충분하다. Gentoo.org는 OpenAFS에 노력을 기울여 리눅스 사용자들도 접근할 수 있도록 했다. 다른 조직들은 여전히 분산 파일 시스템이 필요할 때 NFS를 거론한다.
OpenAFS 아키텍쳐
OpenAFS는 셀로 알려진 파일 서버 그룹 주위에 조직된다. 각 서버의 아이디는 보통 파일 시스템 뒤에 숨겨진다. AFS 클라이언트의 형태로 로그인하는 사용자들은 어떤 서버에서 작업하고 있는지 말할 수 없다. 사용자 관점에서 보면 인식 가능한 유닉스 파일 시스템으로 하나의 시스템 상에서 작업하는 것이기 때문이다. 파일 시스템 콘텐트는 셀을 통해 복제되어 하드 디스크가 오류가 생긴다고 해서 OpenAFS의 클라이언트 작업에 지장을 주지 않는다. OpenAFS는 1 GB 정도의 클라이언트 캐싱 장치를 통해 자주 사용되는 파일에 접근할 수 있어야 한다. 이것 역시 완전히 보안화 된 Kerberos 기반 시스템으로서 작동한다. 이는 액세스 제어 리스트(ACL)을 사용하여 보통의 리눅스 및 유닉스 보안 모델 기반이 아닌 정교한 액세스를 가능하게 만든다.
기저 파일 시스템으로 ext2를 실행하는 OpenAFS의 일부가 되는 캐시 매니저를 제외하고 OpenAFS의 기본적인 구조는 요즘의 NFS 구현을 닮았다. 기본 아키텍쳐는 전혀 같아 보이지 않는다. NFS를 더 선호하는 사람들에겐 OpenAFS 장치를 활용하는 것이 더 좋지만 소위 NFS/AFS 트랜슬레이터를 사용할 수 있다. OpenAFS 클라이언트 머신이 NFS 서버 머신으로 설정되는 한 두 파일 시스템의 장점을 활용할 수 있어야 한다.
OpenAFS 방식
NFS는 위치 독립적이고 로컬 디렉토리들을 원격 파일 시스템 위치로 매핑한다. OpenAFS는 사용자들에게 파일 위치를 숨긴다. 모든 소스 파일들은 다양한 복제된 파일 서버 위치에 읽기-쓰기 카피로 저장되기 때문에 복제된 카피를 동시에 갖게 된다. Ubik이라고 하는 기술을 통해 이것이 가능하다. 동부 유럽과 유비쿼터스에서 사용한다. Ubik 프로세스는 AFS 파일 시스템 상에서 파일, 디렉토리, 볼륨을 동시에 관리하지만 세 개 이상의 파일 서버를 가진 시스템들이 효과를 볼 수 있다. 시스템 관리자는 여러 AFS 셀들을 그룹핑할 수 있다. 오래된 AFS는 OpenAFS 파일 시스템에 보관된다. 관리자는 AFS 셀의 양과 셀이 저장할 수 있는 한계와 그 사이트 내에 AFS 셀에서 사용할 수 있는 파일들을 결정해야 한다.
파티션, 볼륨, 디렉토리
AFS 관리자들은 셀을 볼륨으로 나눈다. 볼륨은 하드 디스크 파티션으로 확장가능하지만 대부분의 관리자들은 한 개의 볼륨으로 전체 파티션을 채우지는 않는다. AFS 볼륨은 실제로 Volume Manager 라고 하는 유닉스 유형의 프로세스로 관리된다. 유닉스 파일 시스템 디렉토리와 비슷한 방식으로 볼륨을 마운트 할 수 있다. 하지만 AFS 볼륨을 파일 서버에서 파일 서버로 옮길 수 있다. 하지만 유닉스 디렉토리는 파티션에서 파티션으로 물리적 이동이 불가능하다. AFS는 Volume Location Manager를 통해 볼륨과 디렉토리 위치를 자동으로 트래킹하고 복제된 볼륨과 파일을 계속 트래킹한다. 따라서 사용자는 파일 서버가 예상치 못하게 작동을 멈출 때 걱정하지 않아도 된다. AFS가 사용자를 다른 파일 서버 머신 상의 복제된 볼륨으로 변환하기 때문이다. 사용자는 이를 인식하지 못한다.
사용자들은 AFS 서버상에 위치한 파일에 대해 절대 작업하지 않는다. 클라이언트 측 캐시 매니저에 의해 파일 서버에 붙은 파일에 대해 작업한다. Cache Manager는 클라이언트 운영 체계 커널에 살아있는 재미있는 동물이다. 리눅스의 경우 패치가 커널에 추가된다. (2.4 커널에서 Cache Manager를 실행할 수 있다.)
Cache Manager
Cache Manager는 로컬 애플리케이션의 요청에 응답하여 AFS 파일 시스템을 통해 파일을 붙일 수 있다. 물론 파일이 종종 바꾸게 되는 소스 파일 이라면 여러 복제된 버전으로 존재하는 것은 이상적이지 못하다. 사용자는 빈번히 요청되는 소스 파일을 자주 변경할 수 있기 때문에 두 가지의 문제가 생긴다: 첫째, 파일은 클라이언트 캐시와 여러 파일 서버 머신 상의 여러 복제된 볼륨에 저장된다; 둘째, Cache Manager는 모든 볼륨을 업데이트 해야 한다. 파일 서버 프로세스는 콜백을 붙여 파일을 클라이언트 캐시로 보내서 시스템이 어딘가에서 일어난 모든 변경들을 처리할 수 있도록 한다. 사용자가 어딘가에서 캐시 된 복제된 파일에 변경을 추가하면 원래 파일 서버는 콜백을 활성화하고 원래 캐시 된 버전에 업데이트가 필요함을 상기시킨다.
분산 버전의 제어 시스템은 이러한 고전적인 문제에 직면해 있지만 중요한 차이가 있다: 분산 버전 제어 시스템은 연결이 해제될 때 완벽히 작동하지만 AFS는 파일 시스템의 일부를 잘라낼 수 없다. 개별 AFS 섹션은 원래 파일 시스템과 재연결 할 수 없다. 파일 서버는 오류가 여전히 실행중인 AFS 파일 서버로 다시 연결되도록 처리하지만 잘려나간 후에 로컬에서 보유하고 잇는 새로운 변경을 추가할 수 없다.
OpenAFS 대안
AFS는 새로운 파일 시스템에 대한 여러 시도의 출발점이다. 원래의 분산 파일 시스템 아키텍쳐에서 배운 교훈들을 두 개의 시스템들이 결합했다: Coda와 스웨덴의 오픈 소스 자원자들의 노력의 산물인, Arla 이다.
Coda 파일 시스템은 원래 AFS를 향상하려는 첫 번째 시도였다. 1987년에 카네기 멜론 대학에서 개발자들은 AFS를 Coda로 발전시켜 V2.0까지 이르렀다. 1980년대 후반과 90년대 초에 Coda 파일 시스템은 Venus라고 하는 다른 캐시 매니저를 선보였다. Coda의 기본적인 기능은 AFS와 비슷한 반면 Venus는 클라이언트가 분산 파일 시스템에서 연결이 끊어져도 Coda를 실행하는 크라이언트를 위해 계속 작동할 수 있다. Venus는 AFS Cache Manager와 같은 기능을 갖고 있다. 커널 내에 VFS에서 파일 시스템 작업을 가져온다.
Coda 서버들과 Venus 캐시 매니저들간 연결 해제가 네트워크 기능에 결정적인 것은 아니다. 랩톱 클라이언트는 중앙 서버에서 멀리 떨어져 작업할 수 있어야 한다. 따라서 Venus는 클라이언트 변경 로그에 모든 업데이트들을 저장한다. 캐시 매니저가 중앙 서버로 재연결 될 때 시스템은 클라이언트 변경 로그를 재통합하면서 모든 파일 시스템 업데이트들을 클라이언트가 사용할 수 있다.
연결 해제는 또 다른 문제를 만들 수 있지만 Venus 캐시 매니저는 분산된 파일 시스템들이, 연결된 상태로 실행되는 복잡한 네트워크들 보다 훨씬 더 많이 확장될 수 있음을 나타내고 있다.
프로그래머들은 1993년 이후 OpenAFS의 GPLed 구현을 제공하는 스웨덴 프로젝트인 Arla를 개발해왔다. 사실 대부분의 개발과 포트는 1997년 이후에 이루어졌다. XFS 파일 시스템이 Arla가 실행되는 모든 운영 체계에서 작동해야 한다는 것을 제외하고는 Arla는 OpenAFS를 아주 잘 모방했다. Arla는 V0.39까지 나왔고 OpenAFS처럼 모든 BSD에서 실행된다. kernel V2.0x과 Sun Solaris 이후 상당량의 리눅스 커널이다. Arla는 원래 AFS 코드에는 없었던 AFS의 기능을 부분적으로 구현한다. 연결 해제 작동이 바로 그것이다. 하지만 갈 길은 멀고 개발자들은 테스팅도 끝마치지 못했다.
GPLed InterMezzo 같은 기타 AFS 유형의 파일 시스템들도 사용 가능하다. 하지만 이것은 AFS 명령행 문법 또는 아키텍쳐를 복제하지 않았다. 오픈 소스 분산 파일 시스템의 세계는 매우 활동적이고 다른 분산 파일 시스템은 모바일 컴퓨팅에서 실행할 애플리케이션을 모색하고 있다.
http://www-128.ibm.com/developerworks/k ··· dex.html
- 프로세스정보 얻어오기 (0)2007/05/14
- 여러 가지 설정으로 공격으로부터 시스템을 안전하... (0)2007/05/10
- 데이터 문제에 대한 차세대 NFS 계열 파일 시스템... (0)2007/05/10
- inotify를 이용한 리눅스 파일 시스템 감시 (0)2007/05/10
- Secure programmer: 컴포넌트를 안전하게 호출하기 (0)2007/05/04
Leave your greetings.
난이도 : 초급
Eli M. Dow
소프트웨어 엔지니어, IBM Linux Test and Integration Center
2005년 4 월 12 일
Inotify는 차기 리눅스 커널에 포함될 파일 시스템 모니터링 메커니즘으로서, 구 커널에서 지원되던 파일 모니터링 메커니즘이였던 dnotify의 강력한 대체재이다. Inotify는 강력하고 세련된 비동기식 메커니즘으로서 보안과 퍼포먼스 등 다양한 파일 모니터링의 필요를 이상적으로 채운다. Inotify를 설치하는 방법과 파일 시스템 이벤트에 응답하는 사용자 공간의 애플리케이션 샘플을 구현하는 방법을 설명한다.
파일 시스템 이벤트 모니터링은 파일 매니저에서 보안 툴에 이르기까지 다양한 유형의 프로그램들에 필수적인 것이지만, 이전 커널의 표준인 dnotify는 아쉽게도 제한이 많았다. 이제, 보다 현대적인 파일 시스템 이벤트-모니터링 대안인 Inotify를 만나보자.
왜 Inotify인가?
dnotify 대신 Inotify를 사용하는데는 많은 이유가 있다. 우선, dnotify는 변경사항을 보고자 할 때 각 디렉토리에 대한 한 개의 파일 디스크립터를 열어야 한다. 따라서, 여러 디렉토리들을 한번에 모니터링 할 때는 작업량이 많을 수 밖에 없다. 프로세스 당 파일 디스크립터 제한에 걸리기 때문이다.
게다가 파일 디스크립터는 디렉토리를 고정하기 때문에 지원 장치의 언마운트가 허용되지 않는다. 따라서 제거 가능한 미디어가 개입된 시나리오에서 문제가 발생한다. Inotify를 사용할 때, 언마운트 된 파일시스템 상의 파일을 보고자 한다면, 보기(watch)는 자동으로 제거되고 언마운트 이벤트를 받는다.
dnotify 보다 Inotify가 더 나은 두 번째 이유는 약간 복잡하다. dnotify 인프라를 사용하는 단순한 파일시스템-모니터링 세분성은 디렉토리 레벨에서만 존재한다. dnotify를 이용하여 보다 정밀한 모니터링을 하려면 애플리케이션 프로그래머들은 관찰되고 있는 각 디렉토리와 관련된 stat 구조의 캐시를 유지해야한다. stat 구조의 사용자 공간 캐시는 공지 신호를 받았을 때 디렉토리에 정확히 어떤 변화가 발생했는지를 결정하는데 필요하다. 공지를 받으면 stat 구조의 리스트가 생성되고 마지막 상태와 비교된다. 확실히 이는 최상의 방법은 아니다.
Inotify의 또 다른 장점은 기본 인터페이스로서 파일 디스크립터를 사용하여 애플리케이션 개발자들이 select와 poll 을 사용하여 장치 볼 수 있다는 점이다. 이로서 효율적인 다중화 I/O와 Glib의 mainloop와의 통합이 가능하다. 이와 반대로, dnotify는 프로그래머들이 어려워하는 신호를 사용하고 있다.
Inotify는 최소한의 파일 디스크립터를 사용하고 보다 세분화된 모니터링을 보장하는 등의 고급 기능을 제공하여 이러한 문제들을 해결한다. Inotify와의 통신은 디바이스 노트를 통해 이루어진다. 따라서, Linux 2.6에서 파일 모니터링을 할 때 선택을 분명히 해야 한다.
Inotify 설치하기
Inotify를 설치하는 첫 번째 단계는 현재 사용하고 있는 리눅스 커널이 이를 지원하는지의 여부를 결정하는 것이다. 배포판을 확인하는 가장 간단한 방법은 /dev/inotify 장치의 존재 유무를 확인하는 것이다. 이 장치가 있으면 애플리케이션에서 Inotify 사용하기섹션으로 이동하기 바란다.
이 글이 쓰여질 당시, Inotify는 Andrew Morton의 리눅스 Linux 2.6-mm 트리에 포함되었고 여러 리눅스 배포판들이 Inotify를 실행하는 커널(Gentoo와 Ubuntu)을 제공하고 있거나 또는 이를 지원하는 보조 커널 패키지를 갖고 있었다. (Fedora와 SuSE). Andrew가 트리에서 Inotify 지원을 제거할 수도 있고 Inotify를 제거하는 일은 개발 단계에서는 종종 있는 일이였기 때문에 처음부터 다시 붙이는 것을 권장한다.
이 장치가 없다면 커널을 패치하고 장치를 만들어야 한다.
Inotify용 커널 패치
Linux Kernel Archives (참고자료)에서 Inotify 패치를 얻을 수 있다.
자신의 커널에 해당하는 버전 숫자 중 가장 높은 숫자를 가진 패치를 적용해야 한다. 배포판마다 커널 설치를 약간 다르게 핸들하긴 하지만 여기에서는 일반적인 가이드라인을 설명하겠다: Note: 배포판의 2.6 리눅스 커널 소스나, Linux Kernel Archives에서 안정적인 최신 릴리스를 사용해야 한다.
우선 커널 소스 디렉토리로 간다:
bash:~$ cd /usr/src
앞서 커널 소스를 설치했기 때문에 이를 패킹을 푼다(unpack):
bash:~$ sudo tar jxvf linux-source-2.6.8.1.tar.bz2
symlink를 새로운 소스 트리로 만든다:
bash:~$ sudo ln -sf linux-source-2.6.8.1 linux
현재 디렉토리를 방금 만든 커널 소스 디렉토리로 변경한다:
bash:~$ cd linux
Inotify 패치를 복사한다:
bash:~$ sudo cp ~/inotify* /usr/src
커널을 붙인다(patch):
bash:~$ sudo patch -p1 < ../inotify*.patch
커널을 구현한다:
bash:~$ sudo make menuconfig
inotify 기능을 확인하면서 일반적인 방식으로 커널을 설정한다. 필요하다면 이 새로운 커널을 bootloader에 추가한다. 단 구 커널 이미지와 bootloader 옵션을 기억해야 한다. 이 단계는 bootloader 마다 다양하다. (bootloader 관련 추가정보는 참고자료 참조). 머신을 재부팅하고 inotify 기능이 추가된 커널을 선택한다. 새로운 커널을 테스트하여 올바르게 작동하는지 확인한다.
inotify 장치 만들기
다음에는, /dev/inotify 장치가 만들어졌는지를 확인해야 한다. 다음은 그 단계이다. Important note: 마이너 넘버(minor number)가 변경될 수 있기 때문에 최신 것인지를 부지런히 검토해야 한다. 자신의 리눅스가 udev 기능을 지원한다면 자동으로 업데이트 된다.
새로운 커널로 재부팅 한 후에, 마이너 넘버를 획득해야 한다:
bash:~$ dmesg | grep ^inotify
다음은 리턴된 예제이다:
inotify device minor=63
inotify는 misc 장치이기 때문에 메이저는 10이다. 다음 명령행을 실행하여 장치 노드를 root 사용자로 만든다:
bash:~$ mknod /dev/inotify c 10 63
Note: 필요할 경우 "63"을 적당한 마이너 넘버로 대체한다.
선택적으로 허가(permission)를 설정해야 한다. 허가 설정 샘플은 다음과 같다:
bash:~$ chown root:root /dev/inotify
bash:~$ chmod 666 /dev/inotify
이제 파일 시스템 모니터링을 위한 inotify 장치를 사용할 준비를 갖추게 되었다.
애플리케이션에서 Inotify 사용하기
파일 시스템 이벤트에 임의의 디렉토리를 감시하는 샘플 프로그램일 만드는 방법을 설명하겠다. inotify가 파일 시스템 모니터링을 얼마나 쉽게 만드는지를 보여줄 예정이다.
주 메소드
이 간단한 예제를 통해 임의의 디렉토리에 대한 watch를 설정하는 것이 얼마나 쉬운지를 보게 될 것이다. 주요 헬퍼 루틴은 나중에 설명하도록 하겠다. 여기에 사용되는 샘플 코드는 Download 섹션을 참조하기 바란다.
Listing 1. 디렉토리에 대한 watch 설정하기
중요한 헬퍼 메소드
다음은 모든 inotify 기반 애플리케이션에 공통적으로 적용되는 가장 중요한 헬퍼 루틴들이다:
읽기를 위한 inotify 장치 열기
이 장치에서 읽혀지는 이벤트 큐잉(queuing)
애플리케이션들이 이벤트 공지를 사용하여 유용하게 일을 수행하도록 하는 이벤트 핸들러
여러 전략들이 사용될 수 있기 때문에 이벤트 큐잉에 대한 자세한 설명은 하지 않겠다. 멀티 쓰레디드 방식이 진화될수록 어디에서나 구현될 수 있다. 그와 같은 구현은 읽기 쓰레드가 inotify 장치에 대해 select()를 수행하고 그런 다음 이벤트를 몇몇 쓰레드가 공유된 스토리지(또는 Glib의 비동기식 메시지 큐)에 복사한다. 이곳은 핸들러 쓰레드가 실행되는 장소이다.
Listing 2. inotify 장치 열기
이것은 리눅스 시스템 상에서 파일들을 이용한 프로그래밍을 했다면 매우 익숙할 것이다.
Listing 3. 실제 이벤트-핸들링 루틴
각 case 문장 내에서 필요에 맞춰 구현한 어떤 메소드라도 실행할 수 있다.
퍼포먼스 모니터링과 관련하여 어떤 파일들이 가장 자주 읽히는지, 그리고 파일이 열려있던 기간을 파악할 수 있다. 이러한 종류의 모니터링은 쉽다. 어떤 상황에서, 짧은 기간 동안 애플리케이션에 의해 파일이 반복적으로 읽힌다면, 디스크로 돌아가기 보다는 메모리의 파일을 캐시하여 퍼포먼스를 향상시키기 때문이다.
특정 액션을 수행하는 이벤트 스팩의 핸들러들에 대한 다른 예제들도 이해하기 쉽다. 예를 들어, 기반 파일 시스템에 대해 메타데이터 스토리지 인덱스를 구현한다면 파일 생성 이벤트를 찾아 그 파일에 대한 데이터 마이닝 작동을 실행할 수 있다. 보안을 위해서는, 어떤 누구도 작성해서는 안되는 디렉토리에 파일이 쓰여졌다면 시스템 경고를 실행할 수 있다.
inotify가 많은 정밀한 이벤트들 CLOSE versus CLOSE_WRITE을 지원한다는 것도 기억하라.
이 글에서 사용된 코드의 많은 이벤트들이 코드가 실행될 때마다 보고자 하는 것이 아닐 수도 있다. 사실, 가능하다면 본인의 애플리케이션에 필요한 이벤트의 하위세트만 요청할 수 있으면 좋겠다. 이 글에서 제시한 코드에서는 전체 마스크를 사용하여 많은 이벤트들을 보여주고자 하였다. (샘플 코드에 있는 51줄의 main 메소드 (참고자료) 또는 Listing 1의 29줄) 애플리케이션 프로그래머들은 일반적으로 많은 선택권이 주워지길 바란다. 또한 여러분도 필요에 맞는 특정 마스크가 필요하다. 이는 앞서 보여준 handle_event() 메소드에서 catch 문장에서 관심 없는 아이템들을 제거할 수 있다.
결론
inotify가 퍼포먼스 모니터링, 디버깅, 자동화 같은 분야에 적용된다면 강력하고 정밀한 리눅스 파일 시스템의 모니터링 메커니즘이 된다. 이 글에 제시된 코드를 사용하여 최소한의 퍼포먼스 오버헤드로 실시간 파일 시스템 이벤트에 응답하고 이를 기록할 수 있는 애플리케이션을 작성할 수 있다.
http://www-128.ibm.com/developerworks/k ··· ify.html
- 여러 가지 설정으로 공격으로부터 시스템을 안전하... (0)2007/05/10
- 데이터 문제에 대한 차세대 NFS 계열 파일 시스템... (0)2007/05/10
- inotify를 이용한 리눅스 파일 시스템 감시 (0)2007/05/10
- Secure programmer: 컴포넌트를 안전하게 호출하기 (0)2007/05/04
- 리눅스에서의 메모리관리 (0)2007/05/04
Leave your greetings.
Level: Intermediate
David A. Wheeler
연구원, Institute for Defense Analyses
2004년 12월 16일
일반적으로, 애플리케이션 프로그램들은 OS, 데이터베이스 시스템, 재사용 가능한 라이브러리, (DNS 같은) 인터넷 서비스, 웹 서비스 등의 다른 컴포넌트들을 호출한다. 이 글에서는 이러한 호출을 악용하는 사람들을 방지하는 방법을 설명한다.
PDF 파일을 보면서 하이퍼텍스트 링크를 클릭하는 것은 파일을 호출한 뷰어를 신뢰하는 한 보안 문제는 생길 리가 없다. 하지만 xpdf version 0.90의 사용자들은 이러한 추측이 완전히 잘못된 것이라는 것을 발견했다.
xpdf 사용자들이 하이퍼텍스트 링크를 클릭했을 때, xpdf는 뷰어를 시작하고(디폴트는 Netscape) URL을 뷰어에게 보냈다. 여기까지는 좋았다. 하지만 xpdf 개발자들이 system()호출을 사용하여 뷰어를 시작하기로 결정했다. 이것이 문제였다.
유닉스 시스템과는 달리 , system()은 명령행 쉘을 호출하는데 이것은 전송된 텍스트를 인터프리팅한다. 다시 말해서, 침입자가 쉘에서 특별히 인터프리팅 된 가짜 URL을 가지고 PDF 파일을 만들 수 있었음을 의미한다. 그런 다음 침입자가 누군가가 하이퍼텍스트 링크를 클릭한다는 것을 확신할 수 있으면 사람들로 하여금 그들이 선택한 아무 프로그램이나 실행할 수 있도록 한다. 모든 파일들을 지우거나, 읽을 수 있는 모든 것을 외부 메일 드랍으로 이메일을 보낸다.
솔루션: 보안 컴포넌트 호출하기.
컴포넌트를 재사용하는 것에 대해서...
애플리케이션 프로그램들은 일반적으로 독립적(self-contained)이지 않다. 대게 다른 컴포넌트들을 호출한다. 컴포넌트로는 다음과 같은 것들이 있다:
OS
데이터베이스 시스템
재사용 가능한 라이브러리(동적으로 로딩되는 라이브러리 포함)
인터넷 서비스 (DNS)
웹 서비스
오늘날 대부분의 프로그래밍 언어에는 규모가 큰 빌트인 라이브러리가 포함된다. 이중 어떤 것은 독립적이고 어떤 것은 외부 프로그램이나 서비스들을 호출하도록 설계된다.
재사용 문제는 너무 중요하다-모든 애플리케이션을 위해 처음부터 모든 것을 작성한다는 것은 말이 안된다. 각 컴포넌트를 다시 만들기엔 너무 많은 시간이 걸리기 때문이다. 그리고 재사용 할 때 잠재적인 보안 관련 모험을 감수해야 한다-개발자들은 그들의 컴포넌트가 보안 취약성이 없다는 것을 확인하고 보안 컴포넌트의 혜택을 더 많은 시스템들에 전달하는데 집중할 수 있다. 나중에 취약점이 발견되더라도 한번에 픽스되고 이 컴포넌트를 사용하는 모든 애플리케이션에 이 픽스가 적용될 수 있다.
하지만, 다른 컴포넌트를 호출하는 것에도 단점은 있다: 침입자들이 이 호출을 이용할 수도 있다.
Open Web Application Security Project (OWASP)의 "Top Ten Most Critical Web Application Vulnerabilities" 는 가장 심각한 취약점 중 하나를 투입 오류(injection flaws)로 구분했다. OWASP는 웹 애플리케이션들은 매개변수를 외부 시스템이나 로컬 OS에 전달하고:
침입자가 악의적인 명령행을 이 매개변수에 삽입할 수 있다면 외부 시스템은 애플리케이션 대신 그 명령어를 실행할 것이다.
컴포넌트를 재사용 할 때 면밀히 계획을 세우기 위해서는 다음 사항들을 고려해야 한다:
안전한 컴포넌트만 안전한 방식으로 사용한다.
유효 데이터만 컴포넌트로 보내고 기대하는 대로 인터프리팅 될 것을 확인한다. 특히, 메타-캐릭터(SQL 투입, 쉘 메타-캐릭터 투입, 포맷 스트링, Perl open() 침입 등의 원인)를 주의한다.
리턴 값을 점검하고 예외를 핸들한다.
애플리케이션과 컴포넌트 사이를 거쳐갈 때, 데이터를 보호한다.
이제부터 좀더 자세히 살펴보자.
안전한 컴포넌트, 안전한 방법
작은 함수라 하더라도 무엇인가를 재사용 하기 전 첫 번째 단계는 문서를 검토하여 보안 경고가 있는지를 확인하는 것이다. 예를 들어, C 표준에는 gets() 함수가 포함되지만 이에 관한 문서는 gets()가 위험하다고 경고하고 있다. 문제는 gets()가 버퍼 오버플로우에서 자신을 보호할 수 없다는 것이다. 침입자는 gets()에 전달된 버퍼가 저장할 수 있는 것 보다 더 많은 데이터를 보낸다. 이로서 침입자는 내부 데이터를 제어하게 되고 프로그램을 접수하게 된다.
여기 또 다른 예제가 있다: 모든 언어는 실제로는 예견 가능한 "랜덤(random)" 값을 리턴하는 함수를 갖고 있다. 이 함수들은 시뮬레이션에는 이상적이지만 보안용 비밀 키에 사용한다면 매우 위험하다. 왜냐하면 침입자가 그 키를 결정하기 쉽기 때문이다.
광범위하게 사용되는 컴포넌트를 사용하고 있다면 웹 검색을 수행하여 알려진 보안 이슈가 있는지 또는 가볍게 사용할 일반적인 방법이 있는지를 검토해도 좋다.
휴먼 인터랙션을 위한 전체 프로그램을 재사용하려 한다면 특별히 조심해야 한다. 좋은 규칙은 그 일을 하지 않는 것이다. 사용자 인터랙션 프로그램들은 도움이 되고 사용자들의 의도를 생각한다. "도움"은 침입자들이 프로그램을 오용할 데이터를 만드는 데에도 도움이 된다.
휴먼 인터랙티브 프로그램들은 종종 시간이 흐르면서 미묘한 변화들을 겪는데 이것은 사용자들에게는 좋은 것이지만 이 프로그램을 호출하는 프로그램에 혼란을 줄 수 있다. 또한 이 프로그램들에는 사용자들이 다른 프로그램들을 호출할 수 있도록 하는 함수들을 갖고 있다. 예를 들어, vim (vi), emacs, "ed" 프로그램 같은 텍스트 편집 프로그램들에는 사용자가 OS 명령어 쉘로 호출할 수 있는 함수가 포함되어 있어 침입자들이 악용할 수 있는 손쉬운 목표물이 되고 있다. 유닉스 계열 시스템상에서 텍스트 편집을 할 때 sed, tr, gawk, perl 같은 프로그램에서 사용할 명령어들 중 하나를 사용하는 것이 더 안전하다.
워드 프로세싱, 스프레드시트 프로그램의 경우도 마찬가지다. 일반적으로 휴먼인터랙티브 프로그램들은 다른 프로그램에 의해 호출될 수 있는 컴포넌트들에 구현되도록 작성되어야 한다. 이러한 방식으로, 사용자 인터페이스를 픽스하기 더 쉬워지고 다른 프로그램들이 프로그램의 기능들을 재사용하기 더 쉬워진다.
유효 데이터를 전달하여 메타-캐릭터 문제 피하기
컴포넌트가 재사용된다면 이것은 특정 애플리케이션을 위해 만들어지지 않을 것이다. 대신 재사용된 컴포넌트는 많은 기능들을 갖춘 훌륭한 인터페이스를 이용한다. 침입자들은 당신이 기대하지 않던 것을 수행하는 애플리케이션으로 데이터를 보낼 것이다.
어떤 데이터가 당신이 재사용하고 있는 것으로 보내질 수 있는지를 정확히 알고 유효 데이터를 확실히 보내는 것이 중요하다. 의심 할 것이 전혀 없다면 컴포넌트에 데이터를 보내기 전에 검사해야 한다. 숫자가 0에서 100 사이라면 검사하라. 반드시 컴포넌트가 사용하게 될 것과 같은 데이터타입을 사용한다. 컴포넌트가 서명 된 정수를 기대한다면 데이터를 서명 된 정수로 변환한 다음 검사한다. 특히 컴포넌트가 가장 작은 값인 0을 가진 서명 된 정수를 기대한다면 정확히 그 부분에 대해 검사하라. 거의 모든 언어에서, 서명 되지 않은 정수는 서명 된 정수로 바뀔 때 음수로 변경된다. 서명 비트가 일반적으로 서명 된 값에서는 가장 큰 비트이기 때문이다.
가능한 한 여러분이 보내는 데이터가 침입자들에 의해 제어되지 않도록 하라. 빈번한 심각한 포맷 스트링 침입은 침입자가 데이터를 디스플레이하는데 사용된 포맷을 제어할 수 있다는 개념을 바탕으로 한다. 하지만 포맷이 프로그램에서 연속적이라면 침입자가 제어할 것은 없다. gcc 컴파일러 옵션인 -Wformat-security는 코드가 포맷 스트링 침입에 취약하다는 것을 경고한다.
컴포넌트가 여러분이 보내는 데이터를 여러분이 기대하는 방식으로 인터프리팅 하는 것인지를 확인해야 한다. 침입자가 그 데이터의 값에 영향을 줄 수 있을 경우라도 그렇게 해야 한다. 이렇게 하면 매우 일반적인 문제인 메타-캐릭터 취약성(meta-character vulnerability) 문제가 발생한다. 많은 복잡한 재사용 컴포넌트들은 고유의 복잡한 명령어 언어를 구현한다. 침입자는 컴포넌트에 의해 인터프리팅 될 텍스트를 여러분이 의도하지 않았던 어떤 것을 수행하기 위해 삽입을 시도할 수 있다. 이는 유닉스 명령어 쉘(/bin/sh) 과 데이터베이스 시스템에 대한 SQL 명령어에서 가장 빈번히 발생한다.
쉘 취약성
유닉스 계열 시스템의 명령어 쉘, 특히 표준 쉘인 /bin/sh는 매우 유용하다. 다른 프로그램들을 한데로 쉽게 결합시켜 유용한 작동을 한다. 쉘에 대한 한 줄의 텍스트 라인이 백 줄의 프로그램과 같은 작동을 할 수 있다는 것은 놀랄 일이 아니다.
표준 쉘은 다른 프로그램들을 통합하기 쉽도록 설계된 많은 빌트인 기능들을 갖춘 완전한 프로그래밍 언어이다. 많은 프로그래밍 언어에는 빌트인 기능들이 포함되어 있어 표준 쉘인 C의 system(3)과 popen(3)을 호출하고 Perl의 backtick (`) 연산자는 표준 쉘을 호출한다. Windows와 MS-DOS의 사용자들만 유닉스 계열 시스템에서 쉘이 빈번히 사용된다는 데에 놀란다. COMMAND.COM은 매우 제한적이고 같은 기능을 할 수 없기 때문이다.
특히 많은 문자들이 쉘에 대해 특별한 의미를 갖고 있어서 일반 침입 트릭은 이러한 특별한 문자들을 호출 될 경우 쉘로 재전송하기 위해 프로그램을 얻으려 시도하는 것이다.
최상의 솔루션은 프로그램 내부에서 직접 쉘을 호출하지 않는 것이다. 실수하기 쉽기 때문에 단지 그렇게 하지 않으면 된다. 다시 말해서, 보안 프로그램에는 system()과 popen()을 사용하지 않는 것이 좋다. 특히 이러한 호출들로 보내지고 있는 것이 일정한 것이 아닐 때 더욱 그렇다. setuid/setgid 쉘 스크립트를 작성하지 말라; 리눅스 기반 시스템에서는 전혀 작동하지 않으며 다른 유닉스 계열 시스템상에서의 보안 취약성이 있다.
주의를 기울인다면 setuid/setgid가 아닌 쉘 스크립트를 작성할 수 있다. 쉘 스크립트는 다른 쉘의 사용 만큼 나쁘지는 않다. 스크립트 자체는 파일 내에서 정적이기 때문이다. 하지만 이 쉘을 사용해도 단점은 있다. 침입자들이 프로그램을 제어할 수 있게 쉘 안에서 무엇인가를 수행하기 쉽기 때문이다.
쉘 스크립트를 작성하고 있다면 각 프로그램의 전체 경로명을 써서 침입자가 PATH를 조작하더라도 침입하기 어렵게 만든다. 이것을 호출하는 setuid/setgid 프로그램이 있다면 환경을 우선 정리해야 한다. (합당한 PATH 값 설정 포함).
프로그램에서 직접 쉘을 호출할 것이라면 환경이 안전하게 설정되었는지를 확인한다. 침입자들은 문제를 일으키는 PATH, IFS, 기타 환경 변수들을 가지고 장난친다. 전체 경로명으로 명령어를 호출한다.
쉘을 직접 호출할 때 가장 큰 문제는 믿을 수 없는 데이터에서 나온 쉘 데이터를 보내는 경우이다. 침입자가 특별한 의미를 가진 문자를 쉘에 보낼 수 있다면 문제가 생길 것이다. 그와 같은 문자들이 많이 있다.
쉘에서 더블 쿼트 (")는 또 다른 더블 쿼트로 끝난다. 세미 콜론(;)은 명령어를 끝내고 새로운 것을 시작한다. 예를 들어 다음 코드를 가진 C++ 프로그램을 생각해 보자:
Listing 1. 취약한 C++ 코드
침입자가 변수 파일이름의 값을 제어할 수 있는지를 묻고있다. 침입자가 할 수 있다면 문제인 것이다.
이와 같은 코드가 어떻게 이용되는지를 예제를 통해 보자. SLOCCount는 프로그램에서 코드의 소스 라인들의 수를 세는 프로그램으로서 내가 직접 작성한 것이다. (참고자료) 어떤 사람들은 이전 코드와 같아 보이는 SLOCCount에 코드를 추가한 것이라고 생각한다. 하지만 어떤 사람들은 SLOCCount를 사용하여 그들이 작성하지 않은 프로그램들을 측정한다는 것을 깨닫는 것은 중요하다. 사실 그들이 측정하고 있는 프로그램은 침입자가 작성한 것일 수도 있다. SLOCCount가 침입자들에 의해 작성된 프로그램으로 보내져서 SLOCCount 사용자들에게 해를 끼칠 수 있기 때문에 SLOCCount는 침입자로부터 사용자들을 보호해야 한다.
침입자가 그들이 가진 파일들 중 하나를 x?;?;rm?-fr?~로 이름을 정했다고 가정해보자. 이 파일 이름들은 가능하다. 다시 말해서 이 시스템 명령어가 쉘에 다음의 명령어를 보낸다는 것을 의미한다:
md5sum < x ; rm -fr ~ > ./my-output
이것은 SLOCCount를 실행하는 사람의 홈 디렉토리 전체를 지울 수 있다. 이 프로그램이 네트워크 인터페이스를 갖고 있지 않더라도 보안에 대해 걱정할 필요가 없다. 침입자들이 일부 인풋을 제공할 수 있기 때문이다.
이 코드에는 다른 잠재적 문제가 있다. 결과를 저장하는 방식 때문이다. my-output에 작성하는 한 개 이상의 프로그램이 있다면? 침입자가 파일, 디렉토리, 조상 디렉토리를 조작할 수 있겠는가?
메타-캐릭터 문제에 대한 간단한 솔루션은 인풋을 메타-캐릭터가 아닌 문자(A-Z, a-z, 0-9)로만 인풋을 제한하는 것이다. 가끔씩 이렇게 할 수 있다.
그러나, 쉘을 사용해야 하고 들어오는 데이터를 제한할 수 없다면 명령어에 있는 것을 쉘로 보내기 전에 모든 잠재적인 메타-캐릭터들을 취소해야 한다. 쉘의 경우 의심이 가는 모든 문자 앞에 "\" 문자를 삽입하여 명령어의 일부로서 쉘에 보낸다. NUL 문자는 절대 허용하지 말라. 어떤 쉘은 이것을 핸들하지 않는다.
SQL 투입
SQL 투입은 쉘 메타-캐릭터와 같은 문제이다. 다만 쉘 대신 SQL 인터프리터 문제이다.
SQL 투입 침입에서 프로그램은 SQL 명령어를 만들고 이를 SQL 인터프리터로 보낸다. 프로그램은 침입자가 SQL 명령어의 의미를 변경하는 문자를 추가할 수 있도록 한다. 침입의 결과는 광범위하다. 기밀로 유지하고 싶은 데이터를 드러낼 수도 있고 임의의 데이터 변경일 수도 있으며 허용해서는 안될 곳의 인증을 하락할 수도 있고 데이터베이스를 묶어 시스템을 사용할 수 없게 만들 수도 있다.
SQL 투입 침입은 고가의 사이트를 해칠 수 있는 취약성이다. 사이트가 SQL 데이터베이스의 사용을 정렬할 수 있을 만한 데이터를 갖고 있다면 그 데이터는 누군가에 의해 악용될 충분한 소지가 있다. 쉘로의 호출을 재작성 하기 쉬워서 쉘은 개입하지 않아도 SQL 요청을 피할 수 없다. 일반적으로 프로그램의 전체 포인트는 SQL 데이터베이스에 저장된 데이터로 작업한다.
취약한 코드는 무엇을 찾고 있는지 일단 알고 있을 경우 찾기 쉽다. SQL을 호출하는 그릇된 방식은 스트링 연결을 수행하여 극도로 좁은 스크리닝을 경험하지 않은 데이터를 사용하는 SQL 명령어를 만드는 것이다. 다음의 Perl 코드는 그 문제의 예이다: $cmd = "SELECT salary,lastname,firstname FROM employee WHERE eid=" . $eid; 뒤에는 SQL 명령어인 prepare()와 execute() 에 대한 명령어가 뒤따른다. 여기에서는 간단한 스트링 연결을 갖고 있다. ("."는 Perl에서 연결 연산이다.) $eid 변수가 단순한 숫자를 갖고 있다면 기대하는 대로 작동한다. 하지만 침입자가 $eid 를 조정하여 "5 OR 1=1" 값을 갖도록 한다면 $result는 전체 테이블에서 선택된 칼럼을 포함하는 것으로 끝난다.
다른 트릭은 ";"를 삽입하여 다른 명령어를 실행하면서 여분의 더블 쿼트(")를 삽입하여 쿼트 밖의 스트링을 일찍이 취소하는 것이다. 이러한 종류의 침입에는 무한한 변이들이 있다. 기본적으로 간단한 스트링 연결이나 스트링 대체를 사용하여 SQL 쿼리를 만든다면 문제를 버는 것이다.
이러한 잠재적 문제를 확인하는 두 가지 침입 유형이 있다:
받아들이기 전에 아무 데이터나 검사한다.
취약하지 않은 루틴을 사용한다.
데이터를 받아들이기 전에 원하는 포맷을 설명하는 정규식을 정의하고 이 포맷에 맞지 않는 모든 것을 거절한다. 가능하다면 포맷이 SQL에서 통어적 의미를 갖고 있는 문자를 받아들이지 않는지를 확인한다. 문자와 숫자에 대한 값을 제한하라. Legal 리스트에 공백 문자와 구두점을 포함하지 않음으로서 조작의 잠재적 위험성을 방지할 수 있다.
신중하게 핸들하지 않는다면 문제를 일으킬 수 있는 데이터를 받아들여야 한다. 이 같은 것을 염두하고 텍스트를 연결하여 SQL 명령어를 결코 만들지 말라. 대신 라이브러리 루틴들을 검사하여 그와 같은 실수에서 자동으로 보호되는 고급 루틴들을 찾도록 하라.
다른 언어들은 "bound" 또는 "placeholder" 또는 "prepared" 또는 "parameterized" SQL 문장 같은 다른 것들을 호출한다. 예를 들어, PHP는 사용자 데이터를 정확히 대체하는 bind_param 메소드를 갖고 있다. 이 루틴들은 사용자가 제공한 데이터가 기대했던 포맷으로 있는지를 확인해야 한다. (숫자라면 루틴은 어떤 것이든 받아들인다.); 그런 다음 적절한 escaping를 가진 데이터를 삽입하여 SQL 인터프리터에 의해 잘못 번역되지 않도록 한다.
이 레벨의 기능이 여러분이 선택한 언어에서 사용할 수 없다면 루틴을 만들거나 적어도 쿼리를 만들기 위해 다른 스트링과 이들을 결합하기 전에 기대한 포맷과 값이 매치하는지 검사하는 특별한 루틴을 만든다. 루틴을 직접 작성해야 한다면 어떤 데이터든 정확히 쿼트(")를 달도록 한다.
다음은 취약성을 설명하는 일반적인 예제이다.
불행히도 침입자로부터 그 데이터를 인터프리팅하는 라이브러리로 데이터를 옮기는 것이 일반적이다. C에서 일반적인 실수는 침입자 데이터를 포맷 스트링 매개변수(이를 테면 printf(3)의 첫 번째 매개변수)로 전달하는 것이다. printif 포맷 스트링은 데이터를 작성할 수 있고 (%n 명령어를 사용함), 임의의 데이터를 드러내면서 이를 심각하게 취약한 것으로 만든다. 다음은 이러한 실수의 예제이다:
printf(bad); /* DON'T DO THIS if attacker can control 'bad' */.
포맷 스트링 침입은 일반적으로 프로그램을 목표로 하지만 다른 언어들도 포맷 스트링을 갖고 있다. 이 언어들의 경우 침입자들이 이들을 제어하지 못하도록 해야 한다. 예를 들어, Python은 빌트인 "%" 연산자를 갖고 있는데 이것은 포맷팅을 수행한다. 이를 상수로 만들어서 침입자가 이 포맷을 제어하지 못하도록 한다.
Perl에서 일반적인 실수는 침입자들에게 시스템에 대한 완전한 제어권을 주는 방식으로 open() 함수를 사용하는 것이다. Perl의 open() 함수는 보안을 요구하기에는 너무 느슨하다. 예를 들어 침입자가 파일 이름에 대해 정렬하여 pipe 심볼로 시작하거나 공간으로 시작하고 끝나도록 한다면 open() 이 특정 문자만 특별히 인터프리팅 하기 때문에 문제가 생긴다.
Perl의 빌트인 함수는 시작 공백과 끝 공백을 없앤다. 일반적으로 Perl에서는 open() 대신 sysopen() 을 사용하는 것이 더 낫다. (perlopentut 과 perlfunc 참조)
명령행 프로그램을 호출할 때 주의하라. 대부분의 유닉스 계열 프로그램들은 대시(-)를 선행시켜 옵션을 나타낸다. Windows 프로그램들은 일반적으로 슬래시(/)를 사용하지만 가끔은 대시를 사용하여 옵션임을 나타낸다. 침입자가 선행하는 대시 또는 슬래시를 명령행 프로그램으로 전달될 어떤 것 앞에 둔다면 옵션처럼 잘못 인터프리팅 된다.
C (C++ 포함)이외의 대부분의 언어들은 삽입된 NUL 문자로 스트링을 쉽게 저장할 수 있다. 반면 C 언어는 NUL 문자를 사용하여 스트링의 끝을 표시한다. 대부분의 C 루틴들은 중간에서 NUL 문자를 가진 스트링을 핸들 할 수 없다. 이러한 차이는 미미한 것 처럼 보이지만 다음을 생각해보자: 많은 라이브러리들(OS 시스템 호출 포함)은 C 규약을 사용한다. 실질적으로 무엇이든 C 규약을 사용할 수 있다. 따라서 삽입된 NUL 문자를 가진 스트링이 C 규약을 사용하여 라이브러리로 전달된다면 스트링은 갑자기 첫 번째 NUL에서 중단된다. 침입자들은 이것 또는 이와 비슷한 트릭을 사용하여 애플리케이션 프로그램이 보는 것과 호출된 루틴이 보는 것이 다르다는 것을 확인한다.
리턴 값과 예외
데이터를 정확히 보내는 것으로는 충분하지 않다. 들어오는 어떤 것이든 정확히 핸들해야 한다.
응답을 정확히 핸들링하는 것은 C에서 보안 코드를 작성할 때 가장 큰 문제들 중 하나이다. (C는 버퍼 오버플로우를 피하기 힘들다.) C는 예외를 처리하는 빌트인 기능이 포함되어 있지 않기 때문에 기본적으로 어떤 함수 리턴이든 무시한다. 에러를 리턴할 수 있는 함수를 호출할 때 마다 요청이 기대한 결과를 만들어 냈는지 신중하게 검사해야 한다. (그렇지 않을 경우 에러를 처리해야 한다.) 침입자가 이를 야기시킬 수 있으므로 이 경우도 대비해야 한다.
예를 들어, read(2)는 요청된 바이트 숫자 이하를 읽을 수 있고 write(2)는 요청된 바이트 숫자 이하를 작성할 수 있다. 어떤 것은 그렇지 않지만 gcc 사용자들은 -Wreturn-type을 (-Wall의 일부), 사용하기 바란다. "(void)" 를 선행시킴으로서 값을 없앤다. 하지만 신중해야 한다.
이것은 우선 고통이 될 수 있다. 어떤 프로그래머들은 printf()와 다른 많은 함수들이 실제로 값을 리턴한다는 것을 깨닫지 못하고 있다. 보안에 대해 염려한다면 이것을 깨달아야 한다. C에서 정말로 안전한 프로그램은 "정상" 프로세싱을 핸들하는 작은 양의 코드를 가진 에러 핸들링 코드이다.
감사하게도 대부분의 다른 프로그래밍 언어들에는 예외 핸들링이 포함되어 있어서 에러를 처리하기 더 쉽다. 하지만 너무 기뻐하지는 말라. 어떤 호출에서 어떤 예외가 던져졌으며 적절히 처리하는 방법을 알아야 한다. 특히 반드시 잠금을 해제하고 침입자들이 보낸 데이터로 인한 전채 애플리케이션의 충돌을 피해야 한다.
침입자가 리턴 된 데이터에 영향을 끼칠 수 있다면 조심하라. 예를 들어, DNS resolver를 호출하여 정보를 얻는다면 이 데이터는 침입자가 직접 제공한 데이터임을 기억하라. 이를 조심스럽게 다루고 포맷 또는 크기도 가정하지 말고 믿을 수 있을 때까지 믿어서도 안된다.
인풋인 것처럼 보이는 임의의 데이터를 핸들 할 때 조심하라. (진짜 인풋일수도 있다.) NUL 문자, 무효 문자, 문제를 일으킬 것들을 포함했을 수도 있다.
애플리케이션과 컴포넌트 간 데이터 보호하기
침입자가 애플리케이션과 호출하고 있는 컴포넌트 간 흐르는 데이터와 인터페이싱 또는 읽을 수 없도록 한다. 기저의 OS에 시스템 호출할 할 때 이는 문제가 되지 않는다. (적어도 직접적으로는...)
특히 setuid/setgid 프로그램을 실행한다면 사용자들이 라이브러리 호출을 리다이렉트 하도록 하는 방식은 불가능하고 앞서 언급했듯이, setuid/setgid 프로그램은 환경변수들을 지워 호출하는 것이 무엇이든 보호 받도록 해야 한다.
그러나 그러한 방식으로는 인터넷이라는 광야를 감당할 수 없다. 웹 서비스로의 call을 호출한다면 웹 서비스를 신뢰한다 하더라도 침입자가 애플리케이션과 사용하고 있는 서비스 간 통신과 인터페이싱 할 수 없도록 해야 한다.
기밀성과 무결성을 유지하는 전형적인 솔루션은 이미 존재하고 있는 보안 프로토콜과 암호 알고리즘을 사용하는 것이다. 프로토콜과 알고리즘을 다시 만들지 말라. 일반적인 보안 프로토콜에는 TLS/SSL, OpenSSH, IPSec 등이 있다. RSA, SHA-1, AES, Triple-DES 같은 암호 알고리즘을 적용할 것이다.
암호화하고 인증하는 서비스로의 보안 연결을 만드는 것 만으로도 서비스로의 연결에 기밀성과 무결성을 확보할 수 있다. 첫 번째 단계는 이 프로토콜과 알고리즘들이 필요하다는 것을 인식하는 것이다.
이제는 안전하다!
불안전한 방식으로 다른 컴포넌트에 의존한다면 안전한 프로그램을 가질 수 없다. 이 글에서 침입의 일반적인 종류들을 언급했다. 다른 컴포넌트들을 안전하게 호출하더라도 아웃풋을 다시 보내는 방식이 모든 일을 그르칠 수 있다.
침입자들은 프로그램 아웃풋을 이용할 줄 안다. 놀라운 일이 아니다. 다음 글에서는 그와 같은 종류의 침임을 어떻게 다루는지를 설명하겠다.
http://www-903.ibm.com/developerworks/k ··· lls.html
- 데이터 문제에 대한 차세대 NFS 계열 파일 시스템... (0)2007/05/10
- inotify를 이용한 리눅스 파일 시스템 감시 (0)2007/05/10
- Secure programmer: 컴포넌트를 안전하게 호출하기 (0)2007/05/04
- 리눅스에서의 메모리관리 (0)2007/05/04
- 리눅스 시스템 콜 레퍼런스 (0)2007/05/04
Leave your greetings.
Level: Intermediate
Jonathan Bartlett
기술 디렉터, New Media Worx
2004년 11월 16일
리눅스 프로그래머들이 사용할 수 있는 메모리 관리 기술을 살펴본다. C 언어 중심으로 설명 하겠지만 다른 언어들에도 적용할 수 있다. 메모리 관리가 어떻게 수행되는지, 메모리를 수동으로 관리하는 방법, 카운팅(counting) 또는 풀링(pooling)을 반-수동으로 관리하는 방법, 가비지 컬렉션을 사용하여 메모리를 자동으로 관리하는 방법을 설명한다.
메모리가 관리되어야 하는 이유
메모리 관리는 컴퓨터 프로그래밍의 가장 근본적인 분야 중 하나이다. 스크립팅 언어의 경우 메모리가 관리되는 방법을 신경 쓸 필요는 없지만 그것이 메모리 관리가 덜 중요하다는 것을 의미하는 것은 아니다. 메모리 매니저의 능력과 한계를 아는 것이 효과적인 프로그래밍에 있어 중요하다. C와 C++ 같은 시스템 언어들 경우, 메모리 관리가 필요하다. 이 글에서는 수동, 반자동, 자동 메모리 관리 방법의 기초를 설명하겠다.
Apple II에서 어셈블리 언어 프로그래밍을 하던 시절, 메모리 관리는 큰 문제가 아니었다. 기본적으로 전체 시스템을 실행했다. 시스템이 어떤 메모리를 갖고 있는지는 문제가 아니었다. 얼마나 많은 메모리가 남아있는지 파악할 필요도 없었다. 모든 컴퓨터는 같은 양의 메모리를 갖고 있었기 때문이다. 따라서 메모리 요구사항에 거의 변화가 없다면 사용할 메모리 영역을 선택하여 사용했다.
하지만 프로그램의 각 부분에 얼마나 많은 메모리가 필요한지 알지 못한다면 간단한 컴퓨터일지라도 문제가 있었다. 제한된 공간과 메모리 요구가 다양하다면 다음의 요구사항을 충족시킬 방법이 필요하다:
데이터를 처리할 메모리의 양을 결정한다.
사용할 수 있는 메모리에서 메모리 섹션을 확보한다.
메모리 섹션을 사용 가능한 메모리 풀로 리턴하여 프로그램의 다른 부분 또는 다른 프로그램에서 사용할 수 있도록 한다.
이러한 요구사항을 해결하는 라이브러리를 할당자(allocators)라고 한다. 문제가 동적일수록 메모리 관리는 더욱 중요하고 메모리 할당자의 선택이 더욱 중요해진다. 메모리 관리에 사용할 수 있는 다른 방법을들 보도록 하자.
C-스타일의 메모리 할당자(allocator)
C 프로그래밍 언어는 위 세 가지 요구사항을 수행하기 위해서 두 개의 함수를 제공한다:
malloc: 주어진 바이트 수를 할당하고 여기에 포인터를 리턴한다. 사용할 수 있는 충분한 메모리가 없다면 null 포인터를 리턴한다.
free: malloc에 의해 할당된 메모리 조각에 대한 포인터를 취해, 프로그램이나 OS가 나중에 사용할 수 있도록 이를 리턴한다. (실제로, 몇몇 malloc 구현은 메모리를 OS가 아닌 프로그램으로 리턴할 수 있다.)
물리적 메모리와 가상 메모리
자신의 프로그램 안에서 메모리가 할당되는 방법을 이해하려면 OS로 부터 프로그램으로 메모리가 어떻게 할당되는 지를 이해해야 한다. 컴퓨터 상의 각 프로세스는 모든 물리적 메모리에 액세스 했다고 생각한다. 분명, 동시에 다중의 프로그램을 실행하기 때문에 각 프로세스는 모든 메모리를 가질 수 없다. 프로세스는 가상 메모리를 사용하고 있다.
예를 들어, 프로그램이 메모리 주소 629에 액세스한다고 해보자. 하지만 가상 메모리 시스템은 629 RAM 위치에 이를 반드시 저장할 필요는 없다. 사실 RAM에 존재할 필요도 없다. 물리적 RAM이 꽉 찼다면 디스크로 옮겨질 수도 있다. 주소가 메모리가 할당된 물리적 장소를 반드시 반영할 필요는 없기 때문에 이를 가상 메모리라고 한다. OS는 가상 주소 대 물리적 주소 테이블을 관리하여 컴퓨터 하드웨어가 주소 요청에 적절히 대응할 수 있도록 한다. 그리고, 주소가 RAM이 아닌 디스크상에 있다면 OS는 일시적으로 프로세스를 중지하고 메모리를 디스크에서 언로드(unload)하고 디스크에서 그 요청된 메모리에 로딩하고 프로세스를 재시작 한다. 이러한 방식으로 각 프로세스는 고유의 주소 영역을 갖고 실행하고 물리적으로 설치된 것 보다 많은 메모리에 액세스 할 수 있다.
32-bit x86 시스템상에서, 각 프로세스는 4 GB 메모리에 액세스 할 수 있다. 요즘, 대부분의 사람들은 자신들의 시스템에 4GB 메모리를 갖추고 있지 않다. swap을 포함하더라도 프로세스 당 4GB 이하이다. 따라서, 프로세스가 로딩되면 특정 주소로 초기 메모리 할당이 이루어지고 시스템 브레이크(system break)를 호출한다. 과거에 이것은 언매핑(unmapped) 메모리이다. 즉 RAM 또는 디스크에 상응하는 물리적 위치를 할당 받지 못한 메모리이다. 따라서 프로세스가 초기 할당부터 메모리가 부족하면 OS가 더 많은 메모리를 매핑하도록 요청해야 한다. (매핑은 수학용어로 일대일(one-to-one) 대응을 의미한다-메모리는 가상 주소가 상응하는 물리적 위치를 갖고 있을 경우 "매핑"되어 그곳에 저장된다.)
유닉스 기반 시스템은 추가 메모리로 매핑되는 두 개의 기본적인 시스템 호출을 갖고 있다:
brk:brk()는 매우 간단한 시스템 호출이다. 이 프로세스를 위한 매핑된 메모리의 끝에 있는 위치인 시스템 브레이크를 기억하는가? 프로세스에 메모리를 추가 또는 제거하기 위해 brk()는 그 위치를 단순히 앞뒤로 이동한다.
mmap:mmap()또는 "memory map"은 brk() 비슷하지만 훨씬 유연하다. 우선, 프로세스의 끝 뿐만 아니라 어디에나 메모리를 매핑할 수 있다. 그리고 가상 주소를 물리적 RAM 또는 swap에 매핑할 수 있고 파일과 파일 위치로 매핑하여 읽고 쓰는 메모리 주소가 파일에서 데이터를 읽고 쓸 것이다. 하지만 여기서는 mmap의 기능에 초점을 맞춰 매핑된 RAM을 프로세스에 추가하는 것을 설명하겠다. munmap()은 mmap()와 반대되는 작동을 수행한다.
brk()또는 mmap()은 가외의 가상 메모리를 프로세스에 추가하는데 사용될 수 있다. 우리 예제에서는 보다 간단하고 일반적인 brk()를 사용할 것이다.
할당자(allocator) 구현하기
C 프로그래밍을 많이 사용했다면 malloc()과 free()를 상당히 많이 사용했을 것이다. 하지만 이들이 OS에서 어떻게 구현되는지에 대해서는 많이 생각하지 못했을 것이다. 이 섹션에서는 malloc과 free의 간단한 구현 코드를 소개하겠다.
이 예제를 실행하려면, 코드를 복사하여 malloc.c 파일에 붙인다.
대부분의 OS에서 메모리 할당은 두 개의 함수로 핸들된다:
void *malloc(long numbytes): 메모리의 numbytes를 할당하여 포인터를 파일 바이트로 리턴한다.
void free(void *firstbyte): 이전 malloc에 의해 리턴된 포인터가 있다면, 할당된 공간을 프로세스의 "유휴 공간(free space)"에 준다.
malloc_init은 메모리 할당자를 초기화 할 함수가 될 것이다. 세 가지 일을 수행한다: 초기화되는 할당자를 확인하고, 시스템 상에 유효 메모리 주소를 찾고, 관리되는 메모리의 앞에 포인터를 설정한다. 이 세 변수는 글로벌 변수이다:
앞서 언급했듯이 매핑된 메모리의 끝-마지막 유효 주소-는 시스템 브레이크 또는 커런트 브레이크로 알려져 있다. 대부분의 유닉스 시스템 상에서 커런트 시스템 브레이크를 찾으려면 sbrk(0) 함수를 사용한다. sbrk은 인자에 있는 바이트의 수 만큼 커런트 시스템 브레이크를 움직이고 새로운 시스템 브레이크를 리턴한다. 인자 0으로 이것을 호출하면 커런트 브레이크를 리턴한다. 다음은 malloc 초기화 코드이다. 커런트 브레이크를 찾고 변수를 초기화한다:
이제 메모리를 올바르게 관리하기 위해 무엇을 할당하고 무엇을 할당 해제하는지를 트래킹할 수 있어야 한다. free 가 호출된 후에 mark block 같은 사용되지 않은 것을 실행해야 한다. 그리고 malloc이 호출되면 사용되지 않은 block의 위치를 정할 수 있다. 따라서 malloc에 의해 리턴된 모든 메모리 조각의 시작에는 이 구조를 갖게 된다:
이제, malloc을 호출하는 프로그램에 문제를 일으킬 것이라고 생각할 수도 있다. 이들이 어떻게 구조를 알 수 있겠는가? 답은 '그것에 대해 알 필요가 없다' 이다; 이것을 리턴하기 전에 구조 뒤에 포인터를 움직임으로서 숨길 수 있다. 이는 리턴된 포인터가 어떤 목적으로도 사용되지 않은 메모리를 지적하도록 할 것이다. 이러한 방식으로, 호출 프로그램의 관점에서 볼 때, 그들이 얻는 모든 것은 유휴의 개방 메모리 이다. free()를 통해 포인터를 뒤로 전달하면 얼마간의 메모리 바이트를 저장하여 이 구조를 다시 찾는다.
이제 메모리 할당에 대해 논하기 전에 메모리를 비우는 것에 대해 이야기하려고 한다. 이것이 보다 간단하기 때문이다. 메모리를 비우기 위해 해야 할 일은 주어진 포인터를 취해서 sizeof(struct mem_control_block) 바이트를 백업하고 이를 사용 가능한 것으로 표시하는 것이다:
여러분도 보다시피 이 할당자에서 메모리 비우기는 매우 간단한 방식을 사용하여 일정한 시간에 수행된다. 메모리 할당은 약간 더 어렵다. 다음은 알고리즘의 아웃라인이다:
오픈 청크를 찾고 있는 링크 된 포인터를 사용하여 메모리를 검사하고 있다. 다음은 코드이다:
그리고 이것은 우리의 메모리 매니저이다. 이제 이를 구현하여 우리의 프로그램에서 실행시키도록 한다.
malloc 호환의 할당자를 구현하려면 (realloc()같은 몇몇 함수는 다루지 않았지만, malloc()과 free()도 주요 함수이다.) 다음 명령어를 실행한다:
이것은 malloc.so라는 파일을 만들어낸다. 이것이 우리 코드를 포함하고 있는 공유 라이브러리이다.
다음과 같이 유닉스 시스템에서 시스템 malloc() 대신 본인의 할당자를 사용할 수 있다:
LD_PRELOAD 환경 변수는 동적 링커가 로딩할 실행 파일에 전에 주어진 공유 라이브러리의 심볼을 로딩하게 한다. 지정된 라이브러리에서 그 심볼에 우선순위를 준다. 따라서 이 세션에서 지금부터 우리가 시작하는 모든 애플리케이션은 시스템이 아닌 우리의 malloc()을 사용할 것이다. 몇몇 애플리케이션들 malloc()을 사용하지 않지만 이들은 예외인 경우이다. realloc()같은 메모리 관리 함수를 사용하거나 malloc()의 내부 작동에 대한 어설픈 추측을 한다면 분명 충돌이 일어날 것이다. ash 쉘은 우리의 새로운 malloc()을 사용하면 작동이 잘 될 것이다.
malloc()이 사용되고 있다는 것을 확인하려면 함수의 엔트리 포인트에 있는 write()에 호출을 추가하여 이를 테스트해야 한다.
우리의 메모리 매니저는 보완되어야 할 것이 많이 있다. 다음과 같은 단점들이 있다:
시스템 브레이크(글로벌 변수)에서 작동하기 때문에 다른 할당자 또는 mmap와 공존할 수 없다.
메모리를 할당할 때, 최악의 시나리오는 모든 프로세스의 메모리를 거쳐야 한다는 것이다: 여기에는 디스크상에 위치한 많은 메모리가 포함되어 있다. 이는 OS가 데이터를 디스크에서 이동시켜야 한다는 것을 의미한다.
메모리 부족 에러를 핸들링 할 방도가 없다 (malloc은 에러가 아닌 경우만 취급한다.)
realloc() 같은 다른 많은 메모리 함수들을 구현하지 않는다.
sbrk() 요청한 것 보다 많은 메모리를 주기 때문에 힙의 끝에 메모리를 유출하게 된다.
is_available 플래그는 4-byte 단어를 사용한다. 1 bit 정보를 포함할 때에도 그렇다.
할당자는 쓰레드 보안이 되지 않는다.
할당자는 유휴 공간을 더 큰 블록으로 합체할 수 없다.
할당자의 간단한 적합(fitting) 알고리즘이 잠재적인 메모리 단편화를 만든다.
이외에도 많은 문제들이 남아있다.
기타 malloc 구현
malloc()의 여러 구현이 있고 저마다의 장단점이 있다. 할당자를 디자인할 때 다음 사항을 고려해야 한다:
할당 속도
할당 해제 속도
쓰레디드 환경에서의 작동
메모리가 파일링(filing)에 가까웠을 때의 작동
캐시 지역성
메모리 오버헤드 기록
가상 메모리 환경에서의 작동
크고 작은 객체들
실시간 게런티
각 구현 마다 장단점이 있다. 우리의 단순한 할당자의 경우 할당이 매우 느리지만 할당 해제는 매우 빠르다. 가상 메모리 시스템을 사용한 빈약한 작동 때문이다. 큰 객체에 적합하다.
이외에도 다른 할당자들을 사용할 수 있다:
Doug Lea Malloc: Doug Lea Malloc은 Doug Lea의 원래 할당자, GNU libc 할당자 ptmalloc을 포함한 전체 할당자군이다. Doug Lea의 할당자는 기본적인 구조가 우리 버전과 매우 비슷하지만 인덱스를 결합하여 검색이 훨씬 빠르고 여러 사용되지 않는 청크들을 하나의 큰 청크로 결합하는 기능도 있다. 또한 캐싱을 활용하여 최근에 비워진 메모리를 빠르게 재사용할 수 있도록 한다. ptmalloc은 다중 쓰레드를 지원하도록 확장된 Doug Lea Malloc의 한 버전이다. (참고자료)
BSD Malloc: 4.2 BSD와 함께 배포되고 FreeBSD에 포함된 구현인 BSD Malloc은 사전 결정된 크기의 객체 풀(pool)로부터 객체를 할당하는 할당자이다. 객체 사이즈를 위한 사이즈 클래스를 갖고 있다. 따라서 일정 사이즈의 객체를 요구한다면 객체에 맞는 어떤 사이즈의 클래스든지 할당할 것이다. 빠른 구현이라는 장점이 있지만 메모리를 낭비할 수 있다. (참고자료)
Hoard: Hoard는 멀티쓰레디드 환경에서 빠르게 작동할 목적으로 작성되었다. 따라서 어떤 프로세스라도 메모리 할당을 기다리지 않도록 잠금(locking)을 최대한 활용하도록 설계되었다. 많은 할당과 할당 해제를 수행하는 멀티쓰레디드 프로세스의 속도를 크게 높일 수 있다. (참고자료)
이것들은 많은 할당자들에 대해 잘 알려진 것들이다. 여러분의 프로그램이 할당에 대해 특별한 필요가 있다면 그 프로그램이 메모리를 할당하는 방식에 맞는 커스텀 할당자를 작성하는 것이 더 낫다. 하지만 할당자 디자인이 익숙하지 않다면 커스텀 할당자는 더 많은 문제를 만들어 낼 수 있다. 이 문제에 관해서는 Donald Knuth의 저서The Art of Computer Programming Volume 1: Fundamental Algorithms in section 2.5, "Dynamic Storage Allocation"을 참조하기 바란다. (참고자료)
C++ 에서, operator new()를 오버로드하여 클래스 기반 또는 템플릿 기반의 할당자를 구현할 수 있다. Andrei Alexandrescu의 Modern C++ Design의 4장("Small Object Allocation")에서는 작은 객체 할당자에 대해 설명하고 있다. (참고자료)
malloc() 기반 메모리 관리의 단점
우리의 메모리 매니저 뿐만 아니라 malloc()기반의 메모리 관리에도 단점은 있다. malloc()으로 메모리를 관리하는 것은 장기 실행하는 스토리지를 관리해야 하는 프로그램에게는 쥐약이다. 메모리 여유(floating)에 대한 레퍼런스가 많다면 언제 릴리즈 되어야 하는지 알기 힘들다. 수명주기가 현재 함수까지로 제한되어 있는 메모리는 관리가 쉽지만 그 이상의 수명주기를 가진 메모리는 훨씬 더 어렵다. 또한 많은 API들은 메모리 관리에 대한 책임이 호출 프로그램에 있는지 아니면 호출된 함수에 있는지에 대해 모호하다.
메모리를 관리할 때의 문제들 때문에 많은 프로그램들은 각자의 메모리 관리 규칙을 따르고 있다. C++의 예외 핸들링은 이 일을 더욱 복잡하게 한다. 가끔, 실제 연산 태스크를 수행 하는 것 보다 메모리 할당 및 클린업 관리에 더 많은 코드가 집중되어 있는 것 같다. 따라서 메모리 관리에 대한 다른 대안을 모색하고자 한다.
반자동 메모리 관리 전략
Reference counting
Reference counting은 반자동 메모리 관리 기술로서 어느 정도의 프로그래머 지원이 필요하다. 하지만 객체가 더 이상 사용되지 않는 다는 것 까지 알 필요는 없다. reference counting 방식은 여러분을 위한 것이다.
reference counting에서 공유된 모든 데이터 구조는 그 구조에 현재 활성화된 '레퍼런스'의 수를 포함하는 필드를 갖고 있다. 프로시저가 포인터에서 데이터 구조로 전달되면 레퍼런스 카운트(reference count)에 추가한다. 기본적으로 데이터 구조에 어떻게 위치들이 저장되는지를 명령하고 있는 것이다. 프로시저가 이것을 사용하는 것을 끝마치면 레퍼런스 카운트가 감소한다. 이와 동시에 카운트가 0으로 떨어졌는지를 확인한다. 그렇다면 메모리가 비워진 것이다.
이것의 장점은 주어진 데이터 구조가 따라야 하는 프로그램의 모든 경로를 따라갈 필요가 없다는 것이다. 이것에 대해 각 지역화된 레퍼런스는 카운트를 적절히 감소 또는 증가시킨다. 이로서 메모리를 사용중일 때 비워지게 되는 현상을 막는다. 하지만 레퍼런스 카운팅 방식의 데이터 구조를 사용할 때마다 reference counting 함수를 실행해야 한다. 또한, 빌트인 함수와 삼자 라이브러리는 reference counting 방식을 모르거나 사용할 수 없다. reference counting은 순환식 레퍼런스를 가진 구조에는 어려움을 겪는다.
reference counting을 구현하려면 두 개의 함수가 필요하다. 하나는 레퍼런스 카운트를 증가시키는 함수이고 하나는 레퍼런스 카운트를 감소시켜 0에 도달했을 때 메모리를 비우는 함수이다.
다음은 reference counting 예제이다:
무엇을 하고자 하느냐에 따라 REF와 UNREF가 상황을 복잡하게 할 수 있다. 예를 들어 멀티쓰레디드 프로그램에 잠금을 추가하고자 한다면 refcountedstruct를 확장하여 메모리를 비우기 전에 호출 할 함수에 대한 포인터를 추가하고 싶을 것이다. (객체 지향 언어의 디스트럭터(destructor)와 같다-구조에 포인터가 포함되어 있을 때에만 필요하다.)
REF와 UNREF를 사용할 때, 포인터 할당에 있어 다음 규칙을 준수해야 한다:
UNREF 할당 전에 왼쪽 포인터가 가르키는 값
REF 할당 후에 왼쪽 포인터가 가르키는 값
refcounted 구조로 전달된 함수의 경우 다음 규칙을 따라야 한다:
REF 함수의 시작에 있는 모든 포인터
UNREF 함수의 끝에 있는 모든 포인터
다음은 reference counting을 사용하는 코드 예제이다:
reference counting은 다소 간단하기 때문에 대부분의 프로그래머들은 라이브러리를 사용하기 보다는 이들 자체를 구현한다. 하지만 malloc과 free 같은 저급의 할당자들을 의존하여 메모리를 할당 및 릴리스 하고 있다.
reference counting은 Perl 같은 고급 언어에서 많이 사용된다. 그러한 언어에서 레퍼런스 카운팅은 언어에 의해 자동으로 핸들 되어 확장 모듈을 작성하는 것 외에는 걱정할 것이 없다. 모든 것이 레퍼런스 카운팅 되어야 하기 때문에 속도는 느리지만 안전성이 높고 프로그래밍이 쉽다:
간단한 구현
사용이 쉬움
레퍼런스가 데이터 구조의 일부이기 때문에 캐시 지역성이 우수함
하지만 단점도 있다:
레퍼런스 카운팅 함수를 호출하는 것을 절대 잊어서는 안된다.
순환 데이터 구조의 일부인 구조를 릴리스 하지 않는다.
거의 모든 포인터 할당이 느리다.
레퍼런스 카운팅 된 객체를 사용하는 동안 예외 핸들링 (try 또는 setjmp()/longjmp())을 사용할 때 추가 조치를 취해야 한다.
레퍼런스를 핸들 할 여분의 메모리가 필요하다.
레퍼런스 카운터는 구조 내에서 첫 번째 위치를 차지한다. 이는 대부분의 머신에서 가장 빠르게 액세스 할 수 있다.
멀티쓰레디드 환경에서 수행할 때 느리고 어렵다.
C++는 reference counting 같은 상세한 포인터 핸들링을 핸들 할 수 있는 스마트 포인터(smart pointer)를 사용하여 프로그래머 오류를 줄일 수 있다. 하지만 스마트 포인터(C 라이브러리로의 링크)를 핸들 할 수 없는 레거시 코드를 사용해야 한다면 이를 사용하지 않는 것이 낫다. 따라서 이는 C++ 전용 프로젝트에만 유용하다. 스마트 포인터를 사용하려면 Alexandrescu의 Modern C++ Design의 "Smart Pointers"를 참조하기 바란다.
메모리 풀(pool)
메모리 풀은 메모리 관리를 반자동화 할 수 있는 또 하나의 방식이다. 메모리 풀은 특정 단계를 거쳐야 하는 프로그램을 위해 메모리 관리를 자동화한다. 예를 들어, 많은 네트워크 서버 프로세스들은 커넥션 당 할당된 메모리를 갖고 있다. 최대 수명은 현재 커넥션의 수명이다. 풀링 메모리를 사용하는 Apache는 고유의 메모리 풀을 갖고 있는 단계로 나뉘어진 연결을 갖고 있다. 이 단계의 끝에, 전체 메모리 풀은 한번에 비워진다.
풀링 메모리를 관리 할 때, 각 할당은 할당되어야 하는 곳부터 메모리의 풀을 지정해야 한다. 각 풀은 다른 수명을 갖고 있다. Apache에서 서버의 수명을 지속시키는 풀이 있다. 하나는 연결의 수명을 지속시키는 것이고 하나는 요청의 수명을 지속시키는 것이다. 따라서 연결 보다 오래 지속되는 데이터를 만들지 않는 일련의 함수를 갖고 있다면 커넥션 풀에서 이 모든 것을 할당하면서 연결 끝에는 자동으로 비워진다는 것을 알 수 있다. 게다가 어떤 구현은 메모리 풀이 비워지기 비로 직전에 호출되는 레지스터링 클린업 함수로 하여금 메모리가 비워지기 전에 수행되어야 하는 추가 태스크를 수행하도록 한다. (객체 지향의 디스트럭터와 비슷하다.).
자신이 프로그램에서 풀을 사용하려면 GNU libc의 obstack 구현 또는 Apache의 Apache Portable Runtime을 사용할 수 있다. GNU obstack는 GNU 기반 리눅스 배포판에 기본적으로 포함되기 때문에 유용하다. Apache Portable Runtime은 멀티플랫폼 서버 소프트웨어를 작성하는 모든 측면들을 핸들 할 수 있는 유틸리티가 많아서 좋다.(참고자료)
다음의 가상 코드는 obstack의 사용법을 보여준다:
기본적으로 작동의 주요 단계가 끝나면 그 단계에 대한 obstack이 비워진다. 하지만 프로시저가 현재 단계 보다 더 길게 지속될 메모리를 할당하려면 커넥션 또는 글로벌 같은 장기 obstack을 사용할 수 있다. obstack_free()로 전달된 NULL은 obstack의 전체 내용을 비워야 한다는 것을 나타낸다. 다른 값들은 사용할 수 있지만 유용하지 않다.
다음은 메모리 풀링의 장점들이다:
애플리케이션의 메모리를 관리하기가 수월하다.
풀에서 한번에 되기 때문에 메모리 할당과 할당 해제가 훨씬 빠르다. 할당은 O(1) 시간에 수행될 수 있고 풀 릴리스는 끝난다. (실제로 O(n) 시간이지만 대부분의 경우 O(1)로 만드는 거대한 요소에 의해 나뉘어진다.)
에러 핸들링 풀은 사전 할당되어 정규 메모리가 소진되면 프로그램이 복구할 수 있도록 한다.
사용하기 쉬운 표준 구현들이 있다.
다음은 메모리 풀링의 단점들이다:
메모리 풀은 단계 별로 작동하는 프로그램에만 유용하다.
메모리 풀은 삼자 라이브러리로는 잘 작동하지 않는다.
프로그램 구조가 바뀌면 풀이 변경되어야 하는데, 이로 인해 메모리 관리 시스템을 재디자인 해야 한다.
할당하고 싶은 풀이 어떤 것인지를 기억해야 한다. 이것이 잘못되면 회복이 불가능하다.
가비지 컬렉션
가비지 컬렉션은 더 이상 사용하지 않는 데이터 객체들을 완전 자동으로 탐지 및 제거하는 것이다. 가비지 컬렉터는 사용 가능한 메모리가 특정 임계치로 떨어지면 자동으로 실행된다. 일반적으로 프로그램에서 사용할 수 있는 "기본" 설정-스택 데이터, 글로벌 변수, 레지스터-으로 시작한다. 그런 다음 서로 링크 된 데이터의 모든 조각들을 트레이스한다. 컬렉터가 찾는 모든 것은 좋은 데이터이다. 찾지 못하는 모든 것은 가비지(쓰레기)이고 파괴 및 재사용될 수 있다. 메모리를 효과적으로 관리하려면 많은 유형의 가비지 컬렉터들이 데이터 구조 내의 포인터 레이아웃을 알 필요가 있다. 그리고 올바로 작동하는 언어의 일부가 되어야 한다.
컬렉터 유형
복사(Copying): 메모리 스토리지를 두 부분으로 나누고 데이터가 오직 한 쪽에만 있도록 한다. "기본" 요소들을 갖고 시작하면서 주기적으로 한 쪽에서 다른 쪽으로 데이터를 복사하기 시작한다. 새롭게 채워진 메모리 섹션은 활성이 되고 다른 쪽의 모든 것은 쓰레기로 간주된다. 또한 복사가 발생하면 모든 포인터들은 각 메모리 아이템의 새로운 위치를 가르키도록 업데이트 되어야 한다. 따라서 이 방식의 가비지 컬렉션을 사용하려면 컬렉터는 프로그래밍 언어로 통합되어야 한다.
표시와 청소(Mark and sweep): 데이터의 각 조각은 태그로 표시된다. 가끔 모든 태그들은 0으로 설정되고 컬렉터는 "기본" 요소들로 시작하는 데이터부터 검사한다. 메모리를 만나면 태그를 1로 표시한다. 끝에 1로 태그가 붙지 않는 모든 것은 쓰레기로 간주되어 나중에 재사용된다.
증가(Incremental): 증가 가비지 컬렉터는 모든 데이터 객체들을 전체적으로 실행할 필요가 없다. 모든 메모리를 실행하면 모으는 기간 동안 한꺼번에 멈춰야 하고 현재의 모든 데이터에 액세스하는 것과 관련한 캐시 문제 때문이다. 증가 컬렉터는 이러한 문제들을 방지한다.
Conservative: Conservative 가비지 컬렉터는 메모리를 관리하는 데이터 구조에 대해 알 필요가 없다. 단순히 모든 데이터 바이트를 보고 이들이 모두 포인터를 갖고 있다는 것으로 간주한다. 따라서 바이트의 시퀀스가 할당된 메모리 조각에 대한 포인터가 되면 이를 레퍼런스 된 것으로 표시한다. 이는 정수 필드가 할당된 메모리의 주소인 값을 포함한다면 레퍼런스 되지 않은 메모리가 모아질 때 문제가 될 수 있다. 하지만 이는 매우 드문 경우이고 적은 메모리만을 쓴다. Conservative 컬렉터는 모든 프로그래밍 언어로 통합될 수 있는 장점이 있다.
Hans Boehm의 conservative 가비지 컬렉터는 가장 대중적인 가비지 컬렉터 중 하나이다. 무료이고 Conservative 유형이자 증가 유형이기 때문이다. --enable-redirect-malloc으로 이를 구현하여 (API 대신 Malloc/Free 사용하는)시스템 할당자 대용으로 사용할 수 있다. 사실 이렇게 하면 같은 LD_PRELOAD 트릭을 트릭을 사용할 수 있다. 프로그램이 메모리를 유출한다는 의심이 들면 이 가비지 컬렉터를 사용하여 프로세스 사이즈를 줄일 수 있다. 메모리 유출이 심했을 때 Mozilla 초기에는 많은 사람들이 이 기술을 사용했다. 이 가비지 컬렉터는 Windows®와 UNIX 모두 사용할 수 있다.
다음은 가비지 컬렉션의 장점이다:
메모리의 이중 유휴화(double-freeing)나 객체 수명을 걱정할 필요가 없다.
어떤 컬렉터의 경우 일반 할당에 사용되는 같은 API를 사용할 수 있다.
다음은 가비지 컬렉션의 단점이다:
대부분의 컬렉터를 사용할 때, 메모리가 비워져가고 있을 때 기회가 없다.
많은 경우, 가비지 컬렉션은 다른 형태의 메모리 관리 보다 느리다.
가비지 컬렉션 에러로 인한 버그는 디버그가 어렵다.
사용되지 않는 포인터를 null로 설정하지 않는다면 메모리 유출이 있다.
결론
퍼포먼스, 용이성, 구현가능성, 쓰레딩 기능 등 비교할 것이 많다. 프로젝트 요구사항에 맞는 다양한 메모리 관리 패턴이 있다. 각 패턴마다 광범위한 구현을 갖추고 있고, 이들 각각 장단점이 있다. 프로그래밍 환경을 위해 디폴트 기술을 사용하는 것은 좋은 일이지만 사용할 수 있는 옵션을 아는 것 또한 중요한 일이다. 다음 표에서는 이 글에서 다루어진 메모리 관리 전략들을 비교했다.
표 1. 메모리 할당 전략 비교
전략 할당 속도 할당 해제 속도 캐시 지역성 용이성 보편성 실시간 사용 SMP 및 쓰레드 친화력
커스텀 할당자 구현에 의존 구현에 의존 구현에 의존 매우 어려움 없음 구현에 의존 구현에 의존
일반 할당자 적은 메모리 사용에 빠름 매우 빠름 거의 없음 쉬움 매우 보편적 No No
GNU malloc 중간 빠름 중간 쉬움 Very No 중간
Hoard 중간 중간 중간 쉬움 Very No Yes
Reference counting N/A N/A 우수함 중간 중간 Yes (malloc 구현에 의존) 구현에 의존
Pooling 중간 매우 빠름 우수함 중간 중간 Yes (malloc 구현에 의존) 구현에 의존
가비지 컬렉션 중간 (slow when collection occurs) 중간 거의 없음 중간 중간 No 거의 없음
증가 가비지 컬렉션 중간 중간 중간 중간 중간 No 거의 없음
중도 증가 가비지 컬렉션 중간 중간 중간 쉬움 매우 보편적 No 거의 없음
참고자료
웹 문서
The obstacks section of the GNU C Library manual
The Apache Portable Runtime documentation
기본 할당자
Doug Lea's Malloc
BSD Malloc
ptmalloc
Hoard
GNU Memory-Mapped Malloc (part of GDB)
ElectricFence Malloc Debugger
풀링 할당자
GNU Obstacks
Apache's pooled allocator (in the Apache Portable Runtime)
Squid
NetBSD
talloc
스마트 포인터 및 커스텀 할당자
The Loki C++ Library
가비지 컬렉터
Hahns Boehm Conservative Garbage Collector
가상 메모리 문서
A New Virtual Memory Implementation for Berkeley UNIX
Mel Gorman's Linux VM Documentation
malloc 문서
Malloc in Modern Virtual Memory Environments
Hoard -- a Scalable Memory Allocator for Multithreaded Environments
Design of a General Purpose Memory Allocator for the 4.3BSD UNIX Kernel
A Memory Allocator
Memory Management for High-Performance Applications
커스텀 할당자 문서
Some Storage Management Techniques for Container Classes
Composing High-Performance Memory Allocators
Reconsidering Custom Memory Allocation
가비지 컬렉션 문서
Uniprocessor Garbage Collection Techniques
The Measured Cost of Garbage Collection
Memory Allocation Myths and Half-Truths
Space Efficient Conservative Garbage Collection
웹 참고자료
The Memory Management Reference
OOPS Group Papers on Memory Management and Memory Hierarchies
Memory Management in C++
Programming Alternatives: Memory Management
The Garbage Collection FAQ
Richard Jones's Garbage Collection Bibliography
Debugging Tools for Dynamic Storage Allocation and Memory Management
책
C++ Pointers and Dynamic Memory Management
Memory as a Programming Concept in C and C++
Garbage Collection: Algorithms for Automatic Dynamic Memory Management
Section 2.5, "Dynamic Storage Allocation" Fundamental Algorithms
Section 2.3.5, "Lists and Garbage Collection" Fundamental Algorithms
Chapter 4, "Small Object Allocation" Modern C++ Design
Chapter 7, "Smart Pointers" Modern C++ Design
Jonathan's Chapter 8, "Intermediate Memory Topics" Programming from the Ground Up
developerWorks
Self-manage data buffer memory (developerWorks, January 2004)
A framework for the user defined malloc replacement feature (developerWorks, February 2002)
Mastering Linux debugging techniques (developerWorks, August 2002)
Handling memory leaks in Java programs (developerWorks, February 2001)
developerWorks Linux zone
Speed-start your Linux app
developerWorks blogs
Linux books at discounted prices
목 차:
메모리가 관리되어야 하는 이유
C-스타일의 메모리 할당자(allocator)
반자동 메모리 관리 전략
가비지 컬렉션
결론
참고자료
필자소개
기사에 대한 평가
관련 dW 링크:
Self-manage data buffer memory
A Framework for the User Defined Malloc Replacement Feature
Mastering Linux debugging techniques
Handling memory leaks in Java programs
developerWorks newsletter 구독하기
US 원문 읽기
필자소개
Jonathan Bartlett은 리눅스 어셈블리 언어 입문서인 Programming from the Ground Up의 저자이다. New Media Worx에서 개발 팀을 이끌고 있다.
출처 : http://www-903.ibm.com/developerworks/k ··· emory%2F
- inotify를 이용한 리눅스 파일 시스템 감시 (0)2007/05/10
- Secure programmer: 컴포넌트를 안전하게 호출하기 (0)2007/05/04
- 리눅스에서의 메모리관리 (0)2007/05/04
- 리눅스 시스템 콜 레퍼런스 (0)2007/05/04
- Linux 하드웨어 안정성 가이드, Part 2 (0)2007/05/04
Leave your greetings.
시스템콜은 운영체제에 명령을 내리기 위해서 사용자에게 제공되는 인터페이스다. 이 문서는 리눅스에서 제공하는 거의 대부분의 시스템콜에 대한 번호와 간단한 설명을 제공한다. 프로그래밍시 빠르게 시스템콜을 참조하고자 할때 유용할 것이다.
이 문서는 http://tiger.la.asu.edu/quick_ref/linux ··· kref.pdf 의 번역 문서입니다. 참고하시기 바랍니다.
리눅스 시스템 콜 퀵 레퍼런스
윤 상배
yundream@joinc.co.kr
고친 과정
고침 0.8 2004년 3월 19일 23시
최초 번역
--------------------------------------------------------------------------------
차례
1. 소개
2. 시스템 콜 예제
3. 시스템 콜 레퍼런스
1. 소개
시스템 콜이란 리눅스 커널에 의해 제공되는 서비스이다. 예를 들어 파일에 쓰는 서비스를 이용하길 원한다면 프로그래머는 리눅스에서 제공하는 해당 시스템콜을 이용해서 프로그램을 작성한다. C를 이용해서 프로그래밍을 할경우 대부분의 시스템콜은 libc를 통한 포장(wrapper)함수형태로 제공받을 수 있다.
시스템 콜 함수에 대한 정보는 메뉴얼 페이지(man page)의 섹션 2번을 통해서 얻어올 수 있다. 예를 들어 read()시스템콜에 대한 정보를 얻기를 원한다면 man 2 read 하면 된다. 시스템콜에 대한 소개를 원한다면 man 2 intro를 이용하기 바란다. # man 2 intro
시스템 콜을 사용하기 위해서 libc를 통한 포장함수를 호출하는 외에도 syscall()함수를 이용해서 직접 실행시키는 방법도 있다. 각각의 시스템콜은 고유한 번호를 가지고 있는데, syscall에 이 시스템 콜의 번호를 입력하는 방식으로 호출한다. 내부적으로 syscall은 0x80 인터럽트를 이용해서 커널에 명령을 전달한다.
시스템 콜함수들은 syscall.h 와 unistd.h 에 정의되어 있으며, 시스템 콜 테이블은 "arch/i386/kernel/entry.S"리눅스 커널 소스파일에 정의되어 있다.
--------------------------------------------------------------------------------
2. 시스템 콜 예제
#include <syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
int main()
{
long ID1, ID2;
// 시스템콜의 직접 사용
// 시스템콜 번호 : 20
ID1 = syscall(SYS_getpid);
printf("%ld\n", ID1);
// libc를 이용한 시스템 콜
// 시스템콜 번호 : 20
ID2 = getpid();
printf("%ld\n", ID2);
return (0);
}
--------------------------------------------------------------------------------
3. 시스템 콜 레퍼런스
표 1. 시스템 콜 레퍼런스
번호 함수 이름 설명 소스
1 exit() 현재 프로세스의 종료 kernel/exit.c
2 fork() 자식 프로세스의 생성 arch/i385/kernel/process.c
3 read() 파일 지정자로 부터 읽기 fs/read_write.c
4 write() 파일 지정자로 쓰기 fs/read_write.c
5 open() 파일이나 장치열기 fs/open
6 close() 파일 지정자 닫기 fs/open.c
7 waitpid() 프로세스의 종료를 기다린다 kernel/exit.c
8 creat() 파일이나 장치의 생성 fs/open.c
9 link() 파일을 위한 새로운 이름 만들기 fs/namei.c
10 unlink() 파일 혹은 참조된 이름을 삭제한다 fs/namei.c
11 execv() 프로그램의 실행 arch/i386/kernel/process.c
12 chdir() 작업디렉토리의 변경 fs/open.c
13 time() 초단위의 시간 얻기 kernel/time.h
14 mknod() 일반 혹은 특수파일의 생성 fs/namei.c
15 chmod() 파일의 권한 바구기 fs/open.c
16 chown() 파일의 소유자 변경 fs/open.c
18 stat() 파일의 상태 얻기 fs/stat.c
19 lseek() 파일에서의 위치 변경 fs/read_write.c
20 getpid() 프로세스의 ID를 얻어온다 kernel/sched.c
21 mount() 파일 시스템의 마운트 fs/super.c
22 umount() 파일 시스템 마운트 해제 fs/super.c
23 setuid() 실제 유저 아이디 설정 kernel/sys.c
24 getuid() 실제 유저 아이디 얻어오기 kernel/sched.c
25 stime() 시스템의 시간과 날짜 설정 kernel/time.c
26 ptrace() 부모프로세스가 자식프로세스의 실행을 제어하도록 허가 arch/i386/kernel/ptrace.c
27 alarm() 실정시간후 alarm시그널이 전달되도록 한다. kernel/sched.c
28 fstat() 파일 상태 얻기 fs/stat.c
29 pause() 시그널이 전달될때까지 대기한다. arch/i386/kernel/sys_i386.c
30 utime() 파일의 엑세스시간과 수정시간을 수정한다. fs/open.c
33 access() 파일의 권한을 검사한다. fs/open.c
34 nice() 프로세스의 우선순위를 번경한다. kernel/sched.c
36 sync() 슈퍼블럭을 업데이트 한다. fs/buffer.c
37 kill() 프로세스에 시그널을 전송한다. kernel/signal.h
38 rename() 파일의 이름과 위치를 변경한다. fs/namei.c
39 mkdir() 디렉토리를 생성한다. fs/namei.c
40 rmdir() 디렉토리를 제거한다. fs/namei.c
41 dup() 열린 파일 지정자를 복사한다. fs/fcntl.c
42 pipe() 내부통신을 위한 채널을 생성한다. arch/i386/kernel/sys_i386.c
43 times() 프로세스 시간을 얻는다. kernel/sys.c
45 brk() 프로세스의 데이터 세그먼트 크기를 변경한다. mm/mmap.c
46 setgid() real 그룹 아이디를 설정한다. kernel/sys.c
47 getgid() real 그룹 아이디를 얻어온다. kernel/sched.c
48 sys_signal() ANSI C 시그널 제어 kernel/signal.c
49 geteuid() effective 유저 아이디 가져오기 kernel/sched.c
50 getegid() effective 그룹 아이디 가져오기 kernel/sched.c
51 acct() 프로세스 측정을 켜거나 끈다. kernel/acct.c
52 umount2() 파일시스템 unmount fs/super.c
54 ioctl() 장치 제어 fs/ioctl.c
55 fcntl() 파일 제어 fs/fcntl.c
56 mpx 사용되지 않음
57 setpgid() 프로세스의 그룹 아이디 설정 kernel/sys.c
58 ulimit() 사용되지 않음
59 olduname 구식의 uname 시스템콜 arch/i386/kernel/sys_i386.c
60 umaks() 파일 마스크의 생성 kernel/sys.c
61 chroot() 루트디렉토리의 변경 fs/open.c
62 ustat() 파일시스템의 통계 얻기 fs/super.c
63 dup2() 파일 지정자 복사 fs/fcntl.c
64 getppid() 부모 프로세스의 PID 얻기 kernel/sched.c
65 getpgrp() 프로세스의 그룹 아이디 얻기 kernel/sys.c
66 setsid() 세션과 프로세스 그룹 아이디 설정 kernel/sys.c
67 sigaction() POSIX 시그널 제어 함수 arch/i386/kernel/signal.c
68 sigmask() ANSI C 시그널 제어 kernel/signal.c
69 ssetmask() ANSI C 시그널 제어 kernel/signal.c
70 setreuid() 실제 혹은 유효사용자 아이디의 설정 kernel/sys.c
71 setregid() 실제 혹은 유효그룹 아이디의 설정 kernel/sys.c
72 sigsuspend() 시그널 마스크를 일시적으로 대체한후 시그널을 기다린다. arch/i386/kernel/signal.c
73 sigpending() 시그널을 블럭하고 검사를 수행한다. kernel/signal.c
74 sethostname() 호스트이름 설정 kernel/sys.c
75 setrlimit() 자원의 제한값을 설정한다. kernel/sys.c
76 getrlimit() 자원의 제한값을 얻어온다. kernel/sys.c
77 getrusage() 자원의 제한값을 얻어온다. kernel/sys.c
78 gettimeofday() 날짜와 시간을 얻는다. kernel/time.c
79 settimeofday() 날짜와 시간을 설정한다. kernel/time.c
80 getgroups() 포함된 그룹아이디의 목록을 얻는다. kernel/sys.c
81 setgroups() 포함될 르룹아이디의 목록을 설정한다. kernel/sys.c
82 old_select() 오래된 버젼의 입출력다중화 arch/i386/kernel/sys_i386.c
83 symlink() 파일에 대한 심볼릭링크 생성 fs/namei.c
84 lstat() 파일의 상태 얻기 fs/stat.c
85 readlink() 심볼릭 링크의 연결된 파일 이름을 읽는다. fs/stat.c
86 uselib() 공유라이브를 선택한다. fs/exec.c
87 swapon() 파일과 장치의 스와핑을 시작한다. mm/swapfile.c
88 reboot() 리붓 시키거나 Ctrl-Alt-Del을 활성화/비활성화 시킨다. kernel/sys.c
89 old_readdir() 오래된 버젼의 디렉토리 내용읽기 fs/readdir.c
90 old_mmap() 오래된 버젼의 메모리 파일 대응 arch/i386/kernel/sys/i386.c
91 mnunmap() 메모리 페이지 해제 mm/mmap.c
92 truncate() 파일의 길이 결정 fs/open.c
93 ftruncate() 파일의 길이 결정 fs/open.c
94 fchmod() 파일의 권한 변경 fs/open.c
95 fchown() 파일의 그룹및 소유자 변경 fs/open.c
96 getpriority() 프로그램의 우선순위 얻어오기 kernel/sys.c
97 setpriority() 프로그램의 우선순위 설정 kernel/sys.c
98 profile() execution time profile
99 statfs() 파일시스템 정보 얻기 fs/open.c
100 fstatfs() 파일시스템 정보 얻기 fs/open.c
101 ioperm() set port input/output permissions arch/i386/kernel/ioport.c
102 socketcall() 소켓 시스템콜 net/socket.c
103 syslog() 커널 메시지 버퍼의 내용을 읽거나 클리어한다. kerne/printk.c
104 setitimer() 내부 타이머 설정 kernel/itimer.c
105 getitimer() 내부 타이머 값 가져오기 kernel/itimer.c
106 sys_newstat() 파일의 상태 얻기 fs/stat.c
107 sys_newlstat() 파일의 상태 얻기 fs/stat.c
108 sys_newfstat() 파일의 상태 얻기 fs/stat.c
109 olduname() 최근 커널의 정보얻기 arch/i386/kernel/sys_i386.c
110 iopl() I/O privilege 레벨 변경 arch/i386/kernel/ioport.c
111 vhangup() 가상으로 현재 tty를 중지시킨다. fs/open.c
112 idle() 0번 프로세스를 idel상태로 한다. arch/i386/kernel/process.c
113 vm86old() 가상 8086모드로 들어가기 arch/i386/kernel/vm86.c
114 wait4() 프로세스의 종료를 기다린다. BSD 스타일 kernelk/exit.c
115 swapoff() 파일/장치의 스와핑 끝내기 mm/swapfile.c
116 sysinfo() 시스템의 정보 얻어오기 kernel/info.c
117 ipc() System V IPC 시스템 콜 arch/i386/kernelk/sys_i386.c
118 fsync() 파일의 내부상태와 디스크상의 상태를 동기화 한다. fs/buffer.c
119 sigreturn() 시그널 핸들러와 클린업 스택 프레임으로 부터 반환 arch/i386/kernel/signal.c
120 clone() 자식 프로세스의 생성 arch/i386/kernel/process.c
121 setdomainname() 도메인 이름 설정 kernel/sys.c
122 uname() 최근 커널의 정보 얻어오기 kernel/sys.c
123 modify_ldt() ldt를 가져오거나 설정한다. arch/i386/kernel/ldt.c
124 adjtmex() 커널 클럭을 조율한다. kernel/time.c
125 mprotect() 메모리 영역에 대한 접근을 제어한다. mm/mprotect.c
126 sigprocmask() POSIX 시그널 제어 관련 함수 kernel/signal.c
127 create_module() 적재가능한 모듈엔트리 생성 kernel/module.c
128 init_module() 적재가능한 모듈 엔트리 초기화 kernelk/module.c
129 delete_module() 적재 모듈의 삭제 kernel/module.c
130 get_kernel_syms() retrieve exported kernel and module symbols kernel/module.c
131 quotactl() 디스크 쿼터 수정 fs/dquot.c
132 getpgid() 프로세스 그룹아이디 가져오기 kernel/sys.c
133 fchdir() 작업 디렉토리 변경 fs/open.c
134 bdflush() start, flush, buffer-dirty-flush 데몬을 조정한다 fs/buffer.c
135 sysfs() 파일시스템 타입정보 가져오기 fs/super.c
136 personality() 프로세스 실행 도메인 설정 kernel/exec_domain.c
137 afs_syscall() 사용하지 않음
138 setfsuid() 파일 시스템 검사를 위해 사용되는 사용자 실별자를 설정 kernel/sys.c
139 setfsgid() 파일 시스템 검사를 위해 사용되는 그룹 식별자를 설정
140 sys_llseek() 읽기/쓰기 파일의 위치 이동 fs/read_write.c
141 getdents() 디렉토리 내용을 읽어들인다. fs/readdir.c
142 select() 입출력 다중화 fs/select.c
143 flock() 열린파일에 대한 권고잠금 적용및 제거 fs/locks.c
144 msync() 메모리 맵과 파일의 동기화 mm/filemap.c
145 readv() 벡터를 읽는다 fs/read_write.c
146 writev() 벡터를 쓴다 fs/read_write.c
147 sys_getsid() 세션리더의 프로세스 아이디를 가져온다 kernel/sys.c
148 fdatasync() 파일의 디스크에 있는 in-core 데이터를 동기화 fs/buffer.c
149 sysctl() 시스템 파라메터를 읽고 쓴다
150 mlock() 메모리의 페이지 잠금 mm/mlock.c
151 munlock() 메모리의 페이지 잠금 풀기 mm/mlock.c
152 mlockall() 호출한 프로세스의 페이징을 금지시킨다 mm/mlock.c
153 munlockall() 호출한 프로세스에 대한 페이징을 다시 가능하도록 한다. mm/mlock.c
154 sched_setparam() 스케줄 파라메터 설정 kernel/sched.c
155 sched_getparam() 스케쥴 파라메터 설정값 가져오기 kernel/sched.c
156 sched_setscheduler() 스케쥴 알고리즘 파라메터 설정 kernel/sched.c
157 sched_getscheduler() 스케쥴 알고리즘 파라메터 값 가져오기 kernel/sched.c
158 sched_yield() kernel/sched.c
159 sched_get_priority_max() 정적 선행 범위를 가진다 kernel/sched.c
160 sched_get_priority_mix() kernel/sched.c
161 sched_rr_get_interval() 프로세스의 SCHED_RR간격을 가져온다. kernel/sched.c
162 nanosleep() 지정한 시간에 실행을 잠시 멈춘다 kernel/sched.c
163 mremap() 가상 메모리 주소를 재대응시킨다 mm/mremap.c
164 setresuid() set real, effective and saved user or group ID kernel/sys.c
165 getresuid() get real, effective and saved user or group ID kernel/sys.c
166 vm86() 8086가상 모드로 진입 arch/i386/kernel/vm86.c
167 query_module() query the kernel for various bits pertaining to modules kernel/module.c
168 poll() 파일 지정자로 부터 이벤트를 기다린다 fs/select.c
169 nfsservctl() 커널 nfs 데몬을 위한 인터페이스 fs/filesystems.c
170 setresgid() set real, effective and saved user or group ID kernel/sys.c
171 getresgid() get real, effective and saved user or group ID kernel/sys.c
172 prctl() 프로세스상에서의 실행 kernel/sys.c
173 rt_sigreturn arch/i386/kernel/signal.c
174 rt_sigaction kernel/signal.c
175 rt_sigprocmask kernel/signal.c
176 rt_sigpending kernel/signal.c
177 rt_sigtimedwait kernel/signal.c
178 rt_sigqueueinfo kernel/signal.c
179 rt_sigsuspend arch/i386/kernel/signal.c
180 pread() 파일 지정자로 부터 위치를 가져오거나 읽는다 fs/read_write.c
181 sys_pwrite() 파일 지정자로 부터 위치를 가져오거나 쓴다 fs/read_write.c
182 chown() 파일 소유자 변경 fs/open.c
183 getcwd() 최근 작업 디렉토리 가져오기 fs/dcache.c
184 capget() 프로세스 기능의 설정값 가져오기 kernel/capability.c
185 capset 프로세스 기능 설정하기 kernle/capability.c
186 sigaltstack() 시그널 스택 문맥을 가져오가나 설정 arch/i386/kernel/signal.c
187 sendfile() 파일 지정자 사이의 데이터 교환 mm/filemap.c
188 getpmsg() 사용하지 않음
189 putpmsg() 사용하지 않음
190 vfork() 자식 프로세스 생성과 부모 프로세스 블럭 arch/i386/kernel/process.c
출처 : http://www.joinc.co.kr/modules.php?name ··· 3Dnested
- Secure programmer: 컴포넌트를 안전하게 호출하기 (0)2007/05/04
- 리눅스에서의 메모리관리 (0)2007/05/04
- 리눅스 시스템 콜 레퍼런스 (0)2007/05/04
- Linux 하드웨어 안정성 가이드, Part 2 (0)2007/05/04
- Linux 하드웨어 안정성 가이드, Part 1 (0)2007/05/04

수안이의 컴퓨터 연구실
Linux_Syscall_quickref.pdf



Leave your greetings.