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.

1486 lines
40 KiB

  1. /*++
  2. Copyright (c) 1999-2001 Microsoft Corporation
  3. Module Name:
  4. regmisc.c
  5. Abstract:
  6. This module implement some function used in the registry redirector.
  7. Author:
  8. ATM Shafiqul Khalid (askhalid) 29-Oct-1999
  9. Revision History:
  10. --*/
  11. #include <nt.h>
  12. #include <ntrtl.h>
  13. #include <nturtl.h>
  14. #include <windows.h>
  15. #include <stdio.h>
  16. #include <ntregapi.h>
  17. #include "regremap.h"
  18. #include "wow64reg.h"
  19. #include "wow64reg\reflectr.h"
  20. #ifdef _WOW64DLLAPI_
  21. #include "wow64.h"
  22. #else
  23. #define ERRORLOG 1 //this one is completely dummy
  24. #define LOGPRINT(x)
  25. #define WOWASSERT(p)
  26. #endif //_WOW64DLLAPI_
  27. #include "regremap.h"
  28. #include "wow64reg.h"
  29. ASSERTNAME;
  30. //#define LOG_REGISTRY
  31. const WCHAR IsnNodeListPath[]={WOW64_REGISTRY_SETUP_KEY_NAME};
  32. #define KEY_NAME(x) {x,((sizeof (x) / sizeof (WCHAR))-1)}
  33. typedef struct _REGKEY_LIST {
  34. WCHAR KeyPath[256];
  35. DWORD Len;
  36. } REGKEY_LIST;
  37. //
  38. // Table that will have the list of ISN node. Need to allocate runtime.
  39. //
  40. #define WOW64_ISN_NODE_MAX_NUM 12 // this is internal to wow64 setup might use different size of table
  41. NODETYPE IsnNode[WOW64_ISN_NODE_MAX_NUM]={
  42. {L"\\REGISTRY\\MACHINE\\SOFTWARE\\CLASSES"},
  43. {L"\\REGISTRY\\MACHINE\\SOFTWARE"},
  44. {L"\\REGISTRY\\USER\\*\\SOFTWARE\\CLASSES"}, // ISN node table is always upcase.
  45. {L"\\REGISTRY\\USER\\*_CLASSES"},
  46. {L"\\REGISTRY\\MACHINE\\SYSTEM\\TEST"},
  47. {L""}
  48. };
  49. //
  50. // 64bit IE load mail client dll inproc breaking interop functionality.
  51. // The are some Dll get loaded Inproc {L"\\REGISTRY\\MACHINE\\SOFTWARE\\Clients\\mail"}, //Email Client Key
  52. //
  53. // Must keep 32-bit and 64-bit uninstall keys separate to ensure the correct environment
  54. // variables are used for REG_EXPAND_SZ and to make sure we run the correct bitness of rundll32.exe.
  55. // {L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\UnInstall"}, // UnInstall Key
  56. //
  57. REGKEY_LIST ExemptRedirectedKey[]={
  58. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\SystemCertificates"), // Certificate Key
  59. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Cryptography\\Services"), // Cryptography Service
  60. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Classes\\HCP"), // HelpCenter Key
  61. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\EnterpriseCertificates"), // Enterprise Service
  62. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\MSMQ"), // MSMQ registry
  63. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"), // Profiles
  64. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib"), // Performance counters
  65. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Print"), // Spooler Printers
  66. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Ports"), // Spooler Ports
  67. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Policies"), // policie keys
  68. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy"), // policie keys
  69. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies"), //Policy Keys
  70. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup\\OC Manager"), //OC Manager Keys
  71. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Software\\Microsoft\\Shared Tools\\MSInfo"), //share MSinfo Key
  72. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Setup"), //Share setup Keys
  73. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\CTF\\TIP"), //CTF\TIP Key
  74. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\CTF\\SystemShared"),
  75. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"), //Share fonts
  76. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\RAS"), // RAS keys need to be shared
  77. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Driver Signing"), // Share Driver signing Keys
  78. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Non-Driver Signing"), // Share Driver signing Keys
  79. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Cryptography\\Calais\\Current"), // SmartCard subsytem pipe name
  80. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Cryptography\\Calais\\Readers"), // SmartCard installed readers
  81. KEY_NAME(L"\\REGISTRY\\MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones"), // Share time zone key
  82. KEY_NAME(L""), // Two additional NULL String for additional space.
  83. KEY_NAME(L"")
  84. };
  85. //
  86. // A note about PerfLib... in ntos\config, the init code creates a special
  87. // key called PerfLib\009 and if you call NtOpenKey on that path, it returns
  88. // back HKEY_PERFORMANCE_DATA, not a regular kernel registry handle to
  89. // \\REGISTRY\\MACHINE\\stuff. Instead, HKEY_PERFORMANCE_DATA is intercepted
  90. // in usermode by advapi32.dll. The Counters and Help REG_MULTI_SZ values
  91. // don't really exist - they are synthesized by advapi32 based on the
  92. // contents of the perf*.dat files in system32. This works OK for 32-bit
  93. // advapi32 on WOW64 as advapi opens the *.dat files using NtOpenFile
  94. // with an OBJECT_ATTRIBUTES containing "\SystemRoot\System32\..." which
  95. // doesn't get intercepted by the system32 remapper.
  96. //
  97. //
  98. // Don't let 32bit apps modify ControlSet Keys, exceptions defined by another set of keys.
  99. //
  100. REGKEY_LIST DenyKeyAccess[]={
  101. KEY_NAME(L"\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet*"),
  102. KEY_NAME(L"\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet"),
  103. KEY_NAME(L"")
  104. };
  105. //
  106. // Owner under services should be able to modify. i.e., if 32bit apps create some keys they can modify them.
  107. //
  108. REGKEY_LIST ExemptDenyKeyAccessOwner[]={
  109. KEY_NAME(L"\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet*\\Services"),
  110. KEY_NAME(L"\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet\\Services"),
  111. KEY_NAME(L"")
  112. };
  113. //
  114. // 32bit apps can do whatever they want. generally shareavle across 32bit/64bit apps.
  115. //
  116. REGKEY_LIST ExemptDenyKeyAccess[]={
  117. KEY_NAME(L"\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet*\\Services\\Control\\Session Manager"),
  118. KEY_NAME(L"\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Control\\Session Manager"),
  119. KEY_NAME(L"\\REGISTRY\\MACHINE\\SYSTEM\\ControlSet*\\Services\\EventLog"),
  120. KEY_NAME(L"\\REGISTRY\\MACHINE\\SYSTEM\\CurrentControlSet\\Services\\EventLog"),
  121. KEY_NAME(L"")
  122. };
  123. PWCHAR
  124. wcsistr(
  125. PWCHAR string1,
  126. PWCHAR string2
  127. )
  128. {
  129. PWCHAR p1;
  130. PWCHAR p2;
  131. if ((NULL == string2) || (NULL == string1))
  132. {
  133. // do whatever wcsstr would do
  134. return wcsstr(string1, string2);
  135. }
  136. while (*string1)
  137. {
  138. for (p1 = string1, p2 = string2;
  139. *p1 && *p2 && towlower(*p1) == towlower(*p2);
  140. ++p1, ++p2)
  141. {
  142. // nothing
  143. }
  144. if (!*p2)
  145. {
  146. // we found a match!
  147. return (PWCHAR)string1; // cast away const!
  148. }
  149. ++string1;
  150. }
  151. return NULL;
  152. }
  153. PWCHAR
  154. wcsstrWow6432Node (
  155. PWCHAR pSrc
  156. )
  157. {
  158. return wcsistr (pSrc, NODE_NAME_32BIT);
  159. }
  160. PWCHAR
  161. wcsstrWithWildCard (
  162. PWCHAR srcStr,
  163. PWCHAR destIsnNode
  164. )
  165. /*++
  166. Routine Description:
  167. a customised version of wcsstr with wild card support. For example the
  168. substring might have '*' character which can be matched with any key name.
  169. Arguments:
  170. srcStr - The string where the substring need to be searched for.
  171. destIsnNode - the string to search.
  172. Return Value:
  173. TRUE if the operation succeed, FALSE otherwise.
  174. --*/
  175. {
  176. //multiple wildcard isn't allowed?
  177. PWCHAR src = srcStr;
  178. PWCHAR dest = destIsnNode;
  179. PWCHAR p, t;
  180. DWORD count;
  181. for (;;) {
  182. if (*dest == UNICODE_NULL)
  183. return ( *src == UNICODE_NULL)? src : src+1; //source might point to SLASH
  184. if (*src == UNICODE_NULL)
  185. return NULL;
  186. count = wcslen (dest);
  187. if ( ( p = wcschr( dest,'*') ) == NULL ) {
  188. if ( _wcsnicmp (src, dest, count) == 0 ){
  189. //
  190. // xx\Test shouldn't show xx\test345 as an ISN node.
  191. //
  192. if ( src [ count ] != UNICODE_NULL && src [ count ] != L'\\' ) //terminator need tobe NULL or slash
  193. return NULL;
  194. return (*(src+count) != UNICODE_NULL ) ? src+count+1: src+count; // xx\test return pointer at test if dest is xx.
  195. }
  196. else
  197. return NULL;
  198. }
  199. count = (DWORD) (p-dest);
  200. // LOGPRINT( (ERRORLOG, "\nFinding [%S] withing %S, p=%S Val%d",dest, src, p, count ));
  201. if (_wcsnicmp (src, dest, count) !=0) // checking the initial state
  202. return NULL;
  203. //
  204. // need to check *_Classes type ISN Node
  205. //
  206. p++; //skip the wild card
  207. t=src+count;
  208. while ( *t != L'\\' && *t != UNICODE_NULL )
  209. t++;
  210. for ( count=0;*p != L'\\' && *p != UNICODE_NULL; p++, count++)
  211. ;
  212. if (_wcsnicmp (p-count, t-count, count) != 0)
  213. return NULL;
  214. // LOGPRINT( (ERRORLOG, "\nFinding 2nd[%S] withing %S, p=%S",dest, src, p ));
  215. src = t;
  216. dest = p;
  217. }
  218. return NULL;
  219. }
  220. HKEY
  221. OpenNode (
  222. PWCHAR NodeName
  223. )
  224. /*++
  225. Routine Description:
  226. Open a given key for generic access.
  227. Arguments:
  228. NodeName - name of the key to check.
  229. Return Value:
  230. NULL in case of failure.
  231. Valid handle otherwise.
  232. --*/
  233. {
  234. NTSTATUS st;
  235. HKEY hKey;
  236. OBJECT_ATTRIBUTES Obja;
  237. UNICODE_STRING KeyName;
  238. RtlInitUnicodeString (&KeyName, NodeName);
  239. InitializeObjectAttributes (&Obja, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL );
  240. st = NtOpenKey (&hKey, KEY_ALL_ACCESS, &Obja);
  241. if (!NT_SUCCESS(st))
  242. return NULL;
  243. return hKey;
  244. }
  245. VOID
  246. CloseNode (
  247. HANDLE Key
  248. )
  249. {
  250. NtClose (Key);
  251. }
  252. NTSTATUS
  253. IsNodeExist (
  254. PWCHAR NodeName
  255. )
  256. /*++
  257. Routine Description:
  258. Check if the given key exist if not create the key.
  259. Arguments:
  260. NodeName - name of the key to check.
  261. Return Value:
  262. TRUE if the operation succeed, FALSE otherwise.
  263. --*/
  264. {
  265. NTSTATUS st;
  266. HANDLE hKey;
  267. OBJECT_ATTRIBUTES Obja;
  268. UNICODE_STRING KeyName;
  269. RtlInitUnicodeString (&KeyName, NodeName);
  270. InitializeObjectAttributes (&Obja, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL );
  271. st = NtOpenKey (&hKey, KEY_READ, &Obja);
  272. if (!NT_SUCCESS(st))
  273. return st;
  274. NtClose (hKey);
  275. //LOGPRINT( (ERRORLOG, "\nValid IsnNode [%S]",NodeName ));
  276. return st;
  277. }
  278. BOOL
  279. CreateNode (
  280. PWCHAR Path
  281. )
  282. /*++
  283. Routine Description:
  284. Create all the node along the path if missing. Called by background
  285. thread working on the setup.
  286. Arguments:
  287. Path - name of path to the key.
  288. Return Value:
  289. TRUE if the operation succeed, FALSE otherwise.
  290. --*/
  291. {
  292. //
  293. // isolate individual nodes and backtrack
  294. //
  295. NTSTATUS st;
  296. HANDLE hKey;
  297. HANDLE hKeyCreate;
  298. OBJECT_ATTRIBUTES Obja;
  299. UNICODE_STRING KeyName;
  300. PWCHAR pTrace;
  301. PWCHAR p;
  302. pTrace = Path+wcslen (Path); //pTrace point at the end of path
  303. p=pTrace;
  304. for (;;) {
  305. RtlInitUnicodeString (&KeyName, Path);
  306. InitializeObjectAttributes (&Obja, &KeyName, OBJ_CASE_INSENSITIVE, NULL, NULL );
  307. st = NtOpenKey (&hKey, KEY_WRITE | KEY_READ, &Obja);
  308. if ( st == STATUS_OBJECT_NAME_NOT_FOUND ) {
  309. //backtrack until you hit the line
  310. while ( *p != L'\\' && p!= Path)
  311. p--;
  312. //LOGPRINT( (ERRORLOG, "\nTest Code[%S]",p ));
  313. if ( p == Path ) break;
  314. *p = UNICODE_NULL;
  315. continue;
  316. }
  317. break;
  318. }
  319. if (!NT_SUCCESS(st)) {
  320. //fixup the string and return
  321. for ( ;p != pTrace;p++ )
  322. if ( *p == UNICODE_NULL) *p=L'\\';
  323. return FALSE;
  324. }
  325. //
  326. // now create key from point p until p hit pTrace
  327. //
  328. while ( p != pTrace ) {
  329. *p = L'\\'; //added the char back
  330. p++; //p will point a non NULL string
  331. RtlInitUnicodeString (&KeyName, p);
  332. InitializeObjectAttributes (&Obja, &KeyName, OBJ_CASE_INSENSITIVE, hKey, NULL );
  333. st = NtCreateKey(
  334. &hKeyCreate,
  335. KEY_WRITE | KEY_READ,
  336. &Obja,
  337. 0,
  338. NULL ,
  339. REG_OPTION_NON_VOLATILE,
  340. NULL
  341. );
  342. if (!NT_SUCCESS(st)) {
  343. LOGPRINT( (ERRORLOG, "\nCouldn't create Key named[%S]",p ));
  344. break;
  345. }
  346. NtClose (hKey);
  347. hKey = hKeyCreate;
  348. while ( *p != UNICODE_NULL ) p++;
  349. }
  350. NtClose (hKey);
  351. if (!NT_SUCCESS(st)) {
  352. for ( ;p != pTrace;p++ )
  353. if ( *p == UNICODE_NULL) *p=L'\\';
  354. return FALSE;
  355. }
  356. return TRUE;
  357. }
  358. BOOL
  359. CheckAndCreateNode (
  360. IN PWCHAR Name
  361. )
  362. /*++
  363. Routine Description:
  364. Check if the given key exist if not create the key. called by background
  365. thread working on the setup.
  366. Arguments:
  367. Name - name of the key to check.
  368. Return Value:
  369. TRUE if the operation succeed, FALSE otherwise.
  370. --*/
  371. {
  372. ISN_NODE_TYPE Node;
  373. PWCHAR p;
  374. //
  375. // if parent doesn't exist you shouldn't create the child
  376. //
  377. if (!NT_SUCCESS(IsNodeExist (Name)) ) {
  378. p = wcsstrWow6432Node (Name);
  379. if ( p != NULL ) {
  380. wcsncpy (Node.NodeValue, Name, p-Name-1);
  381. Node.NodeValue[p-Name-1] = UNICODE_NULL;
  382. }
  383. else
  384. return FALSE;
  385. if (NT_SUCCESS(IsNodeExist (Node.NodeValue)) )
  386. return CreateNode (Name);
  387. }
  388. return TRUE;
  389. }
  390. //
  391. // Opaque field might contain some information about the key on the 32bit side.
  392. //
  393. BOOL
  394. IsIsnNode (
  395. PWCHAR wStr,
  396. PWCHAR *pwStrIsn
  397. )
  398. /*++
  399. Routine Description:
  400. Will determine if the given path has any ISN node.
  401. Arguments:
  402. wStr - string to that might contain some ISN node.
  403. pwStrDest - point to the node after ISN node.
  404. Return Value:
  405. TRUE if the string has any ISN node, FALSE otherwise
  406. --*/
  407. {
  408. int Index=0;
  409. //
  410. // Check if the provided string is already on the 32 bit tree, if so we can
  411. // just ignore that
  412. //
  413. //
  414. // check if input string has any known symbolic link like \registry\user\sid_Classes that need to remap to a different location
  415. //
  416. for (;;) {
  417. if ( IsnNode [Index][0]==UNICODE_NULL ) break;
  418. if ( (*pwStrIsn = wcsstrWithWildCard (wStr, IsnNode[Index] ) ) != NULL )
  419. return TRUE;
  420. Index++;
  421. };
  422. *pwStrIsn = NULL;
  423. return FALSE;
  424. }
  425. NTSTATUS
  426. ObjectAttributesToKeyName (
  427. POBJECT_ATTRIBUTES ObjectAttributes,
  428. PWCHAR AbsPath,
  429. DWORD AbsPathLenIn,
  430. BOOL *bPatched,
  431. DWORD *ParentLen
  432. )
  433. /*++
  434. Routine Description:
  435. Determine the text equivalent for key handle
  436. Arguments:
  437. ObjectAttributes define the object attribute Keyname need to be constracted.
  438. AbsPath Unicode string to receive the Name of the key.
  439. bPatched - TRUE if the Name has been compressed/expanded that
  440. the original object can't refer. Caller need to construct
  441. a new obj attribute.
  442. unchanged otherwise.
  443. ParentLen - Length of the parent name.
  444. Return Value:
  445. NTSTATUS
  446. --*/
  447. {
  448. NTSTATUS Status;
  449. ULONG Length;
  450. ULONG AbsPathLen = 0;
  451. BYTE *pAbsPath = (PBYTE)AbsPath;
  452. POBJECT_NAME_INFORMATION ObjectName = (POBJECT_NAME_INFORMATION)AbsPath; //Smartly use user buffer
  453. if (ParentLen)
  454. *ParentLen = 0;
  455. if (ObjectAttributes == NULL)
  456. return STATUS_INVALID_PARAMETER;
  457. if (ObjectAttributes->RootDirectory) {
  458. Status = NtQueryObject(ObjectAttributes->RootDirectory,
  459. ObjectNameInformation,
  460. ObjectName,
  461. AbsPathLenIn,
  462. &Length
  463. );
  464. if ( !NT_SUCCESS(Status) )
  465. return Status;
  466. } else {
  467. AbsPathLen = ObjectAttributes->ObjectName->Length;
  468. if (AbsPathLenIn <= AbsPathLen)
  469. return STATUS_BUFFER_OVERFLOW;
  470. memcpy ( pAbsPath, (PBYTE)ObjectAttributes->ObjectName->Buffer, AbsPathLen );
  471. *(WCHAR *)(pAbsPath+AbsPathLen) = UNICODE_NULL;
  472. if (ParentLen)
  473. *ParentLen = AbsPathLen; // length of the parent handle
  474. return STATUS_SUCCESS;
  475. }
  476. //
  477. // copy the root and sub path
  478. //
  479. AbsPathLen = ObjectName->Name.Length;
  480. memcpy ( pAbsPath, (PBYTE)ObjectName->Name.Buffer, AbsPathLen);
  481. if ( ObjectAttributes->ObjectName->Length > 1 ) { // Valid object name need to be greater
  482. *(WCHAR *)(pAbsPath+AbsPathLen) = L'\\';
  483. AbsPathLen += sizeof ( L'\\');
  484. if (AbsPathLenIn <= (AbsPathLen+ObjectAttributes->ObjectName->Length))
  485. return STATUS_BUFFER_OVERFLOW;
  486. memcpy (
  487. pAbsPath+AbsPathLen,
  488. ObjectAttributes->ObjectName->Buffer,
  489. ObjectAttributes->ObjectName->Length
  490. );
  491. AbsPathLen += ObjectAttributes->ObjectName->Length;
  492. }
  493. *(WCHAR *)(pAbsPath+AbsPathLen) = UNICODE_NULL;
  494. //
  495. // Compress the path in case multiple wow6432node exist
  496. //
  497. for (;;) {
  498. PWCHAR p, t;
  499. if ( (p=wcsstrWow6432Node (AbsPath)) != NULL ) {
  500. if ( (t=wcsstrWow6432Node(p+1)) != NULL) {
  501. wcscpy (p,t);
  502. *bPatched = TRUE;
  503. }
  504. else break;
  505. } else break;
  506. }
  507. return STATUS_SUCCESS;
  508. }
  509. BOOL
  510. HandleToKeyName (
  511. HANDLE Key,
  512. PWCHAR KeyName,
  513. DWORD * dwLen
  514. )
  515. /*++
  516. Routine Description:
  517. Determine the text equivalent for key handle
  518. Arguments:
  519. Key - is key handle for which to obtain its text
  520. KeyName - Unicode string to receive the Name of the key.
  521. dwLen - Length of the buffer pointed by KeyName. (Number of unicode char)
  522. Return Value:
  523. TRUE if the handle text is fetched OK. FALSE if not (ie. error or
  524. Key is an illegal handle, etc.)
  525. --*/
  526. {
  527. NTSTATUS Status;
  528. ULONG Length;
  529. DWORD NameLen;
  530. POBJECT_NAME_INFORMATION ObjectName;
  531. ObjectName = (POBJECT_NAME_INFORMATION)KeyName; //use the user buffer to make the call to save space on stack.
  532. KeyName[0]= UNICODE_NULL;
  533. if (Key == NULL) {
  534. KeyName[0]= UNICODE_NULL;
  535. return FALSE;
  536. }
  537. Status = NtQueryObject(Key,
  538. ObjectNameInformation,
  539. ObjectName,
  540. *dwLen-8,
  541. &Length
  542. );
  543. NameLen = ObjectName->Name.Length/sizeof(WCHAR);
  544. if (!NT_SUCCESS(Status) || !Length || Length >= (*dwLen-8)) {
  545. DbgPrint ("HandleToKeyName: NtQuery Object failed St:%x, Handle: %x\n", Status, Key);
  546. KeyName[0]= UNICODE_NULL;
  547. return FALSE;
  548. }
  549. //
  550. // buffer overflow condition check
  551. //
  552. if (*dwLen < ( NameLen + 8+ 2) ) {
  553. *dwLen = 2 + NameLen + 8;
  554. DbgPrint ("HandleToKeyName: Buffer over flow.\n");
  555. KeyName[0]= UNICODE_NULL;
  556. return FALSE; //buffer overflow
  557. }
  558. wcsncpy(KeyName, ObjectName->Name.Buffer, NameLen);
  559. KeyName[NameLen]=UNICODE_NULL;
  560. return TRUE;
  561. }
  562. BOOL
  563. Map32bitTo64bitKeyName (
  564. IN PWCHAR Name32Key,
  565. OUT PWCHAR Name64Key
  566. )
  567. /*++
  568. Routine Description:
  569. Return a key name valid in the 64-bit registry side. It's the caller responsibility
  570. to give enough space in the output buffer. Its internal routine and no boundary
  571. checking is done here.
  572. Arguments:
  573. Name32Key - Input 32bit/64 bit Key name.
  574. Name64Key - Receiving Buffer that will hold the equivalent 64bit Key.
  575. Return Value:
  576. TRUE if the remapping become successful.
  577. FALSE otherwise.
  578. --*/
  579. {
  580. //
  581. // just remove 32bit related patch from the name if anything like that exist.
  582. // If the key is already on the 64bit side don't bother return the whole copy.
  583. //
  584. PWCHAR NodeName32Bit;
  585. DWORD Count;
  586. try {
  587. if ( ( NodeName32Bit = wcsstrWow6432Node (Name32Key)) == NULL) { // nothing to remap
  588. wcscpy (Name64Key, Name32Key);
  589. return TRUE;
  590. }
  591. Count = (DWORD)(NodeName32Bit - Name32Key);
  592. wcsncpy (Name64Key, Name32Key, Count-1);
  593. Name64Key[Count-1]=UNICODE_NULL;
  594. if (NodeName32Bit[NODE_NAME_32BIT_LEN] == L'\\')
  595. wcscpy (
  596. Name64Key + Count-1,
  597. NodeName32Bit + NODE_NAME_32BIT_LEN); //One if to skip the char'/'
  598. } except( NULL, EXCEPTION_EXECUTE_HANDLER){
  599. return FALSE;
  600. }
  601. return TRUE; //any complete path can have only one instance of NODE_NAME_32BIT
  602. }
  603. BOOL
  604. IsAccessDeniedOnKey (
  605. IN PWCHAR SrcKey,
  606. DWORD *FilteredAccess,
  607. BOOL bCreateCall
  608. )
  609. /*++
  610. Routine Description:
  611. Check if access should be denied on the key.
  612. Arguments:
  613. SrcKey - Input 32bit/64 bit Key name.
  614. *FilteredAccess -
  615. bCreateCall TRUE indicates the call path is on Key Creation.
  616. FALSE means simply OpenCall.
  617. Return Value:
  618. TRUE if given access should be filtered.
  619. FALSE otherwise.
  620. --*/
  621. {
  622. //
  623. // Make 64bit only path
  624. //
  625. DWORD dwIndex;
  626. BOOL bKeyExists = FALSE;
  627. DWORD dwAttribute = 0;
  628. HKEY hKeyTemp;
  629. extern BOOL bEnableCurrentControlSetProtection;
  630. if ( (*FilteredAccess & KEY_WOW64_64KEY) || (!bEnableCurrentControlSetProtection) )
  631. return FALSE; //shouldn't block this call in any case
  632. if ( (!bCreateCall) && !(*FilteredAccess & ~(KEY_READ) ))
  633. return FALSE; //benign OpenCall without update access
  634. for ( dwIndex = 0; ExemptDenyKeyAccess[dwIndex].KeyPath[0] != UNICODE_NULL; dwIndex++ ) {
  635. if (wcsstrWithWildCard (SrcKey, ExemptDenyKeyAccess[dwIndex].KeyPath) != NULL) {
  636. //
  637. // allow access always. ALL access are gauranteed.
  638. //
  639. return FALSE;
  640. }
  641. }
  642. //
  643. // Check owner based access.
  644. //
  645. for ( dwIndex = 0; ExemptDenyKeyAccessOwner[dwIndex].KeyPath[0] != UNICODE_NULL; dwIndex++ ) {
  646. if (wcsstrWithWildCard (SrcKey, ExemptDenyKeyAccessOwner[dwIndex].KeyPath) != NULL) {
  647. hKeyTemp = OpenNode (SrcKey);
  648. if ( NULL != hKeyTemp ) {
  649. bKeyExists = TRUE;
  650. QueryKeyTag ( hKeyTemp, &dwAttribute );
  651. NtClose (hKeyTemp);
  652. }
  653. //
  654. // allow access always. Only Owner get full access to the key it owns, otherwise filtered access.
  655. //
  656. // Open call - should be filter on unowned key,
  657. // CreateCall - new key creation should proceede unfiltered.
  658. //
  659. if ( bKeyExists && !(dwAttribute & TAG_KEY_ATTRIBUTE_32BIT_WRITE) ) {
  660. *FilteredAccess = KEY_READ;
  661. }
  662. //DbgPrint ("Wow64-ControlSet:filtering access:[%S][Call:%x, Exist:%x, Attrib:%x]\n", SrcKey, bCreateCall, bKeyExists, dwAttribute);
  663. return FALSE; //call shouldn't be blocked
  664. }
  665. } //for-loop
  666. for ( dwIndex = 0; DenyKeyAccess[dwIndex].KeyPath[0] != UNICODE_NULL; dwIndex++ )
  667. if (wcsstrWithWildCard (SrcKey, DenyKeyAccess[dwIndex].KeyPath) != NULL) {
  668. hKeyTemp = OpenNode (SrcKey);
  669. if ( NULL != hKeyTemp ) {
  670. bKeyExists = TRUE;
  671. NtClose (hKeyTemp);
  672. }
  673. //
  674. // Yes, update access need to be denied.
  675. //
  676. // Open Call - should succeed here with filtered access
  677. // Create Call - Must fail
  678. //
  679. //*FilteredAccess &= (~KEY_WRITE); //filter access for all case
  680. *FilteredAccess = KEY_READ;
  681. if (bCreateCall && !bKeyExists) {
  682. //DbgPrint ("Wow64-ControlSet:dening access to Registry Key: %S[Call:%x, Exist:%x]\n", SrcKey, bCreateCall, bKeyExists);
  683. return TRUE; // you can't allow a new key creation here.
  684. }
  685. //DbgPrint ("Wow64-ControlSet:Allowing ReadOnly access:[%S][Call:%x, Exist:%x]\n", SrcKey, bCreateCall, bKeyExists);
  686. return FALSE;
  687. }
  688. return FALSE;
  689. }
  690. BOOL
  691. AdvapiAccessDenied (
  692. HKEY hKey,
  693. const WCHAR * lpSubKey,
  694. PWCHAR ParentName,
  695. DWORD dwLen,
  696. DWORD *pAccessMask,
  697. BOOL bCreateCall
  698. )
  699. /*++
  700. Routine Description:
  701. This API dedermine if the target call with given parameter should fail or
  702. Access need to be filtered.
  703. Arguments:
  704. hKey - Input 32bit/64 bit Key name.
  705. lpSubKeyName - pointer to the subkey name.
  706. ParentName - Buffer caller supply to retrieve parent name.
  707. dwLen -Length of the buffer.
  708. pAccessMask -this poin to the current access mask.
  709. On return this might return reasonable accessmask.
  710. Flag - 1 - means caller will perform a open call.
  711. 2 - means caller will perform a Create call.
  712. Return Value:
  713. TRUE if given access should be denied on the key.
  714. FALSE otherwise.
  715. --*/
  716. {
  717. BOOL bDenyAccess;
  718. bDenyAccess = IsAccessDeniedOnKey (ParentName, pAccessMask, bCreateCall);
  719. if (bDenyAccess) {
  720. //DbgPrint ("ADVAPI-Wow64:dening access to Registry Key: %S\n", ParentName);
  721. return TRUE;
  722. }
  723. return FALSE;
  724. }
  725. BOOL
  726. IsExemptRedirectedKey (
  727. IN PWCHAR SrcKey,
  728. OUT PWCHAR DestKey
  729. )
  730. /*++
  731. Routine Description:
  732. Check if the the source key point to the list of exempt key from redirection.
  733. If so DestKey will have the right value.
  734. Arguments:
  735. Name64Key - Input 32bit/64 bit Key name.
  736. Name32Key - Receiving Buffer that will hold the equivalent 32bit Key.
  737. Return Value:
  738. TRUE if the Key is on the list of exempt key from redirection.
  739. FALSE otherwise.
  740. --*/
  741. {
  742. //
  743. // Make 64bit only path
  744. //
  745. PWCHAR NodeName32Bit;
  746. DWORD dwIndex =0;
  747. wcscpy (DestKey, SrcKey);
  748. if ( ( NodeName32Bit = wcsstrWow6432Node (DestKey)) != NULL) { // nothing to remap patch is already there
  749. NodeName32Bit--;
  750. wcscpy (NodeName32Bit, NodeName32Bit+sizeof (NODE_NAME_32BIT)/sizeof (WCHAR));
  751. }
  752. for ( dwIndex = 0; ExemptRedirectedKey[dwIndex].KeyPath[0] != UNICODE_NULL; dwIndex++ )
  753. if (_wcsnicmp (DestKey, ExemptRedirectedKey[dwIndex].KeyPath, ExemptRedirectedKey[dwIndex].Len ) == 0)
  754. return TRUE;
  755. return FALSE;
  756. }
  757. BOOL
  758. Map64bitTo32bitKeyName (
  759. IN PWCHAR Name64Key,
  760. OUT PWCHAR Name32Key
  761. )
  762. /*++
  763. Routine Description:
  764. Return a key name valid in the 32-bit registry side. It's the caller responsibility
  765. to give enough space in the output buffer. Its internal routine and no boundary
  766. checking is done here.
  767. Arguments:
  768. Name64Key - Input 32bit/64 bit Key name.
  769. Name32Key - Receiving Buffer that will hold the equivalent 32bit Key.
  770. Return Value:
  771. TRUE if the remapping become successful.
  772. FALSE otherwise.
  773. --*/
  774. {
  775. //
  776. // just add 32bit related patch from the name if anything like that exist.
  777. // or fall under the ISN nodes.
  778. //
  779. PWCHAR NodeName32Bit;
  780. DWORD Count;
  781. try {
  782. if (IsExemptRedirectedKey (Name64Key, Name32Key) )
  783. return TRUE;
  784. if ( ( NodeName32Bit = wcsstrWow6432Node (Name64Key)) != NULL) { // nothing to remap patch is already there
  785. wcscpy (Name32Key, Name64Key);
  786. return TRUE;
  787. }
  788. if (!IsIsnNode ( Name64Key, &NodeName32Bit)) {
  789. wcscpy (Name32Key, Name64Key);
  790. return TRUE;
  791. }
  792. Count = (DWORD)(NodeName32Bit - Name64Key); // Displacement offset where the patch shoud go.
  793. //
  794. // consider the case when 32bit apps need to create/open the real ISN node which doesn't exist
  795. //
  796. wcsncpy (Name32Key,Name64Key, Count);
  797. if (Name32Key[Count-1] != L'\\') {
  798. Name32Key[Count] = L'\\';
  799. Count++;
  800. }
  801. wcscpy (Name32Key+Count, NODE_NAME_32BIT);
  802. if ( *NodeName32Bit != UNICODE_NULL ) {
  803. wcscat (Name32Key, L"\\");
  804. wcscat (Name32Key, NodeName32Bit);
  805. }
  806. } except( NULL, EXCEPTION_EXECUTE_HANDLER){
  807. return FALSE;
  808. }
  809. return TRUE; //any complete path can have only one instance of NODE_NAME_32BIT
  810. }
  811. NTSTATUS
  812. OpenIsnNodeByObjectAttributes (
  813. POBJECT_ATTRIBUTES ObjectAttributes,
  814. ACCESS_MASK DesiredAccess,
  815. PHANDLE phPatchedHandle
  816. )
  817. /*++
  818. Routine Description:
  819. If this Keyhandle is an open handle to an ISN node then this function
  820. return a handle to the node on the 32 bit tree. If not then we create the whole
  821. path and see if any ISN node is there. If so we Get the path on the 32bit tree and
  822. return Open that key.
  823. Scenario:
  824. 1. Absolute path made from Directory root and relative path don't contain any ISN node.
  825. -Open that normally.
  826. 2. Directory Handle point to the immediate parent of ISN node and the relative path is
  827. just an ISN node.
  828. -if 32 bit equivalent of ISN node exist open that and return that. If the 32 bit node
  829. doesn't exist create one and return that. [Problem open Directory Handle might not
  830. have create access.
  831. 3 Directory Handle point to an ISN node and relative path is just an immediate chield.
  832. - This can never happen. If we follow the algorithm, directory handly can't point on
  833. to an ISN node but on 32 bit equivalent node.
  834. 4. Same as 2 but relative path might be grand child or far bellow.
  835. - If 32 bit equivalent node isn't there just create that and open the rest.
  836. How 32 bit Apps can open an ISN node:
  837. <TBD> the proposal is a s follows:
  838. 1. Redirector will maintain a list of exempt handle that were created to access ISN node.
  839. 2. Any open call relative to those handle will also be on the exemped list.
  840. 3. NtClose thunk will remove
  841. Arguments:
  842. KeyHandle - Handle to the node on the 64 bit tree.
  843. phPatchedHandle - receive the appropriate handle if this function succeed.
  844. Return Value:
  845. NTSTATUS;
  846. --*/
  847. {
  848. UNICODE_STRING Parent;
  849. NTSTATUS st;
  850. OBJECT_ATTRIBUTES Obja;
  851. WCHAR PatchedIsnNode[WOW64_MAX_PATH+256];
  852. WCHAR AbsPath[WOW64_MAX_PATH+256];
  853. BOOL bPatched;
  854. DWORD ParentLen;
  855. //
  856. // Make the complete path in a AbsPath
  857. //
  858. *phPatchedHandle=NULL;
  859. st = ObjectAttributesToKeyName (
  860. ObjectAttributes,
  861. AbsPath,
  862. sizeof (AbsPath) - 30,
  863. &bPatched,
  864. &ParentLen );
  865. if (!NT_SUCCESS(st)) {
  866. LOGPRINT( (ERRORLOG, "Wow64:Couldn't retrieve ObjectName\n"));
  867. return st;
  868. }
  869. if (DesiredAccess & KEY_WOW64_64KEY) {
  870. if (!Map32bitTo64bitKeyName ( AbsPath, PatchedIsnNode ))
  871. return -1; //severe problem shouldn't happen
  872. } else {
  873. PWCHAR p;
  874. if (!Map64bitTo32bitKeyName ( AbsPath, PatchedIsnNode ))
  875. return -1; //severe problem shouldn't happen
  876. }
  877. DesiredAccess = DesiredAccess & (~KEY_WOW64_RES);
  878. //
  879. // no change can be optimize by returning different value from Map64bitTo32bitKeyName
  880. // Caller need to handle this
  881. //
  882. //
  883. // Check if access mask need to be filtered.
  884. //
  885. IsAccessDeniedOnKey (
  886. PatchedIsnNode,
  887. &DesiredAccess,
  888. FALSE
  889. );
  890. RtlInitUnicodeString (&Parent, PatchedIsnNode);
  891. InitializeObjectAttributes (&Obja, &Parent, ObjectAttributes->Attributes, NULL, ObjectAttributes->SecurityDescriptor ); //you have to use caller's context
  892. st = NtOpenKey (phPatchedHandle, DesiredAccess, &Obja);
  893. #ifdef WOW64_LOG_REGISTRY
  894. if (!NT_SUCCESS (st))
  895. Wow64RegDbgPrint (( "RemapNtOpenKeyEx OUT: couldn't open %S\n", PatchedIsnNode));
  896. #endif
  897. return st;
  898. }
  899. NTSTATUS
  900. RemapNtCreateKey(
  901. OUT PHANDLE phPatchedHandle,
  902. IN ACCESS_MASK DesiredAccess,
  903. IN POBJECT_ATTRIBUTES ObjectAttributes,
  904. IN ULONG TitleIndex,
  905. IN PUNICODE_STRING Class OPTIONAL,
  906. IN ULONG CreateOptions,
  907. OUT PULONG Disposition OPTIONAL
  908. )
  909. /*++
  910. Routine Description:
  911. An existing registry key may be opened, or a new one created,
  912. with NtCreateKey.
  913. If the specified key does not exist, an attempt is made to create it.
  914. For the create attempt to succeed, the new node must be a direct
  915. child of the node referred to by KeyHandle. If the node exists,
  916. it is opened. Its value is not affected in any way.
  917. Share access is computed from desired access.
  918. NOTE:
  919. If CreateOptions has REG_OPTION_BACKUP_RESTORE set, then
  920. DesiredAccess will be ignored. If the caller has the
  921. privilege SeBackupPrivilege asserted, a handle with
  922. KEY_READ | ACCESS_SYSTEM_SECURITY will be returned.
  923. If SeRestorePrivilege, then same but KEY_WRITE rather
  924. than KEY_READ. If both, then both access sets. If neither
  925. privilege is asserted, then the call will fail.
  926. Arguments:
  927. KeyHandle - Receives a Handle which is used to access the
  928. specified key in the Registration Database.
  929. DesiredAccess - Specifies the access rights desired.
  930. ObjectAttributes - Specifies the attributes of the key being opened.
  931. Note that a key name must be specified. If a Root Directory is
  932. specified, the name is relative to the root. The name of the
  933. object must be within the name space allocated to the Registry,
  934. that is, all names beginning "\Registry". RootHandle, if
  935. present, must be a handle to "\", or "\Registry", or a key
  936. under "\Registry".
  937. RootHandle must have been opened for KEY_CREATE_SUB_KEY access
  938. if a new node is to be created.
  939. NOTE: Object manager will capture and probe this argument.
  940. TitleIndex - Specifies the index of the localized alias for
  941. the name of the key. The title index specifies the index of
  942. the localized alias for the name. Ignored if the key
  943. already exists.
  944. Class - Specifies the object class of the key. (To the registry
  945. this is just a string.) Ignored if NULL.
  946. CreateOptions - Optional control values:
  947. REG_OPTION_VOLATILE - Object is not to be stored across boots.
  948. Disposition - This optional parameter is a pointer to a variable
  949. that will receive a value indicating whether a new Registry
  950. key was created or an existing one opened:
  951. REG_CREATED_NEW_KEY - A new Registry Key was created
  952. REG_OPENED_EXISTING_KEY - An existing Registry Key was opened
  953. Return Value:
  954. NTSTATUS - Result code from call, among the following:
  955. <TBS>
  956. --*/
  957. {
  958. UNICODE_STRING Parent;
  959. NTSTATUS st;
  960. OBJECT_ATTRIBUTES Obja;
  961. WCHAR PatchedIsnNode[WOW64_MAX_PATH];
  962. WCHAR AbsPath[WOW64_MAX_PATH];
  963. BOOL bPatched=FALSE;
  964. DWORD ParentLen;
  965. //
  966. // Make the complete path in a AbsPath
  967. //
  968. if (ARGUMENT_PRESENT(phPatchedHandle)){
  969. *phPatchedHandle=NULL;
  970. }
  971. st = ObjectAttributesToKeyName (
  972. ObjectAttributes,
  973. AbsPath,
  974. sizeof (AbsPath)-30, //Keep 15 char space
  975. &bPatched,
  976. &ParentLen);
  977. if (!NT_SUCCESS(st)) {
  978. WOWASSERT(FALSE );
  979. return st;
  980. }
  981. if ( IsAccessDeniedOnKey (
  982. AbsPath,
  983. &DesiredAccess,
  984. TRUE
  985. ))
  986. return STATUS_ACCESS_DENIED;
  987. if (DesiredAccess & KEY_WOW64_64KEY) {
  988. if (!Map32bitTo64bitKeyName ( AbsPath, PatchedIsnNode )) {
  989. WOWASSERT(FALSE );
  990. return STATUS_SUCCESS; //severe problem shouldn't happen
  991. }
  992. } else {
  993. PWCHAR p;
  994. if (!Map64bitTo32bitKeyName ( AbsPath, PatchedIsnNode )){
  995. WOWASSERT(FALSE );
  996. return STATUS_SUCCESS; //severe problem shouldn't happen
  997. }
  998. }
  999. DesiredAccess = DesiredAccess & (~KEY_WOW64_RES);
  1000. if (!bPatched) // the abspath hasn't been patched
  1001. if ( !wcscmp (AbsPath, PatchedIsnNode ))
  1002. return STATUS_SUCCESS; // no change can be optimize by returning different value from Map64bitTo32bitKeyName
  1003. RtlInitUnicodeString (&Parent, PatchedIsnNode);
  1004. InitializeObjectAttributes (&Obja,
  1005. &Parent,
  1006. ObjectAttributes->Attributes,
  1007. NULL,
  1008. ObjectAttributes->SecurityDescriptor
  1009. ); //you have to use caller's context
  1010. st = NtCreateKey(
  1011. phPatchedHandle,
  1012. DesiredAccess,
  1013. &Obja,
  1014. TitleIndex,
  1015. Class ,
  1016. CreateOptions,
  1017. Disposition
  1018. );
  1019. return st;
  1020. }
  1021. NTSTATUS
  1022. Wow64NtPreUnloadKeyNotify(
  1023. IN POBJECT_ATTRIBUTES TargetKey
  1024. )
  1025. /*++
  1026. Routine Description:
  1027. This call will notify Wow64 service that wow64 need to release any open handle
  1028. to the hive that is going to be unloaded.
  1029. Drop a subtree (hive) out of the registry.
  1030. Will fail if applied to anything other than the root of a hive.
  1031. Cannot be applied to core system hives (HARDWARE, SYSTEM, etc.)
  1032. Can be applied to user hives loaded via NtRestoreKey or NtLoadKey.
  1033. If there are handles open to the hive being dropped, this call
  1034. will fail. Terminate relevent processes so that handles are
  1035. closed.
  1036. This call will flush the hive being dropped.
  1037. Caller must have SeRestorePrivilege privilege.
  1038. Arguments:
  1039. TargetKey - specifies the path to a key to link the hive to.
  1040. path must be of the form "\registry\user\<username>"
  1041. Return Value:
  1042. NTSTATUS - values TBS.
  1043. --*/
  1044. {
  1045. //todo
  1046. return 0;
  1047. }
  1048. NTSTATUS
  1049. Wow64NtPostLoadKeyNotify(
  1050. IN POBJECT_ATTRIBUTES TargetKey
  1051. )
  1052. /*++
  1053. Routine Description:
  1054. If Load operation succeed, it will notify wow64 service that it
  1055. can listen to the registry operation on the given hive.
  1056. This function can be invoked from NtLoadKey and NtLoadKey2 APIs.
  1057. A hive (file in the format created by NtSaveKey) may be linked
  1058. into the active registry with this call. UNLIKE NtRestoreKey,
  1059. the file specified to NtLoadKey will become the actual backing
  1060. store of part of the registry (that is, it will NOT be copied.)
  1061. The file may have an associated .log file.
  1062. If the hive file is marked as needing a .log file, and one is
  1063. not present, the call will fail.
  1064. Caller must have SeRestorePrivilege privilege.
  1065. This call is used by logon to make the user's profile available
  1066. in the registry. It is not intended for use doing backup,
  1067. restore, etc. Use NtRestoreKey for that.
  1068. Arguments:
  1069. TargetKey - specifies the path to a key to link the hive to.
  1070. path must be of the form "\registry\user\<username>"
  1071. Return Value:
  1072. NTSTATUS - values TBS.
  1073. --*/
  1074. {
  1075. //todo
  1076. return 0;
  1077. }