AppArmor i profilowanie aplikacji

Spis treści

Po ostatnich doniesieniach na temat błędu jaki został znaleziony w Firefox'ie , doszedłem do wniosku, że najwyższy czas nauczyć się obsługi narzędzia AppArmor . Ma ono pomóc w kontrolowaniu praw dostępu do zasobów systemu operacyjnego, np. plików, katalogów czy określonych urządzeń. Jeśli weźmiemy przytoczony wyżej błąd, to przeglądarka bez takiego profilu AppArmor'a była w stanie przeszukać lokalne pliki i wysłać je gdzieś na net, co powodowałoby udostępnienie poufnych danych, np. historia poleceń shell'owych, czy klucze prywatne.

Aktywacja AppArmor'a

Jako, że AppArmor jest częścią kernela, to jego włączenie odbywa się przez dopisanie odpowiednich parametrów w bootloaderze :

APPEND ... apparmor=1 security=apparmor ...

Po dopisaniu tych dwóch opcji, resetujemy maszynę. Po tym jak się ona podniesie, AppArmor powinien być aktywny.

Narzędzia do obsługi AppArmor'a

By wszystko nam ładnie działało, to musimy jeszcze sobie doinstalować kilka pakietów. Na dobrą sprawę wystarczy wgrać pakiet apparmor ale w debianie mamy jeszcze dwa pakiety, w których zawarte są predefiniowane profile dla określonych aplikacji. Oczywiście, możemy sobie te profile stworzyć samemu ale skoro już ktoś się napracował, to czemu nie wykorzystać jego pracy? Wpisujemy zatem w terminal to poniższe polecenie:

# aptitude install apparmor apparmor-profiles apparmor-profiles-extra

Profile dla AppArmor'a

Wszystkie profile AppArmor'a dla aplikacji są trzymane w katalogu /etc/apparmor.d/ . To zwykłe pliki tekstowe, które są później kompilowane do postaci binarnej w celu załadowania do kernela, zwykle w fazie startu systemu. Naturalnie, każdy z profili możemy sobie osobno załadować/wyładować w trakcie pracy systemu operacyjnego jeśli zajdzie taka potrzeba.

Profile możemy ładować w kilku trybach, min. enforce , complain i audit . W przypadku trybu enforce , wszelkie zdarzenia niepasujące do zdefiniowanych reguł będą blokowane, a informacja o całym zajściu zostanie zanotowana w logu. Tryb complain różni się od enforce jedynie tym, że akcja zablokowania dostępu do zasobu nie zostanie podjęta. Z kolei tryb audit ma na celu zalogowanie wszelkich zdarzeń bez względu na to czy są one zaakceptowane czy też blokowane przez AppArmor. Zwykle jednak będziemy korzystać z tych dwóch pierwszych.

Przygotowywanie profilu

Profile możemy pisać manualnie z wykorzystaniem zwykłego edytora tekstu lub też możemy skorzystać z narzędzi obecnych w dodatkowym pakiecie apparmor-utils . Te narzędzia ułatwiają pracę z AppArmor'em ale też mogą pojawić się błędy przy ich wykorzystywaniu. Jeśli chcemy skorzystać z narzędzi, to cały proces tworzenia profilu jest praktycznie automatyczny i do tego wielce interaktywny.

Zaczynamy od określenia położenia pliku wykonywalnego aplikacji, którą chcemy sprofilować, np. /opt/firefox/firefox . Następnie wywołujemy aa-autodep podając w argumencie ścieżkę do tego pliku:

# aa-autodep /opt/firefox/firefox
Writing updated profile for /opt/firefox/firefox.

Zaowocuje to stworzeniem pliku profilu w katalogu /etc/apparmor.d/ o nazwie opt.firefox.firefox mającego poniższą treść:

# Last Modified: Fri Aug 8 11:50:38 2015
#include <tunables/global>

/opt/firefox/firefox flags=(complain) {
  #include <abstractions/base>

  /opt/firefox/firefox mr,

}

Teraz odpalamy aa-genprof , w argumencie którego również podajemy ścieżkę do pliku:

# aa-genprof /opt/firefox/firefox
...
Profiling: /opt/firefox/firefox

[(S)can system log for AppArmor events] / (F)inish

W tej chwili profil dla tej powyższej aplikacji jest ustawiony w tryb complain , czyli nie będą blokowane żadne akcje przy dostępie do zasobów systemowych ale będą zwracane komunikaty zawierające takie żądania. Dlatego też, odpalamy w tym momencie profilowany program i staramy się go używać tak jak dotąd, tj. wchodzimy w menu, przestawiamy różne rzeczy, włazimy na net, itp. Chodzi o to, by ustalić z jakich plików/katalogów/urządzeń dana aplikacja chce korzystać. To wszystko zostanie zalogowane i na podstawie tych komunikatów zostanie stworzony dokładny profil.

W przypadku posiadania systemd, logowanie może nie działać jak należy. Dzieje się tak ze względu na journal, za sprawą którego nie mamy już pliku /var/log/syslog , a to właśnie ten plik AppArmor bierze pod uwagę przy skanowaniu. Dobrze jest zatem przekierować wyjście dziennika do tego pliku przy pomocy:

