fix(safeio): retain private file parent handles

This commit is contained in:
2026-08-18 08:51:59 +02:00
parent 0b77d1e850
commit e7a7f4f066
11 changed files with 359 additions and 212 deletions
+9 -36
View File
@@ -279,7 +279,7 @@ func WriteCanonicalNewPrivateFile(path string, contents []byte, mode os.FileMode
// already private parent and returns a read/write handle for streamed contents. Callers must close
// the returned handle and remove the file if their stream fails.
func CreateCanonicalNewPrivateFile(path string) (*os.File, error) {
if err := validateCanonicalNewFile(path, true); err != nil {
if err := validateCanonicalNewFile(path); err != nil {
return nil, err
}
file, err := createCanonicalNewPrivateParentReadWriteFile(path, 0o600)
@@ -290,7 +290,7 @@ func CreateCanonicalNewPrivateFile(path string) (*os.File, error) {
}
func writeCanonicalNewFile(path string, contents []byte, mode os.FileMode, requirePrivateParent bool) error {
if err := validateCanonicalNewFile(path, requirePrivateParent); err != nil {
if err := validateCanonicalNewFile(path); err != nil {
return err
}
var (
@@ -307,40 +307,28 @@ func writeCanonicalNewFile(path string, contents []byte, mode os.FileMode, requi
}
if _, err := file.Write(contents); err != nil {
_ = file.Close()
_ = os.Remove(path)
_ = RemoveCanonicalPrivateRegular(path)
return ErrUnsafeFile
}
if err := file.Sync(); err != nil {
_ = file.Close()
_ = os.Remove(path)
_ = RemoveCanonicalPrivateRegular(path)
return ErrUnsafeFile
}
if err := file.Close(); err != nil {
_ = os.Remove(path)
_ = RemoveCanonicalPrivateRegular(path)
return ErrUnsafeFile
}
return nil
}
func validateCanonicalNewFile(path string, requirePrivateParent bool) error {
// validateCanonicalNewFile performs only lexical validation. Platform creators retain and
// inspect the actual parent handle before mutation; pathname existence or parent checks here
// would be stale by the time an exclusive create reaches the filesystem.
func validateCanonicalNewFile(path string) error {
if err := ValidateCanonicalPath(path); err != nil {
return err
}
parent := filepath.Dir(path)
if err := requireCanonicalDirectory(parent); err != nil {
return err
}
if requirePrivateParent && ValidatePrivateDirectory(parent) != nil {
return ErrUnsafeFile
}
if info, err := os.Lstat(path); err == nil {
if !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 || info.Mode()&os.ModeType != 0 {
return ErrUnsafeFile
}
return ErrUnsafeFile
} else if !errors.Is(err, os.ErrNotExist) {
return ErrUnsafeFile
}
return nil
}
@@ -406,18 +394,3 @@ func randomTemporaryName() (string, error) {
}
return ".tht-auth-" + hex.EncodeToString(bytes) + ".tmp", nil
}
func requireCanonicalDirectory(path string) error {
if err := ValidateCanonicalPath(path); err != nil {
return err
}
resolved, err := filepath.EvalSymlinks(path)
if err != nil || resolved != path {
return ErrUnsafeFile
}
info, err := os.Stat(path)
if err != nil || !info.IsDir() {
return ErrUnsafeFile
}
return nil
}