Commit message: scsi: libsas: fix memory leak in sas_smp_get_phy_events()

We've got a memory leak with the following producer:

while true;
do cat /sys/class/sas_phy/phy-1:0:12/invalid_dword_count >/dev/null;
done

The buffer req is allocated and not freed after we return. Fix it.

Fixes: 2908d778ab3e ("[SCSI] aic94xx: new driver")
Signed-off-by: Jason Yan <yanaijie@huawei.com>
CC: John Garry <john.garry@huawei.com>
CC: chenqilin <chenqilin2@huawei.com>
CC: chenxiang <chenxiang66@hisilicon.com>
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Hannes Reinecke <hare@suse.com>
Signed-off-by: Martin K. Petersen <martin.petersen@oracle.com>
CVE: CVE-2018-7757
CWE: CWE-772
Vulnerability description: Memory leak in the sas_smp_get_phy_events function in drivers/scsi/libsas/sas_expander.c in the Linux kernel through 4.15.7 allows local users to cause a denial of service (memory consumption) via many read accesses to files in the /sys/class/sas_phy directory, as demonstrated by the /sys/class/sas_phy/phy-1:0:12/invalid_dword_count file.

Project: linux
File: sas_expander.c

static bool dev_type_flutter(enum sas_device_type new, enum sas_device_type old)
{
	if (old == new)
		return true;

	/* treat device directed resets as flutter, if we went
	 * SAS_END_DEVICE to SAS_SATA_PENDING the link needs recovery
	 */
	if ((old == SAS_SATA_PENDING && new == SAS_END_DEVICE) ||
	    (old == SAS_END_DEVICE && new == SAS_SATA_PENDING))
		return true;

	return false;
}

Project: linux
File: sas_expander.c

static int sas_check_ex_subtractive_boundary(struct domain_device *dev)
{
	struct expander_device *ex = &dev->ex_dev;
	int i;
	u8  *sub_sas_addr = NULL;

	if (dev->dev_type != SAS_EDGE_EXPANDER_DEVICE)
		return 0;

	for (i = 0; i < ex->num_phys; i++) {
		struct ex_phy *phy = &ex->ex_phy[i];

		if (phy->phy_state == PHY_VACANT ||
		    phy->phy_state == PHY_NOT_PRESENT)
			continue;

		if ((phy->attached_dev_type == SAS_FANOUT_EXPANDER_DEVICE ||
		     phy->attached_dev_type == SAS_EDGE_EXPANDER_DEVICE) &&
		    phy->routing_attr == SUBTRACTIVE_ROUTING) {

			if (!sub_sas_addr)
				sub_sas_addr = &phy->attached_sas_addr[0];
			else if (SAS_ADDR(sub_sas_addr) !=
				 SAS_ADDR(phy->attached_sas_addr)) {

				SAS_DPRINTK("ex %016llx phy 0x%x "
					    "diverges(%016llx) on subtractive "
					    "boundary(%016llx). Disabled\n",
					    SAS_ADDR(dev->sas_addr), i,
					    SAS_ADDR(phy->attached_sas_addr),
					    SAS_ADDR(sub_sas_addr));
				sas_ex_disable_phy(dev, i);
			}
		}
	}
	return 0;
}

Project: linux
File: sas_expander.c

static int sas_configure_phy(struct domain_device *dev, int phy_id,
				    u8 *sas_addr, int include)
{
	int index;
	int present;
	int res;

	res = sas_configure_present(dev, phy_id, sas_addr, &index, &present);
	if (res)
		return res;
	if (include ^ present)
		return sas_configure_set(dev, phy_id, sas_addr, index,include);

	return res;
}

Project: linux
File: sas_expander.c

static int sas_dev_present_in_domain(struct asd_sas_port *port,
					    u8 *sas_addr)
{
	struct domain_device *dev;

	if (SAS_ADDR(port->sas_addr) == SAS_ADDR(sas_addr))
		return 1;
	list_for_each_entry(dev, &port->dev_list, dev_list_node) {
		if (SAS_ADDR(dev->sas_addr) == SAS_ADDR(sas_addr))
			return 1;
	}
	return 0;
}

Project: linux
File: sas_expander.c

static struct domain_device *sas_ex_discover_expander(
	struct domain_device *parent, int phy_id)
{
	struct sas_expander_device *parent_ex = rphy_to_expander_device(parent->rphy);
	struct ex_phy *phy = &parent->ex_dev.ex_phy[phy_id];
	struct domain_device *child = NULL;
	struct sas_rphy *rphy;
	struct sas_expander_device *edev;
	struct asd_sas_port *port;
	int res;

	if (phy->routing_attr == DIRECT_ROUTING) {
		SAS_DPRINTK("ex %016llx:0x%x:D <--> ex %016llx:0x%x is not "
			    "allowed\n",
			    SAS_ADDR(parent->sas_addr), phy_id,
			    SAS_ADDR(phy->attached_sas_addr),
			    phy->attached_phy_id);
		return NULL;
	}
	child = sas_alloc_device();
	if (!child)
		return NULL;

	phy->port = sas_port_alloc(&parent->rphy->dev, phy_id);
	/* FIXME: better error handling */
	BUG_ON(sas_port_add(phy->port) != 0);


