S-bit -- životu nebezpečno -=-=-=-=-=-=-=-=-=-=-=-=-=-= Unix is a lot more complicated (than CP/M) of course -- the typical Unix hacker can never remember what the PRINT command is called this week -- but when it gets right down to it, Unix is a glorified video game. People don't do serious work on Unix systems; they send jokes around the world on USENET or write adventure games and research papers. -- E. Post "Real Programmers Don't Use Pascal", Datamation, 7/83 Pavel Kaňkovský, 3. března 1998 Mnoho systémových programů v UNIXovém operačním systému -- Linux nevyjímaje -- potřebuje pro svůj běh zvláštní privilegia. Příkladem budiž program passwd, který musí být schopen zapisovat do souboru /etc/passwd resp. /etc/shadow, aniž by k tomu byl oprávněn sám uživatel (z pochopitelných důvodů). Nejčastěji používaným mechanismem, který toto umožňuje, je tzv. propůjčení identifikace. U programu, jehož spustitelný soubor má nastavený aspoň jeden z s-bitů, například takto: -r-sr-xr-x 1 root bin 15796 /usr/bin/passwd dojde po jeho spuštění systémovým voláním execve() k tomu, že efektivní vlastník, efektivní skupina, případně oboje -- podle toho, zda je v přístupových právech nastaven setuid bit ( 04000), setgid bit (02000), nebo oba dva -- jsou nastaveny na hodnoty určené uživatelem resp. skupinou vlastnící soubor. (Množina doplňkových skupin (supplemental groups) je vždy ponechána beze změny.) Program pak pracuje se stejnými oprávněními, jako kdyby byl spuštěn svým vlastníkem resp. uživatelem v dané skupině. Proč je to nebezpečné? Výše popsaný mechanismus není prost rizik. Musí být zajištěno, aby zvláštní privilegia přidělená programu nemohla být zneužita. Část odpovědnosti leží na samotném jádře systému, které musí mj. zabránit jeho trasování nebo čtení jeho paměťového prostoru (kde se mohou nacházet důvěrná data) ze strany skutečného vlastníka, ovšem převážná část spočívá na bedrech samotného programu (a také jím používaných knihovnách). Privilegovaný proces musí pečlivě kontrolovat prostředí, ve kterém je spuštěn, a vstupy, které jsou mu uživatelem předkládány. Naneštěstí mnoho programů (či spíše jejich autorů) toto podceňuje a výsledkem jsou závažné bezpečnostní problémy, které jsou ještě umocněny tím, že velká část takových programů pracuje rovnou s nejvyššími možnými privilegii -- s právy superuživatele. K nejčastějším chybám, které mají za následek bezpečnostní problémy (a které budou detailněji diskutovány níže) patří: - přetečení mezí pole - neopatrná manipulace se soubory - neopatrné spouštění dalších programů Odstrašující příklad -- Xserver Jak si asi mnozí, kteří mají na svém Linuxu nainstalovaný balík {\tt XFree86}, všimli, Xserver běží s právy superuživatele. Je tomu tak, aby mu bylo dovoleno přistupovat přímo ke grafickému hardware, k jehož ovládání neposkytuje standardní linuxové jádro prakticky žádnou podporu. Ovšem Xserver je velmi komplikovaný program a vyskytují se v něm (nyní bude řeč o aktuální verzi 3.3.1 založené na X11R6.3) různé problémy, dokonce ze všech tří výše vyjmenovaných kategorií. Jedná se o následek toho, že kód Xserveru (a do jisté míry to platí o celém X11) byl většinou navrhován a implementován, aniž byl kladen dostatečný důraz na jeho bezpečnost. Svou roli zde nepochybně hraje velký rozsah a složitost kódu, stejně jako jeho značná různorodost. Přetečení mezí pole Toto je snad nejfrekventovanější chyba vyskytující se v céčkových programech vůbec. Poněkud překvapivě se však chyba vyskytuje mnohem častěji nepřímo -- při použití některých funkcí standardní knihovny. Zvláště se jedná o funkce sprintf(), strcpy() a strcat(), které neumožňují explicitně stanovit velikost pole, do kterého je ukládán výsledek, a bezstarostně zapisují za jeho konec. (Což vede k otázce, zda je celková koncepce standardní knihovny vhodná.) Podrobněji k této problematice v dalším článku. Pomocí vhodně sestrojených vstupních dat pak může zlý uživatel přepsat návratovou adresu na zásobníku nebo jiné důležité údaje a donutit program k vykonání prakticky libovolných akcí -- např. ke spuštění privilegovaného shellu. Detailní popis přináší například http://www-miaif.ibp.fr/willy/security/. Příklad (xc/programs/Xserver/os/access.c): void ResetHosts (display) char *display; { register HOST *host; char lhostname[120], ohostname[120]; char *hostname = ohostname; char fname[100]; ... strcpy (fname, "/etc/X"); strcat (fname, display); strcat (fname, ".hosts"); if (fd = fopen (fname, "r")) ... } Uživatel může při spuštění Xserveru specifikovat libovolný (i nesmyslný a hlavně libovolně dlouhý) název displaye pomocí parametru uvozeného dvojtečkou. Tato hodnota je předána do funkce ResetHosts(), která ji používá pro konstrukci jména souboru. Avšak příliš dlouhá hodnota způsobí přetečení mezí pole fname. Neopatrná manipulace se soubory Většina programů pracuje nějakým způsobem se souborovým systémem. Pokud program neprovádí dostatečné kontroly jmen souborů nebo jiných uživatelem poskytnutých dat, z nichž jsou jména souborů konstruována, lze snadno získat neoprávněný přístup k souborům, systémovým zvláště. Příklad (xc/programs/Xserver/os/utils.c) static void InsertFileIntoCommandLine(resargc, resargv, prefix_argc, prefix_argv, filename suffix_argc, suffix_argv) int *resargc; ... { struct stat st; FILE *f; char *p; char *q; int insert_argc; char *buf; int len; int i; f = fopen(filename, "r"); ... } /* end InsertFileIntoCommandLine */ void ExpandCommandLine(pargc, pargv) int *pargc; char ***pargv; { int i; for (i = 1; i << *pargc; i++) { if ( (0 == strcmp((*pargv)[i], "-config")) && (i << (*pargc -- 1)) ) { InsertFileIntoCommandLine(pargc, pargv, i, *pargv, (*pargv)[i+1], /* filename */ *pargc -- i - 2, *pargv + i + 2); i--; } } } /* end ExpandCommandLine */ Funkce ExpandCommandLine() interpretuje parametr -config tak, že načte obsah specifikovaného souboru a přidá ho k parametrům zadaným na příkazové řádce. Nicméně vůbec nekontroluje, zda je daný soubor uživateli, který Xserver spustil, přístupný pro čtení. Například, pokud zlý uživatel zadá soubor /etc/shadow, dostane se mu chybového hlášení typu: Unrecognized option: root:BFLMpsvzABCD:12345::::: a může začít luštit superuživatelovo heslo. Zvláštní kapitolu představují dočasné soubory vytvářené v oblastech přístupných uživateli -- zvláště v adresáři /tmp, ale i v domovském adresáři uživatele, v aktuálním adresáři apod. Zlý uživatel může vytvořením (sym)linků nebo přejmenováváním souborů lehce přesvědčit naivně implementovaný program, aby pracoval s úplně jiným souborem, než zamýšlel. Bohužel tomu opět napomáhá standardní knihovna -- konkrétně funkce mktemp() a tmpnam(). V případě Xserveru lze problém tohoto typu demonstrovat na práci s adresářem /tmp/.X11-unix, i když konkrétní povaha těchto rizik poněkud vybočuje ze zaměření tohoto článku. Neopatrné spouštění dalších programů Příležitostně potřebuje jeden program využít služeb jiného programu. Pokud první program běží se zvláštními privilegii, pak jsou často tato privilegia přenášena i na programy z něj spuštěné. To pak znamená, že se na jedné straně nabízí možnost využít (či spíše zneužít) nedostatků jiného programu, na druhé straně pak možnost přímo donutit privilegovaný proces k spuštění libovolného programu dodaného zlým uživatelem (sem lze zařadit použití dynamicky linkované knihovny podle přání uživatele). Xserver vykazuje vzácnou kombinaci obou problémů, jak uvidíme v příkladu. Příklad (xc/programs/Xserver/xkb/ddxLoad.c) Bool XkbDDXCompileKeymapByNames(XkbDescPtr xkb, XkbComponentNamesPtr names, unsigned want, unsigned need, char * nameRtrn, int nameRtrnLen) { ... if (XkbBaseDirectory!=NULL) { ... sprintf(buf, "%s/xkbcomp -w %d -R%s -xkm -- -em1 %s -emp %s -eml %s \"%s%s.xkm\"", XkbBaseDirectory, ((xkbDebugFlags<<2)?1:((xkbDebugFlags>>10)?10:xkbDebugFlags)), XkbBaseDirectory, PRE_ERROR_MSG,ERROR_PREFIX,POST_ERROR_MSG1, xkm_output_dir,keymap); ... } else { ... } ... out= popen(buf,"w"); ... } Proměnná XkbBaseDirectory obsahuje buď implicitní hodnotu ( /usr/lib/X11/xkb), nebo hodnotu specifikovanou pomocí parametru -xkbdir. (Z tohoto důvodu mi není zcela jasný význam testu na nulovou hodnotu, ale to ponechme stranou.) Uživatel má nyní dvě možnosti jak tento parametr zneužít: jednak může vytvořit libovolný program pod jménem xkbcomp a předat jméno adresáře, ve kterém se nachází, nebo může využít faktu, že funkce popen() způsobí spuštění shellu, a zadat hodnotu, která obsahuje znaky mající pro shell zvláštní význam (např. mezery, obrácené apostrofy aj.). Východiska z nouze Rozsáhlé a těžko prověřitelné programy (jako zmíněný Xserver) neměly být nikdy spouštěny se zvláštními privilegii. Principiálně vzato by těžko prověřitelné programy neměly vůbec vznikat. Často používané bezpečnostní obálky (wrappers) a podobná opatření jsou pouze nápravou následků, nikoli příčin, které spočívají v špatné konstrukci samotných programů, i když za určitých okolností mohou mít své výhody. Návod ke správné konstrukci bezpečných programů lze hledat ve dvou "základních bezpečnostních pravidlech", která zní: - Nikdy nikomu nevěř. - Co není dovoleno, je zakázáno. První pravidlo říká, že v programu nesmí být chyby, ale navržen musí být s ohledem na to, že tam chyby budou. To například znamená, že ty části programů, které zvláštní privilegia vyžadují, je vhodné separovat do malých samostatných programů s přesně definovaným rozhraním, jejichž správnou funkci bude možno ověřit -- ideálně formálním důkazem. Druhé pravidlo pak doporučuje, aby každý program měl přidělena pouze ta privilegia, která ke své práci potřebuje a pokud možno žádná jiná. To bohužel často naráží na omezení operačního systému -- zvláště v UNIXovém světě (Linux opět nevyjímaje), kde vládne silná dichotomie: vše (superuživatel) nebo nic (ostatní uživatelé). Není-li možno dostatečně jemně rozlišovat privilegia, dochází k tomu, že jsou přidělována širší oprávnění, než je zapotřebí, a otevírá se prostor pro jejich zneužití. Jediným skutečným řešením tohoto problému je upravit operační systém tak, aby programy nemusely mít větší oprávnění než je nutno. Komplexnější projekty jako LinuxPrivs http://www.kernel.org/pub/linux/libs/security/linux-privs/ (implementace privilegií podle POSIX.1e) jsou vykročení správným směrem, ale existují i částečná řešení, jako například patch http://www.phrack.com/52/P52-06, který mj. eliminuje dosti nesmyslnou (v dnešní době) potřebu superuživatelských práv pro některé síťové služby. Poněkud paradoxní je, že popisovaná opatření (pomineme-li obecné doporučení, že v programech nemají být chyby) nejsou příliš vhodná pro náš Xserver. Ten totiž ve skutečnosti žádná speciální privilegia nepotřebuje, pouze musí být schopen ovládat grafický hardware. Stačí, aby pro to byla v jádře dostatečná podpora -- jako jí poskytuje například GGI http://www.ggi-project.org/. Závěr Tento článek popisuje pouze malou část z rozsáhlého repertoáru možných rizik: zmiňované problémy (nebo problémy jim podobné) mohou vznikat (a bohužel také vznikají) všude, kde dochází k interakci mezi procesy pracujícími s různými oprávněními, ať už jsou to diskutované programy využívající mechanismus propůjčení identifikace, programy komunikující po síti (na serverové i na klientské straně), nebo dokonce uživatelské aplikace, jsou-li jim předloženy vstupy pocházející z potenciálně nedůvěryhodného zdroje. Proto nelze než doporučit, aby tvůrce libovolného programu zvážil při jeho návrhu a implementaci možná bezpečnostní rizika a snažil se je úplně eliminovat, nebo alespoň maximálně omezit. výheň