← חזרה למאגר המחקרים

JNI as (Un-)Safe Bridge

מאת שי מרדכי | 26 במרץ 2026

Mobile Security Researcher | Reverse Engineer | Vulnerability Researcher


הפרדוקס בתכנות משפות High-Level ל-Low-Level

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

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

אז לכאורה, שפות High-Level מאוד מאובטחות מפני Buffer Overflow. אך הבידוד הזה עולה ביוקר – כל משתנה סוחב איתו את ה-Metadata בנוסף לנתונים עצמם, ודורש הרבה יותר זיכרון. זה פוגע ביעילות ניהול הזיכרון של השפה. לכן, הרבה שפות High-Level קצת "מרמות", ומאפשרות לגשר (באמצעות FFI) בינן לבין שפות Low-Level (ובמיוחד לשפת C) כדי להחזיר את הביצועים שאבדו.

אשליית האבטחה

ב-Java למשל, הגשר FFI הזה נקרא JNI (Java Native Interface). הוא הפרוטוקול שמתרגם את "ניהול הזיכרון" של Java, ל"ניהול הזיכרון החסכוני" של שפת C.

ה-JNI בעצם משיל מעצמו את כל ההגנות של השפה העילית, כמו ה-Garbage Collector (כתוצאה מהסרת ה-Metadata). הוא אינו מספק הגנות חלופיות, אלא מסתמך על כך שהמתכנת יכניס הגנות ידניות של שפת C (כגון Canary values נגד דריסת מחסנית).

כך כביכול, מפתח ה-High-Level חושב שהוא מתכנת בשפה בטוחה עם הגנות ו"נרדם בשמירה" בזמן הכי קריטי – כשהשפה משילה את כל ההגנות שלה, והופכת ל-Low-Level ע"י שפת C, ונשארת ללא הגנות זימון. זה רק מראה שהיום תכנות בשפות עיליות מגיע ממקום של נוחות פיתוח, יותר מאשר מפונקציונליות ואבטחה של השפה עצמה.

סתירה במעבר מחרוזות דרך JNI

הבעיה עמוקה יותר מסתם אובדן של אוסף הזבל. כשנתונים חוצים את גשר ה-JNI, הם סובלים מדיסוננס סמנטי (Semantic Dissonance).

ה-JVM עשויה לאמת מחרוזת ולאשר אותה כבטוחה, כי היא בודקת את האורך המפורש שלה. אבל כשאותם נתונים בדיוק חוצים ל-C, שפת C מפרשת אותם אחרת – היא מפסיקה לקרוא ברגע שהיא מזהה תו Null Terminal (\0).

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

פרדוקס הקומפילציה

אם שפת C כל כך מסוכנת, למה פשוט לא להוסיף שלב סניטציה בזמן הקומפילציה? במצב כזה, הקומפיילר יוסיף אוטומטית בדיקות לפני כל גישה לזיכרון כדי לוודא שהתוכנית לא כותבת מחוץ לגבולות (כמו AddressSanitizer).

אבל כאן טמון הפרדוקס של המפתח: הוא חצה את גשר ה-JNI לעבר שפת C, כדי לברוח מהפגיעה בביצועים של Java. אם הוא יפעיל את מנגנוני החיטוי והבדיקות הדינמיות ב-C כדי להישאר בטוח, הוא בעצם מחזיר לעצמו בדיוק את אותה פגיעה בביצועי המעבד שממנה ניסה לברוח! הוא מפסיד בשתי החזיתות.

מעבר מלא ל-Rust? לא בהכרח.

ראינו לאחרונה דיווחים על מחקרים פנימיים במיקרוסופט להעברת קוד תשתיתי (כמו ב-Windows 11) לשפת Rust, אך נראה שפרויקט כזה במלואו אינו תמיד כדאי או ישים מיידית. זה אמנם ימנע פגיעויות של Buffer Overflow וזליגת זכרון, אך יכול לגרום לבעיות זרימה אחרות: כפי שהתגלה לאחרונה בחולשה ברכיב ה-Rust בקרנל של Windows, הקוד עלול פשוט לקרוס (Panic / DoS) בכל פעם שיש בעיית הקצאת זיכרון. לפעמים השימושיות, הנוחות, והיציבות של המערכת, גוברות על גישת ה"אבטחה בכל מחיר".

הפתרון המוצע: בניית Tunnels בטוחים

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

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

יישמתי קונספט ארכיטקטוני זהה בפרויקט תשתיתי שבנינו לאחרונה – Data Hop Firewall (פילטר WebAssembly ב-Rust עבור Envoy Proxy הממוקם כחוצץ ברשת בין קונטיינר ה-Backend ל-Frontend).

המטרה שלו היא לשמש כשומר סף חכם בשכבת האפליקציה (Layer 7): הוא סורק את התעבורה הדינמית, מחטא אותה (Sanitization) ומונע זליגת מידע רגיש החוצה. כך למשל, הוא מונע משיכת נתונים עודפים (API Over-fetching) שזולגים דרך רכיבי RSC (React Server Components), או חשיפה ו-Fingerprinting של תשתיות שרתים (כמו עמודי שגיאה דיפולטיים) לדפדפן של המשתמש, תוך אכיפת מודל Zero-Trust הרמטי ברמת ה-Gateway.

בדיוק כפי שהפילטר ב-Envoy מגן על גבולות הרשת מטעויות לוגיות של מפתחי Web (ששולפים מידע עודף ומסתמכים על ה-Client שיסתיר אותו), כך 'מנהרות' ה-Rust ב-JNI צריכות להגן על גבולות הזיכרון מטעויות ניהול של מפתחי C.

העתיד

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

5.7.26 תובנות ל-Malware Analysis: