freebsd-dev/usr.sbin/mptutil/mpt_cam.c
Don Lewis 95320acebc Fix multiple Coverity Out-of-bounds access false postive issues in CAM
The currently used idiom for clearing the part of a ccb after its
header generates one or two Coverity errors for each time it is
used.  All instances generate an Out-of-bounds access (ARRAY_VS_SINGLETON)
error because of the treatment of the header as a two element array,
with a pointer to the non-existent second element being passed as
the starting address to bzero().  Some instances also alsp generate
Out-of-bounds access (OVERRUN) errors, probably because the space
being cleared is larger than the sizeofstruct ccb_hdr).

In addition, this idiom is difficult for humans to understand and
it is error prone.  The user has to chose the proper struct ccb_*
type (which does not appear in the surrounding code) for the sizeof()
in the length calculation.  I found several instances where the
length was incorrect, which could cause either an actual out of
bounds write, or incompletely clear the ccb.

A better way is to write the code to clear the ccb itself starting
at sizeof(ccb_hdr) bytes from the start of the ccb, and calculate
the length based on the specific type of struct ccb_* being cleared
as specified by the union ccb member being used.  The latter can
normally be seen in the nearby code.  This is friendlier for Coverity
and other static analysis tools because they will see that the
intent is to clear the trailing part of the ccb.

Wrap all of the boilerplate code in a convenient macro that only
requires a pointer to the desired union ccb member (or a pointer
to the union ccb itself) as an argument.

Reported by:	Coverity
CID:		1007578, 1008684, 1009724, 1009773, 1011304, 1011306
CID:		1011307, 1011308, 1011309, 1011310, 1011311, 1011312
CID:		1011313, 1011314, 1011315, 1011316, 1011317, 1011318
CID:		1011319, 1011320, 1011321, 1011322, 1011324, 1011325
CID:		1011326, 1011327, 1011328, 1011329, 1011330, 1011374
CID:		1011390, 1011391, 1011392, 1011393, 1011394, 1011395
CID:		1011396, 1011397, 1011398, 1011399, 1011400, 1011401
CID:		1011402, 1011403, 1011404, 1011405, 1011406, 1011408
CID:		1011409, 1011410, 1011411, 1011412, 1011413, 1011414
CID:		1017461, 1018387, 1086860, 1086874, 1194257, 1229897
CID:		1229968, 1306229, 1306234, 1331282, 1331283, 1331294
CID:		1331295, 1331535, 1331536, 1331539, 1331540, 1341623
CID:		1341624, 1341637, 1341638, 1355264, 1355324
Reviewed by:	scottl, ken, delphij, imp
MFH:		1 month
Differential Revision:	https://reviews.freebsd.org/D6496
2016-05-24 00:57:11 +00:00

567 lines
14 KiB
C

/*-
* Copyright (c) 2008 Yahoo!, Inc.
* All rights reserved.
* Written by: John Baldwin <jhb@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__RCSID("$FreeBSD$");
#include <sys/param.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <camlib.h>
#include <cam/scsi/scsi_message.h>
#include <cam/scsi/scsi_pass.h>
#include "mptutil.h"
static int xptfd;
static int
xpt_open(void)
{
if (xptfd == 0)
xptfd = open(XPT_DEVICE, O_RDWR);
return (xptfd);
}
/* Fetch the path id of bus 0 for the opened mpt controller. */
static int
fetch_path_id(path_id_t *path_id)
{
struct bus_match_pattern *b;
union ccb ccb;
size_t bufsize;
int error;
if (xpt_open() < 0)
return (ENXIO);
/* First, find the path id of bus 0 for this mpt controller. */
bzero(&ccb, sizeof(ccb));
ccb.ccb_h.func_code = XPT_DEV_MATCH;
bufsize = sizeof(struct dev_match_result) * 1;
ccb.cdm.num_matches = 0;
ccb.cdm.match_buf_len = bufsize;
ccb.cdm.matches = calloc(1, bufsize);
bufsize = sizeof(struct dev_match_pattern) * 1;
ccb.cdm.num_patterns = 1;
ccb.cdm.pattern_buf_len = bufsize;
ccb.cdm.patterns = calloc(1, bufsize);
/* Match mptX bus 0. */
ccb.cdm.patterns[0].type = DEV_MATCH_BUS;
b = &ccb.cdm.patterns[0].pattern.bus_pattern;
snprintf(b->dev_name, sizeof(b->dev_name), "mpt");
b->unit_number = mpt_unit;
b->bus_id = 0;
b->flags = BUS_MATCH_NAME | BUS_MATCH_UNIT | BUS_MATCH_BUS_ID;
if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) {
error = errno;
free(ccb.cdm.matches);
free(ccb.cdm.patterns);
return (error);
}
free(ccb.cdm.patterns);
if (((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) ||
(ccb.cdm.status != CAM_DEV_MATCH_LAST)) {
warnx("fetch_path_id got CAM error %#x, CDM error %d\n",
ccb.ccb_h.status, ccb.cdm.status);
free(ccb.cdm.matches);
return (EIO);
}
/* We should have exactly 1 match for the bus. */
if (ccb.cdm.num_matches != 1 ||
ccb.cdm.matches[0].type != DEV_MATCH_BUS) {
free(ccb.cdm.matches);
return (ENOENT);
}
*path_id = ccb.cdm.matches[0].result.bus_result.path_id;
free(ccb.cdm.matches);
return (0);
}
int
mpt_query_disk(U8 VolumeBus, U8 VolumeID, struct mpt_query_disk *qd)
{
struct periph_match_pattern *p;
struct periph_match_result *r;
union ccb ccb;
path_id_t path_id;
size_t bufsize;
int error;
/* mpt(4) only handles devices on bus 0. */
if (VolumeBus != 0)
return (ENXIO);
if (xpt_open() < 0)
return (ENXIO);
/* Find the path ID of bus 0. */
error = fetch_path_id(&path_id);
if (error)
return (error);
bzero(&ccb, sizeof(ccb));
ccb.ccb_h.func_code = XPT_DEV_MATCH;
ccb.ccb_h.path_id = CAM_XPT_PATH_ID;
ccb.ccb_h.target_id = CAM_TARGET_WILDCARD;
ccb.ccb_h.target_lun = CAM_LUN_WILDCARD;
bufsize = sizeof(struct dev_match_result) * 5;
ccb.cdm.num_matches = 0;
ccb.cdm.match_buf_len = bufsize;
ccb.cdm.matches = calloc(1, bufsize);
bufsize = sizeof(struct dev_match_pattern) * 1;
ccb.cdm.num_patterns = 1;
ccb.cdm.pattern_buf_len = bufsize;
ccb.cdm.patterns = calloc(1, bufsize);
/* Look for a "da" device at the specified target and lun. */
ccb.cdm.patterns[0].type = DEV_MATCH_PERIPH;
p = &ccb.cdm.patterns[0].pattern.periph_pattern;
p->path_id = path_id;
snprintf(p->periph_name, sizeof(p->periph_name), "da");
p->target_id = VolumeID;
p->flags = PERIPH_MATCH_PATH | PERIPH_MATCH_NAME | PERIPH_MATCH_TARGET;
if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) {
error = errno;
free(ccb.cdm.matches);
free(ccb.cdm.patterns);
return (error);
}
free(ccb.cdm.patterns);
if (((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) ||
(ccb.cdm.status != CAM_DEV_MATCH_LAST)) {
warnx("mpt_query_disk got CAM error %#x, CDM error %d\n",
ccb.ccb_h.status, ccb.cdm.status);
free(ccb.cdm.matches);
return (EIO);
}
/*
* We should have exactly 1 match for the peripheral.
* However, if we don't get a match, don't print an error
* message and return ENOENT.
*/
if (ccb.cdm.num_matches == 0) {
free(ccb.cdm.matches);
return (ENOENT);
}
if (ccb.cdm.num_matches != 1) {
warnx("mpt_query_disk got %d matches, expected 1",
ccb.cdm.num_matches);
free(ccb.cdm.matches);
return (EIO);
}
if (ccb.cdm.matches[0].type != DEV_MATCH_PERIPH) {
warnx("mpt_query_disk got wrong CAM match");
free(ccb.cdm.matches);
return (EIO);
}
/* Copy out the data. */
r = &ccb.cdm.matches[1].result.periph_result;
snprintf(qd->devname, sizeof(qd->devname), "%s%d", r->periph_name,
r->unit_number);
free(ccb.cdm.matches);
return (0);
}
static int
periph_is_volume(CONFIG_PAGE_IOC_2 *ioc2, struct periph_match_result *r)
{
CONFIG_PAGE_IOC_2_RAID_VOL *vol;
int i;
if (ioc2 == NULL)
return (0);
vol = ioc2->RaidVolume;
for (i = 0; i < ioc2->NumActiveVolumes; vol++, i++) {
if (vol->VolumeBus == 0 && vol->VolumeID == r->target_id)
return (1);
}
return (0);
}
/* Much borrowed from scsireadcapacity() in src/sbin/camcontrol/camcontrol.c. */
static int
fetch_scsi_capacity(struct cam_device *dev, struct mpt_standalone_disk *disk)
{
struct scsi_read_capacity_data rcap;
struct scsi_read_capacity_data_long rcaplong;
union ccb *ccb;
int error;
ccb = cam_getccb(dev);
if (ccb == NULL)
return (ENOMEM);
/* Zero the rest of the ccb. */
CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->csio);
scsi_read_capacity(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, &rcap,
SSD_FULL_SIZE, 5000);
/* Disable freezing the device queue */
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
if (cam_send_ccb(dev, ccb) < 0) {
error = errno;
cam_freeccb(ccb);
return (error);
}
if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
cam_freeccb(ccb);
return (EIO);
}
/*
* A last block of 2^32-1 means that the true capacity is over 2TB,
* and we need to issue the long READ CAPACITY to get the real
* capacity. Otherwise, we're all set.
*/
if (scsi_4btoul(rcap.addr) != 0xffffffff) {
disk->maxlba = scsi_4btoul(rcap.addr);
cam_freeccb(ccb);
return (0);
}
/* Zero the rest of the ccb. */
CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->csio);
scsi_read_capacity_16(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, 0, 0, 0,
(uint8_t *)&rcaplong, sizeof(rcaplong), SSD_FULL_SIZE, 5000);
/* Disable freezing the device queue */
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
if (cam_send_ccb(dev, ccb) < 0) {
error = errno;
cam_freeccb(ccb);
return (error);
}
if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
cam_freeccb(ccb);
return (EIO);
}
cam_freeccb(ccb);
disk->maxlba = scsi_8btou64(rcaplong.addr);
return (0);
}
/* Borrowed heavily from scsi_all.c:scsi_print_inquiry(). */
static void
format_scsi_inquiry(struct mpt_standalone_disk *disk,
struct scsi_inquiry_data *inq_data)
{
char vendor[16], product[48], revision[16], rstr[12];
if (SID_QUAL_IS_VENDOR_UNIQUE(inq_data))
return;
if (SID_TYPE(inq_data) != T_DIRECT)
return;
if (SID_QUAL(inq_data) != SID_QUAL_LU_CONNECTED)
return;
cam_strvis(vendor, inq_data->vendor, sizeof(inq_data->vendor),
sizeof(vendor));
cam_strvis(product, inq_data->product, sizeof(inq_data->product),
sizeof(product));
cam_strvis(revision, inq_data->revision, sizeof(inq_data->revision),
sizeof(revision));
/* Hack for SATA disks, no idea how to tell speed. */
if (strcmp(vendor, "ATA") == 0) {
snprintf(disk->inqstring, sizeof(disk->inqstring),
"<%s %s> SATA", product, revision);
return;
}
switch (SID_ANSI_REV(inq_data)) {
case SCSI_REV_CCS:
strcpy(rstr, "SCSI-CCS");
break;
case 5:
strcpy(rstr, "SAS");
break;
default:
snprintf(rstr, sizeof (rstr), "SCSI-%d",
SID_ANSI_REV(inq_data));
break;
}
snprintf(disk->inqstring, sizeof(disk->inqstring), "<%s %s %s> %s",
vendor, product, revision, rstr);
}
/* Much borrowed from scsiinquiry() in src/sbin/camcontrol/camcontrol.c. */
static int
fetch_scsi_inquiry(struct cam_device *dev, struct mpt_standalone_disk *disk)
{
struct scsi_inquiry_data *inq_buf;
union ccb *ccb;
int error;
ccb = cam_getccb(dev);
if (ccb == NULL)
return (ENOMEM);
/* Zero the rest of the ccb. */
CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->csio);
inq_buf = calloc(1, sizeof(*inq_buf));
if (inq_buf == NULL) {
cam_freeccb(ccb);
return (ENOMEM);
}
scsi_inquiry(&ccb->csio, 1, NULL, MSG_SIMPLE_Q_TAG, (void *)inq_buf,
SHORT_INQUIRY_LENGTH, 0, 0, SSD_FULL_SIZE, 5000);
/* Disable freezing the device queue */
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
if (cam_send_ccb(dev, ccb) < 0) {
error = errno;
free(inq_buf);
cam_freeccb(ccb);
return (error);
}
if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
free(inq_buf);
cam_freeccb(ccb);
return (EIO);
}
cam_freeccb(ccb);
format_scsi_inquiry(disk, inq_buf);
free(inq_buf);
return (0);
}
int
mpt_fetch_disks(int fd, int *ndisks, struct mpt_standalone_disk **disksp)
{
CONFIG_PAGE_IOC_2 *ioc2;
struct mpt_standalone_disk *disks;
struct periph_match_pattern *p;
struct periph_match_result *r;
struct cam_device *dev;
union ccb ccb;
path_id_t path_id;
size_t bufsize;
int count, error;
uint32_t i;
if (xpt_open() < 0)
return (ENXIO);
error = fetch_path_id(&path_id);
if (error)
return (error);
for (count = 100;; count+= 100) {
/* Try to fetch 'count' disks in one go. */
bzero(&ccb, sizeof(ccb));
ccb.ccb_h.func_code = XPT_DEV_MATCH;
bufsize = sizeof(struct dev_match_result) * (count + 1);
ccb.cdm.num_matches = 0;
ccb.cdm.match_buf_len = bufsize;
ccb.cdm.matches = calloc(1, bufsize);
bufsize = sizeof(struct dev_match_pattern) * 1;
ccb.cdm.num_patterns = 1;
ccb.cdm.pattern_buf_len = bufsize;
ccb.cdm.patterns = calloc(1, bufsize);
/* Match any "da" peripherals. */
ccb.cdm.patterns[0].type = DEV_MATCH_PERIPH;
p = &ccb.cdm.patterns[0].pattern.periph_pattern;
p->path_id = path_id;
snprintf(p->periph_name, sizeof(p->periph_name), "da");
p->flags = PERIPH_MATCH_PATH | PERIPH_MATCH_NAME;
if (ioctl(xptfd, CAMIOCOMMAND, &ccb) < 0) {
error = errno;
free(ccb.cdm.matches);
free(ccb.cdm.patterns);
return (error);
}
free(ccb.cdm.patterns);
/* Check for CCB errors. */
if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
free(ccb.cdm.matches);
return (EIO);
}
/* If we need a longer list, try again. */
if (ccb.cdm.status == CAM_DEV_MATCH_MORE) {
free(ccb.cdm.matches);
continue;
}
/* If we got an error, abort. */
if (ccb.cdm.status != CAM_DEV_MATCH_LAST) {
free(ccb.cdm.matches);
return (EIO);
}
break;
}
/* Shortcut if we don't have any "da" devices. */
if (ccb.cdm.num_matches == 0) {
free(ccb.cdm.matches);
*ndisks = 0;
*disksp = NULL;
return (0);
}
/* We should have N matches, 1 for each "da" device. */
for (i = 0; i < ccb.cdm.num_matches; i++) {
if (ccb.cdm.matches[i].type != DEV_MATCH_PERIPH) {
warnx("mpt_fetch_disks got wrong CAM matches");
free(ccb.cdm.matches);
return (EIO);
}
}
/*
* Some of the "da" peripherals may be for RAID volumes, so
* fetch the IOC 2 page (list of RAID volumes) so we can
* exclude them from the list.
*/
ioc2 = mpt_read_ioc_page(fd, 2, NULL);
if (ioc2 == NULL)
return (errno);
disks = calloc(ccb.cdm.num_matches, sizeof(*disks));
count = 0;
for (i = 0; i < ccb.cdm.num_matches; i++) {
r = &ccb.cdm.matches[i].result.periph_result;
if (periph_is_volume(ioc2, r))
continue;
disks[count].bus = 0;
disks[count].target = r->target_id;
snprintf(disks[count].devname, sizeof(disks[count].devname),
"%s%d", r->periph_name, r->unit_number);
dev = cam_open_device(disks[count].devname, O_RDWR);
if (dev != NULL) {
fetch_scsi_capacity(dev, &disks[count]);
fetch_scsi_inquiry(dev, &disks[count]);
cam_close_device(dev);
}
count++;
}
free(ccb.cdm.matches);
free(ioc2);
*ndisks = count;
*disksp = disks;
return (0);
}
/*
* Instruct the mpt(4) device to rescan its busses to find new devices
* such as disks whose RAID physdisk page was removed or volumes that
* were created. If id is -1, the entire bus is rescanned.
* Otherwise, only devices at the specified ID are rescanned. If bus
* is -1, then all busses are scanned instead of the specified bus.
* Note that currently, only bus 0 is supported.
*/
int
mpt_rescan_bus(int bus, int id)
{
union ccb ccb;
path_id_t path_id;
int error;
/* mpt(4) only handles devices on bus 0. */
if (bus != -1 && bus != 0)
return (EINVAL);
if (xpt_open() < 0)
return (ENXIO);
error = fetch_path_id(&path_id);
if (error)
return (error);
/* Perform the actual rescan. */
bzero(&ccb, sizeof(ccb));
ccb.ccb_h.path_id = path_id;
if (id == -1) {
ccb.ccb_h.func_code = XPT_SCAN_BUS;
ccb.ccb_h.target_id = CAM_TARGET_WILDCARD;
ccb.ccb_h.target_lun = CAM_LUN_WILDCARD;
ccb.ccb_h.timeout = 5000;
} else {
ccb.ccb_h.func_code = XPT_SCAN_LUN;
ccb.ccb_h.target_id = id;
ccb.ccb_h.target_lun = 0;
}
ccb.crcn.flags = CAM_FLAG_NONE;
/* Run this at a low priority. */
ccb.ccb_h.pinfo.priority = 5;
if (ioctl(xptfd, CAMIOCOMMAND, &ccb) == -1)
return (errno);
if ((ccb.ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
warnx("mpt_rescan_bus rescan got CAM error %#x\n",
ccb.ccb_h.status & CAM_STATUS_MASK);
return (EIO);
}
return (0);
}