Hlavní navigace

Létající cirkus (17)

Jan Švec 20. 9. 2002

Náš seriálu o jazyce Python pokračuje dnešním dílem věnovaným vytváření "bezpečného" prostředí, které umožňuje spouštění nedůvěryhodného (a potenciálně nebezpečného) kódu a přitom ho izoluje od samotného prostředí operačního systému. K implementaci tohoto prostředí Python používá dva moduly - rexec a Bastion, o nichž bude dnes (především) řeč. Nejprve si ale probereme problematiku dynamické interpretace kódu.

DYNAMICKÁ INTERPRETACE KÓDU

Jako téměř každý interpretovaný jazyk, i Python umožňuje svému kódu přístup k jádru interpretru a proto i program napsaný v Pythonu může spouštět své vlastní bloky kódu. K tomu slouží příkaz exec, kterému můžeme předat tři typy objektů. Jednak to může být řetězec. Ten je pak považován za zdrojový kód, zkompilován a následně spuštěn. Dále se může jednat i o otevřený soubor, který je přečten a interpretován jako kód Pythonu. A konečně jím může být i kódový objekt získaný voláním interní funkce compile(). Pokud za tímto objektem uvedeme ještě klíčové slovo in následované asociativním polem, je toto pole bráno jako prostor jmen. Dokonce můžeme uvést i dvě asociativní pole, pak jedno má význam globálního a druhé lokálního prostoru jmen. Neuvedeme-li ani jedno pole, je kód spuštěn přímo v kontextu kódu obsahujícího příkaz exec. Následující ukázka naznačuje používání tohoto příkazu:

>>> v = 1
>>> g = {‚v‘: ‚jedna‘}
>>> l = {‚v‘: ‚jedenact‘}
>>> exec ‚print v‘
1
>>> exec ‚print v‘ in g
jedna
>>> exec ‚print v‘ in g, l
jedenact

V případě víceřádkového složeného příkazu (for apod.) musí být řetězec k interpretaci ukončen znakem nový řádek. Více o tomto příkazu se dozvíte z dokumentace jazyka.

Zatímco příkaz exec slouží pro spouštění jednotlivých příkazů, pro vyhodnocování výrazů slouží interní funkce eval(). Té předáme jako řetězec požadovaný výraz, funkce eval() provede jeho vyhodnocení a výsledek vrátí jako svou návratovou hodnotu. Podobně jako u příkazu exec jí můžeme předat dvě asociativní pole ve významu globálního a lokálního prostoru jmen.

>>> eval(‚1 + 1‘)
2
>>> jedna = 2
>>> eval(‚jedna + jedna‘)
4
>>> eval(‚eval(„\‘Ahoj \‚“) * 3‘)
'Ahoj Ahoj Ahoj '

Kromě funkce eval() zná Python ještě interní funkci execfile(), která provede spuštění všech příkazů uložených v souboru. Argumentem je soubor otevřený pro čtení.

BEZPEČNÉ PROSTŘEDÍ

V tuto chvíli si pravděpodobně každý programátor – začátečník položí otázku: Proč se vůbec bezpečným prostředím zabývat? Pravidelným čtenářům ROOTa je odpověď na tuto otázku jistě více než jasná, bezpečnost aplikací by měla být na prvním místě. Ne vždy tomu tak ale je, spousta programátorů této problematice nevěnuje dostatečnou pozornost!

Každý program v Pythonu má díky mnoha modulům přístup téměř ke všem službám operačního systému. Bez omezení může otevírat soubory a pracovat s adresáři, odesílat signály, případně může používat další „citlivá“ systémová volání. Pro většinu typů úloh to plně postačuje.

V některých případech však tato přílišná otevřenost aplikace může být na závadu. Typickým příkladem je internetový browser Grail, napsaný v Pythonu, jenž umožňuje interpretaci apletů napsaných v tomto jazyku a vložených do webových stránek. Představte si, že by tyto aplety mohly využívat veškeré možnosti vašeho počítače, nekontrolovaně by používaly systémová volání, upravovaly by vaše soubory nebo komunikovoly s vnějším světem. A protože browser potřebuje úplnou sadu služeb, kdežto aplet samotný musí mít tuto sadu (vcelku razantně) omezenu, nelze použít postup, jenž by napadl pravděpodobně každého – odstranění nechtěných modulů (nehledě na to, že některé moduly ani odstranit nelze, jsou zakompilovány přímo v jádře interpretru).

