
GNU IFUNC는 CVE-2024-3094의 진짜 원인입니다.
[주황색 웹사이트][hn]에 있는 조커들이 저를 힘들게 하는 것 같군요. 아래에 몇 가지를 골라 답변하겠습니다. 하지만 먼저 도전을 하나 제안합니다: 제 돈 500달러를 ifunc 없이 이 공격을 입증하는 첫 번째 사람에게 보내겠습니다. 진심으로 관심이 있으며, 깨달음을 위해 기꺼이 지불할 의향이 있습니다. 이 저장소를 포크하고 작동하는 PoC와 함께 PR을 제출하세요. 우승 시 개인적으로 연락처를 요청하겠습니다. 자, 답변을 시작합니다:
이것은 잘못된 나무를 짖는 것이다.
꼬마야, 나는 잘못된 나무에 살고 있어. 내가 원하는 사람한테는 누구든지 짖을 수 있어.
그것은 익스플로잇에 필수적이지 않았다.
네가 익스플로잇에 필수적이지 않아!
루트로 실행되는 임의의 코드에 대한 보호를 추가하려면 항상 selinux가 있다.
일단 로드되면, 이 공격은 더 이상의 시스템 호출 경계를 넘을 필요가 없었다. 그래서, 우리는 "보너스" 루트 세션을 제한할 수 있었겠지만, 여전히 승인되지 않은 손님이 머신에 들어와 있었을 것이다!
이게 빌보의 생일인가? 파티 업무가 아니면 입장 불가!!
- IFUNC은 main 전에 코드를 실행하는 유일한 방법은 아니다.
하지만 메모리 보호가 설정되기 전에 코드를 실행하는 불필요한 방법이다.
- 그들이 제시하는 대안은 함수 포인터가 프로세스 수명 동안 쓰기 가능한 상태로 남아 있기 때문에 덜 안전하다고 주장할 수 있다.
mprotect로 즉흥적으로 대처할 수 있다! 수정 중인 LD_PRELOAD 하위 섹션 위의 마지막 문장을 참고하라.
네, 이 블로그는 잘못된 방향으로 가고 있다.
실례합니다만 이 블로그는 방향이 없었습니다. 이 모든 장난을 제가 직접 했습니다! 아무도 저를 이렇게 멍청하게 만들지 않았어요.
IFUNC은 [클라이언트] 소프트웨어 자체에서 구현되어야 한다,
@CountWSS 💯 오 그렇군
Github 유지관리자부터 시작되는 일련의 명백한 프로세스 실패...
이것이 제가 진지하게 답변할 유일한 점입니다:
xz-utils 유지관리자에게 지극히 불공평하고 커뮤니티에 매우 위험한 것은, 이 사건이 그의 실수로 시작되었다고 생각하는 것입니다. 이 사건은 아무도 이 프로젝트를 유지하는 데 신경 쓰지 않은 것에서 시작되었습니다. 공격자는 ifunc을 기술적 취약점으로, 그리고 xz-utils에 대한 우리의 집단적 방치는 사회적 취약점으로 이용했습니다. Collin 씨의 행동을 수년간의 영웅적인 지역사회 봉사 헌신 외의 다른 것으로 보는 것은 부끄러운 일이라고 생각합니다.
또한 Bruce Schneier도 저와 동의합니다... 그러니 안타깝지만, 당신의 주장은 끝났어요.
언어가 필요 이상으로 강했을 수도 있다.
제 친구들이 제가 이것을 얼마나 순화하게 했는지 당신은 믿지 못할 것입니다.
리눅스 배포판들은 OpenBSD가 자신들의 혼란에 적응하고 따르기를 기대할 정도로 자신을 과대평가해서는 안 된다.
@debazel!!! 나는 좋아한다.
완전 개소리야.
좋아요, 그 부분은 정확합니다.
왜 xz-utils를 [CVE-2024-3094][nvd] 탓해서는 안 되는지. 또한 ETSA Talk를 확인하세요!

CVE-2024-3094, 더 일반적으로 "xz-utils 백도어"로 알려진 이 사건은 글로벌 사이버보안에 가까운 아차사건이었습니다. 이 공격이 [Andres Freund][freund]에 의해 간발의 차로 발견되지 않았다면, 지구상 대부분의 SSH 서버가 이 공격 배후에 있는 자에게 루트 접근 권한을 부여하기 시작했을 것입니다.
불행히도, 너무 많은 분석이 어떻게 [악성 코드][JiaT75]가 xz-utils 저장소에 유입되었는지에 집중되었습니다. 대신 저는 중요한 오픈 소스 소프트웨어의 두 가지 오랜 설계 결정이 이 공격을 가능하게 했다고 주장하고자 합니다: [OpenSSH를 SystemD에 연결][biebl]한 것과 [GNU IFUNC][sourceware]의 존재입니다.
시작하기 전에: 이 논의의 상당 부분은 리눅스에서의 동적 링킹의 복잡성에 관한 것입니다. 복습이 필요하다면 dynamic_linking.md를 확인하세요.
xz-utils 백도어의 높은 수준의 세부 사항을 설명하는 좋은 글들이 많이 있습니다. 예를 들어 Dan Goodin의 [xz Utils 백도어가 세계를 거의 감염시킬 뻔한 것에 대해 우리가 아는 것][goodin1]과 Sam James의 [xz-utils 백도어(CVE-2024-3094) FAQ][thesamesam] gist가 있습니다. 여기서 그것들을 모두 다시 다룰 필요는 없으므로, 이 글의 목적을 위해 매우 대략적인 요약을 제시합니다:
## 리눅스 배포판이 OpenSSH를 수정하는 이유는?
짧게 말하면, 어쩔 수 없기 때문입니다. OpenSSH는 OpenBSD 커뮤니티가 OpenBSD 커뮤니티를 위해 개발하며, 리눅스에 대해서는 전혀 신경 쓰지 않습니다. [Portable OpenSSH][mindrot] 프로젝트는 OpenBSD 전용 구성 요소를 일반적인 POSIX 구성 요소로 대체하고, 해당되는 경우 플랫폼별 코드를 포함하는 최선의 패치 모음입니다. 실제 SSH 소프트웨어 공급망은 다음과 같은 형태를 띱니다:```mermaid
flowchart TD
subgraph OpenBSD Folks
A[OpenBSD]
B[OpenSSH]
H[improvements]
end
B-->A
A-->H
H-->B
B-->C
C[Portable OpenSSH]
subgraph Debian Folks
D[Debian SSH]
G[improvements]
end
C-->D
D-->G
G-->C
subgraph Fedora Folks
J[Fedora SSH]
K[improvements]
end
C-->J
J-->K
K-->C
OpenBSD의 OpenSSH 버전은 다른 모든 것의 업스트림이며, 대부분의 개선 사항은 OpenBSD 커뮤니티 내에서 비롯됩니다. 이러한 변경 사항은 Portable OpenSSH 프로젝트로 다운스트림되어, OpenBSD에 특화되지 않은 방식으로 새로운 기능을 재구현하려고 시도합니다. 이것이 SSH가 Linux, macOS, FreeBSD, 심지어 Windows와 같은 플랫폼에서 작동할 수 있게 하는 이유입니다.
하지만 여기서 끝나지 않습니다. 일부 운영 체제는 Portable OpenSSH가 제공하는 것 이상으로 추가 사용자 지정을 적용합니다. 예를 들어, Apple은 macOS 비밀번호 관리자와 통합을 돕기 위해 ssh-add에 [--apple-use-keychain][keith] 플래그를 추가합니다.
CVE-2024-3094의 경우, Fedora와 Debian은 [sshd 재시작 관련 경합 조건][schmidt]을 해결하기 위해 OpenSSH 포크에 자체 [SystemD 패치][biebl]를 유지했습니다. 따라서 SSH의 실제 공급망은 다음과 같아지기 시작했습니다:```mermaid
flowchart TD
A[OpenSSH]
B[Portable OpenSSH]
C[Debian SSH]
D[Fedora SSH]
A-->B
B-->C
B-->D
C<-->|SystemD Patches|D
이 패치들은 Portable OpenSSH에 병합되지 않았습니다. Portable OpenSSH팀은 ["libsystemd에 대한 의존성을 추가하는 데 관심이 없다고 밝혔기 때문입니다"][djmdjm]. 또한 업스트림 OpenSSH에도 병합되지 않았는데, OpenBSD는 SystemD를 지원할 필요가 전혀 없기 때문입니다.
### '관심사 분리'에 대한 우려
이것은 충분히 무해해 보이지만, 오픈 소스, 특히 리눅스에서 훨씬 더 큰 문제의 예입니다: 운영 체제의 중요한 구성 요소들이 서로를 모르고 대화하지 않는 사람들에 의해 개발됩니다.
* OpenSSH에 SystemD 패치를 적용한 사람들은 libsystemd가 xz-utils에 의존한다는 것을 알고 있었을까요? (또는 신경 썼을까요?)
* SystemD 팀은 xz-utils가 ifunc을 사용하기 시작했다는 것을 알고 있었을까요? (또는 신경 썼을까요?)
* OpenSSH 팀은 ifunc가 존재한다는 것을 알고 있었을까요? (또는 신경 썼을까요?) OpenBSD에서는 확실히 존재하지 않습니다.
어떤 의미에서, 이러한 의사소통의 단절은 오픈 소스의 특징입니다: 당신을 귀찮게 하지 않고도 당신의 작업을 내 필요에 맞게 조정할 수 있습니다. 하지만 이는 또한 중요한 설계 가정(예: 전통적인 동적 링킹 과정)이 유지되는 것을 방해하는 간접성의 정도를 초래할 수 있습니다.
[Conway의 법칙][conway]의 명백한 결과는 조직도를 배포한다면, 조직도의 균열에 서식하는 버그도 함께 배포한다는 것입니다. 여기서 어떤 개인이나 팀이 실제로 실수를 한 것은 아니지만, 후견지명의 이점을 통해 공격자들이 데비안/페도라 SSH의 왼손이 xz-utils의 오른손이 무엇을 하고 있는지 모른다는 것을 인지했음이 분명합니다.
## GNU IFUNC는 *원래* 무엇을 하기 위한 것인가?
이는 런타임에 어떤 함수 버전을 사용할지 결정할 수 있게 해줍니다. 링커가 심볼을 해석하는 방식에 영향을 미치기 위해 **임의의 코드**를 실행할 기회를 제공함으로써 이를 수행합니다.

### CPU 기능 감지
다양한 x86 CPU에서 실행되어야 하는 애플리케이션이 있다고 가정해보세요. 현재 CPU의 특정 기능에 따라 동일한 작업에 대해 서로 다른 알고리즘을 사용하는 것이 더 나을 수 있습니다. IFUNC의 원래 아이디어는 프로그램이 함수가 처음 호출될 때 CPU 기능을 확인하고, 그 이후에는 해당 CPU에 가장 적합한 구현을 사용할 수 있도록 하는 것이었습니다.
다음 [`cpu_demo.c`](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/code/cpu_demo.c)를 살펴보세요:```c
void print_cpu_info() __attribute__((ifunc ("resolve_cpu_info")));
void print_avx2() { printf("AVX2 is present.\n"); }
void print_nope() { printf("AVX2 is missing.\n"); }
static void* resolve_cpu_info(void) {
__builtin_cpu_init();
if (__builtin_cpu_supports("avx2")) {
return print_avx2;
} else {
return print_nope;
}
}
int main() {
print_cpu_info();
return 0;
}
이 프로그램은 IFUNC의 가장 일반적인 사용 사례를 보여줍니다: CPU가 특정 기능을 지원하는지 여부를 묻고, 지원되는 기능에 따라 함수의 다른
구현을 제공합니다.
이 경우, print_cpu_info 함수는 CPU의 성능에 따라
"AVX2가 있습니다" 또는 "AVX2가 없습니다" 중 하나를 출력하게 됩니다.
IFUNC는 CPU 기능을 조사하기 위한 것이지만, 리졸버에서 더 복잡한 코드를 실행하는 것을 막지는 않습니다.
예를 들어,
tty_demo.c는 STDOUT이 파일인지 터미널인지에 따라 다른 함수 구현을 로드하는 방법을 보여줍니다:```c
// Print Green text to the Terminal
void print_to_tty(const char *message) {
const char *green_start = "\033[32m";
const char *color_reset = "\033[0m";
printf("%sTTY: %s%s\n", green_start, message, color_reset);
}
// Print plain text to a file void print_to_file(const char *message) { printf("FILE: %s\n", message); }
void print_message(const char *message)
attribute((ifunc("resolve_print_function")));
void (*resolve_print_function(void))(const char *) { struct termios term;
// Ask the kernel whether stdout is a file or a tty
int result = ioctl(STDOUT_FILENO, TCGETS, &term);
if (result == 0) {
// stdout is a terminal
return print_to_tty;
} else {
// stdout is not a terminal
return print_to_file;
}
}
int main() { print_message("Hello, World!"); return 0; }
이는 IFUNC의 의도된 사용 방식은 아니지만, 가능한 것을 보여줍니다: 사용자가 선언한 IFUNC를 사용하는 모든 프로그램에서 `main` 이전에 임의의 코드를 실행할 수 있습니다.
## IFUNC는 아마도 나쁜 아이디어입니다

