Linux의 버퍼와 OP-TEE의 메모리 참조 연결
사용자 가상 주소, Linux의 tee_shm 및 커널 가상 주소, 전송 ABI의 물리 주소 또는 cookie/FF-A handle, OP-TEE의 매핑은 같은 버퍼를 서로 다르게 표현할 수 있습니다. 주소 숫자가 같다는 가정을 두지 않고 offset·size·등록 수명을 함께 확인해야 합니다. 공유 메모리의 내용은 비보안 쪽이 바꿀 수 있으므로 응답을 받았다는 사실만으로 신뢰하지 않습니다.
QUESTION
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는 신뢰하지 않고 읽는다.
STRUCTURE
빌드 설정으로 갈리는 세 memref 해석
CFG_CORE_FFA에서 FMEM과 TMEM/RMEM은 배타적 분기FMEM
global_id와 internal_offs로 FF-A mobj 조회
64비트 오프셋 offs_high:offs_low와 size 검사
TMEM
buf_ptr의 물리 주소 범위 또는 NONCONTIG 페이지 목록
조건에 맞는 mobj 생성 또는 참조
RMEM
shm_ref 쿠키로 등록 mobj 조회
offs + size 검사
CALL PATH
연속 단계가 아닌 조건부 선택
| 빌드/입력 | 선택되는 helper | 키와 범위 | 실패 또는 반환 |
|---|---|---|---|
CFG_CORE_FFA · FMEM | set_fmem_param() | global ID + internal offset → mobj; 64-bit offs + size | lookup 실패 BAD_PARAMETERS; overflow/out-of-range SECURITY |
| 비 FF-A · TMEM | set_tmem_param() | PA range, 선택적 NONCONTIG page list; NULL PA는 빈 memref | 허용 mobj가 없으면 BAD_PARAMETERS |
비 FF-A · CFG_CORE_DYN_SHM · RMEM | set_rmem_param() | cookie → 등록 mobj; offs + size | lookup 실패 BAD_PARAMETERS; overflow/out-of-range SECURITY |
copy_in_params()는 성공한 각 branch를 TEE_PARAM_TYPE_MEMREF_*로 옮기고 마지막에 ta_param->types를 채운다. 여기서 TA VA를 만든다고 해석하지 않는다.
STATE LEDGER
키, mobj, 참조의 소유자
| 상태 | 입력/생산자 | 검증/변경 | 수명 경계 |
|---|---|---|---|
saved_attr[n] | 비보안 optee_msg_param | READ_ONCE; META 거부; type mask로 branch 선택 | open/invoke 호출의 정리·출력에 다시 사용 |
TMEM mobj | PA/선택적 page list | noncontig 동적 객체 또는 허용된 contiguous 객체 참조 | 이번 parameter 사용 뒤 put 시도 |
RMEM mobj | 등록 SHM cookie | lookup에서 참조 획득; offs+size overflow/bounds | 등록 수명과 호출별 참조를 구별 |
FMEM mobj | FF-A global ID + internal offset | FF-A 객체 참조; 64-bit offs+size 검사 | FF-A 등록/회수와 호출별 참조를 구별 |
tee_ta_param | copy_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의 참조 누수를 공개했다.
VISUAL WALKTHROUGH
세 wire-format 필드가 뜻하는 주소
| 형식 | wire-format 입력 | secure 변환 결과 | 특수 조건 |
|---|---|---|---|
| TMEM | buf_ptr · size · shm_ref | PA가 허용된 mobj 안에 있으면 그 offset; NONCONTIG이면 page list에서 객체 생성 | buf_ptr=0이면 mobj=NULL, offs=size=0 |
| RMEM | shm_ref · offs · size | 등록 cookie lookup으로 얻은 mobj의 부분 범위 | CFG_CORE_DYN_SHM인 비 FF-A 빌드만 |
| FMEM | global_id · internal_offs · offs_low/high · size | FF-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 전처리 분기를 함께 본다.
UPSTREAM SOURCE
원본 코드
아래 코드는 OP-TEE OS 4.10.0의 core/tee/entry_std.c에서 160-248줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.
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(¶ms[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(¶ms[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(¶ms[n].u.rmem,
246 &ta_param->u[n].mem);
247 if (res)
248 return res;
READING CONTRACT
원본 발췌에서 보이는 것과 보이지 않는 것
원본 코드 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의 구현이므로, 모든 속성 조합에서 참조를 정상적으로 해제한다고 해석하면 안 된다.
RELATED SOURCE
함께 읽어야 하는 원본 코드
두 추가 코드 조각은 같은 고정 commit의 원문이다. 첫 조각의 cleanup은 4.10.0에서 모든 attr flag 조합을 제대로 처리하지 못한다. 공식 보안 권고와 함께 읽는다.
01. 실패·반환 시 정리 시도와 4.10.0 누수 경계
core/tee/entry_std.c 254-330줄이다.
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줄이다.
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:
DETAILS
입력 검사와 TA 사용 시점을 분리
type을 읽되 다른 attr bit를 지워 버리지 않는다
saved_attr[n]은 READ_ONCE로 읽고 META를 거부한다. 분기용 attr에는 TYPE_MASK를 적용하지만 TMEM helper에는 원래 saved_attr[n]를 넘겨 NONCONTIG flag를 본다. 이 함수가 모든 나머지 bit를 0으로 검증한다고 서술하지 않는다.
TMEM은 물리주소 하나만 쓰지 않는다
비 FF-A TMEM은 NULL memref, NONCONTIG page list, reserved contiguous SHM, SDP/protected memory의 분기가 있다. page list와 물리 범위의 검증 지점이 다르다. shm_ref는 모든 TMEM에서 등록 cookie 조회에 쓰이는 값이 아니다.
RMEM/FMEM은 획득한 참조와 bounds를 기록한다
각 helper는 cookie/ID로 mobj를 얻은 뒤 ADD_OVERFLOW(offs,size)와 mobj size를 검사한다. 검증 실패 뒤에도 얻은 참조를 정리해야 하므로 helper 반환값만으로 refcount 상태를 판단하지 않는다.
TA mapping은 다음 단계다
이 함수는 tee_ta_param을 만들고 반환한다. 호출자는 open/invoke를 진행하고 output/value size를 복사한 뒤 cleanup을 시도한다. 사용자 TA 가상주소를 항상 이 함수에서 바로 만든다는 화살표는 코드와 맞지 않는다.
IMPLEMENTATION NOTES
참조 수명에서 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()와 동일한 함수가 아니다.
DEEP ANALYSIS
조건부 경로와 보장 범위를 소스별로 확인
| 분기 | 입력/객체 | 이 함수가 실제로 확인하는 것 | 이 함수만으로 주장할 수 없는 것 |
|---|---|---|---|
CFG_CORE_FFA | FMEM global ID, internal offset | 특수 NULL 조합, mobj lookup, offs+size overflow 및 size | 모든 TA의 최종 VA/권한·cache 동기화 완료 |
| 비 FF-A TMEM | PA 또는 NONCONTIG page list | NULL 처리, 허용된 mobj/range 또는 page list 변환 | 모든 TMEM이 사전 등록 cookie를 lookup한다는 주장 |
| 비 FF-A + DYN_SHM RMEM | 등록 cookie, offs, size | ref 획득 및 ADD_OVERFLOW 후 mobj size | 4.10.0 모든 flag 조합에서 ref가 정리된다는 주장 |
| 설정/flag | 소스의 실제 영향 |
|---|---|
CFG_CORE_FFA | FMEM case를 포함하고 TMEM/RMEM case는 제외한다. FMEM type 값은 RMEM의 alias다. |
CFG_CORE_DYN_SHM | 비 FF-A branch의 RMEM case와 등록 SHM lookup을 포함한다. |
CFG_CORE_RESERVED_SHM | TMEM에서 contiguous shm_mobj range 시도를 포함한다. |
CFG_SECURE_DATA_PATH | TMEM의 SDP mobj range 시도를 추가한다. |
OPTEE_MSG_ATTR_NONCONTIG | TMEM의 page-list 경로를 선택한다. 4.10.0 cleanup의 unmasked switch와 함께 검토한다. |
변환 원본, 비연속 TMEM page-list 파서, 메시지 형식 및 4.10.0에 해당하는 공식 권고를 함께 읽는다. 보호 메모리와 user TA 매핑의 자세한 정책은 이 entry 함수 밖에 있으므로 여기서 일괄 보장하지 않는다.
OBJECT LIFETIME
등록 수명과 한 번의 호출 참조
| 대상 | 생산자 | 이 글에서의 수명 판단 |
|---|---|---|
optee_msg_param | 비보안 caller의 message | 입력 attr/union은 호출 중에도 신뢰하지 않음; saved_attr과 변환값으로 사용 |
| TMEM mobj | page-list 변환 또는 허용된 기존 mobj | 호출별 참조·객체가 있으며 사전 등록 cookie와 항상 같지 않음 |
| RMEM mobj | 비 FF-A 등록 SHM cookie table | 등록 수명과 별개로 조회 시 참조 증가; put 누락 여부 확인 |
| FMEM mobj | FF-A global ID 객체 | FF-A active/inactive 및 회수 규칙; RMEM release 함수와 별도 |
tee_ta_param.mem | copy_in_params() | mobj/offs/size를 다음 TA 호출에 전달; TA VA 생성은 여기의 반환 결과와 다름 |
FAILURE PATH
실패 지점과 반환 코드
| 조건 | 관측 | 참조 수명 점검 |
|---|---|---|
| RMEM/FMEM cookie 또는 ID lookup 실패 | TEE_ERROR_BAD_PARAMETERS | 참조를 얻지 못했음 |
| RMEM/FMEM offs+size overflow 또는 mobj 초과 | TEE_ERROR_SECURITY | helper가 획득한 mobj는 호출 경계에서 정리해야 함 |
| TMEM PA가 허용 객체 밖/비연속 list 실패 | TEE_ERROR_BAD_PARAMETERS | 생성된 mobj 또는 이전 param의 참조를 함께 확인 |
| 4.10.0 NONCONTIG attr 후 반복 호출 | secure heap의 참조 누수 가능 | 공식 권고의 cleanup 누락 조건과 실제 빌드 비교 |
EVIDENCE
구성별로 같은 필드를 기록
| 관측 위치 | 기록할 값 | 구분할 질문 |
|---|---|---|
| 빌드 설정 | 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 index | FMEM/RMEM alias와 NONCONTIG flag를 구분했나? |
| 각 helper | global ID/cookie 또는 PA, offs, size, mobj size, 결과 | lookup 실패와 범위 실패가 어디서 갈렸나? |
| 호출 및 정리 | mobj refcount, cleanup_shm_refs()의 switch 진입, 반복 호출 뒤 heap | 4.10.0의 알려진 ref 누수 조건인가? |
nm 출력에서는 C 함수 선언 전체가 아닌 실제 심벌명으로 검색한다. 최적화로 심벌이 사라질 수 있으므로 디버그 정보와 소스 중단점도 함께 확인한다.
LAB
격리된 시험 환경에서 세 경로 분리
- 01전처리 분기 확인
테스트 빌드의
CFG_CORE_FFA와CFG_CORE_DYN_SHM값을 먼저 적고 FMEM 또는 TMEM/RMEM 중 실제 포함된 case만 추적한다. - 02범위 경계 시험
RMEM 또는 FMEM에서 정상 offs+size, 끝 경계, overflow 입력을 별도로 보내
BAD_PARAMETERS와SECURITY반환을 구분한다. - 034.10.0 참조 누수 관찰
격리된 QEMU/테스트 보드에서 NONCONTIG TMEM 반복 호출과 cleanup switch/refcount를 관찰한다.
PRIMARY REFERENCES