Manwe Work 1
Manwe Work
Bodyguardd 1
Bodyguardd
Vahsi Uzman 1
Vahsi Uzman
Reklam vermek için turkmmo@gmail.com

[DB] CLoginData memory leak

TL;DR: YMIR dev ağabeyler duplicate login'i temizleyeceğiz derken bir mekanizma kurmuşlar ama normal çıkışa bağlamayı unutup sıvamışlar.

oyuncumuz zühtü olsun;

zühtü oyuna giriş yapıyor. arka planda ne oluyor?

QUERY_AUTH_LOGIN çalışıyor, bir CLoginData nesnesi free store'dan allocate ediliyor. bu nesneye dört farklı container'dan non-owning raw pointer ile erişiliyor amma velakin "non-owning" demek aslında doğru değil(inanın böyle demeyi çok isterdim) çünkü codebasede ownership'i taşıyan tek tip yok, dördü de fiilen "owner" gibi davranıyor, gerçi bi refcount falan var ama dökümantasyon falan olmadığı için pek anlamlandıramadım, binevi elle shared_ptr taklit edilmiş işte danglinge düşmeyelin falan ama 10 saatimi kodu anlamaya çalışarak geçirecek değilim.

"o nasıl oluyor la?" derseniz non-static data memberları inceleyebilirsiniz:
  1. m_map_pkLoginData // login-key -> nesne
  2. m_map_pkLoginDataByLogin // "zuhtu123" -> nesne
  3. m_map_pkLoginDataByAID // account id -> nesne
  4. m_map_kLogonAccount // "zuhtu123" -> nesne

yani şu..., neyse(hocam dayanamıyorum YMIR'e laf atıcam).

diyelim ki zühtü biraz takılıp çıkıyor veya alt + f4 çekiyor. şimdi arka planda ne oluyor? QUERY_LOGOUT çalışıyor, orada da işte DeleteLogonAccount("zuhtu123") çağrılıyor.

DeleteLogonAccount'un flowu şöyle:

C++:
if (pkLD->IsDeleted())   // hımmmmmmmmmm
    delete pkLD;

m_map_kLogonAccount.erase(it);

"IsDeleted() nedir lo?" m_bDeleted non-static data memberı return eden bir accessor. bunu true set eden tek yer SetDeleted mutator, o da yalnızca DeleteLoginData() içinde çağrılıyor. DeleteLoginData()'yı çağıran tek yer ise QUERY_AUTH_LOGIN, yani bu demek oluyor ki aynı hesabın tekrar login olması gerekiyor.

yani düşünelim;

zühtü DB processi hiç restart olmayan bir sunucuda bir kez login/logout tetiklese ve bir daha hiç girmese ne olur? süper olurdu aslında ama, neyse olacak olan şu: QUERY_AUTH_LOGIN bir daha tetiklenmez, bu da sırayla şunlara yol açar:
  • DeleteLoginData çağrılmaz
  • SetDeleted çağrılmaz
  • IsDeleted() hep false return eder
  • "delete pkLD;" delete-expression hiçbir zaman çalışmaz

vay be zühtü, dağları devirdin.

ek olarak zühtü'den bağımsız aynı pattern RemovePeer() içinde de tekrar ediyor:

C++:
if (pkLD->IsDeleted())
{
    delete pkLD;
}

m_map_kLogonAccount.erase(it++);

bu sefer zühtü'nün suçu yok, bir game server peer'i koparsa (artık crash mi olur restart mı olur siz karar verin) o peer üzerinden bağlı tüm hesapları m_map_kLogonAccount'tan silmeye çalışlıyor ama yine aynı döngü IsDeleted normal şartlarda hep false.

yani game server bir sebepten ötürü restart attığı anda o sırada bağlı olan (kaç kişi olduğuna siz karar verin) oyuncuların CLoginData'sı free edilmiyor.

ee noldu yani derseniz olay tam olarak CWE-772(araştırın bi zahmet)

gözümden kaçmadıysa herhangi bir dangling pointer, use after free falan yok, diğer 3 containerda halen geçerli ve erişilebilir. olay nesnenin işi bitmiş ama halen serbest bırakılmamış olması. uğraştırıcı ve fark edilmesi yüksek ihtimal olsa da bir saldırı senaryosu kurulabilir. zaten normal oyuncu devride başlı başına bir dert, art niyetli birini aramanıza da gerek yok uptime'da yardımcı olur.

fix:



bu oyunun codebaseinin içine biraz dalmak bile insanı akıl sağlığından eder, bu büyük ihtimalle açtığım son konu.

rica ederim, iyi forumlar.

Not: warp akışını kontrol etmedim, bir sorun bariz olarak var ama direkt olarak delete etmek farklı bir duruma yol açabilir, Metin2 mayın tarlası gibi bir oyun. uygulayıp bir sorun yaşayan olursa belirtirse analizini yapıp düzeltiriz.

Özel rica: Lütfen kullandığınız AI chate girip "ne anlatıyor la bu? şuna bir yorum yaz kallavi olsun" benzeri bir şeyin çıktısıyla yorum yapmayın, bildiğiniz konu ise teknik konuşmak istiyor iseniz doğrusunu yanlışını konuşalım yoksa ne sizin tokenler gitsin ne ben yorulayım, teşekkürler.
 
Son düzenleme:

En Çok Reaksiyon Alan Mesajlar

DeleteLoginData fonksiyonuna bakarsak nesneyi sadece map'lerden temizliyor, delete etmiyor. Yani yazdığın else bloğu çalıştığında nesne her yerden silinip RAM'de ulaşılamaz şekilde sahipsiz kalıyor.
else kısmında DeleteLoginData(pkLD); altına bir de delete pkLD; eklersek sorun kusursuz çözülür bence zühtüde patlamaz :)
yinede eline sağlık paylaşım için teşekkürler
Öğeyi görmek için üye olmalısınız.

