6.8. 경쟁 상태를 피해라

``경쟁 상태 (race condition)" 는 "이벤트들의 상대적인 타이밍에 대한 돌발적인 임계 의존성때문에 발생하는 이상한 동작" 으로 정의될 수 있다 [FOLDOC]. 경쟁 상태는 일반적으로 파일 또는 변수와 같은 공유 자원을 접근하는 하나 또는 그 이상의 프로세스를 포함하는데 이러한 다중 접근이 적절히 제어되지 않는 것이다.

일반적으로 프로세스는 원자적으로 (atomically) 실행되지 않는데 다른 프로세스가 본래 어떤 두 명령 사이에서 이를 인터럽트할 수도 있다. 보안적인 프로그램의 프로세스가 이러한 인터럽션에 대비하지 않았다면 다른 프로세스가 이 프로세스를 방해할 수도 있다. 다른 프로세스의 임의의 코드가 두 연산 사이에 실행된다 하더라도 모든 연산쌍이 실패해서는 안된다.

경쟁 상태 문제는 개념적으로 다음의 두 범주로 분류할 수 있다:

6.8.1. 시퀀싱 (비원자적) 문제

일반적으로 모든 연산쌍에 대해 임의의 코드가 이들 사이에서 실행될 때 코드가 실패할 수 있는지 검사해야 한다.

공유 변수 적재 및 저장은 보통 별개의 연산으로 구현되며 원자적이 아님을 주목해라. 이는 ``증가 변수 (increment variable)" 연산이 보통 적재, 증가 그리고 저장 연산으로 변환됨을 의미하며 따라서 변수 메모리가 공유된다면 다른 프로세스가 증가하기를 간섭할 수도 있다.

보안적인 프로그램이 요청이 허가되었는지를 결정해야 하며 그렇다면 그 요청에 따라 작동해야 한다. 신뢰되지 않은 사용자가 프로그램이 요청에 따라 작동하기 전에 허가권 결정에 사용되는 어떤 것도 변경시킬 수 있는 방법은 없어야 한다. 이러한 유형의 경쟁 상태는 때때로 "time of check - time of use" (TOCTOU) 경쟁 상태로 지칭된다.

6.8.1.1. 파일시스템에서의 원자적 동작

원자적 동작을 수행할 수 없다는 문제는 파일시스템에서도 되풀이하여 나온다. 일반적으로 파일시스템은 많은 프로그램들이 사용하는 공유 자원으로 어떤 프로그램들은 다른 프로그램들이 이를 사용하는 것을 간섭할 수도 있다. 보안적인 프로그램들은 허가된 요청인지를 결정하기 위해 access(2) 를 사용하는 것을 피해야 한다. 다음 호출은 open(2) 로 사용자들이 아마도 심볼릭 링크 또는 자신들이 선택한 파일을 생성함으로써 access(2) 와 open(2) 호출 사이에 파일을 자주 옮길 수도 있기 때문이다. 보안적인 프로그램은 대신 자신의 유효 id 또는 파일시스템 id 를 설정한 후 직접적으로 open 함수를 호출해야 한다. access(2) 를 안전하게 사용하는 것은 가능한데 단지 사용자가 파일 또는 파일시스템 루트로부터 경로에 존재하는 모든 디렉토리에 영향을 미칠 수 없을 때만 가능하다.

파일을 생성할 때는 O_CREAT | O_EXCL 모드를 사용해서 열고 단지 매우 제한된 허가 (단지 현재 사용자에게만) 를 주어야 한다; 또한 open 실패에 대비할 필요가 있다. 파일을 오픈할 필요가 있다면 (예, 서비스 부인을 예방하기 위해) 반복적으로 ``임의의" 파일 이름을 생성해, 이 이름의 파일을 오픈하고 오픈 성공시 이러한 반복을 중지할 필요가 있다.

일반 프로그램들은 파일을 적절히 생성하지 않는 경우 보안 약점을 가질 수 있다. 예를 들어 ``joe" 텍스트 편집기는 "DEADJOE" 심볼릭 링크 취약성이라고 불리는 약점을 갖고 있다. 시스템 크래쉬, xterm 닫기 또는 네트워크 연결 끊김과 같이 비표준 방식으로 joe 가 종료될 때 joe 는 오픈 버퍼를 "DEADJOE" 파일에 무제한으로 덧붙일 수 있다. 이는 루트가 일반적으로 joe 를 사용할 수 있는 디렉토리에 DEADJOE 심볼릭 링크를 생성함으로써 악용될 수 있다. 이러한 방법으로 joe 는 가비지를 잠재적으로 문제가 될 수 있는 파일에 덧붙이는데 사용될 수 있으며 이는 서비스 부인 및/또는 본의아닌 접근을 야기할 것이다.

다른 예로서 파일의 메타 정보에 일련의 연산 (소유자 변경, 파일 stat-ing 또는 허가권 비트 변경과 같은) 을 수행할 때 우선 파일을 오픈한 후 이에 연산을 적용해라. 이는 chown(), chgrp() 와 chmod() 와 같이 파일 이름을 취하는 함수들 대신 fchown(), fstat() 또는 fchmod() 시스템 호출을 사용한다는 것을 의미하며 프로그램을 작동시키는 도중에 파일이 대체되는 것을 막을 수 있을 것이다 (가능한 경쟁 상태). 예를 들어 파일을 닫은 후 chmod() 를 사용해서 허가권을 변경한다면 공격자가 이 두 단계 사이에 파일을 이동 및 제거해서 다른 파일 (/etc/passwd 과 같은) 에 대한 심볼릭 링크를 생성할 수도 있다. 다른 재미있는 파일은 /dev/zero 로 이는 프로그램에 무한히 긴 입력 데이타 스트림을 제공할 수 있다; 공격자가 파일의 중간 스트림을 ``교환" 할 수 있다면 결과는 위험할 수 있다.

그러나 이는 복잡해진다 - 파일들을 생성할 때 이들에게 가능한 최소한의 권한셋을 주어야 하며 그 후 원할 때 권한을 더욱 확대 변경해야 한다. 일반적으로 이는 단지 사용자와 사용자 그룹에 대해 초기 접근을 제한하기 위해 umask 및/또는 open 매개변수를 사용해야 함을 의미한다. 예를 들어 초기에 모두 다 읽을 수 있는 파일을 생성한 후 ``world-readable" 비트를 없애려고 하면 허가권 비트가 무방하다고 말한 동안에 공격자는 파일을 오픈하려고 할 수 있다. 대부분의 유닉스 계열 시스템에서 허가권은 오픈시에만 검사되는데 따라서 공격자가 의도한 것보다 더욱 많은 권한을 갖게 될 것이다.

일반적으로 다수의 사용자가 유닉스 계열 시스템의 디렉토리에 쓰기를 할 수 있으려면 그 디렉토리에 ``sticky" 비트를 설정하는 것이 더욱 좋을 것이다. 이러한 sticky 디렉토리는 더욱 잘 구현되어왔다. 그러나 문제를 완전히 피하는 것이 더욱 좋은데 단지 신뢰된 특별 프로세스만이 접근할 수 있는 디렉토리를 만들어라 (그 후 이를 주의깊게 구현해라). 전통적인 유닉스의 일시적인 디렉토리 (/tmp 와 /var/tmp) 는 보통 ``sticky" 디렉토리로 구현되지만 다음에 보듯이 모든 유형의 모든 문제가 역시 표면화할 것이다.

6.8.1.2. 임시 파일

원자적 연산을 정확히 수행하는 문제는 임시 파일들을 생성할 때 특히 나타난다. 유닉스 계열 시스템에서 임시 파일들은 모든 사용자들이 공유하는 /tmp 또는 /var/tmp 디렉토리에 일반적으로 생성된다. 공격자들이 공통적으로 사용하는 수법은 보안적인 프로그램이 작동하는 동안 임시 디렉토리내에 어떤 다른 파일 (예, /etc/passwd) 에 대한 심볼릭 링크를 생성하는 것이다. 공격자의 목적은 보안적인 프로그램이 주어진 파일 이름이 존재하지 않는다고 결정할 상황을 만드는 것으로 공격자가 다른 파일에 대한 심볼릭 링크를 만든 후에 보안적인 프로그램이 어떤 연산을 수행 (그러나 실제로는 의도하지 않은 파일을 오픈했다) 하게 된다. 때때로 중요한 파일들은 이런식으로 타격을 받거나 수정될 수 있다. 일반 파일 생성과 같이 이 공격의 많은 변형이 있는데 이들은 모두 공격자가 임시 파일들에 대해 보안적인 프로그램이 사용하는 동일 디렉토리에 파일시스템 객체를 생성 (또는 접근) 할 수 있다는 개념에 기초한다.

이러한 공유 디렉토리에 파일들을 생성할 때 일반적인 문제는 사용하려고 하는 파일 이름이 생성시 이미 존재하지 않음을 보장해야 한다는 것이다. 파일 생성 전에 이를 검사하는 것은 효과적이지 않은데 이는 검사 후에 그러나 생성 전에 다른 프로세스가 그 파일 이름을 갖는 파일을 생성할 수 있기 때문이다. ``예측할 수 없는" 또는 ``유일한" 파일 이름 사용도 다른 프로세스가 성공할 때까지 반복적으로 이름을 추측할 수 있기 때문에 일반적으로 효과적이지 않다.

기본적으로 공유 (sticky) 디렉토리에 임시 파일을 생성하기 위해서는 (1) ``임의의" 파일 이름 생성, (2) O_CREAT | O_EXCL 및 매우 제한된 허가권을 사용한 오픈 및 (3) 오픈 성공시 중지 이들을 반복적으로 해야한다.

1997 년 ``Single Unix Specification" 에 따르면 임의의 임시 파일을 생성할 때 선호되는 방법은 tmpfile(3) 이다. tmpfile(3) 함수는 임시 파일을 생성하여 해당 스트림을 오픈하며 그 스트림을 반환한다 (반환하지 않는다면 NULL). 불행히도 스펙은 파일이 보안적으로 생성될 것인지에 대해 아무런 보장도 하지 않고 있는데 저자 자신도 모든 구현이 이를 안전하게 하는지 보증할 수 없기 때문에 염려하고 있다고 이 책의 초기 버전에서 말했다. 그 후 예전 System V 시스템이 tmpfile(3) (와 tmpnam(3) 및tempnam(3)) 를 비보안적으로 구현했음을 발견했다. tmpfile(3)의 라이브러리 구현은 임시 파일을 보안적으로 생성해야 하지만 사용자가 시스템 라이브러리가 이러한 보안 결점을 갖고 있다고 늘 인식하지는 못하며 때때로 사용자가 할 수 있는 것은 아무 것도 없다.

Kris Kennaway 는 일반적으로 임시 파일들을 만들때 mkstemp(3) 를 사용하라고 추천한다. 그의 논리적 근거는 이 태스크를 수행하기 위해서는 각자의 함수를 돌리는 대신 잘 알려진 라이브러리 함수를 사용해야 하며 이 함수가 잘 알려진 의미 체계를 갖고 있다는 것이다. 이는 확실히 근거있는 견해이다. 저자는 mkstemp(3) 를 사용하려면 단지 소유자에게만 임시 파일 허가권을 제한하기 위해 반드시 umask(2) 를 사용하라고 덧붙인다. 이는 mkstemp(3) 의 몇몇 구현 (기본적으로 예전 구현) 이 임시 파일을 all readable 및 writable 하게 만들어 공격자가 디렉토리내에 비밀 자료를 읽거나 쓸 수 있는 상태를 만들 수 있기 때문이다. 약간 귀찮은 것은 mkstemp(3) 가 환경 변수 TMP 또는 TMPDIR 를 직접적으로 지원하지 않는다는 것인데 따라서 환경 변수를 지원하려면 코드를 추가해야 한다. 다음은 이러한 목적 및 TMP 와 TMPDIR 에 대한 지원을 추가할 때 mkstemp(3) 를 사용하는 방법을 설명하는 C 프로그램이다.

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>

void failure(msg) {
 fprintf(stderr, "%s\n", msg);
 exit(1);
}

/*
 * 임시 파일이름에 대한 "패턴"이 주어지면
 * (디렉토리 위치로 시작해 XXXXXX 로 끝나는)
 * 파일을 생성해 이를 반환해라.
 * 이 루틴은 파일을 언링크시키며 따라서 보통 디렉토리 리스팅시
 * 이 파일은 보이지 않을 것이다.
 * 패턴은 최종 파일이름을 보이도록 변경될 것이다.
 */

FILE *create_tempfile(char *temp_filename_pattern)
{
 int temp_fd;
 mode_t old_mode;
 FILE *temp_file;

 old_mode = umask(077);  /* 제한적인 허가권을 갖는 파일을 생성 */
 temp_fd = mkstemp(temp_filename_pattern);
 (void) umask(old_mode);
 if (temp_fd == -1) {
   failure("Couldn't open temporary file");
 }
 if (!(temp_file = fdopen(temp_fd, "w+b"))) {
   failure("Couldn't create temporary file's file descriptor");
 }
 if (unlink(temp_filename_pattern) == -1) {
   failure("Couldn't unlink temporary file");
 }
 return temp_file;
}


/*
 * "태그" (XXXXXX 로 끝나는 상대적 파일 이름) 가 주어지면
 * 태그를 사용해 임시 파일을 생성한다. 환경 변수 TMP 또는
 * TMPDIR 에 정의되어 있고 setuid/setgid 가 아니라면 이 변수에
 * 지정된 디렉토리에 파일이 생성될 것이다.
 * 그렇지 않은 경우는 /tmp 디렉토리에 생성될 것이다.
 * 루트 (및 su 된 루트) 는 TMPDIR or TMP (이 정의되어 있다면)
 *  를 사용할 것임을 주목해라.
 */
