WEBA

באג עתיק ב-Rust מאפשר לעקוף את מנגנון הזיכרון הבטוח

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

486 מילים14 עובדות אומתו

הבטחת הדגל של שפת Rust נשענת על עיקרון פשוט: אם לא כתבתם את המילה המפורשת unsafe, המהדר לא יאפשר לכם להגיע לקריסות זיכרון או לקרוא מכתובות שנמחקו. השבוע ההבטחה הזו ספגה תזכורת כואבת למציאות ההנדסית. דיווח תקלה חדש שנפתח ב-GitHub תחת מספר 164250 מציג שילוב תחבירי שמתקמפל ללא שום התרעה, אך מייצר בפועל שגיאת use-after-scope וקריסת זיכרון ישירה. צוות השפה סיווג את הדיווח בעדיפות P-high תחת קטגוריית I-unsound, מה שגרר שרשראות תגובות ארוכות ברשת. אבל עוד לפני שאתם מוחקים קונטיינרים, שווה להבין מה באמת קורה שם בפנים.

סדק בהבטחה הבסיסית

הדיווח שנפתח על ידי המפתח jsnell מציג קוד קצר להפליא. הוא יוצר מבנה נתונים עם דרישת זמן חיים של static, מממש עבורו את הטרייט Drop, ולאחר מכן משים לתוכו משתנה מקומי רגיל שחי בתוך בלוק תחום. המהדר אמור לזהות שההפניה המקומית נעלמת לפני שהמבנה משתחרר, ולחסום את הבנייה עם שגיאת השאלה שגרתית. במקום זה, המהדר מאשר את הקוד, מאפשר למבנה לקרוא לנתון בתוך שחרור הזיכרון, ומדפיס זבל ישירות מהמחסנית. בדיקה בסיסית באמצעות כלי AddressSanitizer בשרת ההידור Compiler Explorer איששה מיד כי מדובר בקריאת זיכרון שכבר אינו תקף. הבעיה מחייבת שילוב ספציפי של Drop עם השמה לשדה covariant כדי לעקוף את דרישות ה-lifetime של המבנה.

זה עובד בדיוק הפוך מהמצופה.

איך זה עובד בפועל

הכשל הטכני נובע מהצטלבות בין שני מנגנונים מרכזיים במערכת הטיפוסים: תכונת ה-variance ומנגנון השחרור האוטומטי Destructors. כאשר מגדירים שדה בעל התנהגות covariant בתוך מבנה נתונים, המהדר מניח שאפשר לקצר את זמן החיים של הערך המוזן לתוכו. עם זאת, כאשר ממומש Drop עבור אותו מבנה, מנגנון הבדיקה שוכח לאכוף את מגבלת ה-lifetime המקורית של הטיפוס בזמן ביצוע ההשמה לשדה הבודד. חברי צוות המהדר שחקרו את הנושא גילו גרסאות דומות שאינן דורשות אפילו את מגבלת static, אלא רק יחסי הכלה בין זמני חיים שונים.

My version of the code has been compiling ever since 1.18.0 (2017) How on earth was this not found before this???theemathas, תגובה בדיווח התקלה

ניתוח מעמיק יותר שביצע המפתח lcnr העלה שהבאג מתקמפל ומייצר בעיות תקינות זיכרון עוד מגרסה 1.4 של השפה, ששוחררה בסוף 2015. המשמעות היא שהפרצה הזו ישבה בשקט בתוך מהדר השפה במשך 11 שנה, לאורך החלפת מנגנוני ה-borrow checker השונים ואינספור שדרוגים של מערכת הטיפוסים.

תיאוריה מול פרודקשן

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

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

בפועל, אין סיבה להיבהל.

המחיר של התיקון

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

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

מקורות

הידיעה נכתבה מהמקורות שלמעלה בתאריך הפרסום, וכל טענה בה נבדקה מולם. מצאתם טעות? כתבו.

מה שואלים על זה

האם צריך לשדרג גרסה מיד או לשנות הגדרות בפרויקט?

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

האם קוד שנכתב ללא unsafe עדיין יכול לקרוס עם segfault בגלל הבאג?

באופן תיאורטי כן. הבאג מאפשר לשבור את הנחות זמני החיים בקוד safe לחלוטין, אך זה מחייב תבנית נדירה מאוד של השמה ישירה לשדה covariant בתוך מבנה שמממש Drop.

ממתי הבאג הזה קיים במהדר של Rust?

התקלה אותרה רטרואקטיבית בכל הגרסאות של המהדר החל מגרסה 1.4 שיצאה בשנת 2015, ומתקיימת גם בגרסאות ה-nightly העדכניות ביותר.