fix(auth): harden Windows storage bridge

This commit is contained in:
2026-08-16 22:16:13 +02:00
parent c9b02fc57e
commit 7d9ca13f1d
12 changed files with 473 additions and 29 deletions
+37 -5
View File
@@ -8,6 +8,7 @@ import (
"io"
"os"
"path/filepath"
"sort"
"strings"
"unicode/utf8"
)
@@ -81,8 +82,8 @@ func ReadCanonicalPrivateRegular(path string, maximum int64) ([]byte, error) {
// PrivateDirectoryEntry is a bounded, untrusted directory listing item. Callers must still
// validate each filename and record before using it.
type PrivateDirectoryEntry struct {
Name string
ModifiedUnixMs int64
Name string `json:"name"`
ModifiedUnixMs int64 `json:"modifiedUnixMs"`
}
// ListCanonicalPrivateDirectory lists regular, non-symlinked direct children from an owner-only
@@ -94,8 +95,16 @@ func ListCanonicalPrivateDirectory(path string, maximumEntries int) ([]PrivateDi
if err := ValidatePrivateDirectory(path); err != nil {
return nil, ErrUnsafeFile
}
entries, err := os.ReadDir(path)
if err != nil || len(entries) > maximumEntries {
directory, err := os.Open(path)
if err != nil {
return nil, ErrUnsafeFile
}
defer directory.Close()
entries, err := directory.ReadDir(maximumEntries + 1)
if err != nil && !errors.Is(err, io.EOF) {
return nil, ErrUnsafeFile
}
if len(entries) > maximumEntries {
return nil, ErrUnsafeFile
}
result := make([]PrivateDirectoryEntry, 0, len(entries))
@@ -109,6 +118,7 @@ func ListCanonicalPrivateDirectory(path string, maximumEntries int) ([]PrivateDi
}
result = append(result, PrivateDirectoryEntry{Name: entry.Name(), ModifiedUnixMs: info.ModTime().UnixMilli()})
}
sort.Slice(result, func(left, right int) bool { return result[left].Name < result[right].Name })
if err := ValidatePrivateDirectory(path); err != nil {
return nil, ErrUnsafeFile
}
@@ -116,6 +126,17 @@ func ListCanonicalPrivateDirectory(path string, maximumEntries int) ([]PrivateDi
}
func WriteCanonicalNewFile(path string, contents []byte, mode os.FileMode) error {
return writeCanonicalNewFile(path, contents, mode, false)
}
// WriteCanonicalNewPrivateFile is the authentication-storage variant of exclusive file
// creation. It requires the final parent directory to already have platform-specific owner-only
// protection and preserves that check while the platform primitive opens the parent.
func WriteCanonicalNewPrivateFile(path string, contents []byte, mode os.FileMode) error {
return writeCanonicalNewFile(path, contents, mode, true)
}
func writeCanonicalNewFile(path string, contents []byte, mode os.FileMode, requirePrivateParent bool) error {
if err := ValidateCanonicalPath(path); err != nil {
return err
}
@@ -123,6 +144,9 @@ func WriteCanonicalNewFile(path string, contents []byte, mode os.FileMode) error
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
@@ -131,7 +155,15 @@ func WriteCanonicalNewFile(path string, contents []byte, mode os.FileMode) error
} else if !errors.Is(err, os.ErrNotExist) {
return ErrUnsafeFile
}
file, err := createCanonicalNewPrivateFile(path, mode)
var (
file *os.File
err error
)
if requirePrivateParent {
file, err = createCanonicalNewPrivateParentFile(path, mode)
} else {
file, err = createCanonicalNewPrivateFile(path, mode)
}
if err != nil {
return ErrUnsafeFile
}