Skip to content

Commit

Permalink
Add format support to nvme WipeDisk (#142)
Browse files Browse the repository at this point in the history
### What does this PR do

Adds support for wiping nvme disks using nvme format for those devices
that do not support sanitize.
  • Loading branch information
mmlb committed May 17, 2024
1 parent 4fbf55d commit 514c594
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 18 deletions.
2 changes: 1 addition & 1 deletion utils/fake_executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (e *FakeExecute) Exec(_ context.Context) (*Result, error) {
}

e.Stdout = b
case "sanitize":
case "format", "sanitize":
dev := e.Args[len(e.Args)-1]
f, err := os.OpenFile(dev, os.O_WRONLY, 0)
if err != nil {
Expand Down
52 changes: 52 additions & 0 deletions utils/nvme.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const EnvNvmeUtility = "IRONLIB_UTIL_NVME"
var (
errSanicapNODMMASReserved = errors.New("sanicap nodmmas reserved bits set, not sure what to do with them")
errSanitizeInvalidAction = errors.New("invalid sanitize action")
errFormatInvalidSetting = errors.New("invalid format setting")
)

type Nvme struct {
Expand Down Expand Up @@ -278,6 +279,16 @@ const (
CryptoErase
)

//go:generate stringer -type SecureEraseSetting
type SecureEraseSetting uint8

const (
None SecureEraseSetting = iota
UserDataErase
CryptographicErase
Reserved
)

// WipeDisk implements DiskWiper by running nvme sanitize
func (n *Nvme) WipeDisk(ctx context.Context, logger *logrus.Logger, device string) error {
caps, err := n.DriveCapabilities(ctx, device)
Expand All @@ -290,12 +301,15 @@ func (n *Nvme) WipeDisk(ctx context.Context, logger *logrus.Logger, device strin
func (n *Nvme) wipe(ctx context.Context, logger *logrus.Logger, device string, caps []*common.Capability) error {
var ber bool
var cer bool
var cese bool
for _, cap := range caps {
switch cap.Name {
case "ber":
ber = cap.Enabled
case "cer":
cer = cap.Enabled
case "cese":
cese = cap.Enabled
}
}

Expand All @@ -317,6 +331,23 @@ func (n *Nvme) wipe(ctx context.Context, logger *logrus.Logger, device string, c
}
l.WithError(err).Info("failed")
}
if cese {
l := logger.WithField("method", "format").WithField("setting", CryptographicErase)
l.Info("trying wipe")
err := n.Format(ctx, device, CryptographicErase)
if err == nil {
return nil
}
l.WithError(err).Info("failed")
}

l := logger.WithField("method", "format").WithField("setting", UserDataErase)
l.Info("trying wipe")
err := n.Format(ctx, device, UserDataErase)
if err == nil {
return nil
}
l.WithError(err).Info("failed")
return ErrIneffectiveWipe
}

Expand Down Expand Up @@ -368,6 +399,27 @@ func (n *Nvme) Sanitize(ctx context.Context, device string, sanact SanitizeActio
return verify()
}

func (n *Nvme) Format(ctx context.Context, device string, ses SecureEraseSetting) error {
switch ses { // nolint:exhaustive
case UserDataErase, CryptographicErase:
default:
return fmt.Errorf("%w: %v", errFormatInvalidSetting, ses)
}

verify, err := ApplyWatermarks(device)
if err != nil {
return err
}

n.Executor.SetArgs("format", "--ses="+strconv.Itoa(int(ses)), device)
_, err = n.Executor.Exec(ctx)
if err != nil {
return err
}

return verify()
}

// NewFakeNvme returns a mock nvme collector that returns mock data for use in tests.
func NewFakeNvme() *Nvme {
return &Nvme{
Expand Down
55 changes: 38 additions & 17 deletions utils/nvme_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,44 +194,65 @@ func Test_NvmeSanitize(t *testing.T) {
}
}

func Test_NvmeFormat(t *testing.T) {
for action := range Reserved {
t.Run(action.String(), func(t *testing.T) {
n := NewFakeNvme()
dev := fakeNVMEDevice(t)
err := n.Format(context.Background(), dev, action)

switch action { // nolint:exhaustive
case UserDataErase, CryptographicErase:
require.NoError(t, err)
e, ok := n.Executor.(*FakeExecute)
require.True(t, ok)
require.Equal(t, []string{"format", "--ses=" + strconv.Itoa(int(action)), dev}, e.Args)
default:
require.Error(t, err)
require.ErrorIs(t, err, errFormatInvalidSetting)
}
})
}
}

func Test_NvmeWipe(t *testing.T) {
tests := []struct {
caps map[string]bool
args []string
}{
{caps: map[string]bool{"ber": false, "cer": false}},
{caps: map[string]bool{"ber": false, "cer": true}, args: []string{"sanitize", "--sanact=4"}},
{caps: map[string]bool{"ber": true, "cer": false}, args: []string{"sanitize", "--sanact=2"}},
{caps: map[string]bool{"ber": true, "cer": true}, args: []string{"sanitize", "--sanact=4"}},
{caps: map[string]bool{"ber": false, "cer": false, "cese": false}, args: []string{"format", "--ses=1"}},
{caps: map[string]bool{"ber": false, "cer": false, "cese": true}, args: []string{"format", "--ses=2"}},
{caps: map[string]bool{"ber": false, "cer": true, "cese": false}, args: []string{"sanitize", "--sanact=4"}},
{caps: map[string]bool{"ber": false, "cer": true, "cese": true}, args: []string{"sanitize", "--sanact=4"}},
{caps: map[string]bool{"ber": true, "cer": false, "cese": false}, args: []string{"sanitize", "--sanact=2"}},
{caps: map[string]bool{"ber": true, "cer": false, "cese": true}, args: []string{"sanitize", "--sanact=2"}},
{caps: map[string]bool{"ber": true, "cer": true, "cese": false}, args: []string{"sanitize", "--sanact=4"}},
{caps: map[string]bool{"ber": true, "cer": true, "cese": true}, args: []string{"sanitize", "--sanact=4"}},
}
for _, test := range tests {
name := fmt.Sprintf("ber=%v,cer=%v", test.caps["ber"], test.caps["cer"])
name := fmt.Sprintf("ber=%v,cer=%v,cese=%v", test.caps["ber"], test.caps["cer"], test.caps["cese"])
t.Run(name, func(t *testing.T) {
caps := []*common.Capability{
{Name: "ber", Enabled: test.caps["ber"]},
{Name: "cer", Enabled: test.caps["cer"]},
{Name: "cese", Enabled: test.caps["cese"]},
}
n := NewFakeNvme()
dev := fakeNVMEDevice(t)
logger, hook := tlogrus.NewNullLogger()
defer hook.Reset()

err := n.wipe(context.Background(), logger, dev, caps)

if test.args == nil {
require.Error(t, err)
require.ErrorIs(t, err, ErrIneffectiveWipe)
return
}

require.NoError(t, err)

// FakeExecute is a bad mocker since it doesn't record all calls and sanitize-log calls aren't that interesting
// TODO: Setup better mocks
//
// e, ok := n.Executor.(*FakeExecute)
// require.True(t, ok)
// test.args = append(test.args, dev)
// require.Equal(t, test.args, e.Args)
if test.args[0] == "format" {
e, ok := n.Executor.(*FakeExecute)
require.True(t, ok)
test.args = append(test.args, dev)
require.Equal(t, test.args, e.Args)
}
})
}
}
26 changes: 26 additions & 0 deletions utils/secureerasesetting_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 514c594

Please sign in to comment.