konuda demişim ki iteratoru önce logon'dan erase edin, görseldeki koda bakalım;

logondaki lookup başarısız olursa ne yapıyor?

siz yine de yazın isterseniz, sizin yöntemle zühtü patlamaz belki ama artık dağa mı kalkar indir kaldır mı yapar onu kim bilir..

ama core değişimi durumu düşünülebilir, bir warp akışına bakmak lazım.
selam. paylaşım için teşekkürler.
hata tespiti doğru ama bu düzenleme zühtü'yü oyuna sokamıyor. ekleyip deneyip yorum yazan üye de girememiş muhtemelen bu sebepten.

zühtü karakter seçiminde "Oyuna Gir"e basıyor. istemci CNetworkStream::Connect içindeki Clear() yüzünden mevcut soketi kapatıp yenisini açıyor, yani eski desc'in destructor'ı iki LOGIN_BY_KEY'in arasında kalıyor.
AUTH_LOGIN -> LOGIN_BY_KEY (1) -> ~DESC (GD_LOGOUT) -> LOGIN_BY_KEY (2)

ortadaki adım nesneyi delete ediyor. ikinci LOGIN_BY_KEY ilk iş GetLoginData(dwLoginKey) yapıyor, nullptr, LOGIN_NOT_EXIST -> "NOID". zühtü karakter seçiminde kalıyor. bu sefer de dağın yamacında takıldı :D

aynı sebeple "o zaman normal çıkışta da DestroyLoginKey gönderelim" de çalışmaz, o paket de aynı destructor'dan gider.

oluru ise şöyle olur: çıkışta timestamp, birkaç saniye sonra hala m_map_kLogonAccount'ta değilse DeleteLoginData. gecikme şart, çünkü DeleteLogonAccount zaten çıkışta onu o map'ten siliyor.

bu arada birikim login başına değil hesap başına. zühtü ertesi gün tekrar girerse QUERY_AUTH_LOGIN eski kaydını bulup DeleteLoginData çağırıyor, dünkü nesnesi free oluyor. yani "bir daha hiç girmeyen her zühtü için 600bytecık"

asıl login başına birikeni: DBManager:deleteLoginData hiçbir yerden çağrılmıyor. tanım var, header'da bildirim var, çağrı yok. map'i dolduran LoginPrepare her başarılı girişte M2_NEW CLoginData + InsertLoginData yapıyor. zühtü 500 kez girip çıkarsa 500 defa. tek başına aşağı yukarı 65 kb.

bu arada doğrulamasını yapmak için test ortamı kurabilecek ortama sahip değildim bu nedenle hatalarım olabilir. claude fable 5 ultrathinking de olmadığım için bir şeyleri incelerken hata yapabiliyorum.
valla bir günde yoruldum, laf anlatmaktan yoruldum. bu; konuyla alakalı yapacağım son yorumdur gerçekten vakit kaybı.

ilk önce "bir sorun var ama işte düzeltmenin faydası yok" artık son aşamlarda söylenecek şeyler, trade-off düşünülür (yani bana bunun maliyeti ne faydası ne) çözmenin maliyeti çok değilse çözersiniz ama çok büyük bir maliyet varsa eleme yaparsınız, ben bu "sorun var ama çözmesek de nolcak amaan" mantığına katılmıyorum, profesyonellikten uzak bir yaklaşım ama saygı duyarım karar sizin.

burdan sonra söylediklerim genel içindir.

bu konu bir analiz ve teknik bir paylaşım zevk için yapılmış bir şey, bu kadar büyük bir codebasede her şeyi görmem bilmem mümkün değil (aşağıda değineceğim complexity sebebiyle), ama gelde bunu olaydan bi haber adama gelde anlat. bu konuda bunları hiç tartışmamamız gerekiyordu yorumlar "eyvallah, sağ ol, ya not düşmüşsün hani soket değişimini denemedim diye gerçekten orada bir sorun var bir baksak nasıl olur?" tarzında insanca şeyler olmalıydı, biz burda belli bir yaşı aşmış ama halen olgunlaşamamış insanlarla uğraşıyoruz, neyse.

memory fragmantation, cache locality, RAM nasıl çalışır, CPU nasıl davranır, contiguous container observerlar, network programming, UNİX-Linux sistemler, ağ yapısı, STL 2.0, Modern C++ teknikleri, idiomları, STL, C++'da neden diğer diller gibi mimari tasarım kurulmaz, C++ ile mimari tasarım, Modern C++ design bla bla bla ben bunları açayım bir konu veya youtube kanalı veya farklı bir şey işte ne olursa anlatayım da neden anlatayım?

