QUESTION
FIT image 검증은 어떤 실패를 거부하는가?
fit_image_verify()는 image data와 길이를 얻어 fit_image_verify_with_data()로 전달한다. 서명이 활성화된 빌드에서 이름에 @가 있으면 먼저 거부한다. 그 뒤 필수 signature, hash, 선택 signature, FDT 구조 오류는 서로 다른 분기로 처리된다. hash 통과는 출처 인증이 아니며, image 검증의 성공 반환과 configuration 서명 정책도 분리해서 읽어야 한다. boot/image-fit.c:1357–1419
BRANCH MAP
검증 결과가 갈라지는 지점
좁은 화면에서는 그림을 좌우로 밀어 모든 분기를 볼 수 있습니다.
고정 원문 1309–1419행을 도식화했다. 그림 원본 크게 열기.
DECISION TABLE
같은 “검증”이라는 말의 서로 다른 조건
| 검사 | 실행 조건 | 실패했을 때 | 근거 |
|---|---|---|---|
| image 이름 | CONFIG_FIT_SIGNATURE 활성이고 이름에 @ 포함 | data 취득 전 fit_image_verify()가 0 반환 | 1442–1462 |
| data/size | fit_image_get_data() | fit_image_verify()가 0 반환 | 1450–1462 |
| required signature | FIT_IMAGE_ENABLE_VERIFY이고 required key 정책 적용 | 오류 분기, 0 반환 | 1366–1372 |
| hash node | 순회한 hash node에서 알고리즘을 먼저 읽음; !tools_build()이고 ignore이면 digest 비교 생략 가능 | 알고리즘·값·digest 불일치면 0 반환 | 1321–1354 · 1383–1388 |
| 선택 signature | 검증 활성화 및 verify_all 조건 | “−” 출력 후 계속, 이 실패만으로 0 반환하지 않음 | 1389–1405 |
| FDT 순회 | 순회 결과가 truncated/badstructure | 오류 분기, 0 반환 | 1408–1419 |
RESULT BOUNDARY
성공 반환의 의미
fit_image_verify_with_data()의 1은 이 함수에서 활성화된 image 검사들을 통과했다는 뜻이다. 선택 signature 실패는 표시만 할 수 있고, hash의 ignore도 U-Boot 실행 조건에서 적용될 수 있다. 따라서 이 반환값 하나를 “FIT 전체 구성이 인증됐다”로 바꾸어 읽지 않는다. 선택된 configuration의 검증 호출은 !fit_uname 경로에서 FIT_IMAGE_ENABLE_VERIFY && images->verify일 때 같은 파일의 별도 지점 2138–2149행에서 확인한다.
required="image" 또는 required="conf" 정책이 파일에서 gd_fdt_blob()을 전달한다. control FDT 자체의 신뢰는 별도 부트 체인 조건이다.동일한 image hash들이 맞아도 구성 참조를 바꾸면 다른 조합이 될 수 있다. 구성 서명과 참조 image hash의 관계는 U-Boot 공식 설명의 범위이며, 이 글의 고정 image-fit.c에는 configuration 검증 호출만 보인다.
UPSTREAM SOURCE
원본 코드
아래 코드는 U-Boot v2026.07의 boot/image-fit.c에서 1411-1499줄을 그대로 가져온 것이다. 설명을 위해 실제 코드를 가짜 의사 코드로 바꾸지 않았다.
1411 }
1412
1413 return 1;
1414
1415error:
1416 printf(" error!\n%s for '%s' hash node in '%s' image node\n",
1417 err_msg, fit_get_name(fit, noffset, NULL),
1418 fit_get_name(fit, image_noffset, NULL));
1419 return 0;
1420}
1421
1422/**
1423 * fit_image_verify - verify data integrity
1424 * @fit: pointer to the FIT format image header
1425 * @image_noffset: component image node offset
1426 *
1427 * fit_image_verify() goes over component image hash nodes,
1428 * re-calculates each data hash and compares with the value stored in hash
1429 * node.
1430 *
1431 * returns:
1432 * 1, if all hashes are valid
1433 * 0, otherwise (or on error)
1434 */
1435int fit_image_verify(const void *fit, int image_noffset)
1436{
1437 const char *name = fit_get_name(fit, image_noffset, NULL);
1438 const void *data;
1439 size_t size;
1440 char *err_msg = "";
1441
1442 if (IS_ENABLED(CONFIG_FIT_SIGNATURE) && strchr(name, '@')) {
1443 /*
1444 * We don't support this since libfdt considers names with the
1445 * name root but different @ suffix to be equal
1446 */
1447 err_msg = "Node name contains @";
1448 goto err;
1449 }
1450 /* Get image data and data length */
1451 if (fit_image_get_data(fit, image_noffset, &data, &size)) {
1452 err_msg = "Can't get image data/size";
1453 goto err;
1454 }
1455
1456 return fit_image_verify_with_data(fit, image_noffset, gd_fdt_blob(),
1457 data, size);
1458
1459err:
1460 printf("error!\n%s in '%s' image node\n", err_msg,
1461 fit_get_name(fit, image_noffset, NULL));
1462 return 0;
1463}
1464
1465/**
1466 * fit_all_image_verify - verify data integrity for all images
1467 * @fit: pointer to the FIT format image header
1468 *
1469 * fit_all_image_verify() goes over all images in the FIT and
1470 * for every images checks if all it's hashes are valid.
1471 *
1472 * returns:
1473 * 1, if all hashes of all images are valid
1474 * 0, otherwise (or on error)
1475 */
1476int fit_all_image_verify(const void *fit)
1477{
1478 int images_noffset;
1479 int noffset;
1480 int ndepth;
1481 int count;
1482
1483 /* Find images parent node offset */
1484 images_noffset = fdt_path_offset(fit, FIT_IMAGES_PATH);
1485 if (images_noffset < 0) {
1486 printf("Can't find images parent node '%s' (%s)\n",
1487 FIT_IMAGES_PATH, fdt_strerror(images_noffset));
1488 return 0;
1489 }
1490
1491 /* Process all image subnodes, check hashes for each */
1492 printf("## Checking hash(es) for FIT Image at %08lx ...\n",
1493 (ulong)fit);
1494 for (ndepth = 0, count = 0,
1495 noffset = fdt_next_node(fit, images_noffset, &ndepth);
1496 (noffset >= 0) && (ndepth > 0);
1497 noffset = fdt_next_node(fit, noffset, &ndepth)) {
1498 if (ndepth == 1) {
1499 /*
READING CONTRACT
기존 발췌와 실제 분기 위치
기존의 1411–1499행 발췌는 fit_image_verify() 정의와 호출을 포함하며, 첫 행은 앞 함수의 끝부분이다. required/optional 서명의 핵심 분기는 그보다 앞선 fit_image_verify_with_data()에 있다. 두 범위를 한 함수처럼 이어 붙이지 않는다.
PINNED CODE
required와 optional의 다른 실패 처리
아래 발췌는 U-Boot v2026.07의 원문 물리 행을 그대로 보여 준다. 먼저 필수 서명 검사를 통과하거나 비활성 조건을 지나야 하위 node를 순회한다. 선택 서명 실패는 표시만 하고, hash 실패는 오류로 간다. 원문 1357–1419행
int fit_image_verify_with_data(const void *fit, int image_noffset,
const void *key_blob, const void *data,
size_t size)
{
int noffset = 0;
char *err_msg = "";
int verify_all = 1;
int ret;
/* Verify all required signatures */
if (FIT_IMAGE_ENABLE_VERIFY &&
fit_image_verify_required_sigs(fit, image_noffset, data, size,
key_blob, &verify_all)) {
err_msg = "Unable to verify required signature";
goto error;
}
/* Process all hash subnodes of the component image node */
fdt_for_each_subnode(noffset, fit, image_noffset) {
const char *name = fit_get_name(fit, noffset, NULL);
/*
* Check subnode name, must be equal to "hash".
* Multiple hash nodes require unique unit node
* names, e.g. hash-1, hash-2, etc.
*/
if (!strncmp(name, FIT_HASH_NODENAME,
strlen(FIT_HASH_NODENAME))) {
if (fit_image_check_hash(fit, noffset, data, size,
&err_msg))
goto error;
puts("+ ");
} else if (FIT_IMAGE_ENABLE_VERIFY && verify_all &&
!strncmp(name, FIT_SIG_NODENAME,
strlen(FIT_SIG_NODENAME))) {
ret = fit_image_check_sig(fit, noffset, data, size,
gd_fdt_blob(), -1, &err_msg);
/*
* Show an indication on failure, but do not return
* an error. Only keys marked 'required' can cause
* an image validation failure. See the call to
* fit_image_verify_required_sigs() above.
*/
if (ret)
puts("- ");
else
puts("+ ");
}
}
if (noffset == -FDT_ERR_TRUNCATED || noffset == -FDT_ERR_BADSTRUCTURE) {
err_msg = "Corrupted or truncated tree";
goto error;
}
return 1;
error:
printf(" error!\n%s for '%s' hash node in '%s' image node\n",
err_msg, fit_get_name(fit, noffset, NULL),
fit_get_name(fit, image_noffset, NULL));
return 0;IMPLEMENTATION NOTES
검증 함수와 보안 정책의 경계
이 파일에서 직접 보이는 분기와, 실제 부팅 정책에서 별도로 확인해야 할 질문을 나눈다. image hash만 맞는다고 image 출처나 선택된 configuration 전체를 인증한 것은 아니다. U-Boot 공식 FIT 서명 설명은 image hash와 서명·configuration 보호 범위를 구분한다.
| 경계 | 고정 소스에서 확인 | 상위 경로에서 확인 |
|---|---|---|
| key와 control FDT | fit_image_verify()는 gd_fdt_blob()를 전달하고, required key 검사 실패는 오류로 간다. 1366–1372 · 1450–1457 | public key와 required 정책을 담은 control FDT가 신뢰 체인에 묶였는가? 이 함수만으로 그 보호를 증명할 수 없다. |
| 검증 byte와 사용 byte | 이 함수의 입력 data,size가 실행되는 hash·서명 검사에 전달된다. 1357–1388 | 상위 load/decompress/handoff가 실제로 소비한 byte가 검증 대상과 같은지, offset·size·buffer 변경 가능성을 추적한다. |
| configuration 선택 | image node 검증과, configuration을 선택한 경로에서의 fit_config_verify() 호출은 별도 지점이다. 2138–2149 | 선택된 kernel·FDT·ramdisk 참조와 property가 어떤 서명 정책으로 보호되는지 해당 구현까지 확인한다. 명시적 image 이름을 받은 경로도 별도로 추적한다. |
| 실패 뒤 fallback | 필수 서명/hash 오류는 이 image 검증에서 0을 돌려준다. 1366–1419 | 호출자가 오류 뒤 서명 없는 다른 image 형식이나 configuration으로 진행하지 않는지 실제 boot command 경로를 확인한다. |
표의 오른쪽 항목은 이 파일에서 보안이 자동으로 보장된다는 주장이 아니라 제품별 부트 경로에서 검증할 계약이다. U-Boot 공식 설명도 required 서명에 의존할 때 legacy image 경로를 함께 검토하도록 안내한다.
OBJECTS
입력과 신뢰 경계
| 대상 | 여기서 확인하는 값 | 이 페이지에서 단정하지 않는 것 |
|---|---|---|
| FIT image node | data, size, 하위 hash/signature node | configuration 전체의 선택 정책 |
| key blob | 필수 서명 검사에 전달되는 control FDT | control FDT 자체의 신뢰 확보 방법 |
| 검증 결과 | 활성화된 image 검사 기준 1 또는 0 | 선택 서명 실패가 부팅 거부라는 주장 |
FAILURES
반환 0과 계속 진행을 나누기
필수 key 서명 실패, hash 오류, FDT truncated/badstructure는 fit_image_verify_with_data()의 오류 경로에서 0을 반환한다. 선택 서명 실패는 “−”로 표시하고 순회를 계속한다. 1357–1419
fit_image_get_data()가 실패하면 호출자인 fit_image_verify()가 err로 가며, 실제 return 0은 1462행에 있다. 함수의 0 반환과 최종 부팅 거부는 다르므로, 상위 호출자가 fallback 없이 멈추는지도 대조한다.
EVIDENCE
검증 경계를 실제로 관찰하기
mkimage -l로 FIT의 image·configuration·hash·signature node를 기록하고, 같은 테스트에서 image verifier 반환, configuration verifier 결과, 최종 boot handoff 여부를 따로 남긴다. 반환 하나만으로 control FDT의 신뢰나 실행 byte의 동일성을 판정하지 않는다.
mkimage -l test.itb가능한 빌드에서는 공식 FIT 서명 문서의 sandbox vboot 테스트를 출발점으로 삼고, 제품의 required key·legacy image 설정과 실제 loader 경로를 별도로 재현한다.
LAB
잘못된 입력을 넣었을 때 확인할 결과
| 변경한 입력 | 기록할 분기 | 상위 경로 질문 |
|---|---|---|
| payload 한 byte 변경 | hash 검사 반환·오류 문자열 | 최종 handoff가 차단되는가? |
| required 서명 제거 또는 손상 | required 검사 반환과 image 검증 0 | 다른 unsigned image로 fallback하는가? |
| 선택 서명만 손상 | “−” 출력과 함수 반환을 분리해 기록 | configuration의 별도 정책은 무엇인가? |
| configuration property 변경 | image 검증과 configuration 검증 결과를 각각 기록 | 선택된 kernel/FDT 참조가 보호되는가? |
테스트 FIT와 control FDT의 정확한 정책을 함께 기록한다. 이 표는 고정 함수의 모든 결과를 단정하지 않고, 경계 밖의 실패를 드러내기 위한 비교 설계다.
PRIMARY REFERENCES
고정 원문과 정책 설명
U-Boot v2026.07 boot/image-fit.c:1309–1463 · configuration 검증 호출 2138–2149 · U-Boot FIT Signature Verification. 코드 기준 태그의 peeled commit: ece349ade2973e220f524ce59e59711cc919263f.