Re: [PATCH] introduce CONFIG_DEVICE_TREE_INCLUDES

Rasmus Villemoes
2022-01-24T10:42:17Z

On 14/01/2022 17.51, Simon Glass wrote:
> Hi Rasmus,
>
> On Tue, 26 Oct 2021 at 02:08, Rasmus Villemoes
> <rasmus.villemoes@prevas.dk> wrote:
>>
>> On 26/10/2021 03.28, Simon Glass wrote:
>>> Hi Rasmus,
>>>
>>> On Tue, 28 Sept 2021 at 02:57, Rasmus Villemoes
>>> <rasmus.villemoes@prevas.dk> wrote:
>>>>
>>>> The build system already automatically looks for and includes an
>>>> in-tree *-u-boot.dtsi when building the control .dtb. However, there
>>>> are some things that are awkward to maintain in such an in-tree file,
>>>> most notably the metadata associated to public keys used for verified
>>>> boot.
>>>>
>>>> The only "official" API to get that metadata into the .dtb is via
>>>> mkimage, as a side effect of building an actual signed image. But
>>>> there are multiple problems with that. First of all, the final U-Boot
>>>> (be it U-Boot proper or an SPL) image is built based on a binary
>>>> image, the .dtb, and possibly some other binary artifacts. So
>>>> modifying the .dtb after the build requires the meta-buildsystem
>>>> (Yocto, buildroot, whatnot) to know about and repeat some of the steps
>>>> that are already known to and handled by U-Boot's build system,
>>>> resulting in needless duplication of code. It's also somewhat annoying
>>>> and inconsistent to have a .dtb file in the build folder which is not
>>>> generated by the command listed in the corresponding .cmd file (that
>>>> of course applies to any generated file).
>>>>
>>>> So the contents of the /signature node really needs to be baked into
>>>> the .dtb file when it is first created, which means providing the
>>>> relevant data in the form of a .dtsi file. One could in theory put
>>>> that data into the *-u-boot.dtsi file, but it's more convenient to be
>>>> able to provide it externally: For example, when developing for a
>>>> customer, it's common to use a set of dummy keys for development,
>>>> while the consultants do not (and should not) have access to the
>>>> actual keys used in production. For such a setup, it's easier if the
>>>> keys used are chosen via the meta-buildsystem and the path(s) patched
>>>> in during the configure step. And of course, nothing prevents anybody
>>>> from having DEVICE_TREE_INCLUDES point at files maintained in git, or
>>>> for that matter from including the public key metadata in the
>>>> *-u-boot.dtsi directly and ignore this feature.
>>>>
>>>> There are other uses for this, e.g. in combination with ENV_IMPORT_FDT
>>>> it can be used for providing the contents of the /config/environment
>>>> node, so I don't want to tie this exclusively to use for verified
>>>> boot.
>>>>
>>>> Signed-off-by: Rasmus Villemoes <rasmus.villemoes@prevas.dk>
>>>> ---
>>>>
>>>> Getting the public key metadata into .dtsi form can be done with a
>>>> little scripting (roughly 'mkimage -K' of a dummy image followed by
>>>> 'dtc -I dtb -O dts'). I have a script that does that, along with
>>>> options to set 'required' and 'required-mode' properties, include
>>>> u-boot,dm-spl properties in case one wants to check U-Boot proper from
>>>> SPL with the verified boot mechanism, etc. I'm happy to share that
>>>> script if this gets accepted, but it's moot if this is rejected.
>>>>
>>>> I have previously tried to get an fdt_add_pubkey tool accepted [1,2]
>>>> to disentangle the kernel and u-boot builds (or u-boot and SPL builds
>>>> for that matter!), but as I've since realized, that isn't quite enough
>>>> - the above points re modifying the .dtb after it is created but
>>>> before that is used to create further build artifacts still
>>>> stand. However, such a tool could still be useful for creating the
>>>> .dtsi info without the private keys being present, and my key2dtsi.sh
>>>> script could easily be modified to use a tool like that should it ever
>>>> appear.
>>>>
>>>> [1] https://lore.kernel.org/u-boot/20200211094818.14219-3-rasmus.villemoes@prevas.dk/
>>>> [2] https://lore.kernel.org/u-boot/2184f1e6dd6247e48133c76205feeabe@kaspersky.com/
>>>>
>>>> dts/Kconfig | 9 +++++++++
>>>> scripts/Makefile.lib | 2 ++
>>>> 2 files changed, 11 insertions(+)
>>>
>>> Reviewed-by: Simon Glass <sjg@chromium.org>
>>>
>>> I suggest adding this to the documentation somewhere in doc/develop/devicetree/
>>
>> Will do.
>>
>>> Getting the key into the U-Boot .dtb is normally done with mkimage, as
>>> you say. I don't really understand why this approach is easier.
>>
>> I think I explained that in the commit message, but let me try with a
>> more concrete example. I'm building, using Yocto as the umbrella build
>> system, for a SOC that needs an SPL + U-Boot proper.
>>
>> So I have a U-Boot recipe that handles the configuration and
>> compilation. That's mostly a matter of "make foo_defconfig" followed by
>> "make ${UBOOT_TARGETS}" where UBOOT_TARGETS is something set in the
>> machine config. That results in two artifacts, say SPL and u-boot.itb
>> (the names don't really matter) that are almost ready to be written to
>> whatever storage is used for them. Most likely, the SPL binary is built
>> from a u-boot-spl-nodtb.bin and a spl.dtb and perhaps there's some
>> SOC-specific header in front of it all that the hardware/firmware needs,
>> those details are hidden by U-Boot's build system invoking the right
>> flavour of mkimage with the right parameters. If I really want to know,
>> I can look in spl/.SPL.cmd to see just how it was made [unless it's
>> binman creating a flash.bin, because then it's just black magic]. But I
>> usually don't need to.
>>
>> Enter verified boot. SPL needs to verify U-Boot, and U-Boot must in turn
>> verify the kernel. I can easily, as a post-compile step, create a signed
>> version of u-boot.itb. But the -K option is rather useless here, because
>> SPL has already been built. So if I were to only add the corresponding
>> public key to SPL's dtb after/while signing U-Boot proper, I'd have to
>> manually repeat the step(s) needed to create SPL in the first place. Not
>> to mention that the .dtb living inside u-boot.itb doesn't have the
>> public key needed for verifying the kernel, so I'd _actually_ first have
>> to repeat whatever steps were done to create u-boot.itb, after using
>> mkimage to sign some dummy image just to use the -K option to modify
>> u-boot.dtb. It's really cumbersome.
>>
>> So part of the problem is this "you can only get the public keys in the
>> form required inside the .dtb as a side-effect of signing an image". I
>> believe that's a fundamental design mistake, hence my attempt at
>> creating the fdt_add_pubkey tool. But even with that available, adding
>> the pubkey info into an already-compiled .dtb isn't really enough,
>> because the .dtb gets built as part of a normal "make". Hence the need
>> for a way to ensure the pubkey info gets baked into that .dtb during the
>> build.
>>
>> Yes, I then need to prepare proper .dtsi files. But since key generation
>> is a mostly one-time/manual thing, that easily goes along with the
>> instructions (or script) that generates those, and for every foo.crt,
>> I'll just have that directory also contain a foo.dtsi, which I can then
>> point at during do_configure.
>>
>>> Also, is there any interest in using binman?
>>
>> Not really. I mean, it's fine if U-Boot internally uses that, and I can
>> just take the final output and use that. But as long as binman doesn't
>> play nice with Kbuild and e.g. tells the commands that were used to
>> create a given binary, and pollutes the build dir with stuff that's not
>> removed by "make clean", I'm not very enthusiastic about adding more
>> uses myself.
>>
>> Also, this:
>>
>> BINMAN all
>> Image 'main-section' is missing external blobs and is non-functional:
>> blob-ext@3
>>
>> Some images are invalid
>> $ echo $?
>> 0
>>
>> Really? When building manually, perhaps the developer sees that warning
>> (unless there were other non-BINMAN targets that make decided to build
>> later, making the above scroll away...), but it's not very useful in
>> some CI scenario where artifacts get deployed automatically to a test
>> system after a successful build. Recovering boards with a broken
>> bootloader easily costs many man-hours, plus the time to figure out
>> what's wrong.
>
> I still think binman is the best solution here. The points you are
> raising are annoyances that can be resolved.
>
> We could return a non-zero value from binman when external blobs are
> missing; we'd just need to use a special value (we use 101 for
> buildman) and update the CI to detect and ignore that.

Eh, does make(1) exit with a code that depends on the exit code from a
failing target? What if multiple targets failed to build? Unless one can
quote chapter-and-verse from make documentation, I don't think that's a
particularly robust solution.

> Normally people use a separate output directory from the source
> directory, which is why the 'pollution' is not normally a big problem.

Well, build systems like Yocto do set up a separate build dir, but
that's not really important, as I rarely look inside ${S} nor ${B} - but
when doing my own tinkering, I almost always build in the source tree,
simply because that's a lot faster for quick experiments. So I'd wish
all binman's temporary files were stowed away in some .binman_tmp dir in
the top build dir (whether that's the source dir or an out-of-tree dir).
Commits like 824204e42 are a strong indication that the current state of
things is broken and doesn't scale.

> But it is possible to resolve that problem and it has been discussed
> on the mailing list. It's just that no one has done it yet.
>
> In what way does binman play badly with Kbuild?

My main grievance is that when binman is merely a thin wrapper around
some external tool (often mkimage or one of its derivates), there's no
.<target>.cmd file generated allowing me to see just what command was
run to generate <target>. Another thing is that I can't do "make <target>".

> I'd really suggest changing the mindset to separating build from
> packaging, perhaps by having a separate yocto package/recipe that puts
> the firmware together, adds the signature, etc.

Oh, I couldn't agree more, and in fact I've already done that for all
the kernels I build; Yocto's kernel-fitimage.bbclass is simply too
inflexible to use directly from the kernel recipe, so I just build an
Image (or Image.gz) in the kernel recipe, then have a separate
kernel-fit.bb recipe for actually assembling the different FIT images I
need (often one for normal use and another for bootstrapping, but
possibly more).

But I really can't see how binman helps in any way or form - in fact, it
obscures the process of creating such a separate recipe, exactly because
I can't extract the necessary magic invocation of mkimage that is needed
to wrap SPL in the header format expected by the hardware.

And the thing about "adding the signature" - yes, indeed, _signing_ can
and should be done after building. But that is not at all what this
started with, this is about embedding the metadata that U-Boot (or SPL)
will need for _verifying_ during the build itself - when the private key
may not even be available. Again, I think that it's a fundamental design
bug that generating and adding that metadata in the form needed by
U-Boot can only be done as a side effect of signing some unrelated image.

Rasmus