2 puan yazan GN⁺ 2024-09-15 | 1 yorum | WhatsApp'ta paylaş
  • FlowTracker, Java programlarının veriyi okuma, işleme ve yazma sürecini izleyen bir Java agent’tır; çıktının hangi girdi, dosya, ağ ya da kod sabitinden geldiğini bağlayarak gösterir
  • Çalışan programı gözlemleyerek dosya ve ağ I/O’sunu gösterir; özellikle girdi ile çıktı arasındaki eşleşmeyi izleyerek Java programının çıktısının ne anlama geldiğini ve neden üretildiğini anlamaya yardımcı olur
  • Spring PetClinic demosunda HTTP yanıtının başlıklarından Thymeleaf şablonlarına, veritabanı değerlerine ve SQL ekleme betiklerine kadar iz sürerek yazılım yığınının birden çok katmanında gezinmek mümkündür
  • İçeride JVM yükleme anında bytecode’u enstrümante eder; JDK metot hook’ları, veri akışı analizi, ThreadLocal tabanlı çağrı izleme ve ClassOriginTracker’ı birleştirerek string/karakter/byte odaklı köken eşlemesini korur
  • Mevcut durumu üretime hazır olmaktan çok kavram kanıtına yakındır; bazı örnek programlarda iyi çalışmış olsa da her program için uygun değildir ve büyük ek yük nedeniyle çalışma hızını ciddi ölçüde düşürür

FlowTracker neyi izliyor?

  • FlowTracker, Java programı içinde verinin nasıl okunduğunu, aktarıldığını, dönüştürüldüğünü ve yazıldığını izleyen bir Java agent’tır
  • Yalnızca dosya ve ağ I/O’sunu göstermekle kalmaz, program çıktısının hangi girdiden geldiğini de bağlayarak gösterir
  • Amaç, bir Java programının çıktısının ne anlama geldiğini ve programın bu çıktıyı neden yazdığını anlamaktır
  • Mevcut proje, program davranışına bu bakış açısından bakıldığında hangi içgörülerin elde edilebileceğini araştıran bir proof-of-concept’tir

Demo: Spring PetClinic’te HTTP yanıtının kökenini izleme

  • FlowTracker PetClinic demo, Spring PetClinic’in bir HTTP isteğini işlemesini ve şablonlar ile veritabanı verilerine dayanarak HTML sayfası üretmesini tarayıcıda görmeyi sağlar
  • Ekranda PetClinic’in ağ üzerinden gönderdiği HTTP yanıtı gösterilir; yanıt gövdesinin bir kısmına tıklandığında alt görünümde o kısmın nereden geldiği görülebilir
  • Soldaki ağaçtan veya mobilde sol alttaki düğmeden izlenen girdi/köken ya da çıktı/sink seçilebilir
  • HTTP işleme katmanı

    • "HTTP/1.1" veya HTTP başlıklarına tıklarsanız yanıtın bu bölümünün org.apache.coyote paketindeki Apache Coyote sınıfları tarafından üretildiğini görebilirsiniz
    • FlowTracker hangi kodun hangi çıktıyı ürettiğini gösterir
  • Thymeleaf şablon katmanı

    • "html" veya "head" gibi HTML etiket adlarına tıklarsanız ilgili HTML bölümünün layout.html dosyasından geldiğini görebilirsiniz
    • layout.html’e tıkladıktan sonra alttaki renkli + düğmesine basarsanız o dosyadan gelen tüm bölümler aynı renkle gösterilir
    • Aşağı kaydırdığınızda yanıtın bir bölümünün başka bir dosya olan ownerDetails.html’den geldiğini doğrulayabilirsiniz
    • < veya > karakterine tıklarsanız ilgili karakterin Thymeleaf şablon kütüphanesi tarafından yazıldığını görebilirsiniz
  • Veritabanı değerlerinin kökeni

    • HTML sayfasındaki tabloda veritabanından gelen bilgiler bulunur
    • Tablodaki George’a tıklarsanız bu değerin veritabanından geldiği seviyesinin ötesine geçilerek, değeri ilk başta veritabanına ekleyen SQL betiğine kadar izlenir
    • Bu demoda SQL betiğine kadar izlenebilmesinin nedeni, bellek içi veritabanı kullanıldığı için veritabanı içeriğinin JVM dışına çıkmamış olmasıdır

MySQL demosu ve framework bağımsızlığı

  • Aynı PetClinic demosu MySQL veritabanıyla çalıştırıldığında değerler veritabanı bağlantı noktasına kadar izlenir
  • Bu durumda değeri oluşturmak için daha önce gönderilen SQL sorgusunu ve MySQL JDBC sürücüsünün veritabanıyla nasıl iletişim kurduğuna dair ayrıntıları görebilirsiniz
  • FlowTracker PetClinic mysql demo, FlowTracker’ın veritabanı SSL bağlantısı üzerinden iletilen şifresi çözülmüş içeriği yakaladığını da gösterir
  • Spring PetClinic yalnızca bir örnektir; FlowTracker belirli bir framework’e veya kütüphaneye bağlı değildir
  • javac demo, Java derleyicisini gözlemleyerek üretilen class dosyası biçimini ve içindeki bytecode’u anlamada FlowTracker’ın nasıl yardımcı olduğunu gösterir

Kullanım ve dikkat edilmesi gerekenler

  • FlowTracker şu anda üretime hazır olmaktan çok proof-of-concept’e yakındır
  • Birçok örnek programda iyi çalışmıştır, ancak her programda iyi çalışacağı garanti edilmez
  • Yüksek ek yük getirdiğinden programın çalışması çok daha yavaşlar
  • Kullanım adımları:
    • Github releases pages üzerinden flowtracker-*.jar agent jar’ını indirin
    • Java komut satırına -javaagent:path/to/flowtracker.jar ekleyin
    • FlowTracker’ı engelleyen bazı JVM optimizasyonlarını kapatmak için java -jar flowtracker.jar jvmopts çıktısını da komut satırına ekleyin
    • Varsayılan olarak FlowTracker 8011 portunda bir web sunucusu başlatır; tarayıcıda http://localhost:8011/ adresini açmanız yeterlidir
  • Daha ayrıntılı yapılandırma seçenekleri USAGE.md içinde yer alır

İç işleyiş: bytecode enstrümantasyonu ve Tracker modeli

  • FlowTracker, JVM sınıfları yüklerken class dosyalarına, yani bytecode’a kod enjekte eden bir enstrümantasyon agent’ıdır
  • Enjekte edilen kod, program veriyi okurken, aktarırken ve yazarken bellekteki veri ile kökenleri arasındaki eşlemeyi korur
  • İzleme odağı String, char, byte[] gibi metin ve ikili veriler üzerindedir; sayısal, yapılandırılmış veya hesaplanmış veriler merkeze alınmaz
  • Kullanılan yöntemler:
    • Bazı JDK metot çağrılarını FlowTracker sürümündeki metot çağrılarıyla değiştirir
    • Girdi ve çıktıları izlemek için JDK’nin kritik noktalarına kod enjekte eder
    • Metot içindeki yerel değişkenleri ve stack değerlerini izlemek için veri akışı analizi ve daha derin enstrümantasyon yapar
    • Metot çağrılarının öncesine ve sonrasına, ayrıca çağrılan metodun başına ve sonuna kod ekleyerek argümanları ve dönüş değerlerini ThreadLocal ile izler
  • Tracker veri modeli

    • Tracker: İzlenen nesnenin içeriğini ve köken bilgisini tutar
    • content: InputStream veya OutputStream üzerinden geçen tüm byte’lar gibi veriler
    • source: İçeriğin belirli bir aralığını başka bir tracker’ın belirli bir aralığıyla bağlar
    • TrackerRepository: İlgilenilen nesneler ile bunlara karşılık gelen Tracker’ı bağlayan büyük bir global Map<Object, Tracker> tutar
    • TrackerPoint: Bir tracker içindeki bir konumu gösterir ve tek bir byte kökeni gibi tekil bir primitive değeri temsil eder

