2P by GN⁺ | ★ favorite | 댓글 1개
  • FlowTracker는 Java 프로그램이 데이터를 읽고, 조작하고, 쓰는 과정을 추적하는 Java agent로, 출력이 어떤 입력·파일·네트워크·코드 상수에서 왔는지 연결해 보여줌
  • 실행 중인 프로그램을 관찰해 파일 및 네트워크 I/O를 표시하고, 특히 입력과 출력의 대응 관계를 추적해 Java 프로그램의 출력이 무엇을 의미하고 왜 생성됐는지 이해하게 해줌
  • Spring PetClinic 데모에서는 HTTP 응답의 헤더, Thymeleaf 템플릿, 데이터베이스 값, SQL 삽입 스크립트까지 따라가며 소프트웨어 스택 여러 계층을 탐색할 수 있음
  • 내부적으로는 JVM 로딩 시점에 바이트코드를 계측하고, JDK 메서드 훅, 데이터 흐름 분석, ThreadLocal 기반 호출 추적, ClassOriginTracker를 조합해 문자열·문자·바이트 중심의 출처 매핑을 유지함
  • 현재 상태는 프로덕션 준비보다 개념 증명에 가깝고, 일부 예제 프로그램에서는 잘 동작했지만 모든 프로그램에 적합하지 않으며 큰 오버헤드로 실행 속도가 많이 느려짐

FlowTracker가 추적하는 것

  • FlowTracker는 Java 프로그램 안에서 데이터가 어떻게 읽히고, 전달되고, 변형되고, 쓰이는지 추적하는 Java agent임
  • 파일과 네트워크 I/O를 보여주는 데서 그치지 않고, 프로그램의 출력이 어떤 입력에서 왔는지 연결해 보여줌
  • 목적은 Java 프로그램의 출력이 무엇을 의미하는지, 그리고 프로그램이 왜 그 출력을 썼는지 이해하는 데 있음
  • 현재 프로젝트는 이 관점에서 프로그램 동작을 보면 어떤 통찰을 얻을 수 있는지 탐색하는 proof-of-concept

데모: Spring PetClinic에서 HTTP 응답의 출처 추적

  • FlowTracker PetClinic demo는 Spring PetClinic이 HTTP 요청을 처리하고, 템플릿과 데이터베이스 데이터를 기반으로 HTML 페이지를 생성하는 과정을 브라우저에서 볼 수 있게 함
  • 화면에는 PetClinic이 네트워크로 보낸 HTTP 응답이 표시되며, 응답 본문의 일부를 클릭하면 아래쪽 뷰에서 그 부분이 어디서 왔는지 확인할 수 있음
  • 왼쪽 트리나 모바일의 왼쪽 아래 버튼에서 추적된 입력·출처 또는 출력·싱크를 선택할 수 있음
  • HTTP 처리 계층

    • "HTTP/1.1"이나 HTTP 헤더를 클릭하면 이 응답 부분이 org.apache.coyote 패키지의 Apache Coyote 클래스에서 생성됐음을 볼 수 있음
    • FlowTracker는 어떤 코드가 어떤 출력을 만들었는지 표시함
  • Thymeleaf 템플릿 계층

    • "html"이나 "head" 같은 HTML 태그 이름을 클릭하면 해당 HTML 부분이 layout.html 파일에서 왔음을 볼 수 있음
    • layout.html을 클릭한 뒤 아래쪽의 색상 + 버튼을 누르면 그 파일에서 온 모든 부분이 같은 색으로 표시됨
    • 아래로 스크롤하면 응답 일부가 다른 파일인 ownerDetails.html에서 왔음을 확인할 수 있음
    • < 또는 > 문자를 클릭하면 해당 문자가 Thymeleaf 템플릿 라이브러리에 의해 쓰였음을 볼 수 있음
  • 데이터베이스 값의 출처

    • HTML 페이지의 테이블에는 데이터베이스에서 온 정보가 포함됨
    • 테이블의 George를 클릭하면 그 값이 데이터베이스에서 왔다는 수준을 넘어, 처음 데이터베이스에 값을 삽입한 SQL 스크립트까지 추적됨
    • 이 데모에서 SQL 스크립트까지 추적되는 이유는 인메모리 데이터베이스를 사용해 데이터베이스 내용이 JVM 밖으로 나가지 않았기 때문임

MySQL 데모와 프레임워크 독립성

  • 같은 PetClinic 데모를 MySQL 데이터베이스로 실행하면 값은 데이터베이스 연결 지점까지 추적됨
  • 이 경우 이전에 값을 만들기 위해 전송된 SQL 쿼리와 MySQL JDBC 드라이버가 데이터베이스와 통신하는 세부 사항을 볼 수 있음
  • FlowTracker PetClinic mysql demo는 데이터베이스 SSL 연결을 통해 전송된 복호화된 내용을 FlowTracker가 가로챈다는 점도 보여줌
  • Spring PetClinic은 예시일 뿐이며, FlowTracker는 특정 프레임워크나 라이브러리에 의존하지 않음
  • javac demo는 Java 컴파일러를 관찰해 생성된 class 파일 형식과 그 안의 바이트코드를 이해하는 데 FlowTracker가 어떻게 도움을 주는지 보여줌

사용 방법과 주의사항

  • 현재 FlowTracker는 프로덕션 준비 상태라기보다 proof-of-concept에 가까움
  • 여러 예제 프로그램에서 잘 동작했지만, 모든 프로그램에서 잘 동작한다고 보장되지 않음
  • 많은 오버헤드를 추가하므로 프로그램 실행이 훨씬 느려짐
  • 사용 절차:
    • Github releases pages에서 flowtracker-*.jar agent jar를 다운로드함
    • Java 명령줄에 -javaagent:path/to/flowtracker.jar를 추가함
    • FlowTracker를 방해하는 일부 JVM 최적화를 끄기 위해 java -jar flowtracker.jar jvmopts 출력도 명령줄에 추가함
    • 기본적으로 FlowTracker는 8011 포트에서 웹서버를 시작하므로 브라우저에서 http://localhost:8011/을 열면 됨
  • 더 자세한 설정 옵션은 USAGE.md에 있음

내부 동작: 바이트코드 계측과 Tracker 모델

  • FlowTracker는 JVM이 클래스를 로드할 때 class 파일, 즉 바이트코드에 코드를 주입하는 계측 agent임
  • 주입된 코드는 프로그램이 데이터를 읽고 전달하고 쓰는 동안 메모리 안 데이터와 그 출처의 매핑을 유지함
  • 추적 대상의 초점은 String, char, byte[] 같은 텍스트·바이너리 데이터이며, 숫자·구조화 데이터·계산된 데이터가 중심은 아님
  • 사용되는 방식:
    • 일부 JDK 메서드 호출을 FlowTracker 버전의 메서드 호출로 대체함
    • 입력과 출력을 추적하기 위해 JDK의 핵심 위치에 코드를 주입함
    • 메서드 내부의 로컬 변수와 스택 값을 추적하기 위해 데이터 흐름 분석과 더 깊은 계측을 수행함
    • 메서드 호출 전후와 호출된 메서드의 시작·끝에 코드를 추가해 ThreadLocal로 인자와 반환값을 추적함
  • Tracker 데이터 모델

    • Tracker: 추적 대상 객체의 내용과 출처 정보를 보관함
    • content: InputStream이나 OutputStream을 지나간 모든 바이트 같은 데이터
    • source: 내용의 특정 범위를 다른 tracker의 특정 범위와 연결함
    • TrackerRepository: 관심 있는 객체와 해당 Tracker를 연결하는 큰 전역 Map<Object, Tracker>를 보관함
    • TrackerPoint: tracker 안의 한 위치를 가리키며, 하나의 byte 출처 같은 단일 primitive 값을 표현함

