Patryk Czarnik Systemy Operacyjne 2000/01 Zadanie 4 DYNAMICZNE FUNKCJE ------------------ Dodałem do jądra trzy nowe funkcje: int reg_dynfun(const char *name, func funkcja); int ureg_dynfun(const char *name); int call_dynfun(const char *name, struct marg_t *arg, struct marg_t *wynik); Wywołanie funkcji reg_dynfun powoduje zarejestrowanie funkcji "funkcja" pod nazwą "name". Jeśli pod tą nazwą była już zarejestrowana funkcja, zostanie zastąpiona nową. Rejestrowana funkcja powinna być umieszczona w obszarze pamięci jądra, gdyż będzie wywoływana w trybie jądra. Funkcja zwraca wartość REG_OK, jeśli rejestrowanie się powiodło lub REG_ERR jeśli nastąpił błąd (np. może zabraknąć pamięci). Nazwa funkcji nie może być dłuższa niż 63 znaki. Wywołanie funkcji ureg_dynfun powoduje odrejestrowanie funkcji o podanej nazwie i zwrócenie wartości REG_OK. Jeśli nie ma takiej funkcji, zostanie zwrócony błąd REG_ERR. Aby wywołać zarejestrowaną uprzednio funkcję należy wywołać funkcję call_dynfun, podając nazwę tej funkcji i parametry, które ma ona otrzymać. Funkcja zwraca CALL_OK, lub CALL_NOF jeśli pod zadaną nazwą nie ma zarejestrowanej funkcji. Aby wywoływanie funkcji dobrze działało, wywołujący funkcję musi wiedzieć jakich parametrów ona oczekuje. Do przekazywania argumentów do i z funkcji służą argumenty typu marg_t. W tej strukturze znajduje się tablica wskaźników typu void*, w której można przekazać wskaźniki do danych, które chcemy przekazać funkcji, lub wskaźników do zmiennych, na które funkcja ma coś wpisać. Implementacja. Funkcje te są zaimplementowane w pliku kernel/call_dyn.c. Globalna (w tym pliku) zmienna lista_funkcji wskazuje na początek listy, w której przechowywane są zarejestrowane funkcje. W każdym rekordzie zapisana jest nazwa funkcji oraz wskaźnik do ciała funkcji. Rejestracja polega na dodaniu nowego rekordu do listy, lub zastąpieniu wskaźnika do funkcji, jeśli funkcja o tej nazwie już była. Odrejestrowanie to usunięcie rekordu z listy; wywołanie, to znalezienie odpowiedniego rekordu i wywołanie funkcji, na którą wskazuje wskaźnik w rekordzie. Zmiany w jądrze: dodatkowy plik include/linux/call_dyn.h dodatkowy plik kernel/call_dyn.c dodanie obiektu call_dyn.o do Makefile w katalogu kernel dodanie makr EXPORT_SYMBOL z trzema nowymi funkcjami do pliku kernel/ksyms.c SZYFROWANIE PLIKÓW ------------------ Po opisanej niżej zmianie jądra zainstalowanie modułu szyfr.o spowoduje włączenie mechanizmu szyfrowania plików przy odczycie i zapisie. Pojawi się nowy plik /proc/haslo, do którego każdy użytkownik będzie mógł pisać, a czytanie z którego daje od razu koniec pliku. Jeśli użytkownik wpisze do tego pliku hasło (za hasło uznawany jest ciąg znaków do pierwszego znaku '\0' lub końca wiersza), od tej chwili wszystkie operacje odczytu i zapisu do/z plików, których jest właścicielem będą się odbywały z zastosowaniem szyfrowania (każdy bajt z pliku będzie xorowany z odpowiednią pozycją w haśle). Wpisanie nowego hasła do pliku powoduje jego zmianę, a wpisanie hasła pustego (np. od razu końca wiersza) wyłączy szyfrowanie plików. Także usunięcie modułu z pamięci wyłączy szyfrowanie plików. Szyfrowanie/nieszyfrowanie i hasła są niezależne dla każdego użytkownika. Ze względów bezpieczeństwa dla administratora (root) nie można włączyć szyfrowania plików. Uwaga: Przy włączonym szyfrowaniu wszystkie pliki danego użytkownika są "deszyfrowane", także zapisane wcześniej pliki konfiguracyjne (np. .bashrc, konfiguracja mc), dlatego większość programów nie działa wtedy poprawnie. Bez problemu działa tworzenie, zapisywanie i odczytywanie plików za pomocą echo i cat. Także do pliku /proc/haslo najlepiej pisac echem. Implementacja. W pliku fs/read_write.c zmieniłem funkcje sys_read i sys_write. Teraz zamiast wywołania niskopoziomowej funkcji odczytu/zapisu (file->f_op->read/write) za pomocą mechanizmu "dynamicznych funkcji" wywoływana jest funkcja sec_read lub sec_write. Funkcja ta otrzymuje wszystkie parametry niezbędne jej do działania. Jeśli wywołanie się nie powiodło (taka funkcja nie jest zarejestrowana), wywoływana jest ta funkcja, co normalnie. W module szyfr przechowywana jest lista rekordów, z których każdy zawiera id użytkownika i haslo, które on podał. Na liście znajdują się ci użytkownicy, dla których stosowane ma być szyfrowanie plików. Jeśli użytkownik poda hasło, jego rekord dodawany jest do listy, lub zmieniane jest hasło w już istniejącym. Jeśli użytkownik poda hasło puste, jego rekord jest usuwany. Funkcje sec_read i sec_write sprawdzają, czy użytkownik operujący na pliku nie jest rootem i czy jest właścicielem tego pliku. Jeśli tak, to na liście wyszukiwany jest rekord danego użytkownika. Jeśli taki istnieje, wówczas stosowane jest szyfrowanie, jeśli nie lub nie został spełniony któryś z powyższych warunków, zostaje wywołana normalna funkcja (podana jako parametr). Jeśli należy szyfrować, to - w przypadku odczytu wywoływana jest funkcja odczytująca dane z pliku, a następnie w buforze użytkownika na odczytanych danych stosowane jest odszyfrowanie; - w przypadku zapisu najpierw dane w buforze zostają zaszyfrowane, następnie wywoływana jest funkcja zapisu, a następnie dane w pamięci są odszyfrowywane. Stosuję takie rozwiązanie, ponieważ funkcje odczytu/zapisu oczekują danych z przestrzeni użytkownika. W przypadku zapisu stosuję oszustwo: funkcja sys_write widzi bufor jako const char, ja w funkcji sec_write nie mam const i piszę po tej pamięci. Jest jeszcze problem ze zwiększaniem licznika korzystających z modułu. Przy odłączaniu modułu, program rmmod czyta (prawdopodobnie z /proc/modules) ten licznik, żeby wiedzieć czy można usunąć moduł. Ale czytanie odbywa się przez funkcję sys_read, a więc powoduje wywołanie funkcji sec_read. Umieszczenie makr MOD_INC_USE_COUNT i MOD_DEC_USE_COUNT na początku i końcu tej funkcji powoduje, że rmmod zawsze otrzyma niezerowy licznik użycia i nigdy się nie wykona. Dlatego nie obejmuję tymi makrami wywołania funkcji odczytu/zapisu w przypadku braku szyfrowania (pliki systemowe są własnością roota). Powoduje to niebezpieczeństwo usunięcia modułu w trakcie wykonywania tej funkcji, dzieje się tak np., gdy na innej konsoli system czeka na polecenie. Możliwosc zaradzenia temu widzę w zastosowaniu semafora liczącego liczbę procesów znajdujących się wewnątrz funkcji sec_read/sec_write. Przy usuwaniu modułu najpierw odrejestrować te funkcje, żeby nikt już do nich nie wchodził, a nastepnie zaczekać na semaforze, aż wszyscy wyjdą z tych funkcji i dopiero wtedy dokończyć usuwanie modułu. To spowoduje z kolei zawieszanie się rmmod na bliżej nieokreślony czas (niektóre operacje odczytu mogą być blokujące). Nie zaimplementowałem tego. Zmiany w jądrze: Zmiany w pliku fs/read_write.c --------- Aby zmienić jądro należy wywołać skrypt "zainstaluj" z paramerem określającym katalog, w którym znajdują się źródła jądra, które należy zmienić. Wywołanie bez parametrów zmieni jądro w katalogu /usr/src/linux Aby skompilować moduł użyć make.