Skip to content

Validator#5

Open
sjg20 wants to merge 9 commits into
open-source-firmware:mainfrom
sjg20:valid
Open

Validator#5
sjg20 wants to merge 9 commits into
open-source-firmware:mainfrom
sjg20:valid

Conversation

@sjg20
Copy link
Copy Markdown
Collaborator

@sjg20 sjg20 commented Nov 1, 2023

Summary

A Python-based validator for Flat Image Tree (FIT) files. The validator
walks a binary FIT (or compiles a .dts source on the fly) and reports
errors against a schema derived from chapter5-source-file-format.rst.

It selects the schema per file: .upl-suffixed files are checked
against the Universal Payload (UPL) variant; everything else uses the
plain FIT schema. The -u flag forces UPL globally for callers that
prefer it.

What is checked

  • Property structure on /, /images/<image> and /configurations/<config>
    required and optional properties per the spec, including
    type-conditional requirements (os only for type=kernel; arch
    for standalone/kernel/firmware/ramdisk/fdt; entry/load for
    firmware/kernel; compatible mandatory on fpga images).
  • Closed value setstype, arch, os, compression and
    phase are checked against the spec's enumerations.
  • Hash subnodes (hash-N) — algo (crc16-ccitt | crc32 | md5 |
    sha1 | sha256 | sha384 | sha512) and value are required.
  • Signature subnodes (signature-N) — composite algo
    (<hash>,<rsa|ecdsa>NNNN), key-name-hint, value plus the
    signing-time metadata (hashed-nodes, hashed-strings, signer-name,
    padding pkcs-1.5 / pss, etc.) per the image- and configuration-
    signature sections of the spec.
  • Cross-referencesdefault in /configurations must name an
    existing configuration; firmware, kernel, fdt, ramdisk,
    fpga, script and each item of loadables and sign-images must
    name an existing image; image-data must name an existing image
    and the target must not itself have image-data (no chains).
  • Producer-side mistakes — property names with leading or trailing
    whitespace are flagged with a specific diagnostic and a "did you
    mean" hint, instead of being buried under a generic
    Unexpected property error.

Commits

Add a .gitignore file
Add an initial validator for FIT
fit_validate: Detect surrounding whitespace in property names
fit_validate: Validate hash and signature subnodes
fit_validate: Validate cross-references between configs and images
fit_validate: Tighten image property requirements per the spec
fit_validate: Cover the remaining image and config properties
fit_validate: Fix command-line test on Python-3-only systems
fit_validate: Make require-fit UPL-only, auto-detect .upl files

Tests

python3 -m fit_validate.validate_test

28 unit tests pass. Fixtures cover the happy path plus the most useful
failure modes (missing required props, type-driven requirement gating,
unknown enum values, broken cross-references, malformed property names
and the no-chain rule for image-data). Hash- and signature-related
tests are built directly with libfdt so they exercise the binary
form rather than going through dtc.

Known gaps

Still on the audit list, deferred for follow-up:

  • dm-verity subnodes (chapter 5, lines 555-644) under
    filesystem-typed images, with seven mandatory properties and
    the size/power-of-2 constraints.
  • Tighter value enumeration on the image-level compatible property
    (the spec only enumerates the fpga loading methods explicitly, so
    for now the value list is left open).

Test plan

  • Run python3 -m fit_validate.validate_test and confirm 28
    tests pass.
  • Run the validator against a real FIT or UPL file from your
    build and confirm the output is sensible.
  • Validate a UPL FIT by giving it a .upl suffix; confirm
    project, firmware and require-fit are recognised
    without passing -u.

@netlify
Copy link
Copy Markdown

netlify Bot commented Nov 1, 2023

Deploy Preview for fluffy-chebakia-3fa329 ready!

Name Link
🔨 Latest commit 93ab71f
🔍 Latest deploy log https://app.netlify.com/projects/fluffy-chebakia-3fa329/deploys/683e0ca366b68d0008ecc666
😎 Deploy Preview https://deploy-preview-5--fluffy-chebakia-3fa329.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@sjg20
Copy link
Copy Markdown
Collaborator Author