기본 계측: JDK 훅과 ASM

  • FlowTracker는 특정 JDK 메서드가 호출될 때 hook 메서드 호출을 삽입해 Tracker를 최신 상태로 유지함
  • 가장 단순한 예는 System.arraycopy
    • java.lang.System.arraycopy 호출을 com.coekie.flowtracker.hook.SystemHook.arraycopy 호출로 대체함
    • SystemHook은 실제 arraycopy를 호출한 뒤, TrackerRepository에서 원본·대상 배열의 tracker를 가져와 대상 tracker가 원본을 가리키도록 갱신함
  • 이런 계측에는 ASM 바이트코드 조작 라이브러리를 사용함
  • 대부분의 hook은 호출자 쪽이 아니라 JDK 메서드 내부의 호출받는 쪽에 추가됨
    • 예를 들어 FileInputStream.read(byte[]) 끝에 FileInputStreamHook.afterReadByteArray 호출을 추가함
    • 이 계측은 ASM의 AdviceAdapter를 사용하는 annotation 기반 자체 마이크로 프레임워크로 구현됨
  • FlowTracker는 java.io.FileInputStream, java.io.FileOutputStream, sun.nio.ch.FileChannelImpl, sun.nio.ch.IOUtil, sun.nio.ch.NioSocketImpl 같은 JDK의 I/O 관련 클래스들에 hook을 추가함
  • 관련 구현:

primitive 값 추적과 메서드 내부 데이터 흐름 분석

  • byte 같은 primitive 값은 객체처럼 identity가 없으므로 TrackerRepositoryMap 키로 안전하게 추적할 수 없음
  • FlowTracker는 primitive 값의 출처를 메서드 내부의 로컬 변수에 별도로 저장하도록 코드를 다시 씀
  • 예를 들어 byte b = x[1] 뒤에는 ArrayHook.getElementTracker(x, 1)b의 tracker를 얻고, y[2] = bArrayHook.setElementTracker(y, 2, bTracker)로 대상 배열에 출처를 기록하는 식임
  • 이를 위해 FlowTracker는 ASM의 분석 기능 위에서 상징 해석(symbolic interpretation) 을 수행함
  • 메서드의 각 지점에서 로컬 변수와 스택의 값이 어디서 왔고 어디로 가는지 모델링함
  • 관련 구현:
  • 모든 primitive 값을 추적하지는 않으며, 초점은 bytechar이고 intlong은 그보다 제한적으로 다룸

메서드 호출을 넘는 데이터 흐름

  • 메서드 내부 분석만으로는 primitive 값이 다른 메서드의 인자와 반환값으로 흐르는 상황을 처리할 수 없음
  • FlowTracker는 Invocation에 인자와 반환값의 PointTracker를 저장하고, 메서드 호출 직전에 이를 ThreadLocal에 넣음
  • 호출된 메서드 시작 지점에서는 Invocation.start(...)ThreadLocal의 정보를 꺼내 primitive 인자의 출처를 사용할 수 있음
  • 이 방식으로 out.write(b)처럼 primitive 값을 메서드로 넘기는 경우에도 write(byte value) 내부에서 value의 tracker를 이어받을 수 있음
  • 관련 구현:

코드 자체를 데이터 출처로 다루기

  • FlowTracker가 추적하는 주요 출처는 I/O와 코드 자체에서 온 값임
  • 코드에서 온 값에는 'a', "abc" 같은 primitive 및 String 상수가 포함됨
  • 이런 상수에는 클래스마다 ClassOriginTracker를 만들고, 클래스와 상수 참조를 텍스트로 표현한 내용을 보관함
  • 상수가 참조될 때 해당 값의 tracker는 이 텍스트 표현 안의 위치를 가리키게 됨
  • 이 모델은 상수가 코드의 텍스트 표현에서 읽힌 것처럼 다루므로, I/O 추적 모델과 유사해짐
  • 성능 때문에 constantPoint 메서드가 메서드 실행마다 호출되지 않도록 ConstantDynamic (JEP 309)을 사용함
  • 관련 구현:

