
GNU IFUNC هو الجاني الحقيقي وراء CVE-2024-3094
أرى أيها المزاحون على [الموقع البرتقالي][hn] أنكم تزعجونني. سأقدم بعض الردود المختارة أدناه، لكن أولاً أقدم تحديًا: سأرسل 500 دولار من مالي الخاص لأول شخص يمكنه إثبات هذا الهجوم بدون ifunc. أنا مهتم حقًا ومستعد للدفع من أجل الإيضاح. قم بعمل Fork لهذا المستودع وأرسل PR مع PoC العامل الخاص بك، وسأطلب عنوان بريدك الإلكتروني بشكل خاص إذا فزت. والآن للردود:
هذا هو النباح على الشجرة الخطأ.
يا فتى، أنا أعيش على الشجرة الخطأ، يمكنني النباح على من أريد.
لم يكن ضروريًا للاستغلال،
أنت لست ضروريًا للاستغلال!
هناك دائمًا selinux إذا أردنا إضافة حماية ضد تشغيل كود عشوائي كجذر.
بمجرد التحميل، لم يحتاج هذا الهجوم إلى عبور أي حدود استدعاء نظام إضافية. لذا نعم، كان بإمكاننا تقييد جلسة جذر "إضافية"، لكننا كنا سنظل لدينا ضيوف غير مدعوين على الجهاز!
ما هذا، عيد ميلاد Bilbo؟؟ لا دخول إلا في حفلة عمل!!
- IFUNC ليست الطريقة الوحيدة لتشغيل الكود قبل main.
لكنها طريقة غير ضرورية لتشغيل الكود قبل إعداد حماية الذاكرة
- البديل الذي يقدمونه هو أقل أمانًا بشكل يمكن الجدال فيه لأن مؤشر الدالة سيظل قابلاً للكتابة طوال عمر العملية،
يمكننا الارتجال باستخدام mprotect! انظر الجملة الأخيرة فوق القسم الفرعي تعديل LD_PRELOAD.
نعم، هذه المدونة مضللة.
عذرًا، هذه المدونة كانت غير موجهة. لقد فعلت كل هذه الحيل بنفسي! لا أحد خدعني لأكون بهذا الغباء.
يجب تنفيذ IFUNC بواسطة برنامج [العميل] نفسه،
@CountWSS 💯 نعم بالتأكيد
سلسلة من الإخفاقات الواضحة في العملية من مشرف Github خلال ...
هذه هي النقطة الوحيدة التي سأرد عليها بجدية:
أعتقد أنه من غير العدل بشكل استثنائي لمشرف xz-utils ومن الخطير جدًا للمجتمع التفكير في هذا الأمر على أنه يبدأ بخطأ منه. لقد بدأ بعدم اهتمام أحد بمساعدة صيانة هذا المشروع. اعتمد المهاجم على ifunc كـ ثغرة تقنية وإهمالنا الجماعي لـ xz-utils كـ ثغرة اجتماعية. أعتقد أنه من المخزي النظر إلى أفعال السيد كولين على أنها أي شيء آخر غير تفانٍ بطولي لسنوات في خدمة المجتمع.
أيضًا يتفق معي Bruce Schneier لذا... أكره ذلك من أجلك، حجتك انتهت.
ربما كانت اللغة أقسى مما يجب
لن تصدق كم جعلني أصدقائي أخفف من هذا أولاً.
يجب ألا تظن توزيعات Linux بنفسها كثيرًا لدرجة توقع أن تتوافق OpenBSD وتتكيف مع فوضاهم
@debazel!!! يعجبني.
يا له من هراء تام.
حسنًا، هذا الجزء دقيق.
لماذا يجب أن تتوقف عن إلقاء اللوم على xz-utils بسبب [CVE-2024-3094][nvd]. وأيضًا شاهد محادثة ETSA الخاصة بي!

