Почему одного chmod почти никогда не хватает и как в реальности работают владелец, группы, ACL, capabilities и SELinux.

На файл уже выставили 777. Команда ls -l подтверждает: владелец, группа и остальные пользователи получили чтение, запись и выполнение. Попытка открыть файл снова заканчивается тем же сообщением: Permission denied.
Причина обычно не в том, что Linux «не заметил» новые права. Биты rwx описывают лишь одну проверку из нескольких. Ядро должно сопоставить файл с идентификаторами процесса, пройти по каждому каталогу в пути, учесть ACL, атрибуты объекта, режим файловой системы, capabilities и, если механизм включён, политику SELinux или AppArmor.
Поэтому файл может быть открыт для всех и оставаться недоступным на практике. Родительский каталог без права прохода остановит обращение раньше, чем система дойдёт до прав самого файла. Тот же результат дадут ограничивающая ACL-маска, раздел только для чтения, атрибут immutable или запрет мандатной политики.
Начинать разбор такого отказа с очередного chmod бессмысленно. Сначала нужно понять, какой процесс обращается к объекту и на каком слое доступ обрывается.
В базовой модели Linux каждый файл и каталог хранит права для трёх категорий: владельца, группы и всех остальных. Категория выбирается не по имени человека, который смотрит на экран, а по UID и группам процесса, выполняющего операцию.
Строка из ls -l может выглядеть так:
-rw-r----- 1 alice developers 2048 Apr 30 10:15 report.txt
Первый символ показывает тип объекта. Знак - обозначает обычный файл, d появляется у каталога, l у символической ссылки. Следующие девять символов разбиваются на три тройки:
rw- r-- ---
Владелец alice читает и изменяет report.txt. Группа developers получает только чтение. Любой пользователь вне этой группы не получает доступа.
Команда chmod меняет наборы rwx, но не передаёт файл другому владельцу и не назначает новую группу. Для этого служат chown и chgrp:
chown alice:developers report.txt
Для обычного файла буквы читаются привычно. r разрешает получить содержимое, w позволяет его изменить, x даёт запускать файл как программу. Скрипт без x можно открыть в редакторе или передать интерпретатору, но прямой вызов через ./script.sh завершится отказом.
С каталогами действуют другие правила. Бит r разрешает вывести список находящихся внутри имён. Бит w позволяет добавлять, удалять и переименовывать записи. Бит x нужен для прохода через каталог к вложенному объекту.
Именно x чаще всего объясняет ситуацию, когда файл «открыт», но не открывается. Процесс может знать точное имя объекта и видеть корректные права конечного файла, однако каталог без права прохода не позволит обратиться к нему.
Допустим, приложению нужен файл /srv/app/config.yml. Для чтения мало разрешения r на самом config.yml. Процесс должен пройти через /srv, затем через /srv/app. Если хотя бы на одном из этих каталогов не хватает x, ядро вернёт отказ ещё до проверки содержимого файла.
По этой причине одиночная команда ls -l /srv/app/config.yml нередко показывает правду, но не показывает причину сбоя. Проверять нужно весь путь.
Права можно задавать буквами или числами. Числовой формат удобен, когда требуется сразу установить законченный режим. Символьный формат подходит для аккуратной правки одного разрешения, без пересборки остальных битов.
В числовой записи каждому действию соответствует значение: чтение получает 4, запись 2, выполнение 1. Значения складываются внутри каждой тройки. Поэтому rwx обозначается цифрой 7, rw- цифрой 6, r-- цифрой 4, а отсутствие прав записывается как 0.
chmod 640 report.txt
Режим 640 даёт владельцу чтение и запись, группе оставляет чтение, для остальных закрывает файл полностью. Для конфигурации сервиса, файла с настройками приложения или внутреннего отчёта такой режим обычно соответствует задаче точнее, чем открытие объекта всем пользователям системы.
Символьная запись chmod позволяет указать адресата и конкретное изменение. Буква u относится к владельцу, g к группе, o к остальным, a ко всем трём категориям. Знак + добавляет разрешение, - снимает, = заменяет текущий набор заданным.
chmod g+w shared.txt
chmod o-r secret.txt
chmod u=rw,g=r,o= file.txt
Команда chmod o-r secret.txt удаляет чтение только у посторонних пользователей. Владельца и группу правка не касается. На рабочем сервере такая операция предсказуемее, чем назначение нового числового режима, который случайно снимет нужный бит или, наоборот, откроет лишний.
Режим файла нельзя читать в отрыве от владельца и группы. Два объекта с правами 640 могут дать одному процессу разные результаты, поскольку ядро сначала выяснит, относится ли процесс к группе конкретного файла.
-rw-r----- 1 root nginx 1200 site.conf
-rw-r----- 1 root mysql 1200 database.conf
Процесс, входящий в группу nginx, прочитает site.conf. Файл database.conf для него останется закрытым, если процесс не входит также в группу mysql. Цифры совпадают. Решение меняет групповая принадлежность.
Отсюда возникает ещё один знакомый отказ. Администратор добавляет пользователя в группу, после чего доступ всё равно не появляется. Уже работающая оболочка сохраняет список групп, полученный при старте сессии. Новый состав начинает действовать после повторного входа или запуска newgrp:
usermod -aG docker alice
newgrp docker
Проверять нужно не сам факт записи пользователя в группу, а полномочия процесса, который прямо сейчас пытается открыть файл.
При удалении Linux меняет не содержимое файла, а запись о нём в каталоге. Каталог связывает имя объекта с inode, поэтому решающими становятся права на родительскую директорию.
Пользователь с битами w и x на каталог может удалить находящийся внутри файл, даже когда сам файл ему не принадлежит и недоступен для записи. В общей рабочей директории такое поведение быстро превращается в проблему: участники получают возможность стирать чужие данные.
Для каталогов с совместной записью предусмотрен sticky bit. Его можно увидеть на примере /tmp:
drwxrwxrwt 10 root root 4096 /tmp
Последняя буква t показывает, что sticky bit включён. Пользователи создают файлы в каталоге, однако удалить чужой объект может только владелец файла, владелец каталога или root. Без такого ограничения общий временный каталог позволял бы одному пользователю очищать данные другого.
Помимо обычных rwx, Linux поддерживает специальные биты. В строке прав их выдают символы s и t.
Бит SUID на исполняемом файле запускает программу с эффективными правами владельца файла. Утилита passwd служит классическим примером. Пользователь должен менять собственный пароль, хотя прямой доступ к системному хранилищу хешей ему не разрешён.
-rwsr-xr-x 1 root root /usr/bin/passwd
Символ s на месте пользовательского x указывает на SUID. Программа получает нужные полномочия во время выполнения предусмотренной операции.
Для общего каталога часто полезнее SGID. Если включить такой бит на директории, новые файлы наследуют её группу, а не основную группу пользователя, который создал объект:
chgrp developers /srv/shared
chmod g+s /srv/shared
После настройки файлы внутри /srv/shared будут создаваться с группой developers. Для проектных каталогов такой режим снимает постоянную необходимость вручную исправлять групповую принадлежность.
SUID требует осторожности. Уязвимость в программе, работающей с правами владельца root, способна превратиться в повышение привилегий. Поэтому узкие задачи всё чаще решают через capabilities, а не через запуск программы с полным набором административных возможностей.
Команда chmod работает с уже существующим объектом. При создании новых файлов и каталогов в игру вступает umask. Маска снимает часть разрешений из набора, который запросило приложение.
umask
0022
При маске 0022 обычный файл чаще всего появляется с правами 644, а каталог с 755. Владелец может менять содержимое, группа и остальные получают чтение без записи.
Для общей рабочей директории нередко используют маску 0002, чтобы участники группы могли редактировать созданные коллегами файлы:
umask 0002
У служб маска может отличаться от настроек интерактивной оболочки администратора. В systemd её задаёт директива UMask внутри unit-файла. Поэтому приложение способно создавать слишком закрытые файлы не из-за ошибки в своём коде, а из-за параметров запуска службы.
Трёх стандартных категорий иногда недостаточно. Файл может принадлежать alice и группе developers, тогда как сотруднику bob требуется чтение одного конкретного отчёта без включения в рабочую группу. Такой доступ задают через ACL, расширенные списки прав.
Посмотреть действующие правила можно командой getfacl:
getfacl report.txt
Доступ на чтение для отдельного пользователя добавляется так:
setfacl -m u:bob:r report.txt
Для отдельной группы применяется похожая запись:
setfacl -m g:auditors:rx /srv/reports
Присутствие расширенных правил видно в выводе ls -l по знаку + после стандартных битов:
-rw-r-----+ 1 alice developers 2048 report.txt
Если строка содержит такой знак, одной проверки chmod уже недостаточно. Реальное решение может зависеть от ACL, скрытой за обычной тройкой прав.
У ACL есть маска, которая задаёт верхнюю границу доступа для именованных пользователей и групп. Отдельная запись может содержать rwx, но эффективные права окажутся уже, если маска разрешает только чтение и выполнение:
setfacl -m m::rx /srv/reports
Каталогам можно назначить ACL по умолчанию. Тогда новые файлы и вложенные директории получат правила автоматически:
setfacl -d -m g:developers:rwx /srv/project
При неожиданном отказе или неожиданно широком доступе проверка ACL должна идти сразу после просмотра обычных прав.
Linux принимает решение по идентификаторам процесса. У каждого процесса есть реальный и эффективный UID, основная группа и набор дополнительных групп. При обычном запуске значения обычно совпадают с учётной записью пользователя. После sudo, SUID, запуска через systemd или работы внутри контейнера совпадение исчезает.
Полномочия текущей оболочки показывает команда id:
uid=1000(alice) gid=1000(alice) groups=1000(alice),1001(developers)
Сервисные процессы часто вводят администратора в заблуждение. Команда, запущенная вручную, успешно читает конфигурацию. Та же программа, стартовавшая через systemd от www-data, получает отказ. Файл не изменился. Изменился процесс, от имени которого идёт обращение.
Владельца уже работающего процесса можно проверить так:
ps -o user,group,comm -p 1234
В классической Unix-модели root обходит почти все ограничения обычных прав. Современный Linux добавляет механизмы, способные остановить и административный процесс: SELinux, AppArmor, пространства имён, параметры монтирования, специальные атрибуты файлов и ограниченный набор capabilities.
Файл с атрибутом immutable нельзя изменить или удалить, пока атрибут не снят. Одни только права rwx такого отказа не объяснят:
lsattr config.yml
----i--------- config.yml
chattr -i config.yml
Ограничения файловой системы действуют ещё прямее. Раздел с параметром noexec не позволит напрямую запустить исполняемый файл, хотя бит x будет присутствовать. Параметр ro запрещает запись независимо от режима конкретного объекта:
mount | grep /srv
В контейнере root также не обязан совпадать с root хостовой системы. Пространства имён, профили безопасности и параметры запуска могут оставить контейнерному процессу только ограниченный набор полномочий.
Capabilities делят административные полномочия на отдельные разрешения. Веб-серверу, например, не обязательно работать от root целиком, если от административного режима ему нужна только возможность слушать порт ниже 1024.
Право привязки к порту 80 можно назначить бинарному файлу отдельно:
setcap 'cap_net_bind_service=+ep' /usr/local/bin/myserver
getcap /usr/local/bin/myserver
При компрометации такого сервиса атакующий получает конкретную выданную возможность, а не все полномочия root. Ограничение работает лишь тогда, когда capability действительно узкая.
Некоторые capabilities слишком широки для спокойной выдачи. Примером служит cap_sys_admin: набор разрешённых через него операций настолько велик, что процесс получает значительную часть административной власти.
Поэтому при аудите исполняемых файлов искать нужно не только SUID, но и capabilities:
getcap -r /usr/bin /usr/local/bin 2>/dev/null
SELinux добавляет мандатную политику поверх классических Unix-прав. Обычные биты отвечают на вопрос, разрешён ли доступ владельцу, группе или остальным. SELinux проверяет, вправе ли процесс конкретного типа выполнять нужную операцию с объектом конкретного типа.
Поэтому файл с режимом 644, ожидаемым владельцем и подходящей группой всё равно может вернуть Permission denied. Для SELinux решающим становится контекст безопасности:
ls -Z /var/www/html
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 index.html
В данном контексте ключевую роль играет тип httpd_sys_content_t. Политика разрешает веб-серверу httpd читать файлы такого типа, поскольку они относятся к публикуемому содержимому сайта.
Если перенести сайт в нестандартный каталог, классические права могут сохраниться, а контекст SELinux окажется неподходящим. Веб-сервер получит отказ, пока администратор не назначит корректное правило и не восстановит разметку:
semanage fcontext -a -t httpd_sys_content_t '/srv/site(/.*)?'
restorecon -Rv /srv/site
SELinux поддерживает три режима. Enforcing блокирует действия, запрещённые политикой. Permissive пропускает операции, но записывает нарушения в журнал. Disabled полностью отключает механизм.
getenforce
sestatus
На системах семейства RHEL, Fedora и CentOS Stream SELinux встречается регулярно и часто включён по умолчанию. В Debian и Ubuntu чаще применяют AppArmor: задача похожа, правила и инструменты отличаются.
При первом отказе SELinux легко принять механизм защиты за источник проблемы и просто выключить его. Такой шаг убирает блокировку вместе с контролем, но не объясняет, какое действие приложение пыталось выполнить и почему политика его остановила.
События отказа ищут в журнале:
ausearch -m avc -ts recent
journalctl -t setroubleshoot
Запись AVC обычно содержит домен процесса, тип объекта, запрошенное действие и результат проверки. Веб-сервер, например, может пытаться записать данные в каталог, помеченный как статическое содержимое. Читать такие файлы политика разрешает, изменять их запрещает.
Если приложению действительно требуется каталог для загрузок, каталогу назначают тип, допускающий запись со стороны веб-процесса. Для httpd используется httpd_sys_rw_content_t:
semanage fcontext -a -t httpd_sys_rw_content_t '/srv/site/uploads(/.*)?'
restorecon -Rv /srv/site/uploads
Политика при такой настройке продолжает работать. Запись открывается только для каталога, которому она нужна по назначению.
Отказ доступа лучше разбирать последовательно, не меняя права наугад. Сначала нужно узнать, от чьего имени работает процесс. Для команды в терминале подходит id; для службы потребуется проверить unit systemd и владельца запущенного процесса.
Следом проверяют весь путь к объекту. Команда namei -l показывает права каждого каталога и файла в цепочке:
namei -l /srv/site/config.yml
Такой вывод быстро обнаруживает каталог без x, который невозможно заметить по правам одного только config.yml.
После пути проверяются обычные права и ACL:
ls -l /srv/site/config.yml
getfacl /srv/site/config.yml
Затем нужно исключить ограничения точки монтирования и специальные атрибуты:
mount | grep /srv
lsattr /srv/site/config.yml
В системе с SELinux дополнительно проверяют активный режим, контекст объекта и свежие события AVC:
getenforce
ls -Z /srv/site/config.yml
ausearch -m avc -ts recent
Такой порядок показывает, где оборвался доступ, и позволяет исправить конкретную причину вместо беспорядочной смены разрешений.
Для обычного текстового файла, который должны читать другие пользователи, часто достаточно режима 644. Секреты, приватные ключи и файлы с паролями лучше оставлять доступными только владельцу:
chmod 644 public.txt
chmod 600 secret.key
Каталог с публично читаемым содержимым обычно получает 755. Закрытая директория пользователя подходит под режим 700:
chmod 755 /srv/site
chmod 700 /home/alice/private
Для совместной работы над проектом пригодится общая группа, SGID и наследуемые ACL:
chgrp developers /srv/project
chmod 2775 /srv/project
setfacl -d -m g:developers:rwx /srv/project
Режим 2775 включает SGID на каталоге и сохраняет групповую запись. Новые объекты получают группу проекта, а ACL задаёт нужные права для создаваемых внутри файлов и подкаталогов.
Для ключей SSH требования строже. Клиент не станет использовать приватный ключ, доступный группе или посторонним пользователям:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
Веб-сайту не нужен режим 777 на всём дереве каталогов. Код и статические файлы можно оставить веб-процессу только для чтения, а запись разрешить в отдельной директории загрузок:
chown -R root:nginx /srv/site
chmod -R u=rwX,g=rX,o= /srv/site
chown -R nginx:nginx /srv/site/uploads
chmod -R u=rwX,g=rwX,o= /srv/site/uploads
При включённом SELinux для каталога загрузок потребуется также назначить тип, который разрешает запись веб-серверу.
Команда chmod 777 выдаёт чтение, запись и выполнение владельцу, группе и всем остальным пользователям системы. Если отказ вызван каталогом без права прохода, ACL, параметром ro, атрибутом immutable или политикой SELinux, такая команда ничего не починит.
Зато новые риски появятся сразу. В каталоге загрузок посторонний процесс сможет менять файлы. В директории, откуда приложение запускает код, всеобщая запись открывает путь к подмене компонентов. Во временной рабочей папке широкие права позволяют вмешиваться в данные соседнего сервиса.
Проверять нужно конкретную задачу процесса: под какой учётной записью он работает, какие файлы обязан читать, куда действительно должен писать и какая проверка блокирует операцию. После такого разбора обычно достаточно изменить владельца, группу, ACL, контекст SELinux или права одного каталога.
chmod управляет базовыми битами доступа. Команда не описывает роль процесса, назначение каталога, контекст SELinux, режим монтирования или границы контейнера. Настройка прав начинается с действий, которые сервису нужны для работы, и с действий, которые ему нужно запретить даже после взлома.
Веб-процесс не должен записывать код приложения. Конфигурационные файлы требуется открывать только службе и административной группе. Загрузки следует хранить отдельно от исполняемых компонентов. Сервису лучше работать от выделенного пользователя, capabilities выдавать точечно, а обращения за пределами ожидаемого сценария ограничивать через SELinux или AppArmor.
Такая схема не устраняет уязвимость в приложении, если она уже существует, но сокращает последствия компрометации. Захваченный веб-процесс не сможет беспрепятственно переписать код, изменить конфигурацию или читать соседние каталоги.
Файл с режимом 777 и сообщением Permission denied требует проверки пути, процесса, ACL, точки монтирования и политики доступа, а не ещё одной попытки открыть всё всем.
Базовые правила описаны в страницах руководства chmod, chown и acl. Для работы с мандатными политиками пригодятся материалы SELinux Project и документация Red Hat по SELinux.