OP-TEE OS · source analysis

TMEM/RMEM/FMEM shared memory 변환

빌드 설정에 따라 TMEM·RMEM·FMEM이 메모리 객체를 찾는 방법과 호출이 끝난 뒤 참조를 해제하는 과정을 살펴봅니다.

4.10.0은 소스 분석 기준이며 최신 보안 버전 권고가 아니다. 비 FF-A 구성에서 비연속 공유 메모리를 정리할 때 참조를 해제하지 못하는 결함이 있다. OP-TEE 공식 보안 공지는 이 결함의 수정 버전을 4.11.0 이상으로 명시한다.

Series
08 / 12
Baseline
4.10.0
Commit
753afbbee168
Source
core/tee/entry_std.c:160-248 (발췌 범위)

Linux의 버퍼와 OP-TEE의 메모리 참조 연결

사용자 가상 주소, Linux의 tee_shm 및 커널 가상 주소, 전송 ABI의 물리 주소 또는 cookie/FF-A handle, OP-TEE의 매핑은 같은 버퍼를 서로 다르게 표현할 수 있습니다. 주소 숫자가 같다는 가정을 두지 않고 offset·size·등록 수명을 함께 확인해야 합니다. 공유 메모리의 내용은 비보안 쪽이 바꿀 수 있으므로 응답을 받았다는 사실만으로 신뢰하지 않습니다.

양쪽 코드를 이어서 읽기

TMEM·RMEM·FMEM은 같은 변환 경로인가?

아니다. OP-TEE 4.10.0의 copy_in_params()는 CFG_CORE_FFA 빌드에서 FMEM을, 비 FF-A 빌드에서 TMEM과 조건부 RMEM을 선택한다. 서로 다른 입력을 순서대로 통과하는 파이프라인이 아니다.

변환 결과는 먼저 tee_ta_param의 mem.mobj, offs, size, 방향 type이다. TA의 사용자 가상주소 매핑은 뒤의 TA 실행 경로에서 결정한다. 비보안 메시지의 주소와 size는 신뢰하지 않고 읽는다.

빌드 설정으로 갈리는 세 memref 해석

그림 1. CFG_CORE_FFA에서 FMEM과 TMEM/RMEM은 배타적 분기
copy_in_params()num_params ≤ 4 · saved_attr[n] 읽기 · META 거부 · TYPE_MASK로 분기
CFG_CORE_FFA 사용

FMEM

global_id와 internal_offs로 FF-A mobj 조회

64비트 오프셋 offs_high:offs_low와 size 검사

CFG_CORE_FFA 사용 안 함

TMEM

buf_ptr의 물리 주소 범위 또는 NONCONTIG 페이지 목록

조건에 맞는 mobj 생성 또는 참조

비 FF-A · CFG_CORE_DYN_SHM 사용

RMEM

shm_ref 쿠키로 등록 mobj 조회

offs + size 검사

변환 결과: tee_ta_parammobj · offs · size · 방향 type → 뒤의 open/invoke 경로

switch 원본 · FMEM/RMEM type alias · 세 helper

연속 단계가 아닌 조건부 선택

빌드/입력선택되는 helper키와 범위실패 또는 반환
CFG_CORE_FFA · FMEMset_fmem_param()global ID + internal offset → mobj; 64-bit offs + sizelookup 실패 BAD_PARAMETERS; overflow/out-of-range SECURITY
비 FF-A · TMEMset_tmem_param()PA range, 선택적 NONCONTIG page list; NULL PA는 빈 memref허용 mobj가 없으면 BAD_PARAMETERS
비 FF-A · CFG_CORE_DYN_SHM · RMEMset_rmem_param()cookie → 등록 mobj; offs + sizelookup 실패 BAD_PARAMETERS; overflow/out-of-range SECURITY

copy_in_params()는 성공한 각 branch를 TEE_PARAM_TYPE_MEMREF_*로 옮기고 마지막에 ta_param->types를 채운다. 여기서 TA VA를 만든다고 해석하지 않는다.

키, mobj, 참조의 소유자

