
GNU IFUNC は CVE-2024-3094 の真の原因です
[オレンジのウェブサイト][hn]で、あなたがた道化師が私を困らせているのはわかっている。以下にいくつか選んで応答するが、まず挑戦状を叩きつける: 私の自腹で500ドルを、ifuncを使わずにこの攻撃を実演してみせた最初の人物に送る。純粋に興味があるし、啓蒙のためなら喜んで金を出す。このリポジトリをフォークして、動作するPoCをPRで送れ。勝者には非公開で住所を聞く。では応答:
これは的外れな批判だ。
坊や、俺は的外れな場所に住んでいるんだ。誰にでも吠えられる。
この脆弱性にとって必須ではなかった、
お前が必須じゃないんだよ!
selinuxがあれば、ルートとして任意のコードを実行する攻撃から保護できる。
一度ロードされてしまえば、この攻撃はそれ以上のシステムコール境界を越える必要はなかった。つまり、確かに「ボーナス」のrootセッションは制限できたかもしれないが、それでもマシンに招かれざる客がいたことには変わりない!
これはビルボの誕生日会か? パーティの用事以外は入場禁止だ!
- IFUNCだけがmainより前にコードを実行する方法ではない。
しかし、メモリ保護が設定される前にコードを実行する不要な方法ではある。
- 彼らが提示した代替案は、関数ポインタがプロセスの存続期間中書き込み可能なままになるため、議論の余地なく安全性が低い。
mprotectで間に合わせられる!上記、LD_PRELOADの変更の項の直前の文を見よ。
ええ、このブログは迷走している。
失礼、このブログは導かれずに進んだのだ。これらの悪ふざけは全部自分でやった! 誰も私をこんなバカに仕向けたわけじゃない。
IFUNCは[クライアント]ソフトウェア自身で実装されるべきだ、
@CountWSS 💯 その通り
Githubメンテナからの一連の明白なプロセス障害が……
これだけは真面目に応答する:
xz-utilsのメンテナを非難の始まりと見なすのは極めて不公平であり、コミュニティにとって危険だと思う。始まりは、誰もこのプロジェクトのメンテナンスを手伝おうとしなかったことだ。攻撃者はifuncを技術的脆弱性として、そして我々のxz-utilsに対する集団的怠慢を社会的脆弱性として利用した。Collins氏の行動を、長年にわたる英雄的なコミュニティへの献身以外の何かと見なすのは恥ずべきことだ。
それにブルース・シュナイアーも私に同意しているので……あなたの主張はおしまいだ。
言葉遣いが必要以上にきつかったかもしれない
友達にこれでもかなり和らげさせられたんだ、信じられないだろう。
Linuxディストリビューションは、OpenBSDが自分たちの混乱に合わせて適応することを期待できるほど、自分たちを高く評価すべきではない。
@debazel!!! メ・グスタ。
まったくのでたらめだ。
ああ、その部分は正確だ。
なぜxz-utilsを[CVE-2024-3094][nvd]のせいにするのをやめるべきか。さらに私のETSAトークもご覧ください!

