NAME

rtld-audit - 동적 링커 감사 API

SYNOPSIS

#define _GNU_SOURCE             /* feature_test_macros(7) 참고 */
#include <link.h>

DESCRIPTION

GNU 동적 링커(런타임 링커)에서 감사 API를 제공하는데 이를 통해 다양한 동적 링크 이벤트 발생 시에 응용에서 알림을 받을 수 있다. 이 API는 솔라리스 런타임 링커에서 제공하는 감사 인터페이스와 매우 비슷하다. <link.h>를 포함시켜서 필요한 상수 및 원형들을 정의한다.

이 인터페이스를 사용하려는 프로그래머는 표준 이름의 함수들을 구현한 공유 라이브러리를 만들게 된다. 모든 함수를 구현해야 하는 건 아니다. 대부분의 경우 특정 이벤트 감사 유형에 대해 프로그래머가 관심이 없으면 대응하는 감사 함수 구현을 제공할 필요가 없다.

감사 인터페이스를 사용하려면 환경 변수 LD_AUDIT을 콤마로 구분된 공유 라이브러리 목록을 담도록 정의해야 한다. 그 라이브러리들 각각에서 감사 API(의 일부)를 구현할 수 있다. 감사 대상 이벤트가 발생하면 라이브러리 나열 순서 대로 각 라이브러리의 대응 함수가 호출된다.

la_version()

unsigned int la_version(unsigned int version);

감사 라이브러리에 꼭 정의돼 있어야 하는 유일한 함수이며 동적 링커와 감사 라이브러리 간의 첫인사를 수행한다. 동적 링커가 이 함수를 호출할 때 링커에서 지원하는 가장 높은 감사 인터페이스 버전을 version으로 전달한다.

보통은 그냥 상수 LAV_CURRENT를 반환하게 구현하는데, 그 상수는 감사 모듈을 빌드하는 데 사용한 <link.h>의 버전을 나타낸다. 동적 링커가 이 감사 인터페이스 버전을 지원하지 않으면 감사 모듈을 활성화하지 않게 된다. 함수에서 0을 반환하는 경우에도 동적 링커가 이 감사 모듈을 활성화하지 않는다.

오래된 동적 링커와의 하위 호환성을 위해서 감사 모듈에서 version 인자를 검사해서 LAV_CURRENT보다 앞선 버전을 반환할 수 있다. 물론 이전 감사 인터페이스 버전의 요구 사항에 맞게 구현 방식을 조정할 수 있어야 한다. la_version 함수에서 다른 검사 없이 version 값을 그대로 반환하지는 않는 게 좋다. 감사 모듈 빌드에 쓰인 <link.h> 정의들과 일치하지 않는 인터페이스에 대응하는 버전일 수 있기 때문이다.

la_objsearch()

char *la_objsearch(const char *name, uintptr_t *cookie,
                   unsigned int flag);

동적 링커에서 이 함수를 호출해서 공유 오브젝트 탐색을 하려 한다는 걸 감사 라이브러리에게 알린다. name 인자는 탐색할 파일명이나 경로명이다. cookie는 탐색을 유발한 공유 오브젝트를 식별할 수 있게 해 준다. flag는 다음 값들 중 하나로 설정돼 있다.

LA_SER_ORIG
name이 탐색하려는 원래 이름이다. 보통 ELF DT_NEEDED 항목에서 온 이름이거나 dlopen(3)의 filename 인자이다.
LA_SER_LIBPATH
LD_LIBRARY_PATH에 지정된 디렉터리를 이용해 name을 만들었다.
LA_SER_RUNPATH
ELF DT_RPATH 내지 DT_RUNPATH 목록에 지정된 디렉터리를 이용해 name을 만들었다.
LA_SER_CONFIG
ldconfig(8) 캐시(/etc/ld.so.cache)를 통해 name을 찾았다.
LA_SER_DEFAULT
기본 디렉터리들 중 하나를 탐색해서 name을 찾았다.
LA_SER_SECURE
name이 보안 오브젝트로 한정돼 있다. (리눅스에서는 쓰지 않음.)

함수 결과로 la_objsearch()는 동적 링커가 이후 처리에 사용해야 할 경로명을 반환한다. NULL을 반환하면 이 경로명을 이후 처리에서 무시한다. 감사 라이브러리에서 탐색 경로를 감시만 하려는 거라면 name을 반환하면 된다.

la_activity()

void la_activity(uintptr_t *cookie, unsigned int flag);

동적 링커가 이 함수를 호출해서 링크 맵 동작이 있어나고 있다는 걸 감사 라이브러리에게 알린다. cookie는 링크 맵의 머리에 있는 오브젝트를 식별할 수 있게 해 준다. 동적 링커가 이 함수를 호출할 때 flag를 다음 값들 중 하나로 설정한다.

LA_ACT_ADD
링크 맵에 새 오브젝트들을 추가하려 한다.
LA_ACT_DELETE
링크 맵에서 오브젝트들을 제거하려 한다.
LA_ACT_CONSISTENT
링크 맵 동작이 끝났다. 맵이 다시 모순 없는 상태이다.

la_objopen()

unsigned int la_objopen(struct link_map *map, Lmid_t lmid,
                        uintptr_t *cookie);

새 공유 오브젝트를 적재할 때 동적 링커가 이 함수를 호출한다. map 인자는 그 오브젝트를 기술하는 링크 맵 구조체의 포인터이다. lmid 인자는 다음 값들 중 하나이다.

LM_ID_BASE
링크 맵이 최초 네임스페이스에 속한다.
LM_ID_NEWLM
링크 맵이 dlmopen(3)을 통해 요청된 새 네임스페이스에 속한다.

cookie는 이 오브젝트의 식별자에 대한 포인터이다. 이후의 감사 라이브러리 함수 호출에 이 식별자가 제공되므로 이 오브젝트를 식별할 수 있다. 이 식별자는 오브젝트의 링크 맵을 가리키도록 초기화 돼 있지만 감사 라이브러리에서 오브젝트 식별에 쓰기 편한 다른 어떤 값으로 바꿀 수 있다.

반환 값으로 la_objopen()은 다음 상수들을 0개 이상 OR 해서 만든 비트 마스크를 반환한다. 그래서 감사 라이브러리에서 la_symbind*()로 감시할 오브젝트를 선택할 수 있다.

LA_FLG_BINDTO
이 오브젝트로의 심볼 바인딩을 감사한다.
LA_FLG_BINDFROM
이 오브젝트로부터의 심볼 바인딩을 감사한다.

la_objopen()의 반환 값 0은 이 오브젝트에 대해 어떤 심볼 바인딩도 감사하지 않을 것임을 나타낸다.

la_objclose()

unsinged int la_objclose(uintptr_t *cookie);

오브젝트에 마무리 코드가 있으면 실행한 후이면서 그 오브젝트를 내리기는 전에 동적 링커가 이 함수를 호출한다. cookie 인자는 앞선 la_objopen() 호출에서 얻은 식별자이다.

현재 구현에서는 la_objclose()가 반환하는 값을 무시한다.

la_preinit()

void la_preinit(uintptr_t *cookie);

모든 공유 오브젝트들을 적재한 후이면서 응용으로 제어를 넘기기는 전에 (즉 main() 호출 전에) 동적 링커가 이 함수를 호출한다. 참고로 이후 main()에서도 dlopen(3)으로 오브젝트를 동적으로 적재할 수 있다.

la_symbind*()

uintptr_t la_symbind32(Elf32_Sym *sym, unsigned int ndx,
                       uintptr_t *refcook, uintptr_t *defcook,
                       unsigned int *flags, const char *symname);
uintptr_t la_symbind64(Elf64_Sym *sym, unsigned int ndx,
                       uintptr_t *refcook, uintptr_t *defcook,
                       unsigned int *flags, const char *symname);

la_objopen()에서 감사 알림을 받게 표시했던 두 공유 오브젝트 간에 심볼 바인딩이 이뤄질 때 동적 링커가 이 함수들 중 하나를 호출한다. la_symbind32() 함수는 32비트 플랫폼에서 쓰는 것이고 la_symbind64() 함수는 64비트 플랫폼에서 쓰는 것이다.

sym 인자는 바인드 하려는 심볼에 대한 정보를 제공하는 구조체의 포인터이다. 구조체 정의는 <elf.h>에 있다. 이 구조체의 필드들 중에 st_value은 심볼이 바인드 되는 주소를 나타낸다.

ndx 인자는 바인드 대상 공유 오브젝트의 심볼 테이블에서 그 심볼의 인덱스이다.

refcook 인자는 심볼 참조를 하고 있는 공유 오브젝트를 나타낸다. LA_FLG_BINDFROM을 반환한 la_objopen() 함수에 제공된 것과 같은 식별자이다. defcook 인자는 참조되는 심볼을 정의하고 있는 공유 오브젝트를 나타낸다. LA_FLG_BINDTO를 반환한 la_objopen() 함수에 제공된 것과 같은 식별자이다.

symname 인자는 심볼 이름은 담은 문자열을 가리킨다.

flags 인자는 심볼에 대한 정보를 제공하면서 동시에 이 PLT(Procedure Linkage Table) 항목의 이후 감사 방식을 변경하는 데 쓸 수 있는 비트 마스크이다. 동적 링커가 이 인자에 다음 비트 값들을 제공할 수 있다.

LA_SYMB_DLSYM
dlsym(3) 호출로 인해 바인딩이 발생했다.
LA_SYMB_ALTVALUE
앞선 la_symbind*() 호출이 이 심볼에 대해 대체 값을 반환했다.

기본적으로 감사 라이브러리에서 la_pltenter() 및 la_pltexit() 함수(아래 참고)를 구현하고 있으면 la_symbind() 후에 심볼이 참조될 때마다 PLT 항목에 대해 그 함수들이 호출된다. *flags에 다음 플래그들을 OR 해서 그 기본 동작을 바꿀 수 있다.

LA_SYMB_NOPLTENTER
이 심볼에 la_pltenter()를 호출하지 말 것.
LA_SYMB_NOPLTEXIT
이 심볼에 la_pltexit()을 호출하지 말 것.

la_symbind32() 및 la_symbind64()의 반환 값은 함수 반환 후 제어가 넘어가야 할 주소이다. 감사 라이브러리에서 심볼 바인딩을 감시만 하려는 거라면 sym->st_value를 반환하면 된다. 라이브러리에서 제어를 다른 방향으로 바꾸고 싶다면 다른 값을 반환할 수 있다.

la_pltenter()

이 함수의 정확한 이름과 인자 타입은 하드웨어 플랫폼에 따라 다르다. (<link.h>에 절적한 정의가 있다.) 다음은 x86-32용 정의이다.

Elf32_Addr la_i86_gnu_pltenter(Elf32_Sym *sym, unsigned int ndx,
                 uintptr_t *refcook, uintptr_t *defcook,
                 La_i86_regs *regs, unsigned int *flags,
                 const char *symname, long *framesizep);

바인딩 알림을 받게 표시된 두 공유 오브젝트 간에서 PLT 항목이 호출되기 바로 전에 이 함수가 호출된다.

sym, ndx, refcook, defcook, symname은 la_symbind*()에서와 같다.

regs 인자는 이 PLT 항목 호출에 사용될 레지스터들의 값을 담은 구조체(<link.h>에 정의돼 있음)를 가리킨다.

flags 인자는 la_symbind*()에서처럼 이 PLT 항목에 대한 정보를 담고 있고 이후 감사 방식 변경에 이용할 수 있는 비트 마스크를 가리킨다.

framesizep 인자는 long int 버퍼를 가리키는데 이를 이용해 이 PLT 항목 호출에 사용하는 프레임 크기를 명확히 설정할 수 있다. 이 심볼에 대해 여러 la_pltenter()에서 다른 값들을 반환하면 가장 큰 반환 값을 쓴다. 이 버퍼가 명확하게 적절한 값으로 설정된 경우에만 la_pltexit() 함수가 호출된다.

la_pltenter()의 반환 값은 la_symbind*()에서와 같다.

la_pltexit()

이 함수의 정확한 이름과 인자 타입은 하드웨어 플랫폼에 따라 다르다. (<link.h>에 절적한 정의가 있다.) 다음은 x86-32용 정의이다.

unsigned int la_i86_gnu_pltexit(Elf32_Sym *sym, unsigned int ndx,
                 uintptr_t *refcook, uintptr_t *defcook,
                 const La_i86_regs *inregs, La_i86_retval *outregs,
                 const char *symname);

바인딩 알림을 받게 표시된 두 공유 오브젝트 간에서 PLT 항목이 반환할 때 이 함수가 호출된다. PLT 항목 호출자에게 제어가 돌아가기 바로 전에 함수가 호출된다.

sym, ndx, refcook, defcook, symname은 la_symbind*()에서와 같다.

inregs 인자는 이 PLT 항목 호출에 사용된 레지스터들의 값을 담은 구조체(<link.h>에 정의돼 있음)를 가리킨다. outregs 인자는 이 PLT 항목 호출의 반환 값을 담은 구조체(<link.h>에 정의돼 있음)를 가리킨다. 이 값들을 호출자가 변경할 수 있으며 그 변경 내용이 PLT 항목 호출자에게 보이게 된다.

