5.1. 모듈 작성 시의 보안 관련 사안

5.1.1. 충분한 자원

시스템 자원 부족 때문에 모듈이 올바로 실행되는 데 지장이 발생하지 않도록 주의를 기울여야 한다. 가령 모듈에서 작업을 수행하는 데 충분하게 파일을 열 수 없다면 우아하게 실패 처리를 하거나 추가 자원을 요청해야 한다. 구체적으로 setrlimit(2) 계열 명령으로 조작 가능한 양들을 고려할 필요가 있다.

5.1.2. 사용자 신원

일반적으로 모듈에서 서비스 요청 사용자의 신원을 확인하고 싶을 수 있다. 그런데 그 신원은 pam_get_user()가 반환하는 사용자 이름과는 다를 수도 있다. 사용자 이름은 어떤 신원으로 서비스를 제공받게 될지를 나타낼 뿐이다. 그게 꼭 서비스를 요청하는 사용자인 것은 아니다.

달리 말하면, 사용자 X가 setuid Y인 프로그램을 실행해서 Z의 권한을 부여받는다. 이런 서비스 요청의 확실한 예가 바로 su 프로그램이다. 사용자 joesu를 실행해서 사용자 jane이 된다고 하자. 이때 X=joe, Y=root, Z=jane이다. 모듈에서 이런 여러 사용자들을 혼동해서 부적절한 특권을 부여하지 않도록 하는 게 매우 중요하다.

다음은 사용자 신원을 다룰 때 따라야 할 관행이다.

  • X, 즉 서비스 요청을 호출하는 사용자의 신원. getuid(2) 함수가 반환하는 사용자 식별자다.

  • Y, 즉 요청받은 서비스를 인가하기 위해 응용에서 쓰는 특권 신원. geteuid(2) 함수가 반환하는 실효 사용자 식별자다.

  • Z, 즉 인가될 서비스에서 쓰일 사용자의 신원. pam_get_user()가 반환하는 사용자 이름이며 Linux-PAM 항목 PAM_USER에 저장되는 사용자 이름이기도 하다.

  • Linux-PAM에는 모듈에서 이용할 수도 있는 또 다른 사용자 신원이 있다. 바로 PAM_RUSER 항목이다. 일반적으로 네트워크 관련 모듈/응용에서 이 항목을 설정/조회해서 원격에서 서비스를 요청하고 있는 사용자의 신원을 확인한다.

모듈에서 동작 중인 프로세스의 uideuid를 변경하려는 경우에는 Linux-PAM 라이브러리로 제어를 반환하기 전에 꼭 원래 값으로 복원해 놓도록 해야 한다.

5.1.3. 대화 함수 사용

대화 함수 호출 전에 응용 응답을 반환할 포인터의 값을 모듈에서 초기화해 두는 게 좋다. 응용에서 포인터를 채우지 않을 수도 있고, 그러면 모듈에서 그걸 알아챌 수 있어야 하기 때문이다.

대화 함수 실패에 대한 준비가 되어 있어야 한다. 일반적으로 쓰는 오류 값은 PAM_CONV_ERR지만 PAM_SUCCESS 외의 모든 값을 실패를 나타내는 것으로 처리해야 한다.

5.1.4. 인증 토큰

인증 토큰이 아무렇게나 굴러다니지 않도록 하기 위해 PAM_AUTHTOKPAM_OLDAUTHTOK 항목을 응용에게 제공하지 않는다. 두 값 모두 <security/pam_modules.h>에 정의는 되어 있다. 겉으로 보기엔 보안적 근거가 있는 것 같지만 악의적으로 프로그램된 응용에서 언제든 프로세스의 전체 메모리에 접근할 수 있으므로 피상적인 효과가 있을 뿐이다. 일반적으로 모듈에서는 인증 토큰이 더 이상 필요치 않게 되면 즉시 내용을 다른 값으로 덮어쓰는 게 좋다. 특히 free() 전에 그래야 한다. Linux-PAM 라이브러리에서도 두 인증 토큰 항목을 (재)설정할 때 그렇게 해야 한다.

이를 너무 간단하게 생각하지 말자. 모듈에서 (자동) 함수 변수에, 또는 pam_[gs]et_data()로 인증 토큰을 저장하는 경우에 해당 메모리가 해제되기 전에 확실하게 덮어쓰기를 해야 한다. 뒤쪽 경우에는 연계된 cleanup() 함수에서 *data를 확실하게 덮어쓴 다음 free()해야 한다. 예:

/*
 * 패스워드 저장에 사용했던 메모리를 해제하는 예시 cleanup() 함수.
 */

int cleanup(pam_handle_t *pamh, void *data, int error_status)
{
    char *xx;

    if ((xx = data)) {
        while (*xx)
            *xx++ = '\0';
        free(data);
    }
    return PAM_SUCCESS;
}