UPORABNA INFORMATIKA 59 2024 - πtevilka 2 - letnik XXXII Ranljivosti v programih zaradi dvojnega sproščanja pomnilnika Marin Gazvoda de Reggi, Matevž Pesek Univerza v Ljubljani, Fakulteta za računalništvo in informatiko, V ečna pot 113, 1000 Ljubljana mg4234@student.uni-lj.si, matevz.pesek@fri.uni-lj.si Izvleček V računalništvu je učinkovito upravljanje pomnilnika ključno za delovanje programske opreme. Napake pri tem lahko vodijo do resnih varnostnih ranljivosti, ki omogočajo izvajanje poljubne kode ali krajo občutljivih podatkov . Prispevek obravnava podrobnosti napada na podlagi dvojnega sproščanja pomnilnika ( double free ) in prikazuje primer ranljivosti v programu za upravljanje podatkovne baze. Po- jasni, kako lahko napadalec pridobi administratorske pravice brez gesla. Predlagane rešitve vključujejo uporabo pomnilniško varnih jezikov , orodij za statično analizo, omejevanje privilegijev in defenzivno programiranje. Poudarjen je pomen celovitega pristopa k va- rovanju programske opreme pred takimi napadi. Ključne besede: dvojno sproščanje, napad, upravljanje pomnilnika, varnostne ranljivosti BINAR Y VULNERABILITIES DUE TO DOUBLE FREE Abstract In computer science, effective memory management is crucial for software performance. Mistakes in this area can lead to serious security vulnerabilities, such as arbitrary code execution or theft of sensitive data. This paper discusses the details of a double-free memory attack and illustrates an example of a vulnerability in a database management program. It explains how an attacker can gain administrative rights without a password. Proposed solutions include using memory-safe languages, static analysis tools, privilege restriction, and defensive programming. The importance of a comprehensive approach to protecting software from such attacks is emphasized. Key words: double free, attack, memory management, security vulnerabilities 1 UvOD V sodobni digitalni dobi so računalniški sistemi po- stali nepogrešljiv del naše družbe, prisotni v vseh vidikih življenja in delovanja. Programska oprema, ki poganja te sisteme, igra ključno vlogo pri upravlja- nju procesov od osebnih naprav do kompleksne in- frastrukture. Ta vseprisotnost pa prinaša nove izzive, predvsem na področju varnosti in zanesljivosti. Z naraščajočo odvisnostjo od digitalnih sistemov postajajo posledice varnostnih ranljivosti vse resnej- še. Vdori lahko vodijo do kraje podatkov, finančnih izgub, ogrožanja zasebnosti in motenj v delovanju kritične infrastrukture. Te grožnje niso več omejene le na specializirane skupine, temveč postajajo dostopne širšemu krogu potencialnih napadalcev zaradi razšir- jenosti orodij in znanja o izkoriščanju ranljivosti. ZNANSTVENI PRISPEVKI UPORABNA INFORMATIKA 60 2024 - πtevilka 2 - letnik XXXII Eno ključnih področij, ki zahteva posebno pozor- nost, je upravljanje pomnilnika. To je še posebej po- membno v jezikih z ročnim upravljanjem, kot sta C in C++, ki se pogosto uporabljajo za razvoj sistemske programske opreme zaradi hitrosti in učinkovitosti. Vendar pa lahko ravno ta fleksibilnost vodi do subtil- nih napak z resnimi varnostnimi posledicami. Med najpogostejšimi napakami pri upravljanju pomnilnika so dvojno sproščanje (double free), upo- raba blokov po sprostitvi (use-after-free) in uhajanje pomnilnika (memory leak). Napadalci lahko te napa- ke izkoristijo za izvajanje poljubne kode, pridobiva- nje občutljivih podatkov ali destabilizacijo sistema. Kljub dolgoletnemu zavedanju o teh težavah ostajajo te ranljivosti razširjene in predstavljajo izziv za var- nost programske opreme. V tem članku se osredotočamo na analizo napada, ki izkorišča ranljivost dvojnega sproščanja pomnil- nika. Za ilustracijo predstavljamo primer v progra- mu, ki simulira preprostega upravitelja podatkovne baze, in pokažemo, kako lahko napadalec izkoristi to navidez nedolžno napako za pridobitev administra- torskih pravic brez poznavanja gesla. Razumevanje mehanizma teh napadov je ključno za razvoj varne programske opreme. S poglobljenim vpogledom lahko razvijalci bolje razumejo potenci- alne grožnje in razvijejo učinkovitejše strategije za njihovo preprečevanje. Takšna analiza pomaga tudi pri oblikovanju boljših orodij za odkrivanje in pre- prečevanje tovrstnih ranljivosti. Cilj tega članka je dvojen: prispevati k boljšemu razumevanju mehanizma napadov, ki izkoriščajo ranljivosti pri upravljanju pomnilnika, in spodbudi- ti razvoj robustnejših pristopov k programiranju in varnostnim praksam. S tem želimo prispevati k ra- zvoju varnejših in zanesljivejših računalniških siste- mov, ki bodo kos varnostnim izzivom v vedno bolj digitaliziranem svetu. 2 SORODNa Dela V zadnjih letih je področje varnosti računalniških sistemov doživelo porast raziskav, osredotočenih na ranljivosti pri upravljanju pomnilnika. Te študije se v glavnem ukvarjajo z vzroki, klasifikacijo in za- znavanjem ranljivosti, pri čemer posebno pozornost namenjajo trem ključnim problemom: uhajanju po- mnilnika, dvojnemu sproščanju in uporabi pomnil- niških blokov po njihovi sprostitvi. V nadaljevanju predstavljamo pregled ključnih prispevkov na tem področju, ki skupaj tvorijo celovito sliko trenutnega stanja raziskav. Temeljna študija Caballera in sodelavcev je pou- darila povezavo med ustvarjanjem in uporabo visečih kazalcev ter ranljivostmi zaradi uhajanja in dvojnega sproščanja pomnilnika. Njihovo orodje “Undangle” omogoča zgodnje odkrivanje teh ranljivosti in je bilo uspešno ovrednoteno na osmih praktičnih primerih, vključno z dvema novima ranljivostma v spletnem brskalniku Firefox [1]. Ta raziskava je spodbudila ra- zvoj sorodnih orodij, kot so FreeSentry [2], DangSan [3], HeapExpo [4] in pSweeper [5], ki so dodatno iz- boljšala analizo visečih kazalcev. Na področju odkrivanja varnostnih ranljivosti pri upravljanju pomnilnika so raziskovalci razvili raz- lične pristope, ki združujejo statično analizo in dina- mično testiranje. Hu in sodelavci so v tem kontekstu formalno opredelili tovrstne ranljivosti in razvili ogrodje “MRVDAVF” za analizo izvorne kode. Nji- hov pristop se je izkazal za učinkovitejšega v primer- javi z obstoječimi rešitvami [6]. Komplementarno temu so Rebel in sodelavci predlagali modularni pri- stop za samodejno izkoriščanje in iskanje ranljivosti pri upravljanju kopice [7], s čimer so razširili nabor orodij za celovito analizo varnosti pomnilnika. Pomemben napredek na področju dinamičnega testiranja predstavlja orodje “UAFL”, ki so ga razvili Wang in sodelavci. To orodje za fuzz testiranje teme- lji na analizi stanja tipov (angl. typestate analysis) in se je izkazalo za posebej učinkovito pri odkrivanju ranljivosti zaradi uporabe pomnilniških blokov po sprostitvi [8]. Na tem področju so Yan in sodelavci naredili dodaten korak z integracijo strojnega učenja v statično analizo, kar je privedlo do izboljšane na- tančnosti pri odkrivanju ranljivosti [9]. V sklopu dinamične analize so Gui in sodelavci razvili odprtokodno orodje “UAFSan”, ki vsakemu pomnilniškemu bloku dodeli edinstveno oznako. S preverjanjem konsistence teh oznak so avtorji uspe- šno identificirali napake zaradi uporabe blokov po sprostitvi [10]. Sorodno temu pristopu so Erdő s in sodelavci predstavili orodje “MineSweeper”, ki de- luje na principu karantene sproščenih pomnilniških blokov. To orodje preprečuje ponovno alokacijo blo- ka, dokler se ne potrdi odsotnost kazalcev nanj, s čimer učinkovito preprečuje ranljivosti pri uporabi pomnilniških blokov po sprostitvi [11]. Raziskave so se usmerile tudi v preučevanje teh- nik enkratnega dodeljevanja (OTA) istega bloka po- Marin Gazvoda de Reggi, Matevž Pesek: Ranljivosti v programih zaradi dvonega sproščanja pomnilnika UPORABNA INFORMATIKA 61 2024 - πtevilka 2 - letnik XXXII mnilnika kot preventivnega ukrepa proti omenjenim ranljivostim. V tem kontekstu so Wickman in sode- lavci razvili pomnilniški dodeljevalec “FFmalloc”, ki optimizira določene pomanjkljivosti enkratnih dode- ljevalcev pomnilnika in s tem poveča njihovo prak- tično uporabnost [12]. Za celovit pregled področja so Gui in sodelavci opravili sistematično analizo, primerjavo in ovredno- tenje trenutnih tehnik za odkrivanje in preprečevanje ranljivosti zaradi uporabe pomnilniških blokov po sprostitvi. Njihova študija vključuje primerjavo učin- kovitosti izvajanja in porabe pomnilnika različnih tehnik [13], s čimer ponuja dragocen vpogled v kom- promise med varnostjo in zmogljivostjo. Nadaljnje raziskave so se usmerile tudi v specifične domene, kot so industrijski nadzorni sistemi (ICS), kjer so Liu in sodelavci opravili pregled ranljivosti v izvršljivih datotekah [14], s čimer so poudarili pomen varnosti pomnilnika v kritičnih infrastrukturnih sistemih. Ta pregled sorodnih del kaže na kompleksnost in večplastnost problematike varnosti pomnilnika ter poudarja potrebo po celovitem pristopu k od- krivanju in preprečevanju ranljivosti. Raziskave ka- žejo trend k razvoju vse bolj sofisticiranih orodij in tehnik, ki združujejo statično in dinamično analizo, strojno učenje ter inovativne pristope k upravljanju pomnilnika. Kljub pomembnemu napredku pa osta- ja področje varnosti pomnilnika aktualen izziv, ki zahteva nadaljnje raziskave in razvoj. 3 meTODOlOGIja 3.1 Raziskovalni pristop V tej raziskavi smo uporabili kvalitativni raziskoval- ni pristop, ki temelji na kombinaciji študije primera in analize tveganja. Naš metodološki okvir je zasno- van tako, da omogoča celovito razumevanje proble- matike dvojnega sproščanja pomnilnika, od teoretič- nih osnov do praktičnih implikacij in možnih rešitev. Raziskava je potekala v štirih ključnih fazah: pripra- va, analiza, validacija in sinteza. V fazi priprave smo razvili preprost program, ki simulira upravitelja podatkovne baze, z namerno vgrajeno ranljivostjo dvojnega sproščanja pomnilni- ka. Ta namenski primer nam je služil kot osnova za podrobno preučevanje mehanizma ranljivosti v kon- troliranem okolju. Sledila je faza analize, v kateri smo korak za ko- rakom preučili, kako lahko napadalec izkoristi ranlji- vost za pridobitev nepooblaščenega dostopa. Ta del je vključeval podroben pregled dogajanja v pomnil- niku med izvajanjem programa, kar nam je omogoči- lo globlje razumevanje tehničnih vidikov ranljivosti. Analitična stopnja raziskave je bila ključna za razkri- tje subtilnih mehanizmov, ki omogočajo izkoriščanje te vrste ranljivosti. V fazi validacije smo našo teoretično analizo nad- gradili s študijo primera iz prakse. Preučili smo kon- kreten primer ranljivosti v široko uporabljeni mobilni aplikaciji, kar je služilo kot most med našo teoretično analizo in praktičnimi implikacijami. Ta korak je bil ključen za potrditev relevantnosti naše raziskave v kontekstu kompleksnih produkcijskih sistemov. Zaključna faza sinteze je bila namenjena oblikova- nju nabora strokovnih priporočil za preprečevanje in ublažitev tovrstnih ranljivosti. Na podlagi ugotovi- tev iz predhodnih delov raziskave smo razvili prak- tične smernice, ki razvijalcem in varnostnim strokov- njakom ponujajo konkretne napotke za izboljšanje varnosti programske opreme. Naš večstopenjski pristop, ki združuje nadzoro- van eksperiment in analizo realnega primera, zagota- vlja ravnovesje med natančnostjo laboratorijske ana- lize in relevantnostjo za resnične scenarije v razvo- ju programske opreme. S tem smo dosegli celovito obravnavo problematike, ki presega zgolj teoretično razumevanje in ponuja praktične vpoglede v varno- stne izzive sodobnega razvoja programske opreme. 3.2 Ranljivosti v izvršljivih datotekah Izvršljive datoteke so datoteke, ki jih lahko računalnik izvede (izvrši). V operacijskem sistemu Linux so obi- čajno zapisane v formatu ELF (Executable and Linkable Format). Sestavljene so iz več različnih sekcij, ki vse- bujejo strojno kodo, podatke, simbole in druge meta- podatke [15]. Ko uporabnik zažene izvršljivo datoteko, operacijski sistem prebere metapodatke in naloži kodo v pomnilnik. Koda se po tem začne izvajati (proces). Proces je osnovna enota izvajanja v operacijskem sistemu. Vsak izmed njih ima svoj ločen prostor v po- mnilniku, kjer shranjuje podatke, potrebne za izvaja- nje. Podatki, ustvarjeni v času izvajanja, so običajno ločeni na sklad (angl. Stack), kjer se nahajajo lokalne spremenljivke in naslovi za vrnitve iz funkcij, in ko- pico (angl. Heap), ki se uporablja za dinamično dode- ljevanje pomnilnika. Procesi, ki so ranljivi zaradi napak v programski kodi, so pogosto tarča napadov. Med najpogostejši- Marin Gazvoda de Reggi, Matevž Pesek: Ranljivosti v programih zaradi dvonega sproščanja pomnilnika UPORABNA INFORMATIKA 62 2024 - πtevilka 2 - letnik XXXII Marin Gazvoda de Reggi, Matevž Pesek: Ranljivosti v programih zaradi dvonega sproščanja pomnilnika mi ranljivostmi v izvršljivih datotekah so prelivanje medpomnilnika (angl. Buffer overflow), ranljivosti v knjižnicah (angl. Library vulnerabilities) in ranljivosti pri upravljanju pomnilnika (angl. Memory manage- ment vulnerabilities). Prav tako lahko v grobem ločimo napade na sklad in kopico. V nadaljevanju bomo podrobneje obrav- navali napade na kopico, ki izkoriščajo ranljivosti pri upravljanju pomnilnika. 3.3 Osnovni opis kopice Kopica (angl. Heap) je prilagodljivo območje pomnil- nika za hranjenje večjih podatkovnih struktur in podatkov z dinamično življenjsko dobo. Za razliko od lokalnega pomnilnika, ki se samodejno dodeli in sprosti, je treba s kopico upravljati eksplicitno. V je- zikih, kot sta Java ali C++, se pri ustvarjanju struktur ali objektov to običajno izvede z uporabo operatorja new. V programskem jeziku C pa se za dinamično dodelitev pomnilnika uporablja funkcija malloc(). Dodeljen blok pomnilnika (ali objekt) ostane v uporabi, dokler ni eksplicitno sproščen. V nižjenivoj- skih programskih jezikih to nalogo prevzema pro- gramer. Takšen pristop programerju omogoča večji nadzor nad upravljanjem pomnilnika, a hkrati nala- ga večjo odgovornost za aktivno skrb zanj. Ena po- gostejših napak pri tem je ohranitev reference na po- mnilniško lokacijo brez ustreznega sproščanja. Temu rečemo puščanje pomnilnika (angl. memory leak). V mnogih komercialnih programih, napisanih v C ali C++, se pojavlja puščanje pomnilnika, ki povzroči pomanjkanje prostega pomnilnika in sesutje progra- ma. Java in drugi nekoliko višjenivojski jeziki to na- pako odpravljajo s pomočjo avtomatskega upravlja- nja oz. čiščenja pomnilnika (angl. garbage collection). Slabost tega pristopa pa je, da čiščenje pomnilnika nekoliko upočasni delovanje programa ter se zgodi v nepredvidljivih časih [17]. V operacijskem sistemu Linux je za upravljanje kopice v programskem jeziku C zadolžena knjižnica GNU libc. 3.4 Delovanje funkcij malloc() in free() 3.4.1 Bloki Knjižnica GNU libc deli kopico na bloke različnih ve- likosti. Vsak blok vsebuje metapodatke o velikosti in o sosednjih blokih. Ko je blok v uporabi, se v pomnil- niku hranita le njegova velikost in zastavice, ko pa je sproščen, pa se poleg tega v pomnilnik zapišeta še kazalca na sosednja bloka [18]. Strukturo dodeljene- ga in sproščenega bloka prikazujeta sliki 2 in 3. 3.4.2 koši Sproščeni bloki so shranjeni v različnih seznamih – koših (angl. bins) glede na velikost in zgodovino, da jih lahko knjižnica učinkovito ponovno dodeli ob novi zahtevi. Koši so štirih vrst: hitri, nerazvrščeni, majhni in veliki. Pri večnitnih procesih ima vsaka nit svoj lokalni predpomnilnik blokov, dostopnih brez zaklepanja (angl. tcache) [18]. Slika 1: shematičen prikaz pomnilnika procesa [16]. Visok naslov argumenti ukazne vrstice in okoljske spremenljivke Nastavljeni na nič ob izvršitvi Brano takoj po izvršitvi Nizek naslov Kazalec na blok Slika 2: Blok v uporabi [18]. A = dodeljena arena M = mmap blok P = prejšnji blok v uporabi P = 1 vrne malloc blok UPORABNA INFORMATIKA 63 2024 - πtevilka 2 - letnik XXXII ne od zgornjega primera, so večinoma razpršene po stotinah vrstic kode ali celo po različnih datotekah, kar močno zmanjšuje preglednost in otežuje sledenje toku izvajanja programa. Ta problem je še posebej iz- razit pri uporabi globalnih spremenljivk [19]. 4 PRImeR IN RaZla Ga NaPaDa V tem delu bomo podrobneje opisali potek in specifi- ke napada na primeru enostavnega programa, napi- sanega v programskem jeziku C [20]. Program deluje interaktivno v ukazni vrstici po principu REPL (Read–Eval–Print Loop) in simulira preprostega upravitelja podatkovne baze. Razlikuje med dvema uporabniškima vlogama: uporabnikom in administratorjem. Uporabnik lahko izvaja poi- zvedbe na bazi, medtem ko ima administrator poleg te možnosti tudi pravico vnašati nove vnose in brisa- ti vsebino baze. Za dostop do administratorskih funkcij je potreb- na prijava z geslom, ki ga pozna le administrator, zato navadni uporabnik kot tudi napadalec brez ge- sla ne moreta dostopati do teh funkcij. Poglejmo si poziv, ki ga vidi uporabnik: Če uporabnik želi z vnosom 2 admin spremeniti uporabniško vlogo in ob tem vpiše napačno geslo, se mu kot pričakovano izpiše sporočilo: Rezultat veljavne poizvedbe npr. 3 Jack je sledeč: Če pa je poizvedba neveljavna, npr. za ukazom 3 ne podamo argumenta, se izpiše sporočilo: Kazalec na blok A = dodeljena arena M = mmap blok P = prejšnji blok v uporabi samo za velike bloke enako kot velikost P = 0 Slika 3: s proščen blok [18]. 3.4.3 Zaznavanje okvar kopice Sistem za alokacijo in dealokacijo pomnilnika sproti preverja za morebitne okvare, vendar je večina pre- gledov hevrističnih (npr. preverjanje kazalcev) in se jih da pretentati z lažnimi bloki, ki izgledajo resnič- no. V tem primeru lahko okvara preživi kar nekaj časa, ne da bi bila zaznana [18]. 3.5 Dvojno sproščanje pomnilnika Ena izmed pogostih napak, ki se pojavijo pri pro- gramiranju v jezikih z ročnim upravljanjem pomnil- nika, je dvojno sproščanje pomnilnika (angl. double free). Pojavi se, ko program (po pomoti) dvakrat kli- če funkcijo free() z istim argumentom (kazalcem na dodeljeni blok pomnilnika). To privede do korupcije podatkovnih struktur za upravljanje pomnilnika, kar lahko povzroči, da program preneha delovati ali pa v nekaterih okoliščinah spremeni tok izvajanja [19]. Napadalec lahko s prepisovanjem pomnilniških pro- storov pripravi program, da izvede skorajda polju- ben kos kode, kar mu omogoča pridobitev dostopa do lupine. Poglejmo enostaven primer ranljivosti, ki se poja- vi zaradi dvojnega sproščanja pomnilnika: Pogosta vzroka ranljivosti sta obravnava napak in drugih izjemnih okoliščin ter nejasnost, kateri del programa je odgovoren za sproščanje pomnilnika. Čeprav nekatere ranljivosti niso veliko bolj zaplete- Marin Gazvoda de Reggi, Matevž Pesek: Ranljivosti v programih zaradi dvonega sproščanja pomnilnika blok UPORABNA INFORMATIKA 64 2024 - πtevilka 2 - letnik XXXII Marin Gazvoda de Reggi, Matevž Pesek: Ranljivosti v programih zaradi dvonega sproščanja pomnilnika Poglejmo si poenostavljen del kode, ki je odgovo- ren za obravnavanje poizvedb: Funkcija select_from_db() kot parameter prejme dinamično dodeljen niz znakov line, ki predstavlja vrstico z ukazom. Za dodelitev in sprostitev tega niza sicer skrbi klicatelj funkcije, vendar pa se v primeru neveljavne poizvedbe niz sprosti znotraj funkcije. Zato se ob neveljavni poizvedbi ta blok pomnilnika sprosti dvakrat, kar predstavlja varnostno ranljivost. V našem primeru so alokacije prikladno enakih velikosti. Poglejmo dogajanje v hitrem košu (fastbin) pri sproščanju pomnilnika ob neveljavni poizvedbi: Če bi dvakrat zapored sprostili isti blok, bi tudi starejše verzije knjižnice GNU libc zaradi omenjenih hevristik zaznale napako in prekinile izvajanje pro- grama. V tem primeru pa napaka zaenkrat ostaja ne- zaznavna. Analizirajmo še funkcijo za prijavo: Če takoj za neveljavno poizvedbo uporabnik po- skusi zamenjati uporabniško vlogo, se pri dodeljeva- nju pomnilnika zgodi sledeče: 1. Še izven funkcije se dodeli line. Blok line je zdaj obenem dodeljen kot tudi sproščen. 2. Dodeli se username. V prejšnji funkciji sproščen blok e se zdaj ponovno dodeli kot username. 3. Dodeli se password. Hitri koš je zdaj prazen. Blok line je zdaj dodeljen kot password. Vendar je to isti blok, ki je že v upora- bi. Trenutno oba kazalca, line in password, kažeta na isti kos pomnilnika. V funkciji change_user() najprej preberemo geslo iz datoteke passwd.txt in ga shranimo v blok pas- sword. Za primere demonstracije se geslo hrani kot čistopis v datoteki, v pravih sistemih pa naj bi bilo geslo seveda ustrezno shranjeno in šifrirano. Nato uporabnika prosimo za vnos gesla. Če se vnos ujema s prebranim geslom, uporabniku do- delimo administratorske pravice. Prebrano geslo se UPORABNA INFORMATIKA 65 2024 - πtevilka 2 - letnik XXXII shrani v blok line. Ker pa gre za isti blok kot pri ka- zalcu username, se vsebina bloka enostavno prepiše z uporabniškim vnosom. Posledično se primerja isti niz znakov s samim seboj, kar seveda vedno vrne true, zato se uporabniška vloga uspešno zamenja na administratorsko. Pri tem se izpiše sporočilo: 5 ReZUlTaTI Analiza primera ranljivosti dvojnega sproščanja po- mnilnika v preprostem programu za upravljanje po- datkovne baze je razkrila več ključnih ugotovitev: 1. Uspešno izkoriščanje ranljivosti: Napadalcu je uspelo pridobiti administratorske pravice brez poznavanja gesla. To je bilo doseže- no z zaporedjem specifičnih korakov: izvedba ne- veljavne poizvedbe, ki sproži dvojno sproščanje, in nato poskus zamenjave uporabniške vloge. 2. Mehanizem napada: Dvojno sproščanje je povzročilo, da sta dva kazal- ca (line in password) kazala na isti blok pomnilni- ka. To je omogočilo prepis gesla z uporabniškim vnosom, kar je privedlo do uspešne avtentikacije. 3. Pogoji za uspešen napad in omejitve zaznavanja:  Hevristični varnostni pregledi v knjižnici GNU libc niso zaznali te specifične oblike dvojnega sprošča- nja, kar kaže na pomanjkljivosti varnostnih meha- nizmov pri odkrivanju kompleksnejših vzorcev napačnega upravljanja s pomnilnikom.  Demonstriran napad je učinkovit na sistemih z GNU libc pred različico 2.26. To vključuje Ubuntu 17.10 in 16.04 LTS, pri čemer je slednja še vedno podprta in prejema razširjene varnostne posodo- bitve do aprila 2026 [21]. Posledično je prikazan primer napada še vedno aktualen za določene sis- teme v uporabi.  Izdaja GNU libc 2.26 je uvedla podporo za lokalni predpomnilnik blokov za niti (tcache), ki onemo- goča to specifično obliko napada [22].  Nadaljnje iteracije knjižnice so izboljšale učinko- vitost upravljanja kopice, vendar so hkrati opusti- le nekatere varnostne mehanizme, kar je odprlo vrata za novejše načine napadov [23]. 4. Širše posledice: Čeprav je bil primer demonstriran na preprostem programu, rezultati kažejo, da lahko podobne ran- ljivosti v kompleksnejših sistemih vodijo do resnih varnostnih tveganj. Analiza razkriva potrebo po bolj robustnih metodah za preverjanje pravilnosti upravljanja s pomnilnikom v programih, napisa- nih v jezikih z ročnim upravljanjem pomnilnika. Za namen demonstracije je bil pripravljen tudi vsebnik Docker, ki simulira okolje z ranljivostjo [20]. Bralec je vabljen, da ga prenese in preizkusi sam. Ti rezultati poudarjajo pomen natančnega upravlja- nja s pomnilnikom in potrebo po večslojnih varnostnih pristopih pri razvoju programske opreme. Obenem kažejo na stalno evolucijo varnostnih izzivov in potre- bo po nenehnem prilagajanju varnostnih strategij. 6 DISkUSIja 6.1 Izraba ranljivosti v praksi V grobem so napadi na kopico zahtevnejši od na- padov na sklad in zahtevajo natančno poznavanje notranje strukture kopice. Dodelitve in sprostitve pomnilnika so v sistemih v praksi običajno manj predvidljive, kot je bilo prikazano v našem primeru, princip napada pa ostaja enak. Običajno je cilj napa- dalca izvesti poljubno kodo ali pridobiti dostop do lupine ali občutljivih podatkov. Ker do delitve pravic med izvajanjem programa po navadi ne pride slučajno, kot v našem primeru, se napadalci obenem poslužujejo tudi drugih tehnik, kot so prelivanje medpomnilnika, ranljivosti v knji- žnicah in napadi na sklad. Sproščeni in hkrati aloci- rani blok pa se lahko uporabi za prepisovanje kazal- ca na naslednji sproščeni blok v košu. S tem se lahko doseže, da se programu in s tem napadalcu ob enem izmed prihodnjih klicev funkcije malloc() (v kolikor uspe blok pretentati hevristične varnostne preglede knjižnice) dodeli dostop do skorajda poljubnega se- gmenta pomnilnika, tudi do kode, ki se izvaja, ali pa do občutljivih podatkov. Ko napadalec ugotovi naslov standardne knji- žnice v pomnilniku, lahko vrnitveni naslov prepiše z naslovom funkcije, ki jo želi izvesti, npr. system(“/ bin/sh”) in s tem pridobi dostop do lupine. Takemu napadu pravimo “ret2libc”. Marin Gazvoda de Reggi, Matevž Pesek: Ranljivosti v programih zaradi dvonega sproščanja pomnilnika UPORABNA INFORMATIKA 66 2024 - πtevilka 2 - letnik XXXII Marin Gazvoda de Reggi, Matevž Pesek: Ranljivosti v programih zaradi dvonega sproščanja pomnilnika 6.2 Primeri ranljivosti v aplikaciji Whatsapp za android Leta 2019 so raziskovalci odkrili ranljivost v knjižnici android-gif-drawable [24], ki jo uporablja tudi prilju- bljena aplikacija WhatsApp za Android. Ranljivost je omogočala izvajanje poljubne kode na daljavo in do- stop do lupine na napravi uporabnika, če je ta odprl posebej oblikovan GIF. Napadalec bi lahko izkoristil to ranljivost za krajo občutljivih podatkov, kot so fo- tografije in sporočila [25]. Ranljivost poteka sledeče: 1. Napadalec pošlje GIF datoteko uporabniku preko kateregakoli kanala. Ena od možnosti je, da pošlje datoteko kot dokument preko WhatsApp-a. 2. Če je napadalec v stiku z uporabnikom (npr. prija- telj), se okvarjena GIF datoteka glede na privzete nastavitve samodejno prenese brez uporabniko- vega posredovanja. 3. Uporabnik želi poslati medijsko datoteko katere- mu od svojih prijateljev preko WhatsApp-a. Zato pritisne na gumb za pripenjanje datotek in odpre galerijo WhatsApp-a, da izbere medijsko datote- ko, ki jo želi poslati prijatelju. Pri tem ni potreb- no, da uporabnik dejansko pošlje datoteko, saj že samo odpiranje galerije sproži napako. 4. Ker WhatsApp prikaže predogled vsake medijske datoteke (vključno z GIF datoteko, ki jo je prejel), sproži napako dvojnega sproščanja pomnilnika in omogoči napad. Ranljivost je znana pod oznako CVE-2019-11932 in je bila odpravljena z izdajo posodobitve aplikacije 2.19.244 [25]. Ta primer iz prakse dodatno poudarja resnost ranljivosti dvojnega sproščanja pomnilnika in potrebo po stalnem posodabljanju programske opreme ter implementaciji robustnih varnostnih me- hanizmov. Obenem kaže, da so tovrstne ranljivosti lahko prisotne tudi v zelo razširjenih aplikacijah, kar še povečuje njihov potencialni vpliv. 7 PReDlaGaNe ReŠITve Demonstracija je pokazala, da lahko navidezno ne- dolžna ranljivost dvojnega sproščanja pomnilnika privede do resnih posledic. Za zmanjšanje verjetno- sti pojava takšnih napak predlagamo več pristopov:  Implementacija načela enkratnega lastništva pri upravljanju pomnilnika. To načelo določa, da je za vsak blok pomnilnika odgovoren le en del kode. Če je blok sproščen, ga ni več dovoljeno uporabljati. To načelo je še posebej pomembno pri delu z globalnimi spremenljivkami. V primerih, ko striktno upoštevanje tega načela ni mogoče, pa je ključnega pomena, da so posamezne funkcije in deli kode jasni in pregledni ter da ima vsaka funk- cija konceptualno en sam namen.  Uporaba pomnilniško varnih (angl. memory-safe) programskih jezikov, ki vključujejo vgrajene za- ščite pred omenjenimi napadi. Med te spadajo je- ziki z avtomatskim upravljanjem pomnilnika kot so Python, Swift, C#, Java in Go. Za aplikacije, kjer je kritična učinkovitost izvajanja, pa sta primerni alternativi Rust ali uporaba pametnih kazalcev v C++.  Vključitev orodij za statično analizo kode in odkrivanje napak pri upravljanju pomnilnika v razvojni proces. Primeri takih orodij so Valgrind in AddressSanitizer. Dodatno je koristno izvaja- nje fuzz testiranja, tj. avtomatiziranega testiranja z obsežnim naborom naključnih, nepredvidenih ali neveljavnih vhodov.  Implementacija najnovejših varnostnih smernic in pravil za programiranje. Ključno je tudi redno posodabljanje uporabljenih knjižnic za odpravo znanih ranljivosti. V operacijskih sistemih je pri- poročljiva aktivacija varnostnih mehanizmov, kot sta ASLR in DEP , ki otežujeta napadalcem napo- vedovanje naslovov pomnilnika in izvajanje kode v podatkovnih segmentih.  Omejitev privilegijev programa in izvajanje v peskovniku (angl. sandboxing) za zmanjšanje po- tencialnih posledic napadov. Tehnike kot so Sec- comp, Landlock, AppArmor in SELinux omogo- čajo omejevanje dostopa programa do sistemskih virov (npr. branja in pisanja datotek izven predvi- denih direktorijev ali dostopa do lupine). Virtua- lizacija in uporaba vsebnikov dodatno prispevata k izolaciji programa in preprečevanju dostopa do občutljivih podatkov drugih aplikacij.  Uveljavitev načel defenzivnega programiranja. Ta pristop zahteva sistematično predvidevanje potencialnih napak in napadov ter implementa- cijo ustreznih zaščitnih mehanizmov. To vključuje temeljito preverjanje vhodnih podatkov, validaci- jo rezultatov funkcijskih klicev in verifikacijo ve- ljavnosti kazalcev pred njihovo uporabo. UPORABNA INFORMATIKA 67 2024 - πtevilka 2 - letnik XXXII Marin Gazvoda de Reggi, Matevž Pesek: Ranljivosti v programih zaradi dvonega sproščanja pomnilnika 8 ZaKLjUčeK V tem članku smo podrobno analizirali ranljivost zaradi dvojnega sproščanja pomnilnika, ki se skupaj s sorodnimi ranljivostmi, kot sta uporaba pomnilni- ških blokov po sprostitvi in puščanje pomnilnika, uvršča med najpogostejša varnostna tveganja pri upravljanju s kopico. Te ranljivosti se pojavljajo pred- vsem zaradi napak v programih, napisanih v pro- gramskih jezikih z ročnim upravljanjem pomnilnika. Na praktičnem primeru smo prikazali, kako lahko napadalci izkoristijo omenjeno ranljivost za pridobi- tev administratorskih pravic v preprostem programu, ki simulira upravitelja podatkovne baze. Prav tako smo opisali, kako se lahko ta ranljivost v praksi izko- risti za izvajanje poljubne kode, kar smo ponazorili s primerom ranljivosti v priljubljeni mobilni aplikaciji. Kljub naraščajoči priljubljenosti pomnilniško var- nih programskih jezikov ostaja uporaba jezikov z ročnim upravljanjem pomnilnika, kot je C, pogosta zaradi njihove hitrosti in praktičnosti v specifičnih razvojnih okoljih. To pomeni, da ostaja nevarnost iz- koriščanja opisanih ranljivosti še vedno relevantna. V priporočilih za zmanjšanje tveganj smo podali več strategij, kako lahko razvijalci in skrbniki siste- mov omejijo tovrstne varnostne ranljivosti ter zašči- tijo svoje aplikacije pred napadi. To vključuje upora- bo sodobnih orodij za statično in dinamično analizo kode, uvedbo strožjih pravil in smernic za upravlja- nje pomnilnika ter omejitev privilegijev programa. Prav tako smo poudarili pomen defenzivnega pro- gramiranja, ki pomaga preprečevati napake že v naj- zgodnejših fazah razvoja programske opreme. S tem smo izpostavili kompleksnost varnosti po- mnilnika in nujnost celovitega pristopa k varovanju programske opreme pred napadi. Razumevanje teh ranljivosti in uvedba ustreznih zaščitnih ukrepov sta ključna koraka za zagotavljanje varnosti in zanesljivosti aplikacij v vedno bolj povezanem digitalnem okolju. lITeRa TURa [1] Juan Caballero in sod. “Undangle: early detection of dangling pointers in use-after-free and double-free vulnerabilities”. V: Proceedings of the 2012 International Symposium on Softwa- re Testing and Analysis. ISSTA 2012. Minneapolis, MN, USA: Association for Computing Machinery, 2012, str. 133–143. ISBN: 9781450314541. DOI: 10.1145/2338965.2336769. [2] Yves Younan. “FreeSentry: Protecting Against Use-After- -Free Vulnerabilities Due to Dangling Pointers”. V: jan. 2015. DOI: 10.14722/ndss.2015.23190. [3] Erik van der Kouwe, Vinod Nigade in Cristiano Giuffrida. “Dan- gSan: Scalable Use-after-free Detection”. V: Proceedings of the Twelfth European Conference on Computer Systems. Eu- roSys ’17. Belgrade, Serbia: Association for Computing Ma- chinery, 2017, str. 405–419. ISBN: 9781450349383. DOI: 10. 1145/3064176.3064211. [4] Zekun Shen in Brendan Dolan-Gavitt. “HeapExpo: Pinpoin- ting Promoted Pointers to Prevent Use-After-Free Vulnerabi- lities”. V: Proceedings of the 36th Annual Computer Security Applications Conference. ACSAC ’20. Austin, USA: Associ- ation for Computing Machinery, 2020, str. 454–465. ISBN: 9781450388580. DOI: 10.1145/3427228.3427645. [5] Daiping Liu, Mingwei Zhang in Haining Wang. “A Robust and Efficient Defense against Use-after-Free Exploits via Con- current Pointer Sweeping”. V: Proceedings of the 2018 ACM SIGSAC Conference on Computer and Communications Se- curity. CCS ’18. Toronto, Canada: Association for Computing Machinery, 2018, str. 1635–1648. ISBN: 9781450356930. DOI: 10.1145/3243734.3243826. [6] Jinchang Hu in sod. “A memory-related vulnerability detec- tion approach based on vulnerability features”. V: Tsinghua Science and Technology 25.5 (2020), str. 604–613. DOI: 10.26599/TST.2019.9010068. [7] Dusan Repel, Johannes Kinder in Lorenzo Cavallaro. “Modu- lar Synthesis of Heap Exploits”. V: Proceedings of the 2017 Workshop on Programming Languages and Analysis for Se- curity. PLAS ’17. Dallas, Texas, USA: Association for Com- puting Machinery, 2017, str. 25–35. ISBN: 9781450350990. DOI: 10.1145/3139337.3139346. [8] Haijun Wang in sod. “Typestate-guided fuzzer for discovering use-after-free vulnerabilities”. V: Proceedings of the ACM/ IEEE 42nd International Conference on Software Engineering. ICSE ’20. Seoul, South Korea: Association for Computing Machinery, 2020, str. 999–1010. ISBN: 9781450371216. DOI: 10.1145/3377811.3380386. [9] Hua Yan in sod. “Machine-Learning-Guided Typestate Analysis for Static Use-After-Free Detection”. V: Proceedings of the 33rd Annual Computer Security Applications Confe- rence. ACSAC ’17. Orlando, FL, USA: Association for Com- puting Machinery, 2017, str. 42–54. ISBN: 9781450353458. DOI: 10.1145/ 3134600.3134620. [10] Binfa Gui, Wei Song in Jeff Huang. “UAFSan: an object-iden- tifier-based dynamic approach for detecting use-after-free vulnerabilities”. V: Proceedings of the 30th ACM SIGSOFT International Symposium on Software Testing and Analysis. ISSTA 2021. Virtual, Denmark: Association for Computing Machinery, 2021, str. 309–321. ISBN: 9781450384599. DOI: 10.1145/3460319.3464835. [11] Márton Erdő s, Sam Ainsworth in Timothy M. Jones. “Mine- Sweeper: a “clean sweep” for drop-in use-after-free preventi- on”. V: Proceedings of the 27th ACM International Conferen- ce on Architectural Support for Programming Languages and Operating Systems. ASPLOS ’22. Lausanne, Switzerland: Association for Computing Machinery, 2022, str. 212–225. ISBN: 9781450392051. DOI: 10.1145/ 3503222.3507712. [12] Brian Wickman in sod. “Preventing Use-After-Free Attacks with Fast Forward Allocation”. V: 30th USENIX Security Symposium (USENIX Security 21). USENIX Association, avg. 2021, str. 2453– 2470. ISBN: 978-1-939133-24-3. URL: https://www.usenix.org/conference/usenixsecurity21/ pre- sentation/wickman. [13] Binfa Gui in sod. “Automated Use-After-Free Detection and Exploit Mitigation: How Far Have We Gone?” V: IEEE Tran- sactions on Software Engineering 48.11 (2022), str. 4569– 4589. DOI: 10.1109/TSE.2021.3121994. UPORABNA INFORMATIKA 68 2024 - πtevilka 2 - letnik XXXII Marin Gazvoda de Reggi, Matevž Pesek: Ranljivosti v programih zaradi dvonega sproščanja pomnilnika [14] Qi Liu, Kaibin Bao in Veit Hagenmeyer. “Binary Exploitation in Industrial Control Systems: Past, Present and Future”. V: IEEE Access 10 (2022), str. 48242–48273. DOI: 10.1109/ ACCESS.2022.3171922. [15] TIS Committee. Tool Interface Standard (TIS) Executable and Linking Format (ELF) Specification. Ver. 1.2. 1995. URL: https://refspecs.linuxfoundation .org /elf/elf.pdf (pridobljeno 12. 5. 2024). [16] Yanpas – Wikimedia Commons. C memory layout of pro- gram. bss, stack, heap. 2015. URL: https: //en.wikipedia.org/ wiki/File:C-memlayout.svg (pridobljeno 12. 5. 2024). [17] OpenDSA. Heap Memory. URL: https://opendsa-server.cs.vt. edu/ODSA/Books/CS2/html/ HeapMem.html (pridobljeno 12. 5. 2024). [18] Malloc Internals. URL: https : / / sourceware . org / glibc / wiki / MallocInternals (pridobljeno 12. 5. 2024). [19] CWE-415: Double Free. URL: https://cwe.mitre.org/data/de- finitions/415.html (pridobljeno 12. 5. 2024). [20] GitHub repozitorij. URL: https://github.com/marindereggi/ double-free. [21] Ubuntu 16.04 LTS transitions to Extended Security Mainte- nance (ESM). URL: https://canonical. com/blog/ubuntu-16- -04-lts-transitions-to-extended-security-maintenance-esm (pridobljeno 12. 5. 2024). [22] The GNU C Library Repository. Ta potrditev uvede podporo za lokalni predpomnilnik blokov za niti v izvorni kodi. URL: https : / / sourceware . org / git / ?p = glibc . git ; a = com- mitdiff ; h = d5c3fafc4307c9b7a4c7d5cb381fcdbfad340bcc (pridobljeno 12. 5. 2024). [23] tukan. thread local caching in glibc malloc. URL: http://tukan. farm/2017/07/08/tcache/ (pridobljeno 12. 5. 2024). [24] Android GIF Drawable Source Code. URL: https://github. com/koral--/android-gif-drawable/ tree/dev/android-gif-dra- wable/src/main/c (pridobljeno 12. 5. 2024). [25] Awakened. How a double-free bug in WhatsApp turns to RCE. URL: https://awakened1712.github. io/hacking/ hacking-whatsapp-gif-rce/ (pridobljeno 12. 5. 2024).  marin Gazvoda de Reggi je študent na Fakulteti za računalništvo in informatiko Univerze v Ljubljani. Zanimajo ga področja razvoja programske opreme, kibernetske varnosti in umetne inteligence. Njegovi raziskovalni interesi zajemajo teorijo programskih jezikov in njihovo varnost.  matevž pesek je docent in raziskovalec na Fakulteti za računalništvo in informatiko Univerze v Ljubljani, kjer je diplomiral (2012) in doktoriral (2018). Od leta 2009 je član Laboratorija za računalniško grafiko in multimedije. Od leta 2024 izvaja predmet V arnost programov .