זהות תהליך#

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

משתנה

מה הוא מחזיק

ניתן לשינוי

$0

שם התוכנית (ותצוגת ps)

כן

$$

ה־PID של התהליך הנוכחי

לא (לקריאה בלבד)

$<

UID אמיתי

כן (מורשה)

$>

UID אפקטיבי

כן (מורשה)

$(

GID אמיתי (בתוספת משלימים)

כן (מורשה)

$)

GID אפקטיבי (בתוספת משלימים)

כן (מורשה)

$0 - שם התוכנית#

שם התוכנית המורצת, כפי שהועבר ב־argv[0]:

print "Started by: $0\n";        # e.g. "/usr/local/bin/myscript.pl"

השמה ל־$0 משנה כיצד התהליך מופיע בפלט של ps וברשימות דומות - שימושי לסקריפטים ארוכי־ריצה שמבצעים fork ורוצים שכל ילד יהיה ניתן לזיהוי:

$0 = "myserver: worker pid $$";   # appears in `ps` as that string

השינוי כותב לאזור זיכרון התהליך ש־ps קורא ב־Linux. גרסאות Unix ישנות יותר ו־Windows בעלות כללים שונים; PetaPerl עוקב אחר התנהגות perl5 במדויק. קריאת $0 תמיד מחזירה את הערך הנוכחי, כולל כל שינוי שביצעתם.

$0 אינו שם התוכנית כפי שהסקריפט רואה אותו - לשם כך, ראו @ARGV (שבו $0 הוא הליווי באינדקס [-1] של ה־argv המקורי, ולא כ־@ARGV[0]).

$$ - מזהה תהליך#

לקריאה בלבד. ה־PID של התהליך הנוכחי.

print "Running as PID $$\n";

open my $log, '>>', "/var/log/myapp.log" or die $!;
print $log "[$$] starting...\n";

לאחר fork, הילד רואה $$ שונה מההורה - זוהי הדרך התקנית לדעת באיזה צד של ה־fork אתם נמצאים:

my $pid = fork;
defined $pid or die "fork: $!";
if ($pid == 0) {
    # child
    print "child: my pid is $$\n";
    exit 0;
}
# parent
print "parent: my pid is $$, child is $pid\n";
waitpid $pid, 0;

הכינוי מ־English $PROCESS_ID (וה־$PID המיותר) פועלים כמו $$.

כדי לקבל את ה־PID של ההורה, השתמשו ב־getppid.

מזהים אמיתיים מול אפקטיביים - מה משמעותם#

לכל תהליך Unix יש שני מזהי משתמש ושני מזהי קבוצה:

  • ה־UID/GID האמיתי הוא מי שאתם - בדרך כלל המשתמש שהפעיל את התוכנית.

  • ה־UID/GID האפקטיבי הוא מה שהקרנל בודק עבור החלטות הרשאה - בדרך כלל זהה לאמיתי, אך שונה עבור תוכניות setuid ו־setgid.

כשמריצים קובץ הפעלה setuid-root, הקרנל קובע את ה־UID האפקטיבי ל־root (בעל הקובץ) אך משאיר את ה־UID האמיתי כשלכם. התוכנית יכולה אז לפעול כ־root עבור הפעולה המורשית, ואז או להפיל הרשאות על ידי החזרת האפקטיבי לאמיתי, או לבדוק מי באמת הפעיל אותה על ידי קריאת ה־UID האמיתי.

$< - UID אמיתי#

print "Invoked by user with UID $<\n";

$< הוא ה־UID האמיתי. הוא כמעט אף פעם לא משתנה במהלך חיי התהליך; המקרים שבהם הוא יכול מוגבלים מאוד (רק root יכול לשנותו, ורק דרך syscalls ספציפיים).

מנמוניק: ה־UID שממנו באתם.

$> - UID אפקטיבי#

print "Privileges currently effective: UID $>\n";

$> הוא ה־UID האפקטיבי - זה שחשוב עבור בדיקות הרשאה. בתוכנית setuid, זהו בתחילה ה־UID של בעל הקובץ; בתוכנית רגילה, הוא שווה ל־$<.

מנמוניק: ה־UID שאליו הלכתם.

תבנית ה־setuid הקלאסית#

עוזר setuid-root הצריך לבצע פעולה מורשית אחת ואז להפיל הרשאות כך ששאר הסקריפט ירוץ כמשתמש האמיתי:

#!/usr/bin/perl
use strict;
use warnings;

