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: 0e742e9275 ("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: Vikram Mulukutla <markivx@codeaurora.org> Signed-off-by: Rishabh Bhatnagar <rishabhb@codeaurora.org> Signed-off-by: Luis Chamberlain <mcgrof@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
committed by
Greg Kroah-Hartman
parent
6712cc9c22
commit
422b3db2a5
@ -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,
|
|||||||
*/
|
*/
|
||||||
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,
|
|||||||
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,
|
|||||||
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;
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user