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.

643 lines
21 KiB

  1. // Copyright (c) 1996-1999 Microsoft Corporation
  2. //+-------------------------------------------------------------------------
  3. //
  4. // Microsoft Windows
  5. //
  6. // File: port.cxx
  7. //
  8. // Contents: Code that receives notifications of moves from
  9. // kernel.
  10. //
  11. // Classes:
  12. //
  13. // Functions:
  14. //
  15. //
  16. //
  17. // History:
  18. //
  19. // Notes:
  20. //
  21. // Codework: Security on semaphore and port objects
  22. // _hDllReference when put in services.exe
  23. // InitializeObjectAttributes( &oa, &name, 0, NULL, NULL /* &sd */ );
  24. //
  25. //--------------------------------------------------------------------------
  26. #include "pch.cxx"
  27. #pragma hdrstop
  28. #include "trkwks.hxx"
  29. #define THIS_FILE_NUMBER PORT_CXX_FILE_NO
  30. DWORD WINAPI
  31. PortThreadStartRoutine( LPVOID lpThreadParameter );
  32. //+----------------------------------------------------------------------------
  33. //
  34. // CSystemSD::Initialize
  35. // CSystemSD::UnInitialize
  36. //
  37. // Init and uninit the security descriptor that gives access only
  38. // to System, or to System and Administrators.
  39. //
  40. //+----------------------------------------------------------------------------
  41. void
  42. CSystemSD::Initialize( ESystemSD eSystemSD )
  43. {
  44. // Add ACEs to the DACL in a Security Descriptor which give the
  45. // System and Administrators full access.
  46. _csd.Initialize();
  47. if( SYSTEM_AND_ADMINISTRATOR == eSystemSD )
  48. {
  49. _csidAdministrators.Initialize( CSID::CSID_NT_AUTHORITY,
  50. SECURITY_BUILTIN_DOMAIN_RID,
  51. DOMAIN_ALIAS_RID_ADMINS );
  52. _csd.AddAce( CSecDescriptor::ACL_IS_DACL, CSecDescriptor::AT_ACCESS_ALLOWED,
  53. FILE_ALL_ACCESS, _csidAdministrators );
  54. }
  55. else
  56. TrkAssert( SYSTEM_ONLY == eSystemSD );
  57. _csidSystem.Initialize( CSID::CSID_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID );
  58. _csd.AddAce( CSecDescriptor::ACL_IS_DACL, CSecDescriptor::AT_ACCESS_ALLOWED,
  59. FILE_ALL_ACCESS, _csidSystem );
  60. }
  61. void
  62. CSystemSD::UnInitialize()
  63. {
  64. _csidAdministrators.UnInitialize();
  65. _csidSystem.UnInitialize();
  66. _csd.UnInitialize();
  67. }
  68. //+----------------------------------------------------------------------------
  69. //
  70. // CPort::Initialize
  71. //
  72. // Create an LPC port to which the kernel will send move notifications,
  73. // and open an event created by the kernel with which we'll signal
  74. // our readiness to receive requests.
  75. //
  76. //+----------------------------------------------------------------------------
  77. void
  78. CPort::Initialize( CTrkWksSvc *pTrkWks,
  79. DWORD dwThreadKeepAliveTime )
  80. {
  81. NTSTATUS Status;
  82. OBJECT_ATTRIBUTES oa;
  83. UNICODE_STRING name;
  84. CSystemSD ssd;
  85. DWORD dwThreadId;
  86. __try
  87. {
  88. _hListenPort = NULL;
  89. _hEvent = NULL;
  90. _pTrkWks = pTrkWks;
  91. _hLpcPort = NULL;
  92. _hRegisterWaitForSingleObjectEx = NULL;
  93. _fTerminating = FALSE;
  94. // Create an LPC port to which the kernel will send move-notification requests
  95. RtlInitUnicodeString( &name, TRKWKS_PORT_NAME );
  96. ssd.Initialize();
  97. InitializeObjectAttributes( &oa, &name, 0, NULL, ssd.operator const PSECURITY_DESCRIPTOR() );
  98. Status = NtCreateWaitablePort(&_hListenPort, &oa,
  99. sizeof(ULONG), // IN ULONG MaxConnectionInfoLength
  100. sizeof(TRKWKS_PORT_REQUEST), // IN ULONG MaxMessageLength
  101. 0); // not used : IN ULONG MaxPoolUsage
  102. if (!NT_SUCCESS(Status))
  103. {
  104. TrkLog(( TRKDBG_ERROR, TEXT("Couldn't create LPC connect port") ));
  105. TrkReportInternalError( THIS_FILE_NUMBER, __LINE__, Status, TRKWKS_PORT_NAME );
  106. TrkRaiseException(Status);
  107. }
  108. // Show that we need to do work in the UnInitialize method.
  109. _fInitializeCalled = TRUE;
  110. // Open the event which is created by the kernel for synchronization.
  111. // We tell the kernel that we're available for move-notification requests
  112. // by setting this event.
  113. RtlInitUnicodeString( &name, TRKWKS_PORT_EVENT_NAME );
  114. Status = NtOpenEvent( &_hEvent, EVENT_ALL_ACCESS, &oa );
  115. if (!NT_SUCCESS(Status))
  116. {
  117. TrkReportInternalError( THIS_FILE_NUMBER, __LINE__, Status, TRKWKS_PORT_EVENT_NAME );
  118. TrkLog(( TRKDBG_ERROR, TEXT("Couldn't open %s event"), TRKWKS_PORT_EVENT_NAME ));
  119. TrkRaiseException(Status);
  120. }
  121. // Register our LPC connect port with the thread pool. When that handle signals,
  122. // we'll run CPort::DoWork (it signals when we get any message, including
  123. // LPC_CONNECT_REQUEST).
  124. if( !RegisterWorkItemWithThreadPool() )
  125. {
  126. TrkLog(( TRKDBG_ERROR, TEXT("Failed RegisterWaitForSingleObjectEx in CPort::Initialize (%lu)"),
  127. GetLastError() ));
  128. TrkReportInternalError( THIS_FILE_NUMBER, __LINE__, Status, TRKREPORT_LAST_PARAM );
  129. TrkRaiseLastError();
  130. }
  131. // When we receive a move notification and we get a thread from the pool,
  132. // we'll keep that thread until we've gone idle for this amount of time.
  133. // So if we receive several requests in a short period of time, we won't
  134. // have to get a thread out of the pool for each.
  135. _ThreadKeepAliveTime.QuadPart = -static_cast<LONGLONG>(dwThreadKeepAliveTime) * 10000000;
  136. }
  137. __finally
  138. {
  139. ssd.UnInitialize();
  140. }
  141. }
  142. //+----------------------------------------------------------------------------
  143. //
  144. // CPort::RegisterWorkItemWithThreadPool
  145. //
  146. // Register the LPC connect port (_hListenPort) with the thread pool.
  147. //
  148. //+----------------------------------------------------------------------------
  149. BOOL
  150. CPort::RegisterWorkItemWithThreadPool()
  151. {
  152. // This is an execute-only-once work item, so it's inactive now.
  153. // Delete it, specifying that there should be no completion event
  154. // (if a completion event were used, this call would hang forever).
  155. if( NULL != _hRegisterWaitForSingleObjectEx )
  156. TrkUnregisterWait( _hRegisterWaitForSingleObjectEx, NULL );
  157. // Now register it again.
  158. _hRegisterWaitForSingleObjectEx
  159. = TrkRegisterWaitForSingleObjectEx( _hListenPort, ThreadPoolCallbackFunction,
  160. static_cast<PWorkItem*>(this), INFINITE,
  161. WT_EXECUTEONLYONCE );
  162. if( NULL == _hRegisterWaitForSingleObjectEx )
  163. {
  164. TrkLog(( TRKDBG_ERROR, TEXT("Failed RegisterWaitForSingleObjectEx in CPort::DoWork (%lu)"),
  165. GetLastError() ));
  166. return( FALSE );
  167. }
  168. else
  169. TrkLog(( TRKDBG_PORT, TEXT("Registered LPC port work item") ));
  170. return( TRUE );
  171. }
  172. //+----------------------------------------------------------------------------
  173. //
  174. // CPort::OnConnectionRequest
  175. //
  176. // Called when a connection request has been received. It is either
  177. // accepted or rejected, depending on the request and the current state
  178. // of the service.
  179. //
  180. // When the service is shutting down, CPort::UnInitialize posts a connection
  181. // request with some connection information. When that connection request
  182. // is received, it is rejected, and the pfStopPortThread is set True.
  183. //
  184. //+----------------------------------------------------------------------------
  185. NTSTATUS
  186. CPort::OnConnectionRequest( TRKWKS_PORT_CONNECT_REQUEST *pPortConnectRequest, BOOL *pfStopPortThread )
  187. {
  188. HANDLE hLpcPortT = NULL;
  189. HANDLE *phLpcPort = NULL;
  190. NTSTATUS Status = STATUS_SUCCESS;
  191. BOOL fAccept = TRUE;
  192. *pfStopPortThread = FALSE;
  193. // Determine if we should accept or reject this connection request.
  194. if( pPortConnectRequest->PortMessage.u1.s1.DataLength
  195. >=
  196. sizeof(pPortConnectRequest->Info) )
  197. {
  198. // There's extra connection info in this request. See if it's a request
  199. // code that indicates that we should close down the port.
  200. if( TRKWKS_RQ_EXIT_PORT_THREAD == pPortConnectRequest->Info.dwRequest )
  201. {
  202. fAccept = FALSE;
  203. *pfStopPortThread = TRUE;
  204. TrkLog(( TRKDBG_PORT, TEXT("Received port shutdown connection request") ));
  205. }
  206. else
  207. {
  208. fAccept = FALSE;
  209. TrkLog(( TRKDBG_ERROR, TEXT("CPort: unknown Info.dwRequest (%d)"),
  210. pPortConnectRequest->Info.dwRequest ));
  211. }
  212. }
  213. else if( _fTerminating )
  214. {
  215. // We're shutting down, reject the request
  216. fAccept = FALSE;
  217. TrkLog(( TRKDBG_PORT, TEXT("Received connect request during service shutdown") ));
  218. }
  219. // Point phLpcPort to the real communications handle, or the dummy one used
  220. // for rejections.
  221. if( fAccept )
  222. {
  223. phLpcPort = &_hLpcPort;
  224. // Close out any existing communication port
  225. if( NULL != _hLpcPort )
  226. {
  227. NtClose( _hLpcPort );
  228. _hLpcPort = NULL;
  229. }
  230. }
  231. else
  232. {
  233. phLpcPort = &hLpcPortT;
  234. }
  235. // Accept or reject the new connection.
  236. // In the reject case, this could create a race condition. After we make the
  237. // NtAcceptConnectPort call, the CPort::UnInitialize thread might wake up and
  238. // delete the CPort before this thread runs again. So, after making this
  239. // call, we cannot touch anything in 'this'.
  240. TrkLog(( TRKDBG_PORT, TEXT("%s connect request"),
  241. fAccept ? TEXT("Accepting") : TEXT("Rejecting") ));
  242. TRKWKS_PORT_REQUEST *pPortRequest = (TRKWKS_PORT_REQUEST*) pPortConnectRequest;
  243. pPortRequest->PortMessage.u1.s1.TotalLength = sizeof(*pPortRequest);
  244. pPortRequest->PortMessage.u1.s1.DataLength = sizeof(pPortRequest->Request); // MaxMessageLength
  245. Status = NtAcceptConnectPort(
  246. phLpcPort, // PortHandle,
  247. NULL, // PortContext OPTIONAL,
  248. &pPortRequest->PortMessage,
  249. (BOOLEAN)fAccept, // AcceptConnection,
  250. NULL, // ServerView OPTIONAL,
  251. NULL); // ClientView OPTIONAL
  252. if( !NT_SUCCESS(Status) )
  253. {
  254. TrkLog(( TRKDBG_ERROR, TEXT("Failed NtAcceptConnectPort(%s) %08x"),
  255. fAccept ? TEXT("accept"):TEXT("reject"),
  256. Status ));
  257. goto Exit;
  258. }
  259. // If we rejected it, then phLpcPort was hLpcPortT, and it's just
  260. // a dummy argument which must be present but isn't set by NtAcceptConnectPort.
  261. TrkAssert( NULL != *phLpcPort || !fAccept );
  262. // Wake up the client thread (unblock its call to NtConnectPort)
  263. if( fAccept )
  264. {
  265. Status = NtCompleteConnectPort( _hLpcPort );
  266. if( !NT_SUCCESS(Status) )
  267. {
  268. TrkLog(( TRKDBG_ERROR, TEXT("Failed NtCompleteConnectPort %08x"), Status ));
  269. goto Exit;
  270. }
  271. }
  272. Exit:
  273. return( Status );
  274. }
  275. //+----------------------------------------------------------------------------
  276. //
  277. // CPort::DoWork
  278. //
  279. // This method is called by the thread pool when our LPC connect port
  280. // (_hLpcListenPort) is signaled to indicate that a request is available.
  281. // If the request is a connection request, we accept or reject it and continue.
  282. // If the request is a move notification request, we send it to
  283. // CTrkWksSvc for processing.
  284. //
  285. // This work item is registerd with the thread pool with the
  286. // WT_EXECUTEONLYONCE flag, since the connection port isn't
  287. // auto-reset. So after processing, we must
  288. // re-register. Before doing so, or if re-registration fails,
  289. // we keep the thread in a NtReplyWaitReceiveEx call for several
  290. // (configurable) seconds. This way, if several requests arrive
  291. // in a short amount of time, we don't have to thrash the thread pool.
  292. //
  293. // During service termination, _fTerminate is set, and a connection
  294. // request is made by CPort::UnInitialize. This request is rejected,
  295. // and in that case we don't re-register the connect port with the thread
  296. // pool.
  297. //
  298. //+----------------------------------------------------------------------------
  299. void
  300. CPort::DoWork()
  301. {
  302. NTSTATUS Status = STATUS_SUCCESS;
  303. TRKWKS_PORT_REQUEST PortRequest;
  304. TRKWKS_PORT_REPLY PortReply;
  305. PortRequest.PortMessage.u1.s1.TotalLength = sizeof(PortRequest.PortMessage);
  306. PortRequest.PortMessage.u1.s1.DataLength = (CSHORT)0;
  307. BOOL fReuseThread = FALSE;
  308. // The fact that we're running indicates that there's a request
  309. // waiting for us, and the first NtReplyWaitReceivePortEx below will
  310. // immediately return. We loop until nothing is received for 30
  311. // seconds.
  312. while( TRUE )
  313. {
  314. BOOL fTerminating = FALSE; // TRUE => service is shutting down
  315. BOOL fStopPortThread = FALSE; // TRUE => we should shut down this port
  316. Status = NtReplyWaitReceivePortEx( _hListenPort, //_hLpcPort,
  317. NULL,
  318. NULL,
  319. &PortRequest.PortMessage,
  320. &_ThreadKeepAliveTime
  321. );
  322. // Cache a local copy of _fTerminating. In the shutdown case, there's
  323. // a race condition where the CPort object gets deleted before this routine
  324. // can finish. By caching this flag, we don't have to touch this 'this'
  325. // pointer in that case, and therefore avoid the problem.
  326. fTerminating = _fTerminating;
  327. // If we timeed out, then let the thread return to the thread pool.
  328. if( STATUS_TIMEOUT != Status )
  329. {
  330. // We didn't time out.
  331. #if DBG
  332. if( fReuseThread )
  333. TrkLog(( TRKDBG_PORT, TEXT("CPort re-using thread") ));
  334. #endif
  335. fReuseThread = TRUE;
  336. // Is this a request for a new connection?
  337. if( NT_SUCCESS(Status)
  338. &&
  339. LPC_CONNECTION_REQUEST == PortRequest.PortMessage.u2.s2.Type )
  340. {
  341. TrkLog(( TRKDBG_PORT, TEXT("Received LPC connect request") ));
  342. Status = OnConnectionRequest( (TRKWKS_PORT_CONNECT_REQUEST*) &PortRequest,
  343. &fStopPortThread );
  344. #if DBG
  345. if( !NT_SUCCESS(Status) )
  346. TrkLog(( TRKDBG_ERROR, TEXT("CPort::DoWork couldn't handle connection request %08x"), Status ));
  347. #endif
  348. } // if( ... LPC_CONNECTION_REQUEST == PortRequest.PortMessage.u2.s2.Type )
  349. // Or, is this a good move notification?
  350. else if( NT_SUCCESS(Status) && NULL != _hLpcPort )
  351. {
  352. // Process the move notification in CTrkWksSvc. If we're in the proces,
  353. // though, of shutting the service down, then return the same error that
  354. // the kernel would see if DisableKernelNotifications had been called
  355. // in time.
  356. if( _fTerminating )
  357. PortReply.Reply.Status = STATUS_OBJECT_NAME_NOT_FOUND;
  358. else
  359. // The following doesn't raise.
  360. PortReply.Reply.Status = _pTrkWks->OnPortNotification( &PortRequest.Request );
  361. // Send the resulting Status back to the kernel.
  362. PortReply.PortMessage = PortRequest.PortMessage;
  363. PortReply.PortMessage.u1.s1.TotalLength = sizeof(PortReply);
  364. PortReply.PortMessage.u1.s1.DataLength = sizeof(PortReply.Reply);
  365. Status = NtReplyPort( _hLpcPort, &PortReply.PortMessage );
  366. #if DBG
  367. if( !NT_SUCCESS(Status) )
  368. TrkLog(( TRKDBG_ERROR, TEXT("Failed NtReplyPort (%08x)"), Status ));
  369. #endif
  370. }
  371. // Otherwise, we either got an error, or a non-connect message on an
  372. // unconnected port.
  373. else
  374. {
  375. if( NT_SUCCESS(Status) )
  376. Status = STATUS_CONNECTION_INVALID;
  377. TrkLog(( TRKDBG_ERROR, TEXT("CPort::PortThread - NtReplyWaitReceivePortEx failed %0X/%p"),
  378. Status, _hLpcPort ));
  379. }
  380. // To be robust against some unknown bug causing thrashing, sleep
  381. // if there was an error.
  382. if( !NT_SUCCESS(Status) && !fTerminating && !fStopPortThread )
  383. Sleep( 1000 );
  384. // Unless the service is shutting down, we don't want to fall
  385. // through and re-register yet. We should go back to the
  386. // NtReplyWaitReceivePortEx to see if there are more requests
  387. // or will be soon.
  388. if( !fStopPortThread )
  389. continue;
  390. } // if( STATUS_TIMEOUT != Status )
  391. // Re-register the connect port with the thread pool, unless we're supposed
  392. // to stop the port thread.
  393. if( fStopPortThread )
  394. {
  395. TrkLog(( TRKDBG_PORT, TEXT("Stopping port work item") ));
  396. }
  397. else
  398. {
  399. // If we can't re-register for some reason, just continue back to the top
  400. // and sit in the NtReplyWaitReceiveEx for a while.
  401. if( !RegisterWorkItemWithThreadPool() )
  402. {
  403. TrkLog(( TRKDBG_PORT, TEXT("Re-using port thread due to registration error (%lu)"), GetLastError() ));
  404. continue;
  405. }
  406. else
  407. TrkLog(( TRKDBG_PORT, TEXT("Returning port thread to pool") ));
  408. }
  409. // We're either terminating or we've successfully re-registered. In either case, we can let
  410. // the thread go back to the pool.
  411. break;
  412. } // while( TRUE )
  413. }
  414. //+----------------------------------------------------------------------------
  415. //
  416. // CPort::UnInitialize
  417. //
  418. // Remove the LPC connect port work item from the thread pool, and
  419. // clean everything up.
  420. //
  421. // To remove the work item, we can't safely call UnregisterWait, because
  422. // we register with WT_EXECUTEONLYONCE. Thus when we call UnregisterWait,
  423. // the wait may have already been deleted. So, instead, we attempt a connection
  424. // to the LPC connect port, after first setting _fTerminating. This will be
  425. // picked up on a thread pool thread in DoWork, the connection will be
  426. // rejected, and the work item will not be re-registered.
  427. //
  428. //+----------------------------------------------------------------------------
  429. void
  430. CPort::UnInitialize()
  431. {
  432. if (_fInitializeCalled)
  433. {
  434. NTSTATUS status = STATUS_SUCCESS;
  435. UNICODE_STRING usPortName;
  436. OBJECT_ATTRIBUTES oa;
  437. HANDLE hPort = NULL;
  438. ULONG cbMaxMessage = 0;
  439. TRKWKS_CONNECTION_INFO ConnectionInformation = { TRKWKS_RQ_EXIT_PORT_THREAD };
  440. ULONG cbConnectionInformation = sizeof(ConnectionInformation);
  441. _fTerminating = TRUE;
  442. // Attempt to connect to _hLpcListenPort
  443. RtlInitUnicodeString( &usPortName, TRKWKS_PORT_NAME );
  444. SECURITY_QUALITY_OF_SERVICE dynamicQos;
  445. dynamicQos.ImpersonationLevel = SecurityImpersonation;
  446. dynamicQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
  447. dynamicQos.EffectiveOnly = TRUE;
  448. TrkLog(( TRKDBG_PORT, TEXT("CPort::UnInitialize doing an NtConnectPort to %s"), TRKWKS_PORT_NAME ));
  449. status = NtConnectPort( &hPort, &usPortName, &dynamicQos, NULL, NULL,
  450. &cbMaxMessage, &ConnectionInformation, &cbConnectionInformation );
  451. TrkLog(( TRKDBG_PORT, TEXT("CPort::UnInitialize, NtConnectPort completed (0x%08x)"), status ));
  452. if( NT_SUCCESS(status) )
  453. {
  454. TrkLog(( TRKDBG_PORT, TEXT("CPort::UnInitialize NtConnectPort unexpectedly succeeded"), status ));
  455. if( NULL != hPort )
  456. NtClose( hPort );
  457. }
  458. #if DBG
  459. else
  460. {
  461. TrkAssert( NULL == hPort );
  462. if( STATUS_PORT_CONNECTION_REFUSED != status )
  463. TrkLog(( TRKDBG_ERROR, TEXT("CPort::UnInitialize NtConnectPort failed (%08x)"), status ));
  464. }
  465. #endif
  466. // DoWork has been called and is done. Unregister the work item, waiting for the thread
  467. // to complete.
  468. if( NULL != _hRegisterWaitForSingleObjectEx )
  469. TrkUnregisterWait( _hRegisterWaitForSingleObjectEx );
  470. _hRegisterWaitForSingleObjectEx = NULL;
  471. // Clean up the port.
  472. if (_hLpcPort != NULL)
  473. TrkVerify( NT_SUCCESS( NtClose(_hLpcPort) ) );
  474. _hLpcPort = NULL;
  475. if (_hListenPort != NULL)
  476. TrkVerify( NT_SUCCESS( NtClose(_hListenPort) ) );
  477. _hListenPort = NULL;
  478. if( NULL != _hEvent )
  479. NtClose( _hEvent );
  480. _hEvent = NULL;
  481. _fInitializeCalled = FALSE;
  482. }
  483. }
  484. //+----------------------------------------------------------------------------
  485. //
  486. // CPort::EnableKernelNotifications
  487. // CPort::DisableKernelNotifications
  488. //
  489. // Set/clear the event which tells nt!IopConnectLinkTrackingPort that we're
  490. // up and ready to receive a connection.
  491. //
  492. //+----------------------------------------------------------------------------
  493. void
  494. CPort::EnableKernelNotifications()
  495. {
  496. NTSTATUS Status;
  497. Status = NtSetEvent( _hEvent, NULL );
  498. TrkVerify( NT_SUCCESS( Status ) );
  499. }
  500. void
  501. CPort::DisableKernelNotifications()
  502. {
  503. if (_fInitializeCalled)
  504. {
  505. NTSTATUS Status;
  506. Status = NtClearEvent( _hEvent );
  507. TrkVerify( NT_SUCCESS( Status ) );
  508. }
  509. }