# journalctl -n 0 -f > /var/log/syslog

Gdy skończymy się bawić, zamykamy program i w terminalu wciskamy S . Spowoduje to przeskanowanie logu i zwrócenie szeregu zapytań odnośnie dostępu do zasobów, przykładowo:

...
Profile:  /opt/firefox/firefox
Path:     /dev/dri/card0
Mode:     rw
Severity: unknown

  1 - #include <abstractions/X>
  2 - #include <abstractions/evince>
  3 - #include <abstractions/gnome>
  4 - #include <abstractions/kde>
  5 - #include <abstractions/lightdm>
  6 - #include <abstractions/totem>
  7 - #include <abstractions/ubuntu-browsers.d/chromium-browser>
  8 - #include <abstractions/ubuntu-browsers.d/kde>
  9 - #include <abstractions/ubuntu-browsers.d/mailto>
  10 - #include <abstractions/ubuntu-browsers.d/multimedia>
  11 - #include <abstractions/ubuntu-gnome-terminal>
  12 - #include <abstractions/ubuntu-konsole>
  13 - #include <abstractions/ubuntu-unity7-base>
 [14 - /dev/dri/card0]
[(A)llow] / (D)eny / (I)gnore / (G)lob / Glob with (E)xtension / (N)ew / Abo(r)t / (F)inish / (M)ore
...

W zależności od aplikacji, takich zapytań może być sporo. Generalnie rzecz biorąc, interesują nas głównie ścieżki Path: /dev/dri/card0 oraz uprawnienia Mode: rw . W tym przypadku proces Firefox'a prosi o zapis oraz odczyt urządzenia karty graficznej. Mamy do wyboru 14 pozycji, z czego numery od 1-13 włącznie zawierają predefiniowane profile abstrakcyjne, które możemy wykorzystać budując nowy profil. Te podpowiedzi pojawiają się ilekroć dana ścieżka występuje w którymś z przytoczonych profili.

Jeśli chcemy korzystać z plików zlokalizowanych w katalogu /etc/apparmor.d/abstractions/ , to przydałoby się zajrzeć w każdy profil tam umieszczony i ustalić, czy faktycznie potrzebujemy całego profilu, czy jedynie pojedynczych pozycji. Jeśli w takim profilu, który chcemy załączyć, będzie sporo wpisów i tylko jeden czy dwa, które nam nie będą pasować, to zawsze możemy załączyć cały profil i dodać dyrektywę deny .

W przypadku posiadania dwóch wpisów w profilu, dyrektywa deny zawsze jest brana pod uwagę w pierwszej kolejności. Przykładowo, jeśli zezwolimy na dostęp do urządzenia /dev/dri/card0 dopisując odpowiednią linijkę w pliku profilu, to można przypuszczać, że dostęp zostanie przyznany. Natomiast jeśli w tym samym pliku lub też w którymś załączonym profilu abstrakcyjnym będzie istnieć inny wpis z tą powyższą ścieżką, z tym, że poprzedzony dyrektywą deny , to dostęp do tego urządzenia zostanie odmówiony i to pomimo faktu, że manualnie zezwoliliśmy na jego odczyt/zapis.

Aplikacje zwykle korzystają z plików w określonych katalogach, np. ich własny, i zamiast zezwalać na dostęp do każdego z plików w takim folderze, lepiej jest zezwolić na dostęp do całego katalogu. Jeśli na liście nie widać odpowiedniej pozycji, to zawsze możemy określić nowy zasób wciskając N i podać mniej akuratną pozycję przez zastosowanie masek, np. * lub ** . Różnią się one jedynie poziomem katalogów. * odpowiada za określony katalog, natomiast ** uwzględnia również podkatalogi.