Temel enstrümantasyon: JDK hook’ları ve ASM

  • FlowTracker, belirli JDK metotları çağrıldığında hook metodu çağrıları ekleyerek Tracker’ı güncel tutar
  • En basit örnek System.arraycopy’dir
    • java.lang.System.arraycopy çağrısını com.coekie.flowtracker.hook.SystemHook.arraycopy çağrısıyla değiştirir
    • SystemHook, gerçek arraycopy’yi çağırdıktan sonra TrackerRepository’den kaynak ve hedef dizilerin tracker’larını alır ve hedef tracker’ı kaynağı gösterecek şekilde günceller
  • Bu tür enstrümantasyon için ASM bytecode manipülasyon kütüphanesi kullanılır
  • Hook’ların çoğu çağıran tarafa değil, JDK metodunun içindeki çağrılan tarafa eklenir
    • Örneğin FileInputStream.read(byte[]) sonuna FileInputStreamHook.afterReadByteArray çağrısı eklenir
    • Bu enstrümantasyon, ASM’nin AdviceAdapter’ını kullanan annotation tabanlı özel bir mikro framework ile uygulanır
  • FlowTracker, java.io.FileInputStream, java.io.FileOutputStream, sun.nio.ch.FileChannelImpl, sun.nio.ch.IOUtil, sun.nio.ch.NioSocketImpl gibi JDK’nın I/O ile ilgili sınıflarına hook ekler
  • İlgili implementasyonlar:

primitive değerlerin izlenmesi ve metot içi veri akışı analizi

  • byte gibi primitive değerlerin nesneler gibi identity’si olmadığından, TrackerRepository’nin Map anahtarı olarak güvenli biçimde izlenemezler
  • FlowTracker, primitive değerlerin kaynağını metot içindeki yerel değişkenlerde ayrıca saklayacak şekilde kodu yeniden yazar
  • Örneğin byte b = x[1] sonrasında ArrayHook.getElementTracker(x, 1) ile b’nin tracker’ı alınır; y[2] = b sırasında da ArrayHook.setElementTracker(y, 2, bTracker) ile hedef diziye kaynak bilgisi kaydedilir
  • Bunun için FlowTracker, ASM’nin analiz özellikleri üzerinde sembolik yorumlama (symbolic interpretation) gerçekleştirir
  • Metodun her noktasında yerel değişkenlerdeki ve stack’teki değerlerin nereden geldiğini ve nereye gittiğini modeller
  • İlgili implementasyonlar:
  • Tüm primitive değerler izlenmez; odak byte ve char üzerindedir, int ve long ise daha sınırlı şekilde ele alınır

Metot çağrılarını aşan veri akışı

  • Yalnızca metot içi analiz, primitive değerlerin başka metotların argümanlarına ve dönüş değerlerine aktığı durumları işleyemez
  • FlowTracker, Invocation içinde argümanların ve dönüş değerinin PointTracker’ını saklar ve metot çağrısından hemen önce bunu ThreadLocal’a koyar
  • Çağrılan metodun başlangıç noktasında Invocation.start(...) ile ThreadLocal’daki bilgi çıkarılarak primitive argümanların kaynağı kullanılabilir
  • Bu yöntemle out.write(b) gibi primitive bir değer metoda geçirildiğinde de write(byte value) içinde value’nun tracker’ı devralınabilir
  • İlgili implementasyonlar:

Kodun kendisini veri kaynağı olarak ele almak

  • FlowTracker’ın izlediği başlıca kaynaklar, I/O’dan ve kodun kendisinden gelen değerlerdir
  • Koddan gelen değerler arasında 'a', "abc" gibi primitive ve String sabitleri bulunur
  • Bu tür sabitler için her sınıf başına bir ClassOriginTracker oluşturulur ve sınıf ile sabit referanslarını metin olarak temsil eden içerik saklanır
  • Bir sabite başvurulduğunda, ilgili değerin tracker’ı bu metin temsilindeki konumu işaret eder
  • Bu model, sabitleri kodun metinsel temsilinden okunmuş gibi ele aldığı için I/O izleme modeline benzer hale gelir
  • Performans nedeniyle constantPoint metodunun her metod çalıştırıldığında çağrılmasını önlemek için ConstantDynamic (JEP 309) kullanılır
  • İlgili implementasyonlar:

String literal işleme ve kısıtlar

  • String literal’lar yeni bir String kopyası oluşturur ve String.value içindeki byte[] değerini ClassOriginTracker ile ilişkilendirir
  • String s = "abc"; gibi bir ifade String s = StringHook.constantString("abc", 1234, 81); biçiminde yeniden yazılır
  • Bu yöntem, JVM’in normalde sağladığı String interning garantisini bozar
    • Normalde aynı String sabitinin tüm görünümleri aynı instance’a başvurmalıdır
    • Instrumentation sonrasında bu garantiye dayanan kod bozulabilir
  • FlowTracker bu sorunu azaltmak için bazı mekanizmalar sunar
    • ConstantDynamic kullanarak, aynı satırdaki aynı String literal birden çok kez çalıştırılsa bile her seferinde aynı instance’ın döndürülmesini sağlar
    • Bazı stringA == stringB ifadelerini Objects.equals(stringA, stringB) olarak yeniden yazarak belirli açılardan aynı instance gibi görünmesini sağlar
    • java.lang.* gibi bazı paketlerde String literal izlemeyi devre dışı bırakır
    • Bu davranış, USAGE.md içindeki breakStringInterning ile yapılandırılabilir
  • İlgili implementasyonlar:

İzlenmeyen değerler için fallback

  • FlowTracker programdaki tüm değerleri izlemez
  • Bunun nedenleri performans kaygıları, henüz uygulanmamış kısımlar, düşük ilgili değerler ve birden çok kaynağın birleşiminden doğan değerleri temsil etmek için daha karmaşık bir veri modeline ihtiyaç duyulmasıdır
  • Daha önce izlenmeyen bir değer, izlemenin başlaması gereken bir noktaya ulaştığında, sabitlere benzer şekilde ClassOriginTracker’a bağlanır ve konumu "<?>" olarak temsil edilir
  • Örneğin dizi uzunluğu izlenmediği için write(array.length) çağrıldığında, Invocation’a write çağrı noktasının kod konumunu işaret eden bir PointTracker aktarılır
  • Sonuç olarak, ikili formattaki çıktıda asıl kaynak görülemese bile, çevredeki izlenen dizgeler ve kod konumu sayesinde değerin anlamını hızlıca yorumlamak mümkün olabilir

Ele alınabilecek diğer uygulama konuları

  • MergedValue, dallanma ve döngülerden geçen değerlerin izlenmesini ele alır; bu, veri akışı analizinin en zor kısımlarından biri olarak kabul edilir
  • String concatenation, indification (JEP 280) aracılığıyla StringConcatFactory tarafından döndürülen MethodHandlea hook eklenerek işlenir
  • Kaynak kodu bulma, Vineflower ile decompile etme ve bytecode ile kaynak satırlarını ilişkilendirme de uygulamaya dahildir
  • ClassLoader yapılandırması, bootclasspath bağımlılıkları ve uygulama çakışmalarından kaçınmaya; shading ve nested jar olmadan hızlı bir geliştirme döngüsünü korumaya odaklanır
  • Alanlarda saklanan primitive değerlerin izlenmesi de uygulamaya dahildir
  • Frontend, Jetty ve JAX-RS tabanlı bir web sunucusu ile Svelte tabanlı bir web arayüzünden oluşur

