Defenzivní programování

Defenzivní programování je forma návrhu softwaru, která se snaží zaručit jeho nepřetržitou funkčnost i za podmínek použití, ke kterým nebyl původně navržen. Snaží se eliminovat tzv. Murphyho zákony. Ty říkají, že cokoliv se může potenciálně pokazit, se zaručeně pokazí. Defenzivní forma návrhu se aplikuje především, pokud by mohlo selhání softwaru vést ke katastrofickým či velmi nákladným scénářům.

Při defenzivním programování autor předpokládá, že okolní svět se snaží za každou cenu zničit jeho dílo, a tudíž svůj program připravuje na všechny možné situace, které by ho mohly poškodit. Autor tedy očekává, že uživatelé budou posílat nesmyslné či záludné vstupy, nebudou dodržovat stanovený kontrakt atd. Jedním z cílů defenzivního programování je zabránit poškození vlastních dat či stavu programu, ale na druhou stranu a reagovat benevolentně. Je třeba mít na paměti, že největším nebezpečím pro program je autor sám a tudíž by měl hlídat hlavně sebe a důsledek své práce. Programátor myslí dopředu, musí ošetřit všechny možné stavy, výjimky a chybové situace v kódu.

Defenzivní programování také brání proti SQL injection, DNS útokům, neoprávněnému přístupu a dalším útokům zevnějšku. Podporuje tzv. audity kódu, při kterých dochází ke kontrole čitelnosti a snadnému porozumění kódu.

Zásady defenzivního programování

  • Všechna data jsou důležitá, dokud se neprokáže opak
  • Všechna vstupní data jsou potenciálně nebezpečná, dokud se neprokáže opak
  • Veškerý kód je nebezpečný, dokud se neprokáže opak

Techniky defenzivního programování

Znovupoužití kvalitního kódu

Znovupoužití staršího ověřeného kódu je jednou z nejlepších a hlavně nejlevnějších technik, je třeba ale dávat pozor na to, aby starší kód splňoval dnešní požadavky. Jako příklad lze uvést rozdílnost kódování (UTF-8 vs. ASCII), architekturu systému (32bit vs. 64bit), offline vs. online návrh. Kód napsaný před rokem 1990 je v drtivé většině případů zranitelný na sql injection útoky.

Ošetření I/O

Vstupy programu představují druhou největší hrozbu (po autorovi samotném). Vstupy uživatelů jsou nevyzpytatelné a na nesprávně zvolené vstupy by měl programu umět reagovat. Stejně tak musí zabezpečit odolnost proti přímým útokům na program, jako například vkládání kódu do vstupu, procházení file-systémem, příchozí data ze sítě atd.

Pro každý vstup by měl autor programu definovat množinu validních vstupů a nebrat žádné jiné. Dále definovat chování nekorektních vstupů, např. přerušení programu, nové zadání, varování.

Například uživatel má do programu zadat množství peněz, autor programu by měl zkontrolovat, jestli zadal číslo, jestli je dostatečně velké, jaký symbol se používá pro oddělení desetinných míst, kolik desetinných míst uživatel zadal a jestli číslo není složené (23+74).

Výstupy programu by neměly navenek prozrazovat více, než je nutné, autoři by se měli držet zásady maximálního utajení dat.

Vynucování dodržení kontraktu

Z vnějších zdrojů by měli autoři metod vynucovat dodržení kontraktu povinně, z vnitřních zdrojů většinou jen v před-produkčních verzích pro účely testování, aby kontroly neubíraly na rychlosti programu.

Vstupní, výstupní podmínky a invarianty

Vstupní podmínky by se měly kontrolovat před každým provedení kritické operace. Výstupní podmínky se kontrolují po dokončení operace.

Invarianty datových struktur by se měli kontrolovat před provedením operace na těchto strukturách. Invarianty se většinou vynucují pomocí příkazů Assert (zkontrolovat nenulovost, délku pole, platnost odkazu…).

Invariant neohraničeného pole kontroluje, že současná velikost není menší, než alokovaná velikost. Dále, že alokovaná velikost pole je menší, než největší možná alokovaná velikost a nakonec, že alokovaná velikost pole není menší než 2.

Vnější hranice systému

Vnitřní hranice systému se ošetřují pomocí obsluhy chyb a následným volání programových výjimek. Obecně lze říci, že pokud lze po vzniklé chybě obnovit stav programu a pokračovat, měl by programátor použít v kódu kontrolované výjimky (jejichž ošetření je vynucené překladačem), nebo vlastní konstrukty na ošetření.

Pro ošetření chyb, po kterých nelze obnovit stav programu a pokračovat se využívají nekontrolované výjimky (jejichž ošetření není vynutitelné překladačem).

Testování

Testování přispívá ke kvalitnímu kódu, představuje příslib včasné detekce chyb v programu a ve výsledku snížení počtu chyb v kódu. Správné testování vychází z přesné dokumentace chování programu.

Kanonizace

Autor by si měl dávat pozor, aby kontrolovaná událost nešla obejít více způsoby. Jako příklad lze uvést programové ošetření v kódu na unixovém systému, aby uživatel nechtěl po programu soubor „/etc/passwd“. Pokud ale uživatel zadá „/etc/./passwd“, soubor mu může být programem vydán a kontrolu obejde.

Nízká tolerance

Proaktivní přístup zabraňuje potenciálním chybám kódu. Program by se měl držet zásady, že co nezná, decentně odmítá, nebo na tu vůbec nereaguje.

Další techniky

Kontrola struktur konstantních velikostí (přetečení zásobníku), opatrná práce s řetězci, kódování a autentifikace vzdálených systémů, princip KISS a další.

Literatura

  • Effective Java: Programming Language Guide, ISBN 0-201-31005-8, 2001; second edition: ISBN 978-0-321-35668-0, 2008

Externí odkazy