أكثر رسالة خطأ سوف تراها على سيرفر Linux هي Permission denied: سكربت كتبته للتو ولا يعمل، وContainer يتوقف لأنه لا يستطيع الكتابة في مجلد بياناته، وملف إعداد نسخته بنفسك ولا تستطيع تعديله، ومفتاح SSH يرفضه السيرفر دون سبب واضح. وأول ما يفعله كثير من الناس عندها هو chmod 777، فيختفي الخطأ ويظهر بدلاً منه ثغرة لا يراها أحد. وفي مقال سطر الأوامر تجاوزنا الحروف rwx التي تظهر في أول كل سطر من ls -l، أما هذا المقال فيشرحها حرفاً حرفاً، لأنها القاعدة التي يبني عليها النظام كله قراره: من يقرأ هذا الملف، ومن يعدله، ومن يشغله.
وسوف نناقش في هذا المقال ما يلي:
- المستخدمون والمجموعات وأرقامهم UID وGID، لأن الصلاحيات تعتمد على الأرقام لا على الأسماء.
- قراءة سطر
ls -l، ومعنى r وw وx على الملف وعلى المجلد، وهما مختلفان أكثر مما تتوقع. - الترتيب الذي تفحص به النواة الصلاحيات، ولماذا قد يمنع المالك من ملف يقرؤه غيره.
- الصيغة الرقمية Octal مثل 755 و640، والأوامر
chmodوchownوchgrp. - القناع umask الذي يحدد صلاحيات كل ملف جديد.
- البتات الخاصة: setuid وsetgid والبت اللاصق Sticky Bit على
/tmp. - قوائم التحكم في الوصول ACL باختصار، والفرق بين sudo والعمل بحساب root.
- الأخطاء الشائعة:
chmod 777، وPermission denied في Docker Volume، والمالك الخطأ بعد النسخ، وصلاحية التشغيل على السكربتات.
وهذا المقال من مرحلة أساسيات Linux، أما إدارة المستخدمين والمجموعات نفسها وإعداد sudo وما تفعله عندما يغادر مسؤول النظام فلها عنصر مستقل في المرحلة نفسها، لذلك سوف نذكر منها هنا ما تحتاجه لفهم الصلاحيات فقط.
تجهيز المختبر Lab
كما في المقال السابق، سوف ننفذ كل شيء داخل Container من Docker Image موجودة عندنا هي postgres:18، ونستخدم منها نظام Debian 13 وأدواته فقط، وننشئ فيه مستخدمين اثنين حتى نرى ما يراه كل منهما:
docker run -d --name lab-linux-perm --hostname lab --entrypoint sleep postgres:18 infinity
docker exec -it lab-linux-perm bashثم ننشئ داخل ال Container مجموعة اسمها devs، والمستخدمة sara وهي عضو فيها، والمستخدم omar وهو ليس عضواً فيها:
$ groupadd devs
$ useradd -m -s /bin/bash -G devs sara
$ useradd -m -s /bin/bash omar
$ id sara
uid=1000(sara) gid=1001(sara) groups=1001(sara),1000(devs)
$ id omar
uid=1001(omar) gid=1002(omar) groups=1002(omar)
$ grep -E '^(sara|omar)' /etc/passwd
sara:x:1000:1001::/home/sara:/bin/bash
omar:x:1001:1002::/home/omar:/bin/bash
$ grep -E '^(devs|sara|omar)' /etc/group
devs:x:1000:sara
sara:x:1001:
omar:x:1002:
$ ls -ld /home/sara /home/omar
drwx------ 2 omar omar 4096 Oct 7 05:31 /home/omar
drwx------ 2 sara sara 4096 Oct 7 05:31 /home/sara
$ grep -E '^(HOME_MODE|UMASK)' /etc/login.defs
HOME_MODE 0700في الإعداد أعلاه لاحظ التالي:
- لكل مستخدم رقم UID، ولكل مجموعة رقم GID، ولكل مستخدم مجموعة رئيسية Primary Group تحمل اسمه في العادة، ثم مجموعات إضافية Supplementary Groups مثل
devsلسارة. والنواة Kernel لا تعرف الأسماء أصلاً، وإنما تخزن مع كل ملف رقم مالكه ورقم مجموعته، والأسماء تأتي من/etc/passwdو/etc/groupعند العرض فقط. - أنشأ
useraddمجلد كل مستخدم بالصلاحياتdrwx------، أي لصاحبه وحده، والسبب الإعدادHOME_MODE 0700في/etc/login.defsعلى Debian 13، فعمر لا يستطيع أن يرى ما في مجلد سارة. وهذا يختلف بين التوزيعات والإصدارات، فافحص مجلدات المستخدمين على سيرفرك. - سوف ترى في المخرجات أوامر نفذناها بحساب root وأخرى بحساب سارة أو عمر، وسوف نذكر في كل مرة من الذي ينفذ الأمر. ورسائل Bash التي تبدأ ب
/usr/local/bin/run.sh: line 7:سببها السكربت الذي شغلنا به الأوامر بالترتيب، وفي طرفيتك تبدأ بbash:.
قراءة سطر ls -l
أنشأت سارة في مجلدها سكربتاً صغيراً اسمه deploy.sh ومجلداً اسمه site، ثم عرضتهما ب ls -l، وب stat الذي يعرض الصلاحيات بالحروف وبالأرقام معاً:
$ printf '#!/bin/bash\necho "deploying..."\n' > deploy.sh
$ mkdir site
$ ls -l
total 8
-rw-r--r-- 1 sara sara 32 Oct 7 05:32 deploy.sh
drwxr-xr-x 2 sara sara 4096 Oct 7 05:32 site
$ stat -c '%A %a %U %G %n' deploy.sh site
-rw-r--r-- 644 sara sara deploy.sh
drwxr-xr-x 755 sara sara siteوالصورة التالية تفكك السطر الأول بعد أن نعطيه الصلاحية 750 كما سيأتي:

- الحرف الأول هو النوع:
-ملف عادي، وdمجلد Directory، وlرابط رمزي Symbolic Link. - الخانات الثلاث التالية للمالك User ورمزه
u، وهو المستخدم المذكور في العمود الثالث. - الثلاث التي بعدها للمجموعة Group ورمزها
g، وهي المجموعة المذكورة في العمود الرابع. - الثلاث الأخيرة للآخرين Others ورمزهم
o، أي كل مستخدم ليس المالك وليس عضواً في المجموعة. - وفي كل ثلاثية:
rللقراءة Read، وwللكتابة Write، وxللتشغيل Execute، والشرطة تعني أن الصلاحية غير موجودة.
r وw وx على المجلد Directory
على الملف المعنى واضح: r تقرأ محتواه، وw تعدله، وx تشغله كبرنامج. أما المجلد فهو في الحقيقة قائمة أسماء، وكل اسم يشير إلى ملف، لذلك فإن r على المجلد تعني أن تقرأ قائمة الأسماء فقط، وx تعني أن تدخله وتصل إلى الملفات التي فيه إذا عرفت أسماءها، وw تعني أن تضيف أسماء إلى القائمة أو تحذفها أو تغيرها، أي أن تنشئ ملفات وتحذفها وتعيد تسميتها، كما تشرح صفحة الدليل path_resolution(7). ولنرى ذلك في التجربة، أنشأت سارة مجلداً فيه ملف، ثم غيرت صلاحيات المجلد ثلاث مرات:
$ mkdir box && echo "secret plan" > box/plan.txt
$ chmod 600 box
$ ls -ld box
drw------- 2 sara sara 4096 Oct 7 05:34 box
$ ls box
plan.txt
$ cat box/plan.txt
cat: box/plan.txt: Permission denied
$ chmod 100 box
$ ls box
ls: cannot open directory 'box': Permission denied
$ cat box/plan.txt
secret plan
$ chmod 500 box
$ touch box/new.txt
touch: cannot touch 'box/new.txt': Permission denied
$ rm box/plan.txt
rm: cannot remove 'box/plan.txt': Permission denied
$ chmod 700 boxفي الإعداد أعلاه لاحظ التالي:
- مع
rw-فقط استطاعت أن ترى اسم الملفplan.txt، لكنها لم تستطع قراءته مع أن الملف نفسه يسمح لها بالقراءة، والسبب أن الوصول إلى أي ملف يحتاج x على كل مجلد في الطريق إليه. - مع
--xفقط انعكس الوضع: لا تستطيع أن ترى ما في المجلد، لكنها قرأت الملف لأنها تعرف اسمه. - مع
r-xقرأت ودخلت، لكنها لم تستطع إنشاء ملف ولا حذف ملف، فالحذف يحتاج w على المجلد لا على الملف، وهذه نقطة سوف نعود إليها في الأخطاء الشائعة.
ولهذا السبب فإن المجلدات تأخذ عادة 755 أو 750 أو 700، أي أن x موجودة دائماً مع r، والمجلد الذي فيه r دون x لا فائدة منه تقريباً.
من يطبق عليه أي جزء؟ ترتيب الفحص في النواة Kernel
عندما تحاول عملية أن تفتح ملفاً، لا تجمع النواة صلاحيات المالك والمجموعة والآخرين، وإنما تختار ثلاثية واحدة فقط بهذا الترتيب كما تشرح صفحة path_resolution(7): إذا كانت العملية تعمل بحساب مالك الملف تطبق ثلاثية المالك، وإلا فإذا كانت مجموعة الملف من مجموعات العملية تطبق ثلاثية المجموعة، وإلا تطبق ثلاثية الآخرين. وحساب root يتجاوز هذا الفحص كله، مع استثناء واحد هو أن التشغيل يحتاج x واحدة على الأقل في أي من الثلاثيات. والصورة التالية تبين الترتيب:

والمثال على اليمين في الصورة من مختبرنا: أنشأت سارة ملفاً في /tmp وأعطته الصلاحيات 077، أي لا شيء للمالك وكل شيء للمجموعة والآخرين:
$ echo "for everyone except me" > /tmp/odd.txt
$ chmod 077 /tmp/odd.txt
$ ls -l /tmp/odd.txt
----rwxrwx 1 sara sara 23 Oct 7 05:34 /tmp/odd.txt
$ cat /tmp/odd.txt
cat: /tmp/odd.txt: Permission deniedثم قرأه عمر دون مشكلة:
$ whoami
omar
$ cat /tmp/odd.txt
for everyone except me
$ ls /home/sara
ls: cannot open directory '/home/sara': Permission deniedسارة هي المالكة، فطبقت عليها ثلاثية المالك وهي فارغة، ولم تنظر النواة إلى ما بعدها، أما عمر فليس المالك ولا عضواً في مجموعة sara فطبقت عليه ثلاثية الآخرين. وهذا المثال غريب عمداً، لكن الحالة نفسها تحدث في الواقع بشكل أهدأ، مثل ملف صلاحياته 604 يقرؤه الجميع إلا أعضاء مجموعته. ولاحظ أن سارة تستطيع دائماً إعادة الصلاحيات بالأمر chmod لأنها المالكة.
تغيير الصلاحيات بالأمر chmod
للأمر chmod صيغتان: الصيغة الرقمية Octal، والصيغة الرمزية Symbolic.
في الصيغة الرقمية لكل صلاحية قيمة: r تساوي 4، وw تساوي 2، وx تساوي 1، وتجمع قيم كل ثلاثية في رقم واحد من 0 إلى 7، فتكتب ثلاثة أرقام للمالك ثم المجموعة ثم الآخرين. ف rwx تساوي 7، وr-x تساوي 5، وr-- تساوي 4، و--- تساوي 0، وبالتالي فإن rwxr-x--- هي 750. وهذه القيم التي سوف تكتبها أغلب الوقت:
| القيمة | الحروف | الاستخدام |
|---|---|---|
644 | rw-r--r-- | ملف عادي يقرؤه الجميع ويعدله المالك، مثل ملفات الموقع الثابتة |
640 | rw-r----- | ملف إعداد يقرؤه أعضاء مجموعة الخدمة فقط |
600 | rw------- | الأسرار: ملف .env والمفتاح الخاص لSSH |
755 | rwxr-xr-x | مجلد عام، أو سكربت يشغله الجميع |
750 | rwxr-x--- | مجلد أو سكربت للمالك ومجموعته فقط |
700 | rwx------ | مجلد خاص، مثل ~/.ssh |
أما الصيغة الرمزية فتغير جزءاً محدداً وتترك الباقي كما هو: تختار الفئة u أو g أو o أو a للجميع، ثم + للإضافة أو - للحذف أو = للتحديد الكامل، ثم الصلاحيات. والمثال التالي يبين الصيغتين على السكربت نفسه:
$ ./deploy.sh
/usr/local/bin/run.sh: line 7: ./deploy.sh: Permission denied
$ chmod u+x deploy.sh
$ ./deploy.sh
deploying...
$ ls -l deploy.sh
-rwxr--r-- 1 sara sara 32 Oct 7 05:32 deploy.sh
$ chmod 750 deploy.sh
$ ls -l deploy.sh
-rwxr-x--- 1 sara sara 32 Oct 7 05:32 deploy.sh
$ chmod u=rw,g=r,o= deploy.sh
$ ls -l deploy.sh
-rw-r----- 1 sara sara 32 Oct 7 05:32 deploy.shوالقاعدة العملية أن الصيغة الرقمية مناسبة عندما تعرف النتيجة النهائية التي تريدها، والرمزية مناسبة عندما تريد تغيير صلاحية واحدة دون أن تلمس الباقي، مثل chmod +x على سكربت.
chmod -R والحرف X الكبير
الخيار -R يطبق الصلاحيات على المجلد وكل ما فيه، وهنا خطأ يقع فيه كثيرون: الملفات تحتاج 640 مثلاً والمجلدات تحتاج 750، فإذا طبقت 640 على الشجرة كلها نزعت x من المجلدات فلم يعد أحد يستطيع دخولها، حتى أنت. والمثال التالي من المختبر يبين ذلك، ثم يبين الحل بالحرف X الكبير الذي يعني «أعط x للمجلدات، وللملفات التي فيها x أصلاً»:
$ mkdir -p www/css && touch www/index.html www/css/site.css
$ chmod -R 640 www
chmod: cannot access 'www/index.html': Permission denied
chmod: cannot access 'www/css': Permission denied
$ ls -l www
ls: cannot access 'www/index.html': Permission denied
ls: cannot access 'www/css': Permission denied
total 0
d????????? ? ? ? ? ? css
-????????? ? ? ? ? ? index.html
$ chmod -R u=rwX,g=rX,o= www
$ ls -lR www
www:
total 4
drwxr-x--- 2 sara sara 4096 Oct 7 05:44 css
-rw-r----- 1 sara sara 0 Oct 7 05:44 index.html
www/css:
total 0
-rw-r----- 1 sara sara 0 Oct 7 05:44 site.cssغير chmod صلاحيات المجلد www نفسه أولاً إلى rw-r-----، فلم يعد يستطيع الدخول إليه ليكمل على ما فيه، ثم أظهر ls -l علامات الاستفهام لأنه يرى الأسماء (بفضل r) ولا يستطيع الوصول إلى الملفات لمعرفة تفاصيلها (لغياب x). والصيغة u=rwX,g=rX,o= أصلحت الشجرة في أمر واحد، فأخذت المجلدات 750 والملفات 640.
تغيير المالك Owner والمجموعة Group بالأمرين chown وchgrp
الأمر chown يغير مالك الملف ومجموعته بالصيغة chown user:group file، والأمر chgrp يغير المجموعة فقط. وهنا قاعدة تحددها النواة كما تشرح صفحة chown(2): لا يستطيع تغيير مالك الملف إلا root، أما المالك فيستطيع أن يغير مجموعة ملفه إلى أي مجموعة هو عضو فيها فقط. والمثال التالي نفذته سارة:
$ touch report.txt
$ chown omar report.txt
chown: changing ownership of 'report.txt': Operation not permitted
$ chgrp devs report.txt
$ chgrp omar report.txt
chgrp: changing group of 'report.txt': Operation not permitted
$ ls -l report.txt
-rw-r--r-- 1 sara devs 0 Oct 7 05:36 report.txtثم نفذ root الأمر نفسه فنجح:
# chown omar:devs report.txt
# ls -l report.txt
-rw-r--r-- 1 omar devs 0 Oct 7 05:36 report.txtلاحظ أن سارة استطاعت تغيير المجموعة إلى devs لأنها عضو فيها، ورفض النظام المجموعة omar لأنها ليست عضواً فيها. والخيار -R يعمل مع chown أيضاً، وسوف تستخدمه كثيراً مع مجلدات Docker كما سيأتي.
صلاحيات كل ملف جديد والقناع umask
لم نحدد صلاحيات أي ملف أنشأناه حتى الآن، ومع ذلك أخذ كل ملف 644 وكل مجلد 755. والسبب أن البرنامج الذي ينشئ الملف يطلب صلاحيات معينة، فالأمر touch يطلب 666 للملف والأمر mkdir يطلب 777 للمجلد، ثم تحذف النواة منها كل صلاحية مذكورة في القناع umask الخاص بالعملية، كما تشرح صفحة umask(2). والصورة التالية تبين الحساب بتاً بتاً:

