fix: complete P2 host workspace contract
This commit is contained in:
@@ -56,9 +56,19 @@ func ReadCanonicalRegular(path string, maximum int64) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
// Checking the retained descriptor alone misses a pathname replacement while the
|
||||
// read is in progress. The parent descriptor and final name must still resolve to
|
||||
// the exact open file after reading.
|
||||
if err := unix.Fstat(int(file.Fd()), &after); err != nil || after.Nlink > 1 || after.Mode != before.Mode || after.Ino != before.Ino || after.Dev != before.Dev || after.Size != before.Size {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
var named unix.Stat_t
|
||||
if err := unix.Fstatat(directory, components[len(components)-1], &named, unix.AT_SYMLINK_NOFOLLOW); err != nil || named.Nlink > 1 || named.Mode != before.Mode || named.Ino != before.Ino || named.Dev != before.Dev {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
if !recheckUnixParents(components, directories) {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
return contents, nil
|
||||
}
|
||||
|
||||
@@ -89,6 +99,10 @@ func writeCanonicalExclusive(path string, contents []byte, mode fs.FileMode) err
|
||||
unix.Close(dir)
|
||||
dir = next
|
||||
}
|
||||
var parentBefore unix.Stat_t
|
||||
if err := unix.Fstat(dir, &parentBefore); err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
fd, err := unix.Openat(dir, components[len(components)-1], unix.O_WRONLY|unix.O_CREAT|unix.O_EXCL|unix.O_CLOEXEC|unix.O_NOFOLLOW, uint32(mode.Perm()))
|
||||
if err != nil {
|
||||
return ErrUnsafeFile
|
||||
@@ -108,6 +122,16 @@ func writeCanonicalExclusive(path string, contents []byte, mode fs.FileMode) err
|
||||
if err := f.Sync(); err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
var opened, named, parentAfter unix.Stat_t
|
||||
if err := unix.Fstat(fd, &opened); err != nil || opened.Nlink != 1 {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
if err := unix.Fstatat(dir, components[len(components)-1], &named, unix.AT_SYMLINK_NOFOLLOW); err != nil || named.Nlink != 1 || named.Ino != opened.Ino || named.Dev != opened.Dev {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
if err := unix.Fstat(dir, &parentAfter); err != nil || parentAfter.Ino != parentBefore.Ino || parentAfter.Dev != parentBefore.Dev || !recheckUnixParentPath(components[:len(components)-1], dir) {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -140,3 +164,46 @@ func validateCanonicalOutputPath(path string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func recheckUnixParents(components []string, retained []int) bool {
|
||||
if len(retained) != len(components) {
|
||||
return false
|
||||
}
|
||||
dir, err := unix.Open("/", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer unix.Close(dir)
|
||||
for i, component := range components[:len(components)-1] {
|
||||
next, e := unix.Openat(dir, component, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
|
||||
if e != nil {
|
||||
return false
|
||||
}
|
||||
var got, want unix.Stat_t
|
||||
if unix.Fstat(next, &got) != nil || unix.Fstat(retained[i+1], &want) != nil || got.Ino != want.Ino || got.Dev != want.Dev {
|
||||
unix.Close(next)
|
||||
return false
|
||||
}
|
||||
unix.Close(dir)
|
||||
dir = next
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func recheckUnixParentPath(components []string, retained int) bool {
|
||||
dir, err := unix.Open("/", unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer unix.Close(dir)
|
||||
for _, component := range components {
|
||||
next, e := unix.Openat(dir, component, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC|unix.O_NOFOLLOW, 0)
|
||||
if e != nil {
|
||||
return false
|
||||
}
|
||||
unix.Close(dir)
|
||||
dir = next
|
||||
}
|
||||
var got, want unix.Stat_t
|
||||
return unix.Fstat(dir, &got) == nil && unix.Fstat(retained, &want) == nil && got.Ino == want.Ino && got.Dev == want.Dev
|
||||
}
|
||||
|
||||
@@ -54,7 +54,29 @@ func ReadCanonicalRegular(path string, maximum int64) ([]byte, error) {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
defer file.Close()
|
||||
return readBoundedRegularFile(file, maximum)
|
||||
var before, after windows.ByHandleFileInformation
|
||||
if windows.GetFileInformationByHandle(handle, &before) != nil || before.NumberOfLinks > 1 {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
contents, err := readBoundedRegularFile(file, maximum)
|
||||
if err != nil {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
if windows.GetFileInformationByHandle(handle, &after) != nil || after.NumberOfLinks > 1 || !sameWindowsFile(before, after) {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
// Re-opened pathname identity is checked as well as the retained handle. This
|
||||
// catches replacement by an ordinary local actor where share/delete policy permits it.
|
||||
check, err := openWindowsComponent(current, false)
|
||||
if err != nil {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
defer windows.CloseHandle(check)
|
||||
var named windows.ByHandleFileInformation
|
||||
if windows.GetFileInformationByHandle(check, &named) != nil || named.NumberOfLinks > 1 || !sameWindowsFile(before, named) {
|
||||
return nil, ErrUnsafeFile
|
||||
}
|
||||
return contents, nil
|
||||
}
|
||||
|
||||
func openWindowsComponent(path string, directory bool) (windows.Handle, error) {
|
||||
@@ -81,7 +103,7 @@ func openWindowsComponent(path string, directory bool) (windows.Handle, error) {
|
||||
windows.CloseHandle(handle)
|
||||
return 0, err
|
||||
}
|
||||
if information.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 ||
|
||||
if information.NumberOfLinks > 1 || information.FileAttributes&windows.FILE_ATTRIBUTE_REPARSE_POINT != 0 ||
|
||||
(directory && information.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY == 0) ||
|
||||
(!directory && information.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY != 0) {
|
||||
windows.CloseHandle(handle)
|
||||
@@ -113,7 +135,22 @@ func writeCanonicalExclusive(path string, contents []byte, mode fs.FileMode) err
|
||||
if _, err := f.Write(contents); err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
return f.Sync()
|
||||
if err := f.Sync(); err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
var opened, named windows.ByHandleFileInformation
|
||||
if windows.GetFileInformationByHandle(h, &opened) != nil || opened.NumberOfLinks != 1 {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
check, err := openWindowsComponent(path, false)
|
||||
if err != nil {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
defer windows.CloseHandle(check)
|
||||
if windows.GetFileInformationByHandle(check, &named) != nil || named.NumberOfLinks != 1 || !sameWindowsFile(opened, named) {
|
||||
return ErrUnsafeFile
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCanonicalOutputPath(path string) error {
|
||||
@@ -140,3 +177,7 @@ func validateCanonicalOutputPath(path string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func sameWindowsFile(a, b windows.ByHandleFileInformation) bool {
|
||||
return a.VolumeSerialNumber == b.VolumeSerialNumber && a.FileIndexHigh == b.FileIndexHigh && a.FileIndexLow == b.FileIndexLow
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user