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.

383 lines
14 KiB

  1. #include "shellprv.h"
  2. #pragma hdrstop
  3. // this makes sure the DLL for the given clsid stays in memory
  4. // this is needed because we violate COM rules and hold apparment objects
  5. // across the lifetime of appartment threads. these objects really need
  6. // to be free threaded (we have always treated them as such)
  7. STDAPI_(HINSTANCE) SHPinDllOfCLSIDStr(LPCTSTR pszCLSID)
  8. {
  9. CLSID clsid;
  10. SHCLSIDFromString(pszCLSID, &clsid);
  11. return SHPinDllOfCLSID(&clsid);
  12. }
  13. // translate string form of CLSID into binary form
  14. STDAPI SHCLSIDFromString(LPCTSTR psz, CLSID *pclsid)
  15. {
  16. *pclsid = CLSID_NULL;
  17. if (psz == NULL)
  18. return NOERROR;
  19. return GUIDFromString(psz, pclsid) ? NOERROR : CO_E_CLASSSTRING;
  20. }
  21. BOOL _IsShellDll(LPCTSTR pszDllPath)
  22. {
  23. LPCTSTR pszDllName = PathFindFileName(pszDllPath);
  24. return lstrcmpi(pszDllName, TEXT("shell32.dll")) == 0;
  25. }
  26. HKEY g_hklmApprovedExt = (HKEY)-1; // not tested yet
  27. // On NT, we must check to ensure that this CLSID exists in
  28. // the list of approved CLSIDs that can be used in-process.
  29. // If not, we fail the creation with ERROR_ACCESS_DENIED.
  30. // We explicitly allow anything serviced by this DLL
  31. BOOL _IsShellExtApproved(LPCTSTR pszClass, LPCTSTR pszDllPath)
  32. {
  33. BOOL fIsApproved = TRUE;
  34. ASSERT(!_IsShellDll(pszDllPath));
  35. #ifdef FULL_DEBUG
  36. if (TRUE)
  37. #else
  38. if (SHRestricted(REST_ENFORCESHELLEXTSECURITY))
  39. #endif
  40. {
  41. if (g_hklmApprovedExt == (HKEY)-1)
  42. {
  43. g_hklmApprovedExt = NULL;
  44. RegOpenKey(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"), &g_hklmApprovedExt);
  45. }
  46. if (g_hklmApprovedExt)
  47. {
  48. fIsApproved = SHQueryValueEx(g_hklmApprovedExt, pszClass, 0, NULL, NULL, NULL) == ERROR_SUCCESS;
  49. if (!fIsApproved)
  50. {
  51. HKEY hk;
  52. if (RegOpenKey(HKEY_CURRENT_USER, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved"), &hk) == ERROR_SUCCESS)
  53. {
  54. fIsApproved = SHQueryValueEx(hk, pszClass, 0, NULL, NULL, NULL) == ERROR_SUCCESS;
  55. RegCloseKey(hk);
  56. }
  57. }
  58. }
  59. }
  60. #ifdef FULL_DEBUG
  61. if (!SHRestricted(REST_ENFORCESHELLEXTSECURITY) && !fIsApproved)
  62. {
  63. TraceMsg(TF_WARNING, "%s not approved; fortunately, shell security is disabled", pszClass);
  64. fIsApproved = TRUE;
  65. }
  66. #endif
  67. return fIsApproved;
  68. }
  69. STDAPI_(BOOL) IsGuimodeSetupRunning()
  70. {
  71. DWORD dwSystemSetupInProgress;
  72. DWORD dwMiniSetupInProgress;
  73. DWORD dwType;
  74. DWORD dwSize;
  75. dwSize = sizeof(dwSystemSetupInProgress);
  76. if ((SHGetValueW(HKEY_LOCAL_MACHINE, L"SYSTEM\\Setup", L"SystemSetupInProgress", &dwType, (LPVOID)&dwSystemSetupInProgress, &dwSize) == ERROR_SUCCESS) &&
  77. (dwType == REG_DWORD) &&
  78. (dwSystemSetupInProgress != 0))
  79. {
  80. // starting w/ whistler on a syspreped machine the SystemSetupInProgress will be set EVEN AFTER guimode setup
  81. // has finished (needed for OOBE on the boot after guimode finishes). So, to distinguish the "first-boot" case
  82. // from the "guimode-setup" case we check the MiniSetupInProgress value as well.
  83. dwSize = sizeof(dwMiniSetupInProgress);
  84. if ((SHGetValueW(HKEY_LOCAL_MACHINE, L"SYSTEM\\Setup", L"MiniSetupInProgress", &dwType, (LPVOID)&dwMiniSetupInProgress, &dwSize) != ERROR_SUCCESS) ||
  85. (dwType != REG_DWORD) ||
  86. (dwMiniSetupInProgress == 0))
  87. {
  88. return TRUE;
  89. }
  90. }
  91. return FALSE;
  92. }
  93. typedef HRESULT (__stdcall *PFNDLLGETCLASSOBJECT)(REFCLSID rclsid, REFIID riid, void **ppv);
  94. HRESULT _CreateFromDllGetClassObject(PFNDLLGETCLASSOBJECT pfn, const CLSID *pclsid, IUnknown *punkOuter, REFIID riid, void **ppv)
  95. {
  96. IClassFactory *pcf;
  97. HRESULT hr = pfn(pclsid, &IID_IClassFactory, &pcf);
  98. if (SUCCEEDED(hr))
  99. {
  100. hr = pcf->lpVtbl->CreateInstance(pcf, punkOuter, riid, ppv);
  101. #ifdef DEBUG
  102. if (SUCCEEDED(hr))
  103. {
  104. // confirm that OLE can create this object to
  105. // make sure our objects are really CoCreateable
  106. IUnknown *punk;
  107. HRESULT hrTemp = CoCreateInstance(pclsid, punkOuter, CLSCTX_INPROC_SERVER, riid, &punk);
  108. if (SUCCEEDED(hrTemp))
  109. punk->lpVtbl->Release(punk);
  110. else
  111. {
  112. if (hrTemp == CO_E_NOTINITIALIZED)
  113. {
  114. // shell32.dll works without com being inited
  115. TraceMsg(TF_WARNING, "shell32 or friend object used without COM being initalized");
  116. }
  117. // the RIPMSG below was hitting too often in out-of-memory cases where lame class factories return E_FAIL, E_NOTIMPL, and a bunch of
  118. // other meaningless error codes. I have therefore relegaed this ripmsg to FULL_DEBUG only status.
  119. #ifdef FULL_DEBUG
  120. else if ((hrTemp != E_OUTOFMEMORY) && // stress can hit the E_OUTOFMEMORY case
  121. (hrTemp != E_NOINTERFACE) && // stress can hit the E_NOINTERFACE case
  122. (hrTemp != HRESULT_FROM_WIN32(ERROR_COMMITMENT_LIMIT)) && // stress can hit the ERROR_COMMITMENT_LIMIT case
  123. (hrTemp != HRESULT_FROM_WIN32(ERROR_NO_SYSTEM_RESOURCES)) && // stress can hit the ERROR_NO_SYSTEM_RESOURCES case
  124. !IsGuimodeSetupRunning()) // and we don't want to fire the assert during guimode (shell32 might not be registered yet)
  125. {
  126. // others failures are bad
  127. RIPMSG(FALSE, "CoCreate failed with %x", hrTemp);
  128. }
  129. #endif // FULL_DEBUG
  130. }
  131. }
  132. #endif
  133. pcf->lpVtbl->Release(pcf);
  134. }
  135. return hr;
  136. }
  137. HRESULT _CreateFromShell(const CLSID *pclsid, IUnknown *punkOuter, REFIID riid, void **ppv)
  138. {
  139. return _CreateFromDllGetClassObject(DllGetClassObject, pclsid, punkOuter, riid, ppv);
  140. }
  141. HRESULT _CreateFromDll(LPCTSTR pszDllPath, const CLSID *pclsid, IUnknown *punkOuter, REFIID riid, void **ppv)
  142. {
  143. HMODULE hmod = LoadLibraryEx(pszDllPath,NULL,LOAD_WITH_ALTERED_SEARCH_PATH);
  144. if (hmod)
  145. {
  146. HRESULT hr;
  147. PFNDLLGETCLASSOBJECT pfn = (PFNDLLGETCLASSOBJECT)GetProcAddress(hmod, "DllGetClassObject");
  148. if (pfn)
  149. hr = _CreateFromDllGetClassObject(pfn, pclsid, punkOuter, riid, ppv);
  150. else
  151. hr = E_FAIL;
  152. if (FAILED(hr))
  153. FreeLibrary(hmod);
  154. return hr;
  155. }
  156. return HRESULT_FROM_WIN32(GetLastError());
  157. }
  158. STDAPI SHGetInProcServerForClass(const CLSID *pclsid, LPTSTR pszDllPath, LPTSTR pszClass, BOOL *pbLoadWithoutCOM)
  159. {
  160. TCHAR szKeyToOpen[GUIDSTR_MAX + 128], szInProcServer[GUIDSTR_MAX];
  161. HKEY hkeyInProcServer;
  162. DWORD dwSize = MAX_PATH * sizeof(TCHAR); // convert to count of bytes
  163. DWORD dwError;
  164. SHStringFromGUID(pclsid, szInProcServer, ARRAYSIZE(szInProcServer));
  165. lstrcpy(pszClass, szInProcServer);
  166. *pszDllPath = 0;
  167. lstrcpy(szKeyToOpen, TEXT("CLSID\\"));
  168. lstrcat(szKeyToOpen, szInProcServer);
  169. lstrcat(szKeyToOpen, TEXT("\\InProcServer32"));
  170. dwError = RegOpenKeyEx(HKEY_CLASSES_ROOT, szKeyToOpen, 0, KEY_QUERY_VALUE, &hkeyInProcServer);
  171. if (dwError == ERROR_SUCCESS)
  172. {
  173. SHQueryValueEx(hkeyInProcServer, NULL, 0, NULL, (BYTE *)pszDllPath, &dwSize);
  174. *pbLoadWithoutCOM = SHQueryValueEx(hkeyInProcServer, TEXT("LoadWithoutCOM"), NULL, NULL, NULL, NULL) == ERROR_SUCCESS;
  175. RegCloseKey(hkeyInProcServer);
  176. }
  177. //
  178. // Return a more accurate error code so we don't
  179. // fire a bogus assertion.
  180. //
  181. if (*pszDllPath)
  182. {
  183. return S_OK;
  184. }
  185. else
  186. {
  187. // If error was "key not found", then the class is not registered.
  188. // If no error, then class is not registered properly (e.g., null
  189. // string for InProcServer32).
  190. if (dwError == ERROR_FILE_NOT_FOUND || dwError == ERROR_SUCCESS)
  191. {
  192. return REGDB_E_CLASSNOTREG;
  193. }
  194. else
  195. {
  196. // Any other error is worth reporting as-is (out of memory,
  197. // access denied, etc.)
  198. return HRESULT_FROM_WIN32(dwError);
  199. }
  200. }
  201. }
  202. STDAPI _SHCoCreateInstance(const CLSID * pclsid, IUnknown *punkOuter, DWORD dwCoCreateFlags,
  203. BOOL bMustBeApproved, REFIID riid, void **ppv)
  204. {
  205. HRESULT hr = E_FAIL;
  206. TCHAR szClass[GUIDSTR_MAX + 64], szDllPath[MAX_PATH];
  207. BOOL bLoadWithoutCOM = FALSE;
  208. *ppv = NULL;
  209. *szDllPath = 0;
  210. // save us some registry accesses and try the shell first
  211. // but only if its INPROC
  212. if (dwCoCreateFlags & CLSCTX_INPROC_SERVER)
  213. hr = _CreateFromShell(pclsid, punkOuter, riid, ppv);
  214. #ifdef DEBUG
  215. if (SUCCEEDED(hr))
  216. {
  217. HRESULT hrRegistered = THR(SHGetInProcServerForClass(pclsid, szDllPath, szClass, &bLoadWithoutCOM));
  218. //
  219. // check to see if we're the explorer process before complaining (to
  220. // avoid ripping during setup before all objects have been registered)
  221. //
  222. if (IsProcessAnExplorer() && !IsGuimodeSetupRunning() && hrRegistered == REGDB_E_CLASSNOTREG)
  223. {
  224. ASSERTMSG(FAILED(hr), "object not registered (add to selfreg.inx) pclsid = %x", pclsid);
  225. }
  226. }
  227. #endif
  228. if (FAILED(hr))
  229. {
  230. BOOL fNeedsInProc = ((dwCoCreateFlags & CLSCTX_ALL) == CLSCTX_INPROC_SERVER);
  231. hr = fNeedsInProc ? THR(SHGetInProcServerForClass(pclsid, szDllPath, szClass, &bLoadWithoutCOM)) : S_FALSE;
  232. if (SUCCEEDED(hr))
  233. {
  234. if (hr == S_OK && _IsShellDll(szDllPath))
  235. {
  236. // Object likely moved out of the shell DLL.
  237. hr = CLASS_E_CLASSNOTAVAILABLE;
  238. }
  239. else if (bMustBeApproved &&
  240. SHStringFromGUID(pclsid, szClass, ARRAYSIZE(szClass)) &&
  241. !_IsShellExtApproved(szClass, szDllPath))
  242. {
  243. TraceMsg(TF_ERROR, "SHCoCreateInstance() %s needs to be registered under HKLM or HKCU"
  244. ",Software\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", szClass);
  245. hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED);
  246. }
  247. else
  248. {
  249. hr = THR(SHCoCreateInstanceAC(pclsid, punkOuter, dwCoCreateFlags, riid, ppv));
  250. if (FAILED(hr) && fNeedsInProc && bLoadWithoutCOM)
  251. {
  252. if ((hr == REGDB_E_IIDNOTREG) || (hr == CO_E_NOTINITIALIZED))
  253. {
  254. hr = THR(_CreateFromDll(szDllPath, pclsid, punkOuter, riid, ppv));
  255. }
  256. }
  257. // only RIP if this is not a secondary explorer process since secondary explorers dont init com or ole since
  258. // they are going to delegate to an existing process and we don't want to have to load ole for perf in that case.
  259. if (!IsSecondaryExplorerProcess())
  260. {
  261. RIPMSG((hr != CO_E_NOTINITIALIZED), "COM not inited for dll %s", szDllPath);
  262. }
  263. // sometimes we need to permanently pin these objects.
  264. if (SUCCEEDED(hr) && fNeedsInProc && (OBJCOMPATF_PINDLL & SHGetObjectCompatFlags(NULL, pclsid)))
  265. {
  266. SHPinDllOfCLSID(pclsid);
  267. }
  268. }
  269. }
  270. }
  271. #ifdef DEBUG
  272. if (FAILED(hr) && (hr != E_NOINTERFACE)) // E_NOINTERFACE means riid not accepted
  273. {
  274. ULONGLONG dwTF = IsFlagSet(g_dwBreakFlags, BF_COCREATEINSTANCE) ? TF_ALWAYS : TF_WARNING;
  275. TraceMsg(dwTF, "CoCreateInstance: failed (%s,%x)", szClass, hr);
  276. }
  277. #endif
  278. return hr;
  279. }
  280. STDAPI SHCoCreateInstance(LPCTSTR pszCLSID, const CLSID * pclsid, IUnknown *punkOuter, REFIID riid, void **ppv)
  281. {
  282. CLSID clsid;
  283. if (pszCLSID)
  284. {
  285. SHCLSIDFromString(pszCLSID, &clsid);
  286. pclsid = &clsid;
  287. }
  288. return _SHCoCreateInstance(pclsid, punkOuter, CLSCTX_INPROC_SERVER, FALSE, riid, ppv);
  289. }
  290. //
  291. // create a shell extension object, ensures that object is in the approved list
  292. //
  293. STDAPI SHExtCoCreateInstance2(LPCTSTR pszCLSID, const CLSID *pclsid, IUnknown *punkOuter, DWORD dwClsCtx, REFIID riid, void **ppv)
  294. {
  295. CLSID clsid;
  296. if (pszCLSID)
  297. {
  298. SHCLSIDFromString(pszCLSID, &clsid);
  299. pclsid = &clsid;
  300. }
  301. return _SHCoCreateInstance(pclsid, punkOuter, dwClsCtx, TRUE, riid, ppv);
  302. }
  303. STDAPI SHExtCoCreateInstance(LPCTSTR pszCLSID, const CLSID *pclsid, IUnknown *punkOuter, REFIID riid, void **ppv)
  304. {
  305. return SHExtCoCreateInstance2(pszCLSID, pclsid, punkOuter, CLSCTX_NO_CODE_DOWNLOAD | CLSCTX_INPROC_SERVER, riid, ppv);
  306. }
  307. STDAPI_(BOOL) SHIsBadInterfacePtr(const void *pv, UINT cbVtbl)
  308. {
  309. IUnknown const * punk = pv;
  310. return IsBadReadPtr(punk, sizeof(punk->lpVtbl)) ||
  311. IsBadReadPtr(punk->lpVtbl, cbVtbl) ||
  312. IsBadCodePtr((FARPROC)punk->lpVtbl->Release);
  313. }
  314. // private API that loads COM inproc objects out of band of COM. this
  315. // should be used very carefully, only in special legacy cases where
  316. // we knowingly need to break COM rules. right now this is only for AVIFile
  317. // as it depended on the Win95 behavior of SHCoCreateInstance() loading objects
  318. // without COM being inited and without them being marshalled
  319. STDAPI SHCreateInstance(REFCLSID clsid, REFIID riid, void **ppv)
  320. {
  321. TCHAR szClass[GUIDSTR_MAX + 64], szDllPath[MAX_PATH];
  322. BOOL bLoadWithoutCOM;
  323. HRESULT hr = SHGetInProcServerForClass(clsid, szDllPath, szClass, &bLoadWithoutCOM);
  324. if (SUCCEEDED(hr))
  325. {
  326. hr = THR(_CreateFromDll(szDllPath, clsid, NULL, riid, ppv));
  327. }
  328. else
  329. {
  330. *ppv = NULL;
  331. }
  332. return hr;
  333. }