מטפלי קובץ קבצים ספריות

chroot#

משנה את ספריית השורש של התהליך הנוכחי וכל ילד שהוא ייצור בהמשך.

לאחר chroot מוצלח, כל שם נתיב המתחיל ב־/ נפתר ביחס לספרייה הנקובה ב־FILENAME במקום שורש מערכת הקבצים האמיתי. התהליך מאבד את היכולת לנקוב בשמות של קבצים מחוץ לתת־עץ זה באמצעות נתיבים מוחלטים. הקרנל אוכף זאת עבור התהליך הנוכחי ועבור כל צאצא - אין תחום פר־thread ואין דרך לבטל זאת מתוך הכלא.

chroot אינו משנה את ספריית העבודה הנוכחית. אם ספריית העבודה הייתה מחוץ לשורש החדש כשהקריאה הצליחה, היא נשארת בחוץ, ונתיבים יחסיים עדיין יכולים לברוח. תמיד עקוב אחרי chroot מוצלח ב־chdir ל־/ (שמשמעו כעת בתוך הכלא) לפני שתעשה כל דבר אחר.

הקריאה מוגבלת ל־super-user. תהליך לא־מורשה מקבל EPERM ו־$! נקבע בהתאם.

תקציר#

chroot FILENAME
chroot

מה מקבלים בחזרה#

1 בהצלחה, 0 בכישלון. בכישלון $! מחזיק את ה־errno מקריאת המערכת chroot(2) שמתחת - בדרך כלל EPERM (לא root), ENOENT (הנתיב אינו קיים), או ENOTDIR (הנתיב אינו ספרייה).

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

chroot "/var/empty"
    or die "chroot: $!";
chdir "/"
    or die "chdir after chroot: $!";

מצב גלובלי שהוא נוגע בו#

  • $_ - כאשר נקרא ללא ארגומנט, chroot משתמש בערך של $_ כנתיב. בתוך לולאת while (<>) זה לעיתים נדירות מה שאתה רוצה; העבר את הנתיב במפורש.

  • $! - נקבע ל־errno שמתחת בכישלון.

דוגמאות#

הרצף הבטוח הקנוני - שנה את השורש, אז שנה את ספריית העבודה לשורש החדש, אז ויתר על הרשאות:

chroot "/srv/jail"   or die "chroot: $!";
chdir  "/"           or die "chdir: $!";
$) = $(= 65534;                           # drop to 'nobody' gids
$> = $< = 65534;                          # drop to 'nobody' uids

פתח קבצים וטען ספריות משותפות לפני chroot, לא אחרי. בשורש החדש בדרך כלל אין לא /etc/resolv.conf ולא את נתיבי החיפוש של ה־linker הדינמי:

open my $log, ">>", "/var/log/app.log" or die $!;
my $cfg = read_config("/etc/app.conf");

chroot "/srv/jail" or die "chroot: $!";
chdir  "/"         or die "chdir: $!";

# $log and $cfg are still usable inside the jail.

בדיקת מצבי כישלון בנפרד - הבחנה בין ״לא root״ ל־״נתיב גרוע״ חשובה לאבחון:

use Errno qw(EPERM ENOENT ENOTDIR);

unless (chroot $path) {
    die "chroot: need root privileges\n"       if $! == EPERM;
    die "chroot: $path does not exist\n"       if $! == ENOENT;
    die "chroot: $path is not a directory\n"   if $! == ENOTDIR;
    die "chroot: $!";
}

צורת ארגומנט ברירת המחדל קוראת את הנתיב מ־$_:

$_ = "/srv/jail";
chroot or die "chroot: $!";

מקרי קצה#

  • לא root: נכשל עם EPERM. אין הקלה מבוססת־יכולות בלינוקס סטנדרטי אלא אם התהליך מחזיק CAP_SYS_CHROOT.

  • נתיב יחסי: chroot "jail" נפתר כנגד ספריית העבודה הנוכחית ברגע הקריאה. העדף נתיבים מוחלטים כך שההתנהגות אינה תלויה ב־cwd של הקורא.

  • symlinks ב־FILENAME: נפתרים לפני שה־chroot נכנס לתוקף. symlinks בתוך השורש החדש המצביעים על נתיבים מוחלטים ייפתרו ביחס לשורש החדש, לא לשורש האמיתי - זוהי התנהגות הכליאה המיועדת, לא באג.

  • file descriptors פתוחים שורדים: כל מטפל שנפתח לפני הקריאה נשאר שמיש לאחר מכן. כך daemons מתנהגים היטב שומרים על לוגים, קונפיגורציה, וסוקטים מאזינים על פני המעבר.

  • אין בריחה מבפנים: תהליך אינו יכול chroot את דרכו החוצה. הדרך היחידה לעזוב chroot היא שתהליך מורשה מחוץ לו יאסוף ויחליף אותו.

  • ספריית עבודה מחוץ לשורש החדש: הקרנל מתיר את הקריאה, אך נתיבים יחסיים ומעבר .. משם עדיין יכולים להגיע לקבצים מחוץ לכלא. ה־chdir "/" החובה לאחר chroot סוגר את הפער.

  • Threads: chroot בלינוקס הוא פר־process (במדויק, פר fs struct). תחת use threads כל ה־threads של המפענח חולקים את אותו שורש לאחר הקריאה - אין כלא פר־thread.

  • אינו משפיע על $0, %ENV, או סוקטים פתוחים. רק פתרון נתיב משתנה.

הבדלים מ־upstream#

תואם מלא ל־upstream Perl 5.42.

ראו גם#

  • chdir - המעקב החובה לאחר chroot מוצלח; בלעדיו ספריית העבודה עשויה עדיין להצביע מחוץ לשורש החדש

  • fork - ילדים יורשים את ה־chroot; שלב עם chroot כדי להריץ קוד לא־מהימן בתת־עץ מוגבל

  • exec - מריץ את תוכנית ההחלפה בתוך השורש הנוכחי, כך שהקובץ הבינארי המטרה והספריות הדינמיות שלו חייבים להתקיים בתוך הכלא

  • open - פתיחת קבצים בנתיב מוחלט לאחר chroot מחפשת שמות בתוך הכלא; פתח מראש כל מה שאתה צריך מבחוץ לפני הקריאה

  • $_ - נתיב ברירת המחדל כאשר chroot נקרא ללא ארגומנט

  • $! - מחזיק את ה־errno מ־chroot(2) בכישלון