Как ядро проверяет права на файл и почему chmod 777 не помог
«Permission denied» — и первая реакция почти у всех одна: chmod 777 файл. Иногда это правда помогает, а иногда ошибка остаётся один в один, только вы уже потеряли полчаса и заодно раздали файлу права, которые ему были не нужны. Разберёмся, как ядро на самом деле решает, пускать вас к файлу или нет, и почему проблема часто прячется совсем не там, где вы её чините.
Содержание
- Модель прав Unix: владелец, группа, остальные
- Как ядро выбирает, какая категория применяется именно к вам
- Read, write и execute значат разное для файла и для каталога
- Почему chmod 777 на файле не помог: дело было в каталоге
- Когда дело не в Unix-правах вообще: SELinux, AppArmor, ACL
- Как диагностировать отказ, если 777 не сработал
Модель прав Unix: владелец, группа, остальные
У каждого файла и каталога в Linux есть три независимых набора прав, и они не складываются — это не «разрешено, если хотя бы один уровень разрешает». Категории такие:
- владелец (owner) — конкретный пользователь, записанный в inode файла как UID;
- группа (group) — одна группа, записанная как GID; неважно, сколько ещё групп существует в системе, у файла она одна;
- остальные (other) — все, кто не подходит под первые две категории.
Для каждой категории отдельно хранятся три бита: read (r), write (w), execute (x). В выводе ls -l это видно как строка из десяти символов:
$ ls -l deploy.sh
-rwxr-x--- 1 telim devops 1840 авг 28 09:14 deploy.sh
Первый символ — тип файла (- для обычного, d для каталога), дальше идут три триады: rwx для владельца, r-x для группы, --- для остальных. В числовом виде это 750: каждая буква — степень двойки (r=4, w=2, x=1), сумма даёт цифру для категории. 750 значит: владелец может всё, группа devops — читать и запускать, все остальные — вообще ничего.
Отдельно от этих девяти битов есть ещё специальные — setuid, setgid и sticky bit, но это тема для отдельного разговора; здесь важно зафиксировать: то, что вы видите в ls -l, — это ровно три категории, и ядро всегда выбирает из них только одну.
Как ядро выбирает, какая категория применяется именно к вам
Вот ключевой момент, который чаще всего путают. Когда процесс обращается к файлу, ядро не объединяет права владельца, группы и остальных — оно выбирает ровно одну категорию и проверяет только её биты. Логика (упрощённо, как это происходит при системном вызове вроде open()) такая:
- Если эффективный UID процесса совпадает с UID владельца файла — применяются права владельца, и точка. Дальше ядро не смотрит ни на права группы, ни на права остальных, даже если они шире.
- Если UID не совпал, но эффективный GID процесса (или одна из дополнительных групп пользователя) совпадает с GID файла — применяются права группы, и снова точка.
- Если не подошло ни первое, ни второе — применяются права остальных.
Отсюда классическая ловушка. Представим файл:
$ ls -l secret.sh
-rw-rwxrwx 1 telim telim 512 авг 27 18:02 secret.sh
Владельцу telim здесь доступны только rw- — то есть нет execute. Группе и всем остальным разрешено всё, включая запуск. Но если скрипт запускает сам telim, ядро проверит именно права владельца (rw-) и откажет в исполнении — несмотря на то, что у «остальных» стоит rwx. Права группы и остальных для владельца попросту не рассматриваются: он владелец, значит, для него действует только строка владельца.
Это работает и в обратную сторону — именно поэтому chmod 777 не универсальное лекарство: он расширяет права по всем трём категориям сразу, но если процесс упирается не в биты самого файла, а в биты каталога на пути к нему, то хоть 777, хоть 777 дважды — ничего не изменится.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверRead, write и execute значат разное для файла и для каталога
Это второй источник путаницы. Для обычного файла всё интуитивно: r — прочитать содержимое, w — изменить, x — выполнить как программу или скрипт. Но для каталога те же буквы означают совсем другое:
| Бит | Что значит для каталога |
|---|---|
r (read) | можно прочитать список имён файлов внутри (то, что выводит ls) |
w (write) | можно создавать, удалять и переименовывать записи внутри каталога |
x (execute) | можно «войти» в каталог — то есть обратиться к конкретному файлу внутри по имени, получить его метаданные, открыть его |
Execute-бит на каталоге часто называют search-битом — это точнее отражает суть. Без него ядро не даст пройти сквозь этот каталог к файлу внутри, даже если у самого файла права rwxrwxrwx. И наоборот: r без x даёт вам список имён файлов (ls покажет их), но попытка прочитать любой из них или узнать его размер (stat) провалится — у вас есть список, но нет права «войти» и посмотреть, что там.
Здесь же стоит различать локальный доступ и доступ по пути. Чтобы дотянуться до файла /var/www/app/storage/uploads/avatar.jpg, ядру нужно последовательно пройти каталоги /, var, www, app, storage, uploads — и на каждом из них у обращающегося процесса должен быть execute-бит для его категории (владелец/группа/остальные — для этого конкретного каталога, определяется по тем же правилам из предыдущего раздела). Если хотя бы один каталог в цепочке закрыт для этой категории, доступ обрывается на этом шаге, и до прав самого файла дело просто не доходит.
Почему chmod 777 на файле не помог: дело было в каталоге
Типичная история: приложение (веб-сервер, воркер, скрипт деплоя) не может прочитать или записать файл. Админ делает chmod 777 файл, ошибка Permission denied остаётся. Причина почти всегда одна из двух:
Первая — execute-бит закрыт на одном из родительских каталогов. Например, файл лежит в /home/telim/private/data.json, у файла rw-rw-rw-, но у каталога private права rwx------ — доступ только владельцу. Веб-сервер, работающий от www-data, не входит ни в владельца, ни в группу этого каталога — и упирается в стену ещё до того, как ядро вообще посмотрит на права data.json. Права файла в этом случае были не при чём с самого начала.
Проверяется это одной командой — namei показывает права всех каталогов по пути:
$ namei -l /home/telim/private/data.json
f: /home/telim/private/data.json
drwxr-xr-x root root /
drwxr-xr-x root root home
drwx------ telim telim private
-rw-rw-rw- telim telim data.json
Видно сразу: проблема в строке private — там rwx------, и любой, кто не telim, дальше не проходит.
Вторая — путают «применяются права владельца» с «шире, значит, лучше». Если процесс работает от владельца файла, но у владельца стоит rw-, а у группы или у остальных — rwx, добавление execute остальным ничего не даст: для процесса-владельца всё равно действует только строка владельца, как разобрано в разделе выше. Здесь chmod 777 формально сработает — просто потому что расширяет и владельца тоже, — но это лечение симптома вслепую, а не понимание причины.
Практический вывод: прежде чем менять права файла, стоит явно проверить, от какого пользователя работает процесс (ps aux или systemctl show имя-сервиса -p User), и прогнать namei -l по полному пути — это почти всегда быстрее, чем перебирать варианты chmod.
Когда дело не в Unix-правах вообще: SELinux, AppArmor, ACL
Бывает и так: стандартные права разрешают всё, namei -l чист, а доступа всё равно нет. Тогда причина — в одном из дополнительных механизмов, которые работают поверх обычной модели владелец/группа/остальные и могут запретить то, что она разрешила.
SELinux (в дистрибутивах вроде AlmaLinux, RHEL) навешивает на каждый файл и процесс метку контекста безопасности и сверяет её с политикой — независимо от прав rwx. Если процесс и файл размещены с несовпадающим контекстом, доступ будет запрещён, даже если ls -l показывает 777. Проверка:
$ getenforce
Enforcing
$ ls -Z /var/www/app/storage/uploads/avatar.jpg
unconfined_u:object_r:user_home_t:s0 avatar.jpg
Отказы SELinux видны в аудит-логе:
$ ausearch -m avc -ts recent
Если там есть записи denied, значит, дело в контексте, а не в правах, и лечится это не chmod, а restorecon (вернуть ожидаемый контекст) или точечным semanage fcontext / audit2allow. Полностью отключать SELinux командой setenforce 0 — соблазнительно, но такой подход снимает всю защиту разом, а не решает конкретную проблему; мы разбирали, почему это плохая идея и как разбирать блокировки через audit.log.
AppArmor (чаще встречается в Ubuntu/Debian-системах) работает похоже, но привязывается к профилю конкретной программы, а не к меткам файлов: профиль явно перечисляет, какие пути программе можно читать, писать или исполнять. Если путь не описан в профиле — доступ запрещён вне зависимости от Unix-прав. Проверка:
$ aa-status
$ journalctl -xe | grep -i apparmor
POSIX ACL — это отдельный механизм, который, наоборот, расширяет стандартную модель: позволяет выдать права конкретному пользователю или группе сверх обычных владельца/группы/остальных. У файла может быть скромный rw-r-----, но при этом отдельная ACL-запись даёт чтение ещё одному пользователю. Проверяется через getfacl, настраивается через setfacl:
$ getfacl deploy.log
# file: deploy.log
# owner: telim
# group: telim
user::rw-
user:www-data:r--
group::r--
mask::r--
other::---
Важная деталь ACL — запись mask: она задаёт верхнюю границу для всех именованных пользователей и групп в ACL (кроме владельца и other), и даже если вы дадите конкретному пользователю rwx через setfacl, при mask::r-- реально сработает только r--. Это ещё одна причина, по которой «права вроде бы выданы», а доступа нет.
Как диагностировать отказ, если 777 не сработал
Рабочий порядок проверки, от самого вероятного к менее вероятному:
- Кто на самом деле обращается к файлу.
ps aux | grep имя_процессаили, для systemd-сервиса,systemctl show имя-сервиса -p User -p Group. - Права по всему пути, а не только у файла.
namei -l /полный/путь/к/файлу— сразу видно, на каком каталоге обрывается доступ. - ACL.
getfacl /путь/к/файлуиgetfacl /путь/к/каталогу— есть ли записи, которые сужают эффективные права черезmask. - SELinux, если он есть и включён.
getenforce, затемausearch -m avc -ts recentилиjournalctl -t setroubleshoot. - AppArmor, если используется.
aa-status, затемjournalctl -xe | grep -i apparmorилиdmesg | grep -i apparmor. - Опции монтирования. Файловая система, смонтированная с
noexec, запретит запуск исполняемых файлов независимо от правx; проверяется черезmount | grep точка_монтированияилиcat /proc/mounts.
Этот порядок неслучаен: сначала исключаете обычные Unix-права по всей цепочке каталогов (самая частая причина), и только если там всё чисто — переходите к более редким и менее очевидным слоям. Как мы разбирали в истории про перенос 2 ТБ данных через rsync, даже безобидная на вид операция копирования может незаметно поменять владельца или группу файлов — и тогда «права те же самые» на глаз, а на деле уже другой владелец. Разбираться с группами и владельцами вручную удобно через инструменты, описанные в статье про управление пользователями и группами в Linux — она пригодится, если систему настраивали давно и не помните, кто есть кто.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Почему root может открыть любой файл вне зависимости от прав?
Классическая Unix-модель прав (DAC, discretionary access control) для root не действует напрямую — у процессов с UID 0 есть capability CAP_DAC_OVERRIDE, которая позволяет игнорировать проверку rwx. Это отдельный механизм ядра, не «особый случай» в самой проверке прав файла.
Нужен ли execute-бит на файле, чтобы его прочитать?
Нет, для чтения содержимого файла нужен read. Execute нужен, только если файл запускают как программу или скрипт (или если это каталог — тогда execute означает право «войти» в него, как описано выше).
Что если убрать execute с каталога, но оставить read?
ls покажет список имён файлов внутри, но обратиться к любому из них — прочитать, изменить, даже узнать его размер через stat — не получится: для этого нужен execute-бит именно на каталоге.
Даёт ли ACL больше прав, чем позволяет запись mask?
Нет. Значение mask в POSIX ACL — это потолок для всех именованных пользователей и групп в списке (кроме владельца и other). Даже щедрая запись user:kто-то:rwx фактически ограничится маской, если она уже, например mask::r--.
Можно ли одновременно использовать SELinux и ACL?
Да, это независимые слои: сначала ядро проверяет стандартные Unix-права (владелец/группа/остальные, с учётом ACL, если она есть), затем — при включённом SELinux — отдельно сверяет метки контекста. Чтобы файл открылся, должны пройти обе проверки.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →