diff mbox series

[v2,2/2] xen/arm: Add support for booting gzip compressed uImages

Message ID 20230202084905.6950-3-michal.orzel@amd.com (mailing list archive)
State New, archived
Headers show
Series xen/arm: Support compressed uImages | expand

Commit Message

Michal Orzel Feb. 2, 2023, 8:49 a.m. UTC
At the moment, Xen does not support booting gzip compressed uImages.
This is because we are trying to decompress the kernel before probing
the u-boot header. This leads to a failure as the header always appears
at the top of the image (and therefore obscuring the gzip header).

Move the call to kernel_uimage_probe before kernel_decompress and make
the function self-containing by taking the following actions:
 - take a pointer to struct bootmodule as a parameter,
 - check the comp field of a u-boot header to determine compression type,
 - in case of compressed image, call kernel_decompress passing uImage
   header size as an offset to gzip header,
 - set up zimage.{kernel_addr,len} accordingly,
 - return -ENOENT in case of a u-boot header not found to distinguish it
   amongst other return values and make it the only case for falling
   through to try to probe other image types.

Modify kernel_decompress to take an additional parameter being an offset
to a gzip header from start address. This is needed so that a function
can first operate on a region containing actually compressed kernel (in case
of compressed uImage, size of u-boot header is an offset to a gzip header)
and then at the end pass the entire region (as it was before taking an offset
into account) to fw_unreserved_regions for freeing.

This approach avoids splitting the uImage probing into 2 stages (executed
before and after decompression) which otherwise would be necessary to
properly parse header, update boot module start and size before
decompression and update zimage.{kernel_addr,len} afterwards.

Remove the limitation from the booting.txt documentation.

Signed-off-by: Michal Orzel <michal.orzel@amd.com>
---
Changes in v2:
 - modify kernel_decompress to take an offset to gzip header
---
 docs/misc/arm/booting.txt |  3 --
 xen/arch/arm/kernel.c     | 81 ++++++++++++++++++++++++++++++++++-----
 2 files changed, 72 insertions(+), 12 deletions(-)

Comments

Julien Grall Feb. 2, 2023, 11:01 a.m. UTC | #1
Hi Michal,

On 02/02/2023 08:49, Michal Orzel wrote:
> @@ -265,11 +284,14 @@ static __init int kernel_decompress(struct bootmodule *mod)
>   #define IH_ARCH_ARM             2       /* ARM          */
>   #define IH_ARCH_ARM64           22      /* ARM64        */
>   
> +/* uImage Compression Types */
> +#define IH_COMP_GZIP            1
> +
>   /*
>    * Check if the image is a uImage and setup kernel_info
>    */
>   static int __init kernel_uimage_probe(struct kernel_info *info,
> -                                      paddr_t addr, paddr_t size)
> +                                      struct bootmodule *mod)
>   {
>       struct {
>           __be32 magic;   /* Image Header Magic Number */
> @@ -287,6 +309,8 @@ static int __init kernel_uimage_probe(struct kernel_info *info,
>       } uimage;
>   
>       uint32_t len;
> +    paddr_t addr = mod->start;
> +    paddr_t size = mod->size;
>   
>       if ( size < sizeof(uimage) )
>           return -EINVAL;

Shouldn't we return -ENOENT here?

The rest look good to me.

Cheers,
Michal Orzel Feb. 2, 2023, 11:12 a.m. UTC | #2
Hi Julien,

On 02/02/2023 12:01, Julien Grall wrote:
> 
> 
> Hi Michal,
> 
> On 02/02/2023 08:49, Michal Orzel wrote:
>> @@ -265,11 +284,14 @@ static __init int kernel_decompress(struct bootmodule *mod)
>>   #define IH_ARCH_ARM             2       /* ARM          */
>>   #define IH_ARCH_ARM64           22      /* ARM64        */
>>
>> +/* uImage Compression Types */
>> +#define IH_COMP_GZIP            1
>> +
>>   /*
>>    * Check if the image is a uImage and setup kernel_info
>>    */
>>   static int __init kernel_uimage_probe(struct kernel_info *info,
>> -                                      paddr_t addr, paddr_t size)
>> +                                      struct bootmodule *mod)
>>   {
>>       struct {
>>           __be32 magic;   /* Image Header Magic Number */
>> @@ -287,6 +309,8 @@ static int __init kernel_uimage_probe(struct kernel_info *info,
>>       } uimage;
>>
>>       uint32_t len;
>> +    paddr_t addr = mod->start;
>> +    paddr_t size = mod->size;
>>
>>       if ( size < sizeof(uimage) )
>>           return -EINVAL;
> 
> Shouldn't we return -ENOENT here?
Frankly speaking, I do not want to fall through in such a case.
If a kernel size is less than 64B, something is wrong, isn't it?
I am not sure if Xen would handle a kernel whose size is less than a page.

I do not like the whole falling through in kernel_probe even in case of obvious violations.
But this is something not related to this series so I added to my TODO to properly handle
the return types from kernel_probe path. If you really think, we should return -ENOENT here,
then ok (although I do not like it). Could this be done on commit if you insist on that?

> 
> The rest look good to me.
Thanks,

> 
> Cheers,
> 
> --
> Julien Grall

~Michal
Julien Grall Feb. 2, 2023, 11:23 a.m. UTC | #3
On 02/02/2023 11:12, Michal Orzel wrote:
> Hi Julien,

Hi Michal,

> 
> On 02/02/2023 12:01, Julien Grall wrote:
>>
>>
>> Hi Michal,
>>
>> On 02/02/2023 08:49, Michal Orzel wrote:
>>> @@ -265,11 +284,14 @@ static __init int kernel_decompress(struct bootmodule *mod)
>>>    #define IH_ARCH_ARM             2       /* ARM          */
>>>    #define IH_ARCH_ARM64           22      /* ARM64        */
>>>
>>> +/* uImage Compression Types */
>>> +#define IH_COMP_GZIP            1
>>> +
>>>    /*
>>>     * Check if the image is a uImage and setup kernel_info
>>>     */
>>>    static int __init kernel_uimage_probe(struct kernel_info *info,
>>> -                                      paddr_t addr, paddr_t size)
>>> +                                      struct bootmodule *mod)
>>>    {
>>>        struct {
>>>            __be32 magic;   /* Image Header Magic Number */
>>> @@ -287,6 +309,8 @@ static int __init kernel_uimage_probe(struct kernel_info *info,
>>>        } uimage;
>>>
>>>        uint32_t len;
>>> +    paddr_t addr = mod->start;
>>> +    paddr_t size = mod->size;
>>>
>>>        if ( size < sizeof(uimage) )
>>>            return -EINVAL;
>>
>> Shouldn't we return -ENOENT here?
> Frankly speaking, I do not want to fall through in such a case.
> If a kernel size is less than 64B, something is wrong, isn't it?

I agree something is likely wrong but this should not be the job of 
kernel_uimage_probe() to enforce this for everyone.

To give a concrete example, let's imagine we decide to re-order the call 
so kernel_uimage_probe() happens *after* an new header A than would 
require 128 bytes (the number is made up).

It would be wrong for A to return -EINVAL because the other protocol may 
require a smaller size. The same goes here even at least for consistency.

So if you really want to enforce a minimum size, then such check should 
be in the caller. Then each probe could return -ENOENT if the size is 
too small.

> I am not sure if Xen would handle a kernel whose size is less than a page.

I don't see any reason why it would not be.

> 
> I do not like the whole falling through in kernel_probe even in case of obvious violations.
> But this is something not related to this series so I added to my TODO to properly handle
> the return types from kernel_probe path. If you really think, we should return -ENOENT here,
> then ok (although I do not like it). Could this be done on commit if you insist on that?

See above for an alternative proposal. Depending on the version we 
settle on I can do it on commit (but this is not going to happen today 
as OSSTEst is still blocked).

Cheers,
Michal Orzel Feb. 2, 2023, 11:36 a.m. UTC | #4
Hi Julien,

On 02/02/2023 12:23, Julien Grall wrote:
> 
> 
> On 02/02/2023 11:12, Michal Orzel wrote:
>> Hi Julien,
> 
> Hi Michal,
> 
>>
>> On 02/02/2023 12:01, Julien Grall wrote:
>>>
>>>
>>> Hi Michal,
>>>
>>> On 02/02/2023 08:49, Michal Orzel wrote:
>>>> @@ -265,11 +284,14 @@ static __init int kernel_decompress(struct bootmodule *mod)
>>>>    #define IH_ARCH_ARM             2       /* ARM          */
>>>>    #define IH_ARCH_ARM64           22      /* ARM64        */
>>>>
>>>> +/* uImage Compression Types */
>>>> +#define IH_COMP_GZIP            1
>>>> +
>>>>    /*
>>>>     * Check if the image is a uImage and setup kernel_info
>>>>     */
>>>>    static int __init kernel_uimage_probe(struct kernel_info *info,
>>>> -                                      paddr_t addr, paddr_t size)
>>>> +                                      struct bootmodule *mod)
>>>>    {
>>>>        struct {
>>>>            __be32 magic;   /* Image Header Magic Number */
>>>> @@ -287,6 +309,8 @@ static int __init kernel_uimage_probe(struct kernel_info *info,
>>>>        } uimage;
>>>>
>>>>        uint32_t len;
>>>> +    paddr_t addr = mod->start;
>>>> +    paddr_t size = mod->size;
>>>>
>>>>        if ( size < sizeof(uimage) )
>>>>            return -EINVAL;
>>>
>>> Shouldn't we return -ENOENT here?
>> Frankly speaking, I do not want to fall through in such a case.
>> If a kernel size is less than 64B, something is wrong, isn't it?
> 
> I agree something is likely wrong but this should not be the job of
> kernel_uimage_probe() to enforce this for everyone.
> 
> To give a concrete example, let's imagine we decide to re-order the call
> so kernel_uimage_probe() happens *after* an new header A than would
> require 128 bytes (the number is made up).
> 
> It would be wrong for A to return -EINVAL because the other protocol may
> require a smaller size. The same goes here even at least for consistency.
> 
> So if you really want to enforce a minimum size, then such check should
> be in the caller. Then each probe could return -ENOENT if the size is
> too small.
> 
>> I am not sure if Xen would handle a kernel whose size is less than a page.
> 
> I don't see any reason why it would not be.
> 
>>
>> I do not like the whole falling through in kernel_probe even in case of obvious violations.
>> But this is something not related to this series so I added to my TODO to properly handle
>> the return types from kernel_probe path. If you really think, we should return -ENOENT here,
>> then ok (although I do not like it). Could this be done on commit if you insist on that?
> 
> See above for an alternative proposal. Depending on the version we
> settle on I can do it on commit (but this is not going to happen today
> as OSSTEst is still blocked).
Ok, lets stick to your original suggestion with s/-EINVAL/-ENOENT/
and I will come up with something for a future patch as this will require more changes
to make it generic. I also do not like at all the fact that we are not informing the user about the error code
when calling panic from construct_{dom0,domU}...

> 
> Cheers,
> 
> --
> Julien Grall

~Michal
Julien Grall Feb. 8, 2023, 1:54 p.m. UTC | #5
On 02/02/2023 11:36, Michal Orzel wrote:
> Hi Julien,

Hi,

> On 02/02/2023 12:23, Julien Grall wrote:
>>
>>
>> On 02/02/2023 11:12, Michal Orzel wrote:
>>> Hi Julien,
>>
>> Hi Michal,
>>
>>>
>>> On 02/02/2023 12:01, Julien Grall wrote:
>>>>
>>>>
>>>> Hi Michal,
>>>>
>>>> On 02/02/2023 08:49, Michal Orzel wrote:
>>>>> @@ -265,11 +284,14 @@ static __init int kernel_decompress(struct bootmodule *mod)
>>>>>     #define IH_ARCH_ARM             2       /* ARM          */
>>>>>     #define IH_ARCH_ARM64           22      /* ARM64        */
>>>>>
>>>>> +/* uImage Compression Types */
>>>>> +#define IH_COMP_GZIP            1
>>>>> +
>>>>>     /*
>>>>>      * Check if the image is a uImage and setup kernel_info
>>>>>      */
>>>>>     static int __init kernel_uimage_probe(struct kernel_info *info,
>>>>> -                                      paddr_t addr, paddr_t size)
>>>>> +                                      struct bootmodule *mod)
>>>>>     {
>>>>>         struct {
>>>>>             __be32 magic;   /* Image Header Magic Number */
>>>>> @@ -287,6 +309,8 @@ static int __init kernel_uimage_probe(struct kernel_info *info,
>>>>>         } uimage;
>>>>>
>>>>>         uint32_t len;
>>>>> +    paddr_t addr = mod->start;
>>>>> +    paddr_t size = mod->size;
>>>>>
>>>>>         if ( size < sizeof(uimage) )
>>>>>             return -EINVAL;
>>>>
>>>> Shouldn't we return -ENOENT here?
>>> Frankly speaking, I do not want to fall through in such a case.
>>> If a kernel size is less than 64B, something is wrong, isn't it?
>>
>> I agree something is likely wrong but this should not be the job of
>> kernel_uimage_probe() to enforce this for everyone.
>>
>> To give a concrete example, let's imagine we decide to re-order the call
>> so kernel_uimage_probe() happens *after* an new header A than would
>> require 128 bytes (the number is made up).
>>
>> It would be wrong for A to return -EINVAL because the other protocol may
>> require a smaller size. The same goes here even at least for consistency.
>>
>> So if you really want to enforce a minimum size, then such check should
>> be in the caller. Then each probe could return -ENOENT if the size is
>> too small.
>>
>>> I am not sure if Xen would handle a kernel whose size is less than a page.
>>
>> I don't see any reason why it would not be.
>>
>>>
>>> I do not like the whole falling through in kernel_probe even in case of obvious violations.
>>> But this is something not related to this series so I added to my TODO to properly handle
>>> the return types from kernel_probe path. If you really think, we should return -ENOENT here,
>>> then ok (although I do not like it). Could this be done on commit if you insist on that?
>>
>> See above for an alternative proposal. Depending on the version we
>> settle on I can do it on commit (but this is not going to happen today
>> as OSSTEst is still blocked).
> Ok, lets stick to your original suggestion with s/-EINVAL/-ENOENT/

It looks like I didn't gave my reviewed-by. So:

Reviewed-by: Julien Grall <jgrall@amazon.com>

I will change it on commit. As we discussed on IRC, even if there is no 
arm32 testing in OSSTest, I expect this to only impact boot and 
therefore should be caught by the Gitlab CI.

You already done some testing [1], so I will commit it now.

> and I will come up with something for a future patch as this will require more changes
> to make it generic. I also do not like at all the fact that we are not informing the user about the error code
> when calling panic from construct_{dom0,domU}...

I saw you posted it. I will add it in my queue of patches to review.

Cheers,

[1] 
https://gitlab.com/xen-project/people/morzel/xen-orzelmichal/-/pipelines/770984105
diff mbox series

Patch

diff --git a/docs/misc/arm/booting.txt b/docs/misc/arm/booting.txt
index bd7bfe7f284a..02f7bb65ec6d 100644
--- a/docs/misc/arm/booting.txt
+++ b/docs/misc/arm/booting.txt
@@ -50,9 +50,6 @@  Also, it is to be noted that if user provides the legacy image header on
 top of zImage or Image header, then Xen uses the attributes of legacy
 image header to determine the load address, entry point, etc.
 
-Known limitation: compressed kernels with a uboot headers are not
-working.
-
 
 Firmware/bootloader requirements
 --------------------------------
diff --git a/xen/arch/arm/kernel.c b/xen/arch/arm/kernel.c
index 068fbf88e492..7dd082b5772c 100644
--- a/xen/arch/arm/kernel.c
+++ b/xen/arch/arm/kernel.c
@@ -191,7 +191,7 @@  static __init uint32_t output_length(char *image, unsigned long image_len)
     return *(uint32_t *)&image[image_len - 4];
 }
 
-static __init int kernel_decompress(struct bootmodule *mod)
+static __init int kernel_decompress(struct bootmodule *mod, uint32_t offset)
 {
     char *output, *input;
     char magic[2];
@@ -204,6 +204,17 @@  static __init int kernel_decompress(struct bootmodule *mod)
     paddr_t addr = mod->start;
     paddr_t size = mod->size;
 
+    if ( size < offset )
+        return -EINVAL;
+
+    /*
+     * It might be that gzip header does not appear at the start address
+     * (e.g. in case of compressed uImage) so take into account offset to
+     * gzip header.
+     */
+    addr += offset;
+    size -= offset;
+
     if ( size < 2 )
         return -EINVAL;
 
@@ -250,6 +261,14 @@  static __init int kernel_decompress(struct bootmodule *mod)
     for ( ; i < (1 << kernel_order_out); i++ )
         free_domheap_page(pages + i);
 
+    /*
+     * When freeing the kernel, we need to pass the module start address and
+     * size as they were before taking an offset to gzip header into account,
+     * so that the entire region will be freed.
+     */
+    addr -= offset;
+    size += offset;
+
     /*
      * Free the original kernel, update the pointers to the
      * decompressed kernel
@@ -265,11 +284,14 @@  static __init int kernel_decompress(struct bootmodule *mod)
 #define IH_ARCH_ARM             2       /* ARM          */
 #define IH_ARCH_ARM64           22      /* ARM64        */
 
+/* uImage Compression Types */
+#define IH_COMP_GZIP            1
+
 /*
  * Check if the image is a uImage and setup kernel_info
  */
 static int __init kernel_uimage_probe(struct kernel_info *info,
-                                      paddr_t addr, paddr_t size)
+                                      struct bootmodule *mod)
 {
     struct {
         __be32 magic;   /* Image Header Magic Number */
@@ -287,6 +309,8 @@  static int __init kernel_uimage_probe(struct kernel_info *info,
     } uimage;
 
     uint32_t len;
+    paddr_t addr = mod->start;
+    paddr_t size = mod->size;
 
     if ( size < sizeof(uimage) )
         return -EINVAL;
@@ -294,13 +318,21 @@  static int __init kernel_uimage_probe(struct kernel_info *info,
     copy_from_paddr(&uimage, addr, sizeof(uimage));
 
     if ( be32_to_cpu(uimage.magic) != UIMAGE_MAGIC )
-        return -EINVAL;
+        return -ENOENT;
 
     len = be32_to_cpu(uimage.size);
 
     if ( len > size - sizeof(uimage) )
         return -EINVAL;
 
+    /* Only gzip compression is supported. */
+    if ( uimage.comp && uimage.comp != IH_COMP_GZIP )
+    {
+        printk(XENLOG_ERR
+               "Unsupported uImage compression type %"PRIu8"\n", uimage.comp);
+        return -EOPNOTSUPP;
+    }
+
     info->zimage.start = be32_to_cpu(uimage.load);
     info->entry = be32_to_cpu(uimage.ep);
 
@@ -330,8 +362,27 @@  static int __init kernel_uimage_probe(struct kernel_info *info,
         return -EINVAL;
     }
 
-    info->zimage.kernel_addr = addr + sizeof(uimage);
-    info->zimage.len = len;
+    if ( uimage.comp )
+    {
+        int rc;
+
+        /*
+         * In case of a compressed uImage, the gzip header is right after
+         * the u-boot header, so pass sizeof(uimage) as an offset to gzip
+         * header.
+         */
+        rc = kernel_decompress(mod, sizeof(uimage));
+        if ( rc )
+            return rc;
+
+        info->zimage.kernel_addr = mod->start;
+        info->zimage.len = mod->size;
+    }
+    else
+    {
+        info->zimage.kernel_addr = addr + sizeof(uimage);
+        info->zimage.len = len;
+    }
 
     info->load = kernel_zimage_load;
 
@@ -561,8 +612,22 @@  int __init kernel_probe(struct kernel_info *info,
         printk("Loading ramdisk from boot module @ %"PRIpaddr"\n",
                info->initrd_bootmodule->start);
 
-    /* if it is a gzip'ed image, 32bit or 64bit, uncompress it */
-    rc = kernel_decompress(mod);
+    /*
+     * uImage header always appears at the top of the image (even compressed),
+     * so it needs to be probed first. Note that in case of compressed uImage,
+     * kernel_decompress is called from kernel_uimage_probe making the function
+     * self-containing (i.e. fall through only in case of a header not found).
+     */
+    rc = kernel_uimage_probe(info, mod);
+    if ( rc != -ENOENT )
+        return rc;
+
+    /*
+     * If it is a gzip'ed image, 32bit or 64bit, uncompress it.
+     * At this point, gzip header appears (if at all) at the top of the image,
+     * so pass 0 as an offset.
+     */
+    rc = kernel_decompress(mod, 0);
     if ( rc && rc != -EINVAL )
         return rc;
 
@@ -570,8 +635,6 @@  int __init kernel_probe(struct kernel_info *info,
     rc = kernel_zimage64_probe(info, mod->start, mod->size);
     if (rc < 0)
 #endif
-        rc = kernel_uimage_probe(info, mod->start, mod->size);
-    if (rc < 0)
         rc = kernel_zimage32_probe(info, mod->start, mod->size);
 
     return rc;