# 주제: 같은 리프 안에서 "정규 형식" 인코딩이 두 가지이며, 그중 하나는 # 바로 옆 파일이 명시적으로 금지한 방식이다 # revision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 # (A) 길이 접두 방식 — ApprovalGrant.canonicalForm() (ApprovalGrant.java:61-87) # javadoc: # "Every field is length-prefixed rather than delimited. A delimiter can be smuggled into a # ticket or an identity to make two different grants render identically; a length prefix # cannot." # 구현: # private static void appendField(StringBuilder canonical, String value) { # canonical.append(value.length()).append(':').append(value); # } # 대상 필드 11개: ticket, approvedBy, requestedBy, operation, source, target, topologyVersion, # maxImpact, planDigest, approvedAt, validUntil # (B) 구분자 결합 방식 — 계획 다이제스트 2곳 # ReplayPlan.digest() (ReplayPlan.java:51-64) # PlanDigest.ofCanonical(String.join("|", # "REPLAY", replayId, destination, isolatedConsumerGroup, from, to, estimatedMessages, # topologyVersion, targetsLiveConsumerGroup)) # RedrivePlan.digest() (RedrivePlan.java:50-62) # PlanDigest.ofCanonical(String.join("|", # "REDRIVE", redriveId, source, target, batchSize, candidates, # alreadyRedrivenCandidates, topologyVersion)) # ---- 각 필드가 '|' 를 담을 수 있는가 ---- # command: cat messaging-core-api/.../destination/DestinationName.java # private static final Pattern VALID = Pattern.compile("[a-z0-9][a-z0-9.-]{0,159}"); # -> destination/source/target 는 '|' 불가. # replayId/redriveId : UUID.toString() -> 불가 # 불리언 : "true"/"false" -> 불가 # from/to : Instant.toString() -> 불가 ("" 가능) # 정수/long : 10진수 -> 불가 # topologyVersion : String, 검증은 isBlank() 뿐 -> **가능** # command: git grep -n "topologyVersion" -- src/messaging/messaging-admin-api/src/main # ReplayPlan.java:37-38 if (topologyVersion == null || topologyVersion.isBlank()) throw … # RedrivePlan.java:37-38 동일 # 생산처: BrokerTopologyInspector.topologyVersion() (messaging-admin-runtime, 애플리케이션이 구현하는 SPI) # -> 형식 제약 없음. # ---- 현재 충돌 가능성 판정 ---- # 자유 필드가 topologyVersion 하나뿐이고, 그 앞의 모든 필드가 '|' 를 담을 수 없으므로 # 왼쪽에서 '|' 를 세면 앞 필드들의 경계가 확정되고, 마지막 필드(고정 형식)도 오른쪽에서 확정된다. # 따라서 현재 필드 구성에서는 인코딩이 단사(injective)이며 **충돌을 만들 수 없다**. # # 판정: 지금은 악용 가능한 결함이 아니다. 기록하는 이유는 두 가지다. # 1) 같은 리프의 ApprovalGrant 가 정확히 이 위험("구분자 밀반입")을 이유로 길이 접두를 쓰고 # 그 이유를 javadoc 에 남겼는데, 승인이 서명으로 묶이는 대상인 계획 다이제스트 두 곳은 # 그 규칙을 따르지 않는다. # 2) 단사성이 "자유 형식 필드가 하나뿐" 이라는 우연한 조건에 의존한다. # 자유 형식 필드가 하나 더 추가되거나 topologyVersion 의 위치가 바뀌면 # 조용히 깨지며, 깨졌을 때의 결과는 "한 승인이 다른 계획을 인가" 로 # PlanDigest 가 존재하는 이유 자체가 무력화되는 것이다. # 참고: PlanDigest 자체는 형식을 강하게 검증한다 (PlanDigest.java:23-28) # if (!value.matches("[0-9a-f]{64}")) throw new IllegalArgumentException(…)