From 514c594733aebd75f2e87a3c65662b2089e3a69e Mon Sep 17 00:00:00 2001 From: Manuel Mendez <708570+mmlb@users.noreply.github.com> Date: Fri, 17 May 2024 04:20:33 -0400 Subject: [PATCH] Add format support to nvme WipeDisk (#142) ### What does this PR do Adds support for wiping nvme disks using nvme format for those devices that do not support sanitize. --- utils/fake_executor.go | 2 +- utils/nvme.go | 52 ++++++++++++++++++++++++++++ utils/nvme_test.go | 55 +++++++++++++++++++++--------- utils/secureerasesetting_string.go | 26 ++++++++++++++ 4 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 utils/secureerasesetting_string.go diff --git a/utils/fake_executor.go b/utils/fake_executor.go index f1e48f9a1..8ba31fff2 100644 --- a/utils/fake_executor.go +++ b/utils/fake_executor.go @@ -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 { diff --git a/utils/nvme.go b/utils/nvme.go index 401e3fd12..9a43258aa 100644 --- a/utils/nvme.go +++ b/utils/nvme.go @@ -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 { @@ -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) @@ -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 } } @@ -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 } @@ -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{ diff --git a/utils/nvme_test.go b/utils/nvme_test.go index 9051a3425..7143116c6 100644 --- a/utils/nvme_test.go +++ b/utils/nvme_test.go @@ -194,22 +194,48 @@ 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) @@ -217,21 +243,16 @@ func Test_NvmeWipe(t *testing.T) { 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) + } }) } } diff --git a/utils/secureerasesetting_string.go b/utils/secureerasesetting_string.go new file mode 100644 index 000000000..5615173fe --- /dev/null +++ b/utils/secureerasesetting_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -type SecureEraseSetting"; DO NOT EDIT. + +package utils + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[None-0] + _ = x[UserDataErase-1] + _ = x[CryptographicErase-2] + _ = x[Reserved-3] +} + +const _SecureEraseSetting_name = "NoneUserDataEraseCryptographicEraseReserved" + +var _SecureEraseSetting_index = [...]uint8{0, 4, 17, 35, 43} + +func (i SecureEraseSetting) String() string { + if i >= SecureEraseSetting(len(_SecureEraseSetting_index)-1) { + return "SecureEraseSetting(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _SecureEraseSetting_name[_SecureEraseSetting_index[i]:_SecureEraseSetting_index[i+1]] +}