CVE-2024-3094、通称「xz-utilsバックドア」は、世界のサイバーセキュリティにとって危機一髪の出来事だった。この攻撃が[Andres Freund][freund]によって間一髪で発見されていなければ、地球上のほとんどのSSHサーバーがこの攻撃の背後にいる者にrootアクセスを許可するようになっていただろう。
残念ながら、あまりにも多くの分析が、どのようにして[悪意のあるコード][JiaT75]がxz-utilsリポジトリに忍び込んだかに焦点を当ててきた。代わりに、私は、重要なオープンソースソフトウェアにおける2つの長年にわたる設計上の決定がこの攻撃を可能にしたと主張したい: [OpenSSHをSystemDにリンクする][biebl]こと、そして[GNU IFUNC][sourceware]の存在である。
始める前に: この議論の多くは、Linuxにおける動的リンクの複雑さを扱う。復習が必要なら、dynamic_linking.mdを参照されたい。
xz-utilsバックドアのハイレベルな詳細を説明する優れた記事はたくさんある。例えば、Dan Goodinの[What we know about the xz Utils backdoor that almost infected the world][goodin1]や、Sam Jamesの[FAQ on the xz-utils backdoor (CVE-2024-3094)][thesamesam] gistなどだ。ここでそれらを繰り返す必要はない。この記事の目的のため、以下に非常に大まかなまとめを示す:
## LinuxディストリビューションはなぜOpenSSHを変更するのか?
簡単な答えは、彼らがそうせざるを得ないからです。OpenSSHはOpenBSDコミュニティによって、OpenBSDコミュニティのために開発されており、Linuxについてはまったく関心がありません。[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はssh-addに[--apple-use-keychain][keith]フラグを追加し、macOSのパスワードマネージャーとの統合を支援しています。
CVE-2024-3094の場合、FedoraとDebianはOpenSSHのフォークに対して独自の[SystemDパッチ][biebl]を維持し、[sshdの再起動に関する競合状態][schmidt]を修正しました。このように、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をサポートする必要が全くなかったからだ。
### 「関心事の分離」についての懸念事項
これは一見無害に見えるが、オープンソース、特にLinuxにおけるより大きな問題の一例である:オペレーティングシステムの重要なコンポーネントが、互いに知らず、会話もしていない人々によって開発されている。
* OpenSSHをSystemD向けにパッチした人々は、libsystemdがxz-utilsに依存していることを知っていた(または気にしていた)だろうか?
* SystemDの人々は、xz-utilsがifuncを使い始めたことを知っていた(または気にしていた)だろうか?
* OpenSSHの人々は、ifuncというものが存在することを知っていた(または気にしていた)だろうか?それは間違いなくOpenBSDには存在しないものだ。
ある意味で、このコミュニケーションの断絶はオープンソースの特徴である:私はあなたに煩わされることなく、あなたの作品を自分の必要に合わせて適応させることができる。しかし、それはまた、重要な設計上の前提(例えば従来の動的リンクプロセス)が守られることを妨げる間接性の度合いを生み出す可能性もある。
[Conwayの法則][conway]の明白な帰結は、組織図を出荷しているなら、その組織図の隙間に潜むバグも出荷しているということである。ここで実際に間違いを犯した個人やチームはいないが、後知恵の恩恵により、攻撃者はDebian/Fedora SSHの左手がxz-utilsの右手が何をしているかを知らないと認識していたことは明らかである。
## GNU IFUNCは何をする*べき*なのか?
これにより、実行時にある関数のどのバージョンを使用したいかを決定することができる。これは、リンカがシンボルを解決する方法に影響を与えるために**任意のコード**を実行する機会を提供することで実現される。

### CPU機能の検出
あなたが幅広い種類のx86 CPU上で動作しなければならないアプリケーションを持っているとする。現在のCPUの特定の機能に応じて、同じタスクに対して異なるアルゴリズムを使用したい場合がある。IFUNCの背後にある元々のアイデアは、プログラムが初めて関数が呼び出された時にCPU機能をチェックし、その後はそのCPUに最も適した実装を使用できるようにすることだった。
[`cpu_demo.c`](https://github.com/robertdfrench/ifuncd-up/blob/trunk/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 is present"または"AVX2 is missing"のいずれかを表示します。
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を弱体化する
Global Offset Tableがまだ書き込み可能な状態で任意のコードを実行できるようにすることで、[RELRO](https://github.com/robertdfrench/ifuncd-up/blob/trunk/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/trunk/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/trunk/code/speed_demo/ifunc/main.c) と [`speed_demo/pointer`](https://github.com/robertdfrench/ifuncd-up/blob/trunk/code/speed_demo/pointer/main.c) をご覧ください。これらのプログラムはどちらも同じ処理(静的カウンターのインクリメント)を行いますが、インクリメント関数の解決方法が異なります。前者は GNU IFUNC を利用し、後者は従来の関数ポインタに依存しています。
全体的なロジックは次のとおりです。
1. リゾルバ関数を呼び出して、使用するインクリメンターを決定します。
1. この回答をどこか(GOT 内、または関数ポインタとして)に記録します。
1. このインクリメンター関数を数十億回呼び出して、そのコストの見積もりを取得します。
対照実験として、[`speed_demo/fixed`](https://github.com/robertdfrench/ifuncd-up/blob/trunk/code/speed_demo/fixed/main.c) もあります。これは同じインクリメント処理を行いますが、動的に解決される関数は使用しません。これを使用すると、ランタイムのどの部分が関数呼び出しに費やされ、どの部分が単なる加算であるかを推定するのに役立ちます。
Makefile のターゲット `rigorous_speed_demo` は、これらの各プログラムを複数回実行し、パフォーマンスに関する簡単な統計を生成します。これらの数値はもちろんハードウェアによって異なりますが、`fixed` テストは比較のベースラインとして機能します。
| *Results* | 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 億回呼び出す場合の約 2 倍の時間がかかります。
これは現実の世界で重要でしょうか? 全く重要ではありません。最適化する価値のある関数は、ここで分析している「1 ずつインクリメントする」関数よりもはるかに高コストです。GNU IFUNC はパフォーマンスの恩恵をもたらすと主張しているにもかかわらず、関数ポインタよりもコストがかかるように見えるため、興味深いだけです。
#### 他の手法のパフォーマンス
ifunc よりも遅い他の手法もあります。`super_rigorous_speed_demo` をご覧ください。これにはさらに 2 つの実験が含まれています: [`speed_demo/upfront`](https://github.com/robertdfrench/ifuncd-up/blob/trunk/code/speed_demo/upfront/main.c) と [`speed_demo/always`](https://github.com/robertdfrench/ifuncd-up/blob/trunk/code/speed_demo/always/main.c) です。
`speed_demo/upfront` は `speed_demo/pointer` と同様に動作しますが、関数ポインタを保持する代わりに、CPU 機能チェックの結果をグローバル変数に保存します。これにより、まず「リゾルバ」関数を実行して、これらのグローバル変数の値に基づいて使用する実装を決定する必要があります。この手法は ifunc よりも遅いことがわかりますが、関数ポインタを保存するよりも安全です。関数ポインタは任意の値に設定できるのに対し、ブールフラグは設定できません。したがって、これらの変数を変更できる攻撃者はプログラムを*遅く*することはできますが、プログラムの*動作を変える*ことはできません。
`speed_demo/always` は最も遅い手法として設計されています。実装が必要になるたびに必要な CPU 機能をすべて確認し、その場で選択します。不思議なことに、この手法は他の手法と比べて大幅に遅いわけではありません。チェックする CPU 機能が 1 つだけの場合、ifunc よりもわずかに遅いだけです。
| TEST | 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` の前、プロセスイメージの重要な部分が初期化および保護される前に任意のコードを実行できるようにすることで、プログラミングの最も基本的な前提の 1 つ、つまりライブラリをロードするという単なる行為がプログラムを本質的に*変更*しないという前提を覆します。
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