CVE-2024-3094، المعروفة أكثر باسم "باب خلفي لـ xz-utils"، كانت قريبة من كارثة للأمن السيبراني العالمي. لو لم يتم اكتشاف هذا الهجوم في الوقت المناسب بواسطة [Andres Freund][freund]، لكانت معظم خوادم SSH على كوكبنا بدأت في منح الوصول كجذر للجهة المسؤولة عن هذا الهجوم.
لسوء الحظ، ركز الكثير من التحليل على كيفية دخول [الكود الخبيث][JiaT75] إلى مستودع xz-utils. بدلاً من ذلك، أود أن أجادل بأن قرارين تصميميين طويلي الأمد في برامج مفتوحة المصدر حاسمة هما ما جعل هذا الهجوم ممكنًا: [ربط OpenSSH بـ SystemD][biebl]، ووجود [GNU IFUNC][sourceware].
قبل أن تبدأ: جزء كبير من هذه المناقشة يتعامل مع تعقيدات الربط الديناميكي في Linux. إذا كنت بحاجة إلى مراجعة، اطلع على dynamic_linking.md.
هناك الكثير من المقالات الجيدة التي توضح التفاصيل عالية المستوى للباب الخلفي لـ xz-utils، مثل مقال دان جودين [ما نعرفه عن الباب الخلفي لـ xz Utils الذي كاد يصيب العالم][goodin1] ومقال سام جيمس [الأسئلة الشائعة حول الباب الخلفي لـ xz-utils (CVE-2024-3094)][thesamesam]. لسنا بحاجة لإعادة كل ذلك هنا، ولأغراض هذه المقالة، إليك ملخص تقريبي جدًا:
## لماذا تقوم توزيعات لينكس بتعديل 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
إصدار OpenSSH الخاص بـ OpenBSD هو المنبع من كل شيء آخر، ومعظم التحسينات التي تطرأ عليه تأتي من داخل مجتمع OpenBSD. تتدفق هذه التغييرات إلى أسفل إلى مشروع Portable OpenSSH، الذي يحاول إعادة تنفيذ الميزات الجديدة بطرق لا تكون خاصة بـ OpenBSD. وهذا ما يسمح لـ SSH بالعمل على منصات مثل Linux و macOS و FreeBSD وحتى Windows.
لكن الأمر لا يتوقف عند هذا الحد. بعض أنظمة التشغيل تطبق تخصيصات إضافية تتجاوز ما يوفره Portable OpenSSH. على سبيل المثال، تضيف Apple علامة [--apple-use-keychain][keith] إلى ssh-add لمساعدتها على التكامل مع مدير كلمات المرور في macOS.
في حالة CVE-2024-3094، حافظت Fedora و Debian على [تصحيحات SystemD خاصة بهما][biebl] لإصداراتهما من OpenSSH من أجل إصلاح [حالة سباق حول إعادة تشغيل 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]. ولم يتم دمجها أبدًا في upstream OpenSSH، لأن OpenBSD لا تحتاج إلى دعم SystemD.
### مخاوف حول "فصل الاهتمامات"
يبدو هذا غير ضار بما يكفي، لكنه مثال على مشكلة أكبر بكثير في المصادر المفتوحة، خاصة في لينكس: المكونات الحرجة لنظام التشغيل تُطوَّر بواسطة أشخاص لا يعرفون بعضهم البعض، ولا يتحدثون مع بعضهم البعض.
* هل عرف (أو اهتم) الأشخاص الذين قاموا بتصحيح OpenSSH لـ SystemD أن libsystemd يعتمد على xz-utils؟
* هل عرف (أو اهتم) فريق SystemD أن xz-utils بدأ في استخدام ifunc؟
* هل عرف (أو اهتم) فريق OpenSSH أن ifunc شيء؟ إنه بالتأكيد ليس شيئًا في OpenBSD.
بمعنى ما، هذا الانهيار في التواصل هو ميزة في المصادر المفتوحة: يمكنني تكييف عملك مع احتياجاتي دون الحاجة إلى إزعاجك بخصوص ذلك. لكنه قد يؤدي أيضًا إلى درجة من عدم المباشرة تمنع افتراضات التصميم الحرجة (مثل عملية الربط الديناميكي التقليدية) من أن يتم الحفاظ عليها.
النتيجة الطبيعية الواضحة لـ [قانون كونواي][conway] هي أنك إذا كنت تُصدر مخطط مؤسستك، فأنت أيضًا تُصدر الأخطاء التي تعيش في شقوق مخطط مؤسستك. لم يرتكب شخص واحد أو فريق واحد خطأً هنا حقًا، لكن مع فائدة النظر إلى الماضي، من الواضح أن المهاجمين أدركوا أن اليد اليسرى لـ SSH في دبيان/فيدورا لم تكن تعرف ما تفعله اليد اليمنى لـ xz-utils.
## ما *مفترض* أن يفعله GNU IFUNC؟
يسمح لك بتحديد، في وقت التشغيل، أي إصدار من دالة ما ترغب في استخدامه. يفعل ذلك من خلال إعطائك فرصة لتشغيل **كود عشوائي** للتأثير على كيفية حل الرابط للرموز.