FILE *smart_create_tempfile(char *tag)
{
 char *tmpdir = NULL;
 char *pattern;
 FILE *result;

 if ((getuid()==geteuid()) && (getgid()==getegid())) {
   if (! ((tmpdir=getenv("TMPDIR")))) {
     tmpdir=getenv("TMP");
   }
 }
 if (!tmpdir) {tmpdir = "/tmp";}

 pattern = malloc(strlen(tmpdir)+strlen(tag)+2);
 if (!pattern) {
   failure("Could not malloc tempfile pattern");
 }
 strcpy(pattern, tmpdir);
 strcat(pattern, "/");
 strcat(pattern, tag);
 result = create_tempfile(pattern);
 free(pattern);
 return result;
}



main() {
 int c;
 FILE *demo_temp_file1;
 FILE *demo_temp_file2;
 char demo_temp_filename1[] = "/tmp/demoXXXXXX";
 char demo_temp_filename2[] = "second-demoXXXXXX";

 demo_temp_file1 = create_tempfile(demo_temp_filename1);
 demo_temp_file2 = smart_create_tempfile(demo_temp_filename2);
 fprintf(demo_temp_file2, "This is a test.\n");
 printf("Printing temporary file contents:\n");
 rewind(demo_temp_file2);
 while (  (c=fgetc(demo_temp_file2)) != EOF) {
   putchar(c);
 }
 putchar('\n');
 printf("Exiting; you'll notice that there are no temporary files on exit.\n");
}

Kennaway 는 mkstemp(3) 를 사용할 수 없다면 mkdtemp(3) 를 사용하여 외부로부터 보호되는 디렉토리를 각자 만들라고 언급하고 있다. 마지막으로 실제 비보안적인 mktemp(3) 를 사용해야 한다면 많은 X 를 사용해라 - 그는 파일 이름이 쉽게 추측될 수 없도록 10 (libc 가 이를 허용한다면) 을 제안하고 있다 (단지 6 개의 X 를 사용한다는 것은 5 개를 PID 가 차지하여 단지 한개의 임의 문자를 남기고 공겨자로 하여금 손쉽게 경쟁 상태를 허용함을 의미한다). 저자는 tmpname(3) 사용을 피해야 한다고 부언한다 - 이들 사용의 일부는 쓰레드가 존재할 때 신뢰할 수 없으며 TMP_MAX 사용 (가장 실용적으로 사용하는 것은 루프 내에서이다) 후에 적절히 작동할 것이라고 보장하지 않는다.

일반적으로 mktemp(3) 또는 tmpnam(3) 과 같은 함수의 비보안성에 대처할 특별한 조치를 취하지 않았거나 보안적인 라이브러리 구현을 설치 작업의 일부분으로 테스트하지 않았다면 이들의 사용을 피해야 한다. 그럼에도 /tmp 또는 world-writable 디렉토리 (또는 그룹을 신뢰하지 않는 경우 group-writable) 내에 파일을 만들려고 하면서 mk*temp() 를 사용하지 않으려고 한다면 (예, 파일 이름이 예측될 수 있게 하려면) 늘 open() 에 O_CREAT 와 O_EXCL 플래그를 사용하고 반환값을 검사해라. open() 호출을 할 수 없다면 원래대로 복귀해라 (예, exit).

GNOME 프로그래밍 지침은 공유 (임시) 디렉토리에 보안 오픈 임시 파일들에 대한 파일시스템 객체를 생성할 때 다음 코드를 추천한다[Quintero 2000]:

 char *filename;
 int fd;

 do {
   filename = tempnam (NULL, "foo");
   fd = open (filename, O_CREAT | O_EXCL | O_TRUNC | O_RDWR, 0600);
   free (filename);
 } while (fd == -1);

비보안적인 tempnam(3) 함수가 사용되고 있음에도 불구하고 보안 약점에 대처하기 위해 O_CREAT 와 O_EXCL 을 사용하여 루프내에서 wrapped 됨을 주목해라. 파일 이름을 free() 할 필요가 있음을 주목해라. 작업을 마친 후 파일을 close() 및 unlink() 해야 한다. 표준 C I/O 라이브러리를 사용하길 원한다면 파일 기술자를 FILE * 로 변환시키기 위해 "w+b" 모드로 fdopen() 을 사용할 수 있다. 이 접근 방법은 예전 NFS 가 정확히 O_EXCL 을 지원하지 않기 때문에 NFS 버전 2 에서는 작동되지 않음을 주목해라. 한가지 중요하지 않은 단점은 tempnam 이 비보안적으로 사용될 수 있기 때문에 다양한 컴파일러와 보안 스캐너가 이 사용에 대해 가짜 경고를 줄 수 있음을 주목해라. 이러한 문제는 mkstemp(3) 에는 없다.

쉘 스크립트에서 임시 파일이 필요하다면 파이프, 지역 디렉토리 (예, 사용자 홈디렉토리 내부의 어떤 디렉토리) 또는 어떤 경우에는 현재 디렉토리를 사용하는 것이 아마 가장 좋다. 이런 방식에서는 사용자가 허용하지 않는다면 어떠한 공유하는 것도 없다. 정말로 /tmp 와 같은 공유 디렉토리내에 임시 파일을 원하거나/필요하다면 템플릿에 프로세스 id 를 사용해서 단지 ">" 같은 일반 연산자를 사용하여 파일을 생성하는 전통적인 쉘 기법을 사용하지 마라. 쉘 스크립트가 PID 를 지정하기 위해 "$$" 를 사용할 수 있지만 PID 는 동일한 이름을 갖는 파일 또는 링크를 미리 생성할 수 있는 공격자에 의해 쉽게 결정 또는 추측될 수 있다. 따라서 다음의 "일반적인" 쉘 스크립트는 안전하지 않다:

   echo "This is a test" > /tmp/test$$  # DON'T DO THIS.

쉘 스크립트에 임시 파일 또는 디렉토리가 필요가고 이를 /tmp 밑에 놓길 원한다면 쉘 스크립트에서 사용할 목적으로 만들어진 mktemp(1) 가 아마도 해결책이다. mktemp(1) 과 mktemp(3) 은 다른 함수임을 주목해라 - 안전한 함수는 mktemp(1) 이다. 솔직히 저자는 공유 디렉토리에 임시 파일을 생성하는 쉘 스크립트에 흥미를 느끼지 않는다; 기밀 디렉토리에 이러한 파일을 생성하거나 대신 파이프를 사용하는 것이 일반적으로 바람직하다. 그러나 정말로 이렇게 하고 싶다면 다음을 사용해라; mktemp(1) 은 템플릿을 취한 후 O_EXCL 을 사용해서 파일 또는 디렉토리를 생성하고 그 이름을 반환한다; 이는 O_EXCL 을 사용하기 때문에 디렉토리가 NFS 버전 2 를 사용하지 않는다면 /tmp 같은 공유 디렉토리에 대해 안전한다. 다음 Bourne 쉘 스크립트에서 mktemp(1) 을 정확히 사용한 예이다; 이 예는 mktemp(1) 맨 페이지에서 얻을 수 있다:

 # mktemp(1) 의 간단한 사용예로 스크립트가 안전한 임시 파일을
 # 얻을 수 없다면 종료하는 스크립트이다.

   TMPFILE=`mktemp /tmp/$0.XXXXXX` || exit 1
   echo "program output" >> $TMPFILE

  # 에러를 잡으려고 하는 경우의 간단한 예:

   TMPFILE=`mktemp -q /tmp/$0.XXXXXX`
   if [ $? -ne 0 ]; then
      echo "$0: Can't create temp file, exiting..."
      exit 1
   fi

처음에 보안적인 임시 파일 이름을 얻었다고 하더라도 이 이름을 재사용하지 마라 (제거한 후 다시 생성해라). 공격자가 두번째로 이를 재생성하기 전에 원래 파일 이름을 보고 있다가 이를 낚아챌 수 있다. 물론 늘 적절한 파일 허가권을 사용해라. 예를 들어 파일에 world 또는 group 접근이 필요하다면 world/group 접근만 허용해라 그렇지 않다면 0600 모드로 유지해라 (즉, 소유자만이 읽고 쓸 수 있다).

디렉토리 엔트리는 없어지지만 이를 가리키고 있는 마지막 파일 기술자가 닫힐 때까지 파일 자체는 접근할 수 있도록 exit 핸들러를 사용하거나 유닉스 파일시스템 의미 체계 사용 및 생성 후 즉각적으로 파일을 unlink() 함으로써 스스로 정리해라. 파일 기술자를 건네줌으로써 프로그램내에서 이를 계속해서 접근할 수 있는데 파일을 unlinking 하는 것은 코드 유지 보수에 많은 장점이 있다: 프로그램의 크래시 여부에 상관없이 파일은 자동적으로 지워진다. 즉각적인 unlinking 과 관련된 한가지 사소한 문제는 관리자가 단순히 이름만으로 파일시스템을 조사할 수 없기 때문에 관리자가 얼마나 많은 디스크 공간이 사용되고 있는지 보는 것을 어렵게 한다는 것이다.

