AddressSanitizer (ASan) to lekkie i wydajne narzędzie wykrywające błędy zarządzania pamięcią w aplikacjach napisanych w językach C i C++. ASan został opracowany przez Google i jest dostępny jako część zarówno kompilatora LLVM (od wersji 3.1), jak i GCC (od wersji 4.8). Jego dokumentacja dostępna jest publicznie na Githubie.
ASan wykrywa:
- odwołania poza zakresem alokowanej pamięci (out-of-bounds access)
- użycie zwolnionej pamięci (use-after-free)
- podwójne zwolnienia (double free)
- wycieki pamięci (memory leaks)
- błędy alokacji i dealokacji
- błędne użycie stosu i sterty
AddressSanitizer działa głównie na architekturach x86 i x86_64, obsługując systemy takie jak Linux, macOS (Darwin), FreeBSD oraz Android. Ze względu na jego architekturę i potrzeby pamięciowe, ASan nie jest raczej uruchamiany bezpośrednio na mikrokontrolerach, które mają ograniczone zasoby RAM, brak pełnego systemu operacyjnego i specyficzne środowiska, ale to jednak nie oznacza, że nie da się go wcale wykorzystać przy tworzeniu firmware...
OpenBeken to wieloplatformowe firmware przeznaczone dla urządzeń IoT. OBK normalnie wspiera platformy takie jak BK723x (BK7231T, BK7231N, BK7238), XRadio (XR809, XR806, XR872), BL602 (znany też jako LF686), WinnerMicro (W600, W601, W800, W801), Lightning Semi (LN882H), Realtek AmebaZ (RTL8710B), Realtek AmebaZ2 (RTL8710C, RTL8720), Realtek AmebaD (RTL8720D, RTL8720CS), Realtek RTL8711/RTL8195 (RTL8711AM, RTL8195A), TR6260, ECR6600, ESP32 (ESP32, S2, S3, C2, C3, C6), ale również można go uruchomić bezpośrednio na PC na systemie operacyjnym Windows lub Linux.
Symulator OBK to w zasadzie port firmware na platformę Windows z dodanym edytorem schematów i prostą symulacją GPIO. Dzięki przeportowaniu obsługi MQTT z LWIP, firmware w takiej formie jest też w stanie połączyć się z Home Assistant. Pozwala on również na uruchamianie automatycznych testów....
Te testy są również uruchamiane na samym Githubie - w pełni automatycznie, z każdą kompilacją. Od tego zostaje już tylko krok do uruchamiania z nimi AddressSanitizera. Powiązana sekcja workflow.yaml:
Kod: YAML
Powyższy workflow kompiluje OBK na Linux (wraz z integracją skryptów Berry) a potem uruchamia jego automatyczne testy - wszystko online na Githubie.
Wymagane ustawienia makefile:
CPPFLAGS += -g -fsanitize=address -fno-omit-frame-pointer
CFLAGS += -g -fsanitize=address -fno-omit-frame-pointer
LDFLAGS += -g -static-libasan -fsanitize=address
Zobaczmy teraz działanie powyższego mechanizmu w praktyce. Otworzyłem Pull Request (upraszczając - sugestię zmian) w którym dodałem tylko prosty wyciek pamięci - wywołanie malloc bez odpowiedniego free:
Github rozpoczął wykonywanie workflow i po niecałej minucie wykrył problem:
Uruchomienie OBK na Linuxie z ASan zakończyło się niepowodzeniem:
W logu dostępne są szczegóły co poszło nie tak:
Direct leak of 1234 byte(s) in 1 object(s) allocated from:
#0 0x561700dfb407 in __interceptor_malloc (/home/runner/work/OpenBK7231T_App/OpenBK7231T_App/build/win_main+0x162407)
#1 0x561700ed3da2 in http_fn_index src/httpserver/http_fns.c:217
#2 0x561700ee9ad5 in HTTP_ProcessPacket src/httpserver/new_http.c:826
#3 0x561700f5c521 in Test_FakeHTTPClientPacket_Generic src/selftest/selftest_http.c:86
#4 0x561700f5c6d8 in Test_FakeHTTPClientPacket_GET src/selftest/selftest_http.c:102
#5 0x561700f62276 in Test_Http_LED_RGB src/selftest/selftest_http_led.c:248
#6 0x561700f63106 in Test_Http_LED src/selftest/selftest_http_led.c:422
#7 0x561700f89785 in Win_DoUnitTests src/win_main.c:253
#8 0x561700f8a4b5 in main src/win_main.c:538
#9 0x7fd1f7429d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f)
Mamy pokazany cały stos wywołań wraz z odpowiednimi liniami kodu.
Druga próba - wykroczenie poza bufor:
Kod: C / C++
Złapane:
=================================================================
==3400==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60c0000000bb at pc 0x558985a08c0b bp 0x7fff5f2c79e0 sp 0x7fff5f2c79d0
WRITE of size 1 at 0x60c0000000bb thread T0
#0 0x558985a08c0a in Main_Init src/user_main.c:1428
#1 0x558985a0eb3b in SIM_ClearOBK src/win_main.c:166
#2 0x558985a0fafb in main src/win_main.c:534
#3 0x7f1632029d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f)
#4 0x7f1632029e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f)
#5 0x5589857f06b4 in _start (/home/runner/work/OpenBK7231T_App/OpenBK7231T_App/build/win_main+0xd26b4)
0x60c0000000bb is located 0 bytes to the right of 123-byte region [0x60c000000040,0x60c0000000bb)
allocated by thread T0 here:
#0 0x558985880407 in __interceptor_malloc (/home/runner/work/OpenBK7231T_App/OpenBK7231T_App/build/win_main+0x162407)
#1 0x558985a08bca in Main_Init src/user_main.c:1427
#2 0x558985a0eb3b in SIM_ClearOBK src/win_main.c:166
#3 0x558985a0fafb in main src/win_main.c:534
#4 0x7f1632029d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f)
SUMMARY: AddressSanitizer: heap-buffer-overflow src/user_main.c:1428 in Main_Init
Shadow bytes around the buggy address:
0x0c187fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c187fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c187fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c187fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x0c187fff8000: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
=>0x0c187fff8010: 00 00 00 00 00 00 00[03]fa fa fa fa fa fa fa fa
0x0c187fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c187fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c187fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c187fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c187fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==3400==ABORTING
Trzecia próba - podwójne zwolnienie:
Kod: C / C++
Rezultat:
=================================================================
==3475==ERROR: AddressSanitizer: attempting double-free on 0x60c000000040 in thread T0:
#0 0x561b502530b7 in __interceptor_free (/home/runner/work/OpenBK7231T_App/OpenBK7231T_App/build/win_main+0x1620b7)
#1 0x561b503dbbe6 in Main_Init src/user_main.c:1429
#2 0x561b503e1b08 in SIM_ClearOBK src/win_main.c:166
#3 0x561b503e2ac8 in main src/win_main.c:534
#4 0x7f82d5229d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f)
#5 0x7f82d5229e3f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x29e3f)
#6 0x561b501c36b4 in _start (/home/runner/work/OpenBK7231T_App/OpenBK7231T_App/build/win_main+0xd26b4)
0x60c000000040 is located 0 bytes inside of 123-byte region [0x60c000000040,0x60c0000000bb)
freed by thread T0 here:
#0 0x561b502530b7 in __interceptor_free (/home/runner/work/OpenBK7231T_App/OpenBK7231T_App/build/win_main+0x1620b7)
#1 0x561b503dbbda in Main_Init src/user_main.c:1428
#2 0x561b503e1b08 in SIM_ClearOBK src/win_main.c:166
#3 0x561b503e2ac8 in main src/win_main.c:534
#4 0x7f82d5229d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f)
previously allocated by thread T0 here:
#0 0x561b50253407 in __interceptor_malloc (/home/runner/work/OpenBK7231T_App/OpenBK7231T_App/build/win_main+0x162407)
#1 0x561b503dbbca in Main_Init src/user_main.c:1427
#2 0x561b503e1b08 in SIM_ClearOBK src/win_main.c:166
#3 0x561b503e2ac8 in main src/win_main.c:534
#4 0x7f82d5229d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f)
SUMMARY: AddressSanitizer: double-free (/home/runner/work/OpenBK7231T_App/OpenBK7231T_App/build/win_main+0x1620b7) in __interceptor_free
==3475==ABORTING
ASan znów sobie poradził.
W ten oto sposób każda zmiana firmware jest automatycznie sprawdzana pod kątem wycieków pamięci i to wszystko odbywa się online, na Githubie.
Warto też dodać, że ASAN oferuje możliwość zablokowania zgłaszania problemów z wybranymi funkcjami - plik supressions, przykład:
leak:CMD_CreateAliasHelper
leak:Test_HassDiscovery
leak:TuyaMCU_RunFrame
Podsumowując, mimo że AddressSanitizer nie działa bezpośrednio na mikrokontrolerach, można z powodzeniem wykorzystać go w projektach firmware jeśli tylko możemy je również skompilować na którąś ze wspieranych platform. OpenBeken to wspiera i jak widać otwiera to naprawdę szerokie możliwości - symulowanie urządzenia IoT na PC, symulowanie kilku urządzeń (różne porty), testowanie z Home Assistant w obrębie jednej maszyny, automatyczne testy no i właśnie, tak jak w tym temacie - automatyczne odnajdywanie błędów zarządzania pamięcią. Od teraz każda zmiana kodu OBK (ode mnie lub od kontrybutorów) jest w ten sposób sprawdzana online, jeszcze na Githubie, nawet bez wgrywania na MCU a błędy są z miejsca wykrywane i zgłaszane przez automat. Bardzo wygodne i przydatne.
Czy korzystacie z AddressSanitizer lub z podobnych rozwiązań?
PS: Oczywiście ASan uruchomiony w ten sposób znajdzie błędy tylko w tej "głównej/wspólnej" części firmware, która jest dostępna na wszystkich platformach. W przypadku platform-specific code, kodu SDK danej platformy, ASan uruchomiony np. na Linuxie nie wykryje problemów, ale nie uważam by to był problem - to skrajny przypadek..