Skip to content

Secure File Removal Improvements#32260

Merged
jentfoo merged 3 commits intomasterfrom
jent/secure_removal_improvements
Sep 22, 2023
Merged

Secure File Removal Improvements#32260
jentfoo merged 3 commits intomasterfrom
jent/secure_removal_improvements

Conversation

@jentfoo
Copy link
Copy Markdown
Contributor

@jentfoo jentfoo commented Sep 20, 2023

This PR improves the reliability of secure file removal (particularly under conditions of unexpected errors), and leverages this functionality when removing certificates on disk. This will have little value for SSD's but can be an effective measure when magnetic hard drives are used.

This PR fixes https://github.com/gravitational/teleport-private/issues/670

Copy link
Copy Markdown
Collaborator

@zmb3 zmb3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't tried to run this yet, just did a pass of the code.

Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go Outdated
Copy link
Copy Markdown
Collaborator

@zmb3 zmb3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks better, one last thought.

Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go Outdated
Copy link
Copy Markdown
Contributor

@espadolini espadolini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we unlink the file first, and then overwrite its contents? As it is, anything opening the file while it still exists on disk but is in the process of being overwritten will just get some random garbage.

Comment thread lib/utils/fs.go Outdated
@jentfoo
Copy link
Copy Markdown
Contributor Author

jentfoo commented Sep 21, 2023

Can we unlink the file first, and then overwrite its contents? As it is, anything opening the file while it still exists on disk but is in the process of being overwritten will just get some random garbage.

We can, but this would be an OS conditional logic. I don't think in Windows you can write to files which have been removed from disk. It would be only Linux and MacOS which would allow us to open the handle, remove, and then overwrite.

Your recommendation is superior when it can be supported, but since we would have to support both methods is it worth doubling the code?

@AntonAM
Copy link
Copy Markdown
Contributor

AntonAM commented Sep 21, 2023

@jentfoo I don't think we have to double the code, there are already fs_unix.go and fs_windows.go, which can be used to provide platform-specific implementations. For example, you could add something like unlinkFile function in those, that we call before overwriting. And on mac/linux it would unlink the file, but on windows it would be no-op. Or something like that.

@jentfoo
Copy link
Copy Markdown
Contributor Author

jentfoo commented Sep 21, 2023

@AntonAM Perhaps you see something I don't. I don't believe you can simply no-op that single action unless you have other actions which are no-op in the unix case. There is a need for action reordering with regards to the unlink / remove.

Unix:

  1. Open
  2. Unlink
  3. Overwrite

Windows:

  1. Overwrite
  2. Remove

If we try to combine these we have a total steps of:

  1. Open
  2. Unlink (optional)
  3. Overwrite
  4. Remove (if not already unlinked)

While we could build the combined operation above, and that would technically be less lines. But I feel like it's more confusing than a single conditional which chooses the flow that makes sense for the OS.

@espadolini
Copy link
Copy Markdown
Contributor

Maybe on windows we can rename the file before overwriting it? Especially around key management, I'm wholly convinced that we have code that expects that if a file exists then its contents must be well-formed.

@jentfoo
Copy link
Copy Markdown
Contributor Author

jentfoo commented Sep 21, 2023

I would be concerned about renaming, that would introduce a new place where the chain of operations may fail and result in a sensitive file being retained on disk. Instead I would lean towards identify and improve any logic that could be impacted by this small overwrite window.

Copy link
Copy Markdown
Contributor

@codingllama codingllama left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for yet another security improvement, Jent!

Comment thread lib/utils/fs.go
Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go
Comment thread lib/utils/fs.go
Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go
Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go Outdated
return &os.PathError{Op: "RemoveAllSecure", Path: path, Err: syscall.EINVAL} // error type matches os.RemoveAll
}

if info, err := os.Lstat(path); err != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI in case you want to try it, but there may be an elegant traversal solution using filepath.WalkDir. No need to change anything though.

https://pkg.go.dev/path/filepath#WalkDir

@AntonAM
Copy link
Copy Markdown
Contributor

AntonAM commented Sep 21, 2023

@jentfoo no, just wanted to highlight this option, I don't insist on the change :)

@jentfoo
Copy link
Copy Markdown
Contributor Author

jentfoo commented Sep 21, 2023

@AntonAM and @espadolini I updated it with the OS conditional logic, it ended up being cleaner than I had imagined

Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go Outdated
Comment on lines 301 to 338
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: drop separate branches. Builds on the previous suggestions by assuming f can't be nil in this block.

Suggested change
if runtime.GOOS == "windows" {
// windows can't unlink the file before overwriting
if f != nil {
for i := 0; i < 3; i++ {
_ = overwriteFile(f)
}
}
}()
return trace.ConvertSystemError(os.Remove(filePath))
} else {
removeErr := os.Remove(filePath)
if f != nil {
for i := 0; i < 3; i++ {
_ = overwriteFile(f)
}
}
return trace.ConvertSystemError(removeErr)
}
}
isWindows := runtime.GOOS == "windows"
var removeErr error
if !isWindows {
// Unlink first on unix systems.
removeErr = os.Remove(filePath)
}
for i := 0; i < 3; i++ {
_ = overwriteFile(f)
}
if isWindows {
removeErr = os.Remove(filePath)
}
return trace.ConvertSystemError(removeErr)
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mixing the conditional multiple times reduces readability to me, but I don't feel strongly about it

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both look fine to me.

Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs_test.go Outdated
@codingllama
Copy link
Copy Markdown
Contributor

Looks good, mostly stylistic comments left.

Comment thread lib/utils/fs.go Outdated
Copy link
Copy Markdown
Collaborator

@zmb3 zmb3 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved, but I do think we need to check the sync error (see comment)

Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go Outdated
Comment thread lib/utils/fs.go Outdated
Comment on lines 301 to 338
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both look fine to me.

Comment thread lib/utils/fs.go Outdated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be ignoring an error here?

Writes are buffered in many cases, meaning the error that we are checking (from CopyN) can be nil and the real error would surface here, but we're ignoring it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated is to that any error (from the copy or sync) will break the overwrite loop, but otherwise the errors are still ignored.

Comment thread lib/utils/fs_test.go Outdated
Comment thread lib/utils/fs_test.go Outdated
As extra caution, even if an error occurs during the overwrite process, we still want to attempt a removal of sensitive files.
This commit ensures that deleted keyfiles have been overwritten.  This has little value on SSD's but can improve the security when the disk is magnetic.
…ible

This adds an OS conditional so that if possible the file will be removed and then overwritten using the previous file handle.
This will reduces the chance that the file will be witnessed with unexpected contents.
@jentfoo jentfoo force-pushed the jent/secure_removal_improvements branch from 70cee2c to d1b5d21 Compare September 22, 2023 21:30
@jentfoo jentfoo added this pull request to the merge queue Sep 22, 2023
Merged via the queue into master with commit ce4ea1c Sep 22, 2023
@jentfoo jentfoo deleted the jent/secure_removal_improvements branch September 22, 2023 22:09
@public-teleport-github-review-bot
Copy link
Copy Markdown

@jentfoo See the table below for backport results.

Branch Result
branch/v12 Failed
branch/v13 Failed
branch/v14 Create PR

jentfoo added a commit that referenced this pull request Sep 22, 2023
* utils.RemoveSecure: Still attempt a removal after error in overwrite

As extra caution, even if an error occurs during the overwrite process, we still want to attempt a removal of sensitive files.

* keystore.go: More secure removal of keyfiles

This commit ensures that deleted keyfiles have been overwritten.  This has little value on SSD's but can improve the security when the disk is magnetic.

* Apply PR feedback, notably better testing and early unlinking if possible

This adds an OS conditional so that if possible the file will be removed and then overwritten using the previous file handle.
This will reduces the chance that the file will be witnessed with unexpected contents.
jentfoo added a commit that referenced this pull request Sep 22, 2023
* utils.RemoveSecure: Still attempt a removal after error in overwrite

As extra caution, even if an error occurs during the overwrite process, we still want to attempt a removal of sensitive files.

* keystore.go: More secure removal of keyfiles

This commit ensures that deleted keyfiles have been overwritten.  This has little value on SSD's but can improve the security when the disk is magnetic.

* Apply PR feedback, notably better testing and early unlinking if possible

This adds an OS conditional so that if possible the file will be removed and then overwritten using the previous file handle.
This will reduces the chance that the file will be witnessed with unexpected contents.
github-merge-queue Bot pushed a commit that referenced this pull request Sep 25, 2023
* utils.RemoveSecure: Still attempt a removal after error in overwrite

As extra caution, even if an error occurs during the overwrite process, we still want to attempt a removal of sensitive files.

* keystore.go: More secure removal of keyfiles

This commit ensures that deleted keyfiles have been overwritten.  This has little value on SSD's but can improve the security when the disk is magnetic.

* Apply PR feedback, notably better testing and early unlinking if possible

This adds an OS conditional so that if possible the file will be removed and then overwritten using the previous file handle.
This will reduces the chance that the file will be witnessed with unexpected contents.
github-merge-queue Bot pushed a commit that referenced this pull request Sep 25, 2023
* utils.RemoveSecure: Still attempt a removal after error in overwrite

As extra caution, even if an error occurs during the overwrite process, we still want to attempt a removal of sensitive files.

* keystore.go: More secure removal of keyfiles

This commit ensures that deleted keyfiles have been overwritten.  This has little value on SSD's but can improve the security when the disk is magnetic.

* Apply PR feedback, notably better testing and early unlinking if possible

This adds an OS conditional so that if possible the file will be removed and then overwritten using the previous file handle.
This will reduces the chance that the file will be witnessed with unexpected contents.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants