adapter->dcb would get silently freed inside qlcnic_dcb_enable() in
case qlcnic_dcb_attach() would return an error, which always happens
under OOM conditions. This would lead to use-after-free because both
of the existing callers invoke qlcnic_dcb_get_info() on the obtained
pointer, which is potentially freed at that point.
Propagate errors from qlcnic_dcb_enable(), and instead free the dcb
pointer at callsite using qlcnic_dcb_free(). This also removes the now
unused qlcnic_clear_dcb_ops() helper, which was a simple wrapper around
kfree() also causing memory leaks for partially initialized dcb.
Found by Linux Verification Center (linuxtesting.org) with the SVACE
static analysis tool.
Fixes: 3c44bba1d2
("qlcnic: Disable DCB operations from SR-IOV VFs")
Reviewed-by: Michal Swiatkowski <michal.swiatkowski@linux.intel.com>
Signed-off-by: Daniil Tatianin <d-tatianin@yandex-team.ru>
Signed-off-by: David S. Miller <davem@davemloft.net>
114 lines
2.6 KiB
C
114 lines
2.6 KiB
C
/* SPDX-License-Identifier: GPL-2.0-only */
|
|
/*
|
|
* QLogic qlcnic NIC Driver
|
|
* Copyright (c) 2009-2013 QLogic Corporation
|
|
*/
|
|
|
|
#ifndef __QLCNIC_DCBX_H
|
|
#define __QLCNIC_DCBX_H
|
|
|
|
#define QLCNIC_DCB_STATE 0
|
|
#define QLCNIC_DCB_AEN_MODE 1
|
|
|
|
#ifdef CONFIG_QLCNIC_DCB
|
|
int qlcnic_register_dcb(struct qlcnic_adapter *);
|
|
#else
|
|
static inline int qlcnic_register_dcb(struct qlcnic_adapter *adapter)
|
|
{ return 0; }
|
|
#endif
|
|
|
|
struct qlcnic_dcb;
|
|
|
|
struct qlcnic_dcb_ops {
|
|
int (*query_hw_capability) (struct qlcnic_dcb *, char *);
|
|
int (*get_hw_capability) (struct qlcnic_dcb *);
|
|
int (*query_cee_param) (struct qlcnic_dcb *, char *, u8);
|
|
void (*init_dcbnl_ops) (struct qlcnic_dcb *);
|
|
void (*aen_handler) (struct qlcnic_dcb *, void *);
|
|
int (*get_cee_cfg) (struct qlcnic_dcb *);
|
|
void (*get_info) (struct qlcnic_dcb *);
|
|
int (*attach) (struct qlcnic_dcb *);
|
|
void (*free) (struct qlcnic_dcb *);
|
|
};
|
|
|
|
struct qlcnic_dcb {
|
|
struct qlcnic_dcb_mbx_params *param;
|
|
struct qlcnic_adapter *adapter;
|
|
struct delayed_work aen_work;
|
|
struct workqueue_struct *wq;
|
|
const struct qlcnic_dcb_ops *ops;
|
|
struct qlcnic_dcb_cfg *cfg;
|
|
unsigned long state;
|
|
};
|
|
|
|
static inline int qlcnic_dcb_get_hw_capability(struct qlcnic_dcb *dcb)
|
|
{
|
|
if (dcb && dcb->ops->get_hw_capability)
|
|
return dcb->ops->get_hw_capability(dcb);
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static inline void qlcnic_dcb_free(struct qlcnic_dcb *dcb)
|
|
{
|
|
if (dcb && dcb->ops->free)
|
|
dcb->ops->free(dcb);
|
|
}
|
|
|
|
static inline int qlcnic_dcb_attach(struct qlcnic_dcb *dcb)
|
|
{
|
|
if (dcb && dcb->ops->attach)
|
|
return dcb->ops->attach(dcb);
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static inline int
|
|
qlcnic_dcb_query_hw_capability(struct qlcnic_dcb *dcb, char *buf)
|
|
{
|
|
if (dcb && dcb->ops->query_hw_capability)
|
|
return dcb->ops->query_hw_capability(dcb, buf);
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static inline void qlcnic_dcb_get_info(struct qlcnic_dcb *dcb)
|
|
{
|
|
if (dcb && dcb->ops->get_info)
|
|
dcb->ops->get_info(dcb);
|
|
}
|
|
|
|
static inline int
|
|
qlcnic_dcb_query_cee_param(struct qlcnic_dcb *dcb, char *buf, u8 type)
|
|
{
|
|
if (dcb && dcb->ops->query_cee_param)
|
|
return dcb->ops->query_cee_param(dcb, buf, type);
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static inline int qlcnic_dcb_get_cee_cfg(struct qlcnic_dcb *dcb)
|
|
{
|
|
if (dcb && dcb->ops->get_cee_cfg)
|
|
return dcb->ops->get_cee_cfg(dcb);
|
|
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static inline void qlcnic_dcb_aen_handler(struct qlcnic_dcb *dcb, void *msg)
|
|
{
|
|
if (dcb && dcb->ops->aen_handler)
|
|
dcb->ops->aen_handler(dcb, msg);
|
|
}
|
|
|
|
static inline void qlcnic_dcb_init_dcbnl_ops(struct qlcnic_dcb *dcb)
|
|
{
|
|
if (dcb && dcb->ops->init_dcbnl_ops)
|
|
dcb->ops->init_dcbnl_ops(dcb);
|
|
}
|
|
|
|
static inline int qlcnic_dcb_enable(struct qlcnic_dcb *dcb)
|
|
{
|
|
return dcb ? qlcnic_dcb_attach(dcb) : 0;
|
|
}
|
|
#endif
|