[하루한줄] CVE-2025-4802 : GLIBC의 정적 setuid 바이너리에서 발생하는 임의 라이브러리 경로 취약점
URL
Target
- 2.27부터 2.38까지의 GNU C Library를 사용하는 환경
Explain
background
일반적으로 리눅스에서 setuid/setgid 권한이 적용된 바이너리를 실행하면 커널은 execve() 내부에서 secure execution이라는 특수 모드를 활성화합니다. 이 과정에서 커널은 bprm->secureexec = 1을 설정[1]하고 ELF 보조 벡터에 AT_SECURE = 1 값을 삽입[2]합니다.
linux-6.17.9/security/commoncap.c
int cap_bprm_creds_from_file(struct linux_binprm *bprm, const struct file *file)
...
/* Check for privilege-elevated exec. */
if (id_changed ||
!uid_eq(new->euid, old->uid) ||
!gid_eq(new->egid, old->gid) ||
(!__is_real(root_uid, new) &&
(effective ||
__cap_grew(permitted, ambient, new))))
bprm->secureexec = 1; // [1]
...
linux-6.17.9/fs/binfmt_elf.c
static int
create_elf_tables(struct linux_binprm *bprm, const struct elfhdr *exec,
unsigned long interp_load_addr,
unsigned long e_entry, unsigned long phdr_addr)
...
NEW_AUX_ENT(AT_SECURE, bprm->secureexec); // [2]
...
AT_SECURE가 1이면 glibc는 secure mode로 전환하여 내부적으로 __libc_enable_secure = 1을 설정[3]하고 LD_LIBRARY_PATH, LD_PRELOAD, LD_AUDIT과 같은 위험한 환경 변수를 무시[4]하게 됩니다.
glibc-2.35/elf/dl-support.c
void
_dl_aux_init (ElfW(auxv_t) *av)
...
case AT_SECURE:
seen = -1;
__libc_enable_secure = av->a_un.a_val; // [3]
__libc_enable_secure_decided = 1;
break;
...
glibc-2.35/elf/rtld.c
static void
dl_main (const ElfW(Phdr) *phdr,
ElfW(Word) phnum,
ElfW(Addr) *user_entry,
ElfW(auxv_t) *auxv)
...
dl_main_state_init (&state);
...
static void
dl_main_state_init (struct dl_main_state *state)
{
audit_list_init (&state->audit_list);
state->library_path = NULL;
state->library_path_source = NULL;
...
static void
process_envvars (struct dl_main_state *state)
...
case 12:
/* The library search path. */
if (!__libc_enable_secure // [4]
&& memcmp (envline, "LIBRARY_PATH", 12) == 0)
{
state->library_path = &envline[13];
state->library_path_source = "LD_LIBRARY_PATH";
break;
}
...
해당 필터링은 프로세스의 초기화 루틴에서 수행되며 이로 인해 일반 사용자가 setuid 바이너리에 임의의 라이브러리 경로를 전달함으로써 root 권한을 획득하는 경로가 차단됩니다.
root cause
해당 취약점의 경우 glibc의 __libc_enable_secure 값을 이용한 환경 변수 필터링 로직이 동적 로더의 초기화 함수인 dl_main() 에서만 적용되어 있었기 때문에 발생하였습니다.
정적 링크된 바이너리는 동적 로더를 사용하지 않기 때문에 _dl_non_dynamic_init() 함수를 통해 라이브러리 경로를 설정[5]하는데 해당 함수에서는 __libc_enable_secure 값에 따른 환경 변수 필터링이 미흡했습니다.
glibc-2.35/elf/dl-support.c
void
_dl_non_dynamic_init (void)
{
...
/* Initialize the data structures for the search paths for shared
objects. */
_dl_init_paths (getenv ("LD_LIBRARY_PATH"), "LD_LIBRARY_PATH", // [5]
/* No glibc-hwcaps selection support in statically
linked binaries. */
NULL, NULL);
...
이로 인해 커널이 secure execution 모드를 활성화하더라도 dlopen()등의 함수 호출에서 환경 변수 기반의 경로를 그대로 사용하게 되어 공격자가 LD_LIBRARY_PATH 환경 변수에 제어 가능한 디렉터리를 설정하면 setuid 바이너리가 공격자의 라이브러리를 로드할 수 있게 됩니다.
patch
패치 커밋 5451fa962cd0a90a0e2ec1d8910a559ace02bba0에서 __libc_enable_secure 값을 통해 환경 변수를 필터링[6]한 후 라이브러리 경로를 로드[7]하도록 변경되었습니다.
glibc-2.39/elf/dl-support.c
void
_dl_non_dynamic_init (void)
{
_dl_main_map.l_origin = _dl_get_origin ();
_dl_main_map.l_phdr = GL(dl_phdr);
_dl_main_map.l_phnum = GL(dl_phnum);
/* Set up the data structures for the system-supplied DSO early,
so they can influence _dl_init_paths. */
setup_vdso (NULL, NULL);
/* With vDSO setup we can initialize the function pointers. */
setup_vdso_pointers ();
if (__libc_enable_secure) // [6]
{
static const char unsecure_envvars[] =
UNSECURE_ENVVARS
;
const char *cp = unsecure_envvars;
while (cp < unsecure_envvars + sizeof (unsecure_envvars))
{
__unsetenv (cp);
cp = strchr (cp, '\0') + 1;
}
}
...
/* Initialize the data structures for the search paths for shared
objects. */
_dl_init_paths (getenv ("LD_LIBRARY_PATH"), "LD_LIBRARY_PATH", // [7]
/* No glibc-hwcaps selection support in statically
linked binaries. */
NULL, NULL);
...
Reference
- https://www.man7.org/linux/man-pages/man3/getauxval.3.html
- https://ubuntu.com/security/CVE-2025-4802
- https://cyberpress.org/critical-glibc-flaw/
- https://articles.manugarg.com/aboutelfauxiliaryvectors
- https://patchwork.yoctoproject.org/project/oe-core/patch/20250611113400.2146584-1-sunilkumar.dora@windriver.com/#28605