umask في Linux: من أين تبدأ صلاحيات الملفات؟
تعرّف إلى الخطأ الشائع مع umask: اطبع القناع، أنشئ ملفاً ودليلاً، واقرأ الصلاحيات الفعلية لتفهم سبب اختلاف root عن حسابك.
ما الذي يفعله umask في Linux
umask هو رقم تحمله كل عملية في Linux، ويحدد نمط كل ملف ودليل تنشئه تلك العملية. يطلب البرنامج من النواة مجموعة من الأذونات عند الإنشاء. تمسح النواة كل بت تحدده القناع، ثم تطبق ما تبقى. لا يمنح umask أي وصول. بل يزيل البتات فقط من الأذونات التي طلبها البرنامج المنشئ.
لا تُعد القيمة خاصية لتوزيعتك. فهي تعتمد على الحساب الذي تستخدمه وعلى كيفية بدء shell. وقد تختلف هاتان القيمتان على الجهاز نفسه وفي اللحظة نفسها، حتى عند استخدام صورة افتراضية قياسية. لذلك لا تبدأ بالبحث في الدليل. ابدأ بقياس القيمة على الجهاز الموجود أمامك.
اطبع umask في الصدفة التي تستخدمها
umask
umask -Sيطبع الشكل الأول القناع بالنظام الثماني. ويطبع الشكل الثاني القناع نفسه على شكل الصلاحيات التي يسمح بها، باستخدام الصيغة الرمزية التي يقبلها chmod. أبقِ السطرين ظاهرين على الشاشة. كل ما يلي هو مقارنة بالقيمة التي طبعتها صدفتك للتو.
إنّ umask أمر مضمّن في الصدفة، وليس برنامجاً على القرص. أكّد ذلك باستخدام type umask. وهذا مهم لأن الأمر المضمّن يغيّر عملية الصدفة نفسها. أما البرنامج المنفصل فلا يمكنه تغيير سوى عمليته الخاصة، ثم ينهيها ويأخذ التغيير معه.
إنشاء ملف ودليل، ثم قراءة الصلاحيات الفعلية
cd "$(mktemp -d)"
touch probe.file
mkdir probe.dir
stat -c '%a %A %n' probe.file probe.dirيعرض %a الوضع بالنظام الثماني، بينما يعرض %A الوضع نفسه بصيغة drwxr-xr-x التي يستخدمها ls -l. قارن السطرين بالقناع الذي عرضته قبل لحظات. كل بت مضبوط في القناع يكون مفقوداً من الوضعين، لأن إزالة هذه البتات هي الوظيفة الوحيدة للقناع. إذا لم يكن من السهل بعد قراءة عمود %A، فابدأ أولاً بفهم سلسلة الصلاحيات drwxr-xr-x.
يختلف الملف عن الدليل، والقناع ليس سبب هذا الاختلاف. يطلب touch من النواة صلاحيات القراءة والكتابة للمالك والمجموعة والآخرين. بينما يطلب mkdir صلاحيات القراءة والكتابة والتنفيذ للفئات الثلاث كلها. يُطرح القناع نفسه من طلبين مختلفين. لذلك لا يكون الملف الذي ينشئه touch قابلاً للتنفيذ أبداً، مهما كانت قيمة القناع: إذ لم يُطلب بت التنفيذ أصلاً، ولا يستطيع القناع إعادة بت تمت إزالته.
( umask a=rwx; touch open.file; stat -c '%a %A %n' open.file )تنفّذ الأقواس الأوامر داخل subshell، لذلك يزول التغيير بانتهائه. يطلب القناع الآن عدم إزالة أي بتات، ومع ذلك يظل stat يعرض عدم وجود بت التنفيذ في الملف. شغّل umask مرة أخرى بعد ذلك، وستعود القيمة الأصلية، ما يوضح أن الإعداد يعيش داخل عملية ويُورَّث إلى العمليات الابنة، بدلاً من تخزينه في أي مكان على القرص.
تظهر آثار إزالة بت التنفيذ بوضوح في الأدلة.
( umask a=rw; mkdir noexec.dir; cd noexec.dir )بصفتك مستخدماً عادياً، يفشل cd مع bash: cd: noexec.dir: Permission denied، لأن القناع أزال بت التنفيذ الذي طلبه mkdir، ولا يمكن دخول دليل يفتقر إلى بت التنفيذ. يتجاوز root هذا الفحص، لذلك لا يظهر هذا الأثر إلا عند استخدام حساب عادي.
لماذا يرى root ومستخدمك الخاص قيمة umask مختلفة
نفّذ القياس نفسه من خلال حساب آخر، وابدأه بطريقة أخرى، ثم اقرأ الناتجين جنباً إلى جنب.
umask
sudo -i umaskيشغّل sudo -i صدفة تسجيل الدخول الخاصة بـroot، ثم ينفّذ الأمر المضمّن داخلها. لذلك فهذا حساب مختلف يصل عبر مسار بدء تشغيل مختلف. في صور خوادم Ubuntu وDebian القياسية، قد يطبع السطران قيمتين مختلفتين. كلا السطرين صحيح. يعرض كل سطر القيمة التي نتجت عن مسار بدء التشغيل الخاص به. ويتناول باقي هذا المنشور الجزء الذي أنتج هذه القيمة في النظام.
أي ملف في صورتك حدّد القيمة
grep -nE '^(UMASK|USERGROUPS_ENAB)' /etc/login.defs
grep -rn pam_umask /etc/pam.d/
grep -rn umask /etc/profile /etc/profile.d/ /etc/bash.bashrc ~/.profile ~/.bashrc 2>/dev/null || echo 'no umask line in the shell startup files'إذا لم يعرض أمر grep الأول أي ناتج، فشغّله مرة أخرى من دون مرساة ^. قد يكون السطر مُعلّقاً، والسطر المعلّق توثيق وليس إعداداً. أما أمر grep الثالث فهو ما يفاجئ الناس. في Debian وUbuntu، تشير /etc/profile التي تأتي مع النظام غالباً إلى PAM بدلاً من تعيين قناع بنفسها، لذلك قد لا يكون الملف الذي افترضت أنه المسؤول هو الملف المسؤول فعلاً. يُرجع grep حالة خروج غير صفرية عندما لا يعثر على أي تطابق، ولهذا ينتهي ذلك السطر بـ || echo. في صورة لا يذكر فيها أي ملف بدء تشغيل قناعاً، ستحصل على الرسالة بدلاً من الصمت، وهذه الرسالة هي النتيجة.
يعلن /etc/login.defs عن قيمة، ويطبّق PAM قيمة أخرى
السطر `UMASK في /etc/login.defs هو القيمة التي تذكرها معظم الأدلة. لا يقرأ kernel ولا shell هذا الملف. يقرأه pam_umask، وهو أحد وحدات PAM (وحدات المصادقة القابلة للتوصيل) التي تعمل عند إنشاء جلسة. يأخذ pam_umask أول قيمة يجدها: إدخال umask= في حقل GECOS الخاص بالمستخدم، ثم وسيطة umask= المكتوبة في السطر pam_umask.so نفسه، ثم UMASK من /etc/login.defs. تعدّل التوزيعات هذه الوحدة، لذا شغّل man pam_umask` على image الخاصة بك واقرأ الترتيب المطبوع هناك.
بهذه الطريقة يمكن لـ`/etc/login.defs أن يعلن عن قيمة، بينما تنتهي جلستك بقيمة أخرى، من دون طباعة أي تحذير من أي من الجانبين. توضح أوامر grep الحالة التي تنطبق عليك. إذا كان السطر pam_umask.so يتضمن وسيطة umask=` خاصة به، فستخسر قيمة السطر الموجود في login.defs.
USERGROUPS_ENAB والاستثناء الخاص بـ root
id -un
id -gnإذا طبع الأمران الاسم نفسه، فلديك مجموعة خاصة بالمستخدم: أُنشئ الحساب مع مجموعة خاصة به تحمل اسمه. يوفّر pam_umask سلوكاً خاصاً بمجموعات المستخدمين، وتتحكم فيه USERGROUPS_ENAB في /etc/login.defs. عند تفعيله، وإذا لم يكن الحساب هو root، وكان اسم المجموعة الأساسية يطابق اسم المستخدم، تنسخ الوحدة رقم المالك من القناع إلى رقم المجموعة. وتنتهي الجلسة بقناع يترك بتات المجموعة مفتوحة لكل ما ينشئه ذلك الحساب. يستبعد root من الوحدة نفسها، وهذا الاستبعاد هو السبب الفردي الأكثر شيوعاً لاختلاف الأقنعة التي تطبعها جلستان في خادم واحد.
يستند هذا الإعداد إلى أن المجموعة الخاصة بالمستخدم تضم عضواً واحداً فقط، ولذلك تعني قابلية الكتابة للمجموعة قابلية الكتابة للمالك ولا شيء أكثر. ويظل ذلك صحيحاً إلى أن يضيف أحدهم عضواً ثانياً إلى المجموعة. منذ تلك اللحظة، يستطيع العضو الجديد الكتابة إلى كل ملف أنشأه الحساب سابقاً، من دون تشغيل أي أمر على هذه الملفات لمنحه تلك الصلاحية. امنح كل خدمة حساب مستخدم خاصاً بها بأقل قدر من الصلاحيات، وحافظ عمداً على بقاء تلك المجموعة مكوّنة من عضو واحد.
صدفة تسجيل الدخول، والصدفة غير المخصّصة لتسجيل الدخول، والصدفة غير التفاعلية
umask
bash -lc 'umask'
bash -c 'umask'يعمل PAM عند إنشاء جلسة: login على وحدة التحكم، sshd، su، sudo -i. ولا يعمل عندما تبدأ صدفةٌ صدفةً أخرى. bash -l هي صدفة تسجيل الدخول، لذلك تقرأ /etc/profile و~/.profile، لكنها لا تستدعي pam_umask مطلقاً، لأنه لم تُنشأ جلسة. أما bash -c فلا تقرأ أياً من الملفين، وترث القناع من العملية التي بدأت تشغيلها. وتندرج مهمة cron وgit hook وبرنامج بدأه مدير خدمات ضمن الحالة الأخيرة، لذلك يكون قناعها هو القناع الذي كانت تملكه العملية الأصلية.
لهذا السبب تتكرر كثيراً العبارة: «ضبطته في /etc/profile، لكن الخدمة ما زالت تكتب بالوضع الخاطئ». فالخدمة لم تقرأ ذلك الملف قط.
مكان ضبطه ليبقى فعالاً
اضبط القناع في نقطة بدء عبء العمل الفعلية، لأن كل مسار بدء يقرأ ملفاً مختلفاً.
- للحسابات التي تسجّل الدخول:
UMASKفي/etc/login.defs، ويطبّقه pam_umask على كل جلسة في الجهاز. هذا الإعداد عام على مستوى الجهاز، لذلك يغيّر إعدادات جميع الحسابات دفعة واحدة. - لحساب واحد: وسيطة
umask=في السطرpam_umask.soعامة على مستوى الجهاز أيضاً، لذلك يجب وضع القيمة الخاصة بالمستخدم في حقل GECOS لذلك المستخدم، أو في~/.profileلصدفات تسجيل الدخول، وفي~/.bashrcللصدفات التفاعلية. - لخدمة تعمل تحت systemd:
UMask=في قسم[Service]الخاص بالوحدة. تبدأ الوحدة بواسطة مدير الخدمات، لذلك لا تتم قراءة/etc/profileولا يعمل pam_umask. ملف الوحدة هو الملف الوحيد من هذه الملفات الذي يصل تأثيره إلى الخدمة. - لبرنامج نصي يبدأ بواسطة cron أو بواسطة hook: استخدم
umaskصراحةً في السطر الأول، قبل أن ينشئ البرنامج النصي أي شيء.
[Service]
UMask=<the octal mask you chose>تحقق بعد بدء جديد عبر المسار نفسه، ولا تتحقق من الصدفة التي عدّلت فيها الملف. تحتفظ الصدفة الحالية بالقناع الخاص بها، ولا يصل تعديل ملف الإعدادات إلى عملية قيد التشغيل.
bash -lc 'umask'
sudo -i umaskلماذا لا يُعد تشغيل chmod لاحقاً إصلاحاً مماثلاً
يُصلح chmod الملفات الموجودة. أما القناع فيحدد وضع الملفات التي لم تُنشأ بعد. شغّل chmod -R على دليل، ثم سيصل الملف التالي الذي تكتبه الخدمة بالوضع القديم مجدداً، لأن ذلك الوضع جاء من العملية التي أنشأت الملف، ولا يغيّر الدليل شيئاً فيه.
هناك أيضاً فترة مكشوفة. بين لحظة إنشاء الملف ولحظة تشغيل chmod، يبقى الملف على القرص بالوضع الأوسع، ويمكن لأي عملية تستطيع قراءة الدليل فتحه. بالنسبة إلى مفتاح خاص أو أرشيف نسخة احتياطية، تمثل هذه الفترة الخطر الذي كنت تحاول إزالته.
اضبط الوضع عند الإنشاء بدلاً من ذلك. يكتب install -m u=rw,go= newfile /etc/app/newfile الوجهة بوضع صريح، ويفعل mkdir -m الشيء نفسه مع الدليل. يأخذ كلاهما الوضع الذي تحدده ويتجاهلان القناع. يضبط ssh-keygen الوضع على المفتاح الخاص الذي يكتبه، ولذلك يكون هذا الملف غالباً صحيحاً على جهاز تكون فيه جميع الملفات الأخرى غير صحيحة.
غالباً ما تكون SSH هي المتضررة. فالـ~/.ssh الذي يُنشأ باستخدام mkdir عادي، أو authorized_keys الذي يُلحق باستخدام cat >>، يرث قناع الصدفة. عند تفعيل StrictModes، يرفض sshd قراءة ملف مفتاح من دليل قابل للكتابة من قبل المجموعة. يتلقى العميل Permission denied (publickey)، بينما يسجل /var/log/auth.log على الخادم السبب الفعلي:
Authentication refused: bad ownership or modes for directory /home/deploy/.sshهذا الفحص مقصود، وتعتمد عليه تقوية SSH على VPS. قِس القناع على خادم جديد قبل إنشاء الحسابات التي ستستخدمه، بالتزامن مع الدقائق العشر الأولى على VPS جديد، وبذلك يكون وضع كل ملف تنشئه تلك الحسابات محدداً مسبقاً.
تتجاهل عمليات النسخ والأرشفة القناع
تستعيد cp -p وrsync -a الوضع المسجّل في الملف المصدر، لذلك لا يؤثر القناع في النتيجة. وينطبق الأمر نفسه على tar عند فك الأرشيف بصفة root، أو بصفة مستخدم عادي مع -p. يحتفظ الملف المستعاد من نسخة احتياطية بالوضع الذي كان عليه عند إنشاء النسخة الاحتياطية. تحقّق من ذلك قبل أن تستنتج أن القناع الصحيح يتم تجاهله: لم يُستخدم القناع أصلاً مع البيانات المستعادة.
FAQ
لماذا تنشئ مهمة cron ملفات بوضع مختلف عن جلسة ssh؟
مهمة cron ليست جلسة تسجيل دخول، لذلك لا يتم تشغيل pam_umask لها، ولا تقرأ /etc/profile ولا ~/.profile. وهي ترث القناع من العملية التي بدأت تشغيلها. ضع umask صراحةً في السطر الأول من البرنامج النصي، قبل أن ينشئ أي شيء، واطبع القناع مرة واحدة من داخل المهمة حتى ترى القناع الفعلي لهذه المهمة، لا القناع الموجود في shell الخاص بك.
لماذا يعرض /etc/login.defs قيمة، بينما يطبع shell الخاص بي قيمة أخرى؟
يُعد UMASK في /etc/login.defs آخر قيمة احتياطية فقط لـ pam_umask. تفضّل الوحدة إدخال umask= في حقل GECOS الخاص بالمستخدم، ثم الوسيط umask= في السطر pam_umask.so داخل /etc/pam.d/. بعد ذلك، يعيد سلوك usergroups، الذي يُفعَّل بواسطة USERGROUPS_ENAB، كتابة رقم المجموعة لأي حساب ليس root وتُسمّى مجموعته الأساسية باسمه. شغّل grep -rn pam_umask /etc/pam.d/ وid -un; id -gn لمعرفة أي من هذه الإعدادات ينطبق على حسابك.
هل يمكن لـ umask أن يجعل الملف قابلاً للتنفيذ؟
لا. يمكن للقناع فقط إزالة البتات التي طلب البرنامج المنشئ ضبطها. لا يطلب touch بت التنفيذ، لذلك لا ينتج أي قناع ملفاً قابلاً للتنفيذ. تحقّق من ذلك في دليل مؤقت باستخدام ( umask a=rwx; touch f; stat -c '%a %A' f ). لإضافة بت التنفيذ، تحتاج إلى chmod أو إلى برنامج مثل install -m يطلبه عند إنشاء الملف.
أين أضبط umask لخدمة systemd؟
في الوحدة، باستخدام UMask= داخل القسم [Service]. تبدأ مدير الخدمة الخدمة، وليس جلسة تسجيل الدخول، لذلك لا تُقرأ ملفات بدء تشغيل shell ولا يتم تشغيل pam_umask لها. بعد systemctl daemon-reload وإعادة تشغيل الوحدة، تحقّق من خارجها: دع الخدمة تنشئ ملفاً، ثم اقرأ النتيجة باستخدام stat -c '%a %n'.
هل القناع الافتراضي الذي يسمح بالكتابة للمجموعة آمن؟
يكون آمناً عندما تضم المجموعة عضواً واحداً فقط، وهو الافتراض الذي يقوم عليه مخطط المجموعة الخاصة بالمستخدم. إذا أضفت حساباً ثانياً إلى تلك المجموعة، فستصبح كل الملفات التي أنشأها الحساب الأول قابلة للكتابة من العضو الجديد فوراً، من دون تشغيل أي أمر على هذه الملفات. شغّل id -un وid -gn: إذا طبع الأمران الاسم نفسه، فأنت تستخدم مجموعة خاصة. إذا كنت تشارك مجموعة بين عدة حسابات، فاضبط قناعاً يزيل بت الكتابة للمجموعة، ثم أنشئ ملفاً واقرأ stat -c '%a %n' لإثبات سريان التغيير.