Source code of Windows XP (NT5)
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.

688 lines
21 KiB

  1. /*++
  2. Copyright (c) 1999-2000 Microsoft Corporation
  3. Module Name:
  4. SafeCann.c (WinSAFER Filename Canonicalization)
  5. Abstract:
  6. This module implements the WinSAFER APIs that produce canonicalized
  7. filenames from a caller-supplied .
  8. Author:
  9. Jeffrey Lawson (JLawson) - Nov 1999
  10. Environment:
  11. User mode only.
  12. Exported Functions:
  13. CodeAuthzFullyQualifyFilename
  14. Revision History:
  15. Created - Nov 2000
  16. --*/
  17. #include "pch.h"
  18. #pragma hdrstop
  19. #include <winsafer.h>
  20. #include <winsaferp.h>
  21. #include "saferp.h"
  22. //
  23. // Defines the maximum recursion depth that will be used when attempting
  24. // to resolve the final mapping of SUBST'ed drives. For worst-case, value
  25. // shouldn't be greater than 26 (the number of possible drive letters).
  26. //
  27. #define MAX_RECURSE_DRIVE_LETTER 10
  28. //
  29. // Some static name prefixes in the NT object namespace.
  30. //
  31. static const UNICODE_STRING UnicodeDeviceWinDfs =
  32. RTL_CONSTANT_STRING( L"\\Device\\WinDfs\\" );
  33. static const UNICODE_STRING UnicodeDeviceLanman =
  34. RTL_CONSTANT_STRING( L"\\Device\\LanmanRedirector\\" );
  35. static const UNICODE_STRING UnicodeDosDevicesUncPrefix =
  36. RTL_CONSTANT_STRING( L"\\??\\UNC\\" );
  37. static const UNICODE_STRING UnicodeDosDevicesPrefix =
  38. RTL_CONSTANT_STRING( L"\\??\\" );
  39. static const UNICODE_STRING UnicodeDevicePrefix =
  40. RTL_CONSTANT_STRING( L"\\Device\\" );
  41. static BOOLEAN FORCEINLINE
  42. SaferpIsAlphaLetter(
  43. IN WCHAR inwcletter
  44. )
  45. {
  46. #if 1
  47. if ((inwcletter >= L'A' && inwcletter <= L'Z') ||
  48. (inwcletter >= L'a' && inwcletter <= L'z'))
  49. return TRUE;
  50. else
  51. return FALSE;
  52. #else
  53. inwcletter = RtlUpcaseUnicodeChar(inwcletter);
  54. return (inwcletter >= L'A' && inwcletter <= 'Z') ? TRUE : FALSE;
  55. #endif
  56. }
  57. static BOOLEAN NTAPI
  58. SaferpQueryActualDriveLetterFromDriveLetter(
  59. IN WCHAR inDriveLetter,
  60. OUT WCHAR *outDriveLetter,
  61. IN SHORT MaxRecurseCount
  62. )
  63. /*++
  64. Routine Description:
  65. Attempts to determine if a specified drive letter is a SUBST'ed
  66. drive letter, a network mapped drive letter, or a physical drive
  67. letter. Unknown cases result in a failure.
  68. Arguments:
  69. inDriveLetter - Drive leter to obtain information about. This must
  70. be an alphabetic character.
  71. outDriveLetter - Receives the result of the evaluation and indicates
  72. what drive letter the requested one actually points to:
  73. --> If the drive letter is a SUBST'ed drive, then the result
  74. will be the drive letter of the original drive.
  75. --> If the drive letter is a network mapped drive, then the
  76. result will be UNICODE_NULL, indicating a network volume.
  77. --> If the drive letter is a local, physical drive, then
  78. the result will be the same as the input letter.
  79. MaxRecurseCount - used for limiting maximum recursion depth.
  80. Recommend specifying a reasonable positive value.
  81. Return Value:
  82. Returns TRUE on successful operation, FALSE if the determination
  83. could not be made.
  84. --*/
  85. {
  86. NTSTATUS Status;
  87. HANDLE LinkHandle;
  88. UNICODE_STRING UnicodeFileName;
  89. OBJECT_ATTRIBUTES Attributes;
  90. const WCHAR FileNameBuffer[7] = { L'\\', L'?', L'?', L'\\',
  91. inDriveLetter, L':', UNICODE_NULL };
  92. UNICODE_STRING LinkValue;
  93. WCHAR LinkValueBuffer[2*MAX_PATH];
  94. ULONG ReturnedLength;
  95. //
  96. // Require that the input drive letter be alphabetic.
  97. //
  98. if (!SaferpIsAlphaLetter(inDriveLetter)) {
  99. // Input drive letter was not uppercase alphabetic.
  100. return FALSE;
  101. }
  102. //
  103. // Open a reference to see if there are any links.
  104. //
  105. RtlInitUnicodeString(&UnicodeFileName, FileNameBuffer);
  106. InitializeObjectAttributes(&Attributes, &UnicodeFileName,
  107. OBJ_CASE_INSENSITIVE, NULL, NULL);
  108. Status = NtOpenSymbolicLinkObject (&LinkHandle,
  109. SYMBOLIC_LINK_QUERY,
  110. &Attributes);
  111. if (!NT_SUCCESS(Status)) {
  112. // Unable to open the drive letter so it must not exist.
  113. return FALSE;
  114. }
  115. //
  116. // Now query the link and see if there is a redirection
  117. //
  118. LinkValue.Buffer = LinkValueBuffer;
  119. LinkValue.Length = 0;
  120. LinkValue.MaximumLength = (USHORT)(sizeof(LinkValueBuffer));
  121. ReturnedLength = 0;
  122. Status = NtQuerySymbolicLinkObject( LinkHandle,
  123. &LinkValue,
  124. &ReturnedLength
  125. );
  126. NtClose( LinkHandle );
  127. if (!NT_SUCCESS(Status)) {
  128. // Could not retrieve final link destination.
  129. return FALSE;
  130. }
  131. //
  132. // Analyze the resulting link destination and extract the
  133. // actual destination drive letter or network path.
  134. //
  135. if (RtlPrefixUnicodeString(
  136. (PUNICODE_STRING) &UnicodeDeviceWinDfs,
  137. &LinkValue, TRUE) ||
  138. RtlPrefixUnicodeString(
  139. (PUNICODE_STRING) &UnicodeDeviceLanman,
  140. &LinkValue, TRUE) ||
  141. RtlPrefixUnicodeString(
  142. (PUNICODE_STRING) &UnicodeDosDevicesUncPrefix,
  143. &LinkValue, TRUE))
  144. // Note: Other network redirectors (Netware, NFS, etc) will not be known as such.
  145. // Maybe there is a way to query if a device is a "network redirector"?
  146. {
  147. // This is a network volume.
  148. *outDriveLetter = UNICODE_NULL;
  149. return TRUE;
  150. }
  151. else if (RtlPrefixUnicodeString(
  152. (PUNICODE_STRING) &UnicodeDosDevicesPrefix,
  153. &LinkValue, TRUE) &&
  154. LinkValue.Length >= 6 * sizeof(WCHAR) &&
  155. LinkValue.Buffer[5] == L':' &&
  156. SaferpIsAlphaLetter(LinkValue.Buffer[4]))
  157. {
  158. // This is a SUBST'ed drive letter.
  159. // We need to recurse, since you can SUBST multiple times,
  160. // or SUBST a network mapped drive to a second drive letter.
  161. if (MaxRecurseCount > 0) {
  162. // Tail recursion here would be nice.
  163. return SaferpQueryActualDriveLetterFromDriveLetter(
  164. LinkValue.Buffer[4], outDriveLetter, MaxRecurseCount - 1);
  165. }
  166. return FALSE;
  167. }
  168. else if (RtlPrefixUnicodeString(
  169. (PUNICODE_STRING) &UnicodeDevicePrefix,
  170. &LinkValue, TRUE))
  171. {
  172. // Otherwise this drive letter is an actual device and is
  173. // apparently its own identity. However, network redirectors
  174. // that we did not know about will also fall into this bucket.
  175. *outDriveLetter = inDriveLetter;
  176. return TRUE;
  177. } else {
  178. // Otherwise we don't know what it is.
  179. return FALSE;
  180. }
  181. }
  182. static BOOLEAN NTAPI
  183. SaferpQueryCanonicalizedDriveLetterFromDosPathname(
  184. IN LPCWSTR szDosPathname,
  185. OUT WCHAR *wcDriveLetter
  186. )
  187. /*++
  188. Routine Description:
  189. Arguments:
  190. Return Value:
  191. --*/
  192. {
  193. RTL_PATH_TYPE PathType;
  194. //
  195. // Verify input arguments were supplied.
  196. //
  197. if (!ARGUMENT_PRESENT(szDosPathname) ||
  198. !ARGUMENT_PRESENT(wcDriveLetter)) {
  199. return FALSE;
  200. }
  201. //
  202. // Determine what syntax this DOS pathname was supplied to us as.
  203. //
  204. PathType = RtlDetermineDosPathNameType_U(szDosPathname);
  205. switch (PathType) {
  206. case RtlPathTypeUncAbsolute:
  207. // definitely a network volume.
  208. *wcDriveLetter = UNICODE_NULL;
  209. return TRUE;
  210. case RtlPathTypeDriveAbsolute:
  211. case RtlPathTypeDriveRelative:
  212. // explicitly specified drive letter, but need to handle subst or network mapped.
  213. {
  214. WCHAR CurDrive = RtlUpcaseUnicodeChar( szDosPathname[0] );
  215. if (SaferpQueryActualDriveLetterFromDriveLetter(
  216. CurDrive, wcDriveLetter, MAX_RECURSE_DRIVE_LETTER)) {
  217. return TRUE;
  218. }
  219. break;
  220. }
  221. case RtlPathTypeRooted:
  222. case RtlPathTypeRelative:
  223. // relative to current drive, but still need to handle subst or network mapped.
  224. {
  225. PCURDIR CurDir;
  226. WCHAR CurDrive;
  227. CurDir = &(NtCurrentPeb()->ProcessParameters->CurrentDirectory);
  228. CurDrive = RtlUpcaseUnicodeChar( CurDir->DosPath.Buffer[0] );
  229. if (SaferpQueryActualDriveLetterFromDriveLetter(
  230. CurDrive, wcDriveLetter, MAX_RECURSE_DRIVE_LETTER)) {
  231. return TRUE;
  232. }
  233. break;
  234. }
  235. // Everything else gets rejected:
  236. // RtlPathTypeUnknown
  237. // RtlPathTypeLocalDevice
  238. // RtlPathTypeRootLocalDevice
  239. }
  240. return FALSE;
  241. }
  242. static BOOLEAN NTAPI
  243. SaferpQueryCanonicalizedDriveLetterFromNtPathname(
  244. IN LPCWSTR szNtPathname,
  245. OUT WCHAR *wcDriveLetter
  246. )
  247. /*++
  248. Routine Description:
  249. Arguments:
  250. Return Value:
  251. --*/
  252. {
  253. UNICODE_STRING LinkValue;
  254. RtlInitUnicodeString(&LinkValue, szNtPathname);
  255. //
  256. // Analyze the resulting link destination and extract the
  257. // actual destination drive letter or network path.
  258. //
  259. if (RtlPrefixUnicodeString(
  260. (PUNICODE_STRING) &UnicodeDeviceWinDfs,
  261. &LinkValue, TRUE) ||
  262. RtlPrefixUnicodeString(
  263. (PUNICODE_STRING) &UnicodeDeviceLanman,
  264. &LinkValue, TRUE) ||
  265. RtlPrefixUnicodeString(
  266. (PUNICODE_STRING) &UnicodeDosDevicesUncPrefix,
  267. &LinkValue, TRUE))
  268. // Note: Other network redirectors (Netware, NFS, etc) will not be known as such.
  269. // Maybe there is a way to query if a device is a "network redirector"?
  270. {
  271. // This is a network volume.
  272. *wcDriveLetter = UNICODE_NULL;
  273. return TRUE;
  274. }
  275. else if (RtlPrefixUnicodeString(
  276. (PUNICODE_STRING) &UnicodeDosDevicesPrefix,
  277. &LinkValue, TRUE) &&
  278. LinkValue.Length >= 6 * sizeof(WCHAR) &&
  279. LinkValue.Buffer[5] == L':' &&
  280. SaferpIsAlphaLetter(LinkValue.Buffer[4]))
  281. {
  282. // This is a SUBST'ed drive letter.
  283. // We need to recurse, since you can SUBST multiple times,
  284. // or SUBST a network mapped drive to a second drive letter.
  285. return SaferpQueryActualDriveLetterFromDriveLetter(
  286. LinkValue.Buffer[4], wcDriveLetter, MAX_RECURSE_DRIVE_LETTER);
  287. }
  288. else {
  289. // Otherwise we don't know what it is.
  290. return FALSE;
  291. }
  292. }
  293. static NTSTATUS NTAPI
  294. SaferpQueryFilenameFromHandle(
  295. IN HANDLE hFileHandle,
  296. IN WCHAR wcDriveLetter,
  297. OUT PUNICODE_STRING pUnicodeOutput
  298. )
  299. /*++
  300. Routine Description:
  301. Attempts to determine the fully qualified, canonicalized long
  302. filename version of the file associated with a given file handle.
  303. Note that the behavior provided by this function is a frequently
  304. requested API by Win32 developers because this information is
  305. normally not available by any other way through documented
  306. Win32 API calls. However, even this implementation is not able to
  307. generally satisfy the general case very well due the limited access
  308. to the full path information from user-mode.
  309. Arguments:
  310. hFileHandle -
  311. wcDriveLetter -
  312. pUnicodeOutput - Canonicalized DOS namespace filename, or
  313. potentially a UNC network path.
  314. Return Value:
  315. Returns STATUS_SUCCESS on successful completion, otherwise an error code.
  316. --*/
  317. {
  318. NTSTATUS Status;
  319. IO_STATUS_BLOCK IoStatusBlock;
  320. WCHAR szLongFileNameBuffer[MAX_PATH];
  321. union {
  322. BYTE FileNameInfoBuffer[ (sizeof(WCHAR) * MAX_PATH) +
  323. sizeof(FILE_NAME_INFORMATION)];
  324. FILE_NAME_INFORMATION FileNameInfo;
  325. } moo;
  326. UNICODE_STRING UnicodeFileName;
  327. //
  328. // Query the full path and filename (minus the drive letter).
  329. //
  330. Status = NtQueryInformationFile(
  331. hFileHandle,
  332. &IoStatusBlock,
  333. &moo.FileNameInfo,
  334. sizeof(moo.FileNameInfoBuffer),
  335. FileNameInformation);
  336. if (!NT_SUCCESS(Status)) {
  337. return Status;
  338. }
  339. //
  340. // Initialize the UNICODE_STRING reference to the output string.
  341. //
  342. UnicodeFileName.Buffer = &moo.FileNameInfo.FileName[0];
  343. UnicodeFileName.Length = (USHORT) moo.FileNameInfo.FileNameLength;
  344. UnicodeFileName.MaximumLength = (USHORT)
  345. ( sizeof(moo.FileNameInfoBuffer) -
  346. ( ((PBYTE) (&moo.FileNameInfo.FileName[0])) -
  347. ((PBYTE) &moo.FileNameInfoBuffer) ) );
  348. ASSERT(UnicodeFileName.Length <= UnicodeFileName.MaximumLength);
  349. //
  350. // Perform some additional fixups depending upon whether we
  351. // were told that the file eventually comes from a local drive
  352. // letter or a network/dfs share.
  353. //
  354. if (wcDriveLetter == UNICODE_NULL)
  355. {
  356. // Ensure there is room for one more character.
  357. if (UnicodeFileName.Length + sizeof(WCHAR) >
  358. UnicodeFileName.MaximumLength) {
  359. return STATUS_BUFFER_OVERFLOW;
  360. }
  361. // We've been told that this comes from a network volume,
  362. // so we need to prepend another backslash to the front.
  363. RtlMoveMemory(&UnicodeFileName.Buffer[1],
  364. &UnicodeFileName.Buffer[0],
  365. UnicodeFileName.Length);
  366. ASSERT(UnicodeFileName.Buffer[0] == L'\\' &&
  367. UnicodeFileName.Buffer[1] == L'\\');
  368. UnicodeFileName.Length += sizeof(WCHAR);
  369. }
  370. else if (SaferpIsAlphaLetter(wcDriveLetter))
  371. {
  372. // Ensure there is room for two more characters.
  373. if (UnicodeFileName.Length + 2 * sizeof(WCHAR) >
  374. UnicodeFileName.MaximumLength) {
  375. return STATUS_BUFFER_OVERFLOW;
  376. }
  377. // We've been told that this comes from a local drive.
  378. RtlMoveMemory(&UnicodeFileName.Buffer[2],
  379. &UnicodeFileName.Buffer[0],
  380. UnicodeFileName.Length);
  381. UnicodeFileName.Buffer[0] = RtlUpcaseUnicodeChar(wcDriveLetter);
  382. UnicodeFileName.Buffer[1] = L':';
  383. ASSERT(UnicodeFileName.Buffer[2] == L'\\');
  384. UnicodeFileName.Length += 2 * sizeof(WCHAR);
  385. }
  386. else {
  387. // Otherwise invalid input.
  388. return STATUS_INVALID_PARAMETER;
  389. }
  390. //
  391. // Make sure the string is NULL terminated
  392. //
  393. UnicodeFileName.Buffer[(UnicodeFileName.Length)/sizeof(WCHAR)] = L'\0';
  394. szLongFileNameBuffer[0] = L'\0';
  395. if (GetLongPathNameW(UnicodeFileName.Buffer,
  396. szLongFileNameBuffer,
  397. sizeof(szLongFileNameBuffer) / sizeof(WCHAR))) {
  398. RtlInitUnicodeString(&UnicodeFileName, szLongFileNameBuffer);
  399. }
  400. //
  401. // Duplicate the local string into a new memory buffer so we
  402. // can pass it back to the caller.
  403. Status = RtlDuplicateUnicodeString(
  404. RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE,
  405. &UnicodeFileName,
  406. pUnicodeOutput);
  407. return Status;
  408. }
  409. NTSTATUS NTAPI
  410. CodeAuthzFullyQualifyFilename(
  411. IN HANDLE hFileHandle OPTIONAL,
  412. IN BOOLEAN bSourceIsNtPath,
  413. IN LPCWSTR szSourceFilePath,
  414. OUT PUNICODE_STRING pUnicodeResult
  415. )
  416. /*++
  417. Routine Description:
  418. Attempts to return fully qualified, canonicalized filename using a
  419. caller-supplied filename and optionally an opened file handle.
  420. The method used by this function is significantly more reliable
  421. and consistent if an opened file handle can additionally be provided.
  422. Arguments:
  423. hFileHandle - optionally supplies the file handle to the file that
  424. is being canonicalized. The handle is used to obtain a more
  425. definitive canonicalization result.
  426. Unfortunately, since NT does not currently allow full information
  427. to be queried from strictly the file handle, the original filename
  428. used to open the file needs to also be supplied. No explicit
  429. verification is done to ensure that the supplied file handle
  430. actually corresponds with the filename that is also supplied.
  431. bSourceIsNtPath - boolean indicator of whether the filename being
  432. supplied is a DOS namespace or an NT-namespace filename.
  433. szSourceFilePath - string of the filename to canonicalize. This
  434. filename may either be a DOS or an NT-namespace filename.
  435. pUnicodeResult - output UNICODE_STRING structure that receives an
  436. allocated string of the resulting canonicalized path.
  437. The resulting path will always be a DOS namespace filename.
  438. Return Value:
  439. Returns STATUS_SUCCESS if successful, otherwise the error code.
  440. --*/
  441. {
  442. NTSTATUS Status = STATUS_UNSUCCESSFUL;
  443. if (ARGUMENT_PRESENT(hFileHandle) && ARGUMENT_PRESENT(szSourceFilePath))
  444. {
  445. //
  446. // When we are given a file handle, or are able to open
  447. // the file ourselves, use the handle to derive the full name.
  448. // First, determine the drive letter by looking at the supplied
  449. // file path itself. This step is necessary because the
  450. // NtQueryInformationFile API that we use later is unable to
  451. // supply the full prefix of the filename.
  452. //
  453. WCHAR wcDriveLetter;
  454. Status = STATUS_SUCCESS;
  455. if (bSourceIsNtPath) {
  456. if (!SaferpQueryCanonicalizedDriveLetterFromNtPathname(
  457. szSourceFilePath, &wcDriveLetter))
  458. Status = STATUS_UNSUCCESSFUL;
  459. } else {
  460. if (!SaferpQueryCanonicalizedDriveLetterFromDosPathname(
  461. szSourceFilePath, &wcDriveLetter))
  462. Status = STATUS_UNSUCCESSFUL;
  463. }
  464. if (NT_SUCCESS(Status)) {
  465. Status = SaferpQueryFilenameFromHandle(
  466. hFileHandle,
  467. wcDriveLetter,
  468. pUnicodeResult);
  469. if (NT_SUCCESS(Status)) return Status;
  470. }
  471. }
  472. if (szSourceFilePath != NULL)
  473. {
  474. //
  475. // Allow the case where a pathname was supplied, but not a
  476. // handle and we were unable to open the file. This case
  477. // will not be very common, so it can be less efficient.
  478. //
  479. UNICODE_STRING UnicodeInput;
  480. WCHAR FileNameBuffer[MAX_PATH];
  481. WCHAR FileNameBuffer2[MAX_PATH];
  482. //
  483. // Transform the name into a fully qualified name.
  484. //
  485. RtlInitUnicodeString(&UnicodeInput, szSourceFilePath);
  486. if ( bSourceIsNtPath )
  487. {
  488. if (RtlPrefixUnicodeString(
  489. (PUNICODE_STRING) &UnicodeDosDevicesPrefix,
  490. &UnicodeInput, TRUE) &&
  491. UnicodeInput.Length >= 6 * sizeof(WCHAR) &&
  492. UnicodeInput.Buffer[5] == L':' &&
  493. SaferpIsAlphaLetter(UnicodeInput.Buffer[4]) &&
  494. UnicodeInput.Buffer[6] == L'\\')
  495. {
  496. // Absolute NT style filename, and assumed to already be
  497. // fully-qualified. Since we want the DOS-namespace,
  498. // the leading NT prefix stuff needs to be chopped.
  499. UnicodeInput.Buffer = &UnicodeInput.Buffer[4];
  500. UnicodeInput.Length -= (4 * sizeof(WCHAR));
  501. Status = STATUS_SUCCESS;
  502. } else {
  503. Status = STATUS_UNSUCCESSFUL;
  504. }
  505. } else {
  506. // Need to possibly fully qualify the path first.
  507. ULONG ulResult = RtlGetFullPathName_U(
  508. UnicodeInput.Buffer,
  509. sizeof(FileNameBuffer2), // yes, BYTEs not WCHARs!
  510. FileNameBuffer2,
  511. NULL);
  512. if (ulResult != 0 && ulResult < sizeof(FileNameBuffer2)) {
  513. UnicodeInput.Buffer = FileNameBuffer2;
  514. UnicodeInput.Length = (USHORT) ulResult;
  515. UnicodeInput.MaximumLength = sizeof(FileNameBuffer2);
  516. Status = STATUS_SUCCESS;
  517. } else {
  518. Status = STATUS_UNSUCCESSFUL;
  519. }
  520. }
  521. //
  522. // Convert any short 8.3 filenames to their full versions.
  523. //
  524. if (NT_SUCCESS(Status))
  525. {
  526. if (!GetLongPathNameW(UnicodeInput.Buffer,
  527. FileNameBuffer,
  528. sizeof(FileNameBuffer) / sizeof(WCHAR))) {
  529. // duplicate UnicodeInput into identStruct.UnicodeFullyQualfiedLongFileName
  530. Status = RtlDuplicateUnicodeString(
  531. RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE |
  532. RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING,
  533. &UnicodeInput,
  534. pUnicodeResult);
  535. } else {
  536. // Conversion was possible, so just return an
  537. // allocated copy of what we were able to find.
  538. // This can happen when the file path doesn't exist.
  539. Status = RtlCreateUnicodeString(
  540. pUnicodeResult,
  541. FileNameBuffer);
  542. }
  543. if (NT_SUCCESS(Status)) return Status;
  544. }
  545. // REVIEW: we could also potentially try to use GetDriveType()
  546. // and expand the drive letter to the actual network volume
  547. // or subst'ed target drive.
  548. }
  549. return Status;
  550. }