	switch (phy->attached_dev_type) {
	case SAS_EDGE_EXPANDER_DEVICE:
		rphy = sas_expander_alloc(phy->port,
					  SAS_EDGE_EXPANDER_DEVICE);
		break;
	case SAS_FANOUT_EXPANDER_DEVICE:
		rphy = sas_expander_alloc(phy->port,
					  SAS_FANOUT_EXPANDER_DEVICE);
		break;
	default:
		rphy = NULL;	/* shut gcc up */
		BUG();
	}
	port = parent->port;
	child->rphy = rphy;
	get_device(&rphy->dev);
	edev = rphy_to_expander_device(rphy);
	child->dev_type = phy->attached_dev_type;
	kref_get(&parent->kref);
	child->parent = parent;
	child->port = port;
	child->iproto = phy->attached_iproto;
	child->tproto = phy->attached_tproto;
	memcpy(child->sas_addr, phy->attached_sas_addr, SAS_ADDR_SIZE);
	sas_hash_addr(child->hashed_sas_addr, child->sas_addr);
	sas_ex_get_linkrate(parent, child, phy);
	edev->level = parent_ex->level + 1;
	parent->port->disc.max_level = max(parent->port->disc.max_level,
					   edev->level);
	sas_init_dev(child);
	sas_fill_in_rphy(child, rphy);
	sas_rphy_add(rphy);

	spin_lock_irq(&parent->port->dev_list_lock);
	list_add_tail(&child->dev_list_node, &parent->port->dev_list);
	spin_unlock_irq(&parent->port->dev_list_lock);

	res = sas_discover_expander(child);
	if (res) {
		sas_rphy_delete(rphy);
		spin_lock_irq(&parent->port->dev_list_lock);
		list_del(&child->dev_list_node);
		spin_unlock_irq(&parent->port->dev_list_lock);
		sas_put_device(child);
		return NULL;
	}
	list_add_tail(&child->siblings, &parent->ex_dev.children);
	return child;
}

Project: linux
File: sas_expander.c

struct domain_device *sas_ex_to_ata(struct domain_device *ex_dev, int phy_id)
{
	struct ex_phy *ex_phy = &ex_dev->ex_dev.ex_phy[phy_id];
	struct domain_device *dev;
	struct sas_rphy *rphy;

	if (!ex_phy->port)
		return NULL;

	rphy = ex_phy->port->rphy;
	if (!rphy)
		return NULL;

	dev = sas_find_dev_by_rphy(rphy);

	if (dev && dev_is_sata(dev))
		return dev;

	return NULL;
}

Project: linux
File: sas_expander.c

static int sas_rediscover_dev(struct domain_device *dev, int phy_id, bool last)
{
	struct expander_device *ex = &dev->ex_dev;
	struct ex_phy *phy = &ex->ex_phy[phy_id];
	enum sas_device_type type = SAS_PHY_UNUSED;
	u8 sas_addr[8];
	int res;

	memset(sas_addr, 0, 8);
	res = sas_get_phy_attached_dev(dev, phy_id, sas_addr, &type);
	switch (res) {
	case SMP_RESP_NO_PHY:
		phy->phy_state = PHY_NOT_PRESENT;
		sas_unregister_devs_sas_addr(dev, phy_id, last);
		return res;
	case SMP_RESP_PHY_VACANT:
		phy->phy_state = PHY_VACANT;
		sas_unregister_devs_sas_addr(dev, phy_id, last);
		return res;
	case SMP_RESP_FUNC_ACC:
		break;
	case -ECOMM:
		break;
	default:
		return res;
	}

	if ((SAS_ADDR(sas_addr) == 0) || (res == -ECOMM)) {
		phy->phy_state = PHY_EMPTY;
		sas_unregister_devs_sas_addr(dev, phy_id, last);
		return res;
	} else if (SAS_ADDR(sas_addr) == SAS_ADDR(phy->attached_sas_addr) &&
		   dev_type_flutter(type, phy->attached_dev_type)) {
		struct domain_device *ata_dev = sas_ex_to_ata(dev, phy_id);
		char *action = "";

		sas_ex_phy_discover(dev, phy_id);

		if (ata_dev && phy->attached_dev_type == SAS_SATA_PENDING)
			action = ", needs recovery";
		SAS_DPRINTK("ex %016llx phy 0x%x broadcast flutter%s\n",
			    SAS_ADDR(dev->sas_addr), phy_id, action);
		return res;
	}

	/* delete the old link */
	if (SAS_ADDR(phy->attached_sas_addr) &&
	    SAS_ADDR(sas_addr) != SAS_ADDR(phy->attached_sas_addr)) {
		SAS_DPRINTK("ex %016llx phy 0x%x replace %016llx\n",
			    SAS_ADDR(dev->sas_addr), phy_id,
			    SAS_ADDR(phy->attached_sas_addr));
		sas_unregister_devs_sas_addr(dev, phy_id, last);
	}

	return sas_discover_new(dev, phy_id);
}

Project: linux
File: sas_expander.c

int sas_smp_phy_control(struct domain_device *dev, int phy_id,
			enum phy_func phy_func,
			struct sas_phy_linkrates *rates)
{
	u8 *pc_req;
	u8 *pc_resp;
	int res;

	pc_req = alloc_smp_req(PC_REQ_SIZE);
	if (!pc_req)
		return -ENOMEM;

	pc_resp = alloc_smp_resp(PC_RESP_SIZE);
	if (!pc_resp) {
		kfree(pc_req);
		return -ENOMEM;
	}

	pc_req[1] = SMP_PHY_CONTROL;
	pc_req[9] = phy_id;
	pc_req[10]= phy_func;
	if (rates) {
		pc_req[32] = rates->minimum_linkrate << 4;
		pc_req[33] = rates->maximum_linkrate << 4;
	}

	res = smp_execute_task(dev, pc_req, PC_REQ_SIZE, pc_resp,PC_RESP_SIZE);

	kfree(pc_resp);
	kfree(pc_req);
	return res;
}