# Privileged work first - we still have effective root.
open my $secret, '<', '/etc/some-restricted-file'
    or die "open: $!";
my $config = do { local $/; <$secret> };
close $secret;

# Drop privileges. From now on, both real and effective UID
# are the invoking user's, so any subsequent open/system call
# is checked against the real user.
$> = $<;                         # set effective UID  to real UID
$) = $(;                         # set effective GID  to real GID

# Now run the rest of the script as the real user.
do_user_work($config);

הפלת הרשאות קבועה חייבת לקבוע את האפקטיבי לפני האמיתי (או את שניהם בבת אחת דרך POSIX::setuid()); ברוב המערכות קביעת האמיתי תחילה תנעל אתכם מחוץ לשחזור האפקטיבי. בדקו את $! לאחר כל השמה - הקרנל דוחה שינויים לא־מורשים:

$> = $<;
die "could not drop privileges: $!" if $> != $<;

החלפת אמיתי ואפקטיבי#

כשרוצים להחליף זמנית את המזהה האפקטיבי עבור פעולה יחידה ואז לשחזר אותו:

my $saved_uid = $>;
$> = $<;                         # become real user
my $ok = open(my $fh, '<', $user_path);
$> = $saved_uid;                 # back to effective
$ok or die "open $user_path: $!";

זה עובד רק במערכות התומכות ב־setreuid() - רוב מערכות Unix המודרניות, כולל Linux. POSIX::seteuid() מהמודול POSIX הוא הכתיב המפורש והנייד.

$( - GID אמיתי (עם קבוצות משלימות)#

$( מחזיר את ה־GID האמיתי ולאחריו כל קבוצה משלימה שאליה התהליך שייך, כולן מופרדות ברווח:

print "Real GID list: $(\n";     # e.g. "1000 1000 4 27 100 999"
                                  #       primary + supplementaries
my @groups = split ' ', $(;
my $primary = $groups[0];

המספר המוביל הוא ה־GID הראשי; השאר הן קבוצות משלימות (מ־/etc/group). השמה ל־$( קובעת את ה־GID; התחביר מקבל מספר יחיד:

$( = 100;                        # become primary GID 100

$) - GID אפקטיבי (עם קבוצות משלימות)#

אותה צורה כמו $(, אך עבור ה־GID האפקטיבי:

print "Effective GID list: $)\n";

# Drop both real and effective GID to a specific gid:
$( = $) = "$gid $gid";

בדיקות הרשאה משתמשות ב־GID האפקטיבי ובמשלימים; רשימת ה־GID האמיתי היא לצורכי מידע.

מתי באמת משתמשים באלה#

קוד Perl יומיומי אינו נוגע ב־UIDs וב־GIDs. מקרי השימוש הריאליסטיים הם:

  1. סקריפטי setuid הצריכים להפיל הרשאות. הניב התקני מוצג לעיל.

  2. הפעלת דמון הרצה כ־root כדי לאגד פורט מורשה, ואז מפילה הרשאות לפני שירות בקשות:

    socket(my $srv, ...) or die $!;
    bind($srv, sockaddr_in(80, INADDR_ANY)) or die $!;
    listen($srv, SOMAXCONN) or die $!;
    
    # Now drop to nobody:nogroup before accepting connections.
    my (undef, undef, $u, $g) = getpwnam('nobody');
    $) = "$g $g";
    $( = $g;
    $> = $u;
    $< = $u;
    
  3. בדיקת הרשאה בסקריפט שאמור לסרב לרוץ כ־root: die "do not run me as root\n" if $< == 0.

אינטראקציה עם מצב taint#

תחת -T, Perl מבחין כאשר המזהים האמיתי והאפקטיבי שונים (כלומר הסקריפט הוא setuid) ומפעיל בדיקות נוספות: פעולות מסוימות דוחות ארגומנטים מוכתמים, ונתיב החיפוש עבור system() ו־exec() מוגבל. הרלוונטיות כאן היא שהשאלה אם -T פעיל מוכרעת על בסיס $< מול $> בהפעלה.

ראו גם#

  • getppid - ה־PID של ההורה, מזווג עם $$.

  • fork - שלאחריו $$ שונה בהורה ובילד.

  • getpwuid, getgrgid - הופכים את המזהים המספריים לשמות משתמש וקבוצה.

  • POSIX::setuid - הדרך הניידת והמפורשת לשנות UIDs יחד.

  • משתני שגיאה - $! לאחר השמת $< / $> אומר לכם מדוע הקרנל סירב.