Niskopoziomowe nowości w Windows 8

   O nowościach w Windows 8 napisano już dużo, szczególnie o wzbudzającym sporo emocji nowym kafelkowym interfejsie. Warto jednak przyjrzeć się zmianom, które nie są widoczne na pierwszy rzut oka.

Jak praktycznie każde jądro systemu operacyjnego, także jądro Windows korzysta z przerwania timera, które pozwala wykonywać różne okresowe zadania, np. przełączanie się pomiędzy procesami. To, że wiele programów działa naraz jest bowiem tylko złudzeniem. Aby zapewnić wielozadaniowość procesor musi się pomiędzy nimi przełączać z dosyć dużą szybkością. Oczywiście są komputery wieloprocesorowe albo mające procesor wielordzeniowy, co jest obecnie standardem. Pozwala to na równoległe działanie aplikacji, ale i tak nie ma w typowym komputerze tylu rdzeni (zwykle dwa lub cztery), co uruchomionych procesów (kilkadziesiąt). Potrzebne jest więc przełączanie między nimi. Do tego celu można wykorzystać przerwanie zgłaszane przez timer. W Windows 7 takie przerwanie wykonywane jest co 15,6 milisekundy (ok. 64 razy na sekundę). Współczesne procesory obsługują różne tryby uśpienia, które pozwalają zmniejszyć pobór energii. W momencie wystąpienia przerwania procesor jest budzony. Im więc częściej następuje przerwanie tym rzadziej procesor znajduje się w trybie oszczędzania energii. Dlatego wymyślono tzw. nietykające jądro (tickless kernell), w którym timer nie zgłasza przerwania w stałych odstępach czasu. Zamiast tego jądro sprawdza, kiedy ma zostać uruchomione najbliższe zdarzenie z tych, które oczekują w kolejce, i ustawia odpowiednio timer, np. na 300 milisekund. W Linuksie tickless kernel pojawił się w wersji 2.6.21, czyli w 2007 roku, teraz trafił do Windows 8. Dzięki temu użytkownicy laptopów działających pod Windows 8 powinni zaobserwować dłuższy czas działania na baterii. Inny pozytywny efekt to zmniejszenie obciążenia systemu, na którym działa jedna lub więcej maszyn wirtualnych. Jest to oczywiste - dłużej przebywając w uśpieniu mniej będą obciążać system hosta. Przy modyfikacji timerów w Windows 8 wykorzystano funkcję, która pojawiła się w Windows 7: timery łączone. Pozwalają one nie tylko ustawić czas ich zadziałania, ale także dopuszczalne opóźnienie. Oznacza to, że mogą być one uruchomione w trochę innym momencie niż by wynikało z czasu, na który zostały uruchomione. Dzięki temu Windows widząc, że kilka timerów zgłosi zdarzenie w niedalekich odstępach czasu, może je poprzestawiać na ten sam moment i obudzić procesor tylko raz a nie kilka razy.

W celu oszczędzania energii stworzono tryb Connected Standby, w którym system jest uśpiony, ale nadal obsługuje połączenia sieciowe. Może np. odbierać pocztę albo połączenia VoIP. Działanie tego trybu wymaga jednak odpowiedniego wsparcia ze strony sprzętu.

W Windows 8 wprowadzono też zmiany mające ograniczyć zapotrzebowanie na pamięć. Osiągnięto to m.in. poprzez deduplikację pamięci. Polega to na tym, że jądro cyklicznie przeszukuje pamięć pod kątem stron, w których znajdują się te same dane. Gdy wykryje strony pamięci z taką samą zawartością, oznacza jedną z nich jako współdzieloną a pozostałą (lub pozostałe) jako wolne. W ten sposób dane przechowywane są tylko raz i są współdzielone przez różne procesy, czy też w obrębie jednego procesu, zależnie przez co te strony były alokowane. Jest to więc sprytny sposób na oszczędność pamięci przez pozbycie się duplikatów. Pojawia się jednak pytanie - co się stanie, gdy jeden z procesów będzie chciał zapisać coś w danej stronie? System operacyjny nie może przecież dopuścić, żeby jeden proces nadpisywał pamięć innego procesu (pomijając oczywiste jawne współdzielenie jakiegoś obszaru przez aplikacje). Otóż w takich sytuacjach jądro szybko kopiuje daną stronę pamięci, dzięki czemu procesy sobie nie przeszkadzają. System więc stara się współdzielić stronę pamięci tak długo, dopóki nie następuje zapis, dopiero wtedy wykonuje kopię. Stąd technika ta nazywa jest copy-on-write. Zysk z deduplikacji oczywiście jest proporcjonalny do ilości duplikatów, a to zależy do tego, co jest uruchomione na danym komputerze. Sprawdzi się na pewno w przypadku uruchomienia kilku maszyn wirtualnych z podobnymi systemami operacyjnymi. Alokacje pamięci i zapełnanie jej takimi samymi danymi mogą wykonywać też niektóre aplikacje. Wszystko zależy od tego, do czego użytkownik używa swojego komputera. Deduplikacja pamięci nie jest unikalna dla Windows, w Linuksie pojawiła się w 2009 roku w jądrze 2.6.32 jako Kernel Samepage Merging.

