# Uživatelé, práva a setuid

„Root může všechno, ostatní skoro nic." Zní to jako poctivé shrnutí unixové bezpečnosti a vyvrátíš ho za deset vteřin.

Jako obyčejný uživatel `martin` spustíš `passwd` a změníš si heslo. Povede se to. Otisk se zapsal do `/etc/shadow` - do souboru, který nesmíš ani přečíst, natož do něj psát. Nezadával jsi heslo roota a nic jsi neobcházel.

Ten mechanismus se jmenuje setuid, je starý přes padesát let a stojí za velkou částí lokálních eskalací práv, jaké kdy v unixových systémech byly.

Stránka vysvětluje, podle čeho se přístup kontroluje a kde se to láme. Neřeší uložení práv v [i-uzlu](Souborovy-system) ani izolaci jmenných prostorů ([Virtualizace-a-kontejnery](Virtualizace-a-kontejnery)). Předpokládá, že víš, co proces dědí po rodiči ([Procesy](Procesy)) a že kontrolu dělá jádro, ne program ([Jadro-a-uzivatelsky-rezim](Jadro-a-uzivatelsky-rezim)).

## Jádro nezná uživatele. Zná čísla.

Tohle je nejdůležitější věta na celé stránce: **práva se nekontrolují podle toho, kdo jsi, ale podle toho, jaké UID a GID má proces v okamžiku, kdy sáhne na soubor - a to číslo se dá za běhu změnit.**

Jméno „martin" existuje kvůli lidem. Jádro pracuje s číslem 1000 a tabulku jmen uvnitř nemá - překlad dělá až knihovna v uživatelském prostoru, aby `ls -l` vypsal něco čitelného.

```bash
id                    # uid=1000(martin) gid=1000(martin) groups=1000(martin),27(sudo)
ls -ln /home/martin   # -n vypíše čísla místo jmen, tedy to, co vidí jádro
```

**Root není účet se seznamem výsad. Root je UID 0.** Jádro na řadě míst testuje doslova „je efektivní UID nula", takže roota nejde omezit změnou hesla ani skupiny.

Odtud plyne to, co překvapuje u kontejnerů. Proces v kontejneru pod UID 1000 je pro jádro hostitele tentýž uživatel jako `martin` a na připojeném svazku dosáhne na jeho soubory. Pomůže až mapování [jmenných prostorů](Virtualizace-a-kontejnery).

### Proč jsou hesla a účty ve dvou souborech

`/etc/passwd` musí být čitelný pro všechny, protože každý `ls -l` potřebuje přeložit 1000 na „martin". Dřív v něm byl i otisk hesla, takže si ho kdokoliv zkopíroval a lámal offline.

Otisky se proto přestěhovaly do `/etc/shadow` s právy `640` a vlastníkem `root:shadow`. Tím vznikl problém, kterým stránka začíná.

### Doplňkové skupiny se načtou při přihlášení

```bash
sudo usermod -aG docker martin   # -a je povinné, bez něj přepíšeš celý seznam skupin
```

Zápis do `/etc/group` proběhne, ale běžící shell se o něm nedozví. Seznam skupin je součástí přihlašovacích údajů procesu a dědí se od rodiče, takže platí až od dalšího přihlášení.

## Devět bitů

Práva jsou tři trojice bitů: pro vlastníka, pro skupinu a pro ostatní. Osmičkově má `r` hodnotu 4, `w` hodnotu 2 a `x` hodnotu 1, takže se každá trojice sečte na jednu číslici. `rw-r--r--` je `644`, `rwxr-xr-x` je `755`.

### Co znamená každý bit u souboru a co u adresáře

U adresáře znamenají stejná písmena něco jiného a tady se to plete nejčastěji.

| Bit | U souboru | U adresáře |
|---|---|---|
| `r` (4) | číst obsah | vypsat jména záznamů |
| `w` (2) | měnit obsah | vytvářet a mazat záznamy |
| `x` (1) | spustit | vstoupit a projít skrz |

Odtud plyne první nepříjemnost: **právo `w` k adresáři je právo mazat cizí soubory.** Maže se adresářový záznam, tedy jméno, ne data - jak rozebírá [souborový systém](Souborovy-system). Práva mazaného souboru se nekontrolují vůbec.

Druhá je `x`. Bez něj se do adresáře nedostaneš, i kdyby soubor v něm měl práva `777`, protože jádro musí projít každou složkou cesty.

## Rozhoduje první shoda, ne nejlepší

Jádro se ptá v pevném pořadí: jsem vlastník, jsem ve skupině souboru, jsem ostatní. **Rozhodne první shoda a další trojice se už nečtou.**

Není to hledání nejvýhodnějšího práva, a proto se dá vyrobit tohle:

```bash
chmod 077 tajne.txt   # vlastník nic, skupina rwx, ostatní rwx
cat tajne.txt         # Permission denied - a to čte vlastník
```

Vlastník na svůj soubor nedosáhne, zatímco kdokoliv jiný ano. Zbývá mu jediná výhoda: `chmod` smí volat bez ohledu na bity.

## umask: proč má nový soubor 644 a ne 666

Program při vytváření souboru žádá o práva `666`, u adresáře o `777`. Bit `x` u nového souboru nedostaneš nikdy a je to záměr.

Z toho jádro odečte masku procesu. Umask je seznam bitů, které se **odebírají**, a při výchozí hodnotě `022` vznikne ze `666` soubor `644` a ze `777` adresář `755`.

```bash
umask         # aktuální maska, typicky 0022
umask 077     # nové soubory budou 600 a adresáře 700
```

Nastavuje se v `/etc/login.defs`, přes `pam_umask`, v `~/.profile` a u služeb volbou `UMask=` v jednotce systemd.

**Na sdíleném stroji si dej `umask 077` do `~/.profile`.** Výchozí `022` znamená, že každý tvůj nový soubor přečte kdokoliv na systému.

## setuid, setgid a sticky bit

Nad devíti bity je ještě čtvrtá osmičková číslice: `4000` je setuid, `2000` setgid a `1000` sticky.

**Setuid znamená, že proces poběží s UID vlastníka souboru, ne toho, kdo ho spustil.** Proto `passwd` vlastněný rootem běží jako root a dostane se do `/etc/shadow`. Poznáš ho podle `s` na místě `x`.

```bash
ls -l /usr/bin/passwd                     # -rwsr-xr-x 1 root root
find / -perm -4000 -type f 2>/dev/null    # všechny setuid programy v systému
```

Ten výpis si projdi. **Každý řádek je program, jehož chyba znamená roota pro kohokoliv s přístupem k příkazové řádce.** Velké `S` je setuid bez `x`, tedy překlep.

### setgid na adresáři

Na adresáři má setgid jediný rozumný význam: nové soubory a podadresáře dědí skupinu adresáře místo primární skupiny svého autora. Podadresář zdědí i samotný setgid bit.

```bash
sudo chgrp projekt /srv/projekt
sudo chmod 2775 /srv/projekt   # dvojka na začátku je setgid
```

Tohle je správné řešení sdíleného adresáře pro tým. Bez něj skončí každý soubor ve skupině svého autora.

### Sticky bit a proč ho má /tmp

`/tmp` musí mít `w` pro všechny, jinak by si tam nikdo nic nezaložil. Jenže to je zároveň právo mazat cizí záznamy, takže by ti kdokoliv smazal rozdělaný soubor.

Sticky bit to omezuje: záznam smí smazat nebo přejmenovat jen vlastník souboru, vlastník adresáře a root.

```bash
ls -ld /tmp   # drwxrwxrwt - to t na konci je sticky bit
```

### Setuid skript nefunguje a je to schválně

Nastav setuid na shellový skript a nestane se nic. Linux ho u souborů se `#!` cíleně ignoruje.

Důvod je souběh: mezi ověřením práv a okamžikem, kdy interpret otevře cestu znovu podle jména, může útočník podstrčit jiný soubor. Privilegovanou akci ze skriptu řeš pravidlem v sudo.

## Pwnkit: dvanáct let v jedné setuid binárce

V lednu 2022 zveřejnila firma Qualys zranitelnost CVE-2021-4034, přezdívanou pwnkit. Šlo o `pkexec` z balíku polkit - setuid binárku vlastněnou rootem, kterou měla ve výchozí instalaci prakticky každá distribuce.

Chyba byla ve zpracování argumentů. Při spuštění s prázdným seznamem argumentů četl `pkexec` mimo pole a přepsal si vlastní prostředí hodnotou, kterou řídil útočník. Odtud se dala vnutit vlastní knihovna a jakýkoliv místní uživatel se stal rootem. Ten kód tam byl od roku 2009, tedy dvanáct let.

**Poučení: setuid program musí být napsaný tak, že nevěří ničemu, co dostane od uživatele - včetně proměnných prostředí.** Útočník neřídí jen vstup, ale celý stav, ve kterém proces startuje.

Že to není unixová specialita, ukázal macOS High Sierra v listopadu 2017: chyba v ověřování umožnila přihlásit se jako root s prázdným heslem.

## Co setuid nahradilo: capabilities

Setuid je „všechno, nebo nic". Program, který potřebuje jedinou drobnost, dostane veškerou moc roota a doufá, že ji sám neztratí.

Linux proto rozdělil práva roota na kousky - capabilities. `CAP_NET_BIND_SERVICE` dovolí naslouchat na portech pod 1024, `CAP_NET_RAW` otevřít surový soket, `CAP_DAC_OVERRIDE` obejít kontrolu devíti bitů.

```bash
getcap -r /usr/bin 2>/dev/null                    # co má na systému capability
sudo setcap 'cap_net_bind_service=+ep' ./program  # smí na port 80, jinak nic
```

`ping` je učebnicový případ: roky to byla setuid binárka, dnes má na Debianu jen `cap_net_raw`.

**Nové programy nedělej setuid, dej jim capability.** A ještě lépe: nech je běžet pod vlastním uživatelem tak, aby nepotřebovaly nic - port pod 1024 umí systemd předat jako hotový soket.

## ACL, když devět bitů nestačí

Devět bitů popíše jednoho vlastníka, jednu skupinu a zbytek světa. Jakmile potřebuješ různá práva pro víc konkrétních uživatelů, nemáš je kam napsat.

```bash
setfacl -m u:martin:rw soubor   # martin smí číst a psát, ostatní beze změny
getfacl soubor                  # vypíše i to, co ls -l neukáže
```

`ls -l` u takového souboru přidá za práva `+` a to je jediná stopa.

**Poctivě: ACL se používají zřídka a většinou je lepší přeskupit skupiny.** Zálohovací nástroje je občas zahodí a za rok nikdo neví, proč tam ten přístup je.

## Nad tím vším: sudo, SELinux a AppArmor

Sudo je lepší než přihlašování jako root ze dvou důvodů: nechává v logu, kdo co spustil, a umí povolit jen konkrétní příkazy místo celého shellu.

Nad systémem UID a bitů leží ještě povinné řízení přístupu - SELinux a AppArmor. Ta pravidla platí i pro procesy s UID 0, takže zkompromitovaná setuid binárka se nedostane dál, než jí profil dovolí.

## Diagnostika: proč tam nemůžu

Nejlepší nástroj na „mám práva a stejně tam nesmím" je `namei`, protože ukáže práva na **celé cestě**, ne jen na cílovém souboru.

```bash
namei -l /home/martin/data/soubor.txt   # práva a vlastník každé složky cesty
id martin                               # skupiny, které uživatel skutečně má
sudo -l                                 # co smí spustit přes sudo
ls -ln soubor                           # UID a GID vlastníka číselně
getfacl soubor                          # ACL, které ls -l neukáže
```

Odmítnutí na systému se SELinuxem najdeš v auditu příkazem `ausearch -m AVC -ts recent`. Zbytek výbavy je na stránce [Nastroje-a-diagnostika](Nastroje-a-diagnostika).

| Příznak | Kde je problém |
|---|---|
| „Permission denied" u souboru s právy `777` | chybí `x` na adresáři cesty, ukáže `namei -l` |
| Přidal jsem uživatele do skupiny a nic se nezměnilo | skupiny se načítají při přihlášení |
| Program nemůže otevřít port 80 | port pod 1024 chce `CAP_NET_BIND_SERVICE` |
| Někdo mi smazal soubor ve sdíleném adresáři | adresář má `w` pro všechny a chybí sticky bit |
| Skript se `s` bitem se chová jako obyčejný | jádro setuid u skriptů ignoruje |
| `ls -l` ukazuje `S` místo `s` | setuid bez `x` |
| Vlastník soubor nepřečte, ostatní ano | rozhodla první shoda, trojice vlastníka |

## Co si odnést

**Jádro nezná jména, jen čísla.** Rozhoduje UID a GID procesu v okamžiku přístupu.

**Root je UID 0.** Není to seznam výsad, je to jedna hodnota, kterou jádro testuje.

**U adresáře znamená `x` vstup, `r` výpis a `w` právo mazat cizí soubory.** Přepiš si to na papír.

**Rozhoduje první shoda.** Vlastník s právy `077` na vlastní soubor nedosáhne.

**Setuid dává programu veškerou moc vlastníka souboru.** Pwnkit to dokazoval dvanáct let.

**Capability místo setuid, vlastní uživatel místo capability.** V tomhle pořadí.

**Než začneš měnit práva, pusť `namei -l`.** Většina odmítnutí je jinde, než hledáš.

## Kam dál

- **[Souborovy-system](Souborovy-system)** - proč mazání odstraňuje jméno, ne data
- **[Virtualizace-a-kontejnery](Virtualizace-a-kontejnery)** - jak se stejné UID chová uvnitř kontejneru
- **[Procesy](Procesy)** - co proces dědí po rodiči, včetně skupin a masky
- **[Systemova-volani](Systemova-volani)** - kde přesně se kontrola práv provádí
- **[Nastroje-a-diagnostika](Nastroje-a-diagnostika)** - `lsof`, `strace` a zbytek výbavy