String literal 처리와 제약

  • String literal은 새 String 복사본을 만들고, String.value 안의 byte[]ClassOriginTracker와 연결함
  • String s = "abc"; 같은 문장은 String s = StringHook.constantString("abc", 1234, 81); 형태로 다시 쓰임
  • 이 방식은 JVM이 일반적으로 제공하는 String interning 보장을 깨뜨림
    • 원래 같은 String 상수의 모든 출현은 같은 인스턴스를 참조해야 함
    • instrumentation 후에는 이 보장에 의존하는 코드가 깨질 수 있음
  • FlowTracker는 이 문제를 줄이기 위해 몇 가지 장치를 둠
    • ConstantDynamic을 사용해 같은 줄의 같은 String literal이 여러 번 실행돼도 매번 같은 인스턴스를 반환함
    • 일부 stringA == stringB 표현식을 Objects.equals(stringA, stringB)로 다시 써서 특정 관점에서는 같은 인스턴스처럼 보이게 함
    • java.lang.* 같은 일부 패키지에서는 String literal 추적을 비활성화함
    • 이 동작은 USAGE.mdbreakStringInterning으로 설정 가능함
  • 관련 구현:

추적되지 않는 값의 fallback

  • FlowTracker는 프로그램의 모든 값을 추적하지 않음
  • 이유는 성능 우려, 아직 구현되지 않은 부분, 관련성이 낮은 값, 여러 출처의 조합으로 생기는 값을 표현하려면 더 복잡한 데이터 모델이 필요하다는 점임
  • 추적되지 않던 값이 추적을 시작해야 하는 위치에 도달하면, 상수와 비슷하게 ClassOriginTracker에 연결하고 그 위치를 "<?>"로 표현함
  • 예를 들어 배열 길이는 추적되지 않으므로 write(array.length)가 호출되면, Invocation에는 write 호출 지점의 코드 위치를 가리키는 PointTracker가 전달됨
  • 결과적으로 바이너리 형식의 출력에서 원래 출처를 보지 못하더라도, 주변의 추적된 문자열과 코드 위치를 통해 값의 의미를 빠르게 해석할 수 있는 경우가 있음

더 다룰 수 있는 구현 주제

댓글과 토론