Inna zmiana w zarządzaniu pamięcią to możliwość alokacji pamięci z niskim priorytetem. Jeśli aplikacja korzysta z jakichś danych rzadko, może je oznaczyć jako posiadające niski priorytet. Wówczas Windows w momencie, gdy zacznie brakować pamięci, może dane te przenieść do pliku stronicowania. Oczywiście Windows i tak to robi, ale nie mając żadnych informacji ze strony aplikacji, musi w pewnym stopniu zgadywać, które dane muszą być dostępne w pamięci operacyjnej, a które można tymczasowo przenieść na dysk. Wprowadzono także inną nowość związaną z rozróżnianiem danych. Na podstawie analizy działania systemu wyodrębniono dane często i rzadko używane a następnie dokonano ich konsolidacji, aby nie były ze sobą wymieszane. Dzięki temu dane rzadziej używane mogą być odłożone do pliku stronicowania bez obaw, że znajdzie się wśród często używana dana, która spowoduje natychmiastowe przywrócenie strony pamięci z pliku. Część stron może więc na dłuższy czas być na dysku i pozwolić na zmniejszenie zajętości pamięci.

Oszczędność pamięci zapewniono także przez zmiany na nieco wyższym poziomie. Dodano m.in. nowy sposób uruchamiania usług, na żądanie. Są one normalnie wyłączone, a włączają się gdy zajdzie określone zdarzenie, np. interfejs sieciowy pozyska adres IP. Następnie wyłączają się. Ponadto usunięto kilkanaście usług, a inne przestawiono z uruchamiania automatycznego na ręczne.

Dużo nowości w Windows 8 dotyczy bezpieczeństwa. Wprowadzono funkcję ForceASLR, która włącza mechanizm ASLR (Address Space Layout Randomization, wprowadza losowość adresów, pod które ładowane są biblioteki DLL) także dla programów, które nie zostały oznaczone jako z nim kompatybilne. Inne usprawnienie dotyczące ASLR to HEASLR, czyli High Entropy ASLR. Ze względu na to, że aplikacje lubią mieć duże ciągłe obszary przestrzeni adresowej, mechanizm ASLR nie może wylosować adresów z pełnej dwugigabajtowej przestrzeni dostępnej dla procesorów na maszynach 32-bitowych. W związku z tym losowanie odbywa się spośród kilkuset adresów, co nie zapewnia zbyt dużej entropii. Ten sam sposób losowania był jednak używany także w 64-bitowych Windows mimo dostępności znacznie większej przestrzeni adresowej. HEASLR rozwiązuje ten problem pozwalając korzystać z zalet tak dużej przestrzeni adresowej na 64-bitowej wersji Windows 8. Aplikacje jednak muszą być odpowiednio skompilowane, aby mechanizm HEASLR był dla nich włączony, Windows 8 nie włącza go dla nich domyślnie. Kolejny sposób na zwiększenie bezpieczeństwa w Windows 8 to obsługa mechanizmu SMEP (Supervisor Mode Execution Prevention) dostępnego w procesorach Ivy Bridge. Uniemożliwia on jądru wykonywanie kodu należącego do przestrzeni użytkownika. Zabezpiecza to przed exploitami wykorzystującymi luki w jądrze do podniesienia uprawnień.

Sporo dziur w Windows dotyczyło luk w graficznych funkcjach GDI, które znajdowały się w sterowniku win32k.sys. Ponieważ działał on w trybie jądra, pozwalał na podniesienie uprawnień aplikacji. W czasach Windows NT 4 ten komponent systemu był poza jądrem, jednak włączono go do niego ze względów wydajnościowych. Przełączanie się bowiem pomiędzy kodem jądra a użytkownika wiąże się ze sporym narzutem. W Windows 8 nie powrócono do architektury NT, ale najzwyczajniej w świecie wyłączono aplikacjom użytkownika dostęp do funkcji z win32k.sys.

