פלטפורמה שמריצה קוד של לקוחות, בלי לדעת מה הקוד הזה מכיל, מסתמכת על הבטחה אחת: שמה שהלקוח העלה לא יכול לגעת בשום דבר מחוץ לזיכרון שהוקצה לו. Wasmtime, מנוע ההרצה של WebAssembly שמפותח תחת Bytecode Alliance, הוא אחד הכלים שאמורים לקיים את ההבטחה הזאת. הוא רץ גם ככלי שורת פקודה עצמאי וגם, בעיקר, כספרייה בתוך יישומים אחרים.

ב-19 באוקטובר פורסמו שתי התראות אבטחה של הפרויקט שאומרות שההבטחה הזאת לא תמיד מחזיקה. CVE-2026-115889 ו-CVE-2026-115893, שתיהן בציון CVSS 9.9, שתיהן ב-Cranelift (רכיב הקימפול שמתרגם את ה-module לקוד מכונה), ושתיהן מאפשרות ל-module שנכתב במיוחד לצאת מארגז החול ולהריץ קוד על המכונה המאחסנת אותו. את הראשונה דיווח חוקר בשם jaydns, את השנייה ilyas-mallah, ושתיהן פורסמו על ידי pchickey מצוות הפרויקט. אין בהתראות אזכור של ניצול בפועל.

נעבור על שתיהן בנפרד, כי המנגנונים שונים לגמרי, ואחר כך נראה למה דווקא ARM64 מקבל כאן מכה שנייה בתוך חצי שנה.

מה בעצם מחזיק את הגבול

כדאי להבין קודם איפה עובר הקיר. ל-module של WebAssembly אין גישה ישירה לזיכרון של התהליך שמריץ אותו: הוא מצהיר על אזור זיכרון משלו, וכל קריאה או כתיבה שלו חייבת ליפול בתוך האזור הזה. מה שאוכף את זה הוא לא שומר שרץ בצד ומסתכל על ההרצה, אלא בדיקות גבולות שרכיב הקימפול עצמו שותל בתוך קוד המכונה שהוא מייצר, בהנחה שהקוד שנוצר אכן עושה את מה שה-module ביקש. כאן נמצאת הנקודה העדינה: אם רכיב הקימפול מייצר קוד שגוי, אין שכבה נוספת שתתפוס את התוצאה. הגבול לא נפרץ, הוא פשוט לא נבנה נכון מלכתחילה.

זה גם מה שמסביר את הציונים. וקטור ה-CVSS של שתי ההתראות כולל S:C, כלומר ההשפעה חורגת מהרכיב הפגוע אל המכונה שמריצה אותו, ו-PR:L, כלומר נדרשת הרשאה מינימלית בלבד. מי שיכול להעלות module ולגרום לפלטפורמה לקמפל אותו עומד בתנאי.

השדה שהיה צר מדי

במהלך הקימפול, Cranelift נותן לכל ערך ביניים בקוד מספר סידורי, ושומר את המספר הזה בתוך מבנה דחוס יחד עם סוג הערך, כדי לחסוך זיכרון. עד גרסה 43 כל מספר כזה קיבל 24 ביט. ב-build של production גלישה של המספר הזה לא מייצרת שגיאה ולא עוצרת את הקימפול, היא פשוט עוטפת בשקט. התוצאה היא ש-Cranelift מתייחס לערך אחר מזה שהתכוון אליו, ומייצר קוד מכונה שגוי. לפי ההתראה, פונקציית Wasm גדולה מאוד, או פונקציה שנבנתה בכוונה כך שתשתמש בהמון ערכי ביניים, יכולה להביא לבלבול טיפוסים (type confusion, מצב שבו הקוד מתייחס לנתון מסוג אחד כאילו הוא מסוג אחר) ומשם לבריחה מארגז החול. ההתראה מסמנת את החולשה גם כ-CWE-197, קטיעה מספרית, וגם כ-CWE-843.

החלק המעניין הוא מתי זה תוקן. השינוי שסוגר את הבעיה נכתב על ידי Chris Fallin, הרחיב כל אחד מהשדות האלה מ-24 ל-32 ביט, מוזג ב-18 בפברואר והגיע לגרסה 43.0.0 בחודש הבא. בתיאור שלו אין מילה על אבטחה: הנימוק שמופיע שם הוא הסרת המגבלה האחרונה על גודל פונקציה, ועוד 3 עד 5 אחוזי שיפור בזמן הקימפול. רק באוקטובר הוא הועבר אחורה לקו התמיכה הארוכה של גרסה 36 ופורסם כתיקון אבטחה. (מי שעדכן ל-43 במרץ היה מוגן מאז בלי לדעת.)

אז למה הסתעפות אחת לא מגיעה ליעד?

החולשה השנייה נוגעת לפקודה אחת בלבד. ל-WebAssembly יש פקודה שמקבלת מספר ומעבירה את ההרצה לאחד מתוך רשימת יעדים, ואם המספר חורג מגודל הרשימה היא מעבירה אותה ליעד ברירת מחדל. זה המימוש של switch, והשם שלה הוא br_table. על ARM64, Cranelift מייצר עבורה רצף קבוע: הסתעפות מותנית שמטפלת במקרה החורג, ומיד אחריה הטבלה עצמה, שבה כל יעד תופס ארבעה בייט.

