С чего начать Первые шаги трек · 15 ступеней
Linux Labs

Capabilities: права root по частям

10 мин Ubuntu 24.04 · Linux 6.8 · systemd 255

Части прав root: процесс или файл получает только нужную — например, CAP_NET_BIND_SERVICE для порта ниже 1024.

Шпаргалка

7 команд Версия для печати
  • grep Cap /proc/self/status наборы текущего процесса
  • capsh --decode=0000000000000400 маска → имена
  • getpcaps 1234 capabilities процесса по PID
  • getcap -r / 2>/dev/null файлы с capabilities
  • sudo setcap cap_net_bind_service=+ep /usr/local/bin/app выдать файлу
  • sudo setcap -r /usr/local/bin/app снять
  • sudo capsh --drop=cap_dac_override -- -c 'cat f' root без части

Обзор

Ядро проверяет права двумя способами. Обычному процессу — по правам файла и его владельцу. Процессу root раньше разрешалось всё. Теперь сила root разделена на части — capabilities (по-русски их зовут возможностями), и ядро спрашивает конкретную часть: слушать порт ниже 1024 можно с CAP_NET_BIND_SERVICE, читать чужой файл — с CAP_DAC_OVERRIDE.

У root по умолчанию все части, поэтому он может всё. Но процесс root без нужной части получает отказ, а процесс обычного пользователя с одной частью делает то, что она разрешает, и ничего сверх. На этом стоит принцип минимальных привилегий: службе — её часть, а не весь root.

На машине их 41. Список печатает capsh --decode=000001ffffffffff; частые:

Capability Что разрешает Кому нужна
CAP_NET_BIND_SERVICE слушать порт ниже 1024 веб-сервер на 80 и 443
CAP_NET_RAW сырые сокеты: ICMP, свои пакеты ping, mtr
CAP_NET_ADMIN интерфейсы, маршруты, правила файрвола systemd-networkd, ip, nft
CAP_DAC_OVERRIDE обходить права rwx файлов root, читающий чужой файл
CAP_DAC_READ_SEARCH читать любой файл и проходить любой каталог резервное копирование
CAP_CHOWN менять владельца файла установка пакетов
CAP_SETUID, CAP_SETGID сменить UID и GID процесса su, sudo, sshd
CAP_KILL сигналы чужим процессам kill от root
CAP_SYS_TIME менять системное время systemd-timesyncd
CAP_SYS_PTRACE трассировать чужие процессы strace -p
CAP_SYS_ADMIN монтирование и десятки других операций почти весь root

Наборы процесса

У процесса пять наборов. Ядро хранит их битовыми масками и показывает в /proc/<PID>/status:

Строка Набор Что значит
CapEff effective действует сейчас — его проверяет ядро
CapPrm permitted что процесс может включить в effective
CapInh inheritable что может перейти в новую программу через exec
CapBnd bounding потолок: больше этого не получат ни процесс, ни потомки
CapAmb ambient переходит в новую программу и без capabilities файла
text
student@ubuntu:~$ grep Cap /proc/self/status
CapInh:	0000000000000000
CapPrm:	0000000000000000
CapEff:	0000000000000000
CapBnd:	000001ffffffffff
CapAmb:	0000000000000000
student@ubuntu:~$ sudo grep Cap /proc/self/status
CapInh:	0000000000000000
CapPrm:	000001ffffffffff
CapEff:	000001ffffffffff
CapBnd:	000001ffffffffff
CapAmb:	0000000000000000

У student действующих частей нет, у процесса под sudo — все 41. Маску в имена переводит capsh --decode=<маска>, процесс по PID показывает getpcaps:

text
student@ubuntu:~$ getpcaps $(pgrep -xo systemd-timesyn)
456: cap_sys_time=eip
student@ubuntu:~$ getpcaps $(pgrep -xo systemd-resolve)
451: cap_net_raw=eip
student@ubuntu:~$ getpcaps $(pgrep -xo nginx)
611: =ep

Буквы после = — наборы: e effective, i inheritable, p permitted. Службы времени и имён работают от своих пользователей с одной частью каждая. =ep без имён — все части сразу: главный процесс nginx работает от root.

Capabilities файла

Исполняемый файл тоже может нести capabilities — как setuid, только вместо всего root процесс получает названные части. Таких файлов на машине три:

text
student@ubuntu:~$ getcap -r / 2>/dev/null
/usr/bin/mtr-packet cap_net_raw=ep
/usr/bin/ping cap_net_raw=ep
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper
  cap_net_bind_service,cap_net_admin,cap_sys_nice=ep

Поэтому ping работает у student: ICMP без привилегий на машине выключен (net.ipv4.ping_group_range = 1 0), и CAP_NET_RAW программа получает от своего файла.

Выдаёт части файлу setcap, и только root. Проверка — скрипт bind81.py, который занимает порт 81:

python
import socket
s = socket.socket()
s.bind(('127.0.0.1', 81))
print('bound 81')

Часть получает копия Python: на символическую ссылку /usr/bin/python3 setcap не ставит.

bash
sudo cp /usr/bin/python3.12 /usr/local/bin/py-bind
sudo setcap cap_net_bind_service=+ep /usr/local/bin/py-bind
text
student@ubuntu:~$ python3 bind81.py
PermissionError: [Errno 13] Permission denied
student@ubuntu:~$ getcap /usr/local/bin/py-bind
/usr/local/bin/py-bind cap_net_bind_service=ep
student@ubuntu:~$ /usr/local/bin/py-bind bind81.py
bound 81

Процесс из такого файла — обычный student с одной частью: CapEff у него 0000000000000400, это cap_net_bind_service. Граница непривилегированных портов — net.ipv4.ip_unprivileged_port_start = 1024.

Capabilities живут в расширенном атрибуте файла. Простой cp их не переносит, cp -a переносит. Новый файл на месте старого — после обновления пакета или пересборки — приходит без них: ping получает свою часть из скрипта установки пакета. На файловой системе с опцией nosuid capabilities файлов не действуют.

Root без нужной части

capsh --drop запускает команду от root, убрав из bounding-набора названные части:

text
student@ubuntu:~$ echo secret > ~/secret; chmod 600 ~/secret
student@ubuntu:~$ sudo cat /home/student/secret
secret
student@ubuntu:~$ sudo capsh \
> --drop=cap_dac_override,cap_dac_read_search \
> -- -c 'cat /home/student/secret'
cat: /home/student/secret: Permission denied

Root остался root (UID 0), но без CAP_DAC_OVERRIDE и CAP_DAC_READ_SEARCH права файла для него действуют, как для всех. Так же root без CAP_NET_BIND_SERVICE получает Permission denied на порт 84.

Службы и контейнеры

Службе части выдаёт и отбирает systemd. AmbientCapabilities= даёт службе с User= нужную часть, CapabilityBoundingSet= задаёт потолок:

text
student@ubuntu:~$ sudo systemd-run --pipe --wait -q -p User=demo \
> -p AmbientCapabilities=CAP_NET_BIND_SERVICE grep Cap /proc/self/status
CapInh:	0000000000000400
CapPrm:	0000000000000400
CapEff:	0000000000000400
CapBnd:	000001ffffffffff
CapAmb:	0000000000000400
student@ubuntu:~$ sudo systemd-run --pipe --wait -q \
> -p CapabilityBoundingSet=CAP_NET_BIND_SERVICE cat /home/student/secret
/usr/bin/cat: /home/student/secret: Permission denied

Во втором случае служба работает от root, но её потолок — CapBnd: 0000000000000400, и чужой файл ей не прочитать. Как собрать эти ключи в юнит — systemd-hardening. Docker запускает контейнер с урезанным набором частей, --cap-drop и --cap-add меняют его (см. docker).

Рецепты

3

Порт ниже 1024 без root

Службе — часть через юнит, программе вне systemd — через файл:

bash
# служба: drop-in /etc/systemd/system/app.service.d/port.conf
[Service]
User=app
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE

# программа вне systemd
sudo setcap cap_net_bind_service=+ep /usr/local/bin/app

sysctl net.ipv4.ip_unprivileged_port_start=80 тоже открывает порт, но всем пользователям машины сразу.

Найти файлы с capabilities

bash
getcap -r / 2>/dev/null

На чистой машине — три файла из блока выше. Лишний файл в списке — особенно интерпретатор, архиватор или редактор — повод разобраться, кто и зачем его выдал.

Какие части у службы

bash
getpcaps $(systemctl show -p MainPID --value nginx)
grep Cap /proc/$(systemctl show -p MainPID --value nginx)/status

Неполадки

5

Permission denied на порту ниже 1024

PermissionError: [Errno 13] Permission denied у Python, bind() to 0.0.0.0:80 failed (13: Permission denied) у nginx: процесс работает не от root и без CAP_NET_BIND_SERVICE. Службе добавь AmbientCapabilities= и проверь CapEff в /proc/<PID>/status.

AmbientCapabilities= уже стоит, а отказ тот же — часть срезана потолком. Пустой CapabilityBoundingSet= в другом drop-in'е обнуляет и ambient-набор: впиши ту же часть и в CapabilityBoundingSet=. Если порт занят, ошибка другая — Address already in use; на учебной машине порт 80 держит nginx.

unable to set CAP_SETFCAP effective capability

text
student@ubuntu:~$ setcap cap_net_bind_service=+ep /usr/local/bin/py-bind
unable to set CAP_SETFCAP effective capability: Operation not permitted

setcap запущен без sudo: выдавать части файлам может только root.

fatal error: Invalid argument

setcap не понял имя части или файл. Имена — как в capsh --decode (cap_net_bind_service), файл — обычный: на символическую ссылку вроде /usr/bin/python3 setcap не ставит, нужен сам python3.12. Нет файла — Failed to set capabilities on file '…': No such file or directory.

Часть пропала после копирования или обновления

getcap ничего не печатает, программа снова получает Permission denied. Файл заменили: cp без -a, обновление пакета, пересборка. Выдай часть заново и закрепи её там, где файл появляется, — в скрипте установки или юните.

Root получает Operation not permitted

Процесс работает от UID 0, а ядро отказывает. Его потолок урезан: служба с CapabilityBoundingSet=, контейнер, capsh --drop. Сравни CapBnd и CapEff в /proc/<PID>/status с нужной частью.

Опасно

2

Часть для интерпретатора

py-bind из примера выше даёт порт ниже 1024 любому скрипту любого пользователя. cap_dac_read_search на Python, tar или vim открывает им /etc/shadow, а cap_setuid — путь к root. Выдавай части файлу конкретной программы и проверяй список getcap -r /.

CAP_SYS_ADMIN

Эта часть разрешает монтировать файловые системы, создавать пространства имён и ещё десятки операций. Служба с ней почти равна root — ищи часть поуже.

См. также

  • systemd-hardening — User=, CapabilityBoundingSet= и AmbientCapabilities= в юните.
  • special-bits — setuid: весь root через файл.
  • proc-filesystem — /proc/<PID>/status и другие файлы процесса.
  • ping — программа с cap_net_raw.
  • docker — части контейнера.
  • apparmor — профиль тоже ограничивает части.
  • sudo — команда от root целиком.
Загрузка…