02:13, деплой «зелений», а ваш скрипт все одно не запускається. Ви ставите chmod. Ви використовуєте sudo. Ви дивитесь на нього так, ніби він винен вам гроші. І ядро відповідає найдратівливішим невизначеним повідомленням у каталозі Unix: Permission denied.
В Ubuntu 24.04 це часто зовсім не про файлові права. Це файловa система каже: «Я збережу ваші байти, але не виконуватиму їх». Це noexec, і це один із тих розумних налаштувань безпеки, що перетворюється на загадку в продакшені, коли ви забуваєте про його існування.
Практична модель: що насправді означає «Permission denied»
Коли ви намагаєтеся запустити ./deploy.sh, відбувається ланцюг рішень дуже швидко:
- Shell перевіряє, що файл існує і що у вас є право на виконання (біт
x). - Ядро викликає
execve(). Тут в силу вступають опції монтування і політики MAC. - Якщо це скрипт, ядро читає shebang (
#!/bin/bash) і намагається виконати інтерпретатор із скриптом як аргументом. - Шари безпеки можуть заборонити: прапори монтування файлової системи (
noexec), профілі AppArmor, systemd sandboxing, обмеження рантайму контейнерів і іноді «корисні» дефолти жорсткого захисту.
Ключова думка: у вас може бути -rwxr-xr-x і все одно з’явиться Permission denied, якщо файлову систему змонтовано з noexec. Режим файлу каже «вам можна виконувати», але монтування каже «ніхто тут нічого не виконує». Монтування перемагає.
Оперативний евристичний підхід: якщо chmod +x не допоміг, вважайте, що проблема — не Unix-права. Вважайте, що це політика.
Цитата для запам’ятовування: вільна парафраза з ідеї Gene Kranz (дисципліна місійного контролю): «Бути жорстким і компетентним означає не приймати невизначені збої — виявити обмеження і спроектувати рішення довкола нього.»
Короткий жарт №1: Noexec — це як готель, що дозволяє вам заселитись, але конфіскує ваші взуття, щоб ви не могли піти. Безпечно? Так. Зручно? Не дуже.
Швидкий план діагностики (перевірити перше/друге/третє)
Якщо ви на виклику, вам не потрібна лекція. Вам потрібна послідовність, що швидко звузить область пошуку.
Перше: підтвердіть точний шлях помилки і текст помилки
- Запустіть прямо (
./script) і також через інтерпретатор (bash script). - Зверніть увагу, чи змінюється помилка: «Permission denied» vs «Exec format error» vs «No such file or directory». Це відповідає різним режимам відмови.
Друге: перевірте опції монтування для каталогу
findmnt -no OPTIONS --target /path— найшвидше джерело правди.- Якщо ви бачите
noexec, припиняйте сперечатися зchmod.
Третє: перевірте системну політику, якщо монти виглядають нормально
- Для сервісів: systemd-опції жорсткого захисту і sandboxing можуть імітувати поведінку noexec.
- Для інтерактивних сесій: відмови AppArmor з’являються в журналі і в
dmesg. - Для контейнерів: підтвердьте опції монтування томів і чи виконується код із ConfigMap/Secret/overlay.
Потім: оберіть найменш ризикове виправлення
- Переважно: перемістіть виконуваний файл в місце з дозволом на виконання (наприклад,
/usr/local/binабо виділений/opt). - Допустимо: перемонтуйте конкретний маунт з
exec, якщо ризик зрозумілий. - Останній аргумент: вимкнути загальні налаштування systemd/AppArmor. Пізніше ви про це пошкодуєте.
Цікаві факти та історія (бо контекст запобігає інцидентам)
- noexec — це атрибут монтування, а не файлу. Ви не зможете «chmod-ом» вирішити політику монтування.
- Ядро все одно дозволяє читати і відображати файл. noexec блокує
execve()і деякі форми виконання з файла, але не забороняє читання файлу або копіювання його кудись ще. - Жорстке підсилення /tmp — давня практика. Монтування
/tmpзnoexecстало поширеним після вірусів і невдалих інсталяторів, які використовували/tmpяк канал розповсюдження. - Скрипти не особливі. noexec застосовується до скриптів і ELF-бінарників однаково. Інтерпретатор сам виконується, але файл скрипта відкривається під правилами exec.
- «Permission denied» — не обов’язково помилка прав. Це загальний
EACCES, що може означати «політика», так само як і «біти режиму». - systemd змінив правила гри. Сервіси можуть мати приватні простори монтування і обмеження, що не відповідають тому, що ви бачите в shell.
- Корпоративні образи люблять noexec. CIS-бенчмарки й внутрішні політики безпеки часто рекомендують
nodev,nosuid,noexecдля світлозаписуваних областей. - Платформи контейнеризації відтворили ту саму проблему. Томі ConfigMap/Secret у Kubernetes зазвичай монтуються з обмеженнями; запуск коду прямо з них навмисно ускладнений.
- NFS додає двозначність.
noexecможе бути на боці клієнта, або виконання може блокуватися політикою сервера та налаштуванням root-squash, що плутає людей.
Практичні завдання: команди, виводи, рішення (12+)
Ось ті речі, які я справді роблю, коли хтось каже «це permission denied», і я відчуваю запах потенційної витрати часу.
Завдання 1: перевірити, що файл виконуваний і це не опечатка з директорією
cr0x@server:~$ ls -l ./deploy.sh
-rwxr-xr-x 1 cr0x cr0x 1843 Dec 30 01:55 ./deploy.sh
Що означає: біт виконання встановлений для власника/групи/інших. Якщо ви все одно отримуєте «Permission denied», ймовірно, справа не в режимі Unix.
Рішення: переходьте до перевірки опцій монтування і політик, а не до додаткових chmod.
Завдання 2: спробувати запустити через інтерпретатор (обминає виконання файлу, але не завжди обходить noexec)
cr0x@server:~$ ./deploy.sh
bash: ./deploy.sh: Permission denied
cr0x@server:~$ bash ./deploy.sh
./deploy.sh: line 12: /tmp/helper: Permission denied
Що означає: сам скрипт може бути читабельним, але він намагається виконати щось інше з noexec-локації (/tmp/helper тут).
Рішення: ідентифікуйте всі шляхи до виконуваних файлів, які використовує скрипт; проблемний часто знаходиться в /tmp, змонтованому робочому каталозі або bind-монті.
Завдання 3: перевірити опції монтування для директорії скрипта
cr0x@server:~$ findmnt -no SOURCE,TARGET,FSTYPE,OPTIONS --target /home/cr0x
/dev/nvme0n1p3 /home ext4 rw,relatime,noexec
Що означає: /home змонтовано з noexec. Будь-яке пряме виконання з /home зазнає невдачі.
Рішення: або перемістіть виконувані файли з /home, або видаліть noexec (після ретельної оцінки моделі загроз).
Завдання 4: підтвердити код помилки ядра через strace (швидкий детектор правди)
cr0x@server:~$ strace -f -e execve ./deploy.sh
execve("./deploy.sh", ["./deploy.sh"], 0x7ffce9c4a3b0 /* 45 vars */) = -1 EACCES (Permission denied)
strace: exec: Permission denied
Що означає: ядро відхилило execve() з EACCES. Це узгоджується з noexec, AppArmor або іншою політикою.
Рішення: перевірте журнали монтування та MAC-політики; не витрачайте час на гіпотези рівня shell.
Завдання 5: переглянути всі маунти з noexec (знайти міни)
cr0x@server:~$ mount | grep -w noexec
/dev/nvme0n1p3 on /home type ext4 (rw,relatime,noexec)
/dev/nvme0n1p4 on /tmp type ext4 (rw,relatime,nodev,nosuid,noexec)
Що означає: і /home, і /tmp — noexec. Це типовий корпоративний базовий набір.
Рішення: вирішіть, де має відбуватися виконання: зазвичай /usr/local/bin, /opt або виділений каталог додатку.
Завдання 6: підтвердити точні прапори монтування через findmnt (точніше ніж mount)
cr0x@server:~$ findmnt -no TARGET,PROPAGATION,OPTIONS /tmp
/tmp shared rw,relatime,nodev,nosuid,noexec
Що означає: propagation = shared важливе у контейнерних контекстах; зміни монтування можуть поширюватись непередбачувано.
Рішення: якщо ви в контейнері, враховуйте propagation у сфері впливу.
Завдання 7: швидке тестове виправлення (тимчасовий remount) щоб довести корінь
cr0x@server:~$ sudo mount -o remount,exec /tmp
cr0x@server:~$ findmnt -no OPTIONS --target /tmp
rw,relatime,nodev,nosuid,exec
Що означає: ви тимчасово зробили /tmp виконуваним для цієї сесії (до наступного remount/reboot або відновлення з fstab).
Рішення: якщо виконання тепер працює, то ви підтвердили, що причиною був noexec. Поверніть назад і реалізуйте безпечніше постійне виправлення (зазвичай не «зробити /tmp exec назавжди»).
Завдання 8: оновити fstab для постійної зміни (тільки коли політика дозволяє)
cr0x@server:~$ grep -nE '\s/tmp\s' /etc/fstab
12:/dev/nvme0n1p4 /tmp ext4 defaults,nodev,nosuid,noexec 0 2
Що означає: fstab зафіксував noexec для /tmp.
Рішення: якщо ви змінюєте це, зробіть це свідомо і задокументуйте причини. Краще: залишити /tmp noexec і перемістити артефакти збірки.
Завдання 9: нудне, але безпечне виправлення — встановити скрипти в шлях для виконання
cr0x@server:~$ sudo install -m 0755 ./deploy.sh /usr/local/bin/deploy
cr0x@server:~$ /usr/local/bin/deploy --help
Usage: deploy [options]
Що означає: ви помістили скрипт у стандартний каталог для виконуваних файлів з правильними правами.
Рішення: надавайте перевагу цьому замість робити світлозаписувані монти виконуваними. Це зменшує поверхню атаки «скачати і запустити».
Завдання 10: перевірити shebang (неправильний шлях інтерпретатора виглядає як дивна помилка прав)
cr0x@server:~$ head -n 1 ./deploy.sh
#!/bin/bash
cr0x@server:~$ ls -l /bin/bash
-rwxr-xr-x 1 root root 1265648 Apr 10 2024 /bin/bash
Що означає: інтерпретатор існує і виконується. Якби shebang вказував на відсутнє місце, ви часто побачили б «No such file or directory», а не «Permission denied», але перевірка варта уваги.
Рішення: для портативності розгляньте #!/usr/bin/env bash (з відомими застереженнями про PATH-контроль у привілейованих контекстах).
Завдання 11: виявити відмови AppArmor (політика може імітувати noexec)
cr0x@server:~$ sudo journalctl -k -g apparmor --since "10 min ago"
Dec 30 02:05:41 server kernel: audit: type=1400 apparmor="DENIED" operation="exec" profile="snap.mytool.mytool" name="/home/cr0x/deploy.sh" pid=22109 comm="mytool" requested_mask="x" denied_mask="x" fsuid=1000 ouid=1000
Що означає: AppArmor відхилив виконання. Це не проблема монтування; це ізоляція.
Рішення: виправити профіль, переносити виконання в дозволений шлях або використовувати інший спосіб пакування. Не вимикайте AppArmor глобально, якщо вам подобається відвідувати зустрічі з комплаєнсом.
Завдання 12: перевірити systemd unit sandboxing при відмові сервісу
cr0x@server:~$ systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Service]
ExecStart=/opt/myapp/bin/myapp
ProtectSystem=strict
PrivateTmp=true
NoNewPrivileges=true
Що означає: PrivateTmp=true дає сервісу власний /tmp, який може бути змонтований інакше, ніж ваш інтерактивний /tmp. Sandboxing змінює видимість файлової системи.
Рішення: відтворіть контекст сервісу (див. наступне завдання) і відкоригуйте unit або розміщення файлів відповідно.
Завдання 13: запустити shell у точній пісочниці сервісу, щоб відтворити
cr0x@server:~$ sudo systemd-run --unit debug-myapp --pty --property=PrivateTmp=true /bin/bash
Running as unit: debug-myapp.service
Press ^] three times within 1s to disconnect TTY.
root@server:/# findmnt -no OPTIONS --target /tmp
rw,nosuid,nodev,noexec,relatime
root@server:/# exit
exit
Що означає: у просторі імен юніта /tmp — noexec. Ваш скрипт може працювати інтерактивно, але падати як сервіс.
Рішення: не виконувати з /tmp у сервісах. Розміщуйте допоміжні бінарники в /usr/libexec, /opt або правильно пакуйте їх.
Завдання 14: перевірити опції монтування для bind-монту (часто пропускають)
cr0x@server:~$ sudo mount --bind /srv/build /mnt/build
cr0x@server:~$ findmnt -no SOURCE,TARGET,OPTIONS /mnt/build
/srv/build /mnt/build rw,relatime
cr0x@server:~$ sudo mount -o remount,bind,noexec /mnt/build
cr0x@server:~$ findmnt -no OPTIONS /mnt/build
rw,relatime,bind,noexec
Що означає: bind-монти можуть мати власні прапори. Хтось міг «допомогти» і перемонтувати bind з noexec, хоча базова файловa система має exec.
Рішення: завжди перевіряйте цільовий mountpoint, з якого ви виконуєте, а не лише бекенд файлової системи.
Завдання 15: підтвердити опції монтування NFS (на клієнті часто стоїть noexec)
cr0x@server:~$ findmnt -no SOURCE,FSTYPE,OPTIONS --target /mnt/tools
nfs01:/exports/tools nfs4 rw,relatime,vers=4.2,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noexec
Що означає: NFS-шар на клієнті змонтований з noexec. Скрипти там не працюватимуть.
Рішення: або примонтуйте з exec (якщо політика дозволяє), або копіюйте інструменти локально в exec-enabled директорію під час деплойменту.
Звідки береться noexec в Ubuntu 24.04
Ubuntu 24.04 не стала раптово ворожою до скриптів. Зміни в тенденціях такі: більше середовищ за замовчуванням використовують жорсткі налаштування, а більше інструментів припускають, що можуть виконувати код «з будь-якого місця». Ці тренди зустрічаються лицем до лиця.
1) fstab і образи з «базовою конфігурацією безпеки»
Найпростіший джерело — /etc/fstab. Підприємства готують образи з окремими розділами для /home, /tmp, іноді /var, і застосовують nodev,nosuid,noexec, щоб зменшити можливість зловживань у записуваних користувачами областях.
Якщо ви звикли до єдиного кореневого розділу, це виглядає екзотично. Якщо вас колись сварили аудитори, це виглядає як вівторок.
2) приватні простори монтування systemd
Systemd може дати сервісам їхній власний світ. У цьому світі може бути:
PrivateTmp=(сервісний власний/tmp)ProtectSystem=іProtectHome=(шляхи лише для читання або приховані)ReadWritePaths=іTemporaryFileSystem=(кастомні маунти)
Результат: ви дивитесь findmnt у своєму shell, бачите exec, але в сервісі все одно відмовляє. Це не газлайтинг; це простори імен.
3) монтування томів у контейнерах та дефолти оркестратора
У контейнерах «файлова система» — колаж: overlay-шари, bind-монти, tmpfs і томи, додані оркестратором. Багато з них навмисно обмежувальні.
Класичний приклад: ConfigMap/Secret у Kubernetes не призначені для запуску програм. Якщо ваша аплікація завантажує плагін у такий том і намагається його виконати, ядро відмовить.
4) політики мережевого зберігання (NFS, CIFS)
Віддалене зберігання додає свою специфіку. На клієнті Linux noexec застосовується ядром клієнта. Окрім того, сервер може контролювати доступ і відображення ідентичностей, що призводить до заплутаних відмов при спробі запустити інтерпретатор або допоміжний бінарник.
5) MAC-політики (AppArmor) і confinement (Snap)
Ubuntu віддає перевагу AppArmor. Snaps додають шари ізоляції. Обидва можуть породжувати EACCES з «Permission denied», навіть коли монти в порядку. Процес у confinement може мати право читати файл, але не виконувати його, або виконувати лише з переліку дозволених шляхів.
Шаблони виправлень: спочатку безпечні, потім постійні
Виправлення A: припиніть виконувати з записуваних тимчасових локацій
Якщо ваш pipeline або інсталятор скидає бінарники в /tmp, /var/tmp, домашню директорію або змонтовану робочу область, а потім запускає їх, ви покладаєтесь на анти-патерн безпеки. Це працює, поки не перестає — і тоді втрачається година вашого життя.
Робіть замість цього:
- Встановлюйте виконувані файли в
/usr/local/bin(адмін-керований) або/opt/<app>/bin. - Для допоміжних бінарників розгляньте
/usr/libexec/<app>(поширено на сучасних дистрибутивах) або тримайте їх поруч із основним бінарником у/opt. - Тримайте записувані runtime-дані в
/var/lib/<app>, а логи в/var/log/<app>.
Цей підхід не бореться з ОС. Він їй співпрацює.
Виправлення B: якщо потрібна записувана збіркова директорія — створіть явно exec-enabled
Деякі робочі процеси справді потребують збирати й запускати артефакти (компілятори, JIT, системи збірки). Якщо ваша політика робить /home noexec, вам потрібна виділена директорія для збірки з дозволом на виконання. Назва повинна явно відображати призначення, бо неоднозначність породжує інциденти.
cr0x@server:~$ sudo mkdir -p /srv/exec-work
cr0x@server:~$ sudo chown cr0x:cr0x /srv/exec-work
cr0x@server:~$ findmnt -no OPTIONS --target /srv
rw,relatime
Що означає: /srv зазвичай має exec, якщо ви його не загартували. Тепер артефакти збірки мають місце без послаблення /home або /tmp.
Рішення: спрямовуйте збірки в /srv/exec-work (або еквівалент) і зберігайте інші частини системи захищеними.
Виправлення C: remount з exec (точково, з відкритими очима)
Іноді ви застрягли: старе ПЗ від вендора очікує виконувати з /home або змонтованого шару інструментів. Якщо не можете швидко рефакторити, можна перемонтувати з exec. Робіть це якомога локальніше.
Добре: виділений маунт для інструментів з exec. Погано: робити /tmp exec, бо «так воно працює».
cr0x@server:~$ sudo mount -o remount,exec /home
cr0x@server:~$ findmnt -no OPTIONS --target /home
rw,relatime,exec
Що означає: політика послаблена. Усе в /home стає виконуваним.
Рішення: якщо ви це робите, компенсуйте інакше (кращий моніторинг, суворіший контроль встановлення ПЗ або перехід до запуску в контейнерах/ВМ для користувачів).
Виправлення D: використовуйте пакування і гігієну деплойменту замість «curl | bash»
Якщо ваша система інсталює, завантажуючи в /tmp і запускаючи одразу, ви наткнетесь на noexec і при цьому привчаєте людей до ризикованих практик. Пакуйте, або принаймні завантажуйте в контрольований exec-шлях і перевіряйте підписи/контрольні суми.
Короткий жарт №2: Єдине гірше за «працює на моїй машині» — «працює на моїй машині, бо моя машина незахищена».
Systemd sandboxing, що виглядає як noexec
Оператори звинувачують systemd у всьому, і systemd заслужено бере частину цієї провини. Але коли воно ламає виконання ваших скриптів, здебільшого воно робить саме те, про що ви (або дистро) попросили: обмежувати, що сервіс може бачити і робити.
Як проявляється ця відмова
- Скрипт працює нормально в SSH-сесії.
- Як сервіс падає з «Permission denied» при виконанні допоміжного бінарника.
- Опції монтування виглядають нормально з хост-шелла.
Чому це відбувається
Юніти systemd можуть працювати у власному просторі імен монтувань. У цьому просторі /tmp може бути приватним tmpfs, інколи з noexec. Або використовується TemporaryFileSystem=, щоб змонтувати записувальне дерево з обмежувальними прапорами.
Що робити
- Відтворіть у просторі юніта за допомогою
systemd-runз подібними властивостями (див. Завдання 13). - Припиніть виконувати з /tmp. Розміщуйте допоміжні бінарники в стабільному каталозі і дайте сервісу право читати/виконувати їх.
- Використовуйте ReadWritePaths розумно. Якщо сервісу потрібна записувана директорія, надайте її вузько, а не широке файлове право.
Приклад точного виправлення на боці сервісу
Якщо ваш сервіс розпаковує інструмент і запускає його, переробіть так, щоб інструмент постачався в пакеті. Якщо не можете, принаймні розпаковуйте в директорію, яку ви контролюєте і яка має відомі властивості монтування (не /tmp всередині сандбоксу).
cr0x@server:~$ sudo systemctl edit myapp.service
# (editor opens)
І додайте щось на кшталт:
cr0x@server:~$ sudo systemctl cat myapp.service
# /etc/systemd/system/myapp.service
[Service]
ExecStart=/opt/myapp/bin/myapp
PrivateTmp=true
ReadWritePaths=/var/lib/myapp
Що означає: ви даєте сервісу записувану директорію стану, зберігаючи решту системи в обмеженнях. Ви не намагаєтесь зробити його приватний /tmp виконуваним, щоб запустити випадковий blob.
Рішення: надавайте перевагу стабільним місцям для виконання і вузьким правам на запис. Це операційний варіант «їсти овочі».
Контейнери, Kubernetes і пастка «змонтовано, але не виконується»
Контейнери люблять монтувати файли: конфіг, секрети, спільні томи, тимчасовий простір. Вони також люблять робити ці маунти обмежувальними, бо один скомпрометований pod не повинен ставати хобі для всього кластера.
Типовий режим відмови в контейнеризації
Ви збираєте образ. Він працює локально. Потім ви монтуєте том поверх /app або /app/bin у Kubernetes. Раптом стартовий скрипт падає з «Permission denied». Ви перевантажуєте образ в надії, що він вибачиться. Він не вибачиться.
Завдання: перевірити опції монтування всередині контейнера
cr0x@server:~$ sudo docker run --rm -it -v /tmp:/mnt/tmp ubuntu:24.04 bash -lc 'findmnt -no TARGET,OPTIONS /mnt/tmp'
/mnt/tmp rw,nosuid,nodev,noexec,relatime
Що означає: bind-монт прийшов як noexec (або був перемонтований з noexec політикою). Виконання з нього не вдасться.
Рішення: не запускайте з цього маунту. Скопіюйте виконувані файли в файлову систему контейнера (шар образу) або змонтуйте том, призначений для виконання.
Особливості Kubernetes (тиха частина)
Томи ConfigMap і Secret — не ваша директорія для плагінів. Якщо вам потрібен скрипт з ConfigMap, поширений патерн: скопіювати його на старті в записувану exec-enabled директорію, а потім запустити звідти. Це вимагає, щоб цільова директорія була exec-enabled.
Завдання: продемонструвати noexec на типовому projected-volume шляху (приклад)
cr0x@server:~$ findmnt -R /var/lib/kubelet/pods 2>/dev/null | head -n 5
TARGET SOURCE FSTYPE OPTIONS
/var/lib/kubelet/pods /dev/sda1 ext4 rw,relatime
/var/lib/kubelet/pods/abcd.../volumes/kubernetes.io~secret/default-token tmpfs tmpfs rw,nosuid,nodev,noexec,relatime,size=4096k
Що означає: секретні томи — tmpfs і зазвичай noexec. Це зроблено навмисно.
Рішення: розглядайте ці маунти як лише для даних. Якщо потрібно виконати скрипт, перенесіть його в образ або в спеціальний exec-enabled том з контрольованими правами.
NFS і віддалені файлові системи: політика виконання та сюрпризи
NFS має свою палітру дивакуватостей. Насамперед через те, що він робить відображення ідентичностей, права доступу та кешування по мережі, і всі думають, що це просто «диск, але далі». Це не зовсім так.
Дві реальності, які потрібно розрізняти
- Прапори монтування на клієнті (
noexec) застосовуються ядром клієнта. Якщо клієнт каже noexec, нічого не запуститься, навіть якщо сервер «дозволив би». - Права на боці сервера все одно важливі для читання і обходу директорій. Багато «Permission denied» на NFS — це фактично права на директорію (біт
x) або root-squash, що ламає інсталятор, який запускається від root.
Завдання: відділити «маунт каже noexec» від «серверні права неправильні»
cr0x@server:~$ findmnt -no OPTIONS --target /mnt/tools
rw,relatime,vers=4.2,hard,timeo=600,retrans=2,noexec
cr0x@server:~$ ls -ld /mnt/tools
drwxr-xr-x 10 root root 4096 Dec 29 18:21 /mnt/tools
Що означає: права директорії виглядають нормально, але монтування каже noexec. Це блокуюче обмеження.
Рішення: перемонтуйте з exec, якщо дозволено, або копіюйте інструменти локально. Зміна Unix-прав не допоможе.
Завдання: перемонтувати NFS з exec (якщо політика дозволяє)
cr0x@server:~$ sudo mount -o remount,exec /mnt/tools
cr0x@server:~$ findmnt -no OPTIONS --target /mnt/tools
rw,relatime,vers=4.2,hard,timeo=600,retrans=2,exec
Що означає: клієнт тепер дозволяє виконання з цього маунту.
Рішення: якщо це «tools share», розгляньте зробити його тільки для читання і exec-enabled, а записувані шари тримати noexec. Змішування write+exec — гарна площадка для шкідливого ПЗ.
Поширені помилки: симптом → корінь → виправлення
-
Симптом:
bash: ./script.sh: Permission denied, хочаls -lпоказує, що файл виконуваний.
Корінь: файлову систему змонтовано зnoexec(часто/homeабо робочий каталог).
Виправлення: перемістити скрипт у/usr/local/binабо перемонтувати конкретну файлову систему зexec. -
Симптом: скрипт працює інтерактивно, але падає в systemd сервісі з «Permission denied» при запуску допоміжного в
/tmp.
Корінь: сервіс маєPrivateTmpабо інше sandboxing з noexec tmpfs.
Виправлення: не запускати допоміжні програми з/tmp; пакувати допоміжні бінарники в/optабо/usr/libexec. -
Симптом:
bash: ./script: /bin/bash^M: bad interpreter: No such file or directory(або подібне).
Корінь: CRLF-кінець рядка; не noexec, але виглядає як «не можна виконати».
Виправлення: конвертувати в LF:sed -i 's/\r$//' script(потім повторно тестувати). -
Симптом:
Permission deniedтільки при виконанні через Snap-пакетований інструмент.
Корінь: AppArmor-конфайнмент забороняє виконання з цього шляху.
Виправлення: виконувати з дозволених шляхів, змінити конфайнмент або встановити версію поза snap, де політика дозволяє. -
Симптом: Pod у Kubernetes не може виконати скрипт зі збереженого в ConfigMap файлу.
Корінь: том ConfigMap змонтовано зnoexec(і часто read-only).
Виправлення: скопіювати в записувану exec-enabled директорію на старті або вбудувати скрипт в образ. -
Симптом: «Permission denied» при виконанні з NFS tools-шару; локальний диск працює.
Корінь: клієнт змонтував NFS зnoexec.
Виправлення: перемонтувати зexecабо синхронізувати/копіювати інструменти локально під час деплойменту. -
Симптом: тільки одна конкретна директорія не дозволяє виконання; батьківський розділ має exec.
Корінь: bind-монт перемонтовано зnoexec(поширено в контейнерах і chroot).
Виправлення:findmnt --targetдля точного шляху і перемонтувати цей bind зexec. -
Симптом: ви виконуєте скрипт, і він падає з «Permission denied» на файл, який намагається запустити з
/var/tmp.
Корінь: деякі базові набори також жорстко захищають/var/tmpзnoexec.
Виправлення: змінити скрипт, щоб покласти виконувані файли в exec-enabled директорію; не припускайте, що/var/tmpвідрізняється від/tmp.
Три корпоративні історії з практики
1) Інцидент через неправильне припущення: «chmod вирішує Permission denied»
Середня компанія розгорнула Ubuntu 24.04 на раннерах збірки. Безпека тихо оновила базовий образ: окремий розділ для /home, змонтований з noexec. Мета була слушною — розробники скачували інструменти в домашні каталоги і запускали їх з сумнівним походженням.
Потім CI-пайплайн почав падати. Не одна задача — усі. Помилка була класична: «Permission denied» при запуску скрипта. Інженер припустив, що проблема в невірному власнику або втраченому бітові виконання, можливо Git зіпсував checkout. Вони додали chmod -R +x у pipeline. Це не допомогло. Воно, втім, створило нову проблему: тепер файли, які не мали бути виконуваними, мали біт виконання, що ускладнило перевірки цілісності.
Інцидент загострився, бо люди ганялися за неправильним шаром. Хтось запустив скрипт через sudo. Хтось звинуватив SELinux (на Ubuntu, у кімнаті повній людей — особливий оптимізм). Третій запропонував «просто відключити захист». Його ввічливо попросили піти за кавою і не торкатись нічого.
Фактичне виправлення зайняло п’ять хвилин, коли задали правильне питання: «що показує findmnt для робочої директорії?» Воно сказало noexec. Pipeline почав копіювати toolchain у виділений exec-enabled каталог під /opt/ci-tools під час налаштування раннера, і скрипти виконувалися звідти. Home залишився noexec. Безпека була задоволена. CI була задоволена. Єдина жертва — віра в те, що chmod — універсальний розчинник.
2) Оптимізація, що обернулась проти команди: запуск з /tmp «щоб було швидше»
Внутрішня платформа захотіла зекономити секунди при запуску Java-сервісу, що використовував нативний допоміжний бінарник. Допоміжний бінарник лежав на спільному NFS-тому. Іноді затримки NFS під час бекапу робили деплой нестабільним. Хтось придумав оптимізацію: копіювати допоміжний файл у /tmp при старті та запускати звідти. «Локальний диск швидший», — казали вони. Вузько кажучи, це правда.
У staging це працювало. У production — падало одразу. Нові загартовані вузли монтували /tmp з nodev,nosuid,noexec. Бінарник можна було скопіювати, але його не можна було запустити. Сервіс заходив у цикл крахів. На виклику бачили «Permission denied» і думали, що проблема в власності. Вони міняли власника. Міняли режим. Розгортали знову. Цикл продовжувався.
Виправлення було простим: не запускати з /tmp. Команда створила /var/lib/myapp/bin, яка належала сервісному користувачу і була на файловій системі з exec. Вони також перестали копіювати при runtime і включили допоміжник у збірку. Залежність від NFS зникла, як і хитрі трюки при старті.
Висновок: оптимізації, що змінюють шлях виконання, — це зміни в безпеці, незалежно від наміру. Ви не можете оптимізувати в обхід політики. Політика переможе, і навіть не напружиться.
3) Сумно, але правильно: «перевіряти опції монтування під час збірки і завантаження в прод»
Регульована організація мала змішаний флот: деякі хости загального призначення, інші — загартовані за базовими профілями. Їх вже колись підводили «таємні Permission denied» інциденти, тому вони додали банальну перевірку до валідації хоста: під час підготовки і щоденних health-checkів вони знімали опції монтування для ключових шляхів (/tmp, /home, каталоги додатків, робочі простори). Вивід надсилали в систему логування — не тому, що це цікаво, а тому, що це шукається о 03:00.
Однієї ночі деплой упав на підмножині вузлів. Сервіс намагався виконати міграційний допоміжник з /var/lib/myapp/tmp. На тих вузлах /var/lib виявився окремим розділом змонтованим noexec через помилково застосований профіль. Код був нормальний. Пакунок був нормальний. Політика файлової системи була неконсистентна.
Замість вгадувань, on-call витягнув останню відому добру знімку mount-опцій для робочого вузла і порівняв з тим, що падав. Різниця була одразу помітна. Команда відкотила профіль на уражених вузлах, потім виправила логіку ролі, щоб тільки призначені розділи отримували noexec.
Нічого героїчного не сталося. Ніхто не «дебажив Linux» в живу на критичній системі. У них були нудні дані, що відповідали на питання. Ось як виглядає надійність, коли вона виростає.
Контрольні списки / покроковий план
Контрольний список: тріаж одного падіння скрипта на Ubuntu 24.04
- Запустіть
ls -lдля скрипта; підтвердіть, що він виконуваний і має очікуваного власника. - Запустіть його напряму і через інтерпретатор; визначте, чи падає сам скрипт або якийсь допоміжний виклик.
- Запустіть
findmnt -no OPTIONS --target <path>для скрипта і будь-яких допоміжних бінарників. - Якщо є
noexec: зупиніться. Вирішіть перенести виконуваний файл або змінити політику монтування. - Якщо
noexecнемає: перевірте відмови AppArmor черезjournalctl -k. - Якщо помилка лише в сервісі: відтворіть у контексті юніта через
systemd-runі проінспектуйтеPrivateTmp/TemporaryFileSystem. - Якщо помилка лише в контейнері: перевірте опції монтування всередині контейнера і який том перекриває шлях.
Контрольний список: вибір виправлення, яке не пристыдить вас пізніше
- Переважно: розмістити виконувані файли в адмін-керованому exec-шляху (
/usr/local/bin,/opt). - Наступне найкраще: створити виділений exec-enabled робочий простір і задокументувати його.
- Ризиковано: перемонтувати широкі записувані маунти з
exec. - Зазвичай неправильно: робити
/tmpexec постійно, щоб задовольнити збірковий інструмент. - Операційна вимога: якщо ви змінюєте політику монтування, переконайтесь, що зміна переживе перезавантаження (fstab/systemd mount units) і є консистентною по флоту.
Контрольний список: валідація виправлення
- Повторно запустіть
findmntдля цільового шляху і підтвердьте, що набір опцій відповідає очікуваному. - Запустіть скрипт під тим самим користувачем, що і в продакшені (сервісний), а не під root, якщо це не потрібно.
- Якщо systemd-сервіс: перезапустіть юніт, потім перевірте
systemctl statusі логи. - Якщо контейнер: повторно розгорніть і підтвердьте, що той самий шлях не перекривається томом у проді.
FAQ
1) Чому я бачу «Permission denied», коли файл має chmod +x?
Тому що монтування файлової системи може заборонити виконання через noexec. Ядро відхиляє execve() незалежно від бітів режиму. Перевірте через findmnt -no OPTIONS --target /path.
2) Це noexec — функція безпеки чи лише незручність?
І те, і інше. Воно суттєво зменшує можливість «скинути payload у записуваний директорій та запустити його»-тип атак. Воно також дратує неакуратні шаблони інсталяції, що трактують записувані області як місця для виконання.
3) Чи можна обійти noexec, запустивши bash script.sh?
Іноді можна запустити скрипт через інтерпретатор, бо ви виконуєте /bin/bash, а не сам файл скрипта як бінарник. Але багато сценаріїв noexec все одно блокують виконання допоміжних бінарників, які скрипт викликає, і деякі політики також накладають обмеження. Не покладайтеся на це як на «виправлення».
4) Чи обходить sudo noexec?
Ні. noexec застосовується ядром на тому маунті для всіх користувачів, включно з root.
5) Яке найбезпечніше постійне виправлення?
Припиніть виконувати з загартованих записуваних маунтів. Встановлюйте скрипти/бінарники в /usr/local/bin або /opt/<app> і тримайте записувані runtime-дані в інших місцях (/var/lib).
6) Чому скрипт працює в моєму шеллі, але падає в systemd-сервісі?
Тому що сервіс може працювати в іншому просторі імен монтувань з іншими прапорами (PrivateTmp, TemporaryFileSystem) і обмеженнями доступу (ProtectSystem, ProtectHome).
7) Як зрозуміти, AppArmor чи noexec?
Перевірте журнали ядра: sudo journalctl -k -g apparmor. Відмови AppArmor явні і містять operation="exec" та назву профілю. noexec не згенерує запис AppArmor.
8) Чи слід прибрати noexec з /tmp?
Зазвичай ні. Якщо інструмент вимагає виконання з /tmp, розглядайте це як дизайн-помилку і перемістіть виконуваний файл. Якщо мусите змінити — робіть це вузько і з компенсуючими контролями.
9) А NFS? Чи «noexec» застосовується сервером?
На Linux-клієнтах noexec — опція монтування, яку застосовує клієнтське ядро. Сервер усе одно може створити проблеми через стандартні Unix-права і відображення ідентичностей, але саме noexec зазвичай клієнтське.
10) Я змінив опції монтування, але після перезавантаження проблема повернулася. Чому?
Ваш remount був тимчасовим. Постійна поведінка задається через /etc/fstab або systemd mount-юніти. Виправте джерело істини і забезпечте консистентність по хостах.
Висновок: що змінити в понеділок вранці
Якщо Ubuntu 24.04 каже вам «Permission denied» при виконанні скрипта, вірте йому — але не думайте одразу, що це про chmod. Ваш перший підозрюваний — noexec на маунті, що містить скрипт або один із його допоміжників. Другий підозрюваний — systemd sandboxing. Третій — AppArmor або поведінка контейнерних томів.
Наступні кроки, що приносять користь:
- Уніфікуйте, де зберігаються виконувані файли (
/usr/local/bin,/opt) і припиніть виконувати з/tmp//home/workspaces. - Додайте легкий health-check, що фіксує опції монтування для ключових шляхів по флоту.
- Для сервісів аудитуйте налаштування жорсткого захисту systemd і переконайтесь, що ваше ПЗ не залежить від виконання з епемерних директорій.
- Для контейнерів трактуйте змонтовані конфіги як лише для даних; виконуйте з образу або з призначеного exec-enabled тому.
ОС робить вам послугу. Вона просто робить це голосно, вночі, поки ваш пейджер пильно дивиться.