fix(safeio): retain private file parent handles
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user