Zatem w powyższym przypadku, jeśli chcielibyśmy zezwolić na dostęp do wszystkich plików i katalogów w folderze /dev/ , wciskamy N i wpisujemy /dev/** :

Enter new path: /dev/**
...
  14 - /dev/dri/card0
 [15 - /dev/**]
[(A)llow] / (D)eny / (I)gnore / (G)lob / Glob with (E)xtension / (N)ew / Abo(r)t / (F)inish / (M)ore

Jak widać, pojawiła się nowa pozycja nr. 15. Teraz już tylko pozostaje zezwolić (lub zabronić) na dostęp wciskając klawisz A . Cały proces się będzie powtarzał aż do wyczerpania wszystkich żądań o zasoby.

Nie musimy także się bać o duplikaty reguł, bo te automatycznie zostaną usunięte z pliku ilekroć tylko znajdzie się inna reguła, która będzie pokrywać te wcześniej zdefiniowane wpisy.

Tak powstały profil włączamy przy pomocy aa-enforce :

# aa-enforce /etc/apparmor.d/opt.firefox.firefox
Setting /etc/apparmor.d/opt.firefox.firefox to enforce mode.

Test profilu

Odpalamy teraz profilowaną aplikację i weryfikujemy czy AppArmor ogranicza jej dostęp do zasobów przy pomocy aa-status :

# aa-status
...
3 processes have profiles defined.
3 processes are in enforce mode.
   /opt/firefox/firefox (68279)
...

Możemy także sprawdzić czy proces jest ograniczony przez AppArmor przy pomocy narzędzia ps :

# ps auxZ | grep -v '^unconfined'
LABEL                           USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
/opt/firefox/firefox (enforce)  morfik    68279 10.1 25.3 2734420 484552 ?      Sl   17:25  10:28 /opt/firefox/firefox -new-instance -ProfileManager

Usuwanie profilu

Jako, że te profile są kompilowane do postaci binarnej, to w przypadku gdy chcemy się ich pozbyć, nie wystarczy samo usunięcie pliku profilu. Musimy go usunąć również z kernela. Dlatego też zanim usuniemy sam plik, musimy skorzystać z apparmor_parser z przełącznikiem -R , przykładowo:

# apparmor_parser -R /etc/apparmor.d/opt.firefox.firefox

To powyższe polecenie nie usuwa samego pliku profilu, a jedynie wyładowuje go z kernela. Przy czym, trzeba pamiętać, że profil nie jest określany przez samą nazwę pliku, tylko przez ten poniższy blok:

/opt/firefox/firefox {
...
}

Jeśli byśmy usunęli ten plik, to wyładowanie profilu z kenrela nie będzie możliwe.

Przeładowanie profilu

Zmieniając wpisy w profilu aplikacji AppArmor'a, nie dokonujemy zmian w polityce bezpieczeństwa bezpośrednio. Zmieniony profil trzeba przeładować i do tego celu również wykorzystujemy apparmor_parser , z tym, że teraz z przełącznikiem -r :

# apparmor_parser -r /etc/apparmor.d/opt.firefox.firefox

Włączanie i wyłączanie profilu

Wszystkie pliki obecne w katalogu /etc/apparmor.d/ mające poprawną składnię profilu będą automatycznie ładowane ze startem systemu. Jeśli z jakichś powodów nie chcemy ładować któregoś z nich, nie musimy usuwać samego pliku, wystarczy stworzyć dowiązanie symboliczne do katalogu /etc/apparmor.d/disable/ . Oczywiście, nie musimy tego robić ręcznie -- możemy posłużyć się aa-disable :

# aa-disable opt.firefox.firefox
Disabling /etc/apparmor.d/opt.firefox.firefox.

# ls -al /etc/apparmor.d/disable/
...
lrwxrwxrwx  1 root root   35 2015-08-08 10:29:56 opt.firefox.firefox -> /etc/apparmor.d/opt.firefox.firefox

Profil włączamy przez usunięcie samego dowiązania, z tym, że możemy to zrobić przy po mocy jednego z dwóch narzędzi: aa-enforce oraz aa-complain . Różnice między tymi dwoma wyjaśniliśmy sobie wyżej. Warto jednak dodać, że po włączeniu profilu via aa-complain , do zwrotki profili w pliku zostanie dopisana automatycznie flaga:

/opt/firefox/firefox flags=(complain) {
...
}

I to właśnie tylko ona odróżnia profil enforce od complain.

Zakodowana nazwa pliku

Podczas ręcznego profilowania możemy natknąć się na log, który nie zwróci nam jako takiej nazwy pliku, do którego aplikacja żąda dostępu. Zamiast niego, w logu zostanie nam zwrócony długi ciąg znaków, podobny do tego poniżej:

Aug 8 07:58:21 morfikownia audit[34909]: AVC apparmor="DENIED" operation="open" profile="/opt/firefox/firefox" name=2F686F6D652F6D6F7266696B2F2E6D6F7A696C6C612F66697265666F782F4372617368205265706F7274732F496E7374616C6C54696D653230313530383037303835303435 pid=34909 comm="firefox" requested_mask="r" denied_mask="r" fsuid=1000 ouid=1000

Jak zatem ustalić o jaki plik chodzi? Do tego celu wykorzystujemy narzędzie aa-decode , w argumencie to którego podajemy wartość parametru name widoczną wyżej w logu:

# aa-decode 2F686F6D652F6D6F7266696B2F2E6D6F7A696C6C612F66697265666F782F4372617368205265706F7274732F496E7374616C6C54696D653230313530383037303835303435
Decoded: /home/morfik/.mozilla/firefox/Crash Reports/InstallTime20150807085045

Zatem zakodowany plik to /home/morfik/.mozilla/firefox/Crash Reports/InstallTime20150807085045 . Jak widzimy, w katalogu Crash Reports jest spacja i to właśnie ona sprawia, że AppArmor nie może nam podać prostej ścieżki i musi ją zwrócić w takiej formie jak widać wyżej.

Mikhail Morfikov avatar
Mikhail Morfikov
Po ponad 10 latach spędzonych z różnej maści linux'ami (Debian/Ubuntu, OpenWRT, Android) mogę śmiało powiedzieć, że nie ma rzeczy niemożliwych i problemów, których nie da się rozwiązać. Jedną umiejętność, którą ludzki umysł musi posiąść, by wybrnąć nawet z tej najbardziej nieprzyjemniej sytuacji, to zdolność logicznego rozumowania.