Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1033 lines
28 KiB

  1. //
  2. // SafeFile.cpp
  3. //
  4. // Functions to help prevent opening unsafe files.
  5. //
  6. // History:
  7. //
  8. // 2002-03-18 KenSh Created
  9. //
  10. // Copyright (c) 2002 Microsoft Corporation
  11. //
  12. #include "stdafx.h"
  13. #include "SafeFile.h"
  14. #include <strsafe.h>
  15. //
  16. // Hopefully most projects already define these; if not, ensure we still compile
  17. //
  18. #ifndef ASSERT
  19. #define ASSERT(x)
  20. #endif
  21. #ifndef ARRAYSIZE
  22. #define ARRAYSIZE(ar) (sizeof(ar)/sizeof((ar)[0]))
  23. #endif
  24. //
  25. // Eliminate an unnecessary function call on Unicode builds
  26. //
  27. #ifndef CHARNEXT
  28. #ifdef UNICODE
  29. #define CHARNEXT(psz) (psz+1)
  30. #else
  31. #define CHARNEXT CharNextA
  32. #endif
  33. #endif
  34. //
  35. // Local function declarations
  36. //
  37. static inline BOOL IsSlashOrBackslash(IN TCHAR ch);
  38. static inline BOOL IsSlashOrBackslash(IN TCHAR ch);
  39. static BOOL SkipLangNeutralPrefix(IN LPCTSTR pszString, IN LPCTSTR pszPrefix, OUT LPCTSTR* ppszResult);
  40. static BOOL MyPathFindNextComponent(IN LPCTSTR pszFileName, IN BOOL fAllowForwardSlash, OUT LPCTSTR* ppszResult);
  41. static BOOL SkipPathDrivePart(IN LPCTSTR pszFileName, OUT OPTIONAL int* pcchDrivePart, OUT OPTIONAL BOOL* pfUNC, OUT OPTIONAL BOOL* pfExtendedSyntax);
  42. static HRESULT CheckValidDriveType(IN LPCTSTR pszFileName, IN BOOL fAllowNetworkDrive, IN BOOL fAllowRemovableDrive);
  43. static BOOL WINAPI DoesPathContainDotDot(IN LPCTSTR pszFileName);
  44. static BOOL DoesPathContainStreamSyntax(IN LPCTSTR pszFileName);
  45. static HRESULT CheckReparsePointPermissions(IN DWORD dwReparseType);
  46. //============================================================================
  47. static inline HRESULT GetLastErrorAsHresult()
  48. {
  49. DWORD dwErr = GetLastError();
  50. return HRESULT_FROM_WIN32(dwErr);
  51. }
  52. // IsSlashOrBackslash [private]
  53. //
  54. // Helper function to simplify code that checks for path separators.
  55. // Most places where backslash is valid, forward slash is also valid.
  56. //
  57. static inline BOOL IsSlashOrBackslash(IN TCHAR ch)
  58. {
  59. return (ch == _T('\\') || ch == _T('/'));
  60. }
  61. // StrLenWithMax [private]
  62. //
  63. // Returns the equivalent of min(lstrlen(pszString), cchMax)
  64. // but avoids most of the lstrlen when cchMax is small.
  65. //
  66. static int StrLenWithMax(IN LPCTSTR pszString, IN int cchMax)
  67. {
  68. int cch = 0;
  69. while (*pszString && cch < cchMax)
  70. cch++;
  71. return cch;
  72. }
  73. // SkipLangNeutralPrefix [private]
  74. //
  75. // Sets the out param to the new string pointer after skipping the prefix,
  76. // if the string starts with the prefix (case-insensitive). Otherwise sets
  77. // the out param to the start of the input string.
  78. //
  79. // Returns TRUE if the prefix was found & skipped, otherwise FALSE.
  80. //
  81. static BOOL SkipLangNeutralPrefix(IN LPCTSTR pszString, IN LPCTSTR pszPrefix, OUT LPCTSTR* ppszResult)
  82. {
  83. int cchPrefix = lstrlen(pszPrefix);
  84. int cchString = StrLenWithMax(pszString, cchPrefix);
  85. BOOL fResult = FALSE;
  86. if (CSTR_EQUAL == CompareString(MAKELCID(LANG_ENGLISH, SORT_DEFAULT), NORM_IGNORECASE,
  87. pszString, cchString, pszPrefix, cchPrefix))
  88. {
  89. fResult = TRUE;
  90. pszString += cchPrefix;
  91. }
  92. *ppszResult = pszString;
  93. return fResult;
  94. }
  95. // MyPathFindNextComponent [private]
  96. //
  97. // Skips past the next component of the given path, including the slash or
  98. // backslash that follows it.
  99. //
  100. // Sets the out param to the beginning of the next path component, or to
  101. // the end of string if there is no next path component.
  102. //
  103. // Returns TRUE if a slash or backslash was found and skipped. Note that
  104. // the out param can be "" even if function returns TRUE.
  105. //
  106. static BOOL MyPathFindNextComponent
  107. (
  108. IN LPCTSTR pszFileName,
  109. IN BOOL fAllowForwardSlash,
  110. OUT LPCTSTR* ppszResult
  111. )
  112. {
  113. // This is a string-parsing helper function; params should never be NULL
  114. ASSERT(pszFileName != NULL);
  115. ASSERT(ppszResult != NULL);
  116. LPCTSTR pszStart = pszFileName;
  117. TCHAR chSlash2 = (fAllowForwardSlash ? _T('/') : _T('\\'));
  118. BOOL fResult = FALSE;
  119. for (;;)
  120. {
  121. TCHAR ch = *pszFileName;
  122. if (ch == _T('\0'))
  123. break; // didn't find a path separator; we'll return FALSE
  124. // Advance to next char, even if current char is path separator (\ or /)
  125. pszFileName = CHARNEXT(pszFileName);
  126. if (ch == _T('\\') || ch == chSlash2)
  127. {
  128. fResult = TRUE;
  129. break;
  130. }
  131. }
  132. *ppszResult = pszFileName;
  133. return fResult;
  134. }
  135. // SkipPathDrivePart [private]
  136. //
  137. // Parses the filename to determine the length of the base drive portion of
  138. // the filename, and to determine what syntax the name is in.
  139. //
  140. // This function does not actually examine the drive or file to ensure existence,
  141. // or to recognize that a drive letter like X:\ might be a network drive.
  142. //
  143. // Returns:
  144. // TRUE - if the input is a full path
  145. // FALSE - if input param is not a full path, or is bogus. The pcchDrivePart
  146. // out param is set to 0 in this case.
  147. //
  148. static BOOL SkipPathDrivePart
  149. (
  150. IN LPCTSTR pszFileName, // input path name (full or relative path)
  151. OUT OPTIONAL int* pcchDrivePart, // # of TCHARs used by drive part
  152. OUT OPTIONAL BOOL* pfUNC, // TRUE if path is UNC (not incl mapped drive)
  153. OUT OPTIONAL BOOL* pfExtendedSyntax // TRUE if path is \\?\ syntax
  154. )
  155. {
  156. BOOL fFullPath = FALSE;
  157. LPCTSTR pszOriginalFileName = pszFileName;
  158. int fUNC = FALSE;
  159. int fExtendedSyntax = FALSE;
  160. if (!pszFileName)
  161. goto done;
  162. // BLOCK
  163. {
  164. //
  165. // Skip \\?\ if present. (This part must use backslashes, not forward slashes)
  166. //
  167. #ifdef UNICODE
  168. if (SkipLangNeutralPrefix(pszFileName, _T("\\\\?\\"), &pszFileName))
  169. {
  170. fExtendedSyntax = TRUE;
  171. if (SkipLangNeutralPrefix(pszFileName, _T("UNC\\"), &pszFileName))
  172. {
  173. fUNC = TRUE; // Found "\\?\UNC\..."
  174. }
  175. else if (SkipLangNeutralPrefix(pszFileName, _T("Volume{"), &pszFileName))
  176. {
  177. // Found "\\?\Volume{1f3b3813-ddbf-11d5-ab2e-806d6172696f}\".
  178. // Skip the rest of the volume name.
  179. fFullPath = MyPathFindNextComponent(pszFileName, FALSE, &pszFileName);
  180. goto done;
  181. }
  182. // else continue normal parsing starting at updated pszFileName pointer
  183. }
  184. #endif // UNICODE
  185. //
  186. // Check for path of the form C:\
  187. //
  188. TCHAR chFirstUpper = (TCHAR)CharUpper((LPTSTR)(pszFileName[0]));
  189. if (chFirstUpper >= _T('A') && chFirstUpper <= _T('Z') &&
  190. pszFileName[1] == _T(':') && pszFileName[2] == _T('\\'))
  191. {
  192. pszFileName += 3;
  193. fFullPath = TRUE;
  194. goto done;
  195. }
  196. //
  197. // Check for UNC of the form \\server\share\
  198. //
  199. if (!fExtendedSyntax &&
  200. pszFileName[0] == _T('\\') &&
  201. pszFileName[1] == _T('\\'))
  202. {
  203. fUNC = TRUE;
  204. pszFileName += 2; // skip the "\\"
  205. }
  206. if (fUNC) // may be \\server\share\ or \\?\UNC\server\share\
  207. {
  208. // Skip past server and share names. Trailing backslash is NOT optional.
  209. if (!MyPathFindNextComponent(pszFileName, TRUE, &pszFileName) ||
  210. !MyPathFindNextComponent(pszFileName, TRUE, &pszFileName))
  211. {
  212. goto done; // incomplete UNC path -> return failure
  213. }
  214. fFullPath = TRUE;
  215. }
  216. }
  217. done:
  218. if (pcchDrivePart)
  219. *pcchDrivePart = fFullPath ? (int)(pszFileName - pszOriginalFileName) : 0;
  220. if (pfUNC)
  221. *pfUNC = fUNC;
  222. if (pfExtendedSyntax)
  223. *pfExtendedSyntax = fExtendedSyntax;
  224. return fFullPath;
  225. }
  226. // GetReparsePointType [public]
  227. //
  228. // Given the full path of a file or directory, determines what type of
  229. // reparse point the path represents.
  230. //
  231. // Returns S_OK if the type of reparse point could be determined, or
  232. // an appropriate error code if not.
  233. //
  234. // The out param is set to the reparse point type, or 0 if none.
  235. // The value for both volume mount points and junction points is
  236. // IO_REPARSE_TAG_MOUNT_POINT. (Use GetVolumeNameForVolumeMountPoint
  237. // to distinguish, if necessary.)
  238. //
  239. HRESULT WINAPI GetReparsePointType
  240. (
  241. IN LPCTSTR pszFileName, // full path to folder to check
  242. OUT DWORD* pdwReparsePointType // set to reparse point type, or 0 if none
  243. )
  244. {
  245. HRESULT hr = S_OK;
  246. DWORD dwReparseType = 0;
  247. ASSERT(pdwReparsePointType);
  248. // BLOCK
  249. {
  250. if (!pszFileName)
  251. {
  252. hr = E_INVALIDARG;
  253. goto done;
  254. }
  255. DWORD dwAttrib = GetFileAttributes(pszFileName);
  256. if (dwAttrib == INVALID_FILE_ATTRIBUTES)
  257. goto win32_error;
  258. if (dwAttrib & FILE_ATTRIBUTE_REPARSE_POINT)
  259. {
  260. WIN32_FIND_DATA Find;
  261. HANDLE hFind = FindFirstFile(pszFileName, &Find);
  262. if (hFind == INVALID_HANDLE_VALUE)
  263. goto win32_error;
  264. dwReparseType = Find.dwReserved0;
  265. FindClose(hFind);
  266. }
  267. goto done;
  268. }
  269. win32_error:
  270. hr = GetLastErrorAsHresult();
  271. done:
  272. *pdwReparsePointType = dwReparseType;
  273. ASSERT(hr != E_INVALIDARG);
  274. return hr;
  275. }
  276. // CheckReparsePointPermissions [private]
  277. //
  278. // Determines whether or not it's ok to trust the given reparse type.
  279. // Returns S_OK if it's safe, or an appropriate error message if not.
  280. //
  281. static HRESULT CheckReparsePointPermissions(IN DWORD dwReparseType)
  282. {
  283. HRESULT hr = S_OK;
  284. // REVIEW: Any reason to worry about these other types of reparse points?
  285. // IO_REPARSE_TAG_HSM, IO_REPARSE_TAG_SIS, IO_REPARSE_TAG_DFS, etc.
  286. if (dwReparseType == IO_REPARSE_TAG_MOUNT_POINT)
  287. {
  288. hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
  289. }
  290. return hr;
  291. }
  292. // CheckValidDriveType [private]
  293. //
  294. // Gets the volume name associated with the given file, and checks the
  295. // return value from GetDriveType() to see whether or not operations
  296. // are allowed on the file.
  297. //
  298. static HRESULT CheckValidDriveType
  299. (
  300. IN LPCTSTR pszFileName, // full path of a file whose drive we want to check
  301. IN BOOL fAllowNetworkDrive, // determines whether or not net drives are allowed
  302. IN BOOL fAllowRemovableDrive // determines whether or not removable drives are allowed
  303. )
  304. {
  305. HRESULT hr = E_INVALIDARG;
  306. LPTSTR pszVolumePath = NULL;
  307. // BLOCK
  308. {
  309. if (!pszFileName)
  310. {
  311. goto done; // hr is already E_INVALIDARG
  312. }
  313. int cchFileName = lstrlen(pszFileName);
  314. pszVolumePath = (LPTSTR)SafeFileMalloc(sizeof(TCHAR) * (cchFileName+1));
  315. if (!pszVolumePath)
  316. {
  317. hr = E_OUTOFMEMORY;
  318. goto done;
  319. }
  320. #ifdef UNICODE
  321. if (!GetVolumePathName(pszFileName, pszVolumePath, cchFileName+1))
  322. {
  323. hr = GetLastErrorAsHresult();
  324. goto done;
  325. }
  326. #else
  327. int cchDrivePart;
  328. if (!SkipPathDrivePart(pszFileName, &cchDrivePart, NULL, NULL))
  329. {
  330. hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
  331. goto done;
  332. }
  333. StringCchCopy(pszVolumePath, cchDrivePart+1, pszFileName);
  334. #endif
  335. UINT uDriveType = GetDriveType(pszVolumePath);
  336. switch (uDriveType)
  337. {
  338. case DRIVE_FIXED:
  339. hr = S_OK;
  340. break;
  341. case DRIVE_REMOVABLE:
  342. case DRIVE_CDROM:
  343. case DRIVE_UNKNOWN:
  344. case DRIVE_RAMDISK:
  345. hr = fAllowRemovableDrive ? S_OK : E_ACCESSDENIED;
  346. break;
  347. case DRIVE_REMOTE:
  348. hr = fAllowNetworkDrive ? S_OK : E_ACCESSDENIED;
  349. break;
  350. default:
  351. hr = E_INVALIDARG;
  352. break;
  353. }
  354. }
  355. done:
  356. SafeFileFree(pszVolumePath);
  357. ASSERT(hr != E_INVALIDARG);
  358. return hr;
  359. }
  360. // IsFullPathName [public]
  361. //
  362. // Determines whether the given filename is a full path including a drive
  363. // or UNC. Filenames such as \\?\ are supported, and can be considered
  364. // valid or not depending on the dwSafeFlags parameter.
  365. //
  366. // Returns:
  367. // TRUE - if the filename is a full path
  368. // FALSE - if filename is NULL, isn't a full path, or fails to meet
  369. // the criteria given in the dwSafeFlags parameter.
  370. //
  371. BOOL WINAPI IsFullPathName
  372. (
  373. IN LPCTSTR pszFileName, // full or relative path to a file
  374. OUT OPTIONAL BOOL* pfUNC, // TRUE path is UNC (int incl mapped drive)
  375. OUT OPTIONAL BOOL* pfExtendedSyntax // TRUE if path is \\?\ syntax
  376. )
  377. {
  378. return SkipPathDrivePart(pszFileName, NULL, pfUNC, pfExtendedSyntax);
  379. }
  380. // DoesPathContainDotDot [private]
  381. //
  382. // Returns TRUE if the path contains any ".." references, else FALSE.
  383. //
  384. static BOOL WINAPI DoesPathContainDotDot(IN LPCTSTR pszFileName)
  385. {
  386. if (!pszFileName)
  387. return FALSE;
  388. while (*pszFileName)
  389. {
  390. // Flag path components that consist exactly of ".." (nothing following)
  391. if (pszFileName[0] == _T('.') && pszFileName[1] == _T('.') &&
  392. (pszFileName[2] == _T('/') || pszFileName[2] == _T('\\') || pszFileName[2] == _T('\0')))
  393. {
  394. return TRUE;
  395. }
  396. MyPathFindNextComponent(pszFileName, TRUE, &pszFileName);
  397. }
  398. return FALSE;
  399. }
  400. // DoesPathContainStreamSyntax [private]
  401. //
  402. // Returns TRUE if the path contains any characters that could cause it
  403. // to refer to an alternate NTFS stream (namely any ":" characters beyond
  404. // the drive specification).
  405. //
  406. static BOOL DoesPathContainStreamSyntax(IN LPCTSTR pszFileName)
  407. {
  408. if (!pszFileName)
  409. return FALSE;
  410. int cchSkip;
  411. SkipPathDrivePart(pszFileName, &cchSkip, NULL, NULL);
  412. for (LPCTSTR pch = pszFileName + cchSkip; *pch; pch = CHARNEXT(pch))
  413. {
  414. if (*pch == _T(':'))
  415. return TRUE;
  416. }
  417. return FALSE;
  418. }
  419. // SafeCreateFile [public]
  420. //
  421. // Opens the given file, ensuring that it meets certain path standards (e.g.
  422. // doesn't contain "..") and that it is a file, not a device or named pipe.
  423. //
  424. HRESULT WINAPI SafeCreateFile
  425. (
  426. OUT HANDLE* phFileResult, // receives handle to opened file, or INVALID_HANDLE_VALUE
  427. IN DWORD dwSafeFlags, // zero or more SCF_* flags
  428. IN LPCTSTR pszFileName, // same as CreateFile
  429. IN DWORD dwDesiredAccess, // same as CreateFile
  430. IN DWORD dwShareMode, // same as CreateFile
  431. IN LPSECURITY_ATTRIBUTES lpSecurityAttributes, // same as CreateFile
  432. IN DWORD dwCreationDisposition, // same as CreateFile
  433. IN DWORD dwFlagsAndAttributes, // same as CreateFile + (SECURITY_SQOS_PRESENT|SECURITY_ANONYMOUS)
  434. IN HANDLE hTemplateFile // same as CreateFile
  435. )
  436. {
  437. HANDLE hFile = INVALID_HANDLE_VALUE;
  438. HRESULT hr = S_OK;
  439. // BLOCK
  440. {
  441. if (!pszFileName || !phFileResult ||
  442. (dwSafeFlags & ~(SCF_ALLOW_NETWORK_DRIVE | SCF_ALLOW_REMOVABLE_DRIVE | SCF_ALLOW_ALTERNATE_STREAM)))
  443. {
  444. hr = E_INVALIDARG;
  445. goto done;
  446. }
  447. // We require a full pathname.
  448. if (!IsFullPathName(pszFileName))
  449. {
  450. hr = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
  451. goto done;
  452. }
  453. // Ensure path doesn't contain ".." references
  454. if (DoesPathContainDotDot(pszFileName))
  455. {
  456. hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
  457. goto done;
  458. }
  459. // Ensure filename doesn't refer to alternate stream unless allowed
  460. if (!(dwSafeFlags & SCF_ALLOW_ALTERNATE_STREAM) &&
  461. DoesPathContainStreamSyntax(pszFileName))
  462. {
  463. hr = HRESULT_FROM_WIN32(ERROR_INVALID_NAME);
  464. goto done;
  465. }
  466. // Check drive type to ensure it's allowed by dwSafeFlags
  467. if (FAILED(hr = CheckValidDriveType(pszFileName, (dwSafeFlags & SCF_ALLOW_NETWORK_DRIVE),
  468. (dwSafeFlags & SCF_ALLOW_REMOVABLE_DRIVE))))
  469. {
  470. goto done;
  471. }
  472. // Open the file w/ extra security attributes
  473. dwFlagsAndAttributes |= (SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS);
  474. hFile = CreateFile(pszFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes,
  475. dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
  476. if (hFile == INVALID_HANDLE_VALUE)
  477. {
  478. goto win32_error;
  479. }
  480. // Ensure it's really a file
  481. if (FILE_TYPE_DISK != GetFileType(hFile))
  482. {
  483. CloseHandle(hFile);
  484. hFile = INVALID_HANDLE_VALUE;
  485. hr = HRESULT_FROM_WIN32(ERROR_OPEN_FAILED);
  486. }
  487. goto done;
  488. } // end BLOCK
  489. win32_error:
  490. hr = GetLastErrorAsHresult();
  491. done:
  492. if (phFileResult)
  493. *phFileResult = hFile;
  494. ASSERT(hr != E_INVALIDARG);
  495. return hr;
  496. }
  497. // SafeRemoveFileAttributes [public]
  498. //
  499. // Given a filename and that file's current attributes, checks whether
  500. // any of the bits in dwRemoveAttrib need to be removed from the file,
  501. // and if necessary calls SetFileAttributes() to remove them.
  502. //
  503. // Designed to check for invalid dwCurAttrib and call GetLastError()
  504. // for you, so you can pass GetFileAttributes() directly as a parameter.
  505. //
  506. HRESULT WINAPI SafeRemoveFileAttributes
  507. (
  508. IN LPCTSTR pszFileName, // full path to file whose attributes we will change
  509. IN DWORD dwCurAttrib, // current attributes of the file
  510. IN DWORD dwRemoveAttrib // attribute bits to remove
  511. )
  512. {
  513. HRESULT hr = S_OK; // this is default if attrib doesn't need to be removed
  514. if (!pszFileName || !dwRemoveAttrib)
  515. {
  516. hr = E_INVALIDARG;
  517. goto done;
  518. }
  519. if (dwCurAttrib & dwRemoveAttrib) // note: always true if dwCurAttrib==INVALID_FILE_ATTRIBUTES
  520. {
  521. if (dwCurAttrib == INVALID_FILE_ATTRIBUTES ||
  522. !SetFileAttributes(pszFileName, dwCurAttrib & ~dwRemoveAttrib))
  523. {
  524. hr = GetLastErrorAsHresult();
  525. }
  526. }
  527. done:
  528. ASSERT(hr != E_INVALIDARG);
  529. return hr;
  530. }
  531. // SafeDeleteFolderAndContentsHelper [private]
  532. //
  533. // Does all work except the parameter validation for for
  534. // SafeDeleteFolderAndContents.
  535. //
  536. static HRESULT SafeDeleteFolderAndContentsHelper
  537. (
  538. IN LPCTSTR pszFolderToDelete, // folder in current level of recursion
  539. IN DWORD dwSafeFlags, // zero or more SDF_* flags
  540. OUT WIN32_FIND_DATA* pFind // struct to use for FindFirst/FindNext (to avoid malloc)
  541. )
  542. {
  543. HRESULT hr = S_OK;
  544. LPTSTR pszCurFile = NULL;
  545. HANDLE hFind = INVALID_HANDLE_VALUE;
  546. // Allocate room for folder + backslash + MAX_PATH (includes trailing null)
  547. int cchFolderName = lstrlen(pszFolderToDelete);
  548. int cchAllocCurFile = cchFolderName + 1 + MAX_PATH;
  549. pszCurFile = (LPTSTR)SafeFileMalloc(sizeof(TCHAR) * cchAllocCurFile);
  550. if (!pszCurFile)
  551. {
  552. hr = E_OUTOFMEMORY;
  553. goto done;
  554. }
  555. // Check for read-only base folder
  556. if (dwSafeFlags & SDF_DELETE_READONLY_FILES)
  557. {
  558. hr = SafeRemoveFileAttributes(pszFolderToDelete, GetFileAttributes(pszFolderToDelete), FILE_ATTRIBUTE_READONLY);
  559. if (FAILED(hr) && !(dwSafeFlags & SDF_CONTINUE_IF_ERROR))
  560. goto done;
  561. }
  562. // Build search path by appending "\*.*"
  563. StringCchCopy(pszCurFile, cchAllocCurFile, pszFolderToDelete);
  564. if (!IsSlashOrBackslash(pszCurFile[cchFolderName-1]))
  565. pszCurFile[cchFolderName++] = _T('\\');
  566. StringCchCopy(pszCurFile + cchFolderName, cchAllocCurFile - cchFolderName, _T("*.*"));
  567. // Iterate through all files in this folder
  568. hFind = FindFirstFile(pszCurFile, pFind);
  569. if (hFind == INVALID_HANDLE_VALUE)
  570. {
  571. hr = GetLastErrorAsHresult(); // probably doesn't exist, or not a folder
  572. goto done;
  573. }
  574. else
  575. {
  576. do
  577. {
  578. if (0 == lstrcmp(pFind->cFileName, _T(".")) ||
  579. 0 == lstrcmp(pFind->cFileName, _T("..")))
  580. {
  581. continue;
  582. }
  583. StringCchCopy(pszCurFile + cchFolderName, cchAllocCurFile - cchFolderName, pFind->cFileName);
  584. HRESULT hrCur = S_OK;
  585. if (!(pFind->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) ||
  586. SUCCEEDED(hrCur = CheckReparsePointPermissions(pFind->dwReserved0)))
  587. {
  588. // Remove read-only attribute if allowed
  589. if (dwSafeFlags & SDF_DELETE_READONLY_FILES)
  590. {
  591. hrCur = SafeRemoveFileAttributes(pszCurFile, pFind->dwFileAttributes, FILE_ATTRIBUTE_READONLY);
  592. }
  593. if (SUCCEEDED(hrCur) || (dwSafeFlags & SDF_CONTINUE_IF_ERROR))
  594. {
  595. HRESULT hrCur2 = S_OK;
  596. if (pFind->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  597. {
  598. // Recursively delete folder and contents
  599. // Note that pFind's contents are clobbered by this call
  600. hrCur2 = SafeDeleteFolderAndContentsHelper(pszCurFile, dwSafeFlags, pFind);
  601. }
  602. else
  603. {
  604. // Delete the file
  605. if (!DeleteFile(pszCurFile))
  606. {
  607. hrCur2 = GetLastErrorAsHresult();
  608. }
  609. }
  610. if (FAILED(hrCur2))
  611. hrCur = hrCur2;
  612. }
  613. }
  614. if (FAILED(hrCur))
  615. hr = hrCur;
  616. if (FAILED(hr) && !(dwSafeFlags & SDF_CONTINUE_IF_ERROR))
  617. goto done;
  618. } while (FindNextFile(hFind, pFind));
  619. FindClose(hFind);
  620. hFind = INVALID_HANDLE_VALUE;
  621. }
  622. // Delete the folder
  623. if (!RemoveDirectory(pszFolderToDelete))
  624. {
  625. if (SUCCEEDED(hr))
  626. hr = GetLastErrorAsHresult();
  627. }
  628. done:
  629. if (hFind != INVALID_HANDLE_VALUE)
  630. FindClose(hFind);
  631. SafeFileFree(pszCurFile);
  632. return hr;
  633. }
  634. // SafeDeleteFolderAndContents [public]
  635. //
  636. // Deletes the given folder and all of its contents, but refuses to walk
  637. // across reparse points.
  638. //
  639. HRESULT WINAPI SafeDeleteFolderAndContents
  640. (
  641. IN LPCTSTR pszFolderToDelete, // full path of folder to delete
  642. IN DWORD dwSafeFlags // zero or more SDF_* flags
  643. )
  644. {
  645. HRESULT hr = E_INVALIDARG;
  646. if (!pszFolderToDelete || !(*pszFolderToDelete) ||
  647. (dwSafeFlags & ~(SDF_ALLOW_NETWORK_DRIVE | SDF_DELETE_READONLY_FILES | SDF_CONTINUE_IF_ERROR)))
  648. {
  649. goto done; // hr already set to E_INVALIDARG
  650. }
  651. //
  652. // Ensure it's a full path, but not the root of a drive
  653. //
  654. int cchDrivePart;
  655. if (!SkipPathDrivePart(pszFolderToDelete, &cchDrivePart, NULL, NULL) ||
  656. pszFolderToDelete[cchDrivePart] == _T('\0'))
  657. {
  658. hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
  659. goto done;
  660. }
  661. //
  662. // Ensure we're not deleting from a network drive unless allowed
  663. //
  664. if (FAILED(hr = CheckValidDriveType(pszFolderToDelete, (dwSafeFlags & SDF_ALLOW_NETWORK_DRIVE), TRUE)))
  665. {
  666. goto done;
  667. }
  668. //
  669. // Ensure starting point is not a reparse point
  670. //
  671. DWORD dwReparseType;
  672. if (FAILED(hr = GetReparsePointType(pszFolderToDelete, &dwReparseType)) ||
  673. FAILED(hr = CheckReparsePointPermissions(dwReparseType)))
  674. {
  675. goto done;
  676. }
  677. WIN32_FIND_DATA Find;
  678. hr = SafeDeleteFolderAndContentsHelper(pszFolderToDelete, dwSafeFlags, &Find);
  679. done:
  680. ASSERT(hr != E_INVALIDARG);
  681. return hr;
  682. }
  683. // SafeFileCheckForReparsePoint [public]
  684. //
  685. // Checks a subset of the given filename's component parts to ensure that
  686. // they are not reparse points (specifically, volume mount points or
  687. // junction points: see linkd.exe and mountvol.exe).
  688. //
  689. // Normal return values are S_OK or HRESULT_FROM_WIN32(ERROR_REPARSE_TAG_MISMATCH).
  690. // Other values may be returned in exceptional cases such as out-of-memory.
  691. //
  692. HRESULT WINAPI SafeFileCheckForReparsePoint
  693. (
  694. IN LPCTSTR pszFileName, // full path of a file
  695. IN int nFirstUntrustedOffset, // char offset of first path component to check
  696. IN DWORD dwSafeFlags // zero or more SRP_* flags
  697. )
  698. {
  699. HRESULT hr = E_INVALIDARG;
  700. LPTSTR pszMutableFileName = NULL;
  701. // BLOCK
  702. {
  703. if (!pszFileName || (dwSafeFlags & ~SRP_FILE_MUST_EXIST))
  704. {
  705. goto done; // hr is already E_INVALIDARG
  706. }
  707. int cchFileName = lstrlen(pszFileName);
  708. if ((UINT)nFirstUntrustedOffset >= (UINT)cchFileName) // bad offset, or zero-length filename
  709. {
  710. goto done; // hr is already E_INVALIDARG
  711. }
  712. pszMutableFileName = (LPTSTR)SafeFileMalloc(sizeof(TCHAR) * (cchFileName+1));
  713. if (!pszMutableFileName)
  714. {
  715. hr = E_OUTOFMEMORY;
  716. goto done;
  717. }
  718. StringCchCopy(pszMutableFileName, cchFileName+1, pszFileName);
  719. //
  720. // Always consider the drive part of the path to be trusted
  721. //
  722. int cchDrivePart;
  723. if (!SkipPathDrivePart(pszMutableFileName, &cchDrivePart, NULL, NULL))
  724. {
  725. hr = HRESULT_FROM_WIN32(ERROR_BAD_PATHNAME);
  726. goto done;
  727. }
  728. if (nFirstUntrustedOffset < cchDrivePart)
  729. nFirstUntrustedOffset = cchDrivePart;
  730. //
  731. // Validate left-to-right, starting after trusted base path
  732. //
  733. LPTSTR pszNextComponent = pszMutableFileName + nFirstUntrustedOffset;
  734. BOOL fMoreComponents = TRUE;
  735. do
  736. {
  737. //
  738. // Advance pszNextComponent; truncate after current path component
  739. //
  740. fMoreComponents = MyPathFindNextComponent(pszNextComponent, TRUE, (LPCTSTR*)&pszNextComponent);
  741. TCHAR chSave = *(pszNextComponent-1);
  742. if (fMoreComponents)
  743. {
  744. *(pszNextComponent-1) = _T('\0');
  745. }
  746. // Get reparse point type of truncated string, and undo the truncation
  747. DWORD dwReparseType;
  748. if (FAILED(hr = GetReparsePointType(pszMutableFileName, &dwReparseType)))
  749. goto done;
  750. *(pszNextComponent-1) = chSave;
  751. // Check for forbidden reparse point type, e.g. mounted drive
  752. if (FAILED(hr = CheckReparsePointPermissions(dwReparseType)))
  753. goto done;
  754. }
  755. while (fMoreComponents);
  756. } // end BLOCK
  757. done:
  758. SafeFileFree(pszMutableFileName);
  759. // Ignore file-not-found errors, if requested in dwSafeFlags
  760. if (!(dwSafeFlags & SRP_FILE_MUST_EXIST) &&
  761. (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) ||
  762. hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)))
  763. {
  764. hr = S_OK;
  765. }
  766. ASSERT(hr != E_INVALIDARG);
  767. return hr;
  768. }
  769. // SafePathCombine [public]
  770. //
  771. // Combines a path and filename, ensuring exactly one backslash between them.
  772. // The second "untrusted" half of the path is checked to ensure that it is
  773. // safe (doesn't contain ".." or ":", or point to existing reparse points).
  774. //
  775. // File-not-found errors are ignored unless SPC_FILE_MUST_EXIST flag is specified.
  776. //
  777. // It's ok for the base path and the output buffer to point to the same buffer.
  778. //
  779. // Returns S_OK if successful, or an appropriate error code if not.
  780. //
  781. HRESULT WINAPI SafePathCombine
  782. (
  783. OUT LPTSTR pszBuf, // buffer where combined path will be stored
  784. IN int cchBuf, // size of output buffer, in TCHARs
  785. IN LPCTSTR pszTrustedBasePath, // first half of path, all trusted
  786. IN LPCTSTR pszUntrustedFileName, // second half of path, not trusted
  787. IN DWORD dwSafeFlags // zero or more SPC_* flags
  788. )
  789. {
  790. HRESULT hr = E_INVALIDARG;
  791. if (!pszBuf || cchBuf <= 0 || !pszTrustedBasePath || !pszUntrustedFileName ||
  792. (dwSafeFlags & ~(SPC_FILE_MUST_EXIST | SPC_ALLOW_ALTERNATE_STREAM)))
  793. {
  794. goto done; // hr is already E_INVALIDARG
  795. }
  796. // BLOCK
  797. {
  798. int cchBasePath = lstrlen(pszTrustedBasePath);
  799. int cchFileName = lstrlen(pszUntrustedFileName);
  800. if (cchBasePath == 0 || cchFileName == 0)
  801. {
  802. goto done; // hr is already E_INVALIDARG
  803. }
  804. // Ensure nothing bogus in the untrusted part of the filename
  805. if (DoesPathContainDotDot(pszUntrustedFileName))
  806. {
  807. hr = ERROR_BAD_PATHNAME;
  808. goto done;
  809. }
  810. if (!(dwSafeFlags & SPC_ALLOW_ALTERNATE_STREAM) &&
  811. DoesPathContainStreamSyntax(pszUntrustedFileName))
  812. {
  813. hr = HRESULT_FROM_WIN32(ERROR_INVALID_NAME);
  814. goto done;
  815. }
  816. //
  817. // Ensure room for the "\" that will be inserted.
  818. //
  819. int cchInsertSlash = 0;
  820. if (!IsSlashOrBackslash(pszTrustedBasePath[cchBasePath-1]))
  821. {
  822. cchInsertSlash = 1;
  823. }
  824. if (cchBasePath + cchInsertSlash + cchFileName >= cchBuf)
  825. {
  826. hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
  827. goto done;
  828. }
  829. //
  830. // Build full path with a backslash between
  831. //
  832. if (pszBuf != pszTrustedBasePath)
  833. StringCchCopy(pszBuf, cchBuf, pszTrustedBasePath);
  834. int cchUsed = cchBasePath;
  835. if (cchInsertSlash > 0)
  836. {
  837. pszBuf[cchUsed++] = _T('\\');
  838. }
  839. StringCchCopy(pszBuf + cchUsed, cchBuf - cchUsed, pszUntrustedFileName);
  840. //
  841. // Ensure no junctions or volume mount points in untrusted portion
  842. //
  843. DWORD dwReparseFlags = (dwSafeFlags & SPC_FILE_MUST_EXIST) ? SRP_FILE_MUST_EXIST : 0;
  844. hr = SafeFileCheckForReparsePoint(pszBuf, cchUsed, dwReparseFlags);
  845. }
  846. done:
  847. if (FAILED(hr) && pszBuf && cchBuf > 0)
  848. pszBuf[0] = _T('\0');
  849. ASSERT(hr != E_INVALIDARG);
  850. return hr;
  851. }
  852. // SafePathCombineAlloc [public]
  853. //
  854. // See comments for SafePathCombine. The only difference is that this
  855. // function allocates a buffer of sufficient size and stores it in the
  856. // output parameter ppszResult. Caller is responsible for freeing the
  857. // buffer via SafeFileFree.
  858. //
  859. HRESULT WINAPI SafePathCombineAlloc
  860. (
  861. OUT LPTSTR* ppszResult, // ptr to newly alloc'd buffer stored here
  862. IN LPCTSTR pszTrustedBasePath, // first half of path, all trusted
  863. IN LPCTSTR pszUntrustedFileName, // second half of path, not trusted
  864. IN DWORD dwSafeFlags // zero or more SPC_* flags
  865. )
  866. {
  867. HRESULT hr = E_INVALIDARG;
  868. ASSERT(ppszResult);
  869. *ppszResult = NULL;
  870. if (!pszTrustedBasePath || !pszUntrustedFileName)
  871. {
  872. goto done; // hr already set to E_INVALIDARG
  873. }
  874. // Allocate room for the max possible length (includes room for "\" between parts)
  875. int cchMaxNeeded = lstrlen(pszTrustedBasePath) + lstrlen(pszUntrustedFileName) + 2;
  876. LPTSTR pszResult = (LPTSTR)SafeFileMalloc(sizeof(TCHAR) * cchMaxNeeded);
  877. if (!pszResult)
  878. {
  879. hr = E_OUTOFMEMORY;
  880. goto done;
  881. }
  882. hr = SafePathCombine(pszResult, cchMaxNeeded, pszTrustedBasePath, pszUntrustedFileName, dwSafeFlags);
  883. if (FAILED(hr))
  884. {
  885. SafeFileFree(pszResult);
  886. }
  887. else
  888. {
  889. *ppszResult = pszResult;
  890. }
  891. done:
  892. ASSERT(hr != E_INVALIDARG);
  893. return hr;
  894. }