والمثال التالي نفذته سارة بثلاث قيم للقناع:
$ umask
0022
$ umask -S
u=rwx,g=rx,o=rx
$ touch a.txt; mkdir a-dir
$ ls -ld a.txt a-dir
drwxr-xr-x 2 sara sara 4096 Oct 7 05:36 a-dir
-rw-r--r-- 1 sara sara 0 Oct 7 05:36 a.txt
$ umask 077
$ touch b.txt; mkdir b-dir
$ ls -ld b.txt b-dir
drwx------ 2 sara sara 4096 Oct 7 05:36 b-dir
-rw------- 1 sara sara 0 Oct 7 05:36 b.txt
$ umask 033
$ touch c.txt; mkdir c-dir
$ ls -ld c.txt c-dir
drwxr--r-- 2 sara sara 4096 Oct 7 05:36 c-dir
-rw-r--r-- 1 sara sara 0 Oct 7 05:36 c.txt
$ umask 022في الإعداد أعلاه لاحظ التالي:
- القناع 022 يحذف w من المجموعة والآخرين، وهو ما رأيناه في كل الملفات السابقة.
- القناع 077 يجعل كل ملف ومجلد جديد لصاحبه وحده، وهو مناسب للسكربت الذي ينشئ ملفات فيها أسرار أو نسخاً احتياطية، فتضعه في أول السكربت بدل أن تتذكر
chmodبعد كل ملف. - مع القناع 033 أخذ الملف 644 وليس 633 كما يتوقع من يطرح الأرقام، لأن x لم تكن مطلوبة أصلاً فلا يوجد ما يحذف. وأخذ المجلد 744، أي أن المجموعة والآخرين يرون الأسماء ولا يدخلون، وهي الحالة التي قلنا إنه لا فائدة منها.
- القناع خاص بكل عملية، يرثه البرنامج من الصدفة التي شغلته، والأمر
umaskيغيره للصدفة الحالية فقط، لذلك عدنا إلى 022 في آخر المثال.
والقناع لا يضيف صلاحية أبداً، فإذا أردت ملفاً تنفيذياً فعليك أن تضيف x بنفسك بالأمر chmod +x، أو تنشئه بالأمر install -m 755 الذي يحدد الصلاحيات النهائية مباشرة.
البتات الخاصة: setuid وsetgid والبت اللاصق Sticky Bit
بجانب التسع صلاحيات هناك ثلاثة بتات أخرى قيمها 4000 و2000 و1000، كما تشرح صفحة inode(7)، وتكتب كرقم رابع قبل الثلاثة، مثل chmod 2770، وتظهر في ls -l مكان حرف x في إحدى الثلاثيات كما في جدول الصورة الأولى.
البرنامج يعمل بصلاحيات مالكه مع setuid
عندما تغير كلمة مرورك بالأمر passwd، فإن البرنامج يكتب في الملف /etc/shadow الذي لا يستطيع قراءته إلا root، فكيف يفعل ذلك وأنت مستخدم عادي؟ والإجابة هي البت setuid: إذا كان موجوداً على ملف تنفيذي فإن البرنامج يعمل بصلاحيات مالك الملف لا بصلاحيات من شغله، كما تشرح صفحة execve(2)، ومالك passwd هو root. والمثال التالي من المختبر بحساب root:
$ ls -l /usr/bin/passwd /usr/bin/su /usr/bin/chage
-rwxr-sr-x 1 root shadow 113848 Apr 19 2025 /usr/bin/chage
-rwsr-xr-x 1 root root 118168 Apr 19 2025 /usr/bin/passwd
-rwsr-xr-x 1 root root 84360 Jul 31 15:34 /usr/bin/su
$ find / -xdev -perm -4000 -type f 2>/dev/null
/usr/bin/su
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/umount
/usr/bin/newgrp
/usr/bin/mount
/usr/bin/passwd
/usr/bin/gpasswd
$ find / -xdev -perm -2000 -type f 2>/dev/null
/usr/sbin/unix_chkpwd
/usr/bin/chage
/usr/bin/expiryفي الإعداد أعلاه لاحظ التالي:
- الحرف
sمكان x المالك فيpasswdوsuهو setuid، والحرفsمكان x المجموعة فيchageهو setgid على ملف، فيعمل البرنامج بصلاحيات المجموعةshadow. - الأمر
find / -perm -4000يعرض كل برامج setuid على النظام، وهي قائمة يجب أن تعرفها على سيرفرك، لأن كل برنامج فيها يعمل بصلاحيات root مهما كان من شغله، وأي ثغرة فيه تعطي المستخدم العادي صلاحيات root مباشرة.
pkexec من حزمة polkit، وهو برنامج setuid يملكه root ومثبت افتراضياً في كل التوزيعات الكبيرة. وكانت الثغرة موجودة منذ أول نسخة من pkexec في مايو 2009، أي أكثر من اثنتي عشرة سنة، وسمحت لأي مستخدم عادي بالحصول على صلاحيات root كاملة على التثبيت الافتراضي ل Ubuntu وDebian وFedora وCentOS. وكان الحل المؤقت الذي اقترحه الفريق لمن لم يصله التحديث هو نزع البت setuid من pkexec. والدرس أن كل برنامج setuid على سيرفرك باب محتمل إلى root، فاعرف قائمتها، ولا تضف إليها شيئاً، واحرص على التحديثات الأمنية.ولا تحاول أن تجعل سكربت Bash يعمل بصلاحيات root عبر setuid، فقد جربنا ذلك في المختبر: أنشأ root سكربتاً يطبع رقم المستخدم الحقيقي والفعال وأعطاه 4755، ثم شغلته سارة (السطر الأخير):
$ printf '#!/bin/bash\necho "real uid: $(id -ru) effective uid: $(id -u)"\n' > /usr/local/bin/whoami-script.sh
$ chmod 4755 /usr/local/bin/whoami-script.sh
$ ls -l /usr/local/bin/whoami-script.sh
-rwsr-xr-x 1 root root 64 Oct 7 05:39 /usr/local/bin/whoami-script.sh
$ whoami-script.sh
real uid: 1000 effective uid: 1000البت موجود، لكن السكربت عمل بحساب سارة، والسبب أن Linux «يتجاهل setuid وsetgid على السكربتات» كما تقول صفحة execve(2). وإذا احتاج مستخدم أن يشغل سكربتاً معيناً بصلاحيات root، فالطريقة الصحيحة هي قاعدة في sudo تسمح له بهذا السكربت وحده.
وإذا رأيت S كبيرة بدل s، فهذا يعني أن البت الخاص موجود لكن x غير موجودة تحته، وهي غالباً خطأ لا معنى له، كما في هذا المثال بحساب root:
$ touch /tmp/plain.txt && chmod u+s /tmp/plain.txt && ls -l /tmp/plain.txt
-rwSr--r-- 1 root root 0 Oct 7 05:39 /tmp/plain.txtمجلد مشترك لفريق مع setgid
على المجلد يعني setgid شيئاً آخر مفيداً جداً: كل ملف جديد في المجلد يأخذ مجموعة المجلد بدل المجموعة الرئيسية لمن أنشأه، وكل مجلد فرعي جديد يرث البت نفسه، كما يشرح دليل GNU coreutils. وهذا ما تحتاجه لمجلد يعمل فيه فريق، والمثال التالي بحساب root في الأسطر الأربعة الأولى ثم سارة:
$ mkdir /srv/team
$ chgrp devs /srv/team
$ chmod 2770 /srv/team
$ ls -ld /srv/team
drwxrws--- 2 root devs 4096 Oct 7 05:39 /srv/team
$ touch /srv/team/notes.md
$ mkdir /srv/team/drafts
$ ls -l /srv/team
total 4
drwxr-sr-x 2 sara devs 4096 Oct 7 05:39 drafts
-rw-r--r-- 1 sara devs 0 Oct 7 05:39 notes.mdالملف والمجلد الجديدان ملك سارة لكن مجموعتهما devs وليست sara، والمجلد الفرعي ورث البت s، أما عمر فليس عضواً في devs لذلك لا يستطيع حتى أن يرى ما في المجلد:
$ ls /srv/team
ls: cannot open directory '/srv/team': Permission deniedولاحظ أن الملف أخذ 644 بسبب القناع 022، أي أن بقية أعضاء الفريق يقرؤونه ولا يعدلونه، فإذا أردت أن يعدل الجميع ملفات بعضهم فاجعل القناع 002 لمن يعمل في هذا المجلد، أو استخدم ACL كما سيأتي.
البت اللاصق Sticky Bit على /tmp
المجلد /tmp يكتب فيه كل المستخدمين، فصلاحياته rwx للجميع، ومعنى w على المجلد كما رأينا أن تحذف أي اسم فيه، فما الذي يمنع عمر من حذف ملفات سارة المؤقتة؟ والإجابة هي الحرف t في آخر صلاحياته:
$ ls -ld /tmp
drwxrwxrwt 1 root root 4096 Oct 7 05:38 /tmpالبت اللاصق على المجلد يعني أن الملف فيه لا يحذف ولا يعاد تسميته إلا بيد مالك الملف أو مالك المجلد أو root، كما تشرح صفحة inode(7). ولنرى حدوده، أنشأت سارة ملفاً في /tmp وأعطته 666:
$ echo "sara's list" > /tmp/sara-list.txt
$ chmod 666 /tmp/sara-list.txt
$ ls -l /tmp/sara-list.txt
-rw-rw-rw- 1 sara sara 12 Oct 7 05:39 /tmp/sara-list.txtثم حاول عمر:
$ echo "omar was here" >> /tmp/sara-list.txt
$ cat /tmp/sara-list.txt
sara's list
omar was here
$ rm /tmp/sara-list.txt
rm: cannot remove '/tmp/sara-list.txt': Operation not permitted
$ mv /tmp/sara-list.txt /tmp/mine.txt
mv: cannot move '/tmp/sara-list.txt' to '/tmp/mine.txt': Operation not permittedالبت اللاصق منع الحذف وإعادة التسمية، لكنه لم يمنع عمر من الكتابة داخل الملف، لأن هذا تحكمه صلاحيات الملف نفسه وهي 666. والقاعدة أن البت اللاصق يحمي الاسم في المجلد، والصلاحيات تحمي المحتوى، فلا تعط ملفاً 666 في أي مكان.
>> التي نجحت في مختبرنا، والسبب الإعداد fs.protected_regular في النواة، الذي يمنع فتح ملف بالخيار O_CREAT في مجلد لاصق يكتب فيه الجميع إذا لم يكن الملف ملك من يفتحه، ويضبطه systemd على 1 في إعداداته الافتراضية. أما نواة Docker Desktop التي جربنا عليها فقيمته فيها 0، ولذلك نجحت الكتابة. وهذه حماية إضافية لا تغني عن الصلاحيات الصحيحة.قوائم التحكم في الوصول ACL باختصار
الصلاحيات الأساسية تعطيك مالكاً واحداً ومجموعة واحدة، فماذا تفعل إذا أردت أن يقرأ عمر ملفاً واحداً من ملفات سارة دون أن تضيفه إلى مجموعتها ودون أن تفتح الملف للجميع؟ والحل هو قوائم التحكم في الوصول Access Control Lists، واختصاراً ACL، وهي صلاحيات إضافية لمستخدمين أو مجموعات بالاسم فوق الثلاثيات الأساسية، كما تشرح صفحة acl(5).
ولم نستطع تجربتها في مختبرنا، لأن الأداتين getfacl وsetfacl غير موجودتين في ال Docker Image التي استخدمناها، ولم نثبتهما حتى لا نحمل حزماً على جهاز مساحته محدودة، لذلك فالأوامر التالية من صفحتي الدليل setfacl(1) وgetfacl(1) وليست مخرجاً من تجربتنا. وعلى Ubuntu وDebian تثبتهما من الحزمة acl:
sudo apt install acl
setfacl -m u:omar:r report.txt # give omar read access to this one file
getfacl report.txt # show the full list
setfacl -d -m g:devs:rw /srv/team # default ACL: new files in the folder get it too
setfacl -x u:omar report.txt # remove omar's entry
setfacl -b report.txt # remove all extra entriesوالملف الذي عليه ACL يظهر في ls -l بعلامة + بعد الصلاحيات، كما يشرح دليل GNU coreutils، لذلك إذا رأيت -rw-r-----+ فاعلم أن الصلاحيات التي تراها ليست كل القصة واقرأ getfacl. واستخدم ACL للاستثناءات فقط، فالسيرفر الذي كل ملف فيه له قائمة مختلفة يصعب أن تراجع صلاحياته أو تعرف من يصل إلى ماذا.
sudo أم الدخول بحساب root؟
الحساب root هو المستخدم رقم 0، والنواة تتجاوز فحص الصلاحيات كله لأي عملية تعمل به، كما رأينا في ترتيب الفحص. وهناك طريقتان لتعمل بهذا الحساب:
- الأمر su: يفتح لك صدفة كاملة بحساب root بعد أن تكتب كلمة مرور root نفسها، فكل من يدير السيرفر يعرف كلمة مرور واحدة مشتركة، وكل ما يكتبه بعد ذلك يعمل بحساب root حتى يخرج، ولا يعرف السجل من كتب ماذا.
- الأمر sudo: يشغل أمراً واحداً بحساب root بعد أن تكتب كلمة مرورك أنت، ووفق قواعد في الملف sudoers تحدد من يحق له ماذا، ويستطيع أن يسجل كل محاولة ناجحة أو فاشلة باسم صاحبها. وهو ما أعددناه في دليل تأمين السيرفر عندما أنشأنا مستخدماً عادياً ومنعنا الدخول المباشر بحساب root.
والسبب في تفضيل sudo ليس الراحة، وإنما أنك تعمل بصلاحيات root لحظة الأمر الذي يحتاجها فقط، وأن لكل شخص حسابه وسجله، وهذا أساس إدارة الوصول المميز الذي شرحناه في مقال IAM وPAM، ومنه تسجيل جلسات sudo وإعادة تشغيلها. أما إعداد sudoers نفسه والمستخدمين والمجموعات فلها عنصر «المستخدمون والمجموعات وصلاحية sudo» في المرحلة.
وهناك خطأ مع sudo يتكرر كثيراً: تريد أن تكتب في ملف يملكه root فتكتب sudo echo "Welcome" > /etc/motd فيظهر Permission denied مع أنك استخدمت sudo. والسبب أن التوجيه > من عمل صدفتك أنت كما شرحنا في مقال سطر الأوامر، فصدفتك هي التي تفتح الملف بحسابك العادي قبل أن يبدأ sudo، ولم يعمل بحساب root إلا echo. وهذا ما رأيناه عندما حاولت سارة الكتابة في الملف دون sudo، فالخطأ صادر من الصدفة لا من الأمر:
$ echo "Welcome" > /etc/motd
bash: line 1: /etc/motd: Permission deniedوالحل أن تجعل الكتابة نفسها تعمل بحساب root، إما بالأمر tee الذي يقرأ stdin ويكتبه في الملف، أو بتشغيل صدفة كاملة بحساب root للأمر كما في أمثلة دليل sudo:
echo "Welcome" | sudo tee /etc/motd > /dev/null
echo "one more line" | sudo tee -a /etc/motd > /dev/null
sudo sh -c 'echo "Welcome" > /etc/motd'ولا تحل المشكلة ب sudo su ثم تكمل عملك كله بحساب root، فهذا يلغي الفائدة التي من أجلها استخدمت sudo.
الأخطاء الشائعة Common Mistakes وكيف تصلحها
chmod 777 لحل أي Permission denied
الحل البسيط الذي تجده في كثير من المنتديات هو chmod -R 777، وهو يحل الخطأ فعلاً لأنه يعطي كل مستخدم على السيرفر كل الصلاحيات، وهذه هي المشكلة. ولنرى ما يعنيه ذلك، نسخت سارة سكربتها إلى /tmp/backup.sh وأعطته 777:
$ cp deploy.sh /tmp/backup.sh && chmod 777 /tmp/backup.sh
$ ls -l /tmp/backup.sh
-rwxrwxrwx 1 sara sara 32 Oct 7 05:43 /tmp/backup.shثم أضاف عمر سطراً إلى السكربت:
$ echo 'echo "omar runs as $(whoami)"' >> /tmp/backup.shوعندما شغلت سارة سكربتها:
$ /tmp/backup.sh
deploying...
omar runs as saraنفذ سطر عمر بحساب سارة، ولو كان السكربت يعمل من cron بحساب root لنفذ بحساب root. وهذا بالضبط ما يحدث عندما يعطى مجلد موقع 777 فيرفع المخترق ملفاً إلى أي مكان فيه، أو يعطى سكربت نسخ احتياطي 777 فيعدله أي حساب على السيرفر ولو كان حساب خدمة مخترقة. بل إن بعض الأدوات ترفض العمل أصلاً مع صلاحيات واسعة، فبرنامج ssh «يتجاهل المفتاح الخاص إذا كان يستطيع الوصول إليه غير صاحبه»، وسيرفر SSH يفحص صلاحيات مجلد المستخدم وملفاته قبل أن يقبل الدخول بسبب الخيار StrictModes المفعل افتراضياً، وهذا أحد أسباب الخطأ الذي شرحناه في Permission denied (publickey).
والطريقة الصحيحة أن تسأل: من الذي يحتاج الوصول؟ ثم تعطيه هو فقط: غير المالك إلى المستخدم الذي يعمل به البرنامج، أو أضفه إلى مجموعة الملف وأعط المجموعة ما تحتاجه، أو استخدم ACL لاستثناء واحد. وللبحث عن الملفات التي يكتب فيها الجميع على سيرفرك:
sudo find / -xdev -type f -perm -0002 2>/dev/null
sudo find / -xdev -type d -perm -0002 ! -perm -1000 2>/dev/nullالأمر الأول يعرض الملفات التي يكتب فيها الجميع، والثاني يعرض المجلدات التي يكتب فيها الجميع دون البت اللاصق، وكلاهما يجب أن يكون فارغاً أو أن تعرف سبب كل سطر فيه.
Permission denied داخل Docker Volume
هذا الخطأ يظهر كثيراً عند تثبيت التطبيقات بالحاويات، مثل ما شرحناه في مشكلة Mattermost مع /mattermost/config، وسببه دائماً واحد: كثير من ال Images لا تعمل بحساب root وإنما بمستخدم له رقم محدد، والمجلد أو ال Volume ملك رقم آخر، والنواة كما قلنا لا تعرف إلا الأرقام. ولنصنع الخطأ بأنفسنا، أنشأنا Volume وأنشأنا فيه مجلداً بحساب root، ثم شغلنا Container بالمستخدم رقم 2000 يحاول الكتابة فيه:
docker volume create lab-linux-perm-data
docker run --rm -v lab-linux-perm-data:/data --entrypoint sh postgres:18 -c 'mkdir -p /data/uploads && ls -ln /data'
docker run --rm --user 2000:2000 -v lab-linux-perm-data:/data --entrypoint sh postgres:18 -c 'id; touch /data/uploads/photo.jpg'والمخرج سوف يكون كما يلي:
total 4
drwxr-xr-x 2 0 0 4096 Oct 7 05:43 uploads
uid=2000 gid=2000 groups=2000
touch: cannot touch '/data/uploads/photo.jpg': Permission deniedالمجلد ملك الرقم 0 أي root وصلاحياته 755، والتطبيق يعمل بالرقم 2000، فطبقت عليه ثلاثية الآخرين r-x التي لا تسمح بالكتابة. والحل ليس 777، وإنما أن تجعل المجلد ملك الرقم الذي يعمل به التطبيق، عبر Container مؤقت بحساب root حتى لا تحتاج إلى معرفة مسار ال Volume على القرص:
docker run --rm -v lab-linux-perm-data:/data --entrypoint chown postgres:18 -R 2000:2000 /data/uploads
docker run --rm --user 2000:2000 -v lab-linux-perm-data:/data --entrypoint sh postgres:18 -c 'touch /data/uploads/photo.jpg && ls -ln /data/uploads'والمخرج سوف يكون كما يلي:
total 0
-rw-r--r-- 1 2000 2000 0 Oct 7 05:44 photo.jpgفي الإعداد أعلاه لاحظ التالي:
- الخيار
-nمعlsيعرض الأرقام بدل الأسماء، وهو ما تحتاجه دائماً مع ال Volumes، لأن الرقم 2000 داخل ال Container قد لا يكون له اسم على السيرفر، أو يكون له اسم مستخدم آخر لا علاقة له بالتطبيق. - لمعرفة الرقم الذي يعمل به تطبيق، شغل
docker compose exec app id، أو اقرأ التعليمةUSERفي Dockerfile الخاص به، أو ابحث في توثيقه، كما فعلنا مع Mattermost الذي يعمل بالرقم 2000. - مع المجلدات المربوطة Bind Mounts المشكلة نفسها والحل نفسه، فتنفذ
sudo chown -R 2000:2000 ./dataعلى المجلد في السيرفر.
والخطأ نفسه يظهر بعد نقل ال Volumes إلى سيرفر جديد إذا تغيرت ملكية الملفات أثناء النقل، وقد شرحنا ذلك في التطبيق لا يستطيع الكتابة بعد الاستعادة.
المالك Owner الخطأ بعد النسخ بحساب root
عندما ينسخ root ملفاً بالأمر cp، فإن النسخة الجديدة ملك root وليست ملك صاحب المجلد الذي وضعت فيه. ولنفرض أن المسؤول جهز ملف إعداد لسارة ونسخه إلى مجلدها بحساب root:
echo "DB_PASSWORD=change-me" > /root/app.env
cp /root/app.env /home/sara/app3.envثم حاولت سارة أن تعدله:
$ ls -l app3.env
-rw-r--r-- 1 root root 22 Oct 7 05:43 app3.env
$ echo x >> app3.env
bash: line 1: app3.env: Permission denied
$ rm -f app3.env
$ ls app3.env
ls: cannot access 'app3.env': No such file or directoryلاحظ شيئين: سارة لا تستطيع تعديل الملف لأنه ملك root، لكنها استطاعت حذفه، لأن الحذف كما رأينا يحتاج w على المجلد، والمجلد مجلدها. ولاحظ أيضاً أن الملف 644، أي أن كلمة المرور فيه يقرؤها أي مستخدم يصل إلى المجلد. والحل أن يصلح root الملكية بعد النسخ، أو الأفضل أن ينسخ بالأمر install الذي يحدد المالك والمجموعة والصلاحيات في خطوة واحدة:
$ cp /root/app.env /home/sara/app.env
$ ls -l /home/sara/app.env
-rw-r--r-- 1 root root 22 Oct 7 05:43 /home/sara/app.env
$ chown sara:sara /home/sara/app.env
$ install -o sara -g sara -m 600 /root/app.env /home/sara/app2.env
$ ls -l /home/sara/app.env /home/sara/app2.env
-rw------- 1 sara sara 22 Oct 7 05:43 /home/sara/app2.env
-rw-r--r-- 1 sara sara 22 Oct 7 05:43 /home/sara/app.envوعندما تنسخ مجلداً كاملاً وتريد أن تبقى الملكية والصلاحيات كما هي، كما في النسخ الاحتياطي أو النقل بين سيرفرين، فاستخدم cp -a أو rsync -a بحساب root، أو tar مع --numeric-owner حتى تحفظ الأرقام لا الأسماء.
السكربت لا يعمل لغياب صلاحية التشغيل Execute
كتبت سكربتاً أو نزلته أو سحبته من Git، ثم شغلته فظهر Permission denied، والسبب أن الملف الجديد يأخذ 644 بسبب القناع كما رأينا، أي بدون x. والمثال التالي من المختبر:
$ ./deploy.sh; echo "exit code: $?"
deploying...
exit code: 0
$ chmod -x deploy.sh
$ ./deploy.sh; echo "exit code: $?"
bash: line 1: ./deploy.sh: Permission denied
exit code: 126
$ bash deploy.sh
deploying...
$ chmod +x deploy.shفي الإعداد أعلاه لاحظ التالي:
- رمز الخروج 126 يعني أن الملف موجود لكنه غير قابل للتشغيل، وهو الرقم الذي شرحناه في مقال سطر الأوامر.
- الأمر
bash deploy.shيعمل دون x، لأن الذي يشغل هنا هوbash، وهو يقرأ السكربت كملف نصي يحتاج r فقط. وهذا يعني أن نزع x ليس وسيلة حماية، فمن يستطيع قراءة السكربت يستطيع تشغيله. - Git يحفظ صلاحية التشغيل مع الملف، فإذا أضفت السكربت من Windows حيث لا توجد هذه الصلاحية، فاضبطها في المستودع نفسه بالأمر
git update-index --chmod=+x deploy.shثم Commit، حتى يصل السكربت قابلاً للتشغيل إلى كل من يسحبه، وانتبه إلى الإعدادcore.fileModeالذي يحدد هل يلاحظ Git تغيير هذه الصلاحية أصلاً.
قائمة تحقق Checklist للصلاحيات على سيرفرك
- لا يوجد ملف يكتب فيه الجميع، ولا مجلد يكتب فيه الجميع دون البت اللاصق، والأمران في قسم chmod 777 يرجعان فارغين.
- ملفات الأسرار مثل
.envوالمفاتيح الخاصة 600 وملك المستخدم الذي يحتاجها فقط، و~/.ssh700. - تعرف قائمة برامج setuid على السيرفر من
find / -xdev -perm -4000، ولم تضف إليها شيئاً. - مجلدات بيانات ال Containers ملك الرقم الذي يعمل به التطبيق، وتفحصها ب
ls -ln. - المجلدات المشتركة بين فريق لها مجموعة و setgid، وليست 777.
- كل مسؤول يدخل بحسابه ويستخدم sudo، ولا أحد يدخل بحساب root مباشرة.
- السكربتات في المستودع محفوظة بصلاحية التشغيل، ولا تحتاج
chmod +xبعد كل سحب.
حذف المختبر Cleanup
docker rm -f lab-linux-perm
docker volume rm lab-linux-perm-dataالخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- الصلاحيات ثلاث ثلاثيات للمالك والمجموعة والآخرين، والنواة تطبق واحدة فقط، أولها الذي ينطبق، وتعمل بالأرقام UID وGID لا بالأسماء.
- على المجلد r تعني قراءة الأسماء، وx الدخول والوصول إلى الملفات، وw الإنشاء والحذف وإعادة التسمية، لذلك فالحذف يتبع صلاحية المجلد لا صلاحية الملف.
- r=4 وw=2 وx=1، فالقيم التي سوف تستخدمها أغلب الوقت 644 و600 للملفات و755 و750 و700 للمجلدات، و
chmod -RيحتاجXالكبير حتى لا يغلق المجلدات. - القناع umask يحذف من الصلاحيات التي يطلبها البرنامج ولا يضيف شيئاً، و022 تعطي 644 و755، و077 تجعل الجديد لصاحبه وحده.
- setuid يجعل البرنامج يعمل بصلاحيات مالكه، وكل برنامج setuid يملكه root باب محتمل إلى root كما في PwnKit، وsetgid على المجلد يناسب مجلدات الفريق، والبت اللاصق يحمي الأسماء في
/tmpولا يحمي المحتوى. - لا تحل Permission denied ب 777 أبداً، وإنما اعرف من يحتاج الوصول وأعطه هو، سواءً بتغيير المالك أو المجموعة أو ب ACL.
دليلسطر الأوامر في Linux للمبتدئين: أول ما تحتاجه على السيرفركيف تتنقل بين الملفات على السيرفر، وتربط الأوامر ببعضها، وتتحكم في العمليات Processes دون أن تحذف شيئاً بالخطأ.
دليلتأمين سيرفر VPS على Ubuntu من أول دخولقبل أن تثبت أي تطبيق على سيرفرك الجديد، هذه الخطوات تغلق أبرز الثغرات وأولها الدخول بمفتاح SSH بدل كلمة المرور.
دليلإدارة الهوية والوصول IAM والوصول المميز PAMمن يملك صلاحية root على سيرفراتك ومنذ متى، وكيف تضبط حسابات المديرين بصلاحيات مؤقتة وجلسات مسجلة.
دليلتثبيت Docker Engine وCompose على Ubuntuكيف تثبت Docker على Ubuntu من المستودع الرسمي، وتضبطه من البداية حتى لا يمتلئ القرص أو تنكشف منافذك للإنترنت.
دليلتثبيت Mattermost Team Edition مع PostgreSQL وترقيته بأمانمساحة محادثة لفريقك على سيرفرك مع Mattermost، تبقى فيها الرسائل والملفات كاملة، فلا يحذفها حد للرسائل ولا خطة مجانية.
دليلنقل Containers إلى خادم جديد دون فقدان البياناتكيف تنقل ال Containers مع بياناتها إلى سيرفر جديد، دفعة واحدة أو تطبيقاً بعد تطبيق، مع خطة للتراجع إذا تعثر النقل.سجل التحديثات
- أكتوبر 2026: كتابة المقال، وتنفيذ التجارب في Container من Docker Image postgres:18 (Debian 13، GNU coreutils 9.7) على Docker Engine 29.8.0، دون تجربة ACL لعدم وجود getfacl وsetfacl في ال Image.