logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

AddressSanitizer i wyszukiwanie błędów zarządzania pamięcią w firmware na mikrokontrolery

p.kaczmarek2 01 Cze 2025 10:31 702 5

TL;DR

  • AddressSanitizer w firmware OpenBeken uruchamianym na PC/Linux służy do automatycznego wyszukiwania błędów zarządzania pamięcią w wspólnej części kodu IoT.
  • OpenBeken działa jako symulator na Windows i Linux z testami uruchamianymi w GitHub Actions, a kompilacja dostaje flagi -fsanitize=address i -fno-omit-frame-pointer.
  • W logach wykryto wyciek 1234 bajtów oraz heap-buffer-overflow na 123-bajtowym buforze.
  • Automat w mniej niż minutę wykrył wyciek, overflow i double-free, pokazując pełne stosy wywołań z liniami źródłowymi.
  • ASan nie obejmuje kodu platform-specific ani SDK mikrokontrolera, więc sprawdza głównie część wspólną firmware.
Wygenerowane przez model językowy.
📢 Słuchaj (AI):
  • Fragment logu AddressSanitizer wykrywający wyciek pamięci podczas automatycznego testu na Githubie.
    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
    Zaloguj się, aby zobaczyć kod

    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:

    Zrzut ekranu z platformy GitHub pokazujący różnicę w pliku http_fns.c, gdzie do funkcji http_fn_index dodano linię malloc(1234); w ramach Pull Requesta #1661.
    Github rozpoczął wykonywanie workflow i po niecałej minucie wykrył problem:
    Lista zgłoszeń pull request na Githubie, jedno z nich (Update http_fns.c) oznaczone na czerwono jako nieudane.
    Uruchomienie OBK na Linuxie z ASan zakończyło się niepowodzeniem:
    Zrzut ekranu z Github Actions pokazujący niepowodzenie kroku Build Linux Simulator and Run Tests With Address Sanitizer podczas testowania Pull Requesta dla pliku http_fns.c.
    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++
    Zaloguj się, aby zobaczyć kod

    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++
    Zaloguj się, aby zobaczyć kod

    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..
    Pomogłem? Kup mi kawę.
    O autorze
    p.kaczmarek2
    Moderator Smart Home
    Offline 
  • #2 21566865
    Konto nie istnieje
    Poziom 1  
  • #3 21566879
    p.kaczmarek2
    Moderator Smart Home
    Posty: 14620
    Pomógł: 655
    Ocena: 12636
    Oczywiście sprawdzanie na docelowej architekturze jest najlepsze i tu masz rację, ale w tym temacie nie chciałem z tym walczyć, raczej bardziej zależało mi by pokazać, jakiej metody używamy by przyśpieszyć pracę w kontekście kodu który nie jest związany bezpośrednio z platformą. Do tej pory się to sprawdzało, ale jeśli natrafię na jakiś ciekawszy przypadek gdzie to się nie sprawdziło bądź nawet wprowadziło w błąd to dam znać.

    A może jesteś w stanie mi już z góry podać jakiś przykład błędu "pamięciowego", ku przestrodze? Jakiś przykład oczywistego błędu (nie skrajnego przypadku) gdzie prosty test z ASan zawodzi w pokazanym tu kontekście? Z chęcią zobaczę i przyda się na przyszłość. Tylko raczej proszę bez rzeczy pokroju "zły rozmiar typu danych"... o tym to wiadomo, że jak rozmiar struktury wyjdzie inny to "bezpieczne" offsety będą inne.

    Jeśli rozmiar danego typu bądź wyrównanie struktury będzie inne między platformami, to wtedy rzeczywiście potrafię sobie wyobrazić, że odnosząc się do niej np. po offsecie w bajtach, można mieć różne rezultaty. Tylko czy to nie skrajny przypadek... zastanawiam się, czy ja tak gdzieś robię? Korzystam co jakiś czas z makra offsetof, raczej nie daję na sztywno offsetów...

    Chociaż... nigdzie nie dałem założenia, że oczekiwane jest 100% skuteczności od ASan. Tu bardziej chodzi o wyłapywanie prostych pomyłek ode mnie i kontrybutorów. Już samo wstępne odsianie PR jest pomocne.

    Oprócz tego dziękuję za polecenie Wokwi, będzie testowane. Może się uda uruchomić w workflow na Githubie.
    Pomogłem? Kup mi kawę.
  • #4 21566894
    Konto nie istnieje
    Poziom 1  
  • #5 21566902
    p.kaczmarek2
    Moderator Smart Home
    Posty: 14620
    Pomógł: 655
    Ocena: 12636
    Hmm no z jednej strony masz rację, ale z drugiej strony mam nieodparte wrażenie, że to już wkracza w tematykę dość platform-specific, która już z założenia podanego w pierwszym poście nie jest celem użycia ASan u mnie w projekcie.
    Pomogłem? Kup mi kawę.
  • #6 21566909
    Konto nie istnieje
    Poziom 1  
📢 Słuchaj (AI):
REKLAMA