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.

732 lines
21 KiB

  1. /*++
  2. Copyright (c) 1997-2001 Microsoft Corporation
  3. Module Name:
  4. esp.c
  5. Abstract:
  6. This module contains the code to create/verify the ESP headers.
  7. Author:
  8. Sanjay Anand (SanjayAn) 2-January-1997
  9. ChunYe
  10. Environment:
  11. Kernel mode
  12. Revision History:
  13. --*/
  14. #include "precomp.h"
  15. #ifndef _TEST_PERF
  16. CONFID_ALGO conf_algorithms[] = {
  17. { esp_nullinit, esp_nullencrypt, esp_nulldecrypt, DES_BLOCKLEN},
  18. { esp_desinit, esp_desencrypt, esp_desdecrypt, DES_BLOCKLEN},
  19. { esp_desinit, esp_desencrypt, esp_desdecrypt, DES_BLOCKLEN},
  20. { esp_3_desinit, esp_3_desencrypt, esp_3_desdecrypt, DES_BLOCKLEN},
  21. };
  22. #else
  23. CONFID_ALGO conf_algorithms[] = {
  24. { esp_nullinit, esp_nullencrypt, esp_nulldecrypt, DES_BLOCKLEN},
  25. { esp_nullinit, esp_nullencrypt, esp_nulldecrypt, DES_BLOCKLEN},
  26. { esp_nullinit, esp_nullencrypt, esp_nulldecrypt, DES_BLOCKLEN},
  27. { esp_nullinit, esp_nullencrypt, esp_nulldecrypt, DES_BLOCKLEN},
  28. };
  29. #endif
  30. VOID
  31. esp_nullinit (
  32. IN PVOID pState,
  33. IN PUCHAR pKey
  34. )
  35. {
  36. return;
  37. }
  38. VOID
  39. esp_nullencrypt (
  40. PVOID pState,
  41. PUCHAR pOut,
  42. PUCHAR pIn,
  43. PUCHAR pIV
  44. )
  45. {
  46. RtlCopyMemory(pOut, pIn, DES_BLOCKLEN);
  47. return;
  48. }
  49. VOID
  50. esp_nulldecrypt (
  51. PVOID pState,
  52. PUCHAR pOut,
  53. PUCHAR pIn,
  54. PUCHAR pIV
  55. )
  56. {
  57. return;
  58. }
  59. VOID
  60. esp_desinit (
  61. IN PVOID pState,
  62. IN PUCHAR pKey
  63. )
  64. {
  65. DESTable *Table = &((PCONF_STATE_BUFFER)pState)->desTable;
  66. IPSEC_DES_KEY(Table, pKey);
  67. }
  68. VOID
  69. esp_desencrypt (
  70. PVOID pState,
  71. PUCHAR pOut,
  72. PUCHAR pIn,
  73. PUCHAR pIV
  74. )
  75. {
  76. DESTable *Table = &((PCONF_STATE_BUFFER)pState)->desTable;
  77. IPSEC_CBC(IPSEC_DES_ALGO,
  78. pOut,
  79. pIn, // pChunk,
  80. Table,
  81. ENCRYPT,
  82. pIV);
  83. }
  84. VOID
  85. esp_desdecrypt (
  86. PVOID pState,
  87. PUCHAR pOut,
  88. PUCHAR pIn,
  89. PUCHAR pIV
  90. )
  91. {
  92. DESTable *Table = &((PCONF_STATE_BUFFER)pState)->desTable;
  93. IPSEC_CBC(IPSEC_DES_ALGO,
  94. pOut,
  95. pIn, // pChunk,
  96. Table,
  97. DECRYPT,
  98. pIV);
  99. }
  100. VOID
  101. esp_3_desinit (
  102. IN PVOID pState,
  103. IN PUCHAR pKey
  104. )
  105. {
  106. DES3TABLE *Table = &((PCONF_STATE_BUFFER)pState)->des3Table;
  107. IPSEC_3DES_KEY(Table, pKey);
  108. }
  109. VOID
  110. esp_3_desencrypt (
  111. PVOID pState,
  112. PUCHAR pOut,
  113. PUCHAR pIn,
  114. PUCHAR pIV
  115. )
  116. {
  117. DES3TABLE *Table = &((PCONF_STATE_BUFFER)pState)->des3Table;
  118. IPSEC_CBC(IPSEC_3DES_ALGO,
  119. pOut,
  120. pIn, // pChunk,
  121. Table,
  122. ENCRYPT,
  123. pIV);
  124. }
  125. VOID
  126. esp_3_desdecrypt (
  127. PVOID pState,
  128. PUCHAR pOut,
  129. PUCHAR pIn,
  130. PUCHAR pIV
  131. )
  132. {
  133. DES3TABLE *Table = &((PCONF_STATE_BUFFER)pState)->des3Table;
  134. IPSEC_CBC(IPSEC_3DES_ALGO,
  135. pOut,
  136. pIn, // pChunk,
  137. Table,
  138. DECRYPT,
  139. pIV);
  140. }
  141. IPRcvBuf *
  142. CopyToRcvBuf(
  143. IN IPRcvBuf *DestBuf,
  144. IN PUCHAR SrcBuf,
  145. IN ULONG Size,
  146. IN PULONG StartOffset
  147. )
  148. /*++
  149. Copy a flat buffer to an IPRcvBuf chain.
  150. A utility function to copy a flat buffer to an NDIS buffer chain. We
  151. assume that the NDIS_BUFFER chain is big enough to hold the copy amount;
  152. in a debug build we'll debugcheck if this isn't true. We return a pointer
  153. to the buffer where we stopped copying, and an offset into that buffer.
  154. This is useful for copying in pieces into the chain.
  155. Input:
  156. DestBuf - Destination IPRcvBuf chain.
  157. SrcBuf - Src flat buffer.
  158. Size - Size in bytes to copy.
  159. StartOffset - Pointer to start of offset into first buffer in
  160. chain. Filled in on return with the offset to
  161. copy into next.
  162. Returns:
  163. Pointer to next buffer in chain to copy into.
  164. --*/
  165. {
  166. UINT CopySize;
  167. UCHAR *DestPtr;
  168. UINT DestSize;
  169. UINT Offset = *StartOffset;
  170. UCHAR *VirtualAddress;
  171. UINT Length;
  172. if (DestBuf == NULL || SrcBuf == NULL) {
  173. ASSERT(FALSE);
  174. return NULL;
  175. }
  176. IPSecQueryRcvBuf(DestBuf, &VirtualAddress, &Length);
  177. ASSERT(Length >= Offset);
  178. DestPtr = VirtualAddress + Offset;
  179. DestSize = Length - Offset;
  180. for (;;) {
  181. CopySize = MIN(Size, DestSize);
  182. RtlCopyMemory(DestPtr, SrcBuf, CopySize);
  183. DestPtr += CopySize;
  184. SrcBuf += CopySize;
  185. if ((Size -= CopySize) == 0)
  186. break;
  187. if ((DestSize -= CopySize) == 0) {
  188. DestBuf = IPSEC_BUFFER_LINKAGE(DestBuf);
  189. if (DestBuf == NULL) {
  190. ASSERT(FALSE);
  191. break;
  192. }
  193. IPSecQueryRcvBuf(DestBuf, &VirtualAddress, &Length);
  194. DestPtr = VirtualAddress;
  195. DestSize = Length;
  196. }
  197. }
  198. *StartOffset = (ULONG)(DestPtr - VirtualAddress);
  199. return DestBuf;
  200. }
  201. NTSTATUS
  202. IPSecEncryptBuffer(
  203. IN PVOID pData,
  204. IN PNDIS_BUFFER *ppNewMdl,
  205. IN PSA_TABLE_ENTRY pSA,
  206. IN PNDIS_BUFFER pPadBuf,
  207. OUT PULONG pPadLen,
  208. IN ULONG PayloadType,
  209. IN ULONG Index,
  210. IN PUCHAR feedback
  211. )
  212. {
  213. CONF_STATE_BUFFER Key;
  214. PCONFID_ALGO pConfAlgo;
  215. UCHAR scratch[MAX_BLOCKLEN]; // scratch buffer for the encrypt
  216. UCHAR scratch1[MAX_BLOCKLEN]; // scratch buffer for the encrypt
  217. PUCHAR pDest=NULL;
  218. PNDIS_BUFFER pEncryptMdl;
  219. ULONG len;
  220. ULONG blockLen;
  221. NTSTATUS status;
  222. IPSEC_DEBUG(ESP, ("Entering IPSecEncryptBuffer: pData: %lx\n", pData));
  223. if (pSA->CONF_ALGO(Index) > NUM_CONF_ALGOS) {
  224. ASSERT(FALSE);
  225. return STATUS_INVALID_PARAMETER;
  226. }
  227. pConfAlgo = &(conf_algorithms[pSA->CONF_ALGO(Index)]);
  228. blockLen = pConfAlgo->blocklen;
  229. //
  230. // set up the state buffer
  231. //
  232. pConfAlgo->init((PVOID)&Key, pSA->CONF_KEY(Index));
  233. IPSEC_DEBUG(HUGHES, ("pConfAlgo: %lx, blockLen: %lx IV: %lx-%lx\n", pConfAlgo, blockLen, *(PULONG)&feedback[0], *(PULONG)&feedback[4]));
  234. if (*ppNewMdl == NULL) {
  235. //
  236. // We should not encrypt in place: so we alloc a new buffer
  237. // Count up the total size and allocate the new buffer.
  238. // use that buffer as the dest of the encrypt.
  239. //
  240. IPSEC_GET_TOTAL_LEN(pData, &len);
  241. #if DBG
  242. if ((len % 8) != 0) {
  243. DbgPrint("Length not kosher: pData: %lx, len: %d, pPadBuf: %lx, pPadLen: %d\n", pData, len, pPadBuf, pPadLen);
  244. DbgBreakPoint();
  245. }
  246. #endif
  247. IPSecAllocateBuffer(&status, &pEncryptMdl, &pDest, len, IPSEC_TAG_ESP);
  248. if (!NT_SUCCESS(status)) {
  249. NTSTATUS ntstatus;
  250. //ASSERT(FALSE);
  251. IPSEC_DEBUG(ESP, ("Failed to alloc. encrypt MDL\n"));
  252. return status;
  253. }
  254. IPSEC_DEBUG(ESP, ("Alloc. MDL: %lx, pDest: %lx, len: %d, pData: %lx\n", pEncryptMdl, pDest, len, pData));
  255. } else {
  256. ASSERT(FALSE);
  257. IPSecQueryNdisBuf(*ppNewMdl, &pDest, &len);
  258. pEncryptMdl = *ppNewMdl;
  259. }
  260. //
  261. // Now, send 64 bit (8 octet) chunks to CBC. We need to make sure
  262. // that the data is divided on contiguous 8 byte boundaries across
  263. // different buffers.
  264. //
  265. {
  266. PNDIS_BUFFER pBuf = (PNDIS_BUFFER)pData;
  267. ULONG bytesDone = 0;
  268. ULONG bytesLeft;
  269. PUCHAR pChunk;
  270. while (pBuf) {
  271. IPSecQueryNdisBuf(pBuf, &pChunk, &bytesLeft);
  272. pChunk += bytesDone;
  273. bytesLeft -= bytesDone;
  274. IPSEC_DEBUG(ESP, ("ESP: pChunk: %lx, bytesLeft: %d, bytesDone: %d\n", pChunk, bytesLeft, bytesDone));
  275. bytesDone = 0;
  276. while (bytesLeft >= blockLen) {
  277. //
  278. // Create the cipher.
  279. //
  280. pConfAlgo->encrypt( (PVOID)&Key,
  281. pDest,
  282. pChunk,
  283. feedback);
  284. pChunk += blockLen;
  285. bytesLeft -= blockLen;
  286. pDest += blockLen;
  287. }
  288. //
  289. // Check here if we need to collate blocks
  290. //
  291. if (NDIS_BUFFER_LINKAGE(pBuf) != NULL) {
  292. PUCHAR pNextChunk;
  293. ULONG nextSize;
  294. //
  295. // If some left over from prev. buffer, collate with next
  296. // block
  297. //
  298. if (bytesLeft) {
  299. ULONG offset = bytesLeft; // offset into scratch
  300. ULONG bytesToCollect = blockLen - bytesLeft; // # of bytes to collect from next few MDLs
  301. IPSEC_DEBUG(ESP, ("ESP: pChunk: %lx, bytesLeft: %d\n", pChunk, bytesLeft));
  302. ASSERT(bytesLeft < blockLen);
  303. //
  304. // Copy into a scratch buffer
  305. //
  306. RtlCopyMemory( scratch,
  307. pChunk,
  308. bytesLeft);
  309. do {
  310. ASSERT(NDIS_BUFFER_LINKAGE(pBuf));
  311. IPSecQueryNdisBuf(NDIS_BUFFER_LINKAGE(pBuf), &pNextChunk, &nextSize);
  312. if (nextSize >= (blockLen - offset)) {
  313. RtlCopyMemory( scratch+offset,
  314. pNextChunk,
  315. blockLen - offset);
  316. bytesDone = blockLen - offset;
  317. bytesToCollect -= (blockLen - offset);
  318. ASSERT(bytesToCollect == 0);
  319. } else {
  320. IPSEC_DEBUG(ESP, ("special case, offset: %d, bytesLeft: %d, nextSize: %d, pNextChunk: %lx\n",
  321. offset, bytesLeft, nextSize, pNextChunk));
  322. RtlCopyMemory( scratch+offset,
  323. pNextChunk,
  324. nextSize);
  325. bytesToCollect -= nextSize;
  326. ASSERT(bytesToCollect);
  327. offset += nextSize;
  328. ASSERT(offset < blockLen);
  329. ASSERT(bytesDone == 0);
  330. pBuf = NDIS_BUFFER_LINKAGE(pBuf);
  331. }
  332. } while (bytesToCollect);
  333. pConfAlgo->encrypt( (PVOID)&Key,
  334. pDest,
  335. scratch,
  336. feedback);
  337. pDest += blockLen;
  338. }
  339. } else {
  340. PUCHAR pPad;
  341. ULONG padLen;
  342. ULONG bufLen;
  343. //
  344. // End of the chain; pad with length and type to 8 byte boundary
  345. //
  346. ASSERT(bytesLeft < blockLen);
  347. // if ((pSA->sa_eOperation == HUGHES_TRANSPORT) ||
  348. // (pSA->sa_eOperation == HUGHES_TUNNEL)) {
  349. //
  350. // since only hughes is done now, this shd be always true.
  351. //
  352. if (TRUE) {
  353. ASSERT(bytesLeft == 0);
  354. //
  355. // DONE: break out
  356. //
  357. break;
  358. }
  359. }
  360. pBuf = NDIS_BUFFER_LINKAGE(pBuf);
  361. }
  362. //
  363. // save IV for next encrypt cycle
  364. //
  365. RtlCopyMemory( pSA->sa_iv[Index],
  366. feedback,
  367. pSA->sa_ivlen);
  368. IPSEC_DEBUG(HUGHES, ("IV: %lx-%lx\n", *(PULONG)&feedback[0], *(PULONG)&feedback[4]));
  369. }
  370. #if DBG
  371. {
  372. ULONG totalLen;
  373. IPSEC_GET_TOTAL_LEN(pEncryptMdl, &totalLen);
  374. ASSERT((totalLen % 8) == 0);
  375. IPSEC_DEBUG(ESP, ("total len: %lx\n", totalLen));
  376. }
  377. #endif
  378. IPSEC_DEBUG(ESP, ("Exiting IPSecEncryptBuffer\n"));
  379. *ppNewMdl = pEncryptMdl;
  380. return STATUS_SUCCESS;
  381. }
  382. NTSTATUS
  383. IPSecDecryptBuffer(
  384. IN PVOID pData,
  385. IN PSA_TABLE_ENTRY pSA,
  386. OUT PUCHAR pPadLen,
  387. OUT PUCHAR pPayloadType,
  388. IN ULONG Index
  389. )
  390. {
  391. CONF_STATE_BUFFER Key;
  392. PCONFID_ALGO pConfAlgo;
  393. UCHAR feedback[MAX_BLOCKLEN];
  394. UCHAR scratch[MAX_BLOCKLEN]; // scratch buffer for the encrypt
  395. UCHAR scratch1[MAX_BLOCKLEN]; // scratch buffer for the encrypt
  396. LONG Len;
  397. UCHAR padLen;
  398. UCHAR payloadType;
  399. LONG hdrLen;
  400. IPHeader UNALIGNED *pIPH;
  401. ESP UNALIGNED *pEsp;
  402. PUCHAR savePtr;
  403. LONG espLen = sizeof(ESP) + pSA->sa_ivlen + pSA->sa_ReplayLen;
  404. LONG blockLen;
  405. if (pSA->CONF_ALGO(Index) > NUM_CONF_ALGOS) {
  406. return STATUS_INVALID_PARAMETER;
  407. }
  408. pConfAlgo = &(conf_algorithms[pSA->CONF_ALGO(Index)]);
  409. blockLen = pConfAlgo->blocklen;
  410. //
  411. // set up the state buffer
  412. //
  413. pConfAlgo->init((PVOID)&Key, pSA->CONF_KEY(Index));
  414. IPSecQueryRcvBuf(pData, (PUCHAR)&pEsp, &Len);
  415. //
  416. // Init the CBC feedback from the IV in the packet
  417. //
  418. // Actually if the sa_ivlen is 0, use the pSA one
  419. //
  420. if (pSA->sa_ivlen) {
  421. RtlCopyMemory( feedback,
  422. ((PUCHAR)(pEsp + 1) + pSA->sa_ReplayLen),
  423. pSA->sa_ivlen);
  424. IPSEC_DEBUG(ESP, ("IV: %lx-%lx\n", *(PULONG)&feedback[0], *(PULONG)&feedback[4]));
  425. } else {
  426. RtlCopyMemory( feedback,
  427. pSA->sa_iv[Index],
  428. DES_BLOCKLEN);
  429. }
  430. //
  431. // Bump the current pointer to after the ESP header
  432. //
  433. ((IPRcvBuf *)pData)->ipr_size -= espLen;
  434. savePtr = ((IPRcvBuf *)pData)->ipr_buffer;
  435. ((IPRcvBuf *)pData)->ipr_buffer = savePtr + espLen;
  436. //
  437. // Now, send 64 bit (8 octet) chunks to CBC. We need to make sure
  438. // that the data is divided on contiguous 8 byte boundaries across
  439. // different buffers.
  440. // NOTE: the algo below assumes that there are a minimum of 8 bytes
  441. // per buffer in the chain.
  442. //
  443. {
  444. IPRcvBuf *pBuf = (IPRcvBuf *)pData;
  445. LONG bytesDone = 0;
  446. LONG bytesLeft;
  447. LONG saveBytesLeft;
  448. PUCHAR pChunk;
  449. PUCHAR pSaveChunk;
  450. while (pBuf) {
  451. if (IPSEC_BUFFER_LEN(pBuf) == 0) {
  452. pBuf = IPSEC_BUFFER_LINKAGE(pBuf);
  453. continue;
  454. }
  455. IPSecQueryRcvBuf(pBuf, &pSaveChunk, &saveBytesLeft);
  456. bytesLeft = saveBytesLeft - bytesDone;
  457. pChunk = pSaveChunk + bytesDone;
  458. IPSEC_DEBUG(ESP, ("ESP: 1.pChunk: %lx, bytesLeft: %d, bytesDone: %d\n", pChunk, bytesLeft, bytesDone));
  459. bytesDone = 0;
  460. while (bytesLeft >= blockLen) {
  461. //
  462. // Decrypt the cipher.
  463. //
  464. pConfAlgo->decrypt( (PVOID)&Key,
  465. pChunk,
  466. pChunk,
  467. feedback);
  468. pChunk += blockLen;
  469. bytesLeft -= blockLen;
  470. }
  471. IPSEC_DEBUG(ESP, ("ESP: 2.pChunk: %lx, bytesLeft: %d, bytesDone: %d\n", pChunk, bytesLeft, bytesDone));
  472. //
  473. // Check here if we need to collate blocks
  474. //
  475. if (IPSEC_BUFFER_LINKAGE(pBuf) != NULL) {
  476. PUCHAR pNextChunk;
  477. LONG nextSize;
  478. if (IPSEC_BUFFER_LEN(IPSEC_BUFFER_LINKAGE(pBuf)) == 0) {
  479. pBuf = IPSEC_BUFFER_LINKAGE(pBuf);
  480. }
  481. //
  482. // If some left over from prev. buffer, collate with next
  483. // block
  484. //
  485. if (bytesLeft) {
  486. LONG offset = bytesLeft;
  487. IPSEC_DEBUG(ESP, ("ESP: 3.pChunk: %lx, bytesLeft: %d, bytesDone: %d\n", pChunk, bytesLeft, bytesDone));
  488. ASSERT(bytesLeft < blockLen);
  489. //
  490. // Copy into a scratch buffer
  491. //
  492. RtlCopyMemory( scratch,
  493. pChunk,
  494. bytesLeft);
  495. IPSecQueryRcvBuf(IPSEC_BUFFER_LINKAGE(pBuf), &pNextChunk, &nextSize);
  496. if (nextSize >= (blockLen - bytesLeft)) {
  497. //
  498. // Copy remaining bytes into scratch
  499. //
  500. RtlCopyMemory( scratch+bytesLeft,
  501. pNextChunk,
  502. blockLen - bytesLeft);
  503. pConfAlgo->decrypt( (PVOID)&Key,
  504. scratch,
  505. scratch,
  506. feedback);
  507. //
  508. // Copy cipher back into the payload
  509. //
  510. RtlCopyMemory( pChunk,
  511. scratch,
  512. bytesLeft);
  513. RtlCopyMemory( pNextChunk,
  514. scratch+bytesLeft,
  515. blockLen - bytesLeft);
  516. bytesDone = blockLen - bytesLeft;
  517. } else {
  518. //
  519. // Ugh! Collect the remaining bytes from the chain and redistribute them
  520. // after the decryption.
  521. //
  522. LONG bytesToCollect = blockLen - bytesLeft; // # of bytes to collect from next few MDLs
  523. IPRcvBuf *pFirstBuf = IPSEC_BUFFER_LINKAGE(pBuf); // to know where to start the distribution post decryption
  524. do {
  525. ASSERT(IPSEC_BUFFER_LINKAGE(pBuf));
  526. IPSecQueryRcvBuf(IPSEC_BUFFER_LINKAGE(pBuf), &pNextChunk, &nextSize);
  527. if (nextSize >= (blockLen - offset)) {
  528. RtlCopyMemory( scratch+offset,
  529. pNextChunk,
  530. blockLen - offset);
  531. bytesDone = blockLen - offset;
  532. bytesToCollect -= (blockLen - offset);
  533. ASSERT(bytesToCollect == 0);
  534. } else {
  535. IPSEC_DEBUG(ESP, ("special case, offset: %d, bytesLeft: %d, nextSize: %d, pNextChunk: %lx\n",
  536. offset, bytesLeft, nextSize, pNextChunk));
  537. RtlCopyMemory( scratch+offset,
  538. pNextChunk,
  539. nextSize);
  540. bytesToCollect -= nextSize;
  541. ASSERT(bytesToCollect);
  542. offset += nextSize;
  543. ASSERT(offset < blockLen);
  544. ASSERT(bytesDone == 0);
  545. pBuf = IPSEC_BUFFER_LINKAGE(pBuf);
  546. }
  547. } while (bytesToCollect);
  548. pConfAlgo->decrypt( (PVOID)&Key,
  549. scratch,
  550. scratch,
  551. feedback);
  552. //
  553. // Now distribute the bytes back to the MDLs
  554. //
  555. RtlCopyMemory( pChunk,
  556. scratch,
  557. bytesLeft);
  558. pBuf = CopyToRcvBuf(pFirstBuf,
  559. scratch+bytesLeft,
  560. blockLen - bytesLeft,
  561. &bytesDone);
  562. continue;
  563. }
  564. }
  565. } else {
  566. //
  567. // end of chain.
  568. // should never come here with bytes left over since the
  569. // sender should pad to 8 byte boundary.
  570. //
  571. ASSERT(bytesLeft == 0);
  572. IPSEC_DEBUG(ESP, ("ESP: 4.pChunk: %lx, saveBytesLeft: %d, bytesDone: %d\n", pChunk, saveBytesLeft, bytesDone));
  573. IPSEC_DEBUG(ESP, ("ESP: HUGHES: will remove pad later\n"));
  574. break;
  575. }
  576. pBuf = (IPRcvBuf *)IPSEC_BUFFER_LINKAGE(pBuf);
  577. }
  578. }
  579. //
  580. // Restore the first MDL
  581. //
  582. ((IPRcvBuf *)pData)->ipr_size += espLen;
  583. ((IPRcvBuf *)pData)->ipr_buffer = savePtr;
  584. return STATUS_SUCCESS;
  585. }