diff options
author | Lennart Poettering <lennart@poettering.net> | 2024-07-02 10:23:07 +0200 |
---|---|---|
committer | Lennart Poettering <lennart@poettering.net> | 2024-09-06 16:12:03 +0200 |
commit | b6570095ce889b07242d36cd05fa1d1899d0bc6c (patch) | |
tree | 0e4c48f9a5b491b482e16155690aa739663ce4bf /src/ukify/ukify.py | |
parent | ukify: add basic .profile support (diff) | |
download | systemd-b6570095ce889b07242d36cd05fa1d1899d0bc6c.tar.xz systemd-b6570095ce889b07242d36cd05fa1d1899d0bc6c.zip |
ukify: add new --extend= switch for importing an existing UKI's sections to later extend
This options is pretty simple, it allows specifying an UKI whose
sections to import first, and place at the beginning of the new UKI.
This is useful for generating multi-profile UKIs piecemeal: generate the
base UKI first, then append a profile, and another one and another one.
The sections imported this way are not included in any PCR signature,
the assumption is that that already happened before in the imported UKI.
Diffstat (limited to '')
-rwxr-xr-x | src/ukify/ukify.py | 82 |
1 files changed, 65 insertions, 17 deletions
diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py index c8b6ce401b..b3fab755b6 100755 --- a/src/ukify/ukify.py +++ b/src/ukify/ukify.py @@ -381,7 +381,14 @@ class UKI: sections: list[Section] = dataclasses.field(default_factory=list, init=False) def add_section(self, section): - if section.name in [s.name for s in self.sections]: + start = 0 + + # Start search at last .profile section, if there is one + for i in range(len(self.sections)): + if self.sections[i].name == ".profile": + start = i+1 + + if section.name in [s.name for s in self.sections[start:]]: raise ValueError(f'Duplicate section {section.name}') self.sections += [section] @@ -493,6 +500,10 @@ def key_path_groups(opts): pp_groups) +def pe_strip_section_name(name): + return name.rstrip(b"\x00").decode() + + def call_systemd_measure(uki, linux, opts): measure_tool = find_tool('systemd-measure', '/usr/lib/systemd/systemd-measure', @@ -632,6 +643,9 @@ def pe_add_sections(uki: UKI, output: str): # We could strip the signatures, but why would anyone sign the stub? raise PEError('Stub image is signed, refusing.') + # Remember how many sections originate from systemd-stub + n_original_sections = len(pe.sections) + for section in uki.sections: new_section = pefile.SectionStructure(pe.__IMAGE_SECTION_HEADER_format__, pe=pe) new_section.__unpack__(b'\0' * new_section.sizeof()) @@ -665,8 +679,8 @@ def pe_add_sections(uki: UKI, output: str): # Special case, mostly for .sbat: the stub will already have a .sbat section, but we want to append # the one from the kernel to it. It should be small enough to fit in the existing section, so just # swap the data. - for i, s in enumerate(pe.sections): - if s.Name.rstrip(b"\x00").decode() == section.name: + for i, s in enumerate(pe.sections[:n_original_sections]): + if pe_strip_section_name(s.Name) == section.name: if new_section.Misc_VirtualSize > s.SizeOfRawData: raise PEError(f'Not enough space in existing section {section.name} to append new data.') @@ -702,7 +716,7 @@ def merge_sbat(input_pe: [pathlib.Path], input_text: [str]) -> str: continue for section in pe.sections: - if section.Name.rstrip(b"\x00").decode() == ".sbat": + if pe_strip_section_name(section.Name) == ".sbat": split = section.get_data().rstrip(b"\x00").decode().splitlines() if not split[0].startswith('sbat,'): print(f"{f} does not contain a valid SBAT section, skipping.") @@ -784,6 +798,28 @@ def verify(tool, opts): return tool['output'] in info + +def import_to_extend(uki, opts): + + if opts.extend is None: + return + + import_sections = ('.linux', '.osrel', '.cmdline', '.initrd', + '.ucode', '.splash', '.dtb', '.uname', + '.sbat', '.pcrsig', '.pcrpkey', '.profile') + + pe = pefile.PE(opts.extend, fast_load=True) + + for section in pe.sections: + n = pe_strip_section_name(section.Name) + + if n not in import_sections: + continue + + print(f"Copying section '{n}' from '{opts.extend}': {section.Misc_VirtualSize} bytes") + uki.add_section(Section.create(n, section.get_data(length=section.Misc_VirtualSize), measure=False)) + + def make_uki(opts): # kernel payload signing @@ -848,6 +884,9 @@ def make_uki(opts): format=serialization.PublicFormat.SubjectPublicKeyInfo, ) + # Import an existing UKI for extension + import_to_extend(uki, opts) + sections = [ # name, content, measure? ('.profile', opts.profile, True ), @@ -872,21 +911,22 @@ def make_uki(opts): for section in opts.sections: uki.add_section(section) - if linux is not None: - # Merge the .sbat sections from stub, kernel and parameter, so that revocation can be done on either. - input_pes = [opts.stub, linux] - if not opts.sbat: - opts.sbat = ["""sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md + if opts.extend is None: + if linux is not None: + # Merge the .sbat sections from stub, kernel and parameter, so that revocation can be done on either. + input_pes = [opts.stub, linux] + if not opts.sbat: + opts.sbat = ["""sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md uki,1,UKI,uki,1,https://uapi-group.org/specifications/specs/unified_kernel_image/ """] - else: - # Addons don't use the stub so we add SBAT manually - input_pes = [] - if not opts.sbat: - opts.sbat = ["""sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md + else: + # Addons don't use the stub so we add SBAT manually + input_pes = [] + if not opts.sbat: + opts.sbat = ["""sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md uki-addon,1,UKI Addon,addon,1,https://www.freedesktop.org/software/systemd/man/latest/systemd-stub.html """] - uki.add_section(Section.create('.sbat', merge_sbat(input_pes, opts.sbat), measure=linux is not None)) + uki.add_section(Section.create('.sbat', merge_sbat(input_pes, opts.sbat), measure=linux is not None)) # PCR measurement and signing @@ -1043,7 +1083,7 @@ def generate_keys(opts): def inspect_section(opts, section): - name = section.Name.rstrip(b"\x00").decode() + name = pe_strip_section_name(section.Name) # find the config for this section in opts and whether to show it config = opts.sections_by_name.get(name, None) @@ -1384,6 +1424,14 @@ CONFIG_ITEMS = [ ), ConfigItem( + '--extend', + metavar = 'UKI', + type = pathlib.Path, + help = 'path to existing UKI file whose relevant sections to insert into the UKI first', + config_key = 'UKI/Extend', + ), + + ConfigItem( '--pcr-banks', metavar = 'BANKā¦', type = parse_banks, @@ -1687,7 +1735,7 @@ def finalize_options(opts): opts.efi_arch = guess_efi_arch() if opts.stub is None: - if opts.linux is not None: + if opts.linux is not None or opts.extend is not None: opts.stub = pathlib.Path(f'/usr/lib/systemd/boot/efi/linux{opts.efi_arch}.efi.stub') else: opts.stub = pathlib.Path(f'/usr/lib/systemd/boot/efi/addon{opts.efi_arch}.efi.stub') |