FIT image 구조 분석 — multi-image·hash·configuration 추적
#한 줄 요약
FIT(Flattened Image Tree)는 kernel·DTB·ramdisk·overlay를 한 파일에 묶고, 각각에 hash와 서명을 붙이는 표준 컨테이너입니다. 한 이미지로 여러 보드를 지원하고, verified boot의 기초가 됩니다.
10~14장에서 본 부트 흐름은 kernel, DTB, ramdisk를 따로 적재했습니다. 파일이 셋이면 배포·검증이 모두 복잡합니다. 어느 하나가 옛 버전이면 무엇이 잘못됐는지 추적이 어렵습니다. FIT는 이 셋을 한 컨테이너에 묶고, 각 component에 hash·서명을 달아, 전체를 원자적으로 다루는 포맷입니다.
이 글에서는 FIT의 구조, ITS(Image Tree Source) 작성, mkimage로 빌드, U-Boot의 적재 흐름, configuration node, 그리고 hash·서명을 다룹니다.
#왜 FIT인가
uImage 시대에는 kernel 하나당 한 파일이었습니다. DTB는 따로, initramfs도 따로. 여기에 signature를 붙이려면 각 파일에 따로 서명을 만들어야 했습니다. 배포에서 한 파일이라도 빠지거나 잘못된 버전이면 부트가 실패합니다.
FIT는 이 문제를 device tree blob 형식의 컨테이너로 풉니다.
- multi-image — kernel + DTB + ramdisk + DT overlay를 한 파일에.
- configuration node — 같은 이미지에서 여러 보드/모델을 위한 조합 선택.
- hash — 각 component에 hash 노드(SHA-256 등).
- signature — configuration 또는 각 component에 서명.
- 압축 지원 — 각 component에 압축 지정(gzip, lzma, lz4, …).
- 로딩 주소 메타데이터 —
load,entry주소를 노드에 명시.
빌드 산출물은 .itb(Image Tree Binary)이고, U-Boot의 bootm 명령이 이해합니다.
#ITS 파일 — Image Tree Source
ITS는 device tree source 문법으로 작성합니다. 컴파일러는 mkimage(또는 dtc)입니다.
/dts-v1/;
/ { description = "Kernel and DTB for myboard"; #address-cells = <1>;
images { kernel-1 { description = "Linux kernel"; data = /incbin/("./Image"); type = "kernel"; arch = "arm64"; os = "linux"; compression = "none"; load = <0x40400000>; entry = <0x40400000>; hash-1 { algo = "sha256"; }; };
fdt-1 { description = "DTB for myboard rev A"; data = /incbin/("./myboard-revA.dtb"); type = "flat_dt"; arch = "arm64"; compression = "none"; load = <0x43000000>; hash-1 { algo = "sha256"; }; };
fdt-2 { description = "DTB for myboard rev B"; data = /incbin/("./myboard-revB.dtb"); type = "flat_dt"; arch = "arm64"; compression = "none"; load = <0x43000000>; hash-1 { algo = "sha256"; }; };
ramdisk-1 { description = "Initramfs"; data = /incbin/("./rootfs.cpio.gz"); type = "ramdisk"; arch = "arm64"; os = "linux"; compression = "gzip"; load = <0x44000000>; hash-1 { algo = "sha256"; }; }; };
configurations { default = "conf-revA";
conf-revA { description = "Boot Linux on myboard rev A"; kernel = "kernel-1"; fdt = "fdt-1"; ramdisk = "ramdisk-1"; hash-1 { algo = "sha256"; }; };
conf-revB { description = "Boot Linux on myboard rev B"; kernel = "kernel-1"; fdt = "fdt-2"; ramdisk = "ramdisk-1"; hash-1 { algo = "sha256"; }; }; };};images/ 안에 raw component들이 들어가고, configurations/ 안에 조합이 들어갑니다. 같은 kernel을 두 DTB와 조합해 두 보드 revision을 한 파일에 묶었습니다.
#빌드
mkimage로 ITS를 컴파일합니다.
mkimage -f myboard.its myboard.itb내부적으로는 device tree compiler가 ITS를 binary blob으로 만들고, mkimage가 hash·서명을 채워 넣습니다. 결과 .itb 파일은 device tree 형식의 binary입니다. fdtdump나 dtc -I dtb -O dts로 들여다볼 수 있습니다.
$ fdtdump myboard.itb | head -30/dts-v1/;// magic: 0xd00dfeed// totalsize: 0x12ab450// off_dt_struct: 0x38.../ { description = "Kernel and DTB for myboard"; images { kernel-1 { description = "Linux kernel"; type = "kernel"; ... hash-1 { algo = "sha256"; value = <0xabcd1234 ...>; }; }; };};hash-1의 value가 mkimage 시점에 계산되어 채워진 것이 보입니다.
#U-Boot 측 적재 — bootm
.itb를 메모리에 적재한 뒤 bootm을 호출합니다.
=> load mmc 0:1 ${loadaddr} /boot/myboard.itb=> bootm ${loadaddr}#conf-revA## Loading kernel from FIT Image at 40200000 ... Using 'conf-revA' configuration Trying 'kernel-1' kernel subimage Description: Linux kernel Type: Kernel Image Compression: uncompressed Data Start: 0x402000ec Data Size: 10995712 Bytes = 10.5 MiB Load Address: 0x40400000 Entry Point: 0x40400000 Hash algo: sha256 Hash value: abcd1234... Verifying Hash Integrity ... sha256+ OK## Loading fdt from FIT Image at 40200000 ... Using 'conf-revA' configuration Trying 'fdt-1' fdt subimage ... Verifying Hash Integrity ... sha256+ OK## Loading ramdisk from FIT Image at 40200000 ... ...Starting kernel ...bootm ${loadaddr}#conf-revA의 #conf-revA가 configuration 이름입니다. 생략하면 default가 쓰입니다.
각 component를 지정된 load 주소로 복사하고, hash를 검증한 뒤, kernel entry로 점프합니다. 한 명령에 검증·압축 해제·재배치가 모두 들어 있습니다.
#bootargs 변수 통합
configuration 노드에 bootargs를 직접 둘 수도 있습니다.
configurations { conf-revA { kernel = "kernel-1"; fdt = "fdt-1"; ramdisk = "ramdisk-1"; bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2 rw"; hash-1 { algo = "sha256"; }; };};U-Boot 환경의 bootargs를 덮어쓰고 이미지 자체가 지정한 인자로 부팅합니다. OTA로 새 이미지를 배포할 때 환경 변수에 의존하지 않고 새 bootargs를 같이 보낼 수 있습니다.
#서명 추가 — verified boot의 기초
hash는 전송 오류 탐지에는 충분하지만, 공격자가 image와 hash를 같이 위조하면 무용지물입니다. 서명이 필요합니다.
configurations { default = "conf-revA";
conf-revA { kernel = "kernel-1"; fdt = "fdt-1"; signature-1 { algo = "sha256,rsa2048"; key-name-hint = "dev"; sign-images = "kernel", "fdt", "ramdisk"; }; };};mkimage가 RSA 키로 서명을 만들어 value에 채워 넣습니다.
mkimage -f myboard.its -k keys/ -K u-boot.dtb -r myboard.itb-k keys/— 서명 키가 든 디렉터리-K u-boot.dtb— 공개 키를 U-Boot의 control DTB에 박는다-r— 서명을 required로 표시 (verified boot에서 강제)
U-Boot 측에서 CONFIG_FIT_SIGNATURE=y를 켜면 bootm이 서명 없는 FIT를 거부합니다. 공개 키는 U-Boot 빌드 시점에 control DTB에 들어 있으므로, 키 자체가 변조되어도 부트가 막힙니다.
16장에서 verified boot 전체 사슬을 다룹니다. FIT 서명은 그 마지막 단계의 빌딩 블록입니다.
#한 이미지로 여러 보드
configurations 노드의 강력함은 한 .itb로 여러 보드를 지원하는 데 있습니다. 양산 라인에서 보드 revision이 늘어날 때마다 별도 이미지를 빌드하지 않아도 됩니다.
# U-Boot 측에서 보드 자동 감지=> setenv board_rev "revA"=> bootm ${loadaddr}#conf-${board_rev}또는 기본 configuration을 기본 fdt 매칭으로 자동 선택합니다.
configurations { default = "conf-revA";
conf-revA { kernel = "kernel-1"; fdt = "fdt-1"; compatible = "vendor,myboard-revA"; /* 보드 compatible과 매칭 */ }; conf-revB { kernel = "kernel-1"; fdt = "fdt-2"; compatible = "vendor,myboard-revB"; };};U-Boot가 현재 보드의 root compatible과 일치하는 configuration을 자동 선택합니다. bootm에 #을 명시하지 않아도 됩니다.
#DT overlay 적용
DT overlay를 FIT 안에 두고, configuration에서 base + overlay 조합을 지정할 수 있습니다.
images { fdt-base { /* ... */ }; fdt-overlay-camera { type = "flat_dt"; /* ... */ }; fdt-overlay-display { type = "flat_dt"; /* ... */ };};
configurations { conf-with-camera { kernel = "kernel-1"; fdt = "fdt-base", "fdt-overlay-camera"; };};U-Boot가 base DTB에 overlay를 적용한 결과를 커널에 넘깁니다. 같은 보드의 옵션 부품을 configuration 단위로 처리할 수 있습니다.
#자주 하는 실수
- load 주소 충돌. kernel과 ramdisk의 load 주소가 겹치면 조용히 깨집니다. 적재 직후 ramdisk가 kernel을 덮어쓰거나 그 반대.
- 압축 type 불일치. ramdisk를
.cpio.gz로 만들고 ITS에compression = "none"이라 쓰면 U-Boot는 압축 해제를 안 합니다. 부트 후 init이 실패. - default configuration이 서명되지 않음.
signature-1노드 없는 configuration이 default라면CONFIG_FIT_SIGNATURE_STRICT=y에서 부트가 거부됩니다. - mkimage가 옛 버전. mkimage가 새 사양(예: configuration signing)을 모르면 ITS는 컴파일되지만 서명 노드는 비어 있습니다. U-Boot 트리에서 빌드한 mkimage를 쓰세요.
- 공개 키를 control DTB에 안 박았습니다.
-K u-boot.dtb를 빼면 U-Boot가 검증할 키가 없어 모든 FIT를 거부합니다.
#정리
- FIT는 kernel·DTB·ramdisk·overlay를 한 파일에 묶는 device tree 형식의 컨테이너입니다.
- ITS는 device tree source 문법이고, mkimage로
.itb를 빌드합니다. images/는 raw component,configurations/는 component 조합입니다.bootm <addr>#<conf>로 configuration을 선택해 부팅합니다.- 각 노드에
hash-1을 두면 전송 오류 탐지,signature-1을 두면 verified boot가 됩니다. compatible속성으로 보드별 configuration 자동 매칭이 가능합니다.- DT overlay를 configuration 안에서 base + overlay로 조합할 수 있습니다.
#다음 장 예고
다음 글에서는 verified boot의 전체 사슬을 봅니다. SoC ROM → SPL → U-Boot → FIT까지, 서명·해시가 어떻게 이어져 trust chain을 만드는지 정리합니다.
#관련 항목
- Ch 14: bootflow / bootmeth — FIT를 적재하는 새 모델
- Ch 16: Verified Boot — FIT 서명을 root of trust로
- Ch 17: A/B 업데이트 — FIT 단위의 atomic update
- Security Ch 2: Secure Boot — root of trust 개념
- Security Ch 6: OTA Update — FIT와 OTA 결합
- U-Boot FIT 문서
Bootloader Internals · 15 of 37
- 1ROM부터 init까지 — 임베디드 부팅 단계의 빈자리 분석
- 2Das U-Boot vs TF-A vs EDK II — 임베디드 부트로더 생태계 비교
- 3U-Boot 빌드 시스템 분석 — Kconfig·Makefile·defconfig 동작 추적
- 4ARM 임베디드 부트 4단계 분해 — BL1·SPL·TPL·U-Boot Proper의 역할
- 5U-Boot Falcon Mode — SPL이 U-Boot Proper 없이 커널 직접 부팅
- 6Device Tree DTB 부트로더 처리 — 로딩 시점과 fixup 메커니즘 추적
- 7U-Boot Driver Model 내부 — uclass·driver·device 추상화 구조
- 8U-Boot 보드 초기화 시퀀스 — board_init_f와 board_init_r 분리 이유
- 9DDR Controller 프로그래밍과 PHY Training — SPL의 가장 어려운 작업
- 10임베디드 스토리지 부팅 분석 — MMC·SCSI·NAND·SPI Flash 비교
- 11임베디드 네트워크 부팅 — TFTP·PXE·BOOTP 시퀀스 분석
- 12U-Boot USB 부팅 — fastboot·UMS·USB host 메커니즘
- 13U-Boot 환경 변수와 bootcmd — 부팅 시나리오 정의하기
- 14Modern U-Boot bootflow / bootmeth — 새 추상화 레이어 분석
- 15FIT image 구조 분석 — multi-image·hash·configuration 추적
- 16U-Boot Verified Boot — RSA 서명과 public key 임베딩 흐름
- 17임베디드 A/B 부팅 이중화 — OTA 안전성을 위한 부트 슬롯 설계
- 18U-Boot의 EFI 호환 분석 — bootefi 명령과 EFI loader 동작 원리
- 19Linux Boot ABI — ARM/ARM64 커널 진입 규약 추적
- 20임베디드 펌웨어 업데이트 — RAUC vs SWUpdate 비교
- 21새 보드 U-Boot 포팅 실전 — defconfig 작성부터 첫 부팅까지
- 22부트로더 디버깅 기법 — DEBUG·JTAG·serial·post-mortem 분석
- 23SoC BootROM·eFuse·OTP — 부팅의 0단계 분석
- 24SPL·TPL 내부 해부 — 가장 작은 부트 단계의 동작 추적
- 25ARM Trusted Firmware-A 통합 — BL1·BL2·BL31·BL32·BL33 흐름
- 26DDR Training과 PHY Calibration — 보드별 파라미터 튜닝
- 27임베디드 Chain of Trust — 다단계 서명 검증의 전체 흐름
- 28임베디드 Flash Layout 설계 — partition·NAND·eMMC·UBI 비교
- 29U-Boot Distro Boot — extlinux·boot.scr 표준화 분석
- 30부트로더 CI 구축 — build matrix와 자동 부팅 테스트
- 31TF-A BL31 EL3 Runtime 분석 — PSCI·SDEI·RAS dispatcher 추적
- 32PSCI와 SMCCC ABI — ARM 표준 SMC 호출 규약 분석
- 33ARM64 Secondary Core Bring-up — PSCI CPU_ON 호출부터 EL1 진입까지
- 34U-Boot PCIe Enumeration — 부트로더가 디바이스를 찾는 흐름 분석
- 35EFI·UEFI에서 CXL 초기화 — CEDT 생성과 HDM Decoder 사전 설정
- 36부트 시 메모리 토폴로지 결정 — DDR + CXL.mem 통합 인식
- 37UEFI Secure Boot 인증서 만료 — 2011→2023 CA 롤오버와 PQC 대비
관련 글
U-Boot PCIe Enumeration — 부트로더가 디바이스를 찾는 흐름 분석
U-Boot PCIe 열거 과정 — Root Complex 초기화·Config Space scan·BAR sizing·resource 할당, CXL DVSEC 인식까지.
U-Boot Distro Boot — extlinux·boot.scr 표준화 분석
보드별 다른 부트 스크립트를 표준화 — U-Boot Distro Boot, extlinux.conf, boot.scr의 차이와 선택.
SPL·TPL 내부 해부 — 가장 작은 부트 단계의 동작 추적
SPL과 TPL의 정확한 역할, SRAM 안에 들어가는 코드 구조, DDR이 없는 환경에서 어떻게 동작하는가.
이 글을 참조하는 글 (6)
- 임베디드 Chain of Trust — 다단계 서명 검증의 전체 흐름— Bootloader Internals
- U-Boot Verified Boot — RSA 서명과 public key 임베딩 흐름— Bootloader Internals
- Modern U-Boot bootflow / bootmeth — 새 추상화 레이어 분석— Bootloader Internals
- Device Tree DTB 부트로더 처리 — 로딩 시점과 fixup 메커니즘 추적— Bootloader Internals
- U-Boot Falcon Mode — SPL이 U-Boot Proper 없이 커널 직접 부팅— Bootloader Internals
- ARM 임베디드 부트 4단계 분해 — BL1·SPL·TPL·U-Boot Proper의 역할— Bootloader Internals