וכאן הבעיה היא מרחק, לא לוגיקה. הסתעפות מותנית על ARM64 מגיעה עד כמיליון בייט (±1MiB) מהמקום שבו היא יושבת, והטבלה נמצאת בדרך. לפי תיאור התיקון, טבלה גדולה מספיק דוחפת את יעד ברירת המחדל מעבר לטווח הזה, וההיסט עוטף בשקט במקום להיכשל. ההסתעפות נוחתת על יעד אחר. ההתראה מעמידה את הגודל הדרוש על יותר מכ-256 אלף יעדים בפקודת br_table אחת, ואת התוצאה היא מנסחת ישירות: מנוע Wasmtime על ARM64 בהגדרות ברירת מחדל יכול להיאלץ לקפוץ לקוד שרירותי בגלל module שהמשתמש ספק.

התיקון, שנכתב גם הוא על ידי Fallin ומוזג ב-19 באוקטובר, מעביר גם את מקרה ברירת המחדל דרך טבלת ההיסטים, ומחליף את ההסתעפות המותנית בבחירה מותנית שמצמידה אינדקס חורג לרשומה האחרונה בטבלה, זו שמכילה את יעד ברירת המחדל. הבאג עצמו לא חדש: לפי ההתראה הוא קיים בכל גרסה שתומכת ב-ARM64 מאז התמיכה הראשונה בארכיטקטורה ב-2020, עוד לפני גרסה 1.0. אין כאן טריק מתוחכם. יש כאן מרחק.

אותה משפחה של באגים, והפעם השנייה דווקא על ARM

זאת לא החולשה הראשונה מהסוג הזה ב-Cranelift על ARM64 בשנה הזאת. ב-9 באפריל פורסמה CVE-2026-34971 בציון 9.0, ושם דפוס אחד של גישה לזיכרון קומפל שגוי: Cranelift מיסך לא נכון קבוע בתוך חישוב הכתובת, וכך הכתובת שעליה רצה בדיקת הגבולות לא הייתה הכתובת שנקראה בפועל. לא צריך נוזקה בשביל זה, מספיק module שעובר את הבדיקה וניגש למקום אחר לגמרי, מה שההתראה מתארת כקריאה וכתיבה שרירותיות בזיכרון של המכונה המאחסנת. את החולשה דיווח חוקר בשם shumbo, לצד קרדיט ל-bholley ול-deian.

ההבדל בין אפריל לאוקטובר הוא מה שחשוב כאן. החולשה של אפריל דרשה הגדרות שאינן ברירת המחדל, בין היתר השבתה של הגנות Spectre או של מלכודות מבוססות סיגנלים, וזה מה ששמר על רוב המאחסנים. שתי החולשות של אוקטובר לא דורשות שום דבר כזה.

שלוש ההתראות האלה הן מאותה משפחה, והמשפחה הזאת לא דומה לרוב מה שמופיע ברשימות חולשות. אין כאן בדיקה שנשכחה בקוד של המנוע, ואין הרשאה שניתנה בטעות. יש שגיאה בייצור קוד המכונה, במקום שבו הבידוד נוצר בפועל, ולכן באג נכונות ברכיב הקימפול הוא גם באג אבטחה. זה גם מה שהופך את המעקב על חולשות כאלה למשימה לא פשוטה: השינוי שסגר את CVE-2026-115889 נראה, בפברואר, כמו עבודת תחזוקה שמסירה מגבלה על גודל פונקציה ומשפרת זמני קימפול, והקשר שלו לאבטחה נוסח רק בדיעבד, שמונה חודשים אחר כך.

בפועל זה אומר ששטח התקיפה של פלטפורמה שמריצה Wasm של צד שלישי כולל גם את כל שלב הקימפול, ולא רק את ה-API שהמנוע חושף ל-module. מי שבונה מודל איומים סביב WebAssembly כדאי שיכלול בו את העובדה שהקיר והבנאי של הקיר הם אותו רכיב.

מי צריך לעדכן, ומה לא מספיק

גרסה 36.0.19, שיצאה ב-19 באוקטובר, מתקנת את שתי החולשות, ובדרך גם בעיה שלישית: guest ב-WASI-HTTP שהיה יכול להוסיף להודעה שני אורכי תוכן סותרים. בקווים החדשים יותר התיקונים הם 48.0.7, 49.0.4 וכל גרסה מ-50.0.0 והלאה. מי שנמצא על 43.0.0 עד 47.x חשוף רק לחולשת ה-br_table, כי תיקון השדות הדחוסים נכנס כבר ב-43.0.0 עצמה.

ויש כאן מלכודת שכדאי להכיר: Wasmtime הוא בראש ובראשונה ספרייה. עדכון של כלי שורת הפקודה לא משנה דבר ביישום שמטמיע את המנוע בתוכו, וכל יישום כזה צריך להיבנות מחדש מול גרסה מתוקנת. מדיניות הגרסאות של הפרויקט קובעת שכל גרסה שמתחלקת ב-12 היא גרסת תמיכה ארוכה שנתמכת 24 חודשים, ושתיקוני אבטחה מובטחים לכל הגרסאות הנתמכות. התיקון היחיד שפורסם לכל מה שלפני 37.0.0 הוא 36.0.19, כך שמי שעוד יושב על קו תמיכה ארוכה ישן יותר לא מקבל תיקון בכלל.

מה זה אומר בפועל

אם אתם מריצים קוד של צד שלישי בתוך Wasmtime, ובמיוחד על שרתי ARM64, זה עדכון שלא כדאי לדחות: אף אחת מהשתיים לא דורשת הגדרה חריגה, והיכולת להעלות module היא כל מה שצריך בשביל להתחיל. בדקו לא רק את הגרסה של כלי שורת הפקודה אלא את הגרסה שאיתה נבנו השירותים שלכם בפועל, כולל תלויות עקיפות שגוררות את המנוע פנימה. ואם אתם צורכים פלטפורמה חיצונית שמריצה Wasm שלכם או של אחרים, השאלה לספק היא פשוטה: מול איזו גרסה היא נבנתה מחדש, ומתי.