מחקו את התוכנית, והמעבד המשיך לקפוץ לכתובת הישנה: כך חולצה סיסמת root ממערכת לינוקס מעודכנת
מכונת לינוקס מעודכנת, מעבד Intel מודרני, וכל הגנות Spectre דלוקות כברירת מחדל. מריצים עליה su root, מה שטוען את ה-hash של סיסמת root לזיכרון. ואז תוכנית רגילה לגמרי, בלי שום הרשאה מיוחדת, מתחילה לדלות בייטים מהזיכרון של הקרנל בקצב של שמונה בשנייה. היא הולכת אחורה על רשימת התהליכים, מוצאת את התהליך הנכון לפי מספר ה-PID, עוברת על טבלאות הדפים שלו, ומוציאה משם את ה-hash. הכול תוך דקות.
את ההדגמה הזאת פרסמו חוקרי VUSec מהאוניברסיטה החופשית באמסטרדם, יחד עם Scuola Superiore Sant'Anna האיטלקית. את המחקר חתמו Sander Wiebing, Zhu Yuhui, Alessandro Biondi ו-Cristiano Giuffrida, והמאמר התקבל לכנס CCS 2026. הם קוראים למתקפה Branch Target Reuse, בקיצור BTR, ומתארים אותה כ"דוגמה הראשונה למתקפת Spectre-v2 מעשית מסוג in-place". בהמשך נראה למה דווקא מהדרי JIT הם המקום שבו זה עובד, איך נראית ההתקפה שלב אחרי שלב, ומה כבר תוקן.
למה מהדר JIT הוא בדיוק המקום הרגיש
רוב התוכנות שאתם מריצים הן קוד מכונה שנכתב פעם אחת והתקמפל מראש. מהדר JIT עובד אחרת: הוא כותב קוד מכונה חדש תוך כדי ריצה, לתוך אזור זיכרון שמותר גם לכתוב אליו וגם להריץ ממנו. כשקטע קוד כבר לא נחוץ, האזור משוחרר, ובהמשך נכתב לשם קוד אחר לגמרי. זה מה שקורה בכל דפדפן כשג'אווהסקריפט "חם" מתקמפל, בכל מכונה וירטואלית של שפה, וגם בתוך הקרנל של לינוקס.
כאן מתחילה הבעיה. כשקוד משתנה בזמן ריצה, המעבד יודע לוודא שהקוד שירוץ בפועל הוא החדש. מה שהוא לא בהכרח עושה זה לנקות את הניחושים ששמר לעצמו לגבי הקוד הישן. מעבדים מודרניים שומרים טבלה של יעדי קפיצה, שבה הם זוכרים לאן קפיצה עקיפה מסוימת הובילה בפעמים הקודמות, כדי לנחש מראש ולהתחיל להריץ לשם עוד לפני שהיעד האמיתי מחושב. הטבלה הזאת נקראת Branch Target Buffer, או BTB.
הרשומה הישנה ב-BTB שורדת את מחיקת הקוד שיצר אותה. אם המהדר שחרר אזור והקצה במקומו אזור חדש באותה כתובת, הרשומה עכשיו מצביעה לאמצע קוד אחר לגמרי, שנכתב אחריה. החוקרים קוראים לפרימיטיב הזה speculative execute-after-free: המעבד מריץ ספקולטיבית קוד שהתחיל בכתובת של משהו שכבר לא קיים.
ההתקפה, שלב אחרי שלב
הרצף שהחוקרים מתארים קצר. התוקף גורם למהדר ה-JIT להקצות אזור קוד חדש, שנקרא במאמר training chunk, ומאמן עליו את הקפיצה העקיפה בכך שהוא קופץ אליו שוב ושוב. אחר כך הוא גורם לשחרור אותו אזור, ולהקצאה של אזור חדש שתופס חלקית את אותה כתובת. עכשיו הוא מפעיל שוב את אותה קפיצה עקיפה. המעבד מושך את הרשומה הישנה מה-BTB, קופץ ספקולטיבית לנקודת הכניסה של האזור הישן, ומריץ משם את מה שהתוקף דאג שיהיה כתוב.
הקפיצה הספקולטיבית מבוטלת בסוף, כמו בכל מתקפת Spectre. מה שלא מתבטל זה העקבות שהיא משאירה במטמון, ומהן מחלצים את המידע.
מה יש דווקא ב-cBPF שהפך אותו למטרה
כדי לסנן חבילות רשת או קריאות מערכת, לינוקס מריץ תוכניות זעירות שהמשתמש מספק, ומקמפל אותן ל-JIT בתוך הקרנל. הגרסה החזקה, eBPF, מוגבלת כבר מזמן למשתמשים עם הרשאות. הגרסה הישנה, classic BPF או cBPF, עדיין פתוחה לכל תוכנית. הסיבה היא שהיא זעירה בכוונה: שני רגיסטרים של 32 ביט, רק קפיצות קדימה, ואיסור על גישה לזיכרון דרך רגיסטר. המגבלות האלה הן בדיוק מה שגרם לחשוב שהיא בטוחה מספיק, והיא משמשת היום ב-Linux Socket Filtering, במסנני seccomp, ובנתיבי סינון החבילות של תוכנות כמו Docker ו-Chrome.
ב-BTR המגבלות האלה לא מספיקות. החוקרים מתקינים שתי תוכניות cBPF כמסנני seccomp, האחת כאזור האימון והשנייה כאזור היעד באותו מקום בזיכרון. הגאדג'ט שמדליף את המידע לא כתוב כפקודות, אלא מקודד בתוך ערכי הקבועים בני ארבעת הבייטים של תוכנית היעד. כשהמעבד קופץ לכתובת הישנה, הוא נוחת בהיסט לא מיושר באמצע הבייטים האלה, ומפרסר אותם כפקודות אחרות לגמרי מאלה שהמהדר התכוון להן.
הקצב, שמונה בייטים בשנייה, נשמע זניח. הוא לא, כי אין צורך לסרוק את כל הזיכרון: החוקרים רודפים מצביעים. הם מטיילים אחורה על רשימת התהליכים של הקרנל דרך שדה ה-prev של כל רשומה, בודקים בכל שלב אם ה-PID הוא של הקורבן, ומשם מגיעים למבנה הזיכרון של התהליך ולטבלאות הדפים שלו. רק הדף הנכון מדולף במלואו.
ללינוקס יש הקשחה לעניין הזה, bpf_jit_harden, שמערבלת את ערכי הקבועים כדי שלא יהיה אפשר להחביא בהם פקודות. היא כבויה כברירת מחדל, והחוקרים עקפו אותה בכל זאת: במקום להחביא בייטים בקבועים, הם החביאו אותם בהיסטים של קפיצות קדימה, לפי טכניקה שפרסמו Maisuradze ועמיתיו ב-2016. אותו רצף בייטים נקרא בצורה אחת כשרשרת קפיצות תקינה, ובצורה אחרת לגמרי כשנכנסים אליו שני בייטים מאוחר יותר.
הדפדפן והסנדבוקס: שני מקרים שלא הגיעו לסוף הדרך
SpiderMonkey, מנוע הג'אווהסקריפט של Firefox, שומר קבועים של WebAssembly בתוך אותו אזור זיכרון שממנו מריצים. קפיצה ספקולטיבית לשם נותנת לתוקף להריץ את מה שהחביא שם. החוקרים בנו PoC, ראו שעל מעבדי Intel רשומות ה-BTB אכן שורדות מחזור שלם של שחרור והקצאה מחדש, ומעריכים קצב דליפה של עשרות בייטים בשנייה. הם מדגישים שמתקפה שלמה מקצה לקצה בדפדפן דורשת עוד עבודה. מה שכן, הם מציינים שכיוון ש-Firefox עדיין לא פרס במלואו את ההפרדה בין אתרים לתהליכים נפרדים, לשוניות אחרות עלולות לשבת באותו מרחב כתובות.
ב-GraalVM, סביבת הריצה של אורקל, המצב הפוך. הסנדבוקס שלה במצב המחמיר ממסך כל גישה לזיכרון מצד הקוד האורח, כדי שלא יוכל לצאת מהאזור שהוקצה לו. החוקרים השתמשו ב-GraalPy כדי לייצר פונקציות שבהן פעולת המיסוך יושבת בדיוק לפני הגישה למערך, וקפצו ספקולטיבית מעליה ישר אל הגישה עצמה. הקריאה יוצאת מהאזור. אבל פעילות הקימפול ואיסוף הזבל של המנוע עצמו מוחקת את רשומות ה-BTB לפני שמספיקים לנצל אותן, והחוקרים כותבים שהמגבלה הזאת "לא נראית עקרונית".
מה כבר תוקן, ומה נשאר פתוח
יצרני החומרה אישרו את הממצאים, ולפי החוקרים השיבו שמנגנוני ההגנה הדרושים כבר קיימים, למשל IBPB שמנקה את מנבא הענפים, ושהתיקון ל-BTR צריך להיעשות בתוכנה. מפתחי הקרנל של לינוקס הכניסו הקשחה שמבצעת ניקוי כזה בכל הליבות כשתוכנית cBPF משתמשת מחדש באזור שכבר הורצה בו תוכנית קודמת, ומרתיעה מהשימוש החוזר הזה כאופטימיזציה. ההקשחה פועלת בין אם IBT דלוק ובין אם לא. שתי הרשומות שנפתחו לה הן CVE-2026-64507 ו-CVE-2026-64508, ושתיהן פורסמו ב-NVD כבר ב-25 ביולי. כלומר התיקון היה בקרנל חודשיים לפני שהמחקר יצא לאוויר.
אורקל בחרה בדרך אחרת. במקום לנקות את המנבא, היא הגרילה את כתובות מטמון הקוד כדי שאזור משוחרר לא ייפול שוב באותו מקום. השינוי מוזג לענף הראשי של GraalVM ב-19 באוגוסט. מוזילה, לפי החוקרים, שקלה הגנה מבוססת IBPB אך מעדיפה כרגע להשלים את הפרדת האתרים לתהליכים ב-Firefox.
לגבי ההגנות שכבר קיימות בחומרה, התשובה מורכבת. IBT במעבדי x86 ו-BTI ב-Arm דורשים שיעד של קפיצה עקיפה יתחיל בפקודת סימון מיוחדת, וזה אכן מעלה את הרף. אלא שבמעבדי Intel ישנים יותר, פקודה או שתיים עדיין מספיקות כדי לרוץ ספקולטיבית לפני שהבדיקה הזאת נאכפת. לפי החוקרים, Lion Cove הוא הדור הראשון של Intel שבו מצאו שהמרוץ הזה סגור. גם שם, הם מראים, אפשר להחביא בקוד שנוצר ב-JIT בייטים שנקראים כפקודת הסימון עצמה, ולהפוך אותם לנקודת נחיתה חוקית, אם כי זה הצליח להם רק כשערבול הקבועים כבוי. לכן, הם כותבים, IBT שבו המרוץ הזה סגור יחד עם ערבול קבועים הוא הגנה חזקה בהרבה.
ובאשר לשאלה מי חשוף, התשובה שלהם קצרה: כל מעבד שבדקו, של Intel, של AMD ושל Arm, מתנהג ככה. אף מעבד קיים לא מסנכרן בין מנבא הענפים לבין מצב הקוד בפועל.
מה זה אומר בפועל
מי שמריץ לינוקס על שרתים משותפים, ובמיוחד מי שמאפשר לקוד לא מהימן לרוץ על אותה מכונה, צריך לוודא שהקרנל מעודכן לגרסה שכוללת את שתי הרשומות מיולי. מעבר לזה כדאי לבדוק את הערך של bpf_jit_harden אצלכם. הוא כבוי כברירת מחדל, והחוקרים אמנם עקפו אותו בניצול של cBPF, אבל הוא גם מה שמנע מהם לזייף נקודות נחיתה חוקיות מול IBT. בדור מעבד שבו המרוץ הזה סגור, שני אלה יחד הם ההגנה החזקה ביותר שהם מתארים. מי שמריץ קוד אורח בסנדבוקס של GraalVM צריך גרסה שכוללת את השינוי מאוגוסט.
למשתמשי Firefox, לעומת זאת, אין כרגע מה לעשות חוץ מלהמשיך לעדכן. ההגנה שמוזילה מהמרת עליה, הפרדה מלאה של אתרים לתהליכים נפרדים, עדיין בדרך.
תגובות