유닉스 계열 시스템의 코드는 TMP 또는 TMPDIR 환경변수 값들의 제공자가 신뢰된다면 이들을 침해하지 않음을 보증하는 것을 고려할 수 있다. 이렇게 함으로써 사용자가 그들의 임시 파일을 그들의 홈디렉토리내 하부디렉토리와 같은 비공유디렉토리로 이동하는 것을 가능하게 한다. 최근 Bastille 버전에서는 사용자들간의 공유를 제한하기 위해 이러한 변수들을 설정할 수 있다. 불행히 많은 사용자들은 TMP 또는 TMPDIR 을 /tmp 와 같은 공유 디렉토리로 설정하는데 따라서 이러한 환경 변수가 설정된다 하더라도 보안적인 프로그램이 정확히 임시 파일을 생성해야 한다. 이는 GNOME 접근 방법의 장점 중의 하나로 적어도 어떤 시스템에서 tempnam(3) 이 자동적으로 TMPDIR 을 사용하는 반면 mkstemp(3) 접근 방법은 이를 하기 위해 더욱 많은 코드를 필요로 하기 때문이다. 임시 디렉토리를 위해 더욱 많은 환경 변수들 (TEMP 와 같은) 을 생성하지 않기를 바라며 특히 각각의 애플리케이션에 대해 다른 환경 이름을 생성하지 않기를 바란다 (예, MYAPP_TEMP 를 사용하지 마라). 이렇게 하는 것은 시스템 관리를 굉장히 복잡하게 하는데 특정 애플리케이션을 위해 특별 임시 디렉토리를 원하는 사용자는 그 애플리케이션을 운용할 때에 한해 환경 변수를 설정할 수 있다. 물론 이러한 환경 변수가 신뢰되지 않은 소스에 의해 설정된다면 이들을 무시해야 한다 - 4.2.3절 의 충고를 따른다면 어떤 방식으로든 할 수 있다.

이러한 기법은 임시 디렉토리가 NFS 버전 2 (NFSv2) 를 사용해서 원격적으로 마운트된다면 작동하지 않는데 NFSv2 가 O_EXCL 을 적절히 지원하지 않기 때문이다. 더욱 자세한 정보는 6.8.2.1절 을 보라. NFS 버전 3 이상은 O_EXCL 을 적절히 지원한다; 간단한 해결 방법은 임시 디렉토리가 로컬이거나 NFS 를 사용해 마운트된 경우 NFS 버전 3 이상을 사용해서 마운트되는 지를 보증하는 것이다. NFS v2 에서 link(2) 와 stat(2) 를 사용함으로써 안전하게 임시 파일을 생성하는 기법이 있지만 복잡하다; 이에 대해 더욱 자세한 정보는 6.8.2.1절 을 보라.

여담으로 FreeBSD 가 파일이름의 PID 컴포넌트 제거 및 전체를 base-62 인코드 randomness 로 대체하기 위해 mk*tem() 계열 함수를 최근에 변경했음을 언급하는 것은 가치가 있다. 이는 과감하게 6 X 의 디폴트 사용법에 대해 가능한 임시 파일의 수를 증가시키며 매우 빈번히 사용하는 경우를 제외하고는 6 X 를 갖는 mktemp(3) 가 추측에 합리적으로 (통계적으로) 보안적임을 의미한다. 그러나 이 문서의 지침을 따른다면 문제를 제거할 수 있다.

임시 파일에 대한 이 정보의 많은 부분은 Kris Kennaway's posting to Bugtraq about temporary files on December 15, 2000 에서 유래한다.

6.8.2. 로킹

프로그램이 파일, 디바이스 및/또는 특별한 서버 프로세스에 대해 유일한 권한을 갖음을 보장해야하는 상황이 종종 있다. 자원을 잠그는 모든 시스템은 프로그램이 로크 (lock) 를 정리하지 못한다면 로크, 더 자세히 말하면 교착 상태 (deadlocks("deadly embraces")), livelocks 와 releasing "stuck" 로크들의 표준적인 문제를 다뤄야 한다. 교착 상태는 프로그램들이 자원들을 해제하기 위해 서로 기다리면서 교착되어 있다면 일어날 수 있다. 예를 들어 교착 상태는 프로세스 1 이 자원 A 를 로크하며 자원 B 를 기다리는 반면 프로세스 2 는 자원 B 를 로크하며 자원 A 를 기다리고 있다면 발생할 수 있다. 많은 교착 상태는 다중 자원을 로크하는 모든 프로세스들에게 그들을 동일한 순서 (예, 알파벳 이름순으로) 로 로크하도록 요구함으로써 예방될 수 있다.

