fix(auth): harden tht auth mutations

This commit is contained in:
2026-08-16 19:37:12 +02:00
parent 9646ae09a0
commit 856ac05edc
6 changed files with 245 additions and 31 deletions
+9
View File
@@ -118,6 +118,15 @@ func ReplaceCanonicalRegular(path string, contents []byte, mode os.FileMode) err
return replaceCanonicalRegular(path, contents)
}
// RemoveCanonicalPrivateRegular removes one existing private regular file without following a
// symlinked path component. It is intended only for rolling back a file this process published.
func RemoveCanonicalPrivateRegular(path string) error {
if err := ValidatePrivateRegular(path); err != nil {
return ErrUnsafeFile
}
return removeCanonicalPrivateRegular(path)
}
func randomTemporaryName() (string, error) {
bytes := make([]byte, 16)
if _, err := rand.Read(bytes); err != nil {
+26
View File
@@ -43,6 +43,24 @@ func replaceCanonicalRegular(path string, contents []byte) error {
return nil
}
func removeCanonicalPrivateRegular(path string) error {
directory, target, err := openCanonicalParentDirectory(path)
if err != nil {
return ErrUnsafeFile
}
defer unix.Close(directory)
if err := requirePrivateRegularAt(directory, target); err != nil {
return ErrUnsafeFile
}
if err := unix.Unlinkat(directory, target, 0); err != nil {
return ErrUnsafeFile
}
if err := unix.Fsync(directory); err != nil {
return ErrUnsafeFile
}
return nil
}
func openCanonicalParentDirectory(path string) (int, string, error) {
components := strings.Split(strings.TrimPrefix(path, string(os.PathSeparator)), string(os.PathSeparator))
if len(components) < 2 || components[0] == "" || components[len(components)-1] == "" {
@@ -72,6 +90,14 @@ func requireSingleRegularAt(directory int, name string) error {
return nil
}
func requirePrivateRegularAt(directory int, name string) error {
var stat unix.Stat_t
if err := unix.Fstatat(directory, name, &stat, unix.AT_SYMLINK_NOFOLLOW); err != nil || stat.Mode&unix.S_IFMT != unix.S_IFREG || stat.Nlink != 1 || stat.Mode&0o7777 != 0o600 {
return ErrUnsafeFile
}
return nil
}
func writePrivateTemporaryAt(directory int, contents []byte) (string, error) {
for attempt := 0; attempt < 16; attempt++ {
name, err := randomTemporaryName()
@@ -47,6 +47,21 @@ func replaceCanonicalRegular(path string, contents []byte) error {
return nil
}
func removeCanonicalPrivateRegular(path string) error {
parents, target, err := openCanonicalWindowsParent(path)
if err != nil {
return ErrUnsafeFile
}
defer parents.Close()
if err := ValidatePrivateRegular(path); err != nil {
return ErrUnsafeFile
}
if err := windows.DeleteFile(windows.StringToUTF16Ptr(filepath.Join(parents.directory, target))); err != nil {
return ErrUnsafeFile
}
return nil
}
func safeExistingRegular(path string) bool {
return ValidatePrivateRegular(path) == nil
}