Często atakowaną strukturą w systemach operacyjnych jest sterta. W Windows Vista i 7 wprowadzono wiele zabezpieczeń sterty, w Windows 8 pojawiły się kolejne. Zrezygnowano z pola FreeEntryOffset, które było wykorzystywane przez atakujących do nadpisania dowolnego miejsca w pamięci. Zamiast niego stosowana jest mapa bitowa używana przez część FrontEnd zarządcy sterty. Ponadto zwiększono losowość adresów obszarów alokowanych na stercie przy pomocy funkcji NtAllocateVirtualMemory. Wprowadzono ochronę wskaźnika na strukturę _HEAP - sprawdzane jest czy adres zwalnianego fragmentu (chunk) nie pokrywa się ze wskaźnikiem sterty. Dodano niedeterminizm w działaniu LFH (Low Fragmentation Heap, sterta o niskiej fragmentacji), dzięki niemu alokując kolejne fragmenty pamięci nie dostaje się kolejny adresów. Utrudnia to pisanie exploitów. Pojawiła się funkcja Fast Fail. Dotychczas przy wykryciu nadpisania wskaźników listy używanej przez stertę, proces mógł nie zostać zakończony. Fast Fail wprowadza natychmiastowe zakończenie aplikacji przy wykryciu nadpisania wskaźników przez. Ciekawą nową funkcją jest Guard Page. Jest to strona pamięci umieszczana między wolnymi blokami, która ma wyłączone prawo dostępu. Dzięki takim stronom rozdzielającym bloki blokowane jest nadpisane kolejnego bloku, jeśli w bloku sąsiednim exploit doprowadził do przepełnienia. Guard Pages nie są stosowane zawsze, są wstawiane gdy wykryte zostanie zachowanie wskazujące na heap spray albo inne działanie przygotowujące do przepełnienia, np. szybka alokacja wielu bloków o tym samym rozmiarze. Dodano także ochronę przed zwolnieniem dowolnego fragmentu pamięci przez nadpisanie pola SegmentOffset w nagłówku sterty. Z kolei z funkcji RtlpLowFragHeapAllocFromContext usunięto przechwytywanie błędów, które łapało błąd każdego typu i w pewnych warunkach mogło pozwalać na obejście ASLR.

Swoją stertę ma także jądro Windows, określana jest ona mianem kernel pool. Tutaj także zadbano o walidację wskaźników, które łączą wolne bloki w podwójną listę. Były one sprawdzane już w Windows 7 przy odłączaniu z listy (gdy następuje alokacja), jednak niewystarczająco. W Windows 8 poprawiono to i dodano sprawdzanie przy dołączaniu do listy (przy zwalnianiu bloku). Wprowadzono pool cookie służące do wykrywania nadpisania wskaźników dotyczących list lookaside, list zwolnień oczekujących i alokacji z wyrównaniem z pamięci cache. Cookie działa podobnie jak wprowadzone we wcześniejszych wersjach Windows cookie na stercie i stosie. Jest to losowa wartość sprawdzana w określonych sytuacjach, znajdująca się przed ważnymi strukturami w pamięci. Jeśli wystąpi przepełnienie bufora wykonywane w celu nadpisania tych struktur, nadpisane zostanie również cookie, co zostanie wykryte a działanie exploita zostanie przerwane. Dodano walidację pola PoolIndex, które jest używane do odszukania właściwego desktryptora puli. W Windows 7 i wcześniej indeks ten nie był sprawdzany i możliwe było doprowadzenie do odwołania do nieistniejącego deskryptora i spowodowanie odwołania do zerowej strony pamięci poprzez wskaźnik typu NUll pointer. Umieszczając wcześniej swój kod w tej stronie (która byłaby zmapowana jednocześnie w przestrzeni jądra i aplikacji) atakujący mógłby doprowadzić do wykonania swojego kodu w przestrzeni jądra i podnieść swoje uprawnienia w systemie. Dodano szyfrowanie wskaźników do procesów przy alokacjach powiązanych z procesami. Wyłączono możliwość wykonywania kodu dla puli niestronicowanej, co ma zapobiec wykonaniu shellcode'u wstrzykniętego do przestrzeni jądra.

Tym, co widać na pierwszy rzut oka w Windows 8 to szybszy start. Częściowo jest to zasługa ulepszonej obsługi standardu UEFI (dostępny w niektórych nowszych płytach głównych), ale też nowego typu hibernacji systemu. Polega on na tym, że sesja użytkownika, czyli aplikacje oraz usługi, są normalnie zamykane, jednak sesja jądra jest hibernowana, czyli pamięć z przestrzeni jądra systemu jest zapisywana na dysk. Dzięki temu przy starcie ładowanie jądra i sterowników trwa bardzo szybko. Oczywiście inicjalizacja sterowników jest wykonywana gdyż stanu urządzeń nie da się odtworzyć z pliku hibernacji. Dzięki tym optymalizacjom start Windows 8 może zamknąć się w czasie nawet kilku sekund.