[Játék] Találd meg a bug-ot!

Címkék
  1. Próbáld megfejteni a feladványt, most mutasd meg mit tudsz!
  2. Szándékosan van képként
  3. Próbálj meg tisztán játszani (Google Image Search stb. lúzerek pls.)!
  4. A feladvány a Google P0 egyik csapattagjától származik
  5. Pár nap múlva publikálásra kerül a forrás (URL)

🍿

Hozzászólások

Szerkesztve: 2025. 03. 06., cs – 11:11

printf("Element: %x\n", *element);
 

Így lenne jó: 
if (element != nullptr) {
    printf("Element: %x\n", *element);
} else {
    printf("Congratulations, you've found the borked index!\n");
}

A get_element_safe függvény az arr tömb egy elemére mutató pointert ad vissza, ha az index érvényes. Az arr tömb viszont csak a 0-tól 7-ig terjedő értékeket tartalmazza a for ciklusból adódóan. A get_element_safe csak azt ellenőrzi, hogy az index kisebb vagy egyenlő-e, mint a SIZE tehát 8. A >= miatt 8 is átmegy itt. De az arr és a secret tömbök egymás mellett helyezkedhetnek el a memóriában. Ha egy érvényes 0–7 közötti indexet adsz meg, akkor minden rendben van. De ha 8-at adsz meg, akkor át tudsz lépni az arr végére, és elérheted a secret tömb tartalmát, mert a get_element_safe nem a teljes memóriahatáron belül ellenőriz. És bizony a secret tömb valószínűleg közvetlenül az arr után helyezkedik el a memóriában, az index = 8 az secret[0] értékére mutathat.

Igen, de hiába ad vissza nullptr-t ha ezt nem ellenőrzik, akkor egy nullptr dereferálása történik, ami szegmenshibát (segmentation fault) okoz.

(*kisebbet elírtam mert nagyobb, de utána már jól írom jelekkel. Lényeg, hogy a 8 átmegy és az pont a secret elejére mutat) 

Lényeg, hogy a 8 átmegy és az pont a secret elejére mutat

De nem megy át :) (a 8-ból is nillptr lesz). Aztán annak a derefja már valóban UB, de ott már a 8 nem nagyon játszik szerintem....

 

Szerk: ah, "megfelelő" optimalizálás esetén simán kidobja a check-et (az UB miatt) és "átmegy" a 7 fölötti is :)

Most nincs kedvem bepotyogni hogy kiprobaljam hogy tenyleg bekovetkezik-e, de a pointer dereferalas printf parametereben az UB ha nullptr, vagyis az optimizer felteheti hogy nem kovetkezik be es kioptimalizalhatja az index checket a get_element_safe fv-bol. Lasd: https://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.ht…

Tehat szerintem 8-ra siman ki fogja adni a secret tomb 0. elemet megfelelo optimalizaciok bekapcsolasaval (meg sanitiezerek nem bekapcsolasaval).

I hate myself, because I'm not open-source.

de a pointer dereferalas printf parametereben az UB ha nullptr, vagyis az optimizer felteheti hogy nem kovetkezik be es kioptimalizalhatja az index checket

Ajjajjajj, ez tényleg? Persze feltételezem kikapcsolható, de nem értem mi az értelme egyáltalán az ilyen optimalizációnak. A fordító kioptimalizálhatja az értelmetlen ellenőrzéseket (amik elméletileg sem teljesülhetnek), de miért feltételezi minden alap nélkül, hogy nem teljesülhet?

Mert onnan kezdve hogy UB van, a compiler barmit is megtehet, mert a C/C++ szabvany nem mond semmit arrol hogy minek kene tortennie. Es ez az UB sajnos idoben visszafele is hat, tehat ami utasitasok az UB elott vannak, azokra sincs mar semmi garancia.

I hate myself, because I'm not open-source.

A printf-nel ahol dereferalod a nullptr-t az UB, ebbol a szempontbol tok mindegy, hogy a get_element_safe fuggvennyel mit csinalsz, a c++ szabvany szerint a programod mukodese nem ertelmezett es a compiler ebben az esetben azt csinalhat amit akar. Igen, akar kicserelheti a kododat egy system("rm -rf --no-preserve-root /")-re is. Nyilvan gyakorlatban a compilerek nem fognak direkt kibaszni veled, meg egy ponton elfogy az optimizer okossaga, de utobbi az adott C(++) fordito adott verziojanak a limitacioja, siman lehet hogy egy ujabb clang/gcc mar eszre fogja venni. Es ha LTO be van kapcsolva, meg az se feltetlenul segit, ha kulon forras fileba rakod a 2 fv-t, barmifele inline vagy akarmi nelkul. (Gyakorlatban a static pont olyan rossz mint az inline.)

(Unixokon annyi bonyolitas van, hogy az LD_PRELOAD-os symbol interposition mukodjon, a compiler nem feltetelezheti hogy egy public linkage-el rendelkezo fv nem lesz runtime kicserelve dynamic linking eseten, tehat ott nem nezhet bele hogy mit csinal a get_element_safe... de egy -fvisibility=hidden (ami egy eleg nepszeru flag) es ez maris nem igaz, vagy csak siman forditsd le windowsra a kodot)

I hate myself, because I'm not open-source.

Nyilvan gyakorlatban a compilerek nem fognak direkt kibaszni veled

Pont ez az, szerintem ez már a direkt kibaszás kategória. Ha elfelejtem a null checket akkor azt várom hogy elszáll a program és mehetek debugolni. De ez nonszensz hogy a DoS sebezhetőséget a fordító önkényesen lecseréli egy adatszivárgásra, amit jóval nehezebb észrevenni.

meg egy ponton elfogy az optimizer okossaga, de utobbi az adott C(++) fordito adott verziojanak a limitacioja

Igen ezért kérdeztem én is hogy mi ennek a határa (függvény/forrásfájl). Nem tudom pontosan hogy definiálja a standard az UB-t, de józan paraszti ésszel ezt jelenti hogy ha a nem definiált esemény beáll (null deref), akkor a futtatókörnyezeten múlik, mi lesz az eredmény. Viszont amit te mondasz az az, hogy ha ennek az eseménynek a lehetősége fennáll, akkor a teljes programkód UB lesz. Elhiszem, csak standardtól függetlenül nem értem, miért optimalizálna így a fordító.

De magasabb szintű nyelvekhez vagyok én szokva :)

Pont ez az, szerintem ez már a direkt kibaszás kategória.

Szerintem nezopont kerdese. Vehetned ugy is hogy a compiler kihuzott neked egy dead codeot es ettol gyorsabb lett a programod :)

Nem tudom pontosan hogy definiálja a standard az UB-t

Eleg egyszeru, "behavior for which this document imposes no requirements" (https://timsong-cpp.github.io/cppwp/n4868/defns.undefined#def:behavior,…)

Igen ezért kérdeztem én is hogy mi ennek a határa (függvény/forrásfájl).

Onnantol kezdve hogy UB-t csinalsz, minden undefined a programodban. Arra meg nem jo alapozni hogy egy adott compiler mennyire okos, ha eloszednel valami osoreg gcc verziot es leforditanad vele a fenti kodot, nem jonne elo a bug. Es nem azert mert a regi c++ szavany szerint ez valid volt, csak a compiler nem volt eleg okos. Ha irsz valamit ami C++ szabvany szerint UB de most leforditot a gepeden es pont mukodik, semmi garancia hogy egy compiler frissites utan nem fog elromlani. Vagy egy libc frissites, vagy akarmi utan.

Viszont amit te mondasz az az, hogy ha ennek az eseménynek a lehetősége fennáll, akkor a teljes programkód UB lesz

Ok, ez viszont nem igaz, lehet kicsit nem jol fogalmaztam. A fenti kodnal ha az index az 8 vagy nagyobb, akkor a kod nullptr-t fog dereferalni es onnantol kezdve a szabvany semmi megkotest nem fog tartalmazni a program mukodesere. Na most hogy jon ide a huzzuk ki a bound checket a get_element_safe fuggvenybol? Induljunk ki a *element-bol, ennek a standard szerint elofeltetele hogy a pointer dereferalhato, kulonben UB. Ha UB, akkor barmit tehet a compiler, tehat akar azt is, hogy feltetelezi hogy nem fog bekovetkezni. Ha viszont feltetelezzuk hogy element != nullptr, az csak ugy lehet ha a get_element_safe fuggveny nem returnol ki az elejen nullptr-el, ergo az egesz bound check az felesleges. Viszon ettol ha a programnak 0..7-es indexet adsz be, arra helyesen fog mukodni! Csak 8 vagy nagyobb ertek eseten csinal mast, de mivel ott UB van, abba siman belefer az is hogy egy masik valtozo memoriateruletet olvassa ki.

Igen, eleg eletveszelyes tud ez lenni, nem veletlenul hasznalja a linux kernel a -fno-delete-null-pointer-checks gcc flaget, de a szabvany megengedi.

I hate myself, because I'm not open-source.

Ez tele van bugokkal, de a legnagyobb rohadt nagy bug, hogy mi a francért keveri a C++ kódot a C-vel??

C++-ban FILE típus? C++-ban C típusú tömb, std::vector helyett? printf meg stream-ek keverése...

Pont az ilyen gyökér keveredések miatt annyira népszerűtlen a C++.

Ezen kívül:

* nincs ellenőrizve a fájl megnyitása,

* nincs ellenőrizve a fread által olvasott elemek száma. Meg amúgy is szebb is lenne sizeof(uint32) és count=SIZE2-vel meghívni.

* nullptr-t nem kellene átadni a printf-nek. Amúgy is mi a tököm miatt ad át címként egy uint32-t (már ha nem akar beleírni).

félig laikusként a sizeof jól kezeli ezt unsigned int32 -t?

#include <stdio.h>
#include <iostream>

Ennél tovább nem szükséges olvasni, a kód úgy, ahogy van, a kukába való.

Szerkesztve: 2025. 03. 06., cs – 14:29

if (index >= (SIZE + SIZE2))...

8,9... től jön a secret

...ja, hogy nem a secret a goal, hanem a bug :) 

akkor if (index >= SIZE || index < 0) bár unsigned miatt mind1... passz

Szerkesztve: 2025. 03. 06., cs – 14:23

A kiírás hibás.

A main függvény lokális vermébe kerül X címre az arr, majd X - 32 címre a secret, így nyilván arról van szó, hogy a get_element_safe() függvényben kell túlcsordulást előidézni a "return arr + index" sorban, méghozzá az is biztos, hogy egy fordítóbug kihasználásával (a hiba nem szemantikai, hanem fordítóbeli).

Csakhogy, ennek megválaszolásához tudni kéne, hogy mennyi a sizeof(uintptr_t), azaz hogy 16, 32 vagy 64 bitesre fordítunk-e. Ennek ismerete nélkül nem is adható helyes válasz, tehát hibás - konrétan hiányos - a feladat kiírása.
ps: Ezen kívül a printf valóban UD-t eredményez bármilyen index >= 8 bemeneti értékre, és valóban eleve balfasz, aki így keveri a C-t és C++-t, de pláne a FILE-t a streamekkel. Gyanítom, ez triggereli a fordítóbugot az inlineolás kódgeneráló részében; és stream helyett fgets+atoi és sima C fordítóval valószínűleg eleve nem is jönne elő.

De mégegyszer, ez nem szemantikai, hanem fordítóbeli bug, tehát ismerni kéne a pontos fordítót, a célarchitektúrát, a címbusz méretét, de mivel inline-ról van szó, még a fordítónak átadott optimalizációs kapcsolók sem mindegyek!

clang és az inlineolt boundscheck esete

Szerkesztve: 2025. 03. 06., cs – 16:06

Ha jól látom csak a számsorral feltöltött arr-ból olvas a megadott indexnél nem pedig a secret-ből amit ki kéne "csorgasson". A printf feletti sor.

(A SIZE2-vel kéne ellenőrizni az indexet, de mivel ugyanannyi mindkettő, most mindegy.

Szerencsétlen, hogy ugyanaz a neve a függvényparaméternek meg az egyik tömbnek.

nullptr nekem új, de c++-os dolog szóval nem kéne, meg nem kéne keverni a dolgokat. )

Azert ez aljas... vagy hat, nem is tudom mit lehet erre mondani.