상태입력/생산자검증/변경수명 경계
saved_attr[n]비보안 optee_msg_paramREAD_ONCE; META 거부; type mask로 branch 선택open/invoke 호출의 정리·출력에 다시 사용
TMEM mobjPA/선택적 page listnoncontig 동적 객체 또는 허용된 contiguous 객체 참조이번 parameter 사용 뒤 put 시도
RMEM mobj등록 SHM cookielookup에서 참조 획득; offs+size overflow/bounds등록 수명과 호출별 참조를 구별
FMEM mobjFF-A global ID + internal offsetFF-A 객체 참조; 64-bit offs+size 검사FF-A 등록/회수와 호출별 참조를 구별
tee_ta_paramcopy_in_params()mobj, offs, size, direction type이후 TA call 및 cleanup 경계

4.10.0의 실제 cleanup_shm_refs()가 모든 attr flag 조합에서 참조를 내려놓는다고 가정하면 안 된다. 공식 GHSA-c7j8-fgqw-rcgp는 비 FF-A noncontiguous memref의 참조 누수를 공개했다.

세 wire-format 필드가 뜻하는 주소

형식wire-format 입력secure 변환 결과특수 조건
TMEMbuf_ptr · size · shm_refPA가 허용된 mobj 안에 있으면 그 offset; NONCONTIG이면 page list에서 객체 생성buf_ptr=0이면 mobj=NULL, offs=size=0
RMEMshm_ref · offs · size등록 cookie lookup으로 얻은 mobj의 부분 범위CFG_CORE_DYN_SHM인 비 FF-A 빌드만
FMEMglobal_id · internal_offs · offs_low/high · sizeFF-A mobj의 부분 범위invalid global ID와 size 0 조합은 NULL memref

같은 숫자 0x5–0x7이 비 FF-A에서는 RMEM, FF-A에서는 FMEM을 가리킨다. 따라서 attr 값만 보고 union의 rmem/fmem 필드를 선택할 수 없다. optee_msg.h의 alias와 CFG_CORE_FFA 전처리 분기를 함께 본다.

원본 코드

아래 코드는 OP-TEE OS 4.10.0의 core/tee/entry_std.c에서 160-248줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.

