blob: 3902116eeb10e9a4f96da1bec01c1acc3e701a15 [file] [log] [blame]
#!/usr/bin/env python3
#
# Copyright 2020, The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests mkbootimg and unpack_bootimg."""
import filecmp
import logging
import os
import random
import shlex
import subprocess
import sys
import tempfile
import unittest
BOOT_ARGS_OFFSET = 64
BOOT_ARGS_SIZE = 512
BOOT_EXTRA_ARGS_OFFSET = 608
BOOT_EXTRA_ARGS_SIZE = 1024
BOOT_V3_ARGS_OFFSET = 44
VENDOR_BOOT_ARGS_OFFSET = 28
VENDOR_BOOT_ARGS_SIZE = 2048
TEST_KERNEL_CMDLINE = (
'printk.devkmsg=on firmware_class.path=/vendor/etc/ init=/init '
'kfence.sample_interval=500 loop.max_part=7 bootconfig'
)
def generate_test_file(pathname, size, seed=None):
"""Generates a gibberish-filled test file and returns its pathname."""
random.seed(os.path.basename(pathname) if seed is None else seed)
with open(pathname, 'wb') as f:
f.write(random.randbytes(size))
return pathname
def subsequence_of(list1, list2):
"""Returns True if list1 is a subsequence of list2.
>>> subsequence_of([], [1])
True
>>> subsequence_of([2, 4], [1, 2, 3, 4])
True
>>> subsequence_of([1, 2, 2], [1, 2, 3])
False
"""
if len(list1) == 0:
return True
if len(list2) == 0:
return False
if list1[0] == list2[0]:
return subsequence_of(list1[1:], list2[1:])
return subsequence_of(list1, list2[1:])
class MkbootimgTest(unittest.TestCase):
"""Tests the functionalities of mkbootimg and unpack_bootimg."""
def setUp(self):
# Saves the test executable directory so that relative path references
# to test dependencies don't rely on being manually run from the
# executable directory.
# With this, we can just open "./tests/data/testkey_rsa2048.pem" in the
# following tests with subprocess.run(..., cwd=self._exec_dir, ...).
self._exec_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
self._avbtool_path = os.path.join(self._exec_dir, 'avbtool')
# Set self.maxDiff to None to see full diff in assertion.
# C0103: invalid-name for maxDiff.
self.maxDiff = None # pylint: disable=C0103
def _test_legacy_boot_image_v4_signature(self, avbtool_path):
"""Tests the boot_signature in boot.img v4."""
with tempfile.TemporaryDirectory() as temp_out_dir:
boot_img = os.path.join(temp_out_dir, 'boot.img')
kernel = generate_test_file(os.path.join(temp_out_dir, 'kernel'),
0x1000)
ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
0x1000)
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '4',
'--kernel', kernel,
'--ramdisk', ramdisk,
'--cmdline', TEST_KERNEL_CMDLINE,
'--os_version', '11.0.0',
'--os_patch_level', '2021-01',
'--gki_signing_algorithm', 'SHA256_RSA2048',
'--gki_signing_key', './tests/data/testkey_rsa2048.pem',
'--gki_signing_signature_args',
'--prop foo:bar --prop gki:nice',
'--output', boot_img,
]
if avbtool_path:
mkbootimg_cmds.extend(
['--gki_signing_avbtool_path', avbtool_path])
unpack_bootimg_cmds = [
'unpack_bootimg',
'--boot_img', boot_img,
'--out', os.path.join(temp_out_dir, 'out'),
]
# cwd=self._exec_dir is required to read
# ./tests/data/testkey_rsa2048.pem for --gki_signing_key.
subprocess.run(mkbootimg_cmds, check=True, cwd=self._exec_dir)
subprocess.run(unpack_bootimg_cmds, check=True)
# Checks the content of the boot signature.
expected_boot_signature_info = (
'Minimum libavb version: 1.0\n'
'Header Block: 256 bytes\n'
'Authentication Block: 320 bytes\n'
'Auxiliary Block: 832 bytes\n'
'Public key (sha1): '
'cdbb77177f731920bbe0a0f94f84d9038ae0617d\n'
'Algorithm: SHA256_RSA2048\n'
'Rollback Index: 0\n'
'Flags: 0\n'
'Rollback Index Location: 0\n'
"Release String: 'avbtool 1.3.0'\n"
'Descriptors:\n'
' Hash descriptor:\n'
' Image Size: 12288 bytes\n'
' Hash Algorithm: sha256\n'
' Partition Name: boot\n'
' Salt: d00df00d\n'
' Digest: '
'cf3755630856f23ab70e501900050fee'
'f30b633b3e82a9085a578617e344f9c7\n'
' Flags: 0\n'
" Prop: foo -> 'bar'\n"
" Prop: gki -> 'nice'\n"
)
avbtool_info_cmds = [
# use avbtool_path if it is not None.
avbtool_path or 'avbtool',
'info_image', '--image',
os.path.join(temp_out_dir, 'out', 'boot_signature')
]
result = subprocess.run(avbtool_info_cmds, check=True,
capture_output=True, encoding='utf-8')
self.assertEqual(result.stdout, expected_boot_signature_info)
def test_legacy_boot_image_v4_signature_without_avbtool_path(self):
"""Boot signature generation without --gki_signing_avbtool_path."""
self._test_legacy_boot_image_v4_signature(avbtool_path=None)
def test_legacy_boot_image_v4_signature_with_avbtool_path(self):
"""Boot signature generation with --gki_signing_avbtool_path."""
self._test_legacy_boot_image_v4_signature(
avbtool_path=self._avbtool_path)
def test_legacy_boot_image_v4_signature_exceed_size(self):
"""Tests the boot signature size exceeded in a boot image version 4."""
with tempfile.TemporaryDirectory() as temp_out_dir:
boot_img = os.path.join(temp_out_dir, 'boot.img')
kernel = generate_test_file(os.path.join(temp_out_dir, 'kernel'),
0x1000)
ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
0x1000)
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '4',
'--kernel', kernel,
'--ramdisk', ramdisk,
'--cmdline', TEST_KERNEL_CMDLINE,
'--os_version', '11.0.0',
'--os_patch_level', '2021-01',
'--gki_signing_avbtool_path', self._avbtool_path,
'--gki_signing_algorithm', 'SHA256_RSA2048',
'--gki_signing_key', './tests/data/testkey_rsa2048.pem',
'--gki_signing_signature_args',
# Makes it exceed the signature max size.
'--prop foo:bar --prop gki:nice ' * 64,
'--output', boot_img,
]
# cwd=self._exec_dir is required to read
# ./tests/data/testkey_rsa2048.pem for --gki_signing_key.
try:
subprocess.run(mkbootimg_cmds, check=True, capture_output=True,
cwd=self._exec_dir, encoding='utf-8')
self.fail('Exceeding signature size assertion is not raised')
except subprocess.CalledProcessError as e:
self.assertIn('ValueError: boot sigature size is > 4096',
e.stderr)
def test_boot_image_v4_signature_empty(self):
"""Tests no boot signature in a boot image version 4."""
with tempfile.TemporaryDirectory() as temp_out_dir:
boot_img = os.path.join(temp_out_dir, 'boot.img')
kernel = generate_test_file(os.path.join(temp_out_dir, 'kernel'),
0x1000)
ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
0x1000)
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '4',
'--kernel', kernel,
'--ramdisk', ramdisk,
'--cmdline', TEST_KERNEL_CMDLINE,
'--os_version', '11.0.0',
'--os_patch_level', '2021-01',
'--output', boot_img,
]
unpack_bootimg_cmds = [
'unpack_bootimg',
'--boot_img', boot_img,
'--out', os.path.join(temp_out_dir, 'out'),
]
subprocess.run(mkbootimg_cmds, check=True)
subprocess.run(unpack_bootimg_cmds, check=True)
# The boot signature will be empty if no
# --gki_signing_[algorithm|key] is provided.
boot_signature = os.path.join(temp_out_dir, 'out', 'boot_signature')
self.assertFalse(os.path.exists(boot_signature))
def test_vendor_boot_v4(self):
"""Tests vendor_boot version 4."""
with tempfile.TemporaryDirectory() as temp_out_dir:
vendor_boot_img = os.path.join(temp_out_dir, 'vendor_boot.img')
dtb = generate_test_file(os.path.join(temp_out_dir, 'dtb'), 0x1000)
ramdisk1 = generate_test_file(
os.path.join(temp_out_dir, 'ramdisk1'), 0x1000)
ramdisk2 = generate_test_file(
os.path.join(temp_out_dir, 'ramdisk2'), 0x2000)
bootconfig = generate_test_file(
os.path.join(temp_out_dir, 'bootconfig'), 0x1000)
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '4',
'--vendor_boot', vendor_boot_img,
'--dtb', dtb,
'--vendor_ramdisk', ramdisk1,
'--ramdisk_type', 'PLATFORM',
'--ramdisk_name', 'RAMDISK1',
'--vendor_ramdisk_fragment', ramdisk1,
'--ramdisk_type', 'DLKM',
'--ramdisk_name', 'RAMDISK2',
'--board_id0', '0xC0FFEE',
'--board_id15', '0x15151515',
'--vendor_ramdisk_fragment', ramdisk2,
'--vendor_cmdline', TEST_KERNEL_CMDLINE,
'--vendor_bootconfig', bootconfig,
]
unpack_bootimg_cmds = [
'unpack_bootimg',
'--boot_img', vendor_boot_img,
'--out', os.path.join(temp_out_dir, 'out'),
]
expected_output = [
'boot magic: VNDRBOOT',
'vendor boot image header version: 4',
'vendor ramdisk total size: 16384',
f'vendor command line args: {TEST_KERNEL_CMDLINE}',
'dtb size: 4096',
'vendor ramdisk table size: 324',
'size: 4096', 'offset: 0', 'type: 0x1', 'name:',
'0x00000000, 0x00000000, 0x00000000, 0x00000000,',
'0x00000000, 0x00000000, 0x00000000, 0x00000000,',
'0x00000000, 0x00000000, 0x00000000, 0x00000000,',
'0x00000000, 0x00000000, 0x00000000, 0x00000000,',
'size: 4096', 'offset: 4096', 'type: 0x1', 'name: RAMDISK1',
'0x00000000, 0x00000000, 0x00000000, 0x00000000,',
'0x00000000, 0x00000000, 0x00000000, 0x00000000,',
'0x00000000, 0x00000000, 0x00000000, 0x00000000,',
'0x00000000, 0x00000000, 0x00000000, 0x00000000,',
'size: 8192', 'offset: 8192', 'type: 0x3', 'name: RAMDISK2',
'0x00c0ffee, 0x00000000, 0x00000000, 0x00000000,',
'0x00000000, 0x00000000, 0x00000000, 0x00000000,',
'0x00000000, 0x00000000, 0x00000000, 0x00000000,',
'0x00000000, 0x00000000, 0x00000000, 0x15151515,',
'vendor bootconfig size: 4096',
]
subprocess.run(mkbootimg_cmds, check=True)
result = subprocess.run(unpack_bootimg_cmds, check=True,
capture_output=True, encoding='utf-8')
output = [line.strip() for line in result.stdout.splitlines()]
if not subsequence_of(expected_output, output):
msg = '\n'.join([
'Unexpected unpack_bootimg output:',
'Expected:',
' ' + '\n '.join(expected_output),
'',
'Actual:',
' ' + '\n '.join(output),
])
self.fail(msg)
def test_unpack_vendor_boot_image_v4(self):
"""Tests that mkbootimg(unpack_bootimg(image)) is an identity."""
with tempfile.TemporaryDirectory() as temp_out_dir:
vendor_boot_img = os.path.join(temp_out_dir, 'vendor_boot.img')
vendor_boot_img_reconstructed = os.path.join(
temp_out_dir, 'vendor_boot.img.reconstructed')
dtb = generate_test_file(os.path.join(temp_out_dir, 'dtb'), 0x1000)
ramdisk1 = generate_test_file(
os.path.join(temp_out_dir, 'ramdisk1'), 0x121212)
ramdisk2 = generate_test_file(
os.path.join(temp_out_dir, 'ramdisk2'), 0x212121)
bootconfig = generate_test_file(
os.path.join(temp_out_dir, 'bootconfig'), 0x1000)
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '4',
'--vendor_boot', vendor_boot_img,
'--dtb', dtb,
'--vendor_ramdisk', ramdisk1,
'--ramdisk_type', 'PLATFORM',
'--ramdisk_name', 'RAMDISK1',
'--vendor_ramdisk_fragment', ramdisk1,
'--ramdisk_type', 'DLKM',
'--ramdisk_name', 'RAMDISK2',
'--board_id0', '0xC0FFEE',
'--board_id15', '0x15151515',
'--vendor_ramdisk_fragment', ramdisk2,
'--vendor_cmdline', TEST_KERNEL_CMDLINE,
'--vendor_bootconfig', bootconfig,
]
unpack_bootimg_cmds = [
'unpack_bootimg',
'--boot_img', vendor_boot_img,
'--out', os.path.join(temp_out_dir, 'out'),
'--format=mkbootimg',
]
subprocess.run(mkbootimg_cmds, check=True)
result = subprocess.run(unpack_bootimg_cmds, check=True,
capture_output=True, encoding='utf-8')
mkbootimg_cmds = [
'mkbootimg',
'--vendor_boot', vendor_boot_img_reconstructed,
]
unpack_format_args = shlex.split(result.stdout)
mkbootimg_cmds.extend(unpack_format_args)
subprocess.run(mkbootimg_cmds, check=True)
self.assertTrue(
filecmp.cmp(vendor_boot_img, vendor_boot_img_reconstructed),
'reconstructed vendor_boot image differ from the original')
# Also check that -0, --null are as expected.
unpack_bootimg_cmds.append('--null')
result = subprocess.run(unpack_bootimg_cmds, check=True,
capture_output=True, encoding='utf-8')
unpack_format_null_args = result.stdout
self.assertEqual('\0'.join(unpack_format_args) + '\0',
unpack_format_null_args)
def test_unpack_vendor_boot_image_v3(self):
"""Tests that mkbootimg(unpack_bootimg(image)) is an identity."""
with tempfile.TemporaryDirectory() as temp_out_dir:
vendor_boot_img = os.path.join(temp_out_dir, 'vendor_boot.img')
vendor_boot_img_reconstructed = os.path.join(
temp_out_dir, 'vendor_boot.img.reconstructed')
dtb = generate_test_file(os.path.join(temp_out_dir, 'dtb'), 0x1000)
ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
0x121212)
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '3',
'--vendor_boot', vendor_boot_img,
'--vendor_ramdisk', ramdisk,
'--dtb', dtb,
'--vendor_cmdline', TEST_KERNEL_CMDLINE,
'--board', 'product_name',
'--base', '0x00000000',
'--dtb_offset', '0x01f00000',
'--kernel_offset', '0x00008000',
'--pagesize', '0x00001000',
'--ramdisk_offset', '0x01000000',
'--tags_offset', '0x00000100',
]
unpack_bootimg_cmds = [
'unpack_bootimg',
'--boot_img', vendor_boot_img,
'--out', os.path.join(temp_out_dir, 'out'),
'--format=mkbootimg',
]
subprocess.run(mkbootimg_cmds, check=True)
result = subprocess.run(unpack_bootimg_cmds, check=True,
capture_output=True, encoding='utf-8')
mkbootimg_cmds = [
'mkbootimg',
'--vendor_boot', vendor_boot_img_reconstructed,
]
mkbootimg_cmds.extend(shlex.split(result.stdout))
subprocess.run(mkbootimg_cmds, check=True)
self.assertTrue(
filecmp.cmp(vendor_boot_img, vendor_boot_img_reconstructed),
'reconstructed vendor_boot image differ from the original')
def test_unpack_boot_image_v4(self):
"""Tests that mkbootimg(unpack_bootimg(image)) is an identity."""
with tempfile.TemporaryDirectory() as temp_out_dir:
boot_img = os.path.join(temp_out_dir, 'boot.img')
boot_img_reconstructed = os.path.join(
temp_out_dir, 'boot.img.reconstructed')
kernel = generate_test_file(os.path.join(temp_out_dir, 'kernel'),
0x1000)
ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
0x1000)
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '4',
'--kernel', kernel,
'--ramdisk', ramdisk,
'--cmdline', TEST_KERNEL_CMDLINE,
'--output', boot_img,
]
unpack_bootimg_cmds = [
'unpack_bootimg',
'--boot_img', boot_img,
'--out', os.path.join(temp_out_dir, 'out'),
'--format=mkbootimg',
]
subprocess.run(mkbootimg_cmds, check=True)
result = subprocess.run(unpack_bootimg_cmds, check=True,
capture_output=True, encoding='utf-8')
mkbootimg_cmds = [
'mkbootimg',
'--out', boot_img_reconstructed,
]
mkbootimg_cmds.extend(shlex.split(result.stdout))
subprocess.run(mkbootimg_cmds, check=True)
self.assertTrue(
filecmp.cmp(boot_img, boot_img_reconstructed),
'reconstructed boot image differ from the original')
def test_unpack_boot_image_v3(self):
"""Tests that mkbootimg(unpack_bootimg(image)) is an identity."""
with tempfile.TemporaryDirectory() as temp_out_dir:
boot_img = os.path.join(temp_out_dir, 'boot.img')
boot_img_reconstructed = os.path.join(
temp_out_dir, 'boot.img.reconstructed')
kernel = generate_test_file(os.path.join(temp_out_dir, 'kernel'),
0x1000)
ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
0x1000)
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '3',
'--kernel', kernel,
'--ramdisk', ramdisk,
'--cmdline', TEST_KERNEL_CMDLINE,
'--os_version', '11.0.0',
'--os_patch_level', '2021-01',
'--output', boot_img,
]
unpack_bootimg_cmds = [
'unpack_bootimg',
'--boot_img', boot_img,
'--out', os.path.join(temp_out_dir, 'out'),
'--format=mkbootimg',
]
subprocess.run(mkbootimg_cmds, check=True)
result = subprocess.run(unpack_bootimg_cmds, check=True,
capture_output=True, encoding='utf-8')
mkbootimg_cmds = [
'mkbootimg',
'--out', boot_img_reconstructed,
]
mkbootimg_cmds.extend(shlex.split(result.stdout))
subprocess.run(mkbootimg_cmds, check=True)
self.assertTrue(
filecmp.cmp(boot_img, boot_img_reconstructed),
'reconstructed boot image differ from the original')
def test_unpack_boot_image_v2(self):
"""Tests that mkbootimg(unpack_bootimg(image)) is an identity."""
with tempfile.TemporaryDirectory() as temp_out_dir:
# Output image path.
boot_img = os.path.join(temp_out_dir, 'boot.img')
boot_img_reconstructed = os.path.join(
temp_out_dir, 'boot.img.reconstructed')
# Creates blank images first.
kernel = generate_test_file(
os.path.join(temp_out_dir, 'kernel'), 0x1000)
ramdisk = generate_test_file(
os.path.join(temp_out_dir, 'ramdisk'), 0x1000)
second = generate_test_file(
os.path.join(temp_out_dir, 'second'), 0x1000)
recovery_dtbo = generate_test_file(
os.path.join(temp_out_dir, 'recovery_dtbo'), 0x1000)
dtb = generate_test_file(
os.path.join(temp_out_dir, 'dtb'), 0x1000)
cmdline = (BOOT_ARGS_SIZE - 1) * 'x'
extra_cmdline = (BOOT_EXTRA_ARGS_SIZE - 1) * 'y'
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '2',
'--base', '0x00000000',
'--kernel', kernel,
'--kernel_offset', '0x00008000',
'--ramdisk', ramdisk,
'--ramdisk_offset', '0x01000000',
'--second', second,
'--second_offset', '0x40000000',
'--recovery_dtbo', recovery_dtbo,
'--dtb', dtb,
'--dtb_offset', '0x01f00000',
'--tags_offset', '0x00000100',
'--pagesize', '0x00001000',
'--os_version', '11.0.0',
'--os_patch_level', '2021-03',
'--board', 'boot_v2',
'--cmdline', cmdline + extra_cmdline,
'--output', boot_img,
]
unpack_bootimg_cmds = [
'unpack_bootimg',
'--boot_img', boot_img,
'--out', os.path.join(temp_out_dir, 'out'),
'--format=mkbootimg',
]
subprocess.run(mkbootimg_cmds, check=True)
result = subprocess.run(unpack_bootimg_cmds, check=True,
capture_output=True, encoding='utf-8')
mkbootimg_cmds = [
'mkbootimg',
'--out', boot_img_reconstructed,
]
mkbootimg_cmds.extend(shlex.split(result.stdout))
subprocess.run(mkbootimg_cmds, check=True)
self.assertTrue(
filecmp.cmp(boot_img, boot_img_reconstructed),
'reconstructed boot image differ from the original')
def test_unpack_boot_image_v1(self):
"""Tests that mkbootimg(unpack_bootimg(image)) is an identity."""
with tempfile.TemporaryDirectory() as temp_out_dir:
# Output image path.
boot_img = os.path.join(temp_out_dir, 'boot.img')
boot_img_reconstructed = os.path.join(
temp_out_dir, 'boot.img.reconstructed')
# Creates blank images first.
kernel = generate_test_file(
os.path.join(temp_out_dir, 'kernel'), 0x1000)
ramdisk = generate_test_file(
os.path.join(temp_out_dir, 'ramdisk'), 0x1000)
recovery_dtbo = generate_test_file(
os.path.join(temp_out_dir, 'recovery_dtbo'), 0x1000)
cmdline = (BOOT_ARGS_SIZE - 1) * 'x'
extra_cmdline = (BOOT_EXTRA_ARGS_SIZE - 1) * 'y'
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '1',
'--base', '0x00000000',
'--kernel', kernel,
'--kernel_offset', '0x00008000',
'--ramdisk', ramdisk,
'--ramdisk_offset', '0x01000000',
'--recovery_dtbo', recovery_dtbo,
'--tags_offset', '0x00000100',
'--pagesize', '0x00001000',
'--os_version', '11.0.0',
'--os_patch_level', '2021-03',
'--board', 'boot_v1',
'--cmdline', cmdline + extra_cmdline,
'--output', boot_img,
]
unpack_bootimg_cmds = [
'unpack_bootimg',
'--boot_img', boot_img,
'--out', os.path.join(temp_out_dir, 'out'),
'--format=mkbootimg',
]
subprocess.run(mkbootimg_cmds, check=True)
result = subprocess.run(unpack_bootimg_cmds, check=True,
capture_output=True, encoding='utf-8')
mkbootimg_cmds = [
'mkbootimg',
'--out', boot_img_reconstructed,
]
mkbootimg_cmds.extend(shlex.split(result.stdout))
subprocess.run(mkbootimg_cmds, check=True)
self.assertTrue(
filecmp.cmp(boot_img, boot_img_reconstructed),
'reconstructed boot image differ from the original')
def test_unpack_boot_image_v0(self):
"""Tests that mkbootimg(unpack_bootimg(image)) is an identity."""
with tempfile.TemporaryDirectory() as temp_out_dir:
# Output image path.
boot_img = os.path.join(temp_out_dir, 'boot.img')
boot_img_reconstructed = os.path.join(
temp_out_dir, 'boot.img.reconstructed')
# Creates blank images first.
kernel = generate_test_file(
os.path.join(temp_out_dir, 'kernel'), 0x1000)
ramdisk = generate_test_file(
os.path.join(temp_out_dir, 'ramdisk'), 0x1000)
second = generate_test_file(
os.path.join(temp_out_dir, 'second'), 0x1000)
cmdline = (BOOT_ARGS_SIZE - 1) * 'x'
extra_cmdline = (BOOT_EXTRA_ARGS_SIZE - 1) * 'y'
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '0',
'--base', '0x00000000',
'--kernel', kernel,
'--kernel_offset', '0x00008000',
'--ramdisk', ramdisk,
'--ramdisk_offset', '0x01000000',
'--second', second,
'--second_offset', '0x40000000',
'--tags_offset', '0x00000100',
'--pagesize', '0x00001000',
'--os_version', '11.0.0',
'--os_patch_level', '2021-03',
'--board', 'boot_v0',
'--cmdline', cmdline + extra_cmdline,
'--output', boot_img,
]
unpack_bootimg_cmds = [
'unpack_bootimg',
'--boot_img', boot_img,
'--out', os.path.join(temp_out_dir, 'out'),
]
unpack_bootimg_cmds = [
'unpack_bootimg',
'--boot_img', boot_img,
'--out', os.path.join(temp_out_dir, 'out'),
'--format=mkbootimg',
]
subprocess.run(mkbootimg_cmds, check=True)
result = subprocess.run(unpack_bootimg_cmds, check=True,
capture_output=True, encoding='utf-8')
mkbootimg_cmds = [
'mkbootimg',
'--out', boot_img_reconstructed,
]
mkbootimg_cmds.extend(shlex.split(result.stdout))
subprocess.run(mkbootimg_cmds, check=True)
self.assertTrue(
filecmp.cmp(boot_img, boot_img_reconstructed),
'reconstructed boot image differ from the original')
def test_boot_image_v2_cmdline_null_terminator(self):
"""Tests that kernel commandline is null-terminated."""
with tempfile.TemporaryDirectory() as temp_out_dir:
dtb = generate_test_file(os.path.join(temp_out_dir, 'dtb'), 0x1000)
kernel = generate_test_file(os.path.join(temp_out_dir, 'kernel'),
0x1000)
ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
0x1000)
cmdline = (BOOT_ARGS_SIZE - 1) * 'x'
extra_cmdline = (BOOT_EXTRA_ARGS_SIZE - 1) * 'y'
boot_img = os.path.join(temp_out_dir, 'boot.img')
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '2',
'--dtb', dtb,
'--kernel', kernel,
'--ramdisk', ramdisk,
'--cmdline', cmdline + extra_cmdline,
'--output', boot_img,
]
subprocess.run(mkbootimg_cmds, check=True)
with open(boot_img, 'rb') as f:
raw_boot_img = f.read()
raw_cmdline = raw_boot_img[BOOT_ARGS_OFFSET:][:BOOT_ARGS_SIZE]
raw_extra_cmdline = (raw_boot_img[BOOT_EXTRA_ARGS_OFFSET:]
[:BOOT_EXTRA_ARGS_SIZE])
self.assertEqual(raw_cmdline, cmdline.encode() + b'\x00')
self.assertEqual(raw_extra_cmdline,
extra_cmdline.encode() + b'\x00')
def test_boot_image_v3_cmdline_null_terminator(self):
"""Tests that kernel commandline is null-terminated."""
with tempfile.TemporaryDirectory() as temp_out_dir:
kernel = generate_test_file(os.path.join(temp_out_dir, 'kernel'),
0x1000)
ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
0x1000)
cmdline = BOOT_ARGS_SIZE * 'x' + (BOOT_EXTRA_ARGS_SIZE - 1) * 'y'
boot_img = os.path.join(temp_out_dir, 'boot.img')
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '3',
'--kernel', kernel,
'--ramdisk', ramdisk,
'--cmdline', cmdline,
'--output', boot_img,
]
subprocess.run(mkbootimg_cmds, check=True)
with open(boot_img, 'rb') as f:
raw_boot_img = f.read()
raw_cmdline = (raw_boot_img[BOOT_V3_ARGS_OFFSET:]
[:BOOT_ARGS_SIZE + BOOT_EXTRA_ARGS_SIZE])
self.assertEqual(raw_cmdline, cmdline.encode() + b'\x00')
def test_vendor_boot_image_v3_cmdline_null_terminator(self):
"""Tests that kernel commandline is null-terminated."""
with tempfile.TemporaryDirectory() as temp_out_dir:
dtb = generate_test_file(os.path.join(temp_out_dir, 'dtb'), 0x1000)
ramdisk = generate_test_file(os.path.join(temp_out_dir, 'ramdisk'),
0x1000)
vendor_cmdline = (VENDOR_BOOT_ARGS_SIZE - 1) * 'x'
vendor_boot_img = os.path.join(temp_out_dir, 'vendor_boot.img')
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '3',
'--dtb', dtb,
'--vendor_ramdisk', ramdisk,
'--vendor_cmdline', vendor_cmdline,
'--vendor_boot', vendor_boot_img,
]
subprocess.run(mkbootimg_cmds, check=True)
with open(vendor_boot_img, 'rb') as f:
raw_vendor_boot_img = f.read()
raw_vendor_cmdline = (raw_vendor_boot_img[VENDOR_BOOT_ARGS_OFFSET:]
[:VENDOR_BOOT_ARGS_SIZE])
self.assertEqual(raw_vendor_cmdline,
vendor_cmdline.encode() + b'\x00')
def test_vendor_boot_v4_without_dtb(self):
"""Tests building vendor_boot version 4 without dtb image."""
with tempfile.TemporaryDirectory() as temp_out_dir:
vendor_boot_img = os.path.join(temp_out_dir, 'vendor_boot.img')
ramdisk = generate_test_file(
os.path.join(temp_out_dir, 'ramdisk'), 0x1000)
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '4',
'--vendor_boot', vendor_boot_img,
'--vendor_ramdisk', ramdisk,
]
unpack_bootimg_cmds = [
'unpack_bootimg',
'--boot_img', vendor_boot_img,
'--out', os.path.join(temp_out_dir, 'out'),
]
expected_output = [
'boot magic: VNDRBOOT',
'vendor boot image header version: 4',
'dtb size: 0',
]
subprocess.run(mkbootimg_cmds, check=True)
result = subprocess.run(unpack_bootimg_cmds, check=True,
capture_output=True, encoding='utf-8')
output = [line.strip() for line in result.stdout.splitlines()]
if not subsequence_of(expected_output, output):
msg = '\n'.join([
'Unexpected unpack_bootimg output:',
'Expected:',
' ' + '\n '.join(expected_output),
'',
'Actual:',
' ' + '\n '.join(output),
])
self.fail(msg)
def test_unpack_vendor_boot_image_v4_without_dtb(self):
"""Tests that mkbootimg(unpack_bootimg(image)) is an identity when no dtb image."""
with tempfile.TemporaryDirectory() as temp_out_dir:
vendor_boot_img = os.path.join(temp_out_dir, 'vendor_boot.img')
vendor_boot_img_reconstructed = os.path.join(
temp_out_dir, 'vendor_boot.img.reconstructed')
ramdisk = generate_test_file(
os.path.join(temp_out_dir, 'ramdisk'), 0x121212)
mkbootimg_cmds = [
'mkbootimg',
'--header_version', '4',
'--vendor_boot', vendor_boot_img,
'--vendor_ramdisk', ramdisk,
]
unpack_bootimg_cmds = [
'unpack_bootimg',
'--boot_img', vendor_boot_img,
'--out', os.path.join(temp_out_dir, 'out'),
'--format=mkbootimg',
]
subprocess.run(mkbootimg_cmds, check=True)
result = subprocess.run(unpack_bootimg_cmds, check=True,
capture_output=True, encoding='utf-8')
mkbootimg_cmds = [
'mkbootimg',
'--vendor_boot', vendor_boot_img_reconstructed,
]
unpack_format_args = shlex.split(result.stdout)
mkbootimg_cmds.extend(unpack_format_args)
subprocess.run(mkbootimg_cmds, check=True)
self.assertTrue(
filecmp.cmp(vendor_boot_img, vendor_boot_img_reconstructed),
'reconstructed vendor_boot image differ from the original')
# I don't know how, but we need both the logger configuration and verbosity
# level > 2 to make atest work. And yes this line needs to be at the very top
# level, not even in the "__main__" indentation block.
logging.basicConfig(stream=sys.stdout)
if __name__ == '__main__':
unittest.main(verbosity=2)