From b39e6102bf880d047831cecea3d1be8ea5309263 Mon Sep 17 00:00:00 2001 From: Manuel Mendez Date: Wed, 26 Jun 2024 15:27:32 -0400 Subject: [PATCH 1/2] Add support for hdparm based drive wipe Similar to what we're doing for NVME devices, this command supports Sanitizing and Erasing and decides which function and parameters to use depending on the capabilities reported by the drive. Sanitize is preferred over erase. Cryptographic scramble is preferred of block erase within Sanitize. I reused the nvme sanitize/erase type since utils is all one big package anyway to avoid confusion. If we ever split each utility into its own package (as I want) then we'll just create a more specific type for hdparm. --- utils/hdparm.go | 134 +++++++++++++++++++++++++++++++++++++++++++ utils/hdparm_test.go | 58 +++++++++++++++++++ 2 files changed, 192 insertions(+) diff --git a/utils/hdparm.go b/utils/hdparm.go index 4e837d9e9..4d9a3af52 100644 --- a/utils/hdparm.go +++ b/utils/hdparm.go @@ -2,13 +2,17 @@ package utils import ( "bufio" + "bytes" "context" + "fmt" "os" "regexp" "strings" + "time" "github.com/bmc-toolbox/common" "github.com/metal-toolbox/ironlib/model" + "github.com/sirupsen/logrus" ) const ( @@ -207,6 +211,136 @@ func (h *Hdparm) DriveCapabilities(ctx context.Context, logicalName string) ([]* return capabilities, err } +// WipeDrive implements DriveWipe by calling Sanitize or Erase as appropriate. +// Sanitize(CryptoErase) is preferred over Sanitize(BlockErase) which is preferred over Erase(CryptographicErase). +func (h *Hdparm) WipeDrive(ctx context.Context, logger *logrus.Logger, drive *common.Drive) error { // nolint:gocyclo + var ( + esee bool + eseu bool + sanitize bool + bee bool + cse bool + ) + for _, cap := range drive.Capabilities { + switch { + case cap.Name == "esee": + esee = cap.Enabled + case cap.Name == "bee": + bee = cap.Enabled + case cap.Name == "cse": + cse = cap.Enabled + case cap.Name == "sf": + sanitize = cap.Enabled + case strings.HasPrefix(cap.Description, "erase time:"): + eseu = strings.Contains(cap.Description, "enhanced") + } + } + + l := logger.WithField("drive", drive.LogicalName) + if sanitize && cse { + // nolint:govet + l := l.WithField("method", "sanitize").WithField("action", "sanitize-crypto-scramble") + l.Info("wiping") + err := h.Sanitize(ctx, drive, CryptoErase) + if err == nil { + return nil + } + l.WithError(err).Info("failed") + } + if sanitize && bee { + // nolint:govet + l := l.WithField("method", "sanitize").WithField("action", "sanitize-block-erase") + l.Info("wiping") + err := h.Sanitize(ctx, drive, BlockErase) + if err == nil { + return nil + } + l.WithError(err).Info("failed") + } + if esee && eseu { + // nolint:govet + l := l.WithField("method", "security-erase-enhanced") + l.Info("wiping") + err := h.Erase(ctx, drive, CryptographicErase) + if err == nil { + return nil + } + l.WithError(err).Info("failed") + } + return ErrIneffectiveWipe +} + +// Sanitize wipes drive using `ATA Sanitize Device` via hdparm --sanitize +func (h *Hdparm) Sanitize(ctx context.Context, drive *common.Drive, sanact SanitizeAction) error { + var sanType string + switch sanact { // nolint:exhaustive + case BlockErase: + sanType = "block-erase" + case CryptoErase: + sanType = "crypto-scramble" + default: + return fmt.Errorf("%w: %v", errSanitizeInvalidAction, sanact) + } + + verify, err := ApplyWatermarks(drive) + if err != nil { + return err + } + + h.Executor.SetArgs("--yes-i-know-what-i-am-doing", "--sanitize-"+sanType, drive.LogicalName) + _, err = h.Executor.Exec(ctx) + if err != nil { + return err + } + + // now we loop until --sanitize-status reports that sanitization is complete + for { + h.Executor.SetArgs("--sanitize-status", drive.LogicalName) + result, err := h.Executor.Exec(ctx) + if err != nil { + return err + } + if h.sanitizeDone(result.Stdout) { + break + } + time.Sleep(100 * time.Millisecond) + } + + return verify() +} + +func (h *Hdparm) sanitizeDone(output []byte) bool { + return bytes.Contains(output, []byte("Sanitize Idle")) +} + +// Erase wipes drive using ATA Secure Erase via hdparm --security-erase-enhanced +func (h *Hdparm) Erase(ctx context.Context, drive *common.Drive, ses SecureEraseSetting) error { + switch ses { // nolint:exhaustive + case CryptographicErase: + default: + return fmt.Errorf("%w: %v", errFormatInvalidSetting, ses) + } + + h.Executor.SetArgs("--user-master", "u", "--security-set-pass", "p", drive.LogicalName) + _, err := h.Executor.Exec(ctx) + if err != nil { + return err + } + + verify, err := ApplyWatermarks(drive) + if err != nil { + return err + } + + h.Executor.SetArgs("--user-master", "u", "--security-erase-enhanced", "p", drive.LogicalName) + _, err = h.Executor.Exec(ctx) + if err != nil { + return err + } + + return verify() +} + // NewFakeHdparm returns a mock hdparm collector that returns mock data for use in tests. func NewFakeHdparm() *Hdparm { return &Hdparm{ diff --git a/utils/hdparm_test.go b/utils/hdparm_test.go index 10e9b0bd9..a3d70d1b9 100644 --- a/utils/hdparm_test.go +++ b/utils/hdparm_test.go @@ -293,3 +293,61 @@ var fixtureHdparmDeviceCapabilities = []*common.Capability{ Enabled: false, }, } + +func Test_HdparmSanitizeDone(t *testing.T) { + // note the order is not messed up, this is indeed what hdparm showed me + tests := []struct { + name string + done bool + in string + }{ + { + name: "5%", + in: ` +/dev/sdb: +Issuing SANITIZE_STATUS command +Sanitize status: + State: SD2 Sanitize operation In Process + Progress: 0xf5a (5%) +`, + }, + { + name: "1%", + in: ` +/dev/sdb: +Issuing SANITIZE_STATUS command +Sanitize status: + State: SD2 Sanitize operation In Process + Progress: 0x28f (1%) +`, + }, + { + name: "3%", + in: ` +/dev/sdb: +Issuing SANITIZE_STATUS command +Sanitize status: + State: SD2 Sanitize operation In Process + Progress: 0xa3c (3%) +`, + }, + { + name: "done", + done: true, + in: ` +/dev/sdb: +Issuing SANITIZE_STATUS command +Sanitize status: + State: SD0 Sanitize Idle + Last Sanitize Operation Completed Without Error +`, + }, + } + + var h Hdparm + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.done, h.sanitizeDone([]byte(test.in))) + }) + } +} From 42f06901ca3373a6afceba4c27b07ecf7dd83469 Mon Sep 17 00:00:00 2001 From: Manuel Mendez Date: Mon, 8 Jul 2024 07:21:56 -0400 Subject: [PATCH 2/2] Add hdparm to examples/diskwipe --- examples/diskwipe/main.go | 28 ++++++++++++++++++++-------- utils/hdparm.go | 8 ++++---- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/examples/diskwipe/main.go b/examples/diskwipe/main.go index afdb314e4..9bbf959fe 100644 --- a/examples/diskwipe/main.go +++ b/examples/diskwipe/main.go @@ -66,18 +66,30 @@ func main() { case "nvme": wiper = utils.NewNvmeCmd(*verbose) case "sata": - // Lets see if drive supports TRIM, if so we'll use blkdiscard + // Lets figure out the drive capabilities in an easier format + var sanitize bool + var esee bool + var trim bool for _, cap := range drive.Capabilities { - if strings.HasPrefix(cap.Description, "Data Set Management TRIM supported") { - if cap.Enabled { - wiper = utils.NewBlkdiscardCmd(*verbose) - } - break + switch { + case cap.Description == "encryption supports enhanced erase": + esee = cap.Enabled + case cap.Description == "SANITIZE feature": + sanitize = cap.Enabled + case strings.HasPrefix(cap.Description, "Data Set Management TRIM supported"): + trim = cap.Enabled } } - // drive does not support TRIM so we fall back to filling it up with zero - if wiper == nil { + switch { + case sanitize || esee: + // Drive supports Sanitize or Enhanced Erase, so we use hdparm + wiper = utils.NewHdparmCmd(*verbose) + case trim: + // Drive supports TRIM, so we use blkdiscard + wiper = utils.NewBlkdiscardCmd(*verbose) + default: + // Drive does not support any preferred wipe method so we fall back to filling it up with zero wiper = utils.NewFillZeroCmd(*verbose) // If the user supplied a non-default timeout then we'll honor it, otherwise we just go with a huge timeout. diff --git a/utils/hdparm.go b/utils/hdparm.go index 4d9a3af52..4a465bd27 100644 --- a/utils/hdparm.go +++ b/utils/hdparm.go @@ -223,13 +223,13 @@ func (h *Hdparm) WipeDrive(ctx context.Context, logger *logrus.Logger, drive *co ) for _, cap := range drive.Capabilities { switch { - case cap.Name == "esee": + case cap.Description == "encryption supports enhanced erase": esee = cap.Enabled - case cap.Name == "bee": + case cap.Description == "BLOCK ERASE EXT": bee = cap.Enabled - case cap.Name == "cse": + case cap.Description == "CRYPTO SCRAMBLE EXT": cse = cap.Enabled - case cap.Name == "sf": + case cap.Description == "SANITIZE feature": sanitize = cap.Enabled case strings.HasPrefix(cap.Description, "erase time:"): eseu = strings.Contains(cap.Description, "enhanced")