[php-src] Issue #23979: zend_get_typ e_by_const 의 ZEND_UNREACHABLE 위반 (배 열 오프셋 에러 경로)

From: Date: Tue, 29 Sep 2026 08:28:08 +0000
Subject: [php-src] Issue #23979: zend_get_typ e_by_const 의 ZEND_UNREACHABLE 위반 (배 열 오프셋 에러 경로)
Groups: php.bugs 
Request: Send a blank email to php-bugs+get-252847@lists.php.net to get a copy of this message
Issue: https://github.com/php/php-src/issues/23979 Author: rlaaudrb1104 ### Description ## 개요 | | | |---|---| | 대상 | php @ 38956a3 | | 결함 유형 | ZEND_UNREACHABLE(=assert(0)) 위반 → SIGABRT (**디버그 빌드 전용**) | | 크래시 지점 | zend_get_type_by_const@Zend/zend_API.c:126 — default: ZEND_UNREACHABLE(); (← zend_zval_value_name ← zend_fetch_dimension_address_read@zend_execute.c:3206) | | 재현 | 재현됨 (php --enable-debug + ASan, 동일 커밋) | | 최신 릴리스 | 릴리스는 NDEBUG 라 UNREACHABLE 이 컴파일아웃됨 (아래 영향 참조) | | PoC | pocs/04_3e4b59e314ffb841.php (256 bytes) | ## 전체 출력 (디버그 빌드 재현) --enable-debug + ASan 로 빌드한 php(CLI)로 PoC 를 실행하면 다음과 같이 종료한다. ``` Deprecated: Non-canonical cast (integer) is deprecated ... Warning: Octal escape sequence overflow \400 is greater than \377 ... Deprecated: The backtick (`) operator is deprecated ... Warning: String offset cast occurred ... php: Zend/zend_API.c:126: const char *zend_get_type_by_const(int): Assertion `0' failed. ==ABORTING (SIGABRT) ``` ## 원인 $expr[$offset] 형태의 배열 오프셋 읽기에서 컨테이너가 배열/문자열/객체가 아니면, 읽기 경로는 zend_execute.c:3206 의 에러 리포팅 분기로 들어가 zend_zval_value_name(container) 를 호출한다. 이 함수는 다시 zend_get_type_by_const(Z_TYPE_P(container)) 를 호출하는데, PoC 가 만든 컨테이너의 타입 태그가 switch 에 열거되지 않은 값이라 default: ZEND_UNREACHABLE()(zend_API.c:126)에 도달한다. **호출 지점 (Zend/zend_execute.c:3206)** — 컨테이너가 배열이 아닐 때의 경고 경로: ```c 3202 zend_error(E_WARNING, "Cannot use %s as array", zend_zval_type_name(container)); 3203 } 3204 if (!is_list && type != BP_VAR_IS) { 3205 zend_error(E_WARNING, "Trying to access array offset on %s", >> 3206 zend_zval_value_name(container)); 3207 } 3208 ZVAL_NULL(result); 3209 } 3210 } ``` **zend_zval_value_name (Zend/zend_API.c:147)** — 타입 이름을 얻기 위해 zend_get_type_by_const 로 넘긴다: ```c 143 } else if (Z_TYPE_P(arg) == IS_TRUE) { 144 return "true"; 145 } 146 >> 147 return zend_get_type_by_const(Z_TYPE_P(arg)); 148 } 149 150 ZEND_API const char *zend_zval_type_name(const zval *arg) 151 { ``` **zend_get_type_by_const (Zend/zend_API.c:126)** — switch 에 없는 타입은 default: ZEND_UNREACHABLE() 로 떨어진다: ```c 116 case IS_ITERABLE: 117 return "iterable"; 118 case IS_ARRAY: 119 return "array"; 120 case IS_VOID: 121 return "void"; 122 case IS_MIXED: 123 return "mixed"; 124 case _IS_NUMBER: 125 return "int|float"; >> 126 default: ZEND_UNREACHABLE(); 127 } 128 } 129 /* }}} */ 130 131 ZEND_API const char *zend_zval_value_name(const zval *arg) 132 { 133 ZVAL_DEREF(arg); 134 135 if (Z_ISUNDEF_P(arg)) { 136 return "null"; ``` - 깨진 불변식: zend_get_type_by_const/zend_zval_value_name 은 입력 zval 의 타입 태그가 switch 가 열거한 값 중 하나라고 전제한다. 이 전제는 호출자(zend_fetch_dimension_address_read)가 항상 정상 타입의 container 만 넘긴다는 상류 계약에 의존한다. - PoC 는 그 계약을 깨는 container — 정상 타입 태그를 갖지 않는 임시 zval — 을 3206 에러 경로로 흘려보낸다. 따라서 결함은 이름 조회 함수가 아니라, 비정상 타입 container 를 이 경로로 흘려보낸 상류(피연산자 생성·임시 스택 관리)에 있다. 크래시 지점은 증상이고 결함은 상류다. ## 트리거 PoC(pocs/04_3e4b59e314ffb841.php, 256 bytes) 원문: ```php <?php switch ( sprintf ( true , 'b' ) [ 2.2 ] = "\143" ) { } assert ( $ { function ( ) { } } [ function ( ) : void { try { $ { '1!' } [ ( integer ) fn & ( ) =>ire ( ( object ) fn & ( ) => re + "ss\n" ) ] ; } finally { r ?-> db ; } } ] ) ; \400\40\ \u ; ``` - 문법적으로는 통과하지만 의미적으로 극도로 비정상적인 중첩 표현식들의 집합이다. 실행 중 다음 경고가 먼저 출력된다: non-canonical cast, octal overflow \400 > \377, backtick deprecated, String offset cast occurred. - 크래시를 일으키는 핵심 구조는 <임시식>[<오프셋>] 형태의 배열 오프셋 읽기다. VM 핸들러가 ZEND_FETCH_DIM_R_SPEC_TMPVAR_TMP_HANDLER 인 점에서, 컨테이너가 변수가 아니라 임시 계산 결과(TMPVAR)임을 알 수 있다. PoC 안의 후보: - sprintf(true,'b')[2.2] — 함수 반환값(임시 문자열)에 대한 float 오프셋 읽기 → 로그의 String offset cast occurred 경고와 부합. - ${ function(){} }[ ... ] / ${'1!'}[ ... ] — 변수-변수(${expr})로 만든 컨테이너에 대한 오프셋 접근. ${closure} 처럼 정상 변수명으로 환원되지 않는 표현이 컨테이너가 된다. - 오프셋식 자체가 ( integer ) fn & ( ) => ..., ( object ) fn & ( ) => re + "ss\n" 처럼 캐스트·화살표 함수·참조(&)·미정의 심볼이 뒤엉킨 비정상식이다. - 이 중첩식들의 평가가 정상 타입 태그를 갖지 않는 임시 zval 을 컨테이너로 만들어 zend_fetch_dimension_address_read 의 에러 경로(3206)로 흘려보내는 것이 트리거다. 어느 부분식이 최종 오염 컨테이너를 만드는지는 gdb 에서 3206 의 container 를 브레이크해 Z_TYPE_P 를 확인하면 확정된다. ## 영향 - 디버그 빌드(--enable-debug): ZEND_UNREACHABLE 이 abort() 이므로 DoS(크래시)다. - 릴리스 빌드(NDEBUG): ZEND_UNREACHABLE 이 __builtin_unreachable() 로 바뀐다. 옵티마이저가 이 분기를 도달 불가로 가정하므로, 실제로 도달하면 정의되지 않은 동작(잘못된 분기나 메모리 접근)이 될 수 있다. 릴리스에서의 실제 영향은 별도의 릴리스 ASan 빌드로 확인이 필요하다. - 정리하면, 확정된 것은 디버그 전용 도달 불가 위반(타입 매핑 누락 또는 정규화되지 않은 타입 유입)이다. 릴리스 메모리 취약점 여부는 미확정이지만, ZEND_UNREACHABLE 이 실제로 도달 가능하다는 사실 자체가 상류에 보고할 만한 엔진 불변식 버그다. ## 재현 방법 ```bash git clone https://github.com/php/php-src.git php-src && cd php-src git checkout 38956a3 && ./buildconf --force CC=clang CFLAGS="-g -O1 -fsanitize=address -fno-omit-frame-pointer" LDFLAGS="-fsanitize=address" \ ./configure --disable-all --enable-cli --enable-debug --enable-tokenizer --enable-mbstring make -j$(nproc) USE_ZEND_ALLOC=0 ASAN_OPTIONS='symbolize=1:handle_segv=1' sapi/cli/php pocs/04_3e4b59e314ffb841.php ``` **--enable-debug 는 필수다.** 없으면 UNREACHABLE 이 컴파일아웃되어 재현되지 않는다. ### PHP Version ```plain PHP 8.6.0-dev (master, commit 38956a3f3eeb9d878d578efa4163b74fa20ebbfb) ``` ### Operating System _No response_

« previous php.bugs (#252847) next »