RAND_bytes נכשל ב-OpenVPN: שגיאת ספרייה או צומת חסר
כשל של RAND_bytes יכול להעיד על מקור אנטרופיה שנדחה או על צומת שהוסתר על ידי כלא. קראו את תור השגיאות ואת תצוגת התהליך לפני יצירת התקנים.
RAND_bytes() failed הוא הצהרה על פרימיטיב מקומי אחד, לא על החשבון, על המפתח המשותף מראש או על הפורט המרוחק. שני כשלים שונים מאוד מייצרים את אותה שורה ומשאירים עקבות שונות. האחד הוא backend קריפטוגרפי שמסרב לפלוט בתים; האחר הוא תהליך שאינו מגיע למקור האנטרופיה שלשמו נבנה הקוד שלו. כדי להפריד ביניהם דרושים קריאה אחת של תור השגיאות ומבט אחד בתצוגת מערכת הקבצים של התהליך הכלוא, ובדיקה זו צריכה לבוא לפני כל דבר שנוצר תחת /dev.
מה ערך ההחזרה מבדיל
החוזה המתועד מבדיל שלוש תוצאות, לא שתיים: 1 להצלחה, -1 כאשר שיטת ה-RAND הטעונה אינה תומכת בבקשה, ו-0 לכשלים אחרים, כשההסבר נשאר בתור השגיאות. ההפניה ל-RAND_bytes מוסיפה נקודה שקל לפספס: המחולל המוגדר כברירת מחדל מקבל זרע ממערכת ההפעלה בפלטפורמות נתמכות, וכאשר זריעה זו נכשלת הוא נשאר במצב שגיאה במקום להחזיר בתים צפויים.
רק אחד מהמספרים האלה מתאר את הסביבה. 1- הוא שאלה של בנייה, ספק או תכונה, משום שהקוד ביקש משהו שהמימוש הפעיל אינו מציע. 0 עם תלונה על זריעה הוא שאלת סביבה, משום שהמחולל כלל לא הצליח להשיג אנטרופיה. המספר לבדו לעולם אינו אומר איזה, וגם שם הפונקציה לא.
כאשר צומת התקן מוסתר הוא הסיבה האמיתית
שירות מוגבל רואה עץ אחר מאשר ה-shell שאתם מקלידים בו. כלאי chroot, תמונות מכולה וקובצי unit מוקשחים מציגים לעיתים קרובות /dev שמכיל פחות צמתים מזה של המארח. אם המימוש הכושל פותח את /dev/urandom לפי שם נתיב, ההבדל הזה מספיק כדי לשבור את האתחול בזמן שהמארח עצמו נראה בריא לחלוטין. התקן שקיים מחוץ לכלא אינו מוכיח דבר לגבי מה שמותר לתהליך הכלוא לפתוח.
לא כל ממשק אקראי זקוק לנתיב, עם זאת. קריאת המערכת getrandom של Linux אינה מקבלת שם קובץ ואינה מקבלת מתאר קובץ, ועמוד המדריך שלה מציין שתכונה זו שימושית בדיוק במקום שבו chroot מסתיר נתיבי התקנים.
לכן הסבר הצומת החסר הוא השערה לגבי ענף אחד של הקוד, ולא כלל לגבי מספרים אקראיים בכלל. הוא מחזיק כאשר הראיות מזהות נתיב, והוא ניחוש כאשר הראיות מזהות משהו אחר.
קראו את תור השגיאות, ואחר כך את תצוגת התהליך
הדרך המהירה ביותר להכריע את השאלה היא לקרוא את מה שהספרייה כבר רשמה, ואז להסתכל ישירות בתצוגה שבתוך הכלא. התחילו עם בניית OpenSSL שהשירות טען בפועל, משום שמחסני ספקים שונים בין גרסאות ראשיות ובין הפצות. אחר כך שאלו את systemd איזה תהליך פועל ואילו הגבלות חלות עליו. רק כששתי העובדות על השולחן שווה למנות את הצמתים שאותו תהליך רואה.
openssl version -a
systemctl show openvpn-server@lab -p MainPID -p PrivateDevices -p DevicePolicy
pid=$(systemctl show -p MainPID --value openvpn-server@lab)
sudo ls -l /proc/"$pid"/root/dev/urandom /proc/"$pid"/root/dev/random
הריצו את הקריאות האלה על אותו תהליך, לא על ה-shell שמקרה פתוח. השוואת /dev של המארח לתצוגה של תהליך לא קשור היא הדרך שבה התלות השגויה מתוקנת פעמיים.
הוסיפו את התלות רק כשאתם יכולים לנקוב בשמה
כאשר הראיות אכן מזהות נתיב, התיקון קטן: הפכו את הנתיב האחד הזה לזמין בתוך הכלא והניחו לכל השאר. bind mount של הצומת הבודד בדרך כלל מספיק, ו-mknod עם מספרי ה-major וה-minor הסטנדרטיים של הצומת נותן אותה תוצאה בהתקנות שבהן mount אינו אפשרות. הרחבת ה-sandbox, מתן גישה לכל התקן תו או כיבוי PrivateDevices מחליפים תיקון ממוקד ברגרסיית אבטחה קבועה.
דמון אנטרופיה אינו תחליף לתיקון הזה. חבילות כמו haveged משנות כיצד מאכילים את מאגר הליבה; הן אינן יכולות ליצור נתיב שמותר לתהליך כלוא לפתוח. התקנת אחת עשויה לשפר מראה של גרף בריאות בזמן שקראת הפתיחה עדיין נכשלת. החזיקו את שתי הבעיות בנפרד, אחרת הכשל הבא ייוחס שוב לשכבה הלא נכונה.
אמתו את המחולל ואת המנהרה בנפרד
הפעלה מחדש היא הבדיקה היחידה שעושה שימוש חוזר באותה תצורה, באותו קובץ unit ובאותה בידוד שגרמו לכשל. הריצו אותה, ואחר כך קראו את הרשומות החדשות ביותר ביומן ולא דגימה מאתחול קודם.
sudo systemctl restart openvpn-server@lab
sudo journalctl -u openvpn-server@lab --since -5min --no-pager | grep -iE 'rand|entropy|Initialization Sequence'
חפשו שני דברים נפרדים בפלט הזה: ההודעה על מקור אקראי שנעלמת, והמנהרה שמגיעה לאבן הדרך הרגילה של האתחול. מחולל שעובד שוב מסיר כשל מקומי אחד ואינו אומר דבר על זהות השרת, על אישורי הגישה או על הניתוב. בדקו זאת עם אותה תצורת לקוח שנכשלה קודם, כדי שהתוצאה תהיה ניתנת להשוואה ולא רק טובה יותר.