현재 GNU 구현에서는 la_pltexit()의 반환 값을 무시한다.

CONFORMING TO

이 API는 비표준이되 솔라리스 Linker and Libraries Guide의 Runtime Linker Auditing Interface 장에 기술된 솔라리스 API와 매우 비슷하다.

NOTES

솔라리스 동적 링커 감사 API와의 다음 차이에 유의하라.

BUGS

glibc 버전 2.9까지는 LD_AUDIT에 감사 라이브러리를 여러 개 지정하면 런타임 크래시가 발생한다. glibc 2.10에서 고쳐졌다고 한다.

EXAMPLES

#include <link.h>
#include <stdio.h>

unsigned int
la_version(unsigned int version)
{
    printf("la_version(): version = %d; LAV_CURRENT = %u\n",
            version, LAV_CURRENT);

    return LAV_CURRENT;
}

char *
la_objsearch(const char *name, uintptr_t *cookie, unsigned int flag)
{
    printf("la_objsearch(): name = %s; cookie = %p", name, cookie);
    printf("; flag = %s\n",
            (flag == LA_SER_ORIG) ?    "LA_SER_ORIG" :
            (flag == LA_SER_LIBPATH) ? "LA_SER_LIBPATH" :
            (flag == LA_SER_RUNPATH) ? "LA_SER_RUNPATH" :
            (flag == LA_SER_DEFAULT) ? "LA_SER_DEFAULT" :
            (flag == LA_SER_CONFIG) ?  "LA_SER_CONFIG" :
            (flag == LA_SER_SECURE) ?  "LA_SER_SECURE" :
            "???");

    return name;
}

void
la_activity (uintptr_t *cookie, unsigned int flag)
{
    printf("la_activity(): cookie = %p; flag = %s\n", cookie,
            (flag == LA_ACT_CONSISTENT) ? "LA_ACT_CONSISTENT" :
            (flag == LA_ACT_ADD) ?        "LA_ACT_ADD" :
            (flag == LA_ACT_DELETE) ?     "LA_ACT_DELETE" :
            "???");
}

unsigned int
la_objopen(struct link_map *map, Lmid_t lmid, uintptr_t *cookie)
{
    printf("la_objopen(): loading \"%s\"; lmid = %s; cookie=%p\n",
            map->l_name,
            (lmid == LM_ID_BASE) ?  "LM_ID_BASE" :
            (lmid == LM_ID_NEWLM) ? "LM_ID_NEWLM" :
            "???",
            cookie);

    return LA_FLG_BINDTO | LA_FLG_BINDFROM;
}

unsigned int
la_objclose (uintptr_t *cookie)
{
    printf("la_objclose(): %p\n", cookie);

    return 0;
}

void
la_preinit(uintptr_t *cookie)
{
    printf("la_preinit(): %p\n", cookie);
}

uintptr_t
la_symbind32(Elf32_Sym *sym, unsigned int ndx, uintptr_t *refcook,
        uintptr_t *defcook, unsigned int *flags, const char *symname)
{
    printf("la_symbind32(): symname = %s; sym->st_value = %p\n",
            symname, sym->st_value);
    printf("        ndx = %u; flags = %#x", ndx, *flags);
    printf("; refcook = %p; defcook = %p\n", refcook, defcook);

    return sym->st_value;
}

uintptr_t
la_symbind64(Elf64_Sym *sym, unsigned int ndx, uintptr_t *refcook,
        uintptr_t *defcook, unsigned int *flags, const char *symname)
{
    printf("la_symbind64(): symname = %s; sym->st_value = %p\n",
            symname, sym->st_value);
    printf("        ndx = %u; flags = %#x", ndx, *flags);
    printf("; refcook = %p; defcook = %p\n", refcook, defcook);

    return sym->st_value;
}

Elf32_Addr
la_i86_gnu_pltenter(Elf32_Sym *sym, unsigned int ndx,
        uintptr_t *refcook, uintptr_t *defcook, La_i86_regs *regs,
        unsigned int *flags, const char *symname, long *framesizep)
{
    printf("la_i86_gnu_pltenter(): %s (%p)\n", symname, sym->st_value);

    return sym->st_value;
}

SEE ALSO

ldd(1), dlopen(3), ld.so(8), ldconfig(8)


2020-11-01