Hacker News 의견들
  • 멋지네요. 같은 방향으로 Clojure용 도구인 FlowStorm을 만들었음 http://www.flow-storm.org/
    계측에는 계측 에이전트 대신 공식 Clojure 컴파일러의 포크를 사용하고, 개발 중 컴파일러를 쉽게 바꿀 수 있는 Clojure 특성을 이용해 추가 바이트코드를 넣음
    Clojure 프로그램 실행 기록에서 흥미로운 점은 대부분의 값이 불변이라 포인터만 유지해도 스냅샷을 잡을 수 있다는 것임
    원 글 데모가 웹 앱 탐색이라, 관심 있는 사람을 위해 FlowStorm으로 웹 앱을 디버깅하는 데모도 남김 https://www.youtube.com/watch?v=h8AFpZkAwPo
    • 정말 멋짐. 왜 JavaFX를 골랐는지 궁금함. JavaFX를 선택한 뒤 cljfx도 살펴봤는지?
    • 좋네요. 값 추적에 자료구조 메타데이터를 쓰는 방식도 좋아하는지 궁금함
  • 정말 대단함
    Java/JVM 생태계의 도구들이 훌륭해서 좋음. 이렇게 놀란 건 예전에 jitwatch를 봤을 때였음 https://github.com/AdoptOpenJDK/jitwatch
    FlowTracker는 검증되지 않은 사용자 입력이나 비밀값이 프로그램 안에서 어떻게 흐르는지 추적해, 누출되거나 검증 없이 쓰이지 않게 하는 오염 분석이 조금 떠오름
    검색 키워드는 “dynamic taint tracking/analysis”임
    https://github.com/gmu-swe/phosphor
    https://github.com/soot-oss/SootUp
    https://github.com/feliam/klee-taint
  • HTML 요소를 그 값을 데이터베이스에 추가한 SQL 문까지 거슬러 추적하는 데모가 인상적임
    앞으로 이런 도구가 버그를 추적할 때 1차 방어선이 되는 모습이 충분히 상상됨
    • 고마움
      FlowTracker를 개발하면서 많은 작업이 특정 예제 프로그램의 추적이 동작하게 만드는 데서 나왔음
      목표 결과는 알고 있었지만, 특정 예제가 동작하려면 어떤 저수준 메커니즘을 지원해야 하는지는 예측하기 어려웠고, 데이터가 지나가는 JDK나 라이브러리의 내부 구현 세부사항에 자주 좌우됐음
      그런데 HTML 요소가 그 데이터를 DB에 넣은 SQL 스크립트로 연결되는 건 그런 식이 아니었음
      기대하거나 의도해서 만든 게 아니라 그냥 그렇게 됐고, 그래서 나도 꽤 놀랐고 이 접근으로 또 무엇을 할 수 있을지 기대하게 됨
    • 생각해보면 데이터의 출처와 진실성을 추적하는 표준 방식이 있었다면 정말 많은 문제가 예방되고, 많은 비즈니스 규칙도 더 쉽게 표현됐을 것 같음
      데이터가 일시적인 것인지, 다시 기록되어야 하는 것인지 추적하는 방법도 있으면 좋겠음
      이런 제약을 앞에서 더 많이 기술할 수 있을수록 더 좋음
  • 전체 그림이나 활용 방식은 아직 완전히 이해한 건 아니지만, 모든 것을 검사할 수 있는 Smalltalk 환경이 떠오름
    Smalltalk에서는 모든 것이 객체와 메시지라서 거슬러 추적하고 상호작용할 수 있음
  • 아주 멋짐. 데모 영상도 좋고, 낯선 코드베이스를 파고들 때 확실히 유용해 보임
  • 몇 년 전 비슷한 개념을 실험해본 적이 있음[1]. JavaScript 소스 맵 같은 것을 HTML에 적용하고 싶었음
    더 확장할 시간을 내지는 못했지만, 웹 개발 도구는 이런 종류의 전체 스택 귀속 추적에서 큰 이득을 볼 것 같음
    다만 이런 해법을 기존 프레임워크에 통합하는 일은 큰 도전으로 느껴짐
    [1] HTML Source Maps - https://github.com/connorjclark/html-source-maps https://docs.google.com/document/d/19XYWiPL9h9vA6QcOrGV9Nfkr...
  • 좋은 의미에서, 프로그램을 디버깅할 때 단순히 “왜 여기에 없지?”라고 묻던 Eve-lang 데모가 떠오름. 훌륭한 작업임
    https://www.youtube.com/watch?v=TWAMr72VaaU&t=164s 그리고 https://witheve.com/
  • 기억이 맞다면 Java 프로그램에서 SQL 삽입을 동적으로 찾는 비슷한 도구에 대한 논문이 있었음. 이게 같은 도구인지?
    • 아니고, 아마 다른 도구였을 것임
      FlowTracker가 하는 일을 확장하면 SQL이나 다른 삽입 취약점도 찾을 수 있음. 그래서 떠올린 도구가 비슷한 접근을 썼을 가능성은 있음
  • 한때 인터넷 너머로 데이터를 추적하는 상상을 했음. 예를 들어 이미지가 어디서 왔고, 어느 CDN에 있었는지 같은 것
    또는 “이 문자열은 생성된 순간부터 내 화면에 도달하기까지 무엇을 봤나” 같은 질문임
    이건 그 방향으로 가는 한 걸음으로 보임
  • 이 도구를 VSCode에서, 이해하려는 프로젝트와 함께 돌려보려고 하는 중임
    지금은 잠시 멈춰야 하지만, 동작하게 만들어서 이것저것 만져보는 게 기대됨