### الكشف عن ميزات وحدة المعالجة المركزية
لنفترض أن لديك تطبيقًا يجب أن يعمل على مجموعة واسعة من معالجات x86. اعتمادًا على الميزات المحددة لوحدة المعالجة المركزية الحالية، قد تفضل استخدام خوارزميات مختلفة لنفس المهمة. كانت الفكرة الأصلية وراء IFUNC هي السماح للبرامج بالتحقق من ميزات وحدة المعالجة المركزية في المرة الأولى التي يتم فيها استدعاء دالة، وبعد ذلك استخدام تنفيذ سيكون الأكثر ملاءمة لتلك وحدة المعالجة المركزية.
ألقِ نظرة على [`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 بطباعة إما "AVX2 is present" أو "AVX2 is missing" اعتمادًا على مدى قدم وحدة المعالجة المركزية لديك.
بينما يُقصد بـ IFUNC فحص قدرات وحدة المعالجة المركزية، لا شيء يمنعك من تشغيل كود أكثر تعقيدًا في دوال الحل (resolvers) الخاصة بك. على سبيل المثال، يُظهر tty_demo.c كيفية تحميل تطبيق دالة مختلف اعتمادًا على ما إذا كان STDOUT ملفًا أم طرفية (terminal):```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، لكنه يُظهر ما هو ممكن: يمكنك تشغيل كود عشوائي قبل `main` في أي برنامج يستخدم IFUNC قمت بتعريفه.
## IFUNC فكرة سيئة على الأرجح

من الصعب تنفيذ IFUNC في GNU، ومن الصعب استخدامه بشكل صحيح، وكأداة أداء مفترضة، فهو ليس أسرع بكثير من البدائل. كما رأينا مع CVE-2024-3094، فهو أيضًا أداة قوية جدًا لهجمات سلسلة التوريد البرمجية.
يُستخدم IFUNC بشكل مكثف داخل مكتبة GNU C، وهذا ربما يكون مقبولًا. هؤلاء هم الأشخاص الذين طُوِّر من أجلهم أصلاً، وهم على اتصال وثيق بفرق المترجم والرابط الذين ينفذون IFUNC فعليًا. إنهم في أفضل وضع لفهم المقايضات، وهناك عدد هائل من دوال libc التي تستفيد من التطبيقات الخاصة بوحدة المعالجة المركزية. أعتقد أنه يجب علينا اعتبار IFUNC واجهة داخلية لـ glibc، وتجنب استخدامه في التطبيقات الأخرى.
### إنه مربك جدًا لاستخدامه بأمان
ifunc صعب الاستخدام للغاية. هناك [حالات طرفية](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/nagy) كثيرة جدًا، والـ [توثيق الرسمي](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/gnu-cfa) [ضئيل](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/sourceware). وهذا يعطي المستخدمين فكرة مضللة بأن اعتماد ifunc أمر بسيط.
حتى بعد عدة سنوات من توفر ifunc، فإن الواجهة المُعلن عنها [لم تكن تعمل](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/agner). وصفها مطورو GCC بأنها [خطأ](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/odonell) وفكروا في إضافة تحذيرات للتعويض عن هشاشة IFUNC:
> الحلول المطلوبة من glibc لجعل IFUNC قويًا ليست موجودة بعد، ولذا يجب أن نفعل ما في وسعنا لتحذير المستخدمين من أنه قد يتعطل.
الأمر لا يقتصر على IFUNC فحسب. Apple Mach-O لديها ميزة مشابهة تسمى `.symbol_resolver` والتي [يأسفون لإضافتها](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/rjmccall).
### إنه يُضعف RELRO
من خلال السماح بتشغيل كود عشوائي بينما لا يزال جدول الإزاحة العام قابلاً للكتابة، فإن الحماية التي يوفرها [RELRO](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/dynamic_linking.md#relro) تصبح [عديمة الجدوى](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/binarly-io).
من المهم ملاحظة ذلك، لأن RELRO يُعلن عن نفسه كوسيلة لحماية سلامة الرموز المحملة ديناميكيًا. من منظور المستخدم (أنت، كمستخدم للمترجم والرابط)، فإن هذا ينتهك [مبدأ الأقل دهشة](https://github.com/robertdfrench/ifuncd-up/blob/HEAD/pola): لا يتوقع أي شخص عاقل أن *تحميل مكتبة ديناميكية* يجب أن يعرض للخطر ميزة أمان مصممة *لحماية المكتبات الديناميكية*.

### ليس ضروريًا دائمًا
هناك طرق متعددة أخرى للتعامل مع هذا الموقف. لكل منها مقايضات مختلفة، لكنها جميعًا أبسط بكثير من IFUNC. جميعها أكثر قابلية للنقل من IFUNC، وأسهل في الفهم، وأصعب في الاستغلال.
> "ifunc هو مجرد طريقة غبية تمامًا للقيام باختيار كود خاص بالهندسة الدقيقة في وقت التشغيل."
>
> -- [ريتش فيلكر](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;
}
هل هذا سيء حقًا لدرجة أننا بحاجة إلى حيل خاصة في الرابط (linker) فقط لتجنبه؟
أحد عيوب هذا النهج هو أن مؤشر الدالة triple قابل للكتابة في وقت التشغيل، بينما يضمن IFUNC+RELRO أن عناوين ifunc في GOT غير قابلة للتغيير بمجرد حلها. ومع ذلك، مع القليل من الجهد الإضافي، يمكننا استخدام [mprotect(2)][mprotect] لوضع علامة على هذه المؤشرات على أنها للقراءة فقط.
LD_PRELOADإذا كنت تعرف ميزات وحدة المعالجة المركزية التي يحتاجها كودك، وكان لديك نسخة منفصلة من مكتبتك الديناميكية لكل حالة، فيمكنك تحقيق نفس الشيء عن طريق تحديد المكتبة الصحيحة باستخدام $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`، فراجع ["A Simple `LD_PRELOAD` Tutorial"][catonmat] من catonmat.)
#### ثنائيات منفصلة لكل مجموعة ميزات
كم عدد مجموعات ميزات وحدة المعالجة المركزية الفريدة التي تحتاج حقًا لدعمها؟ كم منها موجود حتى؟
للوهلة الأولى، يبدو هذا كانفجار اندماجي. هناك عشرات الإضافات المختلفة للحساب المتجهي، والمحاكاة الافتراضية، والأمان لـ ISA amd64. لكن هذه الميزات لا تحدث بشكل مستقل في الواقع. على سبيل المثال، لا توجد وحدة معالجة مركزية تحتوي على AVX-512 وتفتقر إلى SSE4.2 أو AES-NI.
معرفة ميزات وحدة المعالجة المركزية التي يحتاجها تطبيقك، وأي منها يحدث معًا على الرقائق الحقيقية، يمكن أن يساعدك في تحديد عدد الثنائيات المميزة التي ستحتاج إلى شحنها. قد لا يكون العدد بقدر ما تتوقع. تسمح لك معظم مديري الحزم بتشغيل البرامج النصية في وقت التثبيت؛ يمكنك شحن ثنائيات متعددة في ملف rpm أو deb واحد واستخدام منطق وقت التثبيت لاختيار الأفضل لوحدة المعالجة المركزية للمضيف.
### إنها ليست أسرع بكثير من البدائل
> ما كان واضحًا لي لفترة طويلة هو أنه حتى لو كانت هناك ميزة أداء لـ 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. استدعاء وظيفة محدد لتحديد أي مُزِيد يجب استخدامه.
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` يجب أن يكون بمثابة خط أساس للمقارنة.
| *النتائج* | الأدنى | الأعلى | المتوسط |
|-----------|--------|--------|-----------|
| fixed | 2.93 | 4.20 | 3.477 |
| ifunc | 9.50 | 10.56 | 9.986 |
| pointer | 6.23 | 7.44 | 6.791 |
ما نراه هنا هو أن ifunc له حمل زائد ليس بالقليل مقارنة باستخدام مؤشر وظيفة قديم عادي. في المتوسط، على أجهزتي، يستغرق استدعاء وظيفة ifunc ملياري مرة حوالي ضعف الوقت الذي يستغرقه استدعاء مؤشر وظيفة ملياري مرة.
هل هذا مهم في الحياة الواقعية؟ بالتأكيد لا. الوظائف التي تستحق التحسين أغلى بكثير من وظائف "الزيادة بمقدار واحد" التي نحللها هنا. إنه أمر مثير للاهتمام فقط لأن 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`، إلا أنه يخزن نتائج فحوصات ميزات وحدة المعالجة المركزية في متغيرات عامة بدلاً من تتبع مؤشر وظيفة. لا يزال هذا يتطلب تشغيل وظيفة "محلل" أولاً لتحديد أي تطبيق سيتم استخدامه، بناءً على قيمة هذه المتغيرات العامة. تبين أن هذه التقنية أبطأ من ifunc، ولكنها أيضًا أكثر أمانًا من تخزين مؤشرات الوظائف: حيث يمكن تعيين مؤشرات الوظائف لقيم عشوائية، بينما لا يمكن ذلك للأعلام المنطقية. لذلك يمكن للمهاجم القادر على تعديل هذه المتغيرات أن يجعل البرنامج *أبطأ*، لكنه لا يمكنه جعل البرنامج يتصرف *بشكل مختلف*.
`speed_demo/always` مصمم ليكون أبطأ تقنية - فهو يفحص جميع ميزات وحدة المعالجة المركزية الضرورية في كل مرة تكون هناك حاجة إلى تطبيق ويختار واحدًا أثناء التشغيل. ومن الغريب أن هذه التقنية ليست أبطأ بشكل كبير من أي شيء آخر. إنها أبطأ بشكل طفيف فقط من ifunc في الحالة التي يكون لدينا فيها ميزة وحدة معالجة مركزية واحدة فقط للفحص.
| الاختبار | الأدنى | الأعلى | المتوسط |
|----------|--------|--------|-------------|
| 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 حقيقية، لكنها ليست أفضل بشكل ملحوظ من البدائل. بساطة نشر ثنائي واحد محسّن لعدة وحدات معالجة مركزية جذابة جدًا، لكن يمكن تحقيقها بتقنيات أبسط (مثل مؤشرات الوظائف).
أعتقد أنه يجب تعطيل 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