Long ago Takashi had some points about this strategy breaking
compressed file use. Was that considered?

  Luis

On Fri, Jul 24, 2020 at 02:36:39PM -0700, Kees Cook wrote:
> From: Scott Branden <[email protected]>
> 
> Add request_partial_firmware_into_buf() to allow for portions of a
> firmware file to be read into a buffer. This is needed when large firmware
> must be loaded in portions from a file on memory constrained systems.
> 
> Signed-off-by: Scott Branden <[email protected]>
> Co-developed-by: Kees Cook <[email protected]>
> Signed-off-by: Kees Cook <[email protected]>
> ---
>  drivers/base/firmware_loader/firmware.h |   4 +
>  drivers/base/firmware_loader/main.c     | 109 +++++++++++++++++++-----
>  include/linux/firmware.h                |  12 +++
>  3 files changed, 105 insertions(+), 20 deletions(-)
> 
> diff --git a/drivers/base/firmware_loader/firmware.h 
> b/drivers/base/firmware_loader/firmware.h
> index 7ad5fe52bc72..3f6eda46b3a2 100644
> --- a/drivers/base/firmware_loader/firmware.h
> +++ b/drivers/base/firmware_loader/firmware.h
> @@ -32,6 +32,8 @@
>   * @FW_OPT_FALLBACK_PLATFORM: Enable fallback to device fw copy embedded in
>   *   the platform's main firmware. If both this fallback and the sysfs
>   *      fallback are enabled, then this fallback will be tried first.
> + * @FW_OPT_PARTIAL: Allow partial read of firmware instead of needing to read
> + *   entire file.
>   */
>  enum fw_opt {
>       FW_OPT_UEVENT                   = BIT(0),
> @@ -41,6 +43,7 @@ enum fw_opt {
>       FW_OPT_NOCACHE                  = BIT(4),
>       FW_OPT_NOFALLBACK_SYSFS         = BIT(5),
>       FW_OPT_FALLBACK_PLATFORM        = BIT(6),
> +     FW_OPT_PARTIAL                  = BIT(7),
>  };
>  
>  enum fw_status {
> @@ -68,6 +71,7 @@ struct fw_priv {
>       void *data;
>       size_t size;
>       size_t allocated_size;
> +     size_t offset;
>       u32 opt_flags;
>  #ifdef CONFIG_FW_LOADER_PAGED_BUF
>       bool is_paged_buf;
> diff --git a/drivers/base/firmware_loader/main.c 
> b/drivers/base/firmware_loader/main.c
> index 814a18cc51bd..7aa22bdc2f60 100644
> --- a/drivers/base/firmware_loader/main.c
> +++ b/drivers/base/firmware_loader/main.c
> @@ -170,10 +170,19 @@ static struct fw_priv *__allocate_fw_priv(const char 
> *fw_name,
>                                         struct firmware_cache *fwc,
>                                         void *dbuf,
>                                         size_t size,
> +                                       size_t offset,
>                                         u32 opt_flags)
>  {
>       struct fw_priv *fw_priv;
>  
> +     /* For a partial read, the buffer must be preallocated. */
> +     if ((opt_flags & FW_OPT_PARTIAL) && !dbuf)
> +             return NULL;
> +
> +     /* Only partial reads are allowed to use an offset. */
> +     if (offset != 0 && !(opt_flags & FW_OPT_PARTIAL))
> +             return NULL;
> +
>       fw_priv = kzalloc(sizeof(*fw_priv), GFP_ATOMIC);
>       if (!fw_priv)
>               return NULL;
> @@ -188,6 +197,7 @@ static struct fw_priv *__allocate_fw_priv(const char 
> *fw_name,
>       fw_priv->fwc = fwc;
>       fw_priv->data = dbuf;
>       fw_priv->allocated_size = size;
> +     fw_priv->offset = offset;
>       fw_priv->opt_flags = opt_flags;
>       fw_state_init(fw_priv);
>  #ifdef CONFIG_FW_LOADER_USER_HELPER
> @@ -216,12 +226,17 @@ static int alloc_lookup_fw_priv(const char *fw_name,
>                               struct fw_priv **fw_priv,
>                               void *dbuf,
>                               size_t size,
> +                             size_t offset,
>                               u32 opt_flags)
>  {
>       struct fw_priv *tmp;
>  
>       spin_lock(&fwc->lock);
> -     if (!(opt_flags & FW_OPT_NOCACHE)) {
> +     /*
> +      * Do not merge requests that are marked to be non-cached or
> +      * are performing partial reads.
> +      */
> +     if (!(opt_flags & (FW_OPT_NOCACHE | FW_OPT_PARTIAL))) {
>               tmp = __lookup_fw_priv(fw_name);
>               if (tmp) {
>                       kref_get(&tmp->ref);
> @@ -232,7 +247,7 @@ static int alloc_lookup_fw_priv(const char *fw_name,
>               }
>       }
>  
> -     tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size, opt_flags);
> +     tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size, offset, opt_flags);
>       if (tmp) {
>               INIT_LIST_HEAD(&tmp->list);
>               if (!(opt_flags & FW_OPT_NOCACHE))
> @@ -439,6 +454,12 @@ static int fw_decompress_xz(struct device *dev, struct 
> fw_priv *fw_priv,
>       else
>               return fw_decompress_xz_pages(dev, fw_priv, in_size, in_buffer);
>  }
> +#else
> +static inline int fw_decompress_xz(struct device *dev, struct fw_priv 
> *fw_priv,
> +                                size_t in_size, const void *in_buffer)
> +{
> +     return -ENOENT;
> +}
>  #endif /* CONFIG_FW_LOADER_COMPRESS */
>  
>  /* direct firmware loading support */
> @@ -485,6 +506,9 @@ fw_get_filesystem_firmware(struct device *device, struct 
> fw_priv *fw_priv,
>               return -ENOMEM;
>  
>       for (i = 0; i < ARRAY_SIZE(fw_path); i++) {
> +             size_t file_size = 0;
> +             size_t *file_size_ptr = NULL;
> +
>               /* skip the unset customized path */
>               if (!fw_path[i][0])
>                       continue;
> @@ -498,9 +522,18 @@ fw_get_filesystem_firmware(struct device *device, struct 
> fw_priv *fw_priv,
>  
>               fw_priv->size = 0;
>  
> +             /*
> +              * The total file size is only examined when doing a partial
> +              * read; the "full read" case needs to fail if the whole
> +              * firmware was not completely loaded.
> +              */
> +             if ((fw_priv->opt_flags & FW_OPT_PARTIAL) && buffer)
> +                     file_size_ptr = &file_size;
> +
>               /* load firmware files from the mount namespace of init */
> -             rc = kernel_read_file_from_path_initns(path, 0, &buffer, msize,
> -                                                    NULL,
> +             rc = kernel_read_file_from_path_initns(path, fw_priv->offset,
> +                                                    &buffer, msize,
> +                                                    file_size_ptr,
>                                                      READING_FIRMWARE);
>               if (rc < 0) {
>                       if (rc != -ENOENT)
> @@ -691,7 +724,7 @@ int assign_fw(struct firmware *fw, struct device *device)
>  static int
>  _request_firmware_prepare(struct firmware **firmware_p, const char *name,
>                         struct device *device, void *dbuf, size_t size,
> -                       u32 opt_flags)
> +                       size_t offset, u32 opt_flags)
>  {
>       struct firmware *firmware;
>       struct fw_priv *fw_priv;
> @@ -710,7 +743,7 @@ _request_firmware_prepare(struct firmware **firmware_p, 
> const char *name,
>       }
>  
>       ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv, dbuf, size,
> -                               opt_flags);
> +                                offset, opt_flags);
>  
>       /*
>        * bind with 'priv' now to avoid warning in failure path
> @@ -757,9 +790,10 @@ static void fw_abort_batch_reqs(struct firmware *fw)
>  static int
>  _request_firmware(const struct firmware **firmware_p, const char *name,
>                 struct device *device, void *buf, size_t size,
> -               u32 opt_flags)
> +               size_t offset, u32 opt_flags)
>  {
>       struct firmware *fw = NULL;
> +     bool nondirect = false;
>       int ret;
>  
>       if (!firmware_p)
> @@ -771,18 +805,20 @@ _request_firmware(const struct firmware **firmware_p, 
> const char *name,
>       }
>  
>       ret = _request_firmware_prepare(&fw, name, device, buf, size,
> -                                     opt_flags);
> +                                     offset, opt_flags);
>       if (ret <= 0) /* error or already assigned */
>               goto out;
>  
>       ret = fw_get_filesystem_firmware(device, fw->priv, "", NULL);
> -#ifdef CONFIG_FW_LOADER_COMPRESS
> -     if (ret == -ENOENT)
> +
> +     /* Only full reads can support decompression, platform, and sysfs. */
> +     if (!(opt_flags & FW_OPT_PARTIAL))
> +             nondirect = true;
> +
> +     if (ret == -ENOENT && nondirect)
>               ret = fw_get_filesystem_firmware(device, fw->priv, ".xz",
>                                                fw_decompress_xz);
> -#endif
> -
> -     if (ret == -ENOENT)
> +     if (ret == -ENOENT && nondirect)
>               ret = firmware_fallback_platform(fw->priv);
>  
>       if (ret) {
> @@ -790,7 +826,9 @@ _request_firmware(const struct firmware **firmware_p, 
> const char *name,
>                       dev_warn(device,
>                                "Direct firmware load for %s failed with error 
> %d\n",
>                                name, ret);
> -             ret = firmware_fallback_sysfs(fw, name, device, opt_flags, ret);
> +             if (nondirect)
> +                     ret = firmware_fallback_sysfs(fw, name, device,
> +                                                   opt_flags, ret);
>       } else
>               ret = assign_fw(fw, device);
>  
> @@ -833,7 +871,7 @@ request_firmware(const struct firmware **firmware_p, 
> const char *name,
>  
>       /* Need to pin this module until return */
>       __module_get(THIS_MODULE);
> -     ret = _request_firmware(firmware_p, name, device, NULL, 0,
> +     ret = _request_firmware(firmware_p, name, device, NULL, 0, 0,
>                               FW_OPT_UEVENT);
>       module_put(THIS_MODULE);
>       return ret;
> @@ -860,7 +898,7 @@ int firmware_request_nowarn(const struct firmware 
> **firmware, const char *name,
>  
>       /* Need to pin this module until return */
>       __module_get(THIS_MODULE);
> -     ret = _request_firmware(firmware, name, device, NULL, 0,
> +     ret = _request_firmware(firmware, name, device, NULL, 0, 0,
>                               FW_OPT_UEVENT | FW_OPT_NO_WARN);
>       module_put(THIS_MODULE);
>       return ret;
> @@ -884,7 +922,7 @@ int request_firmware_direct(const struct firmware 
> **firmware_p,
>       int ret;
>  
>       __module_get(THIS_MODULE);
> -     ret = _request_firmware(firmware_p, name, device, NULL, 0,
> +     ret = _request_firmware(firmware_p, name, device, NULL, 0, 0,
>                               FW_OPT_UEVENT | FW_OPT_NO_WARN |
>                               FW_OPT_NOFALLBACK_SYSFS);
>       module_put(THIS_MODULE);
> @@ -909,7 +947,7 @@ int firmware_request_platform(const struct firmware 
> **firmware,
>  
>       /* Need to pin this module until return */
>       __module_get(THIS_MODULE);
> -     ret = _request_firmware(firmware, name, device, NULL, 0,
> +     ret = _request_firmware(firmware, name, device, NULL, 0, 0,
>                               FW_OPT_UEVENT | FW_OPT_FALLBACK_PLATFORM);
>       module_put(THIS_MODULE);
>       return ret;
> @@ -965,13 +1003,44 @@ request_firmware_into_buf(const struct firmware 
> **firmware_p, const char *name,
>               return -EOPNOTSUPP;
>  
>       __module_get(THIS_MODULE);
> -     ret = _request_firmware(firmware_p, name, device, buf, size,
> +     ret = _request_firmware(firmware_p, name, device, buf, size, 0,
>                               FW_OPT_UEVENT | FW_OPT_NOCACHE);
>       module_put(THIS_MODULE);
>       return ret;
>  }
>  EXPORT_SYMBOL(request_firmware_into_buf);
>  
> +/**
> + * request_partial_firmware_into_buf() - load partial firmware into a 
> previously allocated buffer
> + * @firmware_p: pointer to firmware image
> + * @name: name of firmware file
> + * @device: device for which firmware is being loaded and DMA region 
> allocated
> + * @buf: address of buffer to load firmware into
> + * @size: size of buffer
> + * @offset: offset into file to read
> + *
> + * This function works pretty much like request_firmware_into_buf except
> + * it allows a partial read of the file.
> + */
> +int
> +request_partial_firmware_into_buf(const struct firmware **firmware_p,
> +                               const char *name, struct device *device,
> +                               void *buf, size_t size, size_t offset)
> +{
> +     int ret;
> +
> +     if (fw_cache_is_setup(device, name))
> +             return -EOPNOTSUPP;
> +
> +     __module_get(THIS_MODULE);
> +     ret = _request_firmware(firmware_p, name, device, buf, size, offset,
> +                             FW_OPT_UEVENT | FW_OPT_NOCACHE |
> +                             FW_OPT_PARTIAL);
> +     module_put(THIS_MODULE);
> +     return ret;
> +}
> +EXPORT_SYMBOL(request_partial_firmware_into_buf);
> +
>  /**
>   * release_firmware() - release the resource associated with a firmware image
>   * @fw: firmware resource to release
> @@ -1004,7 +1073,7 @@ static void request_firmware_work_func(struct 
> work_struct *work)
>  
>       fw_work = container_of(work, struct firmware_work, work);
>  
> -     _request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0,
> +     _request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0, 0,
>                         fw_work->opt_flags);
>       fw_work->cont(fw, fw_work->context);
>       put_device(fw_work->device); /* taken in request_firmware_nowait() */
> diff --git a/include/linux/firmware.h b/include/linux/firmware.h
> index cb3e2c06ed8a..c15acadc6cf4 100644
> --- a/include/linux/firmware.h
> +++ b/include/linux/firmware.h
> @@ -53,6 +53,9 @@ int request_firmware_direct(const struct firmware **fw, 
> const char *name,
>                           struct device *device);
>  int request_firmware_into_buf(const struct firmware **firmware_p,
>       const char *name, struct device *device, void *buf, size_t size);
> +int request_partial_firmware_into_buf(const struct firmware **firmware_p,
> +                                   const char *name, struct device *device,
> +                                   void *buf, size_t size, size_t offset);
>  
>  void release_firmware(const struct firmware *fw);
>  #else
> @@ -102,6 +105,15 @@ static inline int request_firmware_into_buf(const struct 
> firmware **firmware_p,
>       return -EINVAL;
>  }
>  
> +static inline int request_partial_firmware_into_buf
> +                                     (const struct firmware **firmware_p,
> +                                      const char *name,
> +                                      struct device *device,
> +                                      void *buf, size_t size, size_t offset)
> +{
> +     return -EINVAL;
> +}
> +
>  #endif
>  
>  int firmware_request_cache(struct device *device, const char *name);
> -- 
> 2.25.1
> 

Reply via email to