/*++ Copyright (c) 1997 Microsoft Corporation Module Name: ipc.c Abstract: The routines in this source file implement an interprocess communication mechanism to allow migration DLLs to be isolated into a separate process ("sandboxing"). This is done so that no DLL can affect the results of any other DLL or Setup. The IPC mechanism used here is memory mapped files. Writes to the memory mapped file are synchronized by two events, one for the receiver and one by the host. Author: Jim Schmidt (jimschm) 22-Mar-1997 Revision History: jimschm 19-Mar-2001 Removed DVD check (now in migration dll) jimschm 02-Jun-1999 Added IPC-based DVD check jimschm 21-Sep-1998 Converted from mailslots to memory mapped files. (There are bugs in both Win9x and NT mailslots that broke the original design.) jimschm 19-Jan-1998 Added beginings of WinVerifyTrust calls jimschm 15-Jul-1997 Added many workarounds for Win95 bugs. --*/ #include "pch.h" #include "migutilp.h" #include #ifdef UNICODE #error Build must be ANSI #endif #define DBG_IPC "Ipc" typedef struct { HANDLE Mapping; HANDLE DoCommand; HANDLE GetResults; } IPCDATA, *PIPCDATA; static PCTSTR g_Mode; static HANDLE g_ProcessHandle; static BOOL g_Host; static IPCDATA g_IpcData; VOID pCloseIpcData ( VOID ); BOOL pOpenIpcData ( VOID ); BOOL pCreateIpcData ( IN PSECURITY_ATTRIBUTES psa ); typedef struct { DWORD Command; DWORD Result; DWORD TechnicalLogId; DWORD GuiLogId; DWORD DataSize; BYTE Data[]; } MAPDATA, *PMAPDATA; BOOL OpenIpcA ( IN BOOL Win95Side, IN PCSTR ExePath, OPTIONAL IN PCSTR RemoteArg, OPTIONAL IN PCSTR WorkingDir OPTIONAL ) /*++ Routine Description: OpenIpc has two modes of operation, depending on who the caller is. If the caller is w95upg.dll or w95upgnt.dll, then the IPC mode is called "host mode." If the caller is migisol.exe, then the IPC mode is called "remote mode." In host mode, OpenIpc creates all of the objects necessary to implement the IPC. This includes two events, DoCommand and GetResults, and a file mapping. After creating the objects, the remote process is launched. In remote mode, OpenIpc opens the existing objects that have already been created. Arguments: Win95Side - Used in host mode only. Specifies that w95upg.dll is running when TRUE, or that w95upgnt.dll is running when FALSE. ExePath - Specifies the command line for migisol.exe. Specifies NULL to indicate remote mode. RemoteArg - Used in host mode only. Specifies the migration DLL path. Ignored in remote mode. WorkingDir - Used in host mode only. Specifies the working directory path for the migration DLL. Ignored in remote mode. Return value: TRUE if the IPC channel was opened. If host mode, TRUE indicates that migisol.exe is up and running. If remote mode, TRUE indicates that migisol is ready for commands. --*/ { CHAR CmdLine[MAX_CMDLINE]; STARTUPINFOA si; PROCESS_INFORMATION pi; BOOL ProcessResult; HANDLE SyncEvent = NULL; HANDLE ObjectArray[2]; DWORD rc; PSECURITY_DESCRIPTOR psd = NULL; SECURITY_ATTRIBUTES sa, *psa; BOOL Result = FALSE; #ifdef DEBUG g_Mode = ExePath ? TEXT("host") : TEXT("remote"); #endif __try { g_ProcessHandle = NULL; g_Host = (ExePath != NULL); if (ISNT()) { // // Create nul DACL for NT // ZeroMemory (&sa, sizeof (sa)); psd = (PSECURITY_DESCRIPTOR) MemAlloc (g_hHeap, 0, SECURITY_DESCRIPTOR_MIN_LENGTH); if (!InitializeSecurityDescriptor (psd, SECURITY_DESCRIPTOR_REVISION)) { __leave; } if (!SetSecurityDescriptorDacl (psd, TRUE, (PACL) NULL, FALSE)) { __leave; } sa.nLength = sizeof (sa); sa.lpSecurityDescriptor = psd; psa = &sa; } else { psa = NULL; } if (g_Host) { // // Create the IPC objects // if (!pCreateIpcData (psa)) { DEBUGMSG ((DBG_ERROR, "Cannot create IPC channel")); __leave; } MYASSERT (RemoteArg); SyncEvent = CreateEvent (NULL, FALSE, FALSE, TEXT("win9xupg")); MYASSERT (SyncEvent); // // Create the child process // wsprintfA ( CmdLine, "\"%s\" %s \"%s\"", ExePath, Win95Side ? "-r" : "-m", RemoteArg ); ZeroMemory (&si, sizeof (si)); si.cb = sizeof (si); si.dwFlags = STARTF_FORCEOFFFEEDBACK; ProcessResult = CreateProcessA ( NULL, CmdLine, NULL, NULL, FALSE, CREATE_DEFAULT_ERROR_MODE, NULL, WorkingDir, &si, &pi ); if (ProcessResult) { CloseHandle (pi.hThread); } else { LOG ((LOG_ERROR, "Cannot start %s", CmdLine)); __leave; } // // Wait for process to fail or wait for it to set the win95upg event // ObjectArray[0] = SyncEvent; ObjectArray[1] = pi.hProcess; rc = WaitForMultipleObjects (2, ObjectArray, FALSE, 60000); g_ProcessHandle = pi.hProcess; if (rc != WAIT_OBJECT_0) { DEBUGMSG (( DBG_WARNING, "Process %x did not signal 'ready'. Wait timed out. (%s)", g_ProcessHandle, g_Mode )); LOG ((LOG_ERROR, "Upgrade pack failed during process creation.")); __leave; } DEBUGMSG ((DBG_IPC, "Process %s is running (%s)", CmdLine, g_Mode)); } else { // !g_Host // // Open the IPC objects // if (!pOpenIpcData()) { DEBUGMSG ((DBG_ERROR, "Cannot open IPC channel")); __leave; } // // Set event notifying setup that we've created our mailslot // SyncEvent = OpenEvent (EVENT_ALL_ACCESS, FALSE, TEXT("win9xupg")); if (!SyncEvent) { __leave; } SetEvent (SyncEvent); } Result = TRUE; } __finally { // // Cleanup code // PushError(); if (!Result) { CloseIpc(); } if (SyncEvent) { CloseHandle (SyncEvent); } if (psd) { MemFree (g_hHeap, 0, psd); } PopError(); } return Result; } BOOL OpenIpcW ( IN BOOL Win95Side, IN PCWSTR ExePath, OPTIONAL IN PCWSTR RemoteArg, OPTIONAL IN PCWSTR WorkingDir OPTIONAL ) { PCSTR AnsiExePath, AnsiRemoteArg, AnsiWorkingDir; BOOL b; if (ExePath) { AnsiExePath = ConvertWtoA (ExePath); } else { AnsiExePath = NULL; } if (RemoteArg) { AnsiRemoteArg = ConvertWtoA (RemoteArg); } else { AnsiRemoteArg = NULL; } if (WorkingDir) { AnsiWorkingDir = ConvertWtoA (WorkingDir); } else { AnsiWorkingDir = NULL; } b = OpenIpcA (Win95Side, AnsiExePath, AnsiRemoteArg, AnsiWorkingDir); FreeConvertedStr (AnsiExePath); FreeConvertedStr (AnsiRemoteArg); FreeConvertedStr (AnsiWorkingDir); return b; } VOID CloseIpc ( VOID ) /*++ Routine Description: Tells migisol.exe process to terminate, and then cleans up all resources opened by OpenIpc. Arguments: none Return Value: none --*/ { if (g_Host) { // // Tell migisol.exe to terminate // if the communications channel is up // if (g_IpcData.Mapping && !SendIpcCommand (IPC_TERMINATE, NULL, 0)) { KillIpcProcess(); } if (g_ProcessHandle) { WaitForSingleObject (g_ProcessHandle, 10000); } } pCloseIpcData(); if (g_ProcessHandle) { CloseHandle (g_ProcessHandle); g_ProcessHandle = NULL; } } VOID pCloseIpcData ( VOID ) { if (g_IpcData.DoCommand) { CloseHandle (g_IpcData.DoCommand); g_IpcData.DoCommand = NULL; } if (g_IpcData.GetResults) { CloseHandle (g_IpcData.GetResults); g_IpcData.GetResults = NULL; } if (g_IpcData.Mapping) { CloseHandle (g_IpcData.Mapping); g_IpcData.Mapping = NULL; } } BOOL pCreateIpcData ( IN PSECURITY_ATTRIBUTES psa ) /*++ Routine Description: pCreateIpcData creates the objects necessary to transfer data between migisol.exe and w95upg*.dll. This function is called in host mode (i.e., from w95upg.dll or w95upgnt.dll). Arguments: psa - Specifies NT nul DACL, or NULL on Win9x Return Value: TRUE if the objects were created properly, or FALSE if not. --*/ { ZeroMemory (&g_IpcData, sizeof (g_IpcData)); g_IpcData.DoCommand = CreateEvent (psa, FALSE, FALSE, TEXT("Setup.DoCommand")); g_IpcData.GetResults = CreateEvent (psa, FALSE, FALSE, TEXT("Setup.GetResults")); g_IpcData.Mapping = CreateFileMapping ( INVALID_HANDLE_VALUE, psa, PAGE_READWRITE, 0, 0x10000, TEXT("Setup.IpcData") ); if (!g_IpcData.DoCommand || !g_IpcData.GetResults || !g_IpcData.Mapping ) { pCloseIpcData(); return FALSE; } return TRUE; } BOOL pOpenIpcData ( VOID ) /*++ Routine Description: pOpenIpcData opens objects necessary to transfer data between migisol.exe and w95upg*.dll. This funciton is called in remote mode (i.e., by migisol.exe). This function must be called after the host has created the objects with pCreateIpcData. Arguments: None. Return Value: TRUE of the objects were opened successfully, FALSE otherwise. --*/ { ZeroMemory (&g_IpcData, sizeof (g_IpcData)); g_IpcData.DoCommand = OpenEvent (EVENT_ALL_ACCESS, FALSE, TEXT("Setup.DoCommand")); g_IpcData.GetResults = OpenEvent (EVENT_ALL_ACCESS, FALSE, TEXT("Setup.GetResults")); g_IpcData.Mapping = OpenFileMapping ( FILE_MAP_READ|FILE_MAP_WRITE, FALSE, TEXT("Setup.IpcData") ); if (!g_IpcData.DoCommand || !g_IpcData.GetResults || !g_IpcData.Mapping ) { pCloseIpcData(); return FALSE; } return TRUE; } BOOL IsIpcProcessAlive ( VOID ) /*++ Routine Description: IsIpcProcessAlive checks for the presense of migisol.exe. This function is intended only for host mode. Arguments: None. Return Value: TRUE if migisol.exe is still running, FALSE otherwise. --*/ { if (!g_ProcessHandle) { return FALSE; } if (WaitForSingleObject (g_ProcessHandle, 0) == WAIT_OBJECT_0) { return FALSE; } return TRUE; } VOID KillIpcProcess ( VOID ) /*++ Routine Description: KillIpcProcess forcefully terminates an open migisol.exe process. This is used in GUI mode when the DLL refuses to die. Arguments: None. Return Value: None. --*/ { PushError(); if (IsIpcProcessAlive()) { TerminateProcess (g_ProcessHandle, 0); } PopError(); } DWORD CheckForWaitingData ( IN HANDLE Slot, IN DWORD MinimumSize, IN DWORD Timeout ) /*++ Routine Description: CheckForWaitingData waits for data to be received by a mailslot. If the data does not arrive within the specified timeout, then zero is returned, and ERROR_SEM_TIMEOUT is set as the last error. If the data arrives within the specified timeout, then the number of waiting bytes are returned to the caller. This routine works around a Win95 bug with GetMailslotInfo. Please change with caution. Arguments: Slot - Specifies handle to inbound mailslot MinimumSize - Specifies the number of bytes that must be available before the routine considers the data to be available. NOTE: If a message smaller than MinimumSize is waiting, this routine will be blocked until the timeout expires. This parameter must be greater than zero. Timeout - Specifies the number of milliseconds to wait for the message. Return value: The number of bytes waiting in the mailslot, or 0 if the timeout was reached. --*/ { DWORD WaitingSize; DWORD UnreliableTimeout; DWORD End; MYASSERT (MinimumSize > 0); End = GetTickCount() + Timeout; // // The wrap case -- this is really rare (once every 27 days), // so just let the tick count go back to zero // if (End < GetTickCount()) { while (End < GetTickCount()) { Sleep (100); } End = GetTickCount() + Timeout; } do { if (!GetMailslotInfo (Slot, NULL, &WaitingSize, NULL, &UnreliableTimeout)) { DEBUGMSG ((DBG_ERROR, "CheckForWaitingData: GetMailslotInfo failed (%s)", g_Mode)); return 0; } // // WARNING: Win95 doesn't always return 0xffffffff when there is no data // available. On some machines, Win9x has returned 0xc0ffffff. // WaitingSize = LOWORD(WaitingSize); if (WaitingSize < 0xffff && WaitingSize >= MinimumSize) { return WaitingSize; } } while (GetTickCount() < End); SetLastError (ERROR_SEM_TIMEOUT); return 0; } BOOL pWriteIpcData ( IN HANDLE Mapping, IN PBYTE Data, OPTIONAL IN DWORD DataSize, IN DWORD Command, IN DWORD ResultCode, IN DWORD TechnicalLogId, IN DWORD GuiLogId ) /*++ Routine Description: pWriteIpcData puts data in the memory mapped block that migisol.exe and w95upg*.dll share. The OS takes care of the synchronization for us. Arguments: Mapping - Specifies the open mapping object Data - Specifies binary data to write DataSize - Specifies the number of bytes in Data, or 0 if Data is NULL Command - Specifies a command DWORD, or 0 if not required ResultCode - Specifies the result code of the last command, or 0 if not applicable TechnicalLogId - Specifies the message constant ID (MSG_*) to be added to setupact.log, or 0 if not applicable GuiLogId - Specifies the message constant (MSG_*) of the message to be presented via a popup, or 0 if not applicable Return Value: TRUE if the data was written, FALSE if a sharing violation or other error occurs --*/ { PMAPDATA MapData; MYASSERT (Mapping); MapData = (PMAPDATA) MapViewOfFile (Mapping, FILE_MAP_WRITE, 0, 0, 0); if (!MapData) { return FALSE; } if (!Data) { DataSize = 0; } MapData->Command = Command; MapData->Result = ResultCode; MapData->TechnicalLogId = TechnicalLogId; MapData->GuiLogId = GuiLogId; MapData->DataSize = DataSize; if (DataSize) { CopyMemory (MapData->Data, Data, DataSize); } UnmapViewOfFile (MapData); return TRUE; } BOOL pReadIpcData ( IN HANDLE Mapping, OUT PBYTE *Data, OPTIONAL OUT PDWORD DataSize, OPTIONAL OUT PDWORD Command, OPTIONAL OUT PDWORD ResultCode, OPTIONAL OUT PDWORD TechnicalLogId, OPTIONAL OUT PDWORD GuiLogId OPTIONAL ) /*++ Routine Description: pReadIpcData retrieves data put in the shared memory block. The OS takes care of synchronization for us. Arguments: Mapping - Specifies the memory mapping object Data - Receives the inbound binary data, if any is available, or NULL if no data is available. The caller must free this data with MemFree. DataSize - Receives the number of bytes in Data Command - Receives the inbound command, or 0 if no command was specified ResultCode - Receives the command result code, or 0 if not applicable TechnicalLogId - Receives the message constant (MSG_*) of the message to be logged to setupact.log, or 0 if no message is to be logged GuiLogId - Receives the message constant (MSG_*) of the message to be presented in a popup, or 0 if no message is to be presented Return Value: TRUE if data was read, or FALSE if a sharing violation or other error occurs --*/ { PMAPDATA MapData; MapData = (PMAPDATA) MapViewOfFile (Mapping, FILE_MAP_READ, 0, 0, 0); if (!MapData) { return FALSE; } if (Data) { if (MapData->DataSize) { *Data = MemAlloc (g_hHeap, 0, MapData->DataSize); MYASSERT (*Data); CopyMemory (*Data, MapData->Data, MapData->DataSize); } else { *Data = NULL; } } if (DataSize) { *DataSize = MapData->DataSize; } if (Command) { *Command = MapData->Command; } if (ResultCode) { *ResultCode = MapData->Result; } if (TechnicalLogId) { *TechnicalLogId = MapData->TechnicalLogId; } if (GuiLogId) { *GuiLogId = MapData->GuiLogId; } UnmapViewOfFile (MapData); return TRUE; } BOOL SendIpcCommand ( IN DWORD Command, IN PBYTE Data, OPTIONAL IN DWORD DataSize ) /*++ Routine Description: SendIpcCommand puts a command and optional binary data in the shared memory block. It then sets the DoCommand event, triggering the other process to read the shared memory. It is required that a command result is sent before the next SendIpcCommand. See SendIpcCommandResult. Arguments: Command - Specifies the command to be executed by migisol.exe Data - Specifies the data associated with the command DataSize - Specifies the number of bytes in Data, or 0 if Data is NULL Return Value: TRUE if the command was placed in the shared memory block, FALSE otherwise --*/ { if (!pWriteIpcData ( g_IpcData.Mapping, Data, DataSize, Command, 0, 0, 0 )) { DEBUGMSG ((DBG_ERROR, "SendIpcCommand: Can't send the command to the remote process")); return FALSE; } SetEvent (g_IpcData.DoCommand); return TRUE; } BOOL GetIpcCommandResults ( IN DWORD Timeout, OUT PBYTE *ReturnData, OPTIONAL OUT PDWORD ReturnDataSize, OPTIONAL OUT PDWORD ResultCode, OPTIONAL OUT PDWORD TechnicalLogId, OPTIONAL OUT PDWORD GuiLogId OPTIONAL ) /*++ Routine Description: GetIpcCommandResults reads the shared memory block and returns the available data. Arguments: Timeout - Specifies the amount of time to wait for a command result (in ms), or INFINITE to wait forever. ReturnData - Receives the binary data associated with the command result, or NULL if no data is associated with the result. The caller must free this data with MemFree. ReturnDataSize - Receives the number of bytes in ReturnData, or 0 if ReturnData is NULL. ResultCode - Receives the command result code TechnicalLogId - Receives the message constant (MSG_*) to be logged in setupact.log, or 0 if no message is specified GuiLogId - Receives the message constant (MSG_*) of the message to be presented in a popup, or 0 if no message is to be presented Return Value: TRUE if command results were obtained, or FALSE if the wait timed out or the IPC connection crashed --*/ { DWORD rc; BOOL b; rc = WaitForSingleObject (g_IpcData.GetResults, Timeout); if (rc != WAIT_OBJECT_0) { SetLastError (ERROR_NO_DATA); return FALSE; } b = pReadIpcData ( g_IpcData.Mapping, ReturnData, ReturnDataSize, NULL, ResultCode, TechnicalLogId, GuiLogId ); return b; } BOOL GetIpcCommand ( IN DWORD Timeout, IN PDWORD Command, OPTIONAL IN PBYTE *Data, OPTIONAL IN PDWORD DataSize OPTIONAL ) /*++ Routine Description: GetIpcCommand obtains the command that needs to be processed. This routine is called by migisol.exe (the remote process). Arguments: Timeout - Specifies the amount of time (in ms) to wait for a command, or INFINITE to wait forever Command - Receives the command that needs to be executed Data - Receives the data associated with the command. The caller must free this block with MemFree. DataSize - Receives the number of bytes in Data, or 0 if Data is NULL. Return Value: TRUE if a command was received, FALSE otherwise. --*/ { DWORD rc; BOOL b; rc = WaitForSingleObject (g_IpcData.DoCommand, Timeout); if (rc != WAIT_OBJECT_0) { SetLastError (ERROR_NO_DATA); return FALSE; } b = pReadIpcData ( g_IpcData.Mapping, Data, DataSize, Command, NULL, NULL, NULL ); return b; } BOOL SendIpcCommandResults ( IN DWORD ResultCode, IN DWORD TechnicalLogId, IN DWORD GuiLogId, IN PBYTE Data, OPTIONAL IN DWORD DataSize ) /*++ Routine Description: SendIpcCommandResults puts the command results in the shared memory block. This routine is called by migisol.exe (the remote process). Arguments: ResultCode - Specifies the result code of the command. TechnicalLogId - Specifies the message constant (MSG_*) of the message to be logged in setupact.log, or 0 if no message is to be logged GuiLogId - Specifies the message constant (MSG_*) of the message to be presented in a popup to the user, or 0 if no message needs to be presented Data - Specifies the binary data to pass as command results, or NULL of no binary data is required DataSize - Specifies the number of bytes in Data, or 0 if Data is NULL Return Value: TRUE if the command results were placed in shared memory, FALSE otherwise. --*/ { BOOL b; b = pWriteIpcData ( g_IpcData.Mapping, Data, DataSize, 0, ResultCode, TechnicalLogId, GuiLogId ); if (!b) { DEBUGMSG ((DBG_ERROR, "Can't write command results to IPC buffer")); return FALSE; } SetEvent (g_IpcData.GetResults); return TRUE; } BOOL IsDllSignedA ( IN WINVERIFYTRUST WinVerifyTrustApi, IN PCSTR DllSpec ) { PCWSTR UnicodeStr; BOOL b; UnicodeStr = CreateUnicode (DllSpec); if (!UnicodeStr) { return FALSE; } b = IsDllSignedW (WinVerifyTrustApi, UnicodeStr); DestroyUnicode (UnicodeStr); return b; } BOOL IsDllSignedW ( IN WINVERIFYTRUST WinVerifyTrustApi, IN PCWSTR DllSpec ) { GUID VerifyGuid = WINTRUST_ACTION_GENERIC_VERIFY_V2; WINTRUST_DATA WinTrustData; WINTRUST_FILE_INFO WinTrustFileInfo; LONG rc; if (!WinVerifyTrustApi) { return TRUE; } ZeroMemory (&WinTrustData, sizeof (WinTrustData)); ZeroMemory (&WinTrustFileInfo, sizeof (WinTrustFileInfo)); WinTrustData.cbStruct = sizeof(WINTRUST_DATA); WinTrustData.dwUIChoice = WTD_UI_NONE; WinTrustData.dwUnionChoice = WTD_CHOICE_FILE; WinTrustData.pFile = &WinTrustFileInfo; WinTrustFileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO); WinTrustFileInfo.hFile = INVALID_HANDLE_VALUE; WinTrustFileInfo.pcwszFilePath = DllSpec; rc = WinVerifyTrustApi ( INVALID_HANDLE_VALUE, &VerifyGuid, &WinTrustData ); return rc == ERROR_SUCCESS; }