6.8.2.1. 로크로서 파일 사용

유닉스 계열 시스템에서 자원 로킹 (잠금, locking) 은 전통적으로 로크를 지정하는 파일을 생성함으로써 전통적으로 이루어졌는데 이러한 방법이 매우 이식성이 높기 때문이다. 이는 관리자가 어떤 로크가 설정되었는지 보기 위해 단지 파일시스템만 검사하면 되기 때문에 교착된 로크를 정리하는 것을 쉽게 한다. 교착된 로크는 프로그램이 실행 후 정리하는데 실패하거나 (예, 크래시 또는 오기능 (malfunctioned)) 전체 시스템이 정지했기 때문에 발생할 수 있다. 이들이 권고 (advisory) 로크 (강제 (mandatory) 로크가 아닌) 임을 주목해라 - 모든 프로세스는 자원이 이러한 로크를 사용하기 위해 협력해야 함을 필요로한다.

그러나 피할 수 있는 몇몇 속임수가 있다. 첫번째 creat() 함수 호출 또는 파일 모드를 0 으로 설정하고 (허가권 없음) O_WRONLY | O_CREAT | O_TRUNC open() 모드인 open() 등가 함수를 호출하는 유닉스 C 프로그램의 매우 오래된 기법을 사용하지 마라. 일반적인 파일시스템에서 일반 사용자에게 이는 작동하지만 사용자가 루트 권한을 가질 때 파일을 로크할 수 없다. 루트는 파일이 이미 존재할 때라도 늘 이 연산을 수행할 수 있다. 사실 오래된 유닉스 버전들은 오래된 에디터 "ed" 에 이러한 특별한 문제를 갖고 있다 -- 증상은 때때로 패스워드 파일의 일부분이 사용자 파일내에 놓일 수 있는 것이다 [Rochkind 1985, 22]. 대신 지역적 파일시스템에서 프로세스에 대한 로크를 생성하려면 O_WRONLY | O_CREAT | O_EXCL 플래그를 갖고 open() 을 사용할 수 있다 (동일한 소유자의 다른 프로세스들이 로크를 할 수 없도록 허가권은 없다). 독점적인 파일을 생성하는 공식적인 방식인 O_EXCL 의 사용을 주목해라; 이는 로컬 파일시스템에서 루트에 대해서도 작용한다 [Rochkind 1985, 27].

두번째 로크 파일이 NFS 로 마운트된 파일시스템에 있다면 NFS 버전 2 가 일반 파일 의미 체계를 완벽히 지원하지 않는다는 문제를 갖는다. 이는 클라이언트에 로컬이라고 간주되는 작업에 대해서도 문제가 되는데 어떤 클라이언트가 로컬 디스크를 갖지 못하고 모든 파일이 NFS 를 통해 원격적으로 마운트될 수도 있기 때문이다. open(2) 메뉴얼은 이런 경우 처리 방법을 설명한다 (루트 프로그램의 경우도 또한 다룬다):

""로킹 태스크를 수행하기 위해 [open(2) 의 O_CREAT 와 O_EXCL 플래그] 에 의존하는 프로그램들은 경쟁 상태를 포함할 것이다. 로크파일을 사용해서 원자적 파일 로킹을 수행하기 위한 해결 방안은 동일 파일시스템에서 유일한 파일을 생성하고 (예, 호스트네임과 pid 를 병합), 로크파일에 대한 링크를 만들기 위해 link(2) 를 사용한 후 그 링크 카운트가 2 로 증가되었는지 검사하기 위해 유일한 파일에 stat(2) 를 사용하는 것이다. link(2) 호출의 반환값을 사용하지 마라" "

명백히 이 해결방안은 로킹을 하는 모든 프로그램이 협동하고 모든 협동하지 않는 프로그램들의 간섭이 허용되지 않는 경우에만 작용한다. 특히 파일 로킹을 위해 사용하고 있는 디렉토리는 파일 생성 및 제거를 위한 허용된 파일 허가권을 가져서는 안된다.

NFS 버전 3 은 open(2) 에 O_EXCL 모드에 대한 지원이 추가하였다; IEFT RFC 1813 을 보라 특히 "CREATE" 의 "mode" 인수에 대한 "EXCLUSIVE" 값을 보라. 슬프게도 모든 사람이 이 문서를 작성하는 시점에 NFS 버전 3 이상으로 전환하지 않았으며 따라서 이식성있는 프로그램에서는 아직까지 이에 의존할 수 없다. 결국은 이 문제는 없어질 것이다.

디바이스 또는 로컬 머신에서 프로세스의 존재를 로킹하려면 표준적인 합의를 사용하도록 해라. 저자는 FHS 를 사용하기를 추천한다; 이는 리눅스 시스템에 의해 널리 참조되고 있으며 또한 다른 유닉스 계열 시스템의 개념을 병합하려고 하고 있다. FHS 는 파일들에 대한 파일 네이밍, 위치 및 표준 컨텐츠를 포함해서 파일 로킹 등에 대해 표준적인 합의를 기술한다 [FHS 1997]. 서버가 주어진 머신에서 한번 이상 실행되지 않았음을 확인하려 한다면 보통 컨텐츠와 같이 pid 를 갖는 /var/run/NAME.pid 프로세스 식별자를 생성해야 한다. 동일 맥락으로 디바이스 로크 파일 등을 위해 로크 파일을 /var/lock 밑에 놓아야 한다. 이 접근 방법은 프로그램이 갑자기 정지하는 경우 파일들이 쓸데없이 시간을 보내게 하는 단점이 있지만 이는 표준적인 합의로 다른 시스템 도구에 의해 쉽게 다뤄진다.

로크를 나타내기 위해 파일 사용에 협동하고 있는 프로그램들은 동일한 디렉토리 이름만이 아니라 동일한 디렉토리를 사용하는 것이 중요하다. 이는 네트워크화된 시스템에 관련된 문제이다. FHS 는 명시적으로 /var/run 과 /var/lock 는 공유할 수 없는 반면 /var/mail 은 공유할 수 있다고 언급한다. 따라서 하나의 머신에서 로크가 작동하길 원하지만 다른 머신을 간섭하지 않길 원한다면 /var/run 과 같은 공유할 수 없는 디렉토리를 사용해라 (예, 각 머신에게 자신의 서버를 구동하도록 허용하길 원하는 경우). 그러나 네트워크에서 파일을 공유하는 모든 머신들이 로크를 따르길 원한다면 공유하고 있는 디렉토리를 사용할 필요가 있다; /var/mail 이 이러한 위치이다. 이 주제에 대해 더욱 자세한 정보를 얻기 위해서는 FHS 절을 보라.

6.8.2.2. 로킹에 대한 다른 접근 방법

물론 로크를 나타내기 위해 파일을 사용할 필요는 없다. 네트워크 서버들은 대개 신경쓸 필요가 없다: 단지 포트에 바인딩한다는 것만이 일종의 로크로서 작용하는데 주어진 포트에 바운드된 기존 서버가 있다면 어떠한 다른 서버도 이 포트에 바인딩 할 수 없기 때문인다.

로킹에 대한 다른 접근 방법은 "discretionary lock" 로서 fcntl(2) 를 통해 구현된 POSIX 레코드 로크를 사용하는 것이다. 이는 임의로, 즉 이를 사용하는 것은 로크를 필요로 하는 프로그램들의 협동을 필요로 한다 ( 로크를 나타내기 위해 파일을 사용하는 접근방법과 마찬가지로). POSIX 레코드 로크를 추천하는 많은 이유가 있다: POSIX 레코드 로킹은 거의 모든 유닉스 계열 플랫폼 (POSIX 1 에 의해 강제적으로 요구되는) 에서 지원되며, 전체 파일만이 아닌 파일의 일부분을 로크할 수 있으며 또한 읽기와 쓰기 로크간의 차이를 다룰 수 있다. 더욱 유용한 것은 프로세스가 소멸하면 이 프로세스의 로크도 자동적으로 제거된다는 것으로 이는 보통 원하는 사항이다.

System V 의 mandatory 로킹 스킴에 기초한 강제 로크도 또한 사용할 수 있다. 이는 단지 로크된 파일의 setgid 비트가 설정되어 있지만 그룹의 실행 비트가 설정되어 있지 않은 파일만에 적용된다. 또한 강제 파일 로크를 허용하기 위해 파일시스템을 마운트해야 한다. 이런 경우 모든 read(2) 와 write(2) 는 로킹을 위해 검사된다; 이는 권고 로크보다 더욱 철저하지만 더욱 느리다. 또한 강제 로크는 다른 유닉스 계열 시스템으로 널리 이식되지는 않는다 (리눅스와 System V 에 기초한 시스템에서는 사용할 수 있지만 다른 시스템에서는 필요한 것은 아니다). 루트 권한을 갖는 프로세스는 강제 로크에 의해 또한 정지될 수 있음을 주목해라. 이는 서비스 부인 공격의 기초가 될 수 있게끔 한다.