sjg20 commented Jun 5, 2024

Result from test by Chasel:

fdtdump UniversalPayload.fit

**** fdtdump is a low-level debugging tool, not meant for general use.
**** If you want to decompile a dtb, you probably want
**** dtc -I dtb -O dts

/dts-v1/;
// magic: 0xd00dfeed
// totalsize: 0x1000 (4096)
// off_dt_struct: 0x40
// off_dt_strings: 0x3fc
// off_mem_rsvmap: 0x30
// version: 17
// last_comp_version: 16
// boot_cpuid_phys: 0x0
// size_dt_strings: 0xbe
// size_dt_struct: 0x3bc

/ {
description = "Uefi OS Loader";
timestamp = <0x664da66a>;
size = <0x004ec000>;
spec-version = <0x00000090>;
build-revision = <0x00010105>;
images {
tianocore {
description = "Uefi Universal Payload";
project = "tianocore";
arch = "x86_64";
type = "flat-binary";
producer = "intel";
data-offset = <0x00001000>;
data-size = <0x00010000>;
reloc-start = <0x0000fd20>;
entry-start = <0x00000000 0x008014dc>;
load = <0x00000000 0x00800000>;
};
uefi-fv {
description = "UEFI Firmware Volume";
type = "flat-binary";
arch = "X64";
project = "tianocore";
compression = "none";
data-offset = <0x00011000>;
data-size = <0x0033c000>;
};
bds-fv {
description = "BDS Firmware Volume";
type = "flat-binary";
arch = "X64";
project = "tianocore";
compression = "none";
data-offset = <0x0034d000>;
data-size = <0x00080000>;
};
network-fv {
description = "Network Firmware Volume";
type = "flat-binary";
arch = "X64";
project = "tianocore";
compression = "none";
data-offset = <0x003cd000>;
data-size = <0x0011f000>;
};
};
configurations {
default = "conf-1";
conf-1 {
firmware = "tianocore";
require-fit;
};
};
};

python fit_validate.py ../UniversalPayload.fit
https://github.com/open-source-firmware/flat-image-tree/pull/5/files

../UniversalPayload.fit:
/: Unexpected property 'size', valid list is (timestamp, description, #address-cells)
/: Unexpected property 'spec-version', valid list is (timestamp, description, #address-cells)
/: Unexpected property 'build-revision ', valid list is (timestamp, description, #address-cells)
/: Required property '#address-cells' missing
/images/tianocore: Node name 'tianocore' does not match pattern '^image-(\d)+$'
/images/tianocore: Unexpected property 'project ', valid list is (description, timestamp, arch, type, compression, data-offset, data-size, os, load, project, capabilities, producer, uncomp-size, entry-start, entry, reloc-start)
/images/tianocore: Unexpected property 'arch ', valid list is (description, timestamp, arch, type, compression, data-offset, data-size, os, load, project, capabilities, producer, uncomp-size, entry-start, entry, reloc-start)
/images/tianocore: Unexpected property 'type ', valid list is (description, timestamp, arch, type, compression, data-offset, data-size, os, load, project, capabilities, producer, uncomp-size, entry-start, entry, reloc-start)
/images/tianocore: Unexpected property 'producer ', valid list is (description, timestamp, arch, type, compression, data-offset, data-size, os, load, project, capabilities, producer, uncomp-size, entry-start, entry, reloc-start)
/images/tianocore: Required property 'arch' missing
/images/tianocore: Required property 'type' missing
/images/tianocore: Required property 'os' missing
/images/tianocore: Required property 'project' missing
/images/uefi-fv: Node name 'uefi-fv' does not match pattern '^image-(\d)+$'
/images/uefi-fv: Unexpected property 'project ', valid list is (description, timestamp, arch, type, compression, data-offset, data-size, os, load, project, capabilities, producer, uncomp-size, entry-start, entry, reloc-start)
/images/uefi-fv: Required property 'os' missing
/images/uefi-fv: Required property 'project' missing
/images/bds-fv: Node name 'bds-fv' does not match pattern '^image-(\d)+$'
/images/bds-fv: Unexpected property 'project ', valid list is (description, timestamp, arch, type, compression, data-offset, data-size, os, load, project, capabilities, producer, uncomp-size, entry-start, entry, reloc-start)
/images/bds-fv: Required property 'os' missing
/images/bds-fv: Required property 'project' missing
/configurations: Unexpected property 'default ', valid list is (default)
/configurations/conf-1: Node name 'conf-1' does not match pattern '^config-(\d)+$'
/configurations/conf-1: Required property 'description' missing

@sjg20
Copy link
Copy Markdown
Collaborator Author

sjg20 commented Jun 5, 2024

My guess is that the line endings are not working and this is running on Windows. I will need to break out my Windows VM and figure out what is missing

@netlify
Copy link
Copy Markdown

netlify Bot commented May 7, 2026

Deploy Preview for fluffy-chebakia-3fa329 ready!

Name Link
🔨 Latest commit ea46331
🔍 Latest deploy log https://app.netlify.com/projects/fluffy-chebakia-3fa329/deploys/6a078b75bc9de5000820ce1e
😎 Deploy Preview https://deploy-preview-5--fluffy-chebakia-3fa329.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@sjg20 sjg20 requested review from a3f and trini and removed request for LeanSheng May 7, 2026 21:19
sjg20 added 9 commits May 15, 2026 14:34
Add the build/ directory to this file so that it doesn't show up in
'git status'.

Signed-off-by: Simon Glass <sjg@chromium.org>
There is currently no easy way to check whether a FIT conforms to the
specification. Detection of malformed images is left to consuming tools
and manual inspection.

Add an initial Python-based validator that checks a binary FIT against
a hand-written schema. It does not yet cover the full specification but
provides the framework on which to expand.

Invoke it as:

    python3 -m fit_validate.validate <filename>

A 'check' target in the Makefile runs the unit tests and pylint over
the validator sources.

Signed-off-by: Simon Glass <sjg@chromium.org>
Some FIT producers emit property names with stray leading or trailing
whitespace, for example by splitting a DTS-style 'name  = "value"' line
on '=' without strip()-ing the left-hand side. A binary FIT carries
those names verbatim, so the validator currently reports a generic
'Unexpected property' diagnostic that buries the real issue under the
list of valid schema properties.

Detect property names where strip() changes the value and emit a
specific diagnostic that names the producer as the likely culprit. If
the stripped name matches a property in the schema, include a 'did you
mean' hint to point straight at the fix.

Add a unit test that builds a binary FIT directly with libfdt (since
dtc strips whitespace from names during compilation) and asserts the
new diagnostics fire.

Co-developed-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hash and signature subnodes inside images and configurations are
mandatory parts of every signed FIT and the spec defines their
structure precisely (chapter 5, sections 'Hash nodes',
'Image-signature nodes' and 'Configuration-signature nodes'). The
validator currently has no schema for them, so any well-formed signed
FIT silently slips through with no checking of these subtrees.

Add the missing schema:

* hash-N: requires 'algo' (one of crc16-ccitt, crc32, md5, sha1,
  sha256, sha384, sha512) and 'value'.
* signature-N under an image: requires algo, key-name-hint, value,
  hashed-nodes and hashed-strings; optional sign-images, timestamp,
  signer-name, signer-version, comment, padding (pkcs-1.5 or pss).
* signature-N under a config: requires algo, key-name-hint and value;
  optional sign-images and the same metadata fields.

This also needs two pieces of supporting work:

1. get_element() currently returns the first NodeAny child regardless
   of name pattern, so a schema scope can only have one pattern-matched
   subnode shape. Filter NodeAny siblings by pattern when more than one
   is present.

2. Add PropBytes for opaque byte values such as hash digests and
   signature blobs. PropDesc works but its inherited __init__ takes
   prop_type as the second positional argument, making
   PropDesc(name, True) silently set prop_type=True instead of
   required=True. PropBytes wraps the awkward calling convention.

Co-developed-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Many FIT misconfigurations come from a typo in the value of a string
property that names another node: 'firmware = "image-x"' where there
is no /images/image-x, a 'default = "conf-99"' that points at no
existing config, a 'loadables' entry that has been deleted from the
images list, and so on. The validator currently treats these as plain
strings and lets the broken reference through.

Add three new schema element types that walk the FDT to confirm the
target exists:

* PropImageRef: value names a /images/<name>. Used for fdt, kernel,
  ramdisk, firmware and image-data.
* PropConfigRef: value names a /configurations/<name>. Used for the
  configurations 'default' property.
* PropImageRefList: each item of a stringlist names an image. Used
  for loadables and the signature 'sign-images' list.

Also wire the no-chain rule for image-data (chapter 5: 'The target
node must not itself have an image-data property; chains of references
are not permitted').

Fix the conditional_props on data, data-offset and data-size so they
also treat image-data as an alternative source of image bytes - an
authored FIT with image-data and no data-* should not be flagged as
missing data-offset.

Co-developed-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The schema is too lax in two ways. Property values that the spec
defines as closed enumerations - type, arch, os, compression - are
accepted as free strings, so a typo such as arch='z80' or
compression='snappy' produces a FIT that the validator passes. And
properties that the spec marks as conditionally mandatory (os only
required for type=kernel, arch for a specific list of types, entry
and load for firmware and kernel) are either always-required or
always-optional, neither of which matches the spec.

Add a 'values' option to PropString so a property can declare its
list of allowed values directly. Apply it to type, arch, os and
compression on image nodes.

Add a 'required_when' option that makes a property required only when
a sibling has one of a listed set of values. Use it for arch (when
type is one of standalone, kernel, firmware, ramdisk, fdt), os (when
type=kernel) and entry/load (when type is firmware or kernel).

Relax the root description from required to optional, matching the
spec.

Update the test fixtures to be spec-compliant kernel images (with
load and entry) and add tests for the new behaviour: type-driven
requirement gating, value-set rejection, and the now-optional root
description.

Co-developed-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The schema is still missing several properties that the spec defines.
On image nodes: data-position (machine address as an alternative to
data-offset), phase (spl or u-boot, recent commit 1e69f6b) and
compatible (the loading-method string, mandatory for fpga images and
images without a load address). On configuration nodes: cmdline (the
kernel command line, commit 248258c), fpga and script image
references, and load-only (commit 1be2a60).

Add them all. The data-position property is treated as an alternative
to data and image-data, so the conditional_props on data-offset and
data are extended to skip their requirement when data-position is
set. compatible on an image becomes required when type='fpga'; its
value list is left unconstrained because the spec only enumerates
the fpga loading methods and compatible serves a wider role for
images without a load address.

Pipe required_when through PropStringList so compatible can use the
same type-driven requirement gate as the other image properties.

Co-developed-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The command-line test invokes the validator as a subprocess via the
'python' binary, which is not present on Ubuntu and other systems
that ship only 'python3'. Use sys.executable so the test runs under
whichever interpreter the test suite itself is using.

Also rename test_comannd_line to test_command_line; the typo makes
the test harder to pick out by name.

Co-developed-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
require-fit is a Universal Payload extension and is not defined by the
FIT specification. It currently lives in the shared config schema, so
the validator silently accepts it on a plain FIT - which is misleading,
since other FIT consumers will not understand the property.

Move require-fit to the UPL branch of get_schema(), alongside the
existing UPL-only project and firmware additions.

Also pick the schema per file. Files whose name ends in .upl are
validated against the UPL schema automatically; the rest default to
FIT. The -u flag still forces UPL globally for callers that want the
old behaviour. This means a UPL FIT can be checked just by giving it
a .upl extension, without having to pass the -u flag.

Co-developed-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Simon Glass <sjg@chromium.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant