fix(auth): harden restore verification transaction
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
//go:build windows
|
||||
|
||||
package backup
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/aritmolab/thothii/tools/tht/internal/safeio"
|
||||
)
|
||||
|
||||
func TestReplaceRestoreFileWindowsCreatesAndReplacesOwnerOnlyRegular(t *testing.T) {
|
||||
parent := filepath.Join(t.TempDir(), "private")
|
||||
if err := os.Mkdir(parent, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := safeio.ProtectPrivateDirectory(parent); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
target := filepath.Join(parent, "auth.yaml")
|
||||
if err := replaceRestoreFile(target, []byte("created"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := safeio.ValidatePrivateRegular(target); err != nil {
|
||||
t.Fatalf("created restore file is not owner-only: %v", err)
|
||||
}
|
||||
if err := replaceRestoreFile(target, []byte("replacement"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := safeio.ValidatePrivateRegular(target); err != nil {
|
||||
t.Fatalf("replacement restore file is not owner-only: %v", err)
|
||||
}
|
||||
contents, err := os.ReadFile(target)
|
||||
if err != nil || string(contents) != "replacement" {
|
||||
t.Fatalf("replacement contents = %q, error = %v", contents, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceRestoreFileWindowsPinsParentAndRejectsReparsePath(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
parent := filepath.Join(root, "private")
|
||||
if err := os.Mkdir(parent, 0o700); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := safeio.ProtectPrivateDirectory(parent); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
target := filepath.Join(parent, "users.yaml")
|
||||
if err := os.WriteFile(target, []byte("old"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := safeio.ProtectPrivateRegular(target); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
renamed := parent + "-renamed"
|
||||
var hookCalled bool
|
||||
restoreHook := safeio.SetPrivateDirectoryTestHookForTest(func(stage string) {
|
||||
if stage != "after-restore-parent-open" {
|
||||
return
|
||||
}
|
||||
hookCalled = true
|
||||
if err := os.Rename(parent, renamed); err == nil {
|
||||
t.Fatal("parent rename succeeded while restore retained its directory handles")
|
||||
}
|
||||
})
|
||||
defer restoreHook()
|
||||
if err := replaceRestoreFile(target, []byte("new"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !hookCalled {
|
||||
t.Fatal("parent-pinning race hook was not reached")
|
||||
}
|
||||
|
||||
link := filepath.Join(root, "reparse-parent")
|
||||
if err := os.Symlink(parent, link); err != nil {
|
||||
t.Skipf("Windows host does not permit symlink creation: %v", err)
|
||||
}
|
||||
if err := replaceRestoreFile(filepath.Join(link, "users.yaml"), []byte("unsafe"), 0o600); !errors.Is(err, safeio.ErrUnsafeFile) {
|
||||
t.Fatalf("reparse-parent restore error = %v, want ErrUnsafeFile", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user