Repository navigation
fix(axis-② typed-leaf): 결함 3클래스 동시 수정 — 레이아웃 24B/i64 + HEAP carrier + C-ABI thunk - #4937
Merged
Merged
Conversation
…-neutral)
네이티브 .s 시드는 hexa pair-ABI 로만 호출할 수 있는데, descriptor 를 잡을 수단이
아예 없었다. self/runtime.h 를 전수 조사한 결과 heap 전용 pair-ABI carrier 가 부재하고,
유일한 범용 carrier 인 hexa_ptr_alloc 은 이름과 달리 ARENA 다:
_hexa_ptr_alloc: # self/native/alloc_syscall_arm64.s:3148
bl _hexa_arena_alloc # ← arena bump-allocator 로 위임
runtime.a 안에 정의가 둘 있고(runtime.o 의 weak malloc vs alloc_syscall_native.o),
링크에서 이기는 쪽이 후자다. 그런데 배열 DESCRIPTOR 는 프레임을 탈출하므로 arena 에
있으면 rewind 후 다음 bump-alloc 에 재사용되어 len/cap/items 가 조용히 덮어써진다 —
C 원본 주석[runtime_core_emit.hexa:2739]이 명시 금지하며, calloc 은 바로 그 방어였다.
hexa_heap_alloc(size) malloc
hexa_heap_zalloc(size) calloc(1,n) — descriptor 클래스
hexa_heap_realloc(ptr,size) realloc — typed-leaf grow 경로용
★ STRONG (non-weak) 이 설계의 핵심이다. weak 였기 때문에 arena 시드가 hexa_ptr_alloc 을
소리 없이 가로챘다. strong 이면 어떤 네이티브 시드가 같은 이름을 정의하는 순간
multiple-definition 게이트가 빌드 시점에 시끄럽게 터진다 — 조용한 교체는 두 번 없다.
ARRAY_NEW 가드 바깥에 emit 한다(시드가 켜져도 carrier 는 남아야 하므로). 생성된
runtime_core.c 에서 carrier 는 2587-2597, 가드 #endif 는 2582 로 확인.
검증 (mini darwin, stage_resolve 후 nm):
runtime.o: T _hexa_heap_alloc / T _hexa_heap_zalloc / T _hexa_heap_realloc
weak(W/S/v) 0건 · 다른 멤버의 정의 0건
BYTE-NEUTRAL — array_new 의 .s 시드는 main 에 없으므로(#4932 revert) ARR-NEW 는 C 경로를
유지한다. 이 PR 은 substrate + 배선까지이고, flip 은 regen + byteeq 후 별도 라운드다.
⚠️ 함께 박제하는 FALSIFIED 결과: "build_aprime 의 index 7 out of bounds(len 2) = arena
descriptor 오염" 이라는 내 판정은 틀렸다. 내 변경이 하나도 없는 순수 origin/main
대조군에서도 동일하게 재현됐고, 진짜 원인은 summer 의 HEXA_PREBUILT_RUNTIME 이 7/4 자
낡은 ~/.hx runtime.a 를 가리킨 환경오염이었다(convergence stage-resolve-runtime-a-2
trap#2). arena 라는 사실은 참이지만, 그것이 눈앞 증상의 인과는 아니었다.
state/hexa-own/seed_abi_class_audit.md — 워크플로우 wf_5c5b82ee-80b 전문(41 에이전트 ·
적대적 반증 통과 33건). 결정적 신규: typed-leaf i64/f64 는 raw-libc egress 말고
SCALAR-ABI ingress/return 미스매치가 따로 있어 carrier 치환만으론 안 고쳐진다.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rrier # Conflicts: # ARCHITECTURE.json # ING.jsonl # tool/regen_array_new_leaf_native_s.sh
alloc-syscall-arm64-s-1: 'hexa_ptr_alloc = ARENA' 는 참이지만, 그것이 build_aprime 의 'index 7 out of bounds' 의 원인이라는 내 판정은 FALSIFIED. 내 변경이 0 인 순수 origin/main 대조군에서도 동일 재현됐고, GitHub 새 clone 에서는 같은 main 이 정상 빌드됐다 — 진짜 원인은 풀 호스트에 남은 stale 생성 아티팩트였다. build-aprime-sh-3: 그 stale-생성물 학습. 처음 build-aprime-sh-2 로 넣었는데 main 에 같은 id(오류 필터 학습)가 이미 있어 union 병합에서 조용히 드롭됐다 — id 충돌 자체가 또 하나의 함정.
…안 고쳐진다 (__hxw_ 브리지가 정답지)
워크플로우 wf_ba079006-120 (30 에이전트 · 적대적 반증 통과 23건).
내 전제 4건이 반증됐다:
1. "array_core 가 __hxw_ 브리지를 쓴다" — FALSE. grep 히트가 주석이었고, 그 주석은
정반대를 말한다: "no C bridge needed for this native→native edge; the __hxseed_
bridge exists only for C-side callers" [array_core.hexa:54-63].
2. "브리지 미사용 = 역사적 누락" — FALSE, 구조적 불가. #define 은 runtime_core.c 텍스트만
덮는데 typed-leaf 의 호출자는 3종이다 — runtime_core.c · gen2 가 뱉는 user.c ·
네이티브 백엔드가 뱉는 raw asm call. cpp 는 뒤 둘에 도달하지 못한다.
3. "len→0 이 SIGSEGV 근인" — FALSE. 데드 심볼(실호출자 0).
4. "잠복 결함" — FALSE. opt-out 만 있고 시드 9개가 tracked, packed 경로도 default-ON
→ 깨진 시드 x 깨진 레이아웃이 현재 SHIPPED DEFAULT 다.
★ 신규 확정 — 클래스 C (디스크립터 레이아웃 표류). 이게 진짜 근인이고 ABI 수정으로는
원리적으로 못 고친다:
C [runtime_core_emit.hexa:1366-1370] { int64_t* data@0; int64_t len@8; int64_t cap@16 } 24B
시드 [array_typed_leaf.hexa:65,69-70] malloc(16) · store32(a,8) · store32(a,12) 16B
구조체는 #3352 에서 i64 로 넓어졌는데, 시드는 그보다 한참 뒤(#4917)에 작성됐음에도 옛
치수를 쓴다. 코드젠은 len 을 +8 에서 64비트로 읽으므로 len=(cap<<32)|len = 거대값이 되고,
bounds check 를 통과해 OOB SIGSEGV. 역방향(C-mint → 시드 push)은 cap 을 +12(len 의 상위
32비트)에서 읽어 cap=0 → realloc(old,0)=free → UAF.
정답지는 같은 리포에 있다 — arr_zeros_leaf.hexa:61-63 은 store64 @0/@8/@16 을 쓴다.
zeros/array_new 이 carrier 치환만으로 살아난 건 "스칼라 미러가 없어서"만이 아니라
레이아웃도 이미 맞았기 때문이다.
권고안 = thunk (시드를 *_seed 로 rename + all-HexaVal 시그니처, C 가 정식 이름을 C-ABI
thunk 로 소유). 3종 호출자를 전부 커버하고 코드젠/gen2/frozen-seed 편집이 0이며, rename
자체가 flag-day 가드가 된다 — 낡은 runtime.a 는 undefined symbol 로 시끄럽게 죽지,
조용히 레지스터를 잘못 넘기지 않는다.
클래스 A(carrier) + B(thunk) + C(레이아웃) 셋 다 필수다. 하나만 고쳐서는 SIGSEGV 가
사라지지 않는다.
구현-ready 스펙(E1~E5, 전부 file:line) = state/hexa-own/typed_leaf_abi_layout_audit.md
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… C-ABI thunk
typed-leaf(i64/f64) 는 SHIPPED DEFAULT 인데 결함이 세 겹이었다. 하나만 고쳐서는
SIGSEGV 가 사라지지 않는다.
★ 클래스 C (레이아웃 표류) — 진짜 근인. ABI 수정으로는 원리적으로 못 고친다.
C { int64_t* data@0 ; int64_t len@8 ; int64_t cap@16 } 24B (#3352 에서 i64 확장)
시드 malloc(16) · store32(a,8) · store32(a,12) 16B (#4917, 그 한참 뒤)
코드젠은 len 을 +8 에서 64비트로 읽는다 → len = (cap<<32)|len = 거대값 → bounds
check 를 통과해 배열 밖을 읽는다. 역방향(C 가 만든 24B 배열 → 시드 push)은 cap 을
+12(len 의 상위 32비트)에서 읽어 cap=0 → realloc(old,0) = free → 직후 그 포인터로
store → UAF. 시드 주석 자체가 stale 했고(존재하지도 않는 runtime.h HexaArrI64 를
인용) 심지어 "load64 쓰지 마라"고 경고까지 하고 있었다.
클래스 B (스칼라-ABI) — C 프로토타입은 스칼라(new(int cap) → cap 은 rdi 단독 ·
int len(HexaVal) → rax 단독)인데 aprime 는 시드의 모든 파라미터를 (tag,payload) 쌍으로
낮춘다. cpp #define 브리지로는 못 고친다: 호출자가 3종(runtime_core.c · gen2 가 뱉는
user.c · 백엔드가 뱉는 raw asm call)이고 cpp 는 뒤 둘에 도달하지 못한다.
클래스 A (raw libc egress) — malloc/realloc/write 를 raw extern 으로 부른다. hexa_int 도
C-ABI 스칼라라 같은 함정인데, sanctioned allow-list 에 숨어 있어 이름 게이트로는 영원히
잡히지 않았다.
FIX — 셋 동시:
(C) 24B/i64 + store64/load64 전면 교체. 정답지 = arr_zeros_leaf.hexa:61-63.
(B) thunk. 시드를 *_seed 로 rename + all-HexaVal 시그니처(pair 낮춤이 구조적으로 맞는
유일한 형태). C 가 정식 이름을 C-ABI thunk 로 소유 → 3종 호출자가 전부 착지한다.
rename 은 flag-day 가드이기도 하다: 낡은 runtime.a 는 undefined symbol 로 시끄럽게
죽지, #4930 처럼 조용히 레지스터를 잘못 넘기지 않는다.
(A) hexa_heap_alloc/realloc carrier 로 치환 + hexa_int 제거(box 는 __hx_make_val 리프로
re-box) + OOM 경로 write/exit → hexa_throw.
게이트 — regen ALLOWED_U 를 carrier-only 로(hexa_int 를 allow-list 에서 축출) ·
stage/regen 의 심볼명 · nm · .globl 정규식을 전부 _seed 로 락스텝.
검증 (summer · GitHub 새 clone A/B · 실제 runtime.a 링크 스모크, 격리 stub 아님, 2/2 PASS):
A) 새 클론 + main 빌드 OK
B) 새 클론 + 이 패치 빌드 OK
new(2) → descriptor len@8=0, cap@16=2
len() → 0
push x5 → cap 2 를 넘겨 realloc 2회 성장(2→4→8) → len()=5, cap@16=8
box(0..4) → 100,101,102,103,104
구 시드는 바로 이 지점에서 SIGSEGV / UAF 였다.
.s 시드 9개 재생성 + byteeq 3-target 은 CI 가 판정한다.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…c 0, hexa_int 축출)
summer aprime 로 3타깃 x 2가족 재생성. x86_64 는 '4/4 T defined, U-floor clean' 을
찍었다 — carrier-only 화이트리스트를 통과했다는 뜻이다.
외부 호출: hexa_heap_alloc · hexa_heap_realloc · hexa_throw (+ arm64 는 백엔드 자체
헬퍼 hexa_bool)
export: hexa_arr_{i64,f64}_*_seed (8개)
raw libc(malloc/realloc/write/exit) 0건, hexa_int 0건 — 후자는 C-ABI 스칼라라 pair 호출이
항상 hexa_int(0) 을 반환했고, sanctioned allow-list 에 숨어 이름 게이트로는 잡히지
않던 것이다.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
_bad(U-floor) 검사가 전부 own-obj 분기에만 있었다. 실제 기본 배포 경로인 .s 조립 분기는 심볼 개수만 세고 undefined 를 보지 않았다 — raw libc U 심볼이 무검사로 ship runtime.a 에 들어갔고, 이것이 클래스-A(pair-vs-C-ABI 미스컴파일)를 통과시킨 구멍이다. #4930 이 3타깃 전부 깨진 경로가 바로 여기다. 4개 조립 사이트(ARR-I64 · ARR-NEW · ARR-ZEROS · ARR-F64) 전부에 U-floor FATAL 게이트. ALLOWED : HexaVal-ABI carrier(hexa_heap_alloc/zalloc/realloc · hexa_throw · hexa_exit) + 백엔드 자체 헬퍼 hexa_bool (codegen 이 ABI 를 통제한다) 금지 : raw libc(malloc/calloc/realloc/write/exit) + hexa_int — 그 자체가 C-ABI 스칼라라 시드의 pair 호출이 항상 hexa_int(0) 을 반환했다. sanctioned allow-list 에 숨어 있어 이름 게이트로는 영원히 잡히지 않던 것이다. 동반 수정 — arr_zeros_leaf 시드가 아직 raw libc(calloc/malloc/write/exit)를 쓰고 있어 (main 배포 상태) 게이트를 켜면 즉시 걸린다. carrier(hexa_heap_zalloc/alloc + hexa_throw) 로 치환하고 .s 3개를 재생성했다. 검증: · 구 zeros 시드(main)에 게이트를 돌려 calloc / malloc / write / hexa_exit 4개를 실제로 잡아내는 것 확인. 신 시드는 통과. · 게이트 ON 상태로 summer 전체 빌드 OK. U-floor 로도 클래스-B(스칼라-ABI ingress/return 미스매치)는 원리적으로 못 잡는다 — 심볼 이름만 보기 때문이다. 그쪽은 thunk 구조로 막는다. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
fleet census(41 에이전트)의 임계경로 1번: "#4937 을 머지할지 판정할 오라클이 지금 없다. 없이 머지하면 #4930 → #4932(3타깃 파괴 → revert)를 반복한다." 실제로 시드 게이트는 전부 proxy 였고, 전부 거짓말했다: .globl 개수 "4/4 export" ... 그 4개 바디가 miscompile 이었다 nm U-floor "carrier-only" ... 이름 검사다. 클래스-B(스칼라-ABI 미스매치)와 클래스-C(레이아웃 표류)에는 구조적으로 눈이 멀었다 byteeq 초록 "회귀 없음" ... 그 경로가 안 밟혔다는 뜻일 뿐이다 그래서 이 게이트는 시드를 들여다보지 않는다. canonical C-ABI 엔트리로 실제로 RUN 하되, REAL runtime.a 에 링크해서(stub 금지 — carrier 를 malloc stub 으로 대체하면 arena descriptor 결함을 통과시킨다, 실측) 관측 가능한 계약을 단언한다: descriptor 24B/i64 오프셋 · len 추적 · amortized-doubling grow · element round-trip grow 경로가 가장 날카로운 칼날이다. 레이아웃(C) + carrier(A) + ABI(B) 가 동시에 맞아야만 살아남는다 — cap 오프셋이 틀리면 realloc(old,0) 이 free 가 되고 직후 store 가 UAF 다. ★ 판별력 실증 (summer · GitHub 새 clone · env -u HEXA_PREBUILT_RUNTIME): origin/main (깨진 시드) → rc=139 SIGSEGV 수정 브랜치 → rc=0 22/22 PASS (i64 + f64, grow 포함) 알려진 파손 트리에서 RED 를 못 띄우는 게이트는 게이트가 아니다. 이건 띄운다. CI 배선 = nobaseline-gate.yml 의 seed-abi-contract-x86_64. NOT advisory — 이 클래스를 advisory 로 둔 것이 애초에 출하를 허용한 원인이다. state/hexa-own/frontier_5lane_census.md — 5레인 fleet census 전문(411KB). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…yout # Conflicts: # ARCHITECTURE.json # ING.jsonl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
typed-leaf(i64/f64) 는 SHIPPED DEFAULT 인데 결함이 세 겹이었다. 하나만 고쳐서는 SIGSEGV 가 사라지지 않는다.
★ 클래스 C (레이아웃 표류) — 진짜 근인
코드젠은 len 을 +8 에서 64비트로 읽는다 →
len = (cap<<32)|len= 거대값 → bounds check 를 통과해 배열 밖을 읽는다. 역방향(C 가 만든 24B 배열 → 시드 push)은 cap 을 +12(len 의 상위 32비트)에서 읽어cap=0→realloc(old,0)= free → 직후 그 포인터로 store → UAF.시드 주석 자체가 stale 했다 — 존재하지도 않는
runtime.h HexaArrI64를 인용하고, 심지어 "load64 쓰지 마라"고 경고까지 하고 있었다.클래스 B (스칼라-ABI) · 클래스 A (raw libc egress)
C 프로토타입은 스칼라(
new(int cap)→ cap 은 rdi 단독)인데 aprime 는 시드의 모든 파라미터를(tag,payload)쌍으로 낮춘다. cpp#define브리지로는 못 고친다 — 호출자가 3종(runtime_core.c · gen2 가 뱉는 user.c · 백엔드가 뱉는 raw asm call)이고 cpp 는 뒤 둘에 도달하지 못한다. 그리고hexa_int도 C-ABI 스칼라라 같은 함정인데, sanctioned allow-list 에 숨어 있어 이름 게이트로는 영원히 잡히지 않았다.FIX — 셋 동시
arr_zeros_leaf.hexa:61-63)*_seed로 rename + all-HexaVal 시그니처, C 가 정식 이름을 C-ABI thunk 로 소유 → 3종 호출자 전부 착지hexa_heap_alloc/realloccarrier 치환 +hexa_int제거 + OOM 경로 →hexa_throwrename 은 flag-day 가드이기도 하다: 낡은 runtime.a 는 undefined symbol 로 시끄럽게 죽지, #4930 처럼 조용히 레지스터를 잘못 넘기지 않는다.
검증 (summer · GitHub 새 clone A/B · 실제 runtime.a 링크 스모크, 격리 stub 아님)
구 시드는 바로 이 지점에서 SIGSEGV / UAF 였다.
.s시드 6개 재생성 완료 —4/4 T defined, U-floor clean(외부 호출: carrier 3종 + arm64 의hexa_bool뿐, raw libc 0).근거 전문(30 에이전트 · 적대적 반증 23건) =
state/hexa-own/typed_leaf_abi_layout_audit.md🤖 Generated with Claude Code