commit753afbbee1682f5d16fd30e87b31058a4fd4f4b8anchor · L184static TEE_Result copy_in_params(const struct optee_msg_param *params
160	size_t req_size = 0;
161	uint64_t shm_ref = READ_ONCE(rmem->shm_ref);
162	size_t sz = READ_ONCE(rmem->size);
163
164	mem->mobj = mobj_reg_shm_get_by_cookie(shm_ref);
165	if (!mem->mobj)
166		return TEE_ERROR_BAD_PARAMETERS;
167
168	mem->offs = READ_ONCE(rmem->offs);
169	mem->size = sz;
170
171	/*
172	 * Check that the supplied offset and size is covered by the
173	 * previously verified MOBJ.
174	 */
175	if (ADD_OVERFLOW(mem->offs, mem->size, &req_size) ||
176	    mem->mobj->size < req_size)
177		return TEE_ERROR_SECURITY;
178
179	return TEE_SUCCESS;
180}
181#endif /*CFG_CORE_DYN_SHM*/
182#endif /*!CFG_CORE_FFA*/
183
184static TEE_Result copy_in_params(const struct optee_msg_param *params,
185				 uint32_t num_params,
186				 struct tee_ta_param *ta_param,
187				 uint64_t *saved_attr)
188{
189	TEE_Result res;
190	size_t n;
191	uint8_t pt[TEE_NUM_PARAMS] = { 0 };
192
193	if (num_params > TEE_NUM_PARAMS)
194		return TEE_ERROR_BAD_PARAMETERS;
195
196	memset(ta_param, 0, sizeof(*ta_param));
197
198	for (n = 0; n < num_params; n++) {
199		uint32_t attr;
200
201		saved_attr[n] = READ_ONCE(params[n].attr);
202
203		if (saved_attr[n] & OPTEE_MSG_ATTR_META)
204			return TEE_ERROR_BAD_PARAMETERS;
205
206		attr = saved_attr[n] & OPTEE_MSG_ATTR_TYPE_MASK;
207		switch (attr) {
208		case OPTEE_MSG_ATTR_TYPE_NONE:
209			pt[n] = TEE_PARAM_TYPE_NONE;
210			break;
211		case OPTEE_MSG_ATTR_TYPE_VALUE_INPUT:
212		case OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT:
213		case OPTEE_MSG_ATTR_TYPE_VALUE_INOUT:
214			pt[n] = TEE_PARAM_TYPE_VALUE_INPUT + attr -
215				OPTEE_MSG_ATTR_TYPE_VALUE_INPUT;
216			ta_param->u[n].val.a = READ_ONCE(params[n].u.value.a);
217			ta_param->u[n].val.b = READ_ONCE(params[n].u.value.b);
218			break;
219#ifdef CFG_CORE_FFA
220		case OPTEE_MSG_ATTR_TYPE_FMEM_INPUT:
221		case OPTEE_MSG_ATTR_TYPE_FMEM_OUTPUT:
222		case OPTEE_MSG_ATTR_TYPE_FMEM_INOUT:
223			res = set_fmem_param(&params[n].u.fmem,
224					     &ta_param->u[n].mem);
225			if (res)
226				return res;
227			pt[n] = TEE_PARAM_TYPE_MEMREF_INPUT + attr -
228				OPTEE_MSG_ATTR_TYPE_FMEM_INPUT;
229			break;
230#else /*!CFG_CORE_FFA*/
231		case OPTEE_MSG_ATTR_TYPE_TMEM_INPUT:
232		case OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT:
233		case OPTEE_MSG_ATTR_TYPE_TMEM_INOUT:
234			res = set_tmem_param(&params[n].u.tmem, saved_attr[n],
235					     &ta_param->u[n].mem);
236			if (res)
237				return res;
238			pt[n] = TEE_PARAM_TYPE_MEMREF_INPUT + attr -
239				OPTEE_MSG_ATTR_TYPE_TMEM_INPUT;
240			break;
241#ifdef CFG_CORE_DYN_SHM
242		case OPTEE_MSG_ATTR_TYPE_RMEM_INPUT:
243		case OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT:
244		case OPTEE_MSG_ATTR_TYPE_RMEM_INOUT:
245			res = set_rmem_param(&params[n].u.rmem,
246					     &ta_param->u[n].mem);
247			if (res)
248				return res;

원본 발췌에서 보이는 것과 보이지 않는 것

원본 코드 160–248행은 고정 commit 753afbbee1682f5d16fd30e87b31058a4fd4f4b8과 행·내용을 대조했다. 앞부분 160–180행은 set_rmem_param()의 끝이고, copy_in_params()는 184행부터다. 219–253행의 #ifdef CFG_CORE_FFA/#else를 단일 호출 흐름으로 읽지 않는다.

뒤의 참조 정리·출력 복사 함수와 open/invoke 호출자까지 봐야 참조를 해제하고 출력 크기를 돌려주는 시점을 알 수 있다. 아래 코드는 결함이 수정되기 전인 4.10.0의 구현이므로, 모든 속성 조합에서 참조를 정상적으로 해제한다고 해석하면 안 된다.

함께 읽어야 하는 원본 코드

두 추가 코드 조각은 같은 고정 commit의 원문이다. 첫 조각의 cleanup은 4.10.0에서 모든 attr flag 조합을 제대로 처리하지 못한다. 공식 보안 권고와 함께 읽는다.

01. 실패·반환 시 정리 시도와 4.10.0 누수 경계

core/tee/entry_std.c 254-330줄이다.

filecore/tee/entry_std.canchor · L264static void cleanup_shm_refs
254		default:
255			return TEE_ERROR_BAD_PARAMETERS;
256		}
257	}
258
259	ta_param->types = TEE_PARAM_TYPES(pt[0], pt[1], pt[2], pt[3]);
260
261	return TEE_SUCCESS;
262}
263
264static void cleanup_shm_refs(const uint64_t *saved_attr,
265			     struct tee_ta_param *param, uint32_t num_params)
266{
267	size_t n;
268
269	for (n = 0; n < MIN((unsigned int)TEE_NUM_PARAMS, num_params); n++) {
270		switch (saved_attr[n]) {
271		case OPTEE_MSG_ATTR_TYPE_TMEM_INPUT:
272		case OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT:
273		case OPTEE_MSG_ATTR_TYPE_TMEM_INOUT:
274#ifdef CFG_CORE_DYN_SHM
275		case OPTEE_MSG_ATTR_TYPE_RMEM_INPUT:
276		case OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT:
277		case OPTEE_MSG_ATTR_TYPE_RMEM_INOUT:
278#endif
279			mobj_put(param->u[n].mem.mobj);
280			break;
281		default:
282			break;
283		}
284	}
285}
286
287static void copy_out_param(struct tee_ta_param *ta_param, uint32_t num_params,
288			   struct optee_msg_param *params, uint64_t *saved_attr)
289{
290	size_t n;
291
292	for (n = 0; n < num_params; n++) {
293		switch (TEE_PARAM_TYPE_GET(ta_param->types, n)) {
294		case TEE_PARAM_TYPE_MEMREF_OUTPUT:
295		case TEE_PARAM_TYPE_MEMREF_INOUT:
296			switch (saved_attr[n] & OPTEE_MSG_ATTR_TYPE_MASK) {
297			case OPTEE_MSG_ATTR_TYPE_TMEM_OUTPUT:
298			case OPTEE_MSG_ATTR_TYPE_TMEM_INOUT:
299				params[n].u.tmem.size = ta_param->u[n].mem.size;
300				break;
301			case OPTEE_MSG_ATTR_TYPE_RMEM_OUTPUT:
302			case OPTEE_MSG_ATTR_TYPE_RMEM_INOUT:
303				params[n].u.rmem.size = ta_param->u[n].mem.size;
304				break;
305			default:
306				break;
307			}
308			break;
309		case TEE_PARAM_TYPE_VALUE_OUTPUT:
310		case TEE_PARAM_TYPE_VALUE_INOUT:
311			params[n].u.value.a = ta_param->u[n].val.a;
312			params[n].u.value.b = ta_param->u[n].val.b;
313			break;
314		default:
315			break;
316		}
317	}
318}
319
320/*
321 * Extracts mandatory parameter for open session.
322 *
323 * Returns
324 * false : mandatory parameter wasn't found or malformatted
325 * true  : paramater found and OK
326 */
327static TEE_Result get_open_session_meta(size_t num_params,
328					struct optee_msg_param *params,
329					size_t *num_meta, TEE_UUID *uuid,
330					TEE_Identity *clnt_id)

02. registered-memory cookie와 offset을 mobj로 변환

core/tee/entry_std.c 147-221줄이다.

filecore/tee/entry_std.canchor · L157static TEE_Result set_rmem_param
147
148		mobj_put(rmobj);
149		if (rc)
150			return TEE_SUCCESS;
151	}
152
153	return TEE_ERROR_BAD_PARAMETERS;
154}
155
156#ifdef CFG_CORE_DYN_SHM
157static TEE_Result set_rmem_param(const struct optee_msg_param_rmem *rmem,
158				 struct param_mem *mem)
159{
160	size_t req_size = 0;
161	uint64_t shm_ref = READ_ONCE(rmem->shm_ref);
162	size_t sz = READ_ONCE(rmem->size);
163
164	mem->mobj = mobj_reg_shm_get_by_cookie(shm_ref);
165	if (!mem->mobj)
166		return TEE_ERROR_BAD_PARAMETERS;
167
168	mem->offs = READ_ONCE(rmem->offs);
169	mem->size = sz;
170
171	/*
172	 * Check that the supplied offset and size is covered by the
173	 * previously verified MOBJ.
174	 */
175	if (ADD_OVERFLOW(mem->offs, mem->size, &req_size) ||
176	    mem->mobj->size < req_size)
177		return TEE_ERROR_SECURITY;
178
179	return TEE_SUCCESS;
180}
181#endif /*CFG_CORE_DYN_SHM*/
182#endif /*!CFG_CORE_FFA*/
183
184static TEE_Result copy_in_params(const struct optee_msg_param *params,
185				 uint32_t num_params,
186				 struct tee_ta_param *ta_param,
187				 uint64_t *saved_attr)
188{
189	TEE_Result res;
190	size_t n;
191	uint8_t pt[TEE_NUM_PARAMS] = { 0 };
192
193	if (num_params > TEE_NUM_PARAMS)
194		return TEE_ERROR_BAD_PARAMETERS;
195
196	memset(ta_param, 0, sizeof(*ta_param));
197
198	for (n = 0; n < num_params; n++) {
199		uint32_t attr;
200
201		saved_attr[n] = READ_ONCE(params[n].attr);
202
203		if (saved_attr[n] & OPTEE_MSG_ATTR_META)
204			return TEE_ERROR_BAD_PARAMETERS;
205
206		attr = saved_attr[n] & OPTEE_MSG_ATTR_TYPE_MASK;
207		switch (attr) {
208		case OPTEE_MSG_ATTR_TYPE_NONE:
209			pt[n] = TEE_PARAM_TYPE_NONE;
210			break;
211		case OPTEE_MSG_ATTR_TYPE_VALUE_INPUT:
212		case OPTEE_MSG_ATTR_TYPE_VALUE_OUTPUT:
213		case OPTEE_MSG_ATTR_TYPE_VALUE_INOUT:
214			pt[n] = TEE_PARAM_TYPE_VALUE_INPUT + attr -
215				OPTEE_MSG_ATTR_TYPE_VALUE_INPUT;
216			ta_param->u[n].val.a = READ_ONCE(params[n].u.value.a);
217			ta_param->u[n].val.b = READ_ONCE(params[n].u.value.b);
218			break;
219#ifdef CFG_CORE_FFA
220		case OPTEE_MSG_ATTR_TYPE_FMEM_INPUT:
221		case OPTEE_MSG_ATTR_TYPE_FMEM_OUTPUT:

입력 검사와 TA 사용 시점을 분리

01

type을 읽되 다른 attr bit를 지워 버리지 않는다

saved_attr[n]은 READ_ONCE로 읽고 META를 거부한다. 분기용 attr에는 TYPE_MASK를 적용하지만 TMEM helper에는 원래 saved_attr[n]를 넘겨 NONCONTIG flag를 본다. 이 함수가 모든 나머지 bit를 0으로 검증한다고 서술하지 않는다.

02

TMEM은 물리주소 하나만 쓰지 않는다

비 FF-A TMEM은 NULL memref, NONCONTIG page list, reserved contiguous SHM, SDP/protected memory의 분기가 있다. page list와 물리 범위의 검증 지점이 다르다. shm_ref는 모든 TMEM에서 등록 cookie 조회에 쓰이는 값이 아니다.

03

RMEM/FMEM은 획득한 참조와 bounds를 기록한다

각 helper는 cookie/ID로 mobj를 얻은 뒤 ADD_OVERFLOW(offs,size)와 mobj size를 검사한다. 검증 실패 뒤에도 얻은 참조를 정리해야 하므로 helper 반환값만으로 refcount 상태를 판단하지 않는다.

04

TA mapping은 다음 단계다

이 함수는 tee_ta_param을 만들고 반환한다. 호출자는 open/invoke를 진행하고 output/value size를 복사한 뒤 cleanup을 시도한다. 사용자 TA 가상주소를 항상 이 함수에서 바로 만든다는 화살표는 코드와 맞지 않는다.

참조 수명에서 4.10.0의 알려진 결함

set_rmem_param()과 set_fmem_param()은 lookup 성공 뒤 범위 검사에 실패해도 이미 획득한 mem->mobj를 들고 반환한다. 호출 경계의 정리가 중요하다. 비 FF-A 등록 SHM의 cookie lookup은 호출별 참조를 획득하고, release는 남은 참조가 없어질 때까지 기다린다.

그러나 4.10.0 cleanup_shm_refs()는 saved_attr[n]를 TYPE_MASK 없이 switch한다. NONCONTIG처럼 다른 flag가 붙은 attr은 해당 TMEM/RMEM case와 일치하지 않아 mobj_put()를 놓칠 수 있다. OP-TEE 공식 보안 권고는 비 FF-A noncontiguous SHM 참조 누수를 확인하고 4.11.0 이상을 수정 버전으로 표시한다. 4.10.0 동작을 “정상 정리 보장”으로 도식화하지 않는다.

FF-A의 mobj_ffa_get_by_cookie()는 internal_offs를 검사하고 cookie의 active/inactive 객체를 처리한다. FF-A의 등록·회수 경계는 비 FF-A mobj_reg_shm_release_by_cookie()와 동일한 함수가 아니다.

조건부 경로와 보장 범위를 소스별로 확인

분기입력/객체이 함수가 실제로 확인하는 것이 함수만으로 주장할 수 없는 것
CFG_CORE_FFAFMEM global ID, internal offset특수 NULL 조합, mobj lookup, offs+size overflow 및 size모든 TA의 최종 VA/권한·cache 동기화 완료
비 FF-A TMEMPA 또는 NONCONTIG page listNULL 처리, 허용된 mobj/range 또는 page list 변환모든 TMEM이 사전 등록 cookie를 lookup한다는 주장
비 FF-A + DYN_SHM RMEM등록 cookie, offs, sizeref 획득 및 ADD_OVERFLOW 후 mobj size4.10.0 모든 flag 조합에서 ref가 정리된다는 주장
설정/flag소스의 실제 영향
CFG_CORE_FFAFMEM case를 포함하고 TMEM/RMEM case는 제외한다. FMEM type 값은 RMEM의 alias다.
CFG_CORE_DYN_SHM비 FF-A branch의 RMEM case와 등록 SHM lookup을 포함한다.
CFG_CORE_RESERVED_SHMTMEM에서 contiguous shm_mobj range 시도를 포함한다.
CFG_SECURE_DATA_PATHTMEM의 SDP mobj range 시도를 추가한다.
OPTEE_MSG_ATTR_NONCONTIGTMEM의 page-list 경로를 선택한다. 4.10.0 cleanup의 unmasked switch와 함께 검토한다.

변환 원본, 비연속 TMEM page-list 파서, 메시지 형식 및 4.10.0에 해당하는 공식 권고를 함께 읽는다. 보호 메모리와 user TA 매핑의 자세한 정책은 이 entry 함수 밖에 있으므로 여기서 일괄 보장하지 않는다.

등록 수명과 한 번의 호출 참조

대상생산자이 글에서의 수명 판단
optee_msg_param비보안 caller의 message입력 attr/union은 호출 중에도 신뢰하지 않음; saved_attr과 변환값으로 사용
TMEM mobjpage-list 변환 또는 허용된 기존 mobj호출별 참조·객체가 있으며 사전 등록 cookie와 항상 같지 않음
RMEM mobj비 FF-A 등록 SHM cookie table등록 수명과 별개로 조회 시 참조 증가; put 누락 여부 확인
FMEM mobjFF-A global ID 객체FF-A active/inactive 및 회수 규칙; RMEM release 함수와 별도
tee_ta_param.memcopy_in_params()mobj/offs/size를 다음 TA 호출에 전달; TA VA 생성은 여기의 반환 결과와 다름

실패 지점과 반환 코드

조건관측참조 수명 점검
RMEM/FMEM cookie 또는 ID lookup 실패TEE_ERROR_BAD_PARAMETERS참조를 얻지 못했음
RMEM/FMEM offs+size overflow 또는 mobj 초과TEE_ERROR_SECURITYhelper가 획득한 mobj는 호출 경계에서 정리해야 함
TMEM PA가 허용 객체 밖/비연속 list 실패TEE_ERROR_BAD_PARAMETERS생성된 mobj 또는 이전 param의 참조를 함께 확인
4.10.0 NONCONTIG attr 후 반복 호출secure heap의 참조 누수 가능공식 권고의 cleanup 누락 조건과 실제 빌드 비교

구성별로 같은 필드를 기록

관측 위치기록할 값구분할 질문
빌드 설정CFG_CORE_FFA, CFG_CORE_DYN_SHM, CFG_CORE_RESERVED_SHM, CFG_SECURE_DATA_PATH어느 branch가 컴파일되었나?
copy_in_params()raw saved_attr, masked attr, param indexFMEM/RMEM alias와 NONCONTIG flag를 구분했나?
각 helperglobal ID/cookie 또는 PA, offs, size, mobj size, 결과lookup 실패와 범위 실패가 어디서 갈렸나?
호출 및 정리mobj refcount, cleanup_shm_refs()의 switch 진입, 반복 호출 뒤 heap4.10.0의 알려진 ref 누수 조건인가?

nm 출력에서는 C 함수 선언 전체가 아닌 실제 심벌명으로 검색한다. 최적화로 심벌이 사라질 수 있으므로 디버그 정보와 소스 중단점도 함께 확인한다.

격리된 시험 환경에서 세 경로 분리

  1. 01
    전처리 분기 확인

    테스트 빌드의 CFG_CORE_FFA와 CFG_CORE_DYN_SHM 값을 먼저 적고 FMEM 또는 TMEM/RMEM 중 실제 포함된 case만 추적한다.

  2. 02
    범위 경계 시험

    RMEM 또는 FMEM에서 정상 offs+size, 끝 경계, overflow 입력을 별도로 보내 BAD_PARAMETERS와 SECURITY 반환을 구분한다.

  3. 03
    4.10.0 참조 누수 관찰

    격리된 QEMU/테스트 보드에서 NONCONTIG TMEM 반복 호출과 cleanup switch/refcount를 관찰한다.

고정 버전 원본과 공식 권고