şimdi: ben savunmada çalışıyorum, domainim game programming değil. boş vakit bulursam tamamen hobi amaçlı metin2 ile uğraşıyorum, ne maddi çıkarım var ne manevi ne de bana teknik/bilgi vs açısından bir şey katıyor benim için tamamen vakit öldürme aracı yani. neyse bir şey görüyorum bunu bilgi amaçlı paylaşıyorum amaç katkımız olsun, üniversitede öğrenciyken burdan beni bulan insanlarla harçlık çıkarıyordum. neyse, bir sayfa analiz yapıyoruö bir kaç saat uğraşıp not olarak oraya yazıyorum ki bakın metin2 kompleks bir yazılım, bunu direkt böyle yapmak farklı sebeplere yol açabilir test edin sorun olursa yazın düzeltiriz. sonra yorumlara bir bakıyorum, hiç şaşmaz artık bilgi seviyesi arşa çıktığı için forumda biri bir açığını görür "ZAA ŞU DA VARDI ATLAMIŞSIN ZAA XD" ,"YAV ZATEN BİLİYORDUM BEN YAVV XDXD" mantığıyla yorum yapar 3-5 şarlatanı da alkışlar, sorun ne şarlatan bir şey biliyor ne de yorumu yapan (Tunga'ya laf söylemiyorum yanlış anlaşılmasın, onun da buna benzer bir yorumu var kendisiyle 1*1 sohbetim yok ama saygım vardır) ki bu sadece bana özel bir durum değil, genel böyle, bizim insanımız haset eder, taşlar vs. sever böyle şeyleri, neden? çünkü senin seviyene çıkamıyor etrafa seni onla aynıymış gibi göstermesi gerekiyor. ne başarısı var diye sorsan, neyse.

ya beni geç hadi ben ortamı biliyorum 7 senedir burdayım, başka biri bile olsa bu muameleyle karşılaşsa niye uğraşsın? girdiği strese değmez.

bunu ego olarak algılayabilirsiniz artık zaten ne desem egoist oluyorum pek fark etmiyor, ben bu metin2 camiasındaki akla gelecek her isimden daha çok okumuş çizmişimdir, C++ ile para kazanıyorum, bu dili çok seviyorum, bu dilde üretim yapıyorum, bu dile emek sarf ediyorum. son 3 yıldır okumadığım gün yoktur, binlerce saat bu işin ağa babalarından türkçe/ingilizce kurs almışımdır, yüzlerce saat konferans izlemişimdir metin2 camiasında global dahil benden daha derin C++ bilgisi olan bir kişi daha bulamazsınız, üstünede konuşabiliriz sorun yok. ben bu kadar hasetli bir ortam görmedim, "ecnebi", "gavur" vs. diyip beğenmeyeceğimiz adam dcden yazıp işte paylaşımın için teşekkürler güzel bir şey paylaşmışsın vs yazıyor bizim 5 satır kod okumamış adam yapay zekayla burda seni aşağıya çekmeye çalışıyor. ben neden şimdi bu adamla tartışayım ki? var mı emeğimin bir değeri? yok, üstüne nelerle uğraşıyoruz...

benimle muhabbeti olan herhangi birine soralım, ben egoist biri miyim? hayatımda kimseyi ezmedim, zorbalamadım, zamanı geldi burdaki insanalra taa üniversite zamanlarımda dcden ekran paylaşıp DS anlattım, yeri geldi kod anlattım, yeri geldi ücretsiz hata düzelttim, bu yaşıma kadar hak etmeyen kimseyi ezmedim, içerikleri dahi eğlenceye vurup gülüp geçemiyoruz allah aşkına yapmayın. ha yanlışlarım var mıdır vardır, tıpkı herkesin olduğu gibi (gerçi forumda herkes evliya onun dışında herkes y. ama olsun ben evliya olduğumu iddia etmiyorum, değilim de çok hatalarım vardır)

başka bir forumdan içerik alıp yapay zeka ile emojili falan bir konu oluşturup paylaşsam kodu bir kişi indirip bakmaz, hayatında 5 satır code review yapmamış adam sana klavyeden laf ediyor, ben şimdi bunları gördükten sonra bilgimi neden aktarayım ki? bir kaç sene sonra biri gelip sövsün diye mi? yorumda gelip sövsünler diye mi? paylaştığın özgün içerik olmasına rağmen "ya ben bunu hep biliyordum zaten yaaxd" edasıyla yorum atsınlar diye mi? üstüne birde laf işiteyim diye mi? kraldan çok kralcılık büyük dert. (eleştiri ve hasetle saldırmak farklı şeyler, eleştiriye açığım elbette)

tüm bunların dışında genel bilgi: metin2'nin asıl sorunu kompleks olmasıdır, kompleks sadece karmaşık demek olmuyor gördüğüm 3 semptom:

change amplification
cognitive load
unkown unkowns

tabii complexitynin doğma sebepleri de şunlar: dependencies ve obscurity. codebase tamamen "tactical programming" diye adlandırdığımız yöntemle kodlanmış(halk dilinde hurraa, çalışsın diye yazılmış), teknik be mimari borcun arşa çıktığı, ufak bir değişiklik bile okuyucunun onlarca şey bilmesi gerektiği bir halde, bunu eleştiriyoruz adam bana gelip ymir'i savunuyor, yav kardeşim bunu ben yapıyorsam gel bana da söylen, ama yok illa sana laf atacak falan filan. velhasıl artık benden bir paylaşım göremezsiniz de olurda biri özgün bir içerik paylaşırsa adamı aşağıya çekmeye çalışmayın, hatalar gayet normal. tıpkı milyon dolarlık projelerde dahi sorun yaşandığı gibi, hatalar meydana geldiği gibi, bunlar yazılımın klasik döngüsür, hatta hatasız sistem yoktur, olamaz.

velhasıl "burada hiçbir başarı cezasız kalmaz" benzeri bir durum var, ben çalar ben oynarım ama gerek yok, son olarak şu bahsettiğim sorunu düzeltip konuya ekleyeceğim. herkese hayatında başarılar, bunun bana yarattığı ve yaratacağı sadece stres ve vakit kaybı. bu kadar kasmayın keyfinize bakın..

kasma kendini bu kadar yorma güncel türkiye şartları işte. Sil baştan yazılması gereken bir oyun zaten zaman kaybı kısacası. Psikolojik sağlığına dikkat et çok yorulmuşsun gibi. Bana göre en iyi alan otonom savunma sistemleri otomatik hedefleme savunma altyapısı merkezi yönetim sistemi gibi türk kubbesi.
DeleteLoginData fonksiyonuna bakarsak nesneyi sadece map'lerden temizliyor, delete etmiyor. Yani yazdığın else bloğu çalıştığında nesne her yerden silinip RAM'de ulaşılamaz şekilde sahipsiz kalıyor.
else kısmında DeleteLoginData(pkLD); altına bir de delete pkLD; eklersek sorun kusursuz çözülür bence zühtüde patlamaz :)
yinede eline sağlık paylaşım için teşekkürler
DeleteLoginData fonksiyonuna bakarsak nesneyi sadece map'lerden temizliyor, delete etmiyor. Yani yazdığın else bloğu çalıştığında nesne her yerden silinip RAM'de ulaşılamaz şekilde sahipsiz kalıyor.
else kısmında DeleteLoginData(pkLD); altına bir de delete pkLD; eklersek sorun kusursuz çözülür bence zühtüde patlamaz :)
yinede eline sağlık paylaşım için teşekkürler
 
TL;DR: YMIR dev ağabeyler duplicate login'i temizleyeceğiz derken bir mekanizma kurmuşlar ama normal çıkışa bağlamayı unutup sıvamışlar.

oyuncumuz zühtü olsun;

zühtü oyuna giriş yapıyor. arka planda ne oluyor?

QUERY_AUTH_LOGIN çalışıyor, bir CLoginData nesnesi free store'dan allocate ediliyor. bu nesneye dört farklı container'dan non-owning raw pointer ile erişiliyor amma velakin "non-owning" demek aslında doğru değil(inanın böyle demeyi çok isterdim) çünkü codebasede ownership'i taşıyan tek tip yok, dördü de fiilen "owner" gibi davranıyor, gerçi bi refcount falan var ama dökümantasyon falan olmadığı için pek anlamlandıramadım, binevi elle shared_ptr taklit edilmiş işte danglinge düşmeyelin falan ama 10 saatimi kodu anlamaya çalışarak geçirecek değilim.

"o nasıl oluyor la?" derseniz non-static data memberları inceleyebilirsiniz:
  1. m_map_pkLoginData // login-key -> nesne
  2. m_map_pkLoginDataByLogin // "zuhtu123" -> nesne
  3. m_map_pkLoginDataByAID // account id -> nesne
  4. m_map_kLogonAccount // "zuhtu123" -> nesne

yani şu..., neyse(hocam dayanamıyorum YMIR'e laf atıcam).

diyelim ki zühtü biraz takılıp çıkıyor veya alt + f4 çekiyor. şimdi arka planda ne oluyor? QUERY_LOGOUT çalışıyor, orada da işte DeleteLogonAccount("zuhtu123") çağrılıyor.

DeleteLogonAccount'un flowu şöyle:

C++:
if (pkLD->IsDeleted())   // hımmmmmmmmmm
    delete pkLD;

m_map_kLogonAccount.erase(it);

"IsDeleted() nedir lo?" m_bDeleted non-static data memberı return eden bir accessor. bunu true set eden tek yer SetDeleted mutator, o da yalnızca DeleteLoginData() içinde çağrılıyor. DeleteLoginData()'yı çağıran tek yer ise QUERY_AUTH_LOGIN, yani bu demek oluyor ki aynı hesabın tekrar login olması gerekiyor.

yani düşünelim;

zühtü DB processi hiç restart olmayan bir sunucuda bir kez login/logout tetiklese ve bir daha hiç girmese ne olur? süper olurdu aslında ama, neyse olacak olan şu: QUERY_AUTH_LOGIN bir daha tetiklenmez, bu da sırayla şunlara yol açar:
  • DeleteLoginData çağrılmaz
  • SetDeleted çağrılmaz
  • IsDeleted() hep false return eder
  • "delete pkLD;" delete-expression hiçbir zaman çalışmaz

vay be zühtü, dağları devirdin.

ek olarak zühtü'den bağımsız aynı pattern RemovePeer() içinde de tekrar ediyor:

C++:
if (pkLD->IsDeleted())
{
    delete pkLD;
}

m_map_kLogonAccount.erase(it++);

bu sefer zühtü'nün suçu yok, bir game server peer'i koparsa (artık crash mi olur restart mı olur siz karar verin) o peer üzerinden bağlı tüm hesapları m_map_kLogonAccount'tan silmeye çalışlıyor ama yine aynı döngü IsDeleted normal şartlarda hep false.

yani game server bir sebepten ötürü restart attığı anda o sırada bağlı olan (kaç kişi olduğuna siz karar verin) oyuncuların CLoginData'sı free edilmiyor.

ee noldu yani derseniz olay tam olarak CWE-772(araştırın bi zahmet)

gözümden kaçmadıysa herhangi bir dangling pointer, use after free falan yok, diğer 3 containerda halen geçerli ve erişilebilir. olay nesnenin işi bitmiş ama halen serbest bırakılmamış olması. uğraştırıcı ve fark edilmesi yüksek ihtimal olsa da bir saldırı senaryosu kurulabilir. zaten normal oyuncu devride başlı başına bir dert, art niyetli birini aramanıza da gerek yok uptime'da yardımcı olur.

fix(naked pointer kullanmaya devam etmek isteyenler için):

C++:
    m_map_kLogonAccount.erase(it);

    if (pkLD->IsDeleted())
    {
        delete pkLD;
    }
    else
    {
        DeleteLoginData(pkLD);
    }

burda iteratoru önce erase etmezseniz "DeleteLoginData" çağrısının hiçbir anlamı kalmaz, önce erase edin. bu oyunun codebaseinin içine biraz dalmak bile insanı akıl sağlığından eder, bu büyük ihtimalle açtığım son konu.

rica ederim, iyi forumlar.

Özel rica: Lütfen kullandığınız AI chate girip "ne anlatıyor la bu? şuna bir yorum yaz kallavi olsun" benzeri bir şeyin çıktısıyla yorum yapmayın, bildiğiniz konu ise teknik konuşmak istiyor iseniz doğrusunu yanlışını konuşalım yoksa ne sizin tokenler gitsin ne ben yorulayım, teşekkürler.
bu forumda ismi değişmiş oynayan elemanın paylaşım için teşekkürler :D
 

Şu an konuyu görüntüleyenler (Toplam : 0, Üye: 0, Misafir: 0)

Geri
Üst