Add sandbox tests that exercise the per-image decompression buffer that bootm_load_os() allocates for a compressed kernel_noload image (ALIGN(image_len * 4, SZ_1M)).
The overflow test builds a FIT whose decompressed size far exceeds the per-image buffer and asserts that 'bootm loados' rejects it with a decompression error rather than overflowing. The boundary test builds a FIT whose decompressed size equals the per-image buffer exactly and asserts that 'bootm loados' succeeds, guarding against an off-by-one rejection at the buffer limit. Signed-off-by: Aristo Chen <[email protected]> --- test/py/tests/test_fit.py | 126 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/test/py/tests/test_fit.py b/test/py/tests/test_fit.py index 4f56a1421e1..560b7513e67 100755 --- a/test/py/tests/test_fit.py +++ b/test/py/tests/test_fit.py @@ -117,6 +117,36 @@ host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x ''' +# A minimal ITS for a compressed 'kernel_noload' kernel. bootm allocates a +# per-image decompression buffer for this image type, sized as a multiple of +# the compressed length; see the test_fit_kernel_noload_decomp_* tests. +NOLOAD_ITS = ''' +/dts-v1/; + +/ { + description = "FIT with a compressed kernel_noload image"; + #address-cells = <1>; + + images { + kernel-1 { + data = /incbin/("%(kernel)s"); + type = "kernel_noload"; + arch = "sandbox"; + os = "linux"; + compression = "gzip"; + load = <0>; + entry = <0>; + }; + }; + configurations { + default = "conf-1"; + conf-1 { + kernel = "kernel-1"; + }; + }; +}; +''' + @pytest.mark.boardspec('sandbox') @pytest.mark.buildconfigspec('fit') @pytest.mark.requiredtool('dtc') @@ -426,3 +456,99 @@ class TestFitImage: output = ubman.run_command_list(cmds) assert "can't get kernel image!" in '\n'.join(output) + + def test_fit_kernel_noload_decomp_overflow(self, ubman, fsetup): + """Test that an over-large compressed kernel_noload image is rejected + + For a compressed 'kernel_noload' kernel, bootm_load_os() allocates a + decompression buffer of ALIGN(image_len * 4, SZ_1M) and must bound the + decompressor by that buffer. A kernel that decompresses to far more + than four times its compressed size must therefore fail with a + decompression error instead of overflowing the buffer. + """ + sz_1m = 1 << 20 + + # CONFIG_SYS_BOOTM_LEN is the global decompression limit. Keep the + # uncompressed size below it, so the failure is forced by the smaller + # per-image kernel_noload buffer rather than by that global limit. + bootm_len = int(ubman.config.buildconfig['config_sys_bootm_len'], 0) + + # 4MB of zeros compresses to a few KB, so the decompression buffer + # (ALIGN(image_len * 4, SZ_1M), i.e. 1MB here) ends up far smaller + # than the uncompressed image. + decomp_size = 4 * sz_1m + kernel = fit_util.make_fname(ubman, 'test-noload-kernel.bin') + with open(kernel, 'wb') as fd: + fd.write(b'\0' * decomp_size) + kernel_gz = self.make_compressed(ubman, kernel) + + image_len = self.filesize(kernel_gz) + req_size = (image_len * 4 + sz_1m - 1) // sz_1m * sz_1m + assert req_size < decomp_size <= bootm_len, ( + 'Test setup error: need decomp buffer (%#x) < image (%#x) <= ' + 'CONFIG_SYS_BOOTM_LEN (%#x)' % (req_size, decomp_size, bootm_len)) + + fit = fit_util.make_fit(ubman, fsetup['mkimage'], NOLOAD_ITS, + {'kernel': kernel_gz}) + fit_addr = fsetup['fit_addr'] + + ubman.run_command_list([ + 'host load hostfs 0 %x %s' % (fit_addr, fit), + 'bootm start %x' % fit_addr, + ]) + + # 'bootm loados' decompresses the kernel. Decompression must stop at + # the buffer boundary and report 'Image too large', which resets the + # board; it must not run past the buffer and return to the prompt. + ubman.run_command('bootm loados', wait_for_prompt=False) + matched = ubman.p.expect(['Image too large', ubman.prompt_compiled]) + + # The decompression failure resets the board; bring up a fresh + # instance so later tests start from a clean console. + ubman.restart_uboot() + + assert matched == 0, \ + "'bootm loados' did not reject a kernel_noload image whose " \ + 'decompressed size overflows its decompression buffer' + + def test_fit_kernel_noload_decomp_boundary(self, ubman, fsetup): + """Test that decompression succeeds exactly at the buffer limit + + For a compressed 'kernel_noload' kernel, bootm_load_os() allocates a + decompression buffer of ALIGN(image_len * 4, SZ_1M). A kernel whose + decompressed size equals that buffer exactly must succeed, guarding + against an off-by-one rejection at the buffer limit. + """ + sz_1m = 1 << 20 + + # 1MiB of zeros compresses to a few KB, so image_len * 4 rounds up to + # exactly 1MiB. Picking decomp_size = 1MiB makes the decompressed size + # match the buffer exactly. + decomp_size = sz_1m + kernel = fit_util.make_fname(ubman, 'test-noload-kernel-boundary.bin') + with open(kernel, 'wb') as fd: + fd.write(b'\0' * decomp_size) + kernel_gz = self.make_compressed(ubman, kernel) + + image_len = self.filesize(kernel_gz) + req_size = (image_len * 4 + sz_1m - 1) // sz_1m * sz_1m + assert decomp_size == req_size, ( + 'Test setup error: need decomp_size (%#x) == req_size (%#x)' + % (decomp_size, req_size)) + + fit = fit_util.make_fit(ubman, fsetup['mkimage'], NOLOAD_ITS, + {'kernel': kernel_gz}, + basename='test-noload-boundary.fit') + fit_addr = fsetup['fit_addr'] + + # Decompression at the buffer limit must succeed, returning to the + # prompt cleanly and never printing 'Image too large'. + output = ubman.run_command_list([ + 'host load hostfs 0 %x %s' % (fit_addr, fit), + 'bootm start %x' % fit_addr, + 'bootm loados', + ]) + text = '\n'.join(output) + assert 'Image too large' not in text, ( + "'bootm loados' rejected a kernel_noload image whose decompressed " + 'size matches its buffer exactly: %s' % text) -- 2.43.0