1 yorum

 
GN⁺ 2024-09-15
Hacker News yorumları
  • Harika. Ben de aynı doğrultuda Clojure için FlowStorm adlı bir araç yaptım: http://www.flow-storm.org/
    Enstrümantasyon için bir enstrümantasyon ajanı yerine resmi Clojure derleyicisinin bir fork’unu kullanıyor; geliştirme sırasında derleyiciyi kolayca değiştirmeyi sağlayan Clojure özelliğinden yararlanarak ek bytecode ekliyor
    Clojure programlarının yürütme kayıtlarında ilginç olan şey, değerlerin çoğunun değişmez olması; bu yüzden yalnızca pointer’ları tutarak snapshot alınabiliyor
    Orijinal yazıdaki demo bir web uygulamasında gezinme olduğu için, ilgilenenler adına FlowStorm ile bir web uygulamasını debug etmeyi gösteren demoyu da bırakıyorum: https://www.youtube.com/watch?v=h8AFpZkAwPo
    • Gerçekten harika. Neden JavaFX seçtiğini merak ettim. JavaFX’i seçtikten sonra cljfx’e de baktın mı?
    • Güzel. Değer takibi için veri yapısı metadata’sı kullanma yaklaşımını da sevip sevmediğini merak ediyorum
  • Gerçekten muazzam
    Java/JVM ekosistemindeki araçların bu kadar iyi olması hoşuma gidiyor. En son böyle şaşırdığım zaman jitwatch’ı gördüğüm zamandı: https://github.com/AdoptOpenJDK/jitwatch
    FlowTracker bana, güvenilmeyen kullanıcı girdilerinin veya gizli değerlerin program içinde nasıl aktığını izleyip sızdırılmamasını ya da doğrulanmadan kullanılmamasını sağlayan taint analysis’i biraz hatırlatıyor
    Arama anahtar sözcüğü “dynamic taint tracking/analysis”
    https://github.com/gmu-swe/phosphor
    https://github.com/soot-oss/SootUp
    https://github.com/feliam/klee-taint
  • Bir HTML öğesini, o değeri veritabanına ekleyen SQL sorgusuna kadar geriye doğru izleyen demo etkileyici
    İleride böyle araçların hata izlerken ilk savunma hattı haline geldiğini rahatlıkla hayal edebiliyorum
    • Teşekkürler
      FlowTracker’ı geliştirirken işin büyük kısmı, belirli örnek programlarda izlemenin çalışmasını sağlamaktan çıktı
      Hedef sonucu biliyordum; ama belirli bir örneğin çalışması için hangi düşük seviyeli mekanizmaları desteklemek gerektiğini öngörmek zordu ve bu çoğu zaman verinin içinden geçtiği JDK veya kütüphanelerin iç uygulama ayrıntılarına bağlıydı
      Ama HTML öğesinin, o veriyi DB’ye koyan SQL script’ine bağlanması böyle bir şey değildi
      Beklediğim ya da özellikle tasarladığım bir şey değildi; kendiliğinden oldu. Bu yüzden ben de epey şaşırdım ve bu yaklaşımla başka neler yapılabileceği konusunda heyecanlandım
    • Düşününce, verinin kökenini ve doğruluğunu izlemek için standart bir yol olsaydı gerçekten pek çok sorun önlenebilirdi; birçok iş kuralı da daha kolay ifade edilebilirdi
      Verinin geçici mi olduğu, yoksa yeniden yazılması mı gerektiğini izleyebilmenin de bir yolu olsa güzel olurdu
      Bu tür kısıtları baştan ne kadar çok tanımlayabilirsek o kadar iyi
  • Büyük resmi ya da kullanım biçimini hâlâ tam anlamış değilim, ama her şeyin incelenebildiği Smalltalk ortamını hatırlatıyor
    Smalltalk’ta her şey nesne ve mesaj olduğu için geriye doğru izlenebilir ve etkileşime girilebilir
  • Çok havalı. Demo videosu da güzel; yabancı bir kod tabanının içine dalarken kesinlikle faydalı görünüyor
  • Birkaç yıl önce benzer bir kavramı denemiştim[1]. JavaScript source map benzeri bir şeyi HTML’e uygulamak istemiştim
    Daha fazla genişletmeye zaman ayıramadım, ama web geliştirici araçlarının bu tür tam yığın atıf takibinden büyük fayda görebileceğini düşünüyorum
    Ancak bu tarz çözümleri mevcut framework’lere entegre etmek büyük bir zorluk gibi geliyor
    [1] HTML Source Maps - https://github.com/connorjclark/html-source-maps https://docs.google.com/document/d/19XYWiPL9h9vA6QcOrGV9Nfkr...
  • İyi anlamda, programı debug ederken basitçe “neden burada değil?” diye soran Eve-lang demosunu hatırlatıyor. Harika iş
    https://www.youtube.com/watch?v=TWAMr72VaaU&t=164s ve https://witheve.com/
  • Yanlış hatırlamıyorsam Java programlarında SQL injection’ı dinamik olarak bulan benzer bir araç hakkında bir makale vardı. Bu aynı araç mı?
    • Hayır, muhtemelen başka bir araçtı
      FlowTracker’ın yaptığı iş genişletilirse SQL ya da başka injection açıkları da bulunabilir. Bu yüzden aklındaki aracın benzer bir yaklaşım kullanmış olması mümkün
  • Bir ara veriyi internetin ötesine kadar izlemeyi hayal etmiştim. Örneğin bir görselin nereden geldiği, hangi CDN’de bulunduğu gibi
    Ya da “bu string oluşturulduğu andan ekranıma ulaşana kadar neler gördü?” gibi bir soru
    Bu, o yöne atılmış bir adım gibi görünüyor
  • Bu aracı, anlamaya çalıştığım projeyle birlikte VSCode içinde çalıştırmayı deniyorum
    Şimdilik ara vermem gerekiyor, ama çalışır hale getirip kurcalamayı dört gözle bekliyorum