This shows you the differences between two versions of the page.
so2:laboratoare:lab05:exercitii [2018/03/21 08:51] ionel.ghita |
so2:laboratoare:lab05:exercitii [2019/03/20 11:49] (current) gabriel.bercaru [1. [2.5p] Alocare de porturi I/O] - fixed typo ('kdb' instead of 'kbd') |
||
---|---|---|---|
Line 104: | Line 104: | ||
Obiectivul exercițiilor următoare este să creați un driver pentru a intercepta tastele apăsate (//keylogger//). Driver-ul va intercepta IRQ-ul destinat controller-ului de tastatură și va inspecta codurile tastelor primite, stocându-le într-un buffer. | Obiectivul exercițiilor următoare este să creați un driver pentru a intercepta tastele apăsate (//keylogger//). Driver-ul va intercepta IRQ-ul destinat controller-ului de tastatură și va inspecta codurile tastelor primite, stocându-le într-un buffer. | ||
- | ==== 1. [1.5p] Alocare de porturi I/O ==== | + | ==== 1. [2.5p] Alocare de porturi I/O ==== |
Pentru început ne propunem să alocăm spații din zona I/O pentru dispozitive hardware. Vom vedea că nu putem să alocăm spațiu pentru tastatură întrucât regiunea aferentă e deja alocată. Apoi vom aloca spațiu I/O pentru porturi care nu sunt folosite. | Pentru început ne propunem să alocăm spații din zona I/O pentru dispozitive hardware. Vom vedea că nu putem să alocăm spațiu pentru tastatură întrucât regiunea aferentă e deja alocată. Apoi vom aloca spațiu I/O pentru porturi care nu sunt folosite. | ||
Line 110: | Line 110: | ||
În fișierul ''kbd.c'' avem un schelet de implementare a unui driver de tastatură. Parcurgeți codul sursă din acest fișier și urmăriți funcția ''kbd_init''. Observați că porturile de care avem nevoie sunt ''I8042_STATUS_REG'' și ''I8042_DATA_REG''. | În fișierul ''kbd.c'' avem un schelet de implementare a unui driver de tastatură. Parcurgeți codul sursă din acest fișier și urmăriți funcția ''kbd_init''. Observați că porturile de care avem nevoie sunt ''I8042_STATUS_REG'' și ''I8042_DATA_REG''. | ||
- | Urmăriți comentariile marcate cu ''TODO 1'' pentru a înregistra porturile de I/O și asigurați-vă că ați tratat corect erorile. De asementea, deînregistrați porturile în funcția ''kbd_exit''. | + | Urmăriți comentariile marcate cu ''TODO 1'' pentru a înregistra porturile de I/O și asigurați-vă că ați tratat corect erorile. De asemenea, deînregistrați porturile în funcția ''kbd_exit''. |
<note tip> | <note tip> | ||
Line 141: | Line 141: | ||
Descărcați modulul din kernel folosind comanda<code> | Descărcați modulul din kernel folosind comanda<code> | ||
- | rmmod kdb | + | rmmod kbd |
</code> și verificați acum conținutul fișierului ''/proc/ioports''; observți că a dispărut alocarea spațiului I/O pentru cele două porturi. | </code> și verificați acum conținutul fișierului ''/proc/ioports''; observți că a dispărut alocarea spațiului I/O pentru cele două porturi. | ||
<note important> | <note important> | ||
- | Ștergeți liniile care înregistrează și eliberează cele două porturi înainte de a trece la exercițiul următor. | + | Comentați liniile care înregistrează și eliberează cele două porturi înainte de a trece la exercițiul următor. |
</note> | </note> | ||
- | ==== 2. [1.5p] Rutina de tratare a întreruperii ==== | + | ==== 2. [2p] Rutina de tratare a întreruperii ==== |
Urmărim să înregistrăm o rutină de tratare a întreruperii de tastatură. Veți scrie o funcție (rutină) de tratare a înteruperii și o veți înregistra folosind ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L145|request_irq]]''. | Urmărim să înregistrăm o rutină de tratare a întreruperii de tastatură. Veți scrie o funcție (rutină) de tratare a înteruperii și o veți înregistra folosind ''[[https://elixir.bootlin.com/linux/v4.15/source/include/linux/interrupt.h#L145|request_irq]]''. | ||
Line 207: | Line 207: | ||
<note tip> | <note tip> | ||
Pentru afișare în cadrul handler-ului de întrerupere folosiți o construcție de forma:<code> | Pentru afișare în cadrul handler-ului de întrerupere folosiți o construcție de forma:<code> | ||
- | printk(LOG_LEVEL "IRQ: %d, scancode=0x%x (%u, %c)\n", | + | pr_info("IRQ %d: scancode=0x%x (%u) pressed=%d ch=%c\n", |
- | irq_no, scancode, scancode, scancode); | + | irq_no, scancode, scancode, pressed, ch); |
</code> | </code> | ||
unde ''scancode'' este valoarea registrului citit cu ajutorul funcției ''i8042_read_data()''. | unde ''scancode'' este valoarea registrului citit cu ajutorul funcției ''i8042_read_data()''. | ||
Line 256: | Line 256: | ||
<note tip> | <note tip> | ||
Pentru afișare în cadrul handler-ului de întrerupere folosiți o construcție de forma:<code> | Pentru afișare în cadrul handler-ului de întrerupere folosiți o construcție de forma:<code> | ||
- | printk(LOG_LEVEL "IRQ %d: scancode=0x%x (%u) pressed=%d ch=%c\n", | + | pr_info(LOG_LEVEL "IRQ %d: scancode=0x%x (%u) pressed=%d ch=%c\n", |
irq_no, scancode, scancode, pressed, ch); | irq_no, scancode, scancode, pressed, ch); | ||
</code> | </code> | ||
Line 268: | Line 268: | ||
=== c. [1p] Colectarea caracterelor în buffer === | === c. [1p] Colectarea caracterelor în buffer === | ||
- | Dorim să colectăm caracterele apăsate (nu și celelalte taste) într-un buffer care apoi va fi citit din user space. | + | Dorim să colectăm caracterele apăsate (nu și celelalte taste) într-un buffer circular care apoi va fi citit din user space. |
- | Actualizați handler-ul de tratare a întreruperii (''kbd_interrupt_handler'') astfel încât un caracter ASCII citit să fie adăugat la sfârșitul buffer-ului aferent dispozitivului. Dacă buffer-ul este plin, caracterul va fi ignorat. | + | Actualizați handler-ul de tratare a întreruperii (''kbd_interrupt_handler'') astfel încât un caracter ASCII citit să fie adăugat la sfârșitul buffer-ului aferent dispozitivului. Dacă buffer-ul este plin, caracterul cel mai vechi o sa fie suprascris. |
<note tip> | <note tip> | ||
Line 277: | Line 277: | ||
</code> | </code> | ||
- | Dimensiunea curentă a buffer-ului este dată de câmpul ''buf_idx'' din structura de tip ''struct kbd'' aferentă dispozitivului. Va trebui să incrementați valoarea acestei variabile la fiecare caracter adăugat în buffer. | + | Dimensiunea curentă a buffer-ului este dată de câmpul ''count'' din structura de tip ''struct kbd'' aferentă dispozitivului. Câmpul ''put_idx'' specifică următoarea poziție de scriere, iar ''get_idx'' următoarea poziție de citire. Urmăriți implementarea funcției ''put_char'' pentru a observa cum sunt adăugate datele în buffer-ul circular. |
</note> | </note> | ||
- | |||
- | <note tip> | ||
- | Capacitatea buffer-ului, peste care nu puteți scrie, este dată de macro-ul ''BUFFER_SIZE''. | ||
- | </note> | ||
- | |||
==== 4. [2p] Citirea bufferului printr-un apel ''read'' ==== | ==== 4. [2p] Citirea bufferului printr-un apel ''read'' ==== | ||
Line 304: | Line 299: | ||
</note> | </note> | ||
- | Pentru copierea în user space veți folosi apelul [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/uaccess.h#L152|copy_to_user]]. Întrucât nu puteți folosi ''copy_to_user'' în timp ce aveți un spinlock luat, trebuie să folosiți un buffer temporar. | + | Completați funcția ''get_char'' pentru a obține următorul caracter care trebuie citit din buffer. Aveți grijă la implementarea buffer-ului circular (să nu citiți dincolo de dimensiunea buffer-ului și de poziția de scriere). |
- | <note tip> | + | Pentru copierea în user space veți folosi apelul [[https://elixir.bootlin.com/linux/v4.15/source/arch/x86/include/asm/uaccess.h#L249|put_user]] pentru fiecare caracter în parte, obținut folosind funcția ''get_char''. |
- | Buffer-ul temporar și dimensiunea acestuia sunt indicate de câmpurile ''tmp_buf'' și ''tmp_buf_idx'' din structura de tip dispozitiv. | + | |
+ | <note warning> | ||
+ | Nu puteți folosi ''copy_to_user'' sau ''put_user'' în timp ce aveți un spinlock luat. | ||
Urmăriți în laboratorul 4 secțiunea [[:so2:laboratoare:lab04#accesul_la_spatiul_de_adresa_al_procesului|Accesul la spațiul de adresă al procesului]]. | Urmăriți în laboratorul 4 secțiunea [[:so2:laboratoare:lab04#accesul_la_spatiul_de_adresa_al_procesului|Accesul la spațiul de adresă al procesului]]. | ||
Line 327: | Line 324: | ||
O să obțineți conținutul bufferului keylogger-ului din kernel space. | O să obțineți conținutul bufferului keylogger-ului din kernel space. | ||
- | ==== 5. [3p] Curățarea buffer-ului dacă este tastată o parolă ==== | + | ==== 5. [1.5p] Curățarea buffer-ului la scriere ==== |
- | Dorim să %%"curățăm"%% buffer-ul dacă este tastată o parolă. În acel moment buffer-ul va fi resetat: conținutul său va fi resetat și dimensiunea va fi ''0''. | + | Dorim să %%"curățăm"%% buffer-ul atunci când se scrie în device node-ul asociat driver-ului. În acel moment buffer-ul va fi resetat: conținutul său va fi resetat și dimensiunea va fi ''0''. |
- | Implementarea o veți face în fișierul ''kbd.c''. În rutina de tratare a întreruperii veți reține în câmpul ''passcnt'' câte caractere din parolă au fost potrivite. Parola este definită de macro-urile ''MAGIC_WORD'' și ''MAGIC_WORD_LEN''. | + | Implementarea o veți face în fișierul ''kbd.c''. Urmăriți comentariile marcate cu ''TODO 5''. Implementați funcția ''reset_buffer'' și adăugați operația ''write'' la ''kbd_fops''. |
- | + | ||
- | Când se detectează întreaga parolă, curătați buffer-ul. Pentru curățare, reinițializați câmpul ''buf_idx'' la valoarea ''0'' și umpleți cu ''0''-uri buffer-ul. | + | |
- | + | ||
- | La primirea unui caracter nou, verificați dacă acesta corespunde cu caracterul de pe poziția curentă din parolă. În caz afirmativ, incrementați contorul; altfel, resetați counterul la 0. | + | |
<note tip> | <note tip> | ||
- | În momentul resetării buffer-ului, în handler-ul de întrerupere va trebui să folosiți spinlock-ul pentru accesul exclusiv la buffer. Este posibil ca rutina de read să fie procesată de pe alt procesor și să acceseze buffer-ul în momentul în care handler-ul de întrerupere îl modifică. Veți folosi funcțiile [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L308|spin_lock]] și [[https://elixir.bootlin.com/linux/v4.15/source/include/linux/spinlock.h#L348|spin_unlock]]. | + | În momentul resetării buffer-ului, va trebui să folosiți spinlock-ul cu dezactivarea întreruperilor pentru accesul exclusiv la buffer. |
Parcurgeți secțiunea [[:so2:laboratoare:lab05#utilizare_spinlock-uri|Utilizarea spinlock-uri]]. | Parcurgeți secțiunea [[:so2:laboratoare:lab05#utilizare_spinlock-uri|Utilizarea spinlock-uri]]. | ||
Line 350: | Line 343: | ||
cat /dev/kbd | cat /dev/kbd | ||
</code> | </code> | ||
- | Apăsați pe taste și apoi apăsați conținutul macro-ului ''MAGIC_WORD'' (adică ''root'') și apoi consultați din nou conținutul buffer-ului. Ar trebui să fie resetat. | + | Apăsați pe taste și apoi rulați comanda ''echo "clear" > /dev/kbd'' și apoi consultați din nou conținutul buffer-ului. Ar trebui să fie resetat. |
===== Extra ===== | ===== Extra ===== | ||
- | |||
- | ==== [1 karma] Resetarea buffer-ului în rutina de read ==== | ||
- | |||
- | Implementarea curentă este problematică pentru că rutina de tratare a întreruperii este cea care face resetarea buffer-ului, lucru care este consumator de timp și nerecomandat să fie făcut în context întrerupere. | ||
- | |||
- | O soluție este să actualizăm comportamentul astfel încât resetarea buffer-ului să se producă la apelul funcției ''kbd_read()''. După ce informația este copiată în user space, buffer-ul este resetat. Nu mai resetăm în momentul tastării | ||
- | |||
- | Simplificați implementarea în așa fel încât resetarea buffer-ului să se producă în funcția ''kbd_read()'' imediat după copierea datele în user space. | ||
- | |||
- | <note tip> | ||
- | În momentul în care copiați datele în buffer-ul temporare ''tmp_buf'' resetați buffer-ul principal: adică umpleți buffer-ul ''buf'' cu zero-uri și inițialiați ''buf_idx'' la ''0''. | ||
- | </note> | ||
- | |||
- | Pentru verificarea după citirea din ''/dev/kbd'' veți avea buffer-ul resetat. Citiri ulterioare vor găsi buffer-ul gol. | ||
==== [3 karma] Utilizare kfifo ==== | ==== [3 karma] Utilizare kfifo ==== |