GNU IFUNC는 구현하기 어렵고, 올바르게 사용하기 까다로우며, (성능 도구라 주장되지만) 대안에 비해 훨씬 빠르지 않습니다. CVE-2024-3094에서 본 바와 같이, 이는 소프트웨어 공급망 공격에 매우 강력한 도구이기도 합니다.
IFUNC는 GNU C 라이브러리 내에서 광범위하게 사용되며, 이는 아마도 괜찮습니다. 원래 IFUNC가 개발된 대상이 바로 그들이며, 그들은 실제로 IFUNC를 구현하는 컴파일러 및 링커 팀과 밀접하게 연결되어 있습니다. 그들은 트레이드오프를 이해하기에 가장 좋은 위치에 있으며, CPU별 구현의 혜택을 받는 수많은 libc 함수들이 있습니다. IFUNC를 glibc의 내부 인터페이스로 간주하고 다른 응용 프로그램에서는 사용을 피하는 것이 좋다고 생각합니다.
### 안전하게 사용하기에는 너무 혼란스럽습니다
ifunc는 사용하기에 전적으로 너무 어렵습니다. [모서리 경우][nagy]가 너무 많고, [공식 문서][gnu-cfa]는 [부족합니다][sourceware]. 이로 인해 사용자는 ifunc를 채택하는 것이 간단하다는 오해를 하게 됩니다.
ifunc가 사용 가능해진 지 몇 년이 지난 후에도, 광고된 인터페이스는 [작동하지 않았습니다][agner]. GCC 개발자들은 이를 [실수][odonell]라고 부르며 IFUNC의 취약성을 보완하기 위해 경고를 추가하는 것을 고려했습니다:
> IFUNC를 강건하게 만드는 데 필요한 glibc 솔루션이 마련되지 않았으므로, 사용자에게 이것이 깨질 수 있음을 경고하기 위해 할 수 있는 일을 해야 합니다.
IFUNC만 그런 것도 아닙니다. Apple Mach-O에는 `.symbol_resolver`라는 유사한 기능이 있으며, 이는 [추가한 것을 후회][rjmccall]하고 있습니다.
### RELRO를 약화시킵니다
전역 오프셋 테이블이 여전히 쓰기 가능한 동안 임의의 코드를 실행할 수 있도록 함으로써, [RELRO](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/dynamic_linking.md#relro)가 제공하는 보호가 [무의미해집니다][binarly-io].
이는 주목할 만한 중요한 점입니다. RELRO는 동적으로 로드된 심볼의 무결성을 보호하는 방법으로 자신을 광고하기 때문입니다. 사용자 관점(컴파일러와 링커의 사용자로서)에서 이는 [최소 놀람의 원칙][pola]을 위반합니다: 어떤 합리적인 사람도 *동적 라이브러리를 로드하는 것*이 *동적 라이브러리를 보호*하기 위해 설계된 안전 기능을 손상시킬 것이라고 기대하지 않을 것입니다.

### 항상 필요한 것은 아닙니다
이 상황을 처리하는 다른 여러 방법이 있습니다. 각각 다른 트레이드오프가 있지만, 모두 IFUNC보다 훨씬 간단합니다. 이 모든 방법은 IFUNC보다 이식성이 뛰어나고 이해하기 쉬우며 악용하기 어렵습니다.
> "Ifunc는 런타임 마이크로아키텍처별 코드 선택을 하는 완전히 어리석은 방법일 뿐입니다."
>
> -- [Rich Felker](https://hachyderm.io/@dalias/112952237145378821),
> [musl](https://musl.libc.org) 유지보수자.
#### 전역 함수 포인터
IFUNC는 개발자가 함수 선택을 *명령적으로*가 아니라 *선언적으로* 표현할 수 있게 해주기 때문에 매력적입니다. 하지만 이것을 명령적으로 수행하는 것은 실제로 그렇게 어렵지 않습니다. 런타임에 전역 함수 포인터를 해결하는 [`static_pointer.c`](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/code/static_pointer.c)를 고려해보세요:```c
static int (*triple)(int) = 0;
int triple_sse42(int n) { return 3 * n; }
int triple_plain(int n) { return n + n + n; }
void print_fifteen() {
int fifteen = triple(5);
printf("%d\n", fifteen);
}
int main() {
__builtin_cpu_init();
if (__builtin_cpu_supports("sse4.2")) {
triple = triple_sse42;
} else {
triple = triple_plain;
}
print_fifteen();
return 0;
}
링커에서 특별한 트릭을 써서라도 이를 피해야 할 정도로 심각한 문제인가요?
이 접근 방식의 한 가지 단점은 함수 포인터 triple이 런타임에 쓰기 가능하다는 점입니다. 반면 IFUNC+RELRO는 GOT의 ifunc 주소가 한번 해석되면 변경 불가능하도록 보장합니다. 하지만 약간의 추가 작업을 통해 [mprotect(2)][mprotect]를 사용하여 그러한 포인터를 읽기 전용으로 표시할 수 있습니다.
LD_PRELOAD 수정코드에 필요한 CPU 기능을 알고 있고, 각 경우에 대해 동적 라이브러리의 별도 복사본이 있다면, 다음과 같이 $LD_PRELOAD로 올바른 라이브러리를 지정하여 동일한 작업을 수행할 수 있습니다.```bash
#!/bin/bash
if (cat /proc/cpuinfo | grep flags | grep avx2 > /dev/null); then
LD_PRELOAD=./myfunc_avx2.so ./my_app
else
LD_PRELOAD=./myfunc_normal.so ./my_app
fi
(`LD_PRELOAD`에 익숙하지 않다면 catonmat의 ["A Simple `LD_PRELOAD` Tutorial"][catonmat]을 확인해보세요.)
#### 기능 조합별 개별 바이너리
실제로 지원해야 하는 고유 CPU 기능 조합은 몇 개인가요? 심지어 존재하는 조합은 몇 개인가요?
겉으로 보기에는 이것이 조합 폭발처럼 보입니다. amd64 ISA에는 수십 가지의 다양한 벡터 연산, 가상화, 보안 확장이 있습니다. 그러나 이러한 기능들은 실제로 독립적으로 존재하지 않습니다. 예를 들어, AVX-512가 있는 CPU는 SSE4.2나 AES-NI가 없는 경우가 없습니다.
애플리케이션에 필요한 CPU 기능과 실제 칩에서 함께 발생하는 기능을 알면 배송해야 할 별도의 바이너리 수를 결정하는 데 도움이 될 수 있습니다. 예상보다 많지 않을 수도 있습니다. 대부분의 패키지 관리자는 설치 시 스크립트를 실행할 수 있도록 허용합니다. 단일 rpm 또는 deb 파일에 여러 바이너리를 포함하고 설치 시 로직을 사용하여 호스트 CPU에 가장 적합한 바이너리를 선택할 수 있습니다.
### 대안보다 훨씬 빠르지는 않습니다
> 오랫동안 저에게 명백했던 것은, ifunc에 성능상의 이점이 있다 하더라도, 전체 함수 호출이 너무 짧아 호출 오버헤드가 전체 시간의 상당 부분을 차지할 때만 가능하다는 것입니다.
>
> -- [Rich Felker](https://hachyderm.io/@dalias/113074264762553873), [musl](https://musl.libc.org) 유지보수자.
ifunc의 일반적인 정당화가 성능과 관련되어 있기 때문에, *ifunc 자체*가 얼마나 많은 오버헤드를 유발하는지 보고 싶었습니다. 결국 최적화할 가치가 있는 함수는 아마도 자주 호출될 것이므로 함수 호출의 오버헤드를 인정할 가치가 있습니다.
이를 알아내기 위해, *동적으로 해석되는* 함수를 빈 루프에서 반복해서 호출하는 실험을 설계했습니다. [`speed_demo/ifunc`](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/code/speed_demo/ifunc/main.c)와 [`speed_demo/pointer`](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/code/speed_demo/pointer/main.c)를 살펴보세요. 이 프로그램들은 모두 동일한 작업(정적 카운터 증가)을 수행하지만, 증가 함수는 서로 다른 방식으로 해석됩니다. 전자는 GNU IFUNC를 활용하고, 후자는 일반 함수 포인터에 의존합니다.
전체 로직은 다음과 같습니다:
1. 사용할 증가기를 결정하기 위해 해석기(resolver) 함수를 호출합니다.
1. 이 답변을 어딘가에 기록합니다(GOT 또는 함수 포인터로).
1. 이 증가 함수를 수십억 번 호출하여 비용을 추정합니다.
제어군으로는 [`speed_demo/fixed`](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/code/speed_demo/fixed/main.c)도 있습니다. 이는 동일한 증가 작업을 수행하지만 동적으로 해석되는 함수 없이 수행합니다. 이는 런타임 중 어느 부분이 함수 호출에 사용되고 어느 부분이 단순히 덧셈을 수행하는지 추정하는 데 도움이 될 수 있습니다.
Makefile 대상 `rigorous_speed_demo`는 각 프로그램을 여러 번 실행하고 성능에 대한 간단한 통계를 생성합니다. 이 숫자는 물론 하드웨어에 따라 달라지지만, `fixed` 테스트는 비교를 위한 기준선 역할을 해야 합니다.
| *결과* | LOW | HIGH | AVG |
|-----------|------|------|-------|
| fixed | 2.93 | 4.20 | 3.477 |
| ifunc | 9.50 | 10.56| 9.986 |
| pointer | 6.23 | 7.44 | 6.791 |
여기서 볼 수 있는 것은 ifunc가 일반 함수 포인터를 사용하는 것과 비교하여 무시할 수 없는 오버헤드가 있다는 것입니다. 제 하드웨어에서 평균적으로 ifunc 함수를 20억 번 호출하는 데 함수 포인터를 20억 번 호출하는 것보다 약 두 배의 시간이 걸립니다.
이것이 실제 생활에서 중요할까요? 전혀 그렇지 않습니다. 최적화할 가치가 있는 함수는 여기서 분석 중인 '1씩 증가' 함수보다 훨씬 비쌉니다. GNU IFUNC가 성능에 도움이 된다고 주장하면서도 함수 포인터보다 더 많은 비용을 초래하는 것처럼 보이기 때문에 흥미로울 뿐입니다.
#### 다른 기술의 성능
ifunc보다 느린 다른 기술도 있습니다. `super_rigorous_speed_demo`를 살펴보세요. 여기에는 두 가지 다른 실험이 포함됩니다: [`speed_demo/upfront`](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/code/speed_demo/upfront/main.c)와 [`speed_demo/always`](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/code/speed_demo/always/main.c).
`speed_demo/upfront`는 `speed_demo/pointer`와 유사하게 동작하지만, 함수 포인터를 추적하는 대신 CPU 기능 검사 결과를 전역 변수에 저장합니다. 이는 여전히 이러한 전역 변수의 값을 기반으로 어떤 구현이 사용될지 결정하기 위해 먼저 '해석기(resolver)' 함수를 실행해야 합니다. 이 기술은 ifunc보다 느린 것으로 나타났지만, 함수 포인터를 저장하는 것보다 안전합니다. 함수 포인터는 임의의 값으로 설정될 수 있지만, 부울 플래그는 그럴 수 없기 때문입니다. 따라서 이러한 변수를 수정할 수 있는 공격자는 프로그램을 *더 느리게* 만들 수 있지만, 프로그램이 *다르게* 동작하도록 할 수는 없습니다.
`speed_demo/always`는 가장 느린 기술로 설계되었습니다. 구현이 필요할 때마다 필요한 모든 CPU 기능을 확인하고 즉시 하나를 선택합니다. 흥미롭게도 이 기술은 다른 기술보다 현저히 느리지 않습니다. 확인할 CPU 기능이 하나만 있는 경우 ifunc보다 약간 느릴 뿐입니다.
| 테스트 | LOW | HIGH | AVG |
|---------|------|-------|---------|
| fixed | 5.02 | 5.70 | 5.37 |
| pointer | 6.40 | 7.02 | 6.66 |
| ifunc | 8.56 | 11.11 | 9.64 |
| upfront | 9.24 | 9.41 | 9.33333 |
| always | 10.07| 10.56 | 10.2333 |
## 결론
GNU IFUNC는 gcc/ld.so의 틈새 기능으로, CVE-2024-3094에 사용되기 전까지 거의 알려지지 않았습니다. 명백하지 않은 함정과 불충분한 문서화가 있습니다. 링커가 `main` 이전, 프로세스 이미지의 중요한 부분이 초기화되고 보호되기 전에 임의의 코드를 실행하도록 함으로써, 프로그래밍의 가장 기본적인 가정 중 하나를 훼손합니다: 라이브러리를 로드하는 단순한 행위가 본질적으로 프로그램을 *변경*하지 않는다는 가정입니다.
IFUNC의 성능 이점은 실제로 존재하지만, 대안보다 의미 있게 더 나은 것은 아닙니다. 여러 CPU에 최적화된 단일 바이너리를 배포하는 단순함은 매우 매력적이지만, 더 간단한 기술(함수 포인터 등)로도 달성할 수 있습니다.
저는 IFUNC가 gcc에서 기본적으로 비활성화되어야 한다고 생각합니다. 활성화하려면 `--enable-cve-2024-3094`와 같은 무서운 플래그가 필요해야 합니다. libc 외부에서 이를 사용하는 사람은 대안 해결책이 적절하지 않다는 엄격하고 잘 연구된 논거를 제공해야 합니다.

<!-- REFERENCES -->
[aes-ni]: https://en.wikipedia.org/wiki/AES_instruction_set
[agner]: https://www.agner.org/optimize/blog/read.php?i=167
[biebl]: https://salsa.debian.org/ssh-team/openssh/-/commit/818791ef8edf087481bd49eb32335c8d7e1953d6
[binarly-io]: https://github.com/binarly-io/binary-risk-intelligence/tree/master/xz-backdoor
[catonmat]: https://catonmat.net/simple-ld-preload-tutorial
[conway]: https://en.wikipedia.org/wiki/Conway%27s_law
[djmdjm]: https://github.com/openssh/openssh-portable/pull/251#issuecomment-2027935208
[fr0gger]: https://infosec.exchange/@fr0gger/112189232773640259
[freund]: https://www.openwall.com/lists/oss-security/2024/03/29/4
[gnu-cfa]: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-ifunc-function-attribute
[goodin1]: https://arstechnica.com/security/2024/04/what-we-know-about-the-xz-utils-backdoor-that-almost-infected-the-world/
[hn]: https://news.ycombinator.com/item?id=48056749
[jasoncc]: https://jasoncc.github.io/gnu_gcc_glibc/gnu-ifunc.html#relocations-and-pic
[JiaT75]: https://github.com/tukaani-project/xz/commit/cf44e4b7f5dfdbf8c78aef377c10f71e274f63c0
[keith]: https://keith.github.io/xcode-man-pages/ssh-add.1.html#apple-use-keychain
[mindrot]: https://anongit.mindrot.org/openssh.git
[mprotect]: https://www.man7.org/linux/man-pages/man2/mprotect.2.html
[musl]: https://musl.libc.org
[nagy]: https://sourceware.org/legacy-ml/libc-alpha/2015-11/msg00108.html
[nvd]: https://nvd.nist.gov/vuln/detail/CVE-2024-3094
[odonell]: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70082#c0
[OpenSSH9.8p1]: https://www.openssh.com/releasenotes.html#9.8p1
[openssh-unix-dev]: https://marc.info/?l=openssh-unix-dev&m=171288895109872&w=2
[pola]: https://en.wikipedia.org/wiki/Principle_of_least_astonishment
[rjmccall]: https://reviews.llvm.org/D139163#3993795
[schmidt]: https://bugzilla.redhat.com/show_bug.cgi?id=1381997#c4
[sourceware]: https://sourceware.org/glibc/wiki/GNU_IFUNC
[thesamesam]: https://gist.github.com/thesamesam/223949d5a074ebc3dce9ee78baad9e27#design