For this fix, offset/len could be made uint32_t, as was done in the patch
titled "image-fit-sig: Validate hashed-strings region size", but this
would require changing the prototypes of fit_image_get_data_position(),
fit_image_get_data_offset() and fit_image_get_data_size(). Please confirm
whether this is acceptable.

Thank you,
Anton


On Tue, Jun 2, 2026 at 7:30 PM Anton Ivanov <[email protected]> wrote:

> fit_image_get_data() uses the data-position, data-offset, and
> data-size FIT properties without bounds checking. A crafted FIT
> image can specify values that cause out-of-bounds read during
> signature verification of an untrusted FIT.
>
> Validate that the external data offset and size are non-negative,
> and that the data region fits within the FIT image bounds.
>
> Signed-off-by: Anton Ivanov <[email protected]>
> ---
> Changes in v4:
> - Add test for external data bounds validation
> - Clarify vulnerability reachability in the commit message
>
> Changes in v3:
> - Update From and Signed-off-by to personal email
>
> Changes in v2:
> - Rewrite commit message to be concise per maintainer feedback
>
>  boot/image-fit.c            |  16 ++++
>  test/py/tests/test_vboot.py | 165 ++++++++++++++++++++++++++++++++++++
>  2 files changed, 181 insertions(+)
>
> diff --git a/boot/image-fit.c b/boot/image-fit.c
> index b0fcaf6e17f..b657142f626 100644
> --- a/boot/image-fit.c
> +++ b/boot/image-fit.c
> @@ -1084,8 +1084,24 @@ int fit_image_get_data(const void *fit, int
> noffset, const void **data,
>
>         if (external_data) {
>                 debug("External Data\n");
> +               if (offset < 0 || offset > UINTPTR_MAX - (uintptr_t)fit) {
> +                       printf("Invalid external data offset: %d\n",
> offset);
> +                       return -1;
> +               }
> +
>                 ret = fit_image_get_data_size(fit, noffset, &len);
>                 if (!ret) {
> +                       if (len < 0) {
> +                               printf("Invalid external data size: %d\n",
> len);
> +                               return -1;
> +                       }
> +#if CONFIG_IS_ENABLED(FIT_SIGNATURE)
> +                       if (len > CONFIG_VAL(FIT_SIGNATURE_MAX_SIZE) -
> offset) {
> +                               printf("FIT external data is out of bounds
> (offset=%d, size=%d)\n",
> +                                      offset, len);
> +                               return -1;
> +                       }
> +#endif
>                         *data = fit + offset;
>                         *size = len;
>                 }
> diff --git a/test/py/tests/test_vboot.py b/test/py/tests/test_vboot.py
> index 55518bed07e..85a1bd56af6 100644
> --- a/test/py/tests/test_vboot.py
> +++ b/test/py/tests/test_vboot.py
> @@ -563,6 +563,171 @@ def test_vboot(ubman, name, sha_algo, padding,
> sign_options, required,
>          ubman.restart_uboot()
>
>
> [email protected]('sandbox')
> [email protected]('fit_signature')
> [email protected]('dtc')
> [email protected]('fdtput')
> [email protected]('openssl')
> +def test_vboot_ext_data_bounds(ubman):
> +    """Test that malformed external-data properties are rejected.
> +
> +    A signed FIT with external data exposes 'data-position',
> 'data-offset' and
> +    'data-size' properties. U-Boot must validate these before hashing the
> image
> +    components, otherwise a crafted FIT could trigger an out-of-bounds
> access
> +    during signature verification.
> +
> +    These checks are independent of the hashing algorithm, so a single
> signing
> +    configuration is enough.
> +
> +    This works using sandbox only as it needs to update the device tree
> used
> +    by U-Boot to hold public keys from the signing process.
> +    """
> +    sha_algo = 'sha256'
> +
> +    def run_bootm(test_type, expect_string):
> +        """Run a 'bootm' command in U-Boot and expect it to fail.
> +
> +        This always starts a fresh U-Boot instance since the device tree
> may
> +        contain a new public key.
> +
> +        Args:
> +            test_type: A string identifying the test type.
> +            expect_string: A string which is expected in the output.
> +        """
> +        ubman.restart_uboot()
> +        with ubman.log.section('Verified boot %s %s' % (sha_algo,
> test_type)):
> +            output = ubman.run_command_list(
> +                ['host load hostfs - 100 %s' % fit,
> +                 'fdt addr 100',
> +                 'bootm 100'])
> +        assert expect_string in ''.join(output)
> +        assert 'sandbox: continuing, as we cannot run' not in
> ''.join(output)
> +
> +    def sign_fit(options):
> +        """Sign the FIT
> +
> +        Signs the FIT and writes the signature into it. It also writes the
> +        public key into the dtb.
> +
> +        Args:
> +            options: Options to provide to mkimage.
> +        """
> +        args = [mkimage, '-F', '-k', tmpdir, '-K', dtb, '-r', fit]
> +        if options:
> +            args += options.split(' ')
> +        ubman.log.action('%s: Sign images' % sha_algo)
> +        utils.run_and_log(ubman, args)
> +
> +    def create_rsa_pair(name):
> +        """Generate a new RSA key pair and certificate.
> +
> +        Args:
> +            name: Name of the key (e.g. 'dev')
> +        """
> +        public_exponent = 65537
> +        utils.run_and_log(ubman, 'openssl genpkey -algorithm RSA -out
> %s%s.key '
> +                     '-pkeyopt rsa_keygen_bits:2048 '
> +                     '-pkeyopt rsa_keygen_pubexp:%d' %
> +                     (tmpdir, name, public_exponent))
> +
> +        # Create a certificate containing the public key
> +        utils.run_and_log(ubman, 'openssl req -batch -new -x509 -key
> %s%s.key '
> +                          '-out %s%s.crt' % (tmpdir, name, tmpdir, name))
> +
> +    def set_external_data(prop, value):
> +        """Set an external-data property of the kernel image.
> +
> +        Args:
> +            prop: Property name
> +            value: The new value of the property
> +        """
> +        utils.run_and_log(
> +            ubman, 'fdtput -t x %s /images/kernel %s %#x' % (fit, prop,
> value)
> +        )
> +
> +    def make_signed_fit():
> +        """Build a fresh signed FIT with external data.
> +
> +        sign_fit() overwrites the FIT, so a new one is built before each
> test
> +        case mutates its external-data properties.
> +        """
> +        make_fit('sign-configs-%s.its' % sha_algo, ubman, mkimage,
> dtc_args,
> +                 datadir, fit)
> +        sign_fit('-E')
> +
> +    tmpdir = os.path.join(ubman.config.result_dir, 'ext-data-bounds') +
> '/'
> +    if not os.path.exists(tmpdir):
> +        os.mkdir(tmpdir)
> +    datadir = ubman.config.source_dir + '/test/py/tests/vboot/'
> +    fit = '%stest.fit' % tmpdir
> +    mkimage = ubman.config.build_dir + '/tools/mkimage'
> +    dtc_args = '-I dts -O dtb -i %s' % tmpdir
> +    dtb = '%ssandbox-u-boot.dtb' % tmpdir
> +
> +    bcfg = ubman.config.buildconfig
> +    max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000),
> 0)
> +
> +    create_rsa_pair('dev')
> +
> +    # Create a kernel image filled with zeroes
> +    with open('%stest-kernel.bin' % tmpdir, 'wb') as fd:
> +        fd.write(500 * b'\0')
> +
> +    testcases = [
> +        ('negative data-position',
> +         {'data-position': 0xFFFFFFFF}, 'Invalid external data offset'),
> +        ('negative data-offset',
> +         {'data-offset': 0x80000000}, 'Invalid external data offset'),
> +        ('negative data-size',
> +         {'data-size': 0xFFFFFFFF}, 'Invalid external data size'),
> +        ('off-bounds data-position',
> +         {'data-position': 0x7FFFFFFF}, 'FIT external data is out of
> bounds'),
> +        ('off-bounds data-offset',
> +         {'data-offset': 0x10000000}, 'FIT external data is out of
> bounds'),
> +        ('oversized data-size',
> +         {'data-size': 0x7FFFFFFF}, 'FIT external data is out of bounds'),
> +        ('off-bounds data-position',
> +         {'data-position': max_size + 1, 'data-size': 0},
> +         'FIT external data is out of bounds'),
> +        ('off-bounds data-offset',
> +         {'data-offset': max_size + 1, 'data-size': 0},
> +         'FIT external data is out of bounds'),
> +        ('oversized data-size',
> +         {'data-position': 0x0, 'data-size': max_size + 1},
> +         'FIT external data is out of bounds'),
> +        ('in-bounds data-position',
> +         {'data-position': max_size, 'data-size': 0}, 'Bad Data Hash'),
> +        ('in-bounds data-offset',
> +         {'data-offset': max_size, 'data-size': 0}, 'Bad Data Hash'),
> +        ('in-bounds data-size',
> +         {'data-position': 0x0, 'data-size': max_size}, 'Bad Data Hash'),
> +    ]
> +
> +    # We need to use our own device tree file. Remember to restore it
> +    # afterwards.
> +    old_dtb = ubman.config.dtb
> +    try:
> +        ubman.config.dtb = dtb
> +
> +        # Compile our device tree files for kernel and U-Boot. These are
> +        # regenerated here since mkimage will modify them (by adding a
> +        # public key) below.
> +        dtc('sandbox-kernel.dts', ubman, dtc_args, datadir, tmpdir, dtb)
> +        dtc('sandbox-u-boot.dts', ubman, dtc_args, datadir, tmpdir, dtb)
> +
> +        ubman.log.action(
> +            '%s: Test signed FIT with malformed external-data properties'
> % sha_algo)
> +        for desc, props, expect_string in testcases:
> +            make_signed_fit()
> +            for prop, value in props.items():
> +                set_external_data(prop, value)
> +            run_bootm('Signed config with %s' % desc, expect_string)
> +    finally:
> +        # Go back to the original U-Boot with the correct dtb.
> +        ubman.config.dtb = old_dtb
> +        ubman.restart_uboot()
> +
> +
>  TESTDATA_IN = [
>      ['sha1-basic', 'sha1', '', None, False],
>      ['sha1-pad', 'sha1', '', '-E -p 0x10000', False],
> --
> 2.53.0
>

Reply via email to