Commit 422b3db2 authored by Rishabh Bhatnagar's avatar Rishabh Bhatnagar Committed by Greg Kroah-Hartman

firmware: Fix security issue with request_firmware_into_buf()

When calling request_firmware_into_buf() with the FW_OPT_NOCACHE flag
it is expected that firmware is loaded into buffer from memory.
But inside alloc_lookup_fw_priv every new firmware that is loaded is
added to the firmware cache (fwc) list head. So if any driver requests
a firmware that is already loaded the code iterates over the above
mentioned list and it can end up giving a pointer to other device driver's
firmware buffer.
Also the existing copy may either be modified by drivers, remote processors
or even freed. This causes a potential security issue with batched requests
when using request_firmware_into_buf.

Fix alloc_lookup_fw_priv to not add to the fwc head list if FW_OPT_NOCACHE
is set, and also don't do the lookup in the list.

Fixes: 0e742e92 ("firmware: provide infrastructure to make fw caching optional")
[mcgrof: broken since feature introduction on v4.8]

Cc: stable@vger.kernel.org # v4.8+
Signed-off-by: default avatarVikram Mulukutla <markivx@codeaurora.org>
Signed-off-by: default avatarRishabh Bhatnagar <rishabhb@codeaurora.org>
Signed-off-by: default avatarLuis Chamberlain <mcgrof@kernel.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 6712cc9c
...@@ -209,21 +209,24 @@ static struct fw_priv *__lookup_fw_priv(const char *fw_name) ...@@ -209,21 +209,24 @@ static struct fw_priv *__lookup_fw_priv(const char *fw_name)
static int alloc_lookup_fw_priv(const char *fw_name, static int alloc_lookup_fw_priv(const char *fw_name,
struct firmware_cache *fwc, struct firmware_cache *fwc,
struct fw_priv **fw_priv, void *dbuf, struct fw_priv **fw_priv, void *dbuf,
size_t size) size_t size, enum fw_opt opt_flags)
{ {
struct fw_priv *tmp; struct fw_priv *tmp;
spin_lock(&fwc->lock); spin_lock(&fwc->lock);
tmp = __lookup_fw_priv(fw_name); if (!(opt_flags & FW_OPT_NOCACHE)) {
if (tmp) { tmp = __lookup_fw_priv(fw_name);
kref_get(&tmp->ref); if (tmp) {
spin_unlock(&fwc->lock); kref_get(&tmp->ref);
*fw_priv = tmp; spin_unlock(&fwc->lock);
pr_debug("batched request - sharing the same struct fw_priv and lookup for multiple requests\n"); *fw_priv = tmp;
return 1; pr_debug("batched request - sharing the same struct fw_priv and lookup for multiple requests\n");
return 1;
}
} }
tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size); tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size);
if (tmp) if (tmp && !(opt_flags & FW_OPT_NOCACHE))
list_add(&tmp->list, &fwc->head); list_add(&tmp->list, &fwc->head);
spin_unlock(&fwc->lock); spin_unlock(&fwc->lock);
...@@ -493,7 +496,8 @@ int assign_fw(struct firmware *fw, struct device *device, ...@@ -493,7 +496,8 @@ int assign_fw(struct firmware *fw, struct device *device,
*/ */
static int static int
_request_firmware_prepare(struct firmware **firmware_p, const char *name, _request_firmware_prepare(struct firmware **firmware_p, const char *name,
struct device *device, void *dbuf, size_t size) struct device *device, void *dbuf, size_t size,
enum fw_opt opt_flags)
{ {
struct firmware *firmware; struct firmware *firmware;
struct fw_priv *fw_priv; struct fw_priv *fw_priv;
...@@ -511,7 +515,8 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name, ...@@ -511,7 +515,8 @@ _request_firmware_prepare(struct firmware **firmware_p, const char *name,
return 0; /* assigned */ return 0; /* assigned */
} }
ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv, dbuf, size); ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv, dbuf, size,
opt_flags);
/* /*
* bind with 'priv' now to avoid warning in failure path * bind with 'priv' now to avoid warning in failure path
...@@ -571,7 +576,8 @@ _request_firmware(const struct firmware **firmware_p, const char *name, ...@@ -571,7 +576,8 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
goto out; goto out;
} }
ret = _request_firmware_prepare(&fw, name, device, buf, size); ret = _request_firmware_prepare(&fw, name, device, buf, size,
opt_flags);
if (ret <= 0) /* error or already assigned */ if (ret <= 0) /* error or already assigned */
goto out; goto out;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment