safeopen.go 12 KB

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