Hneď na začiatku musím priznať, že sa nebudeme nikam nabúravať. Naopak, skúsime oživiť historický význam slova hacker a povŕtať sa v systéme. Niečo pri tom objavíme, niečo pokazíme a možno sa trocha pobavíme. Kto sa chce zúčastniť, nech má zdrojáky jadra poruke.
Pri konfigurácii jadra som si všimol nasledovnú voľbu:
Jej popis hovorí:
Root Plug Support (SECURITY_ROOTPLUG)This is a sample LSM module that should only be used as such. It prevents any programs running with egid == 0 if a specific USB device is not present in the system.
See http://www.linuxjournal.com/article.php?sid=6279 for more information about this module.
If you are unsure how to answer this question, answer N.
Teda, že root_plug
, je modul, ktorý bol napísaný, ako ukážka
demonštrujúca, ako písať svoje vlastné "bezpečnostné moduly". Kto chce
viac detailov o histórii tohoto modulu, nech sa pozrie na ten článok
v Linux Journal-e.
Na čo sú dobré bezpečnostné moduly? Linuxové jadro
používa linuxové bezpečnostné moduly
LSM, teda sady pointrov na
funkcie, ktoré rozhodujú o tom, či je nejaká akcia povolená, alebo nie.
Tieto pointre sú združené v štruktúre security_operations
definovanej v súbore include/linux/security.h
. Tam sa v komentároch
dočítame, že tieto funkcie rozhodujú o tom, či je dovolené vytvoriť
súbor, primountovať súborový systém, urobiť chroot()
a podobne.
Každý security modul môže naplniť takúto štruktúru pointrami na svoje funkcie.
Jadro potom pri operáciach, ktoré sú citlivé z bezpečnostného hľadiska,
prejde túto štruktúru z každého bezpečnostného modulu a zavolá funkciu
prislúchajúcu danej situácii. Teda napríklad ak jadro dostane požiadavku
vytvoriť nový adresár, zavolá sa funkcia (*inode_mkdir)()
.
Ak funkcia vráti nulu, volanie sa podarí. Ak napr. vráti kód EPERM
,
volanie skončí s "permission denied" a žiaden nový adresár sa konať nebude.
Root plug je ukážkový modul, ktorý definuje funkciu
rootplug_bprm_check_security()
a priradzuje pointer na ňu
do premennej bprm_check_security
dátovej štruktúry
security_operations
. Táto funkcia sa zavolá, keď systém
dostane požiadavku na vytvorenie nového procesu. Implementovaná je tak,
že zavolá funkciu usb_find_device(int vendor_id, int product_id)
.
static int rootplug_bprm_check_security (struct linux_binprm *bprm)
{
struct usb_device *dev;
...
if (bprm->e_gid == 0) {
dev = usb_find_device(vendor_id, product_id);
if (!dev) {
...
return -EPERM;
}
usb_put_dev(dev);
}
return 0;
}
Tá zistí či sa na USB zbernici nachádza zariadenie s daným ID. Ak áno, akcia je dovolená, ak nie vráti sa chyba o nedostatočnych prístupových právach.
Premenné vendor_id
a product_id
sú naplnené
pomocou makier:
module_param(vendor_id, uint, 0400);
MODULE_PARM_DESC(vendor_id, "USB Vendor ID of device to look for");
V prvom riadku je makro, ktoré definuje premennú vendor_id, typu uint s defaultnou hodnotu 0400. V druhom riadku je slovný popis tohoto parametra. Tieto informácie spolu s ďalšími makrami:
MODULE_DESCRIPTION("Root Plug sample LSM module, written for Linux Journal article");
MODULE_LICENSE("GPL");
používa program modinfo
z balíka module-init-tools
:
$ /sbin/modinfo root_plug
filename: /lib/modules/2.6.20/kernel/security/root_plug.ko
description: Root Plug sample LSM module, written for Linux Journal article
license: GPL
vermagic: 2.6.20 preempt mod_unload K8
depends: commoncap
srcversion: 564B87C638B0C8397B26F50
parm: debug:Debug enabled or not (bool)
parm: product_id:USB Product ID of device to look for (uint)
parm: vendor_id:USB Vendor ID of device to look for (uint)
LSM Root Plug sa zavolá len pri požiadavke na vytvorenie nového procesu.
To znamená, že procesov, ktoré boli spustené ešťe pred zavedením modulu, sa
kontrola netýka. Náš shell beží ďalej, démony bežia ďalej. Ale ak démon
potrebuje spustiť nový process (čo bežne robí napr. inetd
),
tak bez pripojeného správneho USB zariadenia sa mu to nepodarí.
ID zariadenia (v hexa) na USB zbernici prezradí /sbin/lsusb
:
$ lsusb Bus 1 Device 3: ID 0718:0081 Imation Corp. Bus 1 Device 1: ID 0000:0000 ...
Ak teda chceme zaviesť root plug s tým, že bude kontrolovať prítomnosť môjho USB kľúča, urobíme to takto:
# modprobe root_plug vendor_id=0x0718 product_id=0x0081
Pozor na prefix 0x ak ho nepoužijete, tak modul
naparsuje parametre inak ako s predstavujeme. V mojom prípade ID
čísla začínajú nulou a tak ich kód bude interpretovať ako oktálové
hodnoty. ID začínajúce číslicou, budú interpretované dekadicky.
Tak či onak, keď dôjde na lámanie chleba tak sa nebudú zhodovať
s identifikátorom, ktorý sa prečíta z kľúča. A budeme v pr... ;-)
Odstrániť modul príkazom rmmod
nebude možné, lebo by to
bol nový proces bežiaci pod účtom root-a. Neurobíme ani slušný shutdown.
Ak sa USB zariadenie nájde, tak ako root môžeme napr. spraviť:
# ls /etc/passwd /etc/passwd
Bez kľúčika ako root
# ls /etc/passwd -su: /bin/ls: Operation not permitted
Ale bez kľúčika ako obyčajný užívateľ:
$ ls /etc/passwd /etc/passwd
Ak ste to skúšali so mnou, pravdepodobne ste narazili na problém:
# modprobe root_plug vendor_id=0718 product_id=0081 FATAL: Error inserting root_plug (/lib/modules/2.6.20/kernel/security/root_plug.ko): Invalid argument
Vo svete LSM totiž root plug nie je sám. Security modulov môže
byť v systéme nahraných niekoľko. Pokiaľ nie je nahraný žiaden,
tak sa pužijú funkcie definované v súbore security/dummy.c
.
Na konci toho súboru sú pointre na tieto funkcie popriradzované do
štruktúry security_operations
:
void security_fixup_ops (struct security_operations *ops) { set_to_dummy_if_null(ops, ptrace); set_to_dummy_if_null(ops, capget); ...
Jednotlivé bezpečnostné moduly sa môžu reťaziť. Modul pri svojom
zavedení môže predefinovať niektoré z funkcií na seba.
Funkcia mod_reg_security()
v súbore
security/security.c
sa postará o to, že funkcie, ktoré nový
LSM nepredefinoval sa zoberú z dummy.c (funkcia verify()
).
Predefinované funkcie môžu buď definitívne rozhodnúť o povolení operácie,
alebo môžu rozhodnutie delegovať na nadradený LSM. Alebo môžu urobiť oboje
- ak prvý LSM v poradí operáciu zamietne, tak je zamietnutá, ale v opačnom
prípade môže rozhodnutie prenechať na ďalší modul v poradí.
Zádrhel je v tom, že za normálnych okolností je v jadre zapnutý LSM capability. To je ten, ktorý má na screenshot-e popis "Default Linux Capabilities". Na to aby sme sa mohli hrať so svojím root plug, musíme niečo s capability spraviť, pretože nepredefinováva funkciu, ktorá by umožnila nad neho nahrať ďalší LSM. Buď ho odstránime (za predpokladu, že ho máme skompilovaný ako modul)
# rmmod capability # modprobe root_plug vendor_id=0718 product_id=0081 #
, alebo ho upravíme tak, aby dovoľoval nahratie ďalšieho modulu
nad seba. Ani jedna z týchto možností nie je ideálna. Ak urobíme chybu
v úpravách capability, môžeme si vyrobiť slušnú bezpečnostnú dieru do systému.
Ak capability nebudeme mať vôbec, má to tiež nepríjemné následky. Jeden z
prvých, ktorý som si všimol je, že sa pri boote nespustí bind DNS server.
Ten totiž pradvepodobne volanie capget()
(viď manuálové stránky
capget(2)
a capabilities(7)
)
Ako som zistil, môj Slackware do dokonca ošetruje v bootovacích skriptoch
a ak sa bind nepodarí spustiť pokúsi sa modul capability nahrať a spustiť
bind znova.
Rozhodol som sa preto pre tretiu variantu. Nahrám root plug a na neho nahrám
capability. Drobný problém je v tom, že root plug to v originále takisto
nepodporuje a budeme ho musieť mierne upraviť. Konkrétne musíme dodať funkcie
(*register_security)()
a (*unregister_security)()
.
Tie dovolia nahratie nadradeného LSM.
static int rootplug_register_security(const char *name, struct security_operations *ops_to_reg) { int retval=0; if (next_ops_in_chain==NULL) { /* we are the only LSM in chain */ printk (KERN_INFO "RootPlug: registered next in chain: %s \n", name); next_ops_in_chain=ops_to_reg; return 0; /* RootPlug: return "OK, the next LSM is allowed to register" */ } else {/*there is another LSM after us, decision is up to next LSM in chain*/ /*Note: Not sure if this is really needed, I have no 3rd LSM to test with. If next LSM is "capability", than next_ops_in_chain->bprm_check_security is dummy_register_security - and that returns EINVAL */ retval=next_ops_in_chain->register_security(name,ops_to_reg); printk (KERN_INFO "RootPlug: next_ops_in_chain " "register_security() returned %d \n", retval); return retval; } } static int rootplug_unregister_security(const char *name, struct security_operations *ops_to_unreg) { if (next_ops_in_chain==ops_to_unreg) next_ops_in_chain=NULL; printk(KERN_INFO "RootPlug: unregistering %s\n",name); return 0; /* allow */ }
Aby sa tieto funkcie skutočne použili, treba pointrami na ne nainicializovať príslušné položky štruktúry security_operations:
static struct security_operations rootplug_security_ops = { ... .register_security = rootplug_register_security, .unregister_security = rootplug_unregister_security, };
Po prekompilovaní a nainštalovaní modulu môžeme odstrániť capability, nainštalovať root plug a vrátiť capability naspäť:
# rmmod capability # modprobe root_plug vendor_id=0718 product_id=0081 # ls -su: /bin/ls: Operation not permitted # modprobe capability #
No a to je celé. Na záver ešte pár poznámok:
usb_find_device()
je pomerne pomalé a môže mať dopad
na výkon systému ak ku spúšťaniu nového procesu dochádza často (napr. pri
spúšťaní skriptov a podobne)