Většina interpretovaných jazyků (ne-li všechny) mají z těchto důvodů implementovány prostředky, umožňující některým blokům programu omezit možnosti, co se týče využívání služeb operačního systému. V těchto blocích pak lze spouštět i nedůvěryhodný kód (za nedůvěryhodný kód by měl být požadován každý kód, který nenapsal programátor aplikace ani její uživatel). Tento kód pak nemá přístup ke službám operačního systému. Jejich rozsah je závislý na implementaci tohoto bezpečného prostředí. Některé implementace (včetně té pythonovské) dokáží možnosti bezpečného prostředí nastavit přesně na míru požadavkům aplikace.

Kód spouštěný uvnitř tohoto programového pískoviště nemá žádnou šanci dostat se mimo toto prostředí (jde o pouhý teoretický předpoklad, v praxi závisející na kvalitě implementace bezpečného prostředí). Nedůvěryhodný kód v jazyce Python může vytvořit opět své bezpečné prostředí, v němž lze spustit další kód, přičemž množinu služeb, nabízených tomuto kódu, lze pouze zúžit, nikdy ne rozšířit! Takto lze bezpečná prostředí zanořovat do sebe a pečlivě tak odladit bezpečnostní politiku naší aplikace.

Bezpečného prostředí nemusíme používat pouze k omezení pravomocí kódu, jde pomocí něho nahradit služby operačního systému uživatelskými. Například takto lze všechny požadavky na souborový systém přesměrovat na vzdálený server. Pro kód běžící v bezpečném prostředí pak budou všechna volání týkající se souborů vypadat jako klasický lokální přístup, bezpečné prostředí se ale postará o předání požadavků souborovému serveru.

BEZPEČNÉ PROSTŘEDÍ & PYTHON

V jazyce Python bezpečné prostředí reprezentují instance třídy RExec, kterou exportuje modul rexec. Tyto instance nabízejí všechny potřebné funkce pro interpretování kódu uvnitř bezpečného prostředí. Při vytváření instance třídy RExec jí můžeme předat instanci třídy RHooks, jejíž metody jsou volány při importování modulů. Je třeba ještě podotknout, že ke spouštění příkazů můžeme použít metody r_exec(), r_eval() a r_execfile(). Ty mají obdobnou funkci jako příkaz exec a interní metody eval() a execfile(), pracují však nad bezpečným prostředím. Jednoduchá ukázka použití třídy RExec může vypadat třeba takto.

>>> import rexec
>>> sandbox = rexec.RExec()
>>> sandbox.r_exec(‚im­port os‘)
>>> sandbox.r_exec(‚os­.remove(„/bin/zsh­“)‘)
Traceback (most recent call last):
File „<stdin>“, line 1, in ?
File „/usr/local/lib/pyt­hon2.2/rexec.py“, line 254, in r_exec
exec code in m.__dict__
File „<string>“, line 1, in ?
AttributeError: ‚module‘ object has no attribute ‚remove‘
>>> sandbox.r_exec(‚print dir(os)‘)
[ … ]

Jak vidíme z příkladu, nejprve jsme si vytvořili bezpečné prostředí reprezentované instancí třídy RExec. Dále jsme v tomto prostředí pomocí jeho metod spouštěli příkazy. První importoval bezpečnou verzi modulu os, další se pak pokusil smazat soubor /bin/zsh. Bezpečná verze modulu os však nepodporuje funkci remove(), proto toto volání selhalo. Nakonec jsme si nechali vytisknout seznam všech funkcí, exportovaných modulem os.

Nejjednodušeji se bezpečné prostředí nechá nastavovat pomocí třídních atributů třídy RExec. Pro jejich změnu odvoďte od třídy RExec třídu novou a tyto atributy předefinujte (všechny tyto atributy jsou tuple řetězců):

  • nok_builtin_names – interní jména, která nebudou z bezpečného prostředí přístupná.
  • ok_builtin_modules – interní moduly, které lze bez problémů importovat. Jde o moduly typu math, time, struct apod.
  • ok_path – tuple cest, ve kterých budou hledány moduly, které se mají importovat.
  • ok_posix_names, ok_sys_names – jména funkcí modulu os resp. sys, která lze bez problémů používat.
  • r_import – metoda volaná při importování modulu, jde-li o nebezpečný modul, vyvolá výjimku ImportError. Standardní implementace volá metody instance třídy RHooks předané při incializaci prostředí.
  • r_open – metoda volaná při otevírání souboru z bezpečného prostředí. Vrátí souborový objekt reprezentující otevřený soubor. Standardní implementace povoluje otevření pouze pro čtení.
  • r_reload, r_unload – metody volané při znovunačtení/od­stranění modulu. Standardně volá metody instance třídy RHooks.
>>> sandbox.modules['__main__'].foo = 1
>>> sandbox.r_eval('foo')
1

Někdy takto potřebujeme do tohoto prostředí dostat určitý objekt, nechceme však umožnit přístup ke všem atributům objektu. To nám zajistí modul Bastion.

BASTION

Modul Bastion nabízí jednu velice užitečnou funkci jménem Bastion(). Té lze předat určitý objekt a funkci, která kontroluje přístup k tomuto objektu. Funkce Bastion() poté vytvoří zástupce, který při každém pokusu o zpřístupnění libovolného atributu původního objektu nejprve zavolá filtrovací funkci a až podle jejího rozhodnutí případně zpřístupní tento atribut. Filtrovací funkce je funkce, která přebírá jediný argument – jméno atributu – a vrací buď logickou nulu (přístup zamítnut), nebo logickou jedničku (přístup povolen). Filtrovací funkci lze vynechat, pak se použije implicitní filtr, zamezující přístup ke každé metodě, jejíž název začíná znakem podtržítko ‚_‘. Pro zajímavý příklad použití nahlédněte přímo do zdrojového kódu modulu Bastion.

PŘÍŠTĚ

V příštím díle seriálu Létající cirkus se podíváme na ladicí nástroje – debugger pdb a profiler. Nakonec si ještě v krátkosti představíme shell jazyka Python ipython.

Našli jste v článku chybu?

9. 10. 2002 20:28

Petr Mach (neregistrovaný)

V RH 7.3 je python1.5 python2.0 a python2.2 (spousteci soubory). V 1.5 mi funguje import rpm, v tech dalsich ne. Da se s tim neco delat a nebo mam proste smulu? Preci jen je ta 1.5 zastarala a chtel bych psat program ve vyssi verzi.

DigiZone.cz: Česká televize mění schéma ČT :D

Česká televize mění schéma ČT :D

Vitalia.cz: „Připluly“ z Německa a možná obsahují jed

„Připluly“ z Německa a možná obsahují jed

Podnikatel.cz: Vládu obejde, kvůli EET rovnou do sněmovny

Vládu obejde, kvůli EET rovnou do sněmovny

Lupa.cz: Slevové šílenství je tu. Kde nakoupit na Black Friday?

Slevové šílenství je tu. Kde nakoupit na Black Friday?

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Podnikatel.cz: Víme první výsledky doby odezvy #EET

Víme první výsledky doby odezvy #EET

Lupa.cz: UX přestává pro firmy být magie

UX přestává pro firmy být magie

Vitalia.cz: To není kašel! Správná diagnóza zachrání život

To není kašel! Správná diagnóza zachrání život

120na80.cz: Jak oddálit Alzheimera?

Jak oddálit Alzheimera?

Vitalia.cz: Baletky propagují zdravotní superpostel

Baletky propagují zdravotní superpostel

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

DigiZone.cz: ČT má dalšího zástupce v EBU

ČT má dalšího zástupce v EBU

Vitalia.cz: Tesco: Chudá rodina si koupí levné polské kuře

Tesco: Chudá rodina si koupí levné polské kuře

DigiZone.cz: NG natáčí v Praze seriál o Einsteinovi

NG natáčí v Praze seriál o Einsteinovi

Lupa.cz: Není sleva jako sleva. Jak obchodům nenaletět?

Není sleva jako sleva. Jak obchodům nenaletět?

Root.cz: Vypadl Google a rozbilo se toho hodně

Vypadl Google a rozbilo se toho hodně

Měšec.cz: Zdravotní a sociální pojištění 2017: Připlatíte

Zdravotní a sociální pojištění 2017: Připlatíte

Lupa.cz: Babiš: E-shopů se EET možná nebude týkat

Babiš: E-shopů se EET možná nebude týkat

Vitalia.cz: Říká amoleta - a myslí palačinka

Říká amoleta - a myslí palačinka

Lupa.cz: Propustili je z Avastu, už po nich sahá ESET

Propustili je z Avastu, už po nich sahá ESET