misc/159833: Patch that adds ATA security options to camcontrol including secure erase

Steven Hartland killing at multiplay.co.uk
Wed Aug 17 09:10:09 UTC 2011


>Number:         159833
>Category:       misc
>Synopsis:       Patch that adds ATA security options to camcontrol including secure erase
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          change-request
>Submitter-Id:   current-users
>Arrival-Date:   Wed Aug 17 09:10:09 UTC 2011
>Closed-Date:
>Last-Modified:
>Originator:     Steven Hartland
>Release:        8.2-RELEASE
>Organization:
Multiplay
>Environment:
N/A
>Description:
With increase in use of both SSD's and ZFS and the lack of TRIM support under ZFS SSD's need to be secure erased periodically to return performance to that when they are new.

The attached patch adds the relavent options to camcontrol that enables an admin to perform all the ATA security options on a disk, including the ability to secure erase a disk.

Given the dangerous nature of the commands added, the patch also adds long option support to camcontrol so that command line options used are understandable instead of random letters with little or no correspondence to the options they represent.

This patch has been discussed on the fs mailing list here, where some changes where suggested an implemented:-
http://lists.freebsd.org/pipermail/freebsd-fs/2011-August/012148.html

Some more examples and info can also be found here:-
http://blog.multiplay.co.uk/2011/08/freebsd-security-support-for-ata-devices-via-camcontrol/

Much credit to Daniel Roethlisberger for his work on adding security support to atacontrol, detailed in PR bin/127918 which was the basis of this code.
http://www.roe.ch/ATA_Security
http://www.freebsd.org/cgi/query-pr.cgi?pr=bin/127918
>How-To-Repeat:
N/A
>Fix:
N/A

Patch attached with submission follows:

--- sbin/camcontrol/camcontrol.c.orig	2011-08-05 11:02:32.023288993 +0000
+++ sbin/camcontrol/camcontrol.c	2011-08-06 13:03:40.725619592 +0000
@@ -42,6 +42,7 @@
 #include <ctype.h>
 #include <err.h>
 #include <libutil.h>
+#include <getopt.h>
 
 #include <cam/cam.h>
 #include <cam/cam_debug.h>
@@ -77,7 +78,8 @@
 	CAM_CMD_IDENTIFY	= 0x00000013,
 	CAM_CMD_IDLE		= 0x00000014,
 	CAM_CMD_STANDBY		= 0x00000015,
-	CAM_CMD_SLEEP		= 0x00000016
+	CAM_CMD_SLEEP		= 0x00000016,
+	CAM_CMD_SECURITY	= 0x00000017
 } cam_cmdmask;
 
 typedef enum {
@@ -120,6 +122,7 @@
 	cam_cmdmask	cmdnum;
 	cam_argmask	argnum;
 	const char	*subopt;
+	const struct option const *suboptlong;
 };
 
 #ifndef MINIMALISTIC
@@ -128,43 +131,62 @@
 static const char negotiate_opts[] = "acD:M:O:qR:T:UW:";
 #endif
 
+static const struct option no_opts[] = { { NULL, 0, NULL, 0 } };
+
 struct camcontrol_opts option_table[] = {
 #ifndef MINIMALISTIC
-	{"tur", CAM_CMD_TUR, CAM_ARG_NONE, NULL},
-	{"inquiry", CAM_CMD_INQUIRY, CAM_ARG_NONE, "DSR"},
-	{"identify", CAM_CMD_IDENTIFY, CAM_ARG_NONE, NULL},
-	{"start", CAM_CMD_STARTSTOP, CAM_ARG_START_UNIT, NULL},
-	{"stop", CAM_CMD_STARTSTOP, CAM_ARG_NONE, NULL},
-	{"load", CAM_CMD_STARTSTOP, CAM_ARG_START_UNIT | CAM_ARG_EJECT, NULL},
-	{"eject", CAM_CMD_STARTSTOP, CAM_ARG_EJECT, NULL},
-	{"reportluns", CAM_CMD_REPORTLUNS, CAM_ARG_NONE, "clr:"},
-	{"readcapacity", CAM_CMD_READCAP, CAM_ARG_NONE, "bhHNqs"},
+	{"tur", CAM_CMD_TUR, CAM_ARG_NONE, NULL, NULL},
+	{"inquiry", CAM_CMD_INQUIRY, CAM_ARG_NONE, "DSR", NULL},
+	{"identify", CAM_CMD_IDENTIFY, CAM_ARG_NONE, NULL, NULL},
+	{"start", CAM_CMD_STARTSTOP, CAM_ARG_START_UNIT, NULL, NULL},
+	{"stop", CAM_CMD_STARTSTOP, CAM_ARG_NONE, NULL, NULL},
+	{"load", CAM_CMD_STARTSTOP, CAM_ARG_START_UNIT | CAM_ARG_EJECT, NULL, NULL},
+	{"eject", CAM_CMD_STARTSTOP, CAM_ARG_EJECT, NULL, NULL},
+	{"reportluns", CAM_CMD_REPORTLUNS, CAM_ARG_NONE, "clr:", NULL},
+	{"readcapacity", CAM_CMD_READCAP, CAM_ARG_NONE, "bhHNqs", NULL},
 #endif /* MINIMALISTIC */
-	{"rescan", CAM_CMD_RESCAN, CAM_ARG_NONE, NULL},
-	{"reset", CAM_CMD_RESET, CAM_ARG_NONE, NULL},
+	{"rescan", CAM_CMD_RESCAN, CAM_ARG_NONE, NULL, NULL},
+	{"reset", CAM_CMD_RESET, CAM_ARG_NONE, NULL, NULL},
 #ifndef MINIMALISTIC
-	{"cmd", CAM_CMD_SCSI_CMD, CAM_ARG_NONE, scsicmd_opts},
-	{"command", CAM_CMD_SCSI_CMD, CAM_ARG_NONE, scsicmd_opts},
-	{"defects", CAM_CMD_READ_DEFECTS, CAM_ARG_NONE, readdefect_opts},
-	{"defectlist", CAM_CMD_READ_DEFECTS, CAM_ARG_NONE, readdefect_opts},
+	{"cmd", CAM_CMD_SCSI_CMD, CAM_ARG_NONE, scsicmd_opts, NULL},
+	{"command", CAM_CMD_SCSI_CMD, CAM_ARG_NONE, scsicmd_opts, NULL},
+	{"defects", CAM_CMD_READ_DEFECTS, CAM_ARG_NONE, readdefect_opts, NULL},
+	{"defectlist", CAM_CMD_READ_DEFECTS, CAM_ARG_NONE, readdefect_opts, NULL},
 #endif /* MINIMALISTIC */
-	{"devlist", CAM_CMD_DEVTREE, CAM_ARG_NONE, NULL},
+	{"devlist", CAM_CMD_DEVTREE, CAM_ARG_NONE, NULL, NULL},
 #ifndef MINIMALISTIC
-	{"periphlist", CAM_CMD_DEVLIST, CAM_ARG_NONE, NULL},
-	{"modepage", CAM_CMD_MODE_PAGE, CAM_ARG_NONE, "bdelm:P:"},
-	{"tags", CAM_CMD_TAG, CAM_ARG_NONE, "N:q"},
-	{"negotiate", CAM_CMD_RATE, CAM_ARG_NONE, negotiate_opts},
-	{"rate", CAM_CMD_RATE, CAM_ARG_NONE, negotiate_opts},
-	{"debug", CAM_CMD_DEBUG, CAM_ARG_NONE, "IPTSXc"},
-	{"format", CAM_CMD_FORMAT, CAM_ARG_NONE, "qrwy"},
-	{"idle", CAM_CMD_IDLE, CAM_ARG_NONE, "t:"},
-	{"standby", CAM_CMD_STANDBY, CAM_ARG_NONE, "t:"},
-	{"sleep", CAM_CMD_SLEEP, CAM_ARG_NONE, ""},
+	{"periphlist", CAM_CMD_DEVLIST, CAM_ARG_NONE, NULL, NULL},
+	{"modepage", CAM_CMD_MODE_PAGE, CAM_ARG_NONE, "bdelm:P:", NULL},
+	{"tags", CAM_CMD_TAG, CAM_ARG_NONE, "N:q", NULL},
+	{"negotiate", CAM_CMD_RATE, CAM_ARG_NONE, negotiate_opts, NULL},
+	{"rate", CAM_CMD_RATE, CAM_ARG_NONE, negotiate_opts, NULL},
+	{"debug", CAM_CMD_DEBUG, CAM_ARG_NONE, "IPTSXc", NULL},
+	{"format", CAM_CMD_FORMAT, CAM_ARG_NONE, "qrwy", NULL},
+	{"idle", CAM_CMD_IDLE, CAM_ARG_NONE, "t:", NULL},
+	{"standby", CAM_CMD_STANDBY, CAM_ARG_NONE, "t:", NULL},
+	{"sleep", CAM_CMD_SLEEP, CAM_ARG_NONE, "", NULL},
+	{"security", CAM_CMD_SECURITY, CAM_ARG_NONE, "fr:m:s:e:h:d:U:",
+		(const struct option const [])
+		{
+			{ "security-quiet", no_argument, NULL, 'q' },
+			{ "security-confirm", no_argument, NULL, 'y' },
+			{ "security-freeze", no_argument, NULL, 'f' },
+			{ "security-user", required_argument, NULL, 'r' },
+			{ "security-level", required_argument, NULL, 'l' },
+			{ "security-set-password", required_argument, NULL, 's' },
+			{ "security-disable", required_argument, NULL, 'd' },
+			{ "security-unlock", required_argument, NULL, 'U' },
+			{ "security-erase", required_argument, NULL, 'e' },
+			{ "security-erase-enhanced", required_argument, NULL, 'h' },
+			{ "security-erase-timeout", required_argument, NULL, 'i' },
+			{ NULL, 0, NULL, 0 }
+		}
+	},
 #endif /* MINIMALISTIC */
-	{"help", CAM_CMD_USAGE, CAM_ARG_NONE, NULL},
-	{"-?", CAM_CMD_USAGE, CAM_ARG_NONE, NULL},
-	{"-h", CAM_CMD_USAGE, CAM_ARG_NONE, NULL},
-	{NULL, 0, 0, NULL}
+	{"help", CAM_CMD_USAGE, CAM_ARG_NONE, NULL, NULL},
+	{"-?", CAM_CMD_USAGE, CAM_ARG_NONE, NULL, NULL},
+	{"-h", CAM_CMD_USAGE, CAM_ARG_NONE, NULL, NULL},
+	{NULL, 0, 0, NULL, NULL}
 };
 
 typedef enum {
@@ -178,7 +200,7 @@
 
 
 camcontrol_optret getoption(char *arg, cam_cmdmask *cmdnum, cam_argmask *argnum,
-			    const char **subopt);
+			    const char **subopt, const struct option **suboptlong);
 #ifndef MINIMALISTIC
 static int getdevlist(struct cam_device *device);
 #endif /* MINIMALISTIC */
@@ -225,6 +247,9 @@
 			    char *combinedopt, int retry_count, int timeout);
 static int atapm(struct cam_device *device, int argc, char **argv,
 			    char *combinedopt, int retry_count, int timeout);
+static int atasecurity(struct cam_device *device, int retry_count, int timeout,
+			int argc, char **argv, char *combinedopt, const struct option *combinedoptlong);
+
 #endif /* MINIMALISTIC */
 #ifndef min
 #define min(a,b) (((a)<(b))?(a):(b))
@@ -235,10 +260,11 @@
 
 camcontrol_optret
 getoption(char *arg, cam_cmdmask *cmdnum, cam_argmask *argnum,
-	  const char **subopt)
+	  const char **subopt, const struct option **suboptlong)
 {
 	struct camcontrol_opts *opts;
 	int num_matches = 0;
+	const struct option empty_optlong[] = {{NULL, 0, NULL, 0}};
 
 	for (opts = option_table; (opts != NULL) && (opts->optname != NULL);
 	     opts++) {
@@ -246,6 +272,10 @@
 			*cmdnum = opts->cmdnum;
 			*argnum = opts->argnum;
 			*subopt = opts->subopt;
+			if (NULL != opts->suboptlong)
+				*suboptlong = opts->suboptlong;
+			else
+				*suboptlong = empty_optlong;
 			if (++num_matches > 1)
 				return(CC_OR_AMBIGUOUS);
 		}
@@ -1264,33 +1294,48 @@
 }
 
 static int
-ataidentify(struct cam_device *device, int retry_count, int timeout)
+ata_cam_send(struct cam_device *device, union ccb *ccb)
 {
-	union ccb *ccb;
-	struct ata_params *ident_buf;
-	struct ccb_getdev cgd;
-	u_int i, error = 0;
-	int16_t *ptr;
+	if (arglist & CAM_ARG_VERBOSE)
+		warnx("sending ATA %s with timeout of %u msecs", ata_op_string(&(ccb->ataio.cmd)),
+		    ccb->ataio.ccb_h.timeout);
+
+	/* Disable freezing the device queue */
+	ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
+
+	if (arglist & CAM_ARG_ERR_RECOVER)
+		ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
+
+	if (cam_send_ccb(device, ccb) < 0) {
+		warn("error sending ATA %s", ata_op_string(&(ccb->ataio.cmd)));
+
+		if (arglist & CAM_ARG_VERBOSE)
+			cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
 
-	if (get_cgd(device, &cgd) != 0) {
-		warnx("couldn't get CGD");
 		return(1);
 	}
-	ccb = cam_getccb(device);
 
-	if (ccb == NULL) {
-		warnx("couldn't allocate CCB");
+	if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
+		warnx("ATA %s failed", ata_op_string(&(ccb->ataio.cmd)));
+		if (arglist & CAM_ARG_VERBOSE)
+			cam_error_print(device, ccb, CAM_ESF_ALL, CAM_EPF_ALL, stderr);
+
 		return(1);
 	}
 
-	/* cam_getccb cleans up the header, caller has to zero the payload */
-	bzero(&(&ccb->ccb_h)[1],
-	      sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr));
+	return(0);
+}
 
-	ptr = (uint16_t *)malloc(sizeof(struct ata_params));
+static int
+ata_do_identify(struct cam_device *device, int retry_count, int timeout,
+	union ccb *ccb, struct ccb_getdev *cgd, struct ata_params** ident_bufp)
+{
+	struct ata_params *ident_buf;
+	u_int i, error;
+	int16_t *ptr;
 
+	ptr = (uint16_t *)malloc(sizeof(struct ata_params));
 	if (ptr == NULL) {
-		cam_freeccb(ccb);
 		warnx("can't malloc memory for identify\n");
 		return(1);
 	}
@@ -1304,48 +1349,23 @@
 		      /*data_ptr*/(u_int8_t *)ptr,
 		      /*dxfer_len*/sizeof(struct ata_params),
 		      timeout ? timeout : 30 * 1000);
-	if (cgd.protocol == PROTO_ATA)
-		ata_28bit_cmd(&ccb->ataio, ATA_ATA_IDENTIFY, 0, 0, 0);
-	else
-		ata_28bit_cmd(&ccb->ataio, ATA_ATAPI_IDENTIFY, 0, 0, 0);
-
-	/* Disable freezing the device queue */
-	ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
-
-	if (arglist & CAM_ARG_ERR_RECOVER)
-		ccb->ccb_h.flags |= CAM_PASS_ERR_RECOVER;
 
-	if (cam_send_ccb(device, ccb) < 0) {
-		perror("error sending ATA identify");
-
-		if (arglist & CAM_ARG_VERBOSE) {
-			cam_error_print(device, ccb, CAM_ESF_ALL,
-					CAM_EPF_ALL, stderr);
-		}
+	/*
+	 * We check protocol == PROTO_ATAPI using ATA as default to enhance
+	 * compatibility with other controllers which may support passthrough
+	 */
+	ata_28bit_cmd(&ccb->ataio, (cgd->protocol == PROTO_ATAPI) ?
+	    ATA_ATAPI_IDENTIFY : ATA_ATA_IDENTIFY, 0, 0, 0);
 
+	error = ata_cam_send(device, ccb);
+	if (0 != error) {
 		free(ptr);
-		cam_freeccb(ccb);
 		return(1);
 	}
 
-	if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
-		error = 1;
-
-		if (arglist & CAM_ARG_VERBOSE) {
-			cam_error_print(device, ccb, CAM_ESF_ALL,
-					CAM_EPF_ALL, stderr);
-		}
-	}
-
-	cam_freeccb(ccb);
-
-	if (error != 0) {
-		free(ptr);
-		return(error);
-	}
-
 	for (i = 0; i < sizeof(struct ata_params) / 2; i++)
 		ptr[i] = le16toh(ptr[i]);
+
 	if (arglist & CAM_ARG_VERBOSE) {
 		fprintf(stdout, "%s%d: Raw identify data:\n",
 		    device->device_name, device->dev_unit_num);
@@ -1377,18 +1397,559 @@
 	ata_bpack(ident_buf->media_serial, ident_buf->media_serial,
 	    sizeof(ident_buf->media_serial));
 
-	fprintf(stdout, "%s%d: ", device->device_name,
-		device->dev_unit_num);
+	*ident_bufp = ident_buf;
+
+	return 0;
+}
+
+
+static int
+ataidentify(struct cam_device *device, int retry_count, int timeout)
+{
+	union ccb *ccb;
+	struct ata_params *ident_buf;
+	struct ccb_getdev cgd;
+	u_int error = 0;
+
+	if (get_cgd(device, &cgd) != 0) {
+		warnx("couldn't get CGD");
+		return(1);
+	}
+
+	ccb = cam_getccb(device);
+	if (ccb == NULL) {
+		warnx("couldn't allocate CCB");
+		return(1);
+	}
+
+	/* cam_getccb cleans up the header, caller has to zero the payload */
+	bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr));
+
+	error = ata_do_identify(device, retry_count, timeout, ccb, &cgd, &ident_buf);
+	if (0 != error) {
+		cam_freeccb(ccb);
+		return(1);
+	}
+
+	fprintf(stdout, "%s%d: ", device->device_name, device->dev_unit_num);
 	ata_print_ident(ident_buf);
 	camxferrate(device);
 	atacapprint(ident_buf);
 
 	free(ident_buf);
+	cam_freeccb(ccb);
 
 	return(0);
 }
 #endif /* MINIMALISTIC */
 
+
+#ifndef MINIMALISTIC
+enum {
+	ATA_SECURITY_ACTION_PRINT,
+	ATA_SECURITY_ACTION_FREEZE,
+	ATA_SECURITY_ACTION_UNLOCK,
+	ATA_SECURITY_ACTION_DISABLE,
+	ATA_SECURITY_ACTION_ERASE,
+	ATA_SECURITY_ACTION_ERASE_ENHANCED,
+	ATA_SECURITY_ACTION_SET_PASSWORD
+} atasecurity_action;
+
+static void
+atasecurity_print_time(u_int16_t tw)
+{
+	if (tw == 0)
+		printf("unspecified");
+	else if (tw >= 255)
+		printf("> 508 min");
+	else
+		printf("%i min", 2 * tw);
+}
+
+static u_int32_t
+atasecurity_erase_timeout_msecs(u_int16_t timeout)
+{
+	if (0 == timeout)
+		return 2 * 3600 * 1000; /* default: two hours */
+	else if (255 <= timeout)
+		return (508 + 60) * 60 * 1000; /* spec says > 508 minutes */
+
+	return ((2 * timeout) + 5) * 60 * 1000; /* add a 5min margin */
+}
+
+static void
+atasecurity_notify(union ccb *ccb, struct ata_security_password *pwd)
+{
+	fprintf(stdout, "Issuing %s", ata_op_string(&(ccb->ataio.cmd)));
+
+	if (NULL != pwd) {
+		fprintf(stdout, " password='%s', user='%s'",
+		    pwd->password,
+		    (pwd->ctrl & ATA_SECURITY_PASSWORD_MASTER) ? "master" : "user");
+
+		if(ATA_SECURITY_SET_PASSWORD == ccb->ataio.cmd.command)
+			fprintf(stdout, ", mode='%s'",
+			    (pwd->ctrl & ATA_SECURITY_LEVEL_MAXIMUM) ?  "maximum" : "high");
+	}
+
+	fprintf(stdout, "\n");
+}
+
+static int
+atasecurity_freeze(struct cam_device *device, union ccb *ccb, int retry_count,
+	u_int32_t timeout, int quiet)
+{
+	cam_fill_ataio(&ccb->ataio,
+		    retry_count,
+		    NULL,
+		    /*flags*/CAM_DIR_NONE,
+		    MSG_SIMPLE_Q_TAG,
+		    /*data_ptr*/NULL,
+		    /*dxfer_len*/0,
+		    timeout);
+
+	ata_28bit_cmd(&ccb->ataio, ATA_SECURITY_FREEZE_LOCK, 0, 0, 0);
+
+	if (0 == quiet)
+		atasecurity_notify(ccb, NULL);
+
+	return ata_cam_send(device, ccb);
+}
+
+static int
+atasecurity_unlock(struct cam_device *device, union ccb *ccb, int retry_count,
+	u_int32_t timeout, struct ata_security_password *pwd, int quiet)
+{
+	cam_fill_ataio(&ccb->ataio,
+		    retry_count,
+		    NULL,
+		    /*flags*/CAM_DIR_OUT,
+		    MSG_SIMPLE_Q_TAG,
+		    /*data_ptr*/(u_int8_t *)pwd,
+		    /*dxfer_len*/sizeof(struct ata_security_password),
+		    timeout);
+
+	ata_28bit_cmd(&ccb->ataio, ATA_SECURITY_UNLOCK, 0, 0, 0);
+
+	if (0 == quiet)
+		atasecurity_notify(ccb, pwd);
+
+	return ata_cam_send(device, ccb);
+}
+
+static int
+atasecurity_disable(struct cam_device *device, union ccb *ccb, int retry_count,
+	u_int32_t timeout, struct ata_security_password *pwd, int quiet)
+{
+	cam_fill_ataio(&ccb->ataio,
+		    retry_count,
+		    NULL,
+		    /*flags*/CAM_DIR_OUT,
+		    MSG_SIMPLE_Q_TAG,
+		    /*data_ptr*/(u_int8_t *)pwd,
+		    /*dxfer_len*/sizeof(struct ata_security_password),
+		    timeout);
+
+	ata_28bit_cmd(&ccb->ataio, ATA_SECURITY_DISABLE_PASSWORD, 0, 0, 0);
+
+	if (0 == quiet)
+		atasecurity_notify(ccb, pwd);
+
+	return ata_cam_send(device, ccb);
+}
+
+static int
+atasecurity_erase(struct cam_device *device, union ccb *ccb, int retry_count,
+	u_int32_t timeout, u_int32_t erase_timeout, struct ata_security_password *pwd,
+	int confirm, int quiet, struct ata_params* ident_buf)
+{
+	int error = 0, response = 0;
+	if (0 == quiet) {
+        fprintf(stdout, "\nYou are about to ERASE ALL DATA from the "
+            "following device:\n");
+		fprintf(stdout, "%s%d: ", device->device_name, device->dev_unit_num);
+		ata_print_ident(ident_buf);
+	}
+
+	if (0 == confirm) {
+		do {
+			char str[50];
+			fprintf(stdout, "Are you SURE you want to do this? (yes/no) ");
+
+			if (NULL != fgets(str, sizeof(str), stdin)) {
+				if (0 == strncasecmp(str, "yes", 3))
+					response = 1;
+				else if (0 == strncasecmp(str, "no", 2))
+					response = -1;
+				else
+					fprintf(stdout, "Please answer \"yes\" or \"no\"\n");
+			}
+		} while (0 == response);
+
+		if (-1 == response)
+			return(1);
+	}
+
+	cam_fill_ataio(&ccb->ataio,
+		    retry_count,
+		    NULL,
+		    /*flags*/CAM_DIR_NONE,
+		    MSG_SIMPLE_Q_TAG,
+		    /*data_ptr*/NULL,
+		    /*dxfer_len*/0,
+		    timeout);
+
+	ata_28bit_cmd(&ccb->ataio, ATA_SECURITY_ERASE_PREPARE, 0, 0, 0);
+
+	error = ata_cam_send(device, ccb);
+	if (0 != error)
+		return error;
+
+	/* cam_getccb cleans up the header, caller has to zero the payload */
+	bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr));
+
+	cam_fill_ataio(&ccb->ataio,
+		    retry_count,
+		    NULL,
+		    /*flags*/CAM_DIR_OUT,
+		    MSG_SIMPLE_Q_TAG,
+		    /*data_ptr*/(u_int8_t *)pwd,
+		    /*dxfer_len*/sizeof(struct ata_security_password),
+		    erase_timeout);
+
+	ata_28bit_cmd(&ccb->ataio, ATA_SECURITY_ERASE_UNIT, 0, 0, 0);
+
+	if (0 == quiet)
+		atasecurity_notify(ccb, pwd);
+
+	error = ata_cam_send(device, ccb);
+
+	if (0 == error && 0 == quiet)
+		fprintf(stdout, "\nErase Complete\n");
+
+	return error;
+}
+
+static int
+atasecurity_set_password(struct cam_device *device, union ccb *ccb, int retry_count,
+	u_int32_t timeout, struct ata_security_password *pwd, int quiet)
+{
+	cam_fill_ataio(&ccb->ataio,
+		    retry_count,
+		    NULL,
+		    /*flags*/CAM_DIR_OUT,
+		    MSG_SIMPLE_Q_TAG,
+		    /*data_ptr*/(u_int8_t *)pwd,
+		    /*dxfer_len*/sizeof(struct ata_security_password),
+		    timeout);
+
+	ata_28bit_cmd(&ccb->ataio, ATA_SECURITY_SET_PASSWORD, 0, 0, 0);
+
+	if (0 == quiet)
+		atasecurity_notify(ccb, pwd);
+
+	return ata_cam_send(device, ccb);
+}
+
+static void
+atasecurity_print(struct ata_params *parm)
+{
+
+	printf("\nSecurity Option           Value\n");
+	if (arglist & CAM_ARG_VERBOSE) {
+		printf("status                    %04x\n", parm->security_status);
+	}
+	printf("supported                 %s\n",
+		parm->security_status & ATA_SECURITY_SUPPORTED ? "yes" : "no");
+	if (!(parm->security_status & ATA_SECURITY_SUPPORTED))
+		return;
+	printf("enabled                   %s\n",
+		parm->security_status & ATA_SECURITY_ENABLED ? "yes" : "no");
+	printf("drive locked              %s\n",
+		parm->security_status & ATA_SECURITY_LOCKED ? "yes" : "no");
+	printf("security config frozen    %s\n",
+		parm->security_status & ATA_SECURITY_FROZEN ? "yes" : "no");
+	printf("count expired             %s\n",
+		parm->security_status & ATA_SECURITY_COUNT_EXP ? "yes" : "no");
+	printf("security level            %s\n",
+		parm->security_status & ATA_SECURITY_LEVEL ? "maximum" : "high");
+	printf("enhanced erase supported  %s\n",
+		parm->security_status & ATA_SECURITY_ENH_SUPP ? "yes" : "no");
+	printf("erase time                ");
+	atasecurity_print_time(parm->erase_time);
+	printf("\n");
+	printf("enhanced erase time       ");
+	atasecurity_print_time(parm->enhanced_erase_time);
+	printf("\n");
+	printf("master password rev       %04x%s\n",
+			parm->master_passwd_revision,
+			parm->master_passwd_revision == 0x0000 ||
+			parm->master_passwd_revision == 0xFFFF ?
+			" (unsupported)" : "");
+}
+
+static int
+atasecurity(struct cam_device *device, int retry_count, int timeout,
+	int argc, char **argv, char *combinedopt, const struct option *combinedoptlong)
+{
+	union ccb *ccb;
+	struct ata_params *ident_buf;
+	u_int error = 0, confirm = 0, quiet = 0;
+	int c = -1;
+	int action = ATA_SECURITY_ACTION_PRINT;
+	int actions = 0, setpwd = 0, enabled = 0, erase_timeout = 0;
+	struct ata_security_password pwd;
+	struct ccb_getdev cgd;
+
+	memset(&pwd, 0, sizeof(pwd));
+	pwd.ctrl |= ATA_SECURITY_PASSWORD_MASTER; /* user is master by default as its safer that way */
+
+	while ((c = getopt_long(argc, argv, combinedopt, combinedoptlong, NULL)) != -1) {
+        switch(c){
+        case 'f':
+			action = ATA_SECURITY_ACTION_FREEZE;
+			actions++;
+			break;
+
+		case 'r':
+			if (0 == strcasecmp(optarg, "user")) {
+				pwd.ctrl ^= ATA_SECURITY_PASSWORD_MASTER;
+				pwd.ctrl |= ATA_SECURITY_PASSWORD_USER;
+			} else if (0 != strcasecmp(optarg, "master")) {
+				warnx("--security-user argument '%s' is unknown, must be 'user' or 'master'", optarg);
+				return(1);
+			}
+			break;
+
+		case 'l':
+			if (0 == strcasecmp(optarg, "high"))
+				pwd.ctrl |= ATA_SECURITY_LEVEL_HIGH;
+			else if (0 == strcasecmp(optarg, "maximum"))
+				pwd.ctrl |= ATA_SECURITY_LEVEL_MAXIMUM;
+			else {
+				warnx("--security-level argument '%s' is unknown, must be 'high' or 'maximum'", optarg);
+				return(1);
+			}
+			break;
+
+		case 'U':
+			if (sizeof(pwd.password) < strlen(optarg)) {
+				warnx("--security-unlock password is too long");
+				return(1);
+			}
+			strlcpy(pwd.password, optarg, sizeof(pwd.password));
+			action = ATA_SECURITY_ACTION_UNLOCK;
+			actions++;
+			break;
+
+		case 'd':
+			if (sizeof(pwd.password) < strlen(optarg)) {
+				warnx("--security-disable password is too long");
+				return(1);
+			}
+			strlcpy(pwd.password, optarg, sizeof(pwd.password));
+			action = ATA_SECURITY_ACTION_DISABLE;
+			actions++;
+			break;
+
+		case 'e':
+			if (sizeof(pwd.password) < strlen(optarg)) {
+				warnx("--security-erase password is too long");
+				return(1);
+			}
+			strlcpy(pwd.password, optarg, sizeof(pwd.password));
+			action = ATA_SECURITY_ACTION_ERASE;
+			actions++;
+			break;
+
+		case 'h':
+			if (sizeof(pwd.password) < strlen(optarg)) {
+				warnx("--security-erase-enhanced password is too long");
+				return(1);
+			}
+			strlcpy(pwd.password, optarg, sizeof(pwd.password));
+			pwd.ctrl |= ATA_SECURITY_ERASE_ENHANCED;
+			action = ATA_SECURITY_ACTION_ERASE_ENHANCED;
+			actions++;
+			break;
+
+		case 's':
+			if (sizeof(pwd.password) < strlen(optarg)) {
+				warnx("--security-set-password password is too long");
+				return(1);
+			}
+			strlcpy(pwd.password, optarg, sizeof(pwd.password));
+			setpwd = 1;
+			action = ATA_SECURITY_ACTION_SET_PASSWORD;
+			/* don't increment action as this can be combined with other actions */
+			break;
+
+		case 'y':
+			confirm++;
+			break;
+
+		case 'q':
+			quiet++;
+			break;
+
+		case 'i':
+			erase_timeout = atoi(optarg) * 1000;
+			break;
+		}
+	}
+	argc -= optind;
+	argv += optind;
+
+	if (1 < actions) {
+		warnx("too many security actions specified");
+		return(1);
+	}
+
+	if (get_cgd(device, &cgd) != 0) {
+		warnx("couldn't get CGD");
+		return(1);
+	}
+
+	ccb = cam_getccb(device);
+	if (ccb == NULL) {
+		warnx("couldn't allocate CCB");
+		return(1);
+	}
+
+	/* cam_getccb cleans up the header, caller has to zero the payload */
+	bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr));
+
+	error = ata_do_identify(device, retry_count, timeout, ccb, &cgd, &ident_buf);
+	if (0 != error) {
+		cam_freeccb(ccb);
+		return(1);
+	}
+
+	if (0 == quiet) {
+		fprintf(stdout, "%s%d: ", device->device_name, device->dev_unit_num);
+		ata_print_ident(ident_buf);
+		camxferrate(device);
+	}
+
+	if (action == ATA_SECURITY_ACTION_PRINT) {
+		if (0 != quiet) {
+			fprintf(stdout, "%s%d: ", device->device_name, device->dev_unit_num);
+			ata_print_ident(ident_buf);
+			camxferrate(device);
+		}
+		atasecurity_print(ident_buf);
+		free(ident_buf);
+		cam_freeccb(ccb);
+		return 0;
+	}
+
+	if (!(ident_buf->support.command1 & ATA_SUPPORT_SECURITY)) {
+		warnx("Security not supported");
+		free(ident_buf);
+		cam_freeccb(ccb);
+		return(1);
+	}
+
+	/* default timeout 15 seconds the same as linux hdparm */
+	timeout = timeout ? timeout : 15 * 1000;
+
+	/* cam_getccb cleans up the header, caller has to zero the payload */
+	bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr));
+
+	enabled = ident_buf->security_status & ATA_SECURITY_ENABLED;
+
+	/* first set the password if requested */
+	if (setpwd) {
+		/* prepare pwd details */
+		if (pwd.ctrl & ATA_SECURITY_PASSWORD_MASTER) {
+			pwd.revision = ident_buf->master_passwd_revision;
+			if (0 != pwd.revision && 0xffff != pwd.revision && 0 == --pwd.revision)
+				pwd.revision = 0xfffe;
+		}
+		error = atasecurity_set_password(device, ccb, retry_count, timeout, &pwd, quiet);
+		if (0 != error) {
+			cam_freeccb(ccb);
+			free(ident_buf);
+			return error;
+		}
+		enabled = 1;
+	}
+
+	switch(action) {
+	case ATA_SECURITY_ACTION_FREEZE:
+		error = atasecurity_freeze(device, ccb, retry_count, timeout, quiet);
+		break;
+
+	case ATA_SECURITY_ACTION_UNLOCK:
+		if (enabled) {
+			if (ident_buf->security_status & ATA_SECURITY_LOCKED)
+				error = atasecurity_unlock(device, ccb, retry_count, timeout, &pwd, quiet);
+			else {
+				warnx("Can't unlock, drive is not locked");
+				error = 1;
+			}
+		} else {
+			warnx("Can't unlock, security is disabled");
+			error = 1;
+		}
+		break;
+
+	case ATA_SECURITY_ACTION_DISABLE:
+		if (enabled) {
+			/* First unlock the drive if its locked */
+			if (ident_buf->security_status & ATA_SECURITY_LOCKED)
+				error = atasecurity_unlock(device, ccb, retry_count, timeout, &pwd, quiet);
+
+			if (0 == error) {
+				/* cam_getccb cleans up the header, caller has to zero the payload */
+				bzero(&(&ccb->ccb_h)[1], sizeof(struct ccb_ataio) - sizeof(struct ccb_hdr));
+				error = atasecurity_disable(device, ccb, retry_count, timeout, &pwd, quiet);
+			}
+		} else {
+			warnx("Can't disable security, security is already disabled");
+			error = 1;
+		}
+		break;
+
+	case ATA_SECURITY_ACTION_ERASE:
+		if (enabled) {
+			if (0 == erase_timeout)
+				erase_timeout = atasecurity_erase_timeout_msecs(ident_buf->erase_time);
+
+			error = atasecurity_erase(device, ccb, retry_count, timeout,
+			    erase_timeout, &pwd, confirm, quiet, ident_buf);
+		} else {
+			warnx("Can't secure erase, security is disabled");
+			error = 1;
+		}
+		break;
+
+	case ATA_SECURITY_ACTION_ERASE_ENHANCED:
+		if (enabled) {
+			if (ident_buf->security_status & ATA_SECURITY_ENH_SUPP) {
+				if (0 == erase_timeout)
+					erase_timeout = atasecurity_erase_timeout_msecs(ident_buf->enhanced_erase_time);
+
+				error = atasecurity_erase(device, ccb, retry_count, timeout,
+				    erase_timeout, &pwd, confirm, quiet, ident_buf);
+			} else {
+				warnx("Enhanced erase is not supported");
+				error = 1;
+			}
+		} else {
+			warnx("Can't secure erase (enhanced), security is disabled");
+			error = 1;
+		}
+		break;
+	}
+
+	cam_freeccb(ccb);
+	free(ident_buf);
+
+	return error;
+}
+#endif /* MINIMALISTIC */
+
 /*
  * Parse out a bus, or a bus, target and lun in the following
  * format:
@@ -4393,6 +4954,17 @@
 "        camcontrol idle       [dev_id][generic args][-t time]\n"
 "        camcontrol standby    [dev_id][generic args][-t time]\n"
 "        camcontrol sleep      [dev_id][generic args]\n"
+"        camcontrol security   [dev_id][generic args]\n"
+"                              [--security-freeze] [--security-quiet]\n"
+"                              [--security-confirm]\n"
+"                              [--security-user <user|master>]\n"
+"                              [--security-level <high|maximum>]\n"
+"                              [--security-set-password <pwd>]\n"
+"                              [--security-unlock <pwd>]\n"
+"                              [--security-disable <pwd>]\n"
+"                              [--security-erase <pwd>]\n"
+"                              [--security-erase-enhanced <pwd>]\n"
+"                              [--security-erase-timeout <timeout>]\n"
 #endif /* MINIMALISTIC */
 "        camcontrol help\n");
 	if (!verbose)
@@ -4423,6 +4995,7 @@
 "idle        send the ATA IDLE command to the named device\n"
 "standby     send the ATA STANDBY command to the named device\n"
 "sleep       send the ATA SLEEP command to the named device\n"
+"security    report / send ATA security commands to the named device\n"
 "help        this message\n"
 "Device Identifiers:\n"
 "bus:target        specify the bus and target, lun defaults to 0\n"
@@ -4492,7 +5065,17 @@
 "-w                don't send immediate format command\n"
 "-y                don't ask any questions\n"
 "idle/standby arguments:\n"
-"-t <arg>          number of seconds before respective state.\n");
+"-t <arg>          number of seconds before respective state.\n"
+"security arguments:\n"
+"--security-freeze                   freeze the security configuration of the specified device\n"
+"--security-user user|master         specifies which user to set: user or master\n"
+"--security-level high|maximum       specifies which security level to set: high or maximum\n"
+"--security-set-password <pwd>       password the device (enable security) using the given password for the selected user\n"
+"--security-unlock <pwd>             unlock the device using the given password for the selected user\n"
+"--security-disable <pwd>            disable security using the given password for the selected user\n"
+"--security-erase <pwd>              erase the device using the given password for the selected user\n"
+"--security-erase-enhanced <pwd>     enhanced erase the device using the given password for the selected user\n"
+"--security-erase-timeout <timeout>  overrides the timeout (in seconds) used for security erase operation\n");
 #endif /* MINIMALISTIC */
 }
 
@@ -4511,6 +5094,7 @@
 	char combinedopt[256];
 	int error = 0, optstart = 2;
 	int devopen = 1;
+	const struct option *longopts = NULL;
 #ifndef MINIMALISTIC
 	int bus, target, lun;
 #endif /* MINIMALISTIC */
@@ -4526,7 +5110,7 @@
 	/*
 	 * Get the base option.
 	 */
-	optreturn = getoption(argv[1], &cmdlist, &arglist, &subopt);
+	optreturn = getoption(argv[1], &cmdlist, &arglist, &subopt, &longopts);
 
 	if (optreturn == CC_OR_AMBIGUOUS) {
 		warnx("ambiguous option %s", argv[1]);
@@ -4642,7 +5226,7 @@
 	 * options, and ignoring options that possibly belong to
 	 * subfunctions.
 	 */
-	while ((c = getopt(argc, argv, combinedopt))!= -1){
+	while ((c = getopt_long(argc, argv, combinedopt, longopts, NULL)) != -1) {
 		switch(c) {
 			case 'C':
 				retry_count = strtol(optarg, NULL, 0);
@@ -4787,6 +5371,9 @@
 						 combinedopt, retry_count,
 						 timeout);
 			break;
+		case CAM_CMD_SECURITY:
+			error = atasecurity(cam_dev, retry_count, timeout, argc, argv, combinedopt, longopts);
+			break;
 #endif /* MINIMALISTIC */
 		case CAM_CMD_USAGE:
 			usage(1);
--- sbin/camcontrol/camcontrol.8.orig	2011-08-05 11:32:53.897577205 +0000
+++ sbin/camcontrol/camcontrol.8	2011-08-06 01:12:14.602310347 +0000
@@ -183,6 +183,21 @@
 .Op device id
 .Op generic args
 .Nm
+.Ic security
+.Op device id
+.Op generic args
+.Op Fl -security-quiet
+.Op Fl -security-confirm
+.Op Fl -security-freeze
+.Op Fl -security-user Ar user|master
+.Op Fl -security-level Ar high|maximum
+.Op Fl -security-set-password Ar pwd
+.Op Fl -security-unlock Ar pwd
+.Op Fl -security-disable Ar pwd
+.Op Fl -security-erase Ar pwd
+.Op Fl -security-enhanced-erase Ar pwd
+.Op Fl -security-erase-timeout Ar timeout
+.Nm
 .Ic help
 .Sh DESCRIPTION
 The
@@ -853,6 +868,122 @@
 .It Ic sleep
 Put ATA device into SLEEP state. Note that the only way get device out of
 this state may be reset.
+.It Ic security
+Update or report security settings, using an ATA identify command (0xec).
+By default,
+.Nm
+will print out the security support and associated settings of the device.
+The
+.Ic security
+command takes several arguments:
+.Bl -tag -width 0n
+.It Fl -security-quiet
+.Pp
+Be quiet, do not print any status messages.
+This option will not disable the questions, however.
+To disable questions, use the
+.Fl -security-confirm
+argument, below.
+.It Fl -security-confirm
+.Pp
+Confirm yes to dangerous options such as
+.Fl -security-erase
+without prompting for confirmation
+.It Fl -security-freeze
+.Pp
+Freeze the security configuration of the specified device.
+.Pp
+After command completion any other commands that update the device lock mode
+shall be command aborted. Frozen mode is disabled by power-off or hardware reset. 
+.It Fl -security-user Ar user|master
+.Pp
+Specifies which user to set / use for the running action command, valid values
+are user or master.
+.Pp
+This option must be used in conjunction with one of the security action commands.
+.Pp
+Defaults to
+.Em master
+.It Fl -security-level Ar high|maximum
+.Pp
+Specifies which security level to set when issuing a
+.Fl -security-set-password
+command. The security level determines device behavior when the master password
+is used to unlock the device. When the security level is set to high the device
+requires the unlock command and the master password to unlock.
+When the security level is set to maximum the device requires a secure erase
+with the master password to unlock.
+.Pp
+This option must be used in conjunction with one of the security action commands.
+.Pp
+Defaults to
+.Em high
+.It Fl -security-set-password Ar pwd
+.Pp
+Password the device (enable security) using the given password for the selected
+user.
+.Pp
+A master password may be set in a addition to the user password. The purpose of
+the master password is to allow an administrator to establish a password that
+is kept secret from the user, and which may be used to unlock the device if the
+user password is lost.
+.Pp
+.Em Note:
+Setting the master password does not enable device security.
+.Pp
+If the master password is set and the drive supports a Master Revision Code
+feature the Master Password Revision Code will be decremented.
+.It Fl -security-unlock Ar pwd
+.Pp
+Unlock the device using the given password for the selected user according to
+the devices configured security level.
+.It Fl -security-disable Ar pwd
+.Pp
+Disable device security using the given password for the selected user according
+to the devices configured security level.
+.It Fl -security-erase Ar pwd
+.Pp
+Erase the device using the given password for the selected user.
+.Pp
+.Em WARNING! WARNING! WARNING!
+.Pp
+Issuing a secure erase will
+.Em ERASE ALL
+user data on the device and may take several hours to complete.
+.Pp
+When this command is used against an SSD drive all its cells will be marked as
+empty, restoring it to factory default write performance. For SSD's this action
+usually takes just a few seconds.
+.It Fl -security-erase-enhanced Ar pwd
+.Pp
+Enhanced erase the device using the given password for the selected user.
+.Pp
+.Em WARNING! WARNING! WARNING!
+.Pp
+Issuing an enhanced secure erase will 
+.Em ERASE ALL
+user data on the device and may take several hours to complete.
+.Pp
+An enhanced erase writes predetermined data patterns to all user data areas,
+all previously written user data shall be overwritten, including sectors that
+are no longer in use due to reallocation.
+.It Fl -security-erase-timeout Ar timeout
+.Pp
+Overrides the default timeout, specified in seconds, used for both
+.Fl -security-erase
+and
+.Fl -security-erase-enhanced
+this is useful if your system has problems processing long timeouts correctly.
+.Pp
+Usually the timeout is calculated from the information stored on the drive if
+present, otherwise it defaults to 2 hours.
+.Pp
+.El
+If the password specified for any action commands doesn't match the configured
+password for the specified user the command will fail.
+.Pp
+The password in all cases is limited to 32 characters, longer passwords will
+fail.
 .It Ic help
 Print out verbose usage information.
 .El
@@ -971,6 +1102,33 @@
 Negotiate a sync rate of 20MHz and an offset of 15 with da3.
 Then send a
 Test Unit Ready command to make the settings take effect.
+.Pp
+.Bd -literal -offset indent
+camcontrol security ada0
+.Ed
+.Pp
+Report security support and settings for ada0
+.Pp
+.Bd -literal -offset indent
+camcontrol security ada0 --security-user user --security-set-password MyPass 
+.Ed
+.Pp
+Enable security on device ada0 with the password MyPass
+.Pp
+.Bd -literal -offset indent
+camcontrol security ada0 --security-user user --security-erase MyPass
+.Ed
+.Pp
+Secure erase ada0
+.Pp
+.Em WARNING! WARNING! WARNING
+.Pp
+This will
+.Em ERASE ALL
+data from the device, so backup your data before using!
+.Pp
+This command can be used used against an SSD drive to restoring it to
+factory default write performance
 .Sh SEE ALSO
 .Xr cam 3 ,
 .Xr cam_cdbparse 3 ,


>Release-Note:
>Audit-Trail:
>Unformatted:


More information about the freebsd-bugs mailing list