1package safefile 2 3import ( 4 "errors" 5 "io" 6 "os" 7 "path/filepath" 8 "strings" 9 "syscall" 10 "unicode/utf16" 11 "unsafe" 12 13 "github.com/Microsoft/hcsshim/internal/longpath" 14 15 winio "github.com/Microsoft/go-winio" 16) 17 18//go:generate go run $GOROOT\src\syscall\mksyscall_windows.go -output zsyscall_windows.go safeopen.go 19 20//sys ntCreateFile(handle *uintptr, accessMask uint32, oa *objectAttributes, iosb *ioStatusBlock, allocationSize *uint64, fileAttributes uint32, shareAccess uint32, createDisposition uint32, createOptions uint32, eaBuffer *byte, eaLength uint32) (status uint32) = ntdll.NtCreateFile 21//sys ntSetInformationFile(handle uintptr, iosb *ioStatusBlock, information uintptr, length uint32, class uint32) (status uint32) = ntdll.NtSetInformationFile 22//sys rtlNtStatusToDosError(status uint32) (winerr error) = ntdll.RtlNtStatusToDosErrorNoTeb 23//sys localAlloc(flags uint32, size int) (ptr uintptr) = kernel32.LocalAlloc 24//sys localFree(ptr uintptr) = kernel32.LocalFree 25 26type ioStatusBlock struct { 27 Status, Information uintptr 28} 29 30type objectAttributes struct { 31 Length uintptr 32 RootDirectory uintptr 33 ObjectName uintptr 34 Attributes uintptr 35 SecurityDescriptor uintptr 36 SecurityQoS uintptr 37} 38 39type unicodeString struct { 40 Length uint16 41 MaximumLength uint16 42 Buffer uintptr 43} 44 45type fileLinkInformation struct { 46 ReplaceIfExists bool 47 RootDirectory uintptr 48 FileNameLength uint32 49 FileName [1]uint16 50} 51 52type fileDispositionInformationEx struct { 53 Flags uintptr 54} 55 56const ( 57 _FileLinkInformation = 11 58 _FileDispositionInformationEx = 64 59 60 FILE_READ_ATTRIBUTES = 0x0080 61 FILE_WRITE_ATTRIBUTES = 0x0100 62 DELETE = 0x10000 63 64 FILE_OPEN = 1 65 FILE_CREATE = 2 66 67 FILE_DIRECTORY_FILE = 0x00000001 68 FILE_SYNCHRONOUS_IO_NONALERT = 0x00000020 69 FILE_DELETE_ON_CLOSE = 0x00001000 70 FILE_OPEN_FOR_BACKUP_INTENT = 0x00004000 71 FILE_OPEN_REPARSE_POINT = 0x00200000 72 73 FILE_DISPOSITION_DELETE = 0x00000001 74 75 _OBJ_DONT_REPARSE = 0x1000 76 77 _STATUS_REPARSE_POINT_ENCOUNTERED = 0xC000050B 78) 79 80func OpenRoot(path string) (*os.File, error) { 81 longpath, err := longpath.LongAbs(path) 82 if err != nil { 83 return nil, err 84 } 85 return winio.OpenForBackup(longpath, syscall.GENERIC_READ, syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, syscall.OPEN_EXISTING) 86} 87 88func ntRelativePath(path string) ([]uint16, error) { 89 path = filepath.Clean(path) 90 if strings.Contains(path, ":") { 91 // Since alternate data streams must follow the file they 92 // are attached to, finding one here (out of order) is invalid. 93 return nil, errors.New("path contains invalid character `:`") 94 } 95 fspath := filepath.FromSlash(path) 96 if len(fspath) > 0 && fspath[0] == '\\' { 97 return nil, errors.New("expected relative path") 98 } 99 100 path16 := utf16.Encode(([]rune)(fspath)) 101 if len(path16) > 32767 { 102 return nil, syscall.ENAMETOOLONG 103 } 104 105 return path16, nil 106} 107 108// openRelativeInternal opens a relative path from the given root, failing if 109// any of the intermediate path components are reparse points. 110func openRelativeInternal(path string, root *os.File, accessMask uint32, shareFlags uint32, createDisposition uint32, flags uint32) (*os.File, error) { 111 var ( 112 h uintptr 113 iosb ioStatusBlock 114 oa objectAttributes 115 ) 116 117 path16, err := ntRelativePath(path) 118 if err != nil { 119 return nil, err 120 } 121 122 if root == nil || root.Fd() == 0 { 123 return nil, errors.New("missing root directory") 124 } 125 126 upathBuffer := localAlloc(0, int(unsafe.Sizeof(unicodeString{}))+len(path16)*2) 127 defer localFree(upathBuffer) 128 129 upath := (*unicodeString)(unsafe.Pointer(upathBuffer)) 130 upath.Length = uint16(len(path16) * 2) 131 upath.MaximumLength = upath.Length 132 upath.Buffer = upathBuffer + unsafe.Sizeof(*upath) 133 copy((*[32768]uint16)(unsafe.Pointer(upath.Buffer))[:], path16) 134 135 oa.Length = unsafe.Sizeof(oa) 136 oa.ObjectName = upathBuffer 137 oa.RootDirectory = uintptr(root.Fd()) 138 oa.Attributes = _OBJ_DONT_REPARSE 139 status := ntCreateFile( 140 &h, 141 accessMask|syscall.SYNCHRONIZE, 142 &oa, 143 &iosb, 144 nil, 145 0, 146 shareFlags, 147 createDisposition, 148 FILE_OPEN_FOR_BACKUP_INTENT|FILE_SYNCHRONOUS_IO_NONALERT|flags, 149 nil, 150 0, 151 ) 152 if status != 0 { 153 return nil, rtlNtStatusToDosError(status) 154 } 155 156 fullPath, err := longpath.LongAbs(filepath.Join(root.Name(), path)) 157 if err != nil { 158 syscall.Close(syscall.Handle(h)) 159 return nil, err 160 } 161 162 return os.NewFile(h, fullPath), nil 163} 164 165// OpenRelative opens a relative path from the given root, failing if 166// any of the intermediate path components are reparse points. 167func OpenRelative(path string, root *os.File, accessMask uint32, shareFlags uint32, createDisposition uint32, flags uint32) (*os.File, error) { 168 f, err := openRelativeInternal(path, root, accessMask, shareFlags, createDisposition, flags) 169 if err != nil { 170 err = &os.PathError{Op: "open", Path: filepath.Join(root.Name(), path), Err: err} 171 } 172 return f, err 173} 174 175// LinkRelative creates a hard link from oldname to newname (relative to oldroot 176// and newroot), failing if any of the intermediate path components are reparse 177// points. 178func LinkRelative(oldname string, oldroot *os.File, newname string, newroot *os.File) error { 179 // Open the old file. 180 oldf, err := openRelativeInternal( 181 oldname, 182 oldroot, 183 syscall.FILE_WRITE_ATTRIBUTES, 184 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 185 FILE_OPEN, 186 0, 187 ) 188 if err != nil { 189 return &os.LinkError{Op: "link", Old: filepath.Join(oldroot.Name(), oldname), New: filepath.Join(newroot.Name(), newname), Err: err} 190 } 191 defer oldf.Close() 192 193 // Open the parent of the new file. 194 var parent *os.File 195 parentPath := filepath.Dir(newname) 196 if parentPath != "." { 197 parent, err = openRelativeInternal( 198 parentPath, 199 newroot, 200 syscall.GENERIC_READ, 201 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 202 FILE_OPEN, 203 FILE_DIRECTORY_FILE) 204 if err != nil { 205 return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(newroot.Name(), newname), Err: err} 206 } 207 defer parent.Close() 208 209 fi, err := winio.GetFileBasicInfo(parent) 210 if err != nil { 211 return err 212 } 213 if (fi.FileAttributes & syscall.FILE_ATTRIBUTE_REPARSE_POINT) != 0 { 214 return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(newroot.Name(), newname), Err: rtlNtStatusToDosError(_STATUS_REPARSE_POINT_ENCOUNTERED)} 215 } 216 217 } else { 218 parent = newroot 219 } 220 221 // Issue an NT call to create the link. This will be safe because NT will 222 // not open any more directories to create the link, so it cannot walk any 223 // more reparse points. 224 newbase := filepath.Base(newname) 225 newbase16, err := ntRelativePath(newbase) 226 if err != nil { 227 return err 228 } 229 230 size := int(unsafe.Offsetof(fileLinkInformation{}.FileName)) + len(newbase16)*2 231 linkinfoBuffer := localAlloc(0, size) 232 defer localFree(linkinfoBuffer) 233 linkinfo := (*fileLinkInformation)(unsafe.Pointer(linkinfoBuffer)) 234 linkinfo.RootDirectory = parent.Fd() 235 linkinfo.FileNameLength = uint32(len(newbase16) * 2) 236 copy((*[32768]uint16)(unsafe.Pointer(&linkinfo.FileName[0]))[:], newbase16) 237 238 var iosb ioStatusBlock 239 status := ntSetInformationFile( 240 oldf.Fd(), 241 &iosb, 242 linkinfoBuffer, 243 uint32(size), 244 _FileLinkInformation, 245 ) 246 if status != 0 { 247 return &os.LinkError{Op: "link", Old: oldf.Name(), New: filepath.Join(parent.Name(), newbase), Err: rtlNtStatusToDosError(status)} 248 } 249 250 return nil 251} 252 253// deleteOnClose marks a file to be deleted when the handle is closed. 254func deleteOnClose(f *os.File) error { 255 disposition := fileDispositionInformationEx{Flags: FILE_DISPOSITION_DELETE} 256 var iosb ioStatusBlock 257 status := ntSetInformationFile( 258 f.Fd(), 259 &iosb, 260 uintptr(unsafe.Pointer(&disposition)), 261 uint32(unsafe.Sizeof(disposition)), 262 _FileDispositionInformationEx, 263 ) 264 if status != 0 { 265 return rtlNtStatusToDosError(status) 266 } 267 return nil 268} 269 270// clearReadOnly clears the readonly attribute on a file. 271func clearReadOnly(f *os.File) error { 272 bi, err := winio.GetFileBasicInfo(f) 273 if err != nil { 274 return err 275 } 276 if bi.FileAttributes&syscall.FILE_ATTRIBUTE_READONLY == 0 { 277 return nil 278 } 279 sbi := winio.FileBasicInfo{ 280 FileAttributes: bi.FileAttributes &^ syscall.FILE_ATTRIBUTE_READONLY, 281 } 282 if sbi.FileAttributes == 0 { 283 sbi.FileAttributes = syscall.FILE_ATTRIBUTE_NORMAL 284 } 285 return winio.SetFileBasicInfo(f, &sbi) 286} 287 288// RemoveRelative removes a file or directory relative to a root, failing if any 289// intermediate path components are reparse points. 290func RemoveRelative(path string, root *os.File) error { 291 f, err := openRelativeInternal( 292 path, 293 root, 294 FILE_READ_ATTRIBUTES|FILE_WRITE_ATTRIBUTES|DELETE, 295 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 296 FILE_OPEN, 297 FILE_OPEN_REPARSE_POINT) 298 if err == nil { 299 defer f.Close() 300 err = deleteOnClose(f) 301 if err == syscall.ERROR_ACCESS_DENIED { 302 // Maybe the file is marked readonly. Clear the bit and retry. 303 clearReadOnly(f) 304 err = deleteOnClose(f) 305 } 306 } 307 if err != nil { 308 return &os.PathError{Op: "remove", Path: filepath.Join(root.Name(), path), Err: err} 309 } 310 return nil 311} 312 313// RemoveAllRelative removes a directory tree relative to a root, failing if any 314// intermediate path components are reparse points. 315func RemoveAllRelative(path string, root *os.File) error { 316 fi, err := LstatRelative(path, root) 317 if err != nil { 318 if os.IsNotExist(err) { 319 return nil 320 } 321 return err 322 } 323 fileAttributes := fi.Sys().(*syscall.Win32FileAttributeData).FileAttributes 324 if fileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY == 0 || fileAttributes&syscall.FILE_ATTRIBUTE_REPARSE_POINT != 0 { 325 // If this is a reparse point, it can't have children. Simple remove will do. 326 err := RemoveRelative(path, root) 327 if err == nil || os.IsNotExist(err) { 328 return nil 329 } 330 return err 331 } 332 333 // It is necessary to use os.Open as Readdirnames does not work with 334 // OpenRelative. This is safe because the above lstatrelative fails 335 // if the target is outside the root, and we know this is not a 336 // symlink from the above FILE_ATTRIBUTE_REPARSE_POINT check. 337 fd, err := os.Open(filepath.Join(root.Name(), path)) 338 if err != nil { 339 if os.IsNotExist(err) { 340 // Race. It was deleted between the Lstat and Open. 341 // Return nil per RemoveAll's docs. 342 return nil 343 } 344 return err 345 } 346 347 // Remove contents & return first error. 348 for { 349 names, err1 := fd.Readdirnames(100) 350 for _, name := range names { 351 err1 := RemoveAllRelative(path+string(os.PathSeparator)+name, root) 352 if err == nil { 353 err = err1 354 } 355 } 356 if err1 == io.EOF { 357 break 358 } 359 // If Readdirnames returned an error, use it. 360 if err == nil { 361 err = err1 362 } 363 if len(names) == 0 { 364 break 365 } 366 } 367 fd.Close() 368 369 // Remove directory. 370 err1 := RemoveRelative(path, root) 371 if err1 == nil || os.IsNotExist(err1) { 372 return nil 373 } 374 if err == nil { 375 err = err1 376 } 377 return err 378} 379 380// MkdirRelative creates a directory relative to a root, failing if any 381// intermediate path components are reparse points. 382func MkdirRelative(path string, root *os.File) error { 383 f, err := openRelativeInternal( 384 path, 385 root, 386 0, 387 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 388 FILE_CREATE, 389 FILE_DIRECTORY_FILE) 390 if err == nil { 391 f.Close() 392 } else { 393 err = &os.PathError{Op: "mkdir", Path: filepath.Join(root.Name(), path), Err: err} 394 } 395 return err 396} 397 398// LstatRelative performs a stat operation on a file relative to a root, failing 399// if any intermediate path components are reparse points. 400func LstatRelative(path string, root *os.File) (os.FileInfo, error) { 401 f, err := openRelativeInternal( 402 path, 403 root, 404 FILE_READ_ATTRIBUTES, 405 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 406 FILE_OPEN, 407 FILE_OPEN_REPARSE_POINT) 408 if err != nil { 409 return nil, &os.PathError{Op: "stat", Path: filepath.Join(root.Name(), path), Err: err} 410 } 411 defer f.Close() 412 return f.Stat() 413} 414 415// EnsureNotReparsePointRelative validates that a given file (relative to a 416// root) and all intermediate path components are not a reparse points. 417func EnsureNotReparsePointRelative(path string, root *os.File) error { 418 // Perform an open with OBJ_DONT_REPARSE but without specifying FILE_OPEN_REPARSE_POINT. 419 f, err := OpenRelative( 420 path, 421 root, 422 0, 423 syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 424 FILE_OPEN, 425 0) 426 if err != nil { 427 return err 428 } 429 f.Close() 430 return nil 431} 432