/* $Id: kWorker.c 3080 2017-10-02 12:45:07Z bird $ */ /** @file * kWorker - experimental process reuse worker for Windows. * * Note! This module must be linked statically in order to avoid * accidentally intercepting our own CRT calls. */ /* * Copyright (c) 2016 knut st. osmundsen * * This file is part of kBuild. * * kBuild is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * kBuild is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with kBuild. If not, see * */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ //#undef NDEBUG //#define K_STRICT 1 //#define KW_LOG_ENABLED #define PSAPI_VERSION 1 #include #include #include #include #include #include #include #include #include "nt/ntstat.h" #include "kbuild_version.h" #include "nt/ntstuff.h" #include #include "nt/kFsCache.h" #include "nt_fullpath.h" #include "quote_argv.h" #include "md5.h" #include "../kmk/kmkbuiltin.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** @def WITH_TEMP_MEMORY_FILES * Enables temporary memory files for cl.exe. */ #define WITH_TEMP_MEMORY_FILES /** @def WITH_HASH_MD5_CACHE * Enables caching of MD5 sums for cl.exe. * This prevents wasting time on rehashing common headers each time * they are included. */ #define WITH_HASH_MD5_CACHE /** @def WITH_CRYPT_CTX_REUSE * Enables reusing crypt contexts. The Visual C++ compiler always creates a * context which is only used for MD5 and maybe some random bytes (VS 2010). * So, only create it once and add a reference to it instead of creating new * ones. Saves registry access among other things. */ #define WITH_CRYPT_CTX_REUSE /** @def WITH_CONSOLE_OUTPUT_BUFFERING * Enables buffering of all console output as well as removal of annoying * source file echo by cl.exe. */ #define WITH_CONSOLE_OUTPUT_BUFFERING /** @def WITH_STD_OUT_ERR_BUFFERING * Enables buffering of standard output and standard error buffer as well as * removal of annoying source file echo by cl.exe. */ #define WITH_STD_OUT_ERR_BUFFERING /** @def WITH_LOG_FILE * Log to file instead of stderr. */ #define WITH_LOG_FILE /** @def WITH_HISTORY * Keep history of the last jobs. For debugging. */ #define WITH_HISTORY /** @def WITH_FIXED_VIRTUAL_ALLOCS * Whether to pre allocate memory for known fixed VirtualAlloc calls (currently * there is only one, but an important one, from cl.exe). */ #if K_ARCH == K_ARCH_X86_32 # define WITH_FIXED_VIRTUAL_ALLOCS #endif /** @def WITH_PCH_CACHING * Enables read caching of precompiled header files. */ #if K_ARCH_BITS >= 64 # define WITH_PCH_CACHING #endif #ifndef NDEBUG # define KW_LOG_ENABLED #endif /** @def KW_LOG * Generic logging. * @param a Argument list for kwDbgPrintf */ #ifdef KW_LOG_ENABLED # define KW_LOG(a) kwDbgPrintf a #else # define KW_LOG(a) do { } while (0) #endif /** @def KWLDR_LOG * Loader related logging. * @param a Argument list for kwDbgPrintf */ #ifdef KW_LOG_ENABLED # define KWLDR_LOG(a) kwDbgPrintf a #else # define KWLDR_LOG(a) do { } while (0) #endif /** @def KWFS_LOG * FS cache logging. * @param a Argument list for kwDbgPrintf */ #ifdef KW_LOG_ENABLED # define KWFS_LOG(a) kwDbgPrintf a #else # define KWFS_LOG(a) do { } while (0) #endif /** @def KWOUT_LOG * Output related logging. * @param a Argument list for kwDbgPrintf */ #ifdef KW_LOG_ENABLED # define KWOUT_LOG(a) kwDbgPrintf a #else # define KWOUT_LOG(a) do { } while (0) #endif /** @def KWCRYPT_LOG * FS cache logging. * @param a Argument list for kwDbgPrintf */ #ifdef KW_LOG_ENABLED # define KWCRYPT_LOG(a) kwDbgPrintf a #else # define KWCRYPT_LOG(a) do { } while (0) #endif /** Converts a windows handle to a handle table index. * @note We currently just mask off the 31th bit, and do no shifting or anything * else to create an index of the handle. * @todo consider shifting by 2 or 3. */ #define KW_HANDLE_TO_INDEX(a_hHandle) ((KUPTR)(a_hHandle) & ~(KUPTR)KU32_C(0x8000000)) /** Maximum handle value we can deal with. */ #define KW_HANDLE_MAX 0x20000 /** Max temporary file size (memory backed). */ #if K_ARCH_BITS >= 64 # define KWFS_TEMP_FILE_MAX (256*1024*1024) #else # define KWFS_TEMP_FILE_MAX (64*1024*1024) #endif /** Marks unfinished code. */ #if 1 # define KWFS_TODO() do { kwErrPrintf("\nHit TODO on line %u in %s!\n", __LINE__, __FUNCTION__); fflush(stderr); __debugbreak(); } while (0) #else # define KWFS_TODO() do { kwErrPrintf("\nHit TODO on line %u in %s!\n", __LINE__, __FUNCTION__); fflush(stderr); } while (0) #endif /** User data key for tools. */ #define KW_DATA_KEY_TOOL (~(KUPTR)16381) /** User data key for a cached file. */ #define KW_DATA_KEY_CACHED_FILE (~(KUPTR)65521) /** String constant comma length. */ #define TUPLE(a_sz) a_sz, sizeof(a_sz) - 1 /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ typedef enum KWLOCATION { KWLOCATION_INVALID = 0, KWLOCATION_EXE_DIR, KWLOCATION_IMPORTER_DIR, KWLOCATION_SYSTEM32, KWLOCATION_UNKNOWN_NATIVE, KWLOCATION_UNKNOWN, } KWLOCATION; typedef enum KWMODSTATE { KWMODSTATE_INVALID = 0, KWMODSTATE_NEEDS_BITS, KWMODSTATE_NEEDS_INIT, KWMODSTATE_BEING_INITED, KWMODSTATE_INIT_FAILED, KWMODSTATE_READY, } KWMODSTATE; typedef struct KWMODULE *PKWMODULE; typedef struct KWMODULE { /** Pointer to the next image. */ PKWMODULE pNext; /** The normalized path to the image. */ const char *pszPath; /** The hash of the program path. */ KU32 uHashPath; /** Number of references. */ KU32 cRefs; /** UTF-16 version of pszPath. */ const wchar_t *pwszPath; /** The offset of the filename in pszPath. */ KU16 offFilename; /** Set if executable. */ KBOOL fExe; /** Set if native module entry. */ KBOOL fNative; /** Loader module handle. */ PKLDRMOD pLdrMod; /** The windows module handle. */ HMODULE hOurMod; /** The of the loaded image bits. */ KSIZE cbImage; union { /** Data for a manually loaded image. */ struct { /** Where we load the image. */ KU8 *pbLoad; /** Virgin copy of the image. */ KU8 *pbCopy; /** Ldr pvBits argument. This is NULL till we've successfully resolved * the imports. */ void *pvBits; /** The state. */ KWMODSTATE enmState; #if defined(KBUILD_OS_WINDOWS) && defined(KBUILD_ARCH_AMD64) /** The number of entries in the table. */ KU32 cFunctions; /** The function table address (in the copy). */ PRUNTIME_FUNCTION paFunctions; /** Set if we've already registered a function table already. */ KBOOL fRegisteredFunctionTable; #endif /** Set if we share memory with other executables. */ KBOOL fUseLdBuf; /** Set after the first whole image copy is done. */ KBOOL fCanDoQuick; /** Number of quick copy chunks. */ KU8 cQuickCopyChunks; /** Number of quick zero chunks. */ KU8 cQuickZeroChunks; /** Quicker image copy instructions that skips non-writable parts when * possible. Need to check fCanDoQuick, fUseLdBuf and previous executable * image. */ struct { /** The copy destination. */ KU8 *pbDst; /** The copy source. */ KU8 const *pbSrc; /** How much to copy. */ KSIZE cbToCopy; } aQuickCopyChunks[3]; /** For handling BSS and zero alignment padding when using aQuickCopyChunks. */ struct { /** Where to start zeroing. */ KU8 *pbDst; /** How much to zero. */ KSIZE cbToZero; } aQuickZeroChunks[3]; /** TLS index if one was allocated, otherwise KU32_MAX. */ KU32 idxTls; /** Offset (RVA) of the TLS initialization data. */ KU32 offTlsInitData; /** Number of bytes of TLS initialization data. */ KU32 cbTlsInitData; /** Number of allocated bytes for TLS. */ KU32 cbTlsAlloc; /** Number of TLS callbacks. */ KU32 cTlsCallbacks; /** Offset (RVA) of the TLS callback table. */ KU32 offTlsCallbacks; /** Number of imported modules. */ KSIZE cImpMods; /** Import array (variable size). */ PKWMODULE apImpMods[1]; } Manual; } u; } KWMODULE; typedef struct KWDYNLOAD *PKWDYNLOAD; typedef struct KWDYNLOAD { /** Pointer to the next in the list. */ PKWDYNLOAD pNext; /** The module handle we present to the application. * This is the LoadLibraryEx return value for special modules and the * KWMODULE.hOurMod value for the others. */ HMODULE hmod; /** The module for non-special resource stuff, NULL if special. */ PKWMODULE pMod; /** The length of the LoadLibary filename. */ KSIZE cchRequest; /** The LoadLibrary filename. */ char szRequest[1]; } KWDYNLOAD; /** * GetModuleHandle cache for system modules frequently queried. */ typedef struct KWGETMODULEHANDLECACHE { const char *pszName; KU8 cchName; KU8 cwcName; const wchar_t *pwszName; HANDLE hmod; } KWGETMODULEHANDLECACHE; typedef KWGETMODULEHANDLECACHE *PKWGETMODULEHANDLECACHE; /** * A cached file. */ typedef struct KFSWCACHEDFILE { /** The user data core. */ KFSUSERDATA Core; /** Cached file handle. */ HANDLE hCached; /** Cached file section handle. */ HANDLE hSection; /** Cached file content. */ KU8 *pbCached; /** The file size. */ KU32 cbCached; #ifdef WITH_HASH_MD5_CACHE /** Set if we've got a valid MD5 hash in abMd5Digest. */ KBOOL fValidMd5; /** The MD5 digest if fValidMd5 is set. */ KU8 abMd5Digest[16]; #endif /** Circular self reference. Prevents the object from ever going away and * keeps it handy for debugging. */ PKFSOBJ pFsObj; /** The file path (for debugging). */ char szPath[1]; } KFSWCACHEDFILE; /** Pointer to a cached filed. */ typedef KFSWCACHEDFILE *PKFSWCACHEDFILE; #ifdef WITH_HASH_MD5_CACHE /** Pointer to a MD5 hash instance. */ typedef struct KWHASHMD5 *PKWHASHMD5; /** * A MD5 hash instance. */ typedef struct KWHASHMD5 { /** The magic value. */ KUPTR uMagic; /** Pointer to the next hash handle. */ PKWHASHMD5 pNext; /** The cached file we've associated this handle with. */ PKFSWCACHEDFILE pCachedFile; /** The number of bytes we've hashed. */ KU32 cbHashed; /** Set if this has gone wrong. */ KBOOL fGoneBad; /** Set if we're in fallback mode (file not cached). */ KBOOL fFallbackMode; /** Set if we've already finalized the digest. */ KBOOL fFinal; /** The MD5 fallback context. */ struct MD5Context Md5Ctx; /** The finalized digest. */ KU8 abDigest[16]; } KWHASHMD5; /** Magic value for KWHASHMD5::uMagic (Les McCann). */ # define KWHASHMD5_MAGIC KUPTR_C(0x19350923) #endif /* WITH_HASH_MD5_CACHE */ #ifdef WITH_TEMP_MEMORY_FILES typedef struct KWFSTEMPFILESEG *PKWFSTEMPFILESEG; typedef struct KWFSTEMPFILESEG { /** File offset of data. */ KU32 offData; /** The size of the buffer pbData points to. */ KU32 cbDataAlloc; /** The segment data. */ KU8 *pbData; } KWFSTEMPFILESEG; typedef struct KWFSTEMPFILE *PKWFSTEMPFILE; typedef struct KWFSTEMPFILE { /** Pointer to the next temporary file for this run. */ PKWFSTEMPFILE pNext; /** The UTF-16 path. (Allocated after this structure.) */ const wchar_t *pwszPath; /** The path length. */ KU16 cwcPath; /** Number of active handles using this file/mapping (<= 2). */ KU8 cActiveHandles; /** Number of active mappings (mapped views) (0 or 1). */ KU8 cMappings; /** The amount of space allocated in the segments. */ KU32 cbFileAllocated; /** The current file size. */ KU32 cbFile; /** The number of segments. */ KU32 cSegs; /** Segments making up the file. */ PKWFSTEMPFILESEG paSegs; } KWFSTEMPFILE; #endif /* WITH_TEMP_MEMORY_FILES */ #ifdef WITH_CONSOLE_OUTPUT_BUFFERING /** * Console line buffer or output full buffer. */ typedef struct KWOUTPUTSTREAMBUF { /** The main output handle. */ HANDLE hOutput; /** Our backup handle. */ HANDLE hBackup; /** Set if this is a console handle and we're in line buffered mode. * When clear, we may buffer multiple lines, though try flush on line * boundraries when ever possible. */ KBOOL fIsConsole; /** Compressed GetFileType result. */ KU8 fFileType; KU8 abPadding[2]; union { /** Line buffer mode (fIsConsole == K_TRUE). */ struct { /** Amount of pending console output in wchar_t's. */ KU32 cwcBuf; /** The allocated buffer size. */ KU32 cwcBufAlloc; /** Pending console output. */ wchar_t *pwcBuf; } Con; /** Fully buffered mode (fIsConsole == K_FALSE). */ struct { /** Amount of pending output (in chars). */ KU32 cchBuf; #ifdef WITH_STD_OUT_ERR_BUFFERING /** The allocated buffer size (in chars). */ KU32 cchBufAlloc; /** Pending output. */ char *pchBuf; #endif } Fully; } u; } KWOUTPUTSTREAMBUF; /** Pointer to a console line buffer. */ typedef KWOUTPUTSTREAMBUF *PKWOUTPUTSTREAMBUF; /** * Combined console buffer of complete lines. */ typedef struct KWCONSOLEOUTPUT { /** The console output handle. * INVALID_HANDLE_VALUE if we haven't got a console and shouldn't be doing any * combined output buffering. */ HANDLE hOutput; /** The current code page for the console. */ KU32 uCodepage; /** Amount of pending console output in wchar_t's. */ KU32 cwcBuf; /** Number of times we've flushed it in any way (for cl.exe hack). */ KU32 cFlushes; /** Pending console output. */ wchar_t wszBuf[8192]; } KWCONSOLEOUTPUT; /** Pointer to a combined console buffer. */ typedef KWCONSOLEOUTPUT *PKWCONSOLEOUTPUT; #endif /* WITH_CONSOLE_OUTPUT_BUFFERING */ /** Handle type. */ typedef enum KWHANDLETYPE { KWHANDLETYPE_INVALID = 0, KWHANDLETYPE_FSOBJ_READ_CACHE, KWHANDLETYPE_FSOBJ_READ_CACHE_MAPPING, KWHANDLETYPE_TEMP_FILE, KWHANDLETYPE_TEMP_FILE_MAPPING, KWHANDLETYPE_OUTPUT_BUF } KWHANDLETYPE; /** Handle data. */ typedef struct KWHANDLE { KWHANDLETYPE enmType; /** Number of references */ KU32 cRefs; /** The current file offset. */ KU32 offFile; /** Handle access. */ KU32 dwDesiredAccess; /** The handle. */ HANDLE hHandle; /** Type specific data. */ union { /** The file system object. */ PKFSWCACHEDFILE pCachedFile; /** Temporary file handle or mapping handle. */ PKWFSTEMPFILE pTempFile; #ifdef WITH_CONSOLE_OUTPUT_BUFFERING /** Buffered output stream. */ PKWOUTPUTSTREAMBUF pOutBuf; #endif } u; } KWHANDLE; typedef KWHANDLE *PKWHANDLE; /** * Tracking one of our memory mappings. */ typedef struct KWMEMMAPPING { /** Number of references. */ KU32 cRefs; /** The mapping type (KWHANDLETYPE_FSOBJ_READ_CACHE_MAPPING or * KWHANDLETYPE_TEMP_FILE_MAPPING). */ KWHANDLETYPE enmType; /** The mapping address. */ PVOID pvMapping; /** Type specific data. */ union { /** The file system object. */ PKFSWCACHEDFILE pCachedFile; /** Temporary file handle or mapping handle. */ PKWFSTEMPFILE pTempFile; } u; } KWMEMMAPPING; /** Pointer to a memory mapping tracker. */ typedef KWMEMMAPPING *PKWMEMMAPPING; /** Pointer to a VirtualAlloc tracker entry. */ typedef struct KWVIRTALLOC *PKWVIRTALLOC; /** * Tracking an VirtualAlloc allocation. */ typedef struct KWVIRTALLOC { PKWVIRTALLOC pNext; void *pvAlloc; KSIZE cbAlloc; /** This is KU32_MAX if not a preallocated chunk. */ KU32 idxPreAllocated; } KWVIRTALLOC; /** Pointer to a heap (HeapCreate) tracker entry. */ typedef struct KWHEAP *PKWHEAP; /** * Tracking an heap (HeapCreate) */ typedef struct KWHEAP { PKWHEAP pNext; HANDLE hHeap; } KWHEAP; /** Pointer to a FlsAlloc/TlsAlloc tracker entry. */ typedef struct KWLOCALSTORAGE *PKWLOCALSTORAGE; /** * Tracking an FlsAlloc/TlsAlloc index. */ typedef struct KWLOCALSTORAGE { PKWLOCALSTORAGE pNext; KU32 idx; } KWLOCALSTORAGE; /** Pointer to an at exit callback record */ typedef struct KWEXITCALLACK *PKWEXITCALLACK; /** * At exit callback record. */ typedef struct KWEXITCALLACK { PKWEXITCALLACK pNext; _onexit_t pfnCallback; /** At exit doesn't have an exit code. */ KBOOL fAtExit; } KWEXITCALLACK; typedef enum KWTOOLTYPE { KWTOOLTYPE_INVALID = 0, KWTOOLTYPE_SANDBOXED, KWTOOLTYPE_WATCOM, KWTOOLTYPE_EXEC, KWTOOLTYPE_END } KWTOOLTYPE; typedef enum KWTOOLHINT { KWTOOLHINT_INVALID = 0, KWTOOLHINT_NONE, KWTOOLHINT_VISUAL_CPP_CL, KWTOOLHINT_VISUAL_CPP_LINK, KWTOOLHINT_END } KWTOOLHINT; /** * A kWorker tool. */ typedef struct KWTOOL { /** The user data core structure. */ KFSUSERDATA Core; /** The normalized path to the program. */ const char *pszPath; /** UTF-16 version of pszPath. */ wchar_t const *pwszPath; /** The kind of tool. */ KWTOOLTYPE enmType; union { struct { /** The main entry point. */ KUPTR uMainAddr; /** The executable. */ PKWMODULE pExe; /** List of dynamically loaded modules. * These will be kept loaded till the tool is destroyed (if we ever do that). */ PKWDYNLOAD pDynLoadHead; /** Module array sorted by hOurMod. */ PKWMODULE *papModules; /** Number of entries in papModules. */ KU32 cModules; /** Tool hint (for hacks and such). */ KWTOOLHINT enmHint; } Sandboxed; } u; } KWTOOL; /** Pointer to a tool. */ typedef struct KWTOOL *PKWTOOL; typedef struct KWSANDBOX *PKWSANDBOX; typedef struct KWSANDBOX { /** The tool currently running in the sandbox. */ PKWTOOL pTool; /** Jump buffer. */ jmp_buf JmpBuf; /** The thread ID of the main thread (owner of JmpBuf). */ DWORD idMainThread; /** Copy of the NT TIB of the main thread. */ NT_TIB TibMainThread; /** The NT_TIB::ExceptionList value inside the try case. * We restore this prior to the longjmp. */ void *pOutXcptListHead; /** The exit code in case of longjmp. */ int rcExitCode; /** Set if we're running. */ KBOOL fRunning; /** Whether to disable caching of ".pch" files. */ KBOOL fNoPchCaching; /** The command line. */ char *pszCmdLine; /** The UTF-16 command line. */ wchar_t *pwszCmdLine; /** Number of arguments in papszArgs. */ int cArgs; /** The argument vector. */ char **papszArgs; /** The argument vector. */ wchar_t **papwszArgs; /** The _pgmptr msvcrt variable. */ char *pgmptr; /** The _wpgmptr msvcrt variable. */ wchar_t *wpgmptr; /** The _initenv msvcrt variable. */ char **initenv; /** The _winitenv msvcrt variable. */ wchar_t **winitenv; /** Size of the array we've allocated (ASSUMES nobody messes with it!). */ KSIZE cEnvVarsAllocated; /** The _environ msvcrt variable. */ char **environ; /** The _wenviron msvcrt variable. */ wchar_t **wenviron; /** The shadow _environ msvcrt variable. */ char **papszEnvVars; /** The shadow _wenviron msvcrt variable. */ wchar_t **papwszEnvVars; /** Handle table. */ PKWHANDLE *papHandles; /** Size of the handle table. */ KU32 cHandles; /** Number of active handles in the table. */ KU32 cActiveHandles; /** Number of handles in the handle table that will not be freed. */ KU32 cFixedHandles; /** Total number of leaked handles. */ KU32 cLeakedHandles; /** Number of active memory mappings in paMemMappings. */ KU32 cMemMappings; /** The allocated size of paMemMappings. */ KU32 cMemMappingsAlloc; /** Memory mappings (MapViewOfFile / UnmapViewOfFile). */ PKWMEMMAPPING paMemMappings; /** Head of the list of temporary file. */ PKWFSTEMPFILE pTempFileHead; /** Head of the virtual alloc allocations. */ PKWVIRTALLOC pVirtualAllocHead; /** Head of the heap list (HeapCreate). * This is only done from images we forcibly restore. */ PKWHEAP pHeapHead; /** Head of the FlsAlloc indexes. */ PKWLOCALSTORAGE pFlsAllocHead; /** Head of the TlsAlloc indexes. */ PKWLOCALSTORAGE pTlsAllocHead; /** The at exit callback head. * This is only done from images we forcibly restore. */ PKWEXITCALLACK pExitCallbackHead; MY_UNICODE_STRING SavedCommandLine; #ifdef WITH_HASH_MD5_CACHE /** The special MD5 hash instance. */ PKWHASHMD5 pHashHead; /** ReadFile sets these while CryptHashData claims and clears them. * * This is part of the heuristics we use for MD5 caching for header files. The * observed pattern is that c1.dll/c1xx.dll first reads a chunk of a source or * header, then passes the same buffer and read byte count to CryptHashData. */ struct { /** The cached file last read from. */ PKFSWCACHEDFILE pCachedFile; /** The file offset of the last cached read. */ KU32 offRead; /** The number of bytes read last. */ KU32 cbRead; /** The buffer pointer of the last read. */ void *pvRead; } LastHashRead; #endif #ifdef WITH_CRYPT_CTX_REUSE /** Reusable crypt contexts. */ struct { /** The creation provider type. */ KU32 dwProvType; /** The creation flags. */ KU32 dwFlags; /** The length of the container name. */ KU32 cwcContainer; /** The length of the provider name. */ KU32 cwcProvider; /** The container name string. */ wchar_t *pwszContainer; /** The provider name string. */ wchar_t *pwszProvider; /** The context handle. */ HCRYPTPROV hProv; } aCryptCtxs[4]; /** Number of reusable crypt conexts in aCryptCtxs. */ KU32 cCryptCtxs; #endif #ifdef WITH_CONSOLE_OUTPUT_BUFFERING /** The internal standard output handle. */ KWHANDLE HandleStdOut; /** The internal standard error handle. */ KWHANDLE HandleStdErr; /** Standard output (and whatever else) buffer. */ KWOUTPUTSTREAMBUF StdOut; /** Standard error buffer. */ KWOUTPUTSTREAMBUF StdErr; /** Combined buffer of completed lines. */ KWCONSOLEOUTPUT Combined; #endif } KWSANDBOX; /** Replacement function entry. */ typedef struct KWREPLACEMENTFUNCTION { /** The function name. */ const char *pszFunction; /** The length of the function name. */ KSIZE cchFunction; /** The module name (optional). */ const char *pszModule; /** The replacement function or data address. */ KUPTR pfnReplacement; /** Only replace in the executable. * @todo fix the reinitialization of non-native DLLs! */ KBOOL fOnlyExe; } KWREPLACEMENTFUNCTION; typedef KWREPLACEMENTFUNCTION const *PCKWREPLACEMENTFUNCTION; #if 0 /** Replacement function entry. */ typedef struct KWREPLACEMENTDATA { /** The function name. */ const char *pszFunction; /** The length of the function name. */ KSIZE cchFunction; /** The module name (optional). */ const char *pszModule; /** Function providing the replacement. */ KUPTR (*pfnMakeReplacement)(PKWMODULE pMod, const char *pchSymbol, KSIZE cchSymbol); } KWREPLACEMENTDATA; typedef KWREPLACEMENTDATA const *PCKWREPLACEMENTDATA; #endif /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ /** The sandbox data. */ static KWSANDBOX g_Sandbox; /** The module currently occupying g_abDefLdBuf. */ static PKWMODULE g_pModInLdBuf = NULL; /** The module that previuosly occupied g_abDefLdBuf. */ static PKWMODULE g_pModPrevInLdBuf = NULL; /** Module hash table. */ static PKWMODULE g_apModules[127]; /** GetModuleHandle cache. */ static KWGETMODULEHANDLECACHE g_aGetModuleHandleCache[] = { #define MOD_CACHE_STRINGS(str) str, sizeof(str) - 1, (sizeof(L##str) / sizeof(wchar_t)) - 1, L##str { MOD_CACHE_STRINGS("KERNEL32.DLL"), NULL }, { MOD_CACHE_STRINGS("mscoree.dll"), NULL }, }; /** Module pending TLS allocation. See kwLdrModuleCreateNonNativeSetupTls. */ static PKWMODULE g_pModPendingTlsAlloc = NULL; /** The file system cache. */ static PKFSCACHE g_pFsCache; /** The current directory (referenced). */ static PKFSOBJ g_pCurDirObj = NULL; #ifdef KBUILD_OS_WINDOWS /** The windows system32 directory (referenced). */ static PKFSDIR g_pWinSys32 = NULL; #endif /** Verbosity level. */ static int g_cVerbose = 2; /** Whether we should restart the worker. */ static KBOOL g_fRestart = K_FALSE; /** Whether control-C/SIGINT or Control-Break/SIGBREAK have been seen. */ static KBOOL volatile g_fCtrlC = K_FALSE; /* Further down. */ extern KWREPLACEMENTFUNCTION const g_aSandboxReplacements[]; extern KU32 const g_cSandboxReplacements; extern KWREPLACEMENTFUNCTION const g_aSandboxNativeReplacements[]; extern KU32 const g_cSandboxNativeReplacements; extern KWREPLACEMENTFUNCTION const g_aSandboxGetProcReplacements[]; extern KU32 const g_cSandboxGetProcReplacements; /** Create a larget BSS blob that with help of /IMAGEBASE:0x10000 should * cover the default executable link address of 0x400000. * @remarks Early main() makes it read+write+executable. Attempts as having * it as a separate section failed because the linker insists on * writing out every zero in the uninitialized section, resulting in * really big binaries. */ __declspec(align(0x1000)) static KU8 g_abDefLdBuf[16*1024*1024]; #ifdef WITH_LOG_FILE /** Log file handle. */ static HANDLE g_hLogFile = INVALID_HANDLE_VALUE; #endif #ifdef WITH_FIXED_VIRTUAL_ALLOCS /** Virtual address space reserved for CL.EXE heap manager. * * Visual C++ 2010 reserves a 78MB chunk of memory from cl.exe at a fixed * address. It's among other things used for precompiled headers, which * seemingly have addresses hardcoded into them and won't work if mapped * elsewhere. Thus, we have to make sure the area is available when cl.exe asks * for it. (The /Zm option may affect this allocation.) */ static struct { /** The memory address we need. */ KUPTR const uFixed; /** How much we need to fix. */ KSIZE const cbFixed; /** What we actually got, NULL if given back. */ void *pvReserved; /** Whether it is in use or not. */ KBOOL fInUse; } g_aFixedVirtualAllocs[] = { # if K_ARCH == K_ARCH_X86_32 /* Visual C++ 2010 reserves 0x04b00000 by default, and Visual C++ 2015 reserves 0x05300000. We get 0x0f000000 to handle large precompiled header files. */ { KUPTR_C( 0x11000000), KSIZE_C( 0x0f000000), NULL }, # else { KUPTR_C(0x000006BB00000000), KSIZE_C(0x000000002EE00000), NULL }, # endif }; #endif #ifdef WITH_HISTORY /** The job history. */ static char *g_apszHistory[32]; /** Index of the next history entry. */ static unsigned g_iHistoryNext = 0; #endif /** Number of jobs executed. */ static KU32 g_cJobs; /** Number of tools. */ static KU32 g_cTools; /** Number of modules. */ static KU32 g_cModules; /** Number of non-native modules. */ static KU32 g_cNonNativeModules; /** Number of read-cached files. */ static KU32 g_cReadCachedFiles; /** Total size of read-cached files. */ static KSIZE g_cbReadCachedFiles; /** Total number of ReadFile calls. */ static KSIZE g_cReadFileCalls; /** Total bytes read via ReadFile. */ static KSIZE g_cbReadFileTotal; /** Total number of read from read-cached files. */ static KSIZE g_cReadFileFromReadCached; /** Total bytes read from read-cached files. */ static KSIZE g_cbReadFileFromReadCached; /** Total number of read from in-memory temporary files. */ static KSIZE g_cReadFileFromInMemTemp; /** Total bytes read from in-memory temporary files. */ static KSIZE g_cbReadFileFromInMemTemp; /** Total number of WriteFile calls. */ static KSIZE g_cWriteFileCalls; /** Total bytes written via WriteFile. */ static KSIZE g_cbWriteFileTotal; /** Total number of written to from in-memory temporary files. */ static KSIZE g_cWriteFileToInMemTemp; /** Total bytes written to in-memory temporary files. */ static KSIZE g_cbWriteFileToInMemTemp; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ static FNKLDRMODGETIMPORT kwLdrModuleGetImportCallback; static int kwLdrModuleResolveAndLookup(const char *pszName, PKWMODULE pExe, PKWMODULE pImporter, const char *pszSearchPath, PKWMODULE *ppMod); static KBOOL kwSandboxHandleTableEnter(PKWSANDBOX pSandbox, PKWHANDLE pHandle, HANDLE hHandle); #ifdef WITH_CONSOLE_OUTPUT_BUFFERING static void kwSandboxConsoleWriteA(PKWSANDBOX pSandbox, PKWOUTPUTSTREAMBUF pLineBuf, const char *pchBuffer, KU32 cchToWrite); #endif static PPEB kwSandboxGetProcessEnvironmentBlock(void); /** * Debug printing. * @param pszFormat Debug format string. * @param ... Format argument. */ static void kwDbgPrintfV(const char *pszFormat, va_list va) { if (g_cVerbose >= 2) { DWORD const dwSavedErr = GetLastError(); #ifdef WITH_LOG_FILE DWORD dwIgnored; char szTmp[2048]; int cchPrefix = _snprintf(szTmp, sizeof(szTmp), "%x:%x: ", GetCurrentProcessId(), GetCurrentThreadId()); int cch = vsnprintf(&szTmp[cchPrefix], sizeof(szTmp) - cchPrefix, pszFormat, va); if (cch < (int)sizeof(szTmp) - 1 - cchPrefix) cch += cchPrefix; else { cch = sizeof(szTmp) - 1; szTmp[cch] = '\0'; } if (g_hLogFile == INVALID_HANDLE_VALUE) { wchar_t wszFilename[128]; _snwprintf(wszFilename, K_ELEMENTS(wszFilename), L"kWorker-%x-%x.log", GetTickCount(), GetCurrentProcessId()); g_hLogFile = CreateFileW(wszFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL /*pSecAttrs*/, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL /*hTemplateFile*/); } WriteFile(g_hLogFile, szTmp, cch, &dwIgnored, NULL /*pOverlapped*/); #else fprintf(stderr, "debug: "); vfprintf(stderr, pszFormat, va); #endif SetLastError(dwSavedErr); } } /** * Debug printing. * @param pszFormat Debug format string. * @param ... Format argument. */ static void kwDbgPrintf(const char *pszFormat, ...) { if (g_cVerbose >= 2) { va_list va; va_start(va, pszFormat); kwDbgPrintfV(pszFormat, va); va_end(va); } } /** * Debugger printing. * @param pszFormat Debug format string. * @param ... Format argument. */ static void kwDebuggerPrintfV(const char *pszFormat, va_list va) { if (IsDebuggerPresent()) { DWORD const dwSavedErr = GetLastError(); char szTmp[2048]; _vsnprintf(szTmp, sizeof(szTmp), pszFormat, va); OutputDebugStringA(szTmp); SetLastError(dwSavedErr); } } /** * Debugger printing. * @param pszFormat Debug format string. * @param ... Format argument. */ static void kwDebuggerPrintf(const char *pszFormat, ...) { va_list va; va_start(va, pszFormat); kwDebuggerPrintfV(pszFormat, va); va_end(va); } /** * Error printing. * @param pszFormat Message format string. * @param ... Format argument. */ static void kwErrPrintfV(const char *pszFormat, va_list va) { DWORD const dwSavedErr = GetLastError(); fprintf(stderr, "kWorker: error: "); vfprintf(stderr, pszFormat, va); SetLastError(dwSavedErr); } /** * Error printing. * @param pszFormat Message format string. * @param ... Format argument. */ static void kwErrPrintf(const char *pszFormat, ...) { va_list va; va_start(va, pszFormat); kwErrPrintfV(pszFormat, va); va_end(va); } /** * Error printing. * @return rc; * @param rc Return value * @param pszFormat Message format string. * @param ... Format argument. */ static int kwErrPrintfRc(int rc, const char *pszFormat, ...) { va_list va; va_start(va, pszFormat); kwErrPrintfV(pszFormat, va); va_end(va); return rc; } #ifdef K_STRICT KHLP_DECL(void) kHlpAssertMsg1(const char *pszExpr, const char *pszFile, unsigned iLine, const char *pszFunction) { DWORD const dwSavedErr = GetLastError(); fprintf(stderr, "\n" "!!Assertion failed!!\n" "Expression: %s\n" "Function : %s\n" "File: %s\n" "Line: %d\n" , pszExpr, pszFunction, pszFile, iLine); SetLastError(dwSavedErr); } KHLP_DECL(void) kHlpAssertMsg2(const char *pszFormat, ...) { DWORD const dwSavedErr = GetLastError(); va_list va; va_start(va, pszFormat); fprintf(stderr, pszFormat, va); va_end(va); SetLastError(dwSavedErr); } #endif /* K_STRICT */ /** * Hashes a string. * * @returns 32-bit string hash. * @param pszString String to hash. */ static KU32 kwStrHash(const char *pszString) { /* This algorithm was created for sdbm (a public-domain reimplementation of ndbm) database library. it was found to do well in scrambling bits, causing better distribution of the keys and fewer splits. it also happens to be a good general hashing function with good distribution. the actual function is hash(i) = hash(i - 1) * 65599 + str[i]; what is included below is the faster version used in gawk. [there is even a faster, duff-device version] the magic constant 65599 was picked out of thin air while experimenting with different constants, and turns out to be a prime. this is one of the algorithms used in berkeley db (see sleepycat) and elsewhere. */ KU32 uHash = 0; KU32 uChar; while ((uChar = (unsigned char)*pszString++) != 0) uHash = uChar + (uHash << 6) + (uHash << 16) - uHash; return uHash; } /** * Hashes a string. * * @returns The string length. * @param pszString String to hash. * @param puHash Where to return the 32-bit string hash. */ static KSIZE kwStrHashEx(const char *pszString, KU32 *puHash) { const char * const pszStart = pszString; KU32 uHash = 0; KU32 uChar; while ((uChar = (unsigned char)*pszString) != 0) { uHash = uChar + (uHash << 6) + (uHash << 16) - uHash; pszString++; } *puHash = uHash; return pszString - pszStart; } /** * Hashes a string. * * @returns The string length in wchar_t units. * @param pwszString String to hash. * @param puHash Where to return the 32-bit string hash. */ static KSIZE kwUtf16HashEx(const wchar_t *pwszString, KU32 *puHash) { const wchar_t * const pwszStart = pwszString; KU32 uHash = 0; KU32 uChar; while ((uChar = *pwszString) != 0) { uHash = uChar + (uHash << 6) + (uHash << 16) - uHash; pwszString++; } *puHash = uHash; return pwszString - pwszStart; } /** * Converts the given string to unicode. * * @returns Length of the resulting string in wchar_t's. * @param pszSrc The source string. * @param pwszDst The destination buffer. * @param cwcDst The size of the destination buffer in wchar_t's. */ static KSIZE kwStrToUtf16(const char *pszSrc, wchar_t *pwszDst, KSIZE cwcDst) { /* Just to the quick ASCII stuff for now. correct ansi code page stuff later some time. */ KSIZE offDst = 0; while (offDst < cwcDst) { char ch = *pszSrc++; pwszDst[offDst++] = ch; if (!ch) return offDst - 1; kHlpAssert((unsigned)ch < 127); } pwszDst[offDst - 1] = '\0'; return offDst; } /** * Converts the given string to UTF-16, allocating the buffer. * * @returns Pointer to the new heap allocation containing the UTF-16 version of * the source string. * @param pchSrc The source string. * @param cchSrc The length of the source string. */ static wchar_t *kwStrToUtf16AllocN(const char *pchSrc, KSIZE cchSrc) { DWORD const dwErrSaved = GetLastError(); KSIZE cwcBuf = cchSrc + 1; wchar_t *pwszBuf = (wchar_t *)kHlpAlloc(cwcBuf * sizeof(pwszBuf)); if (pwszBuf) { if (cchSrc > 0) { int cwcRet = MultiByteToWideChar(CP_ACP, 0, pchSrc, (int)cchSrc, pwszBuf, (int)cwcBuf - 1); if (cwcRet > 0) { kHlpAssert(cwcRet < (KSSIZE)cwcBuf); pwszBuf[cwcRet] = '\0'; } else { kHlpFree(pwszBuf); /* Figure the length and allocate the right buffer size. */ SetLastError(NO_ERROR); cwcRet = MultiByteToWideChar(CP_ACP, 0, pchSrc, (int)cchSrc, pwszBuf, 0); if (cwcRet) { cwcBuf = cwcRet + 2; pwszBuf = (wchar_t *)kHlpAlloc(cwcBuf * sizeof(pwszBuf)); if (pwszBuf) { SetLastError(NO_ERROR); cwcRet = MultiByteToWideChar(CP_ACP, 0, pchSrc, (int)cchSrc, pwszBuf, (int)cwcBuf - 1); if (cwcRet) { kHlpAssert(cwcRet < (KSSIZE)cwcBuf); pwszBuf[cwcRet] = '\0'; } else { kwErrPrintf("MultiByteToWideChar(,,%*.*s,,) -> dwErr=%d\n", cchSrc, cchSrc, pchSrc, GetLastError()); kHlpFree(pwszBuf); pwszBuf = NULL; } } } else { kwErrPrintf("MultiByteToWideChar(,,%*.*s,,NULL,0) -> dwErr=%d\n", cchSrc, cchSrc, pchSrc, GetLastError()); pwszBuf = NULL; } } } else pwszBuf[0] = '\0'; } SetLastError(dwErrSaved); return pwszBuf; } /** * Converts the given UTF-16 to a normal string. * * @returns Length of the resulting string. * @param pwszSrc The source UTF-16 string. * @param pszDst The destination buffer. * @param cbDst The size of the destination buffer in bytes. */ static KSIZE kwUtf16ToStr(const wchar_t *pwszSrc, char *pszDst, KSIZE cbDst) { /* Just to the quick ASCII stuff for now. correct ansi code page stuff later some time. */ KSIZE offDst = 0; while (offDst < cbDst) { wchar_t wc = *pwszSrc++; pszDst[offDst++] = (char)wc; if (!wc) return offDst - 1; kHlpAssert((unsigned)wc < 127); } pszDst[offDst - 1] = '\0'; return offDst; } /** * Converts the given UTF-16 to ASSI, allocating the buffer. * * @returns Pointer to the new heap allocation containing the ANSI version of * the source string. * @param pwcSrc The source string. * @param cwcSrc The length of the source string. */ static char *kwUtf16ToStrAllocN(const wchar_t *pwcSrc, KSIZE cwcSrc) { DWORD const dwErrSaved = GetLastError(); KSIZE cbBuf = cwcSrc + (cwcSrc >> 1) + 1; char *pszBuf = (char *)kHlpAlloc(cbBuf); if (pszBuf) { if (cwcSrc > 0) { int cchRet = WideCharToMultiByte(CP_ACP, 0, pwcSrc, (int)cwcSrc, pszBuf, (int)cbBuf - 1, NULL, NULL); if (cchRet > 0) { kHlpAssert(cchRet < (KSSIZE)cbBuf); pszBuf[cchRet] = '\0'; } else { kHlpFree(pszBuf); /* Figure the length and allocate the right buffer size. */ SetLastError(NO_ERROR); cchRet = WideCharToMultiByte(CP_ACP, 0, pwcSrc, (int)cwcSrc, pszBuf, 0, NULL, NULL); if (cchRet) { cbBuf = cchRet + 2; pszBuf = (char *)kHlpAlloc(cbBuf); if (pszBuf) { SetLastError(NO_ERROR); cchRet = WideCharToMultiByte(CP_ACP, 0, pwcSrc, (int)cwcSrc, pszBuf, (int)cbBuf - 1, NULL, NULL); if (cchRet) { kHlpAssert(cchRet < (KSSIZE)cbBuf); pszBuf[cchRet] = '\0'; } else { kwErrPrintf("WideCharToMultiByte(,,%*.*ls,,) -> dwErr=%d\n", cwcSrc, cwcSrc, pwcSrc, GetLastError()); kHlpFree(pszBuf); pszBuf = NULL; } } } else { kwErrPrintf("WideCharToMultiByte(,,%*.*ls,,NULL,0) -> dwErr=%d\n", cwcSrc, cwcSrc, pwcSrc, GetLastError()); pszBuf = NULL; } } } else pszBuf[0] = '\0'; } SetLastError(dwErrSaved); return pszBuf; } /** UTF-16 string length. */ static KSIZE kwUtf16Len(wchar_t const *pwsz) { KSIZE cwc = 0; while (*pwsz != '\0') cwc++, pwsz++; return cwc; } /** * Copy out the UTF-16 string following the convension of GetModuleFileName */ static DWORD kwUtf16CopyStyle1(wchar_t const *pwszSrc, wchar_t *pwszDst, KSIZE cwcDst) { KSIZE cwcSrc = kwUtf16Len(pwszSrc); if (cwcSrc + 1 <= cwcDst) { kHlpMemCopy(pwszDst, pwszSrc, (cwcSrc + 1) * sizeof(wchar_t)); return (DWORD)cwcSrc; } if (cwcDst > 0) { KSIZE cwcDstTmp = cwcDst - 1; pwszDst[cwcDstTmp] = '\0'; if (cwcDstTmp > 0) kHlpMemCopy(pwszDst, pwszSrc, cwcDstTmp); } SetLastError(ERROR_INSUFFICIENT_BUFFER); return (DWORD)cwcDst; } /** * Copy out the ANSI string following the convension of GetModuleFileName */ static DWORD kwStrCopyStyle1(char const *pszSrc, char *pszDst, KSIZE cbDst) { KSIZE cchSrc = kHlpStrLen(pszSrc); if (cchSrc + 1 <= cbDst) { kHlpMemCopy(pszDst, pszSrc, cchSrc + 1); return (DWORD)cchSrc; } if (cbDst > 0) { KSIZE cbDstTmp = cbDst - 1; pszDst[cbDstTmp] = '\0'; if (cbDstTmp > 0) kHlpMemCopy(pszDst, pszSrc, cbDstTmp); } SetLastError(ERROR_INSUFFICIENT_BUFFER); return (DWORD)cbDst; } /** * Normalizes the path so we get a consistent hash. * * @returns status code. * @param pszPath The path. * @param pszNormPath The output buffer. * @param cbNormPath The size of the output buffer. */ static int kwPathNormalize(const char *pszPath, char *pszNormPath, KSIZE cbNormPath) { KFSLOOKUPERROR enmError; PKFSOBJ pFsObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError); if (pFsObj) { KBOOL fRc; fRc = kFsCacheObjGetFullPathA(pFsObj, pszNormPath, cbNormPath, '\\'); kFsCacheObjRelease(g_pFsCache, pFsObj); if (fRc) return 0; return KERR_BUFFER_OVERFLOW; } return KERR_FILE_NOT_FOUND; } /** * Get the pointer to the filename part of the path. * * @returns Pointer to where the filename starts within the string pointed to by pszFilename. * @returns Pointer to the terminator char if no filename. * @param pszPath The path to parse. */ static wchar_t *kwPathGetFilenameW(const wchar_t *pwszPath) { const wchar_t *pwszLast = NULL; for (;;) { wchar_t wc = *pwszPath; #if K_OS == K_OS_OS2 || K_OS == K_OS_WINDOWS if (wc == '/' || wc == '\\' || wc == ':') { while ((wc = *++pwszPath) == '/' || wc == '\\' || wc == ':') /* nothing */; pwszLast = pwszPath; } #else if (wc == '/') { while ((wc = *++pszFilename) == '/') /* betsuni */; pwszLast = pwszPath; } #endif if (!wc) return (wchar_t *)(pwszLast ? pwszLast : pwszPath); pwszPath++; } } /** * Retains a new reference to the given module * @returns pMod * @param pMod The module to retain. */ static PKWMODULE kwLdrModuleRetain(PKWMODULE pMod) { kHlpAssert(pMod->cRefs > 0); kHlpAssert(pMod->cRefs < 64); pMod->cRefs++; return pMod; } /** * Releases a module reference. * * @param pMod The module to release. */ static void kwLdrModuleRelease(PKWMODULE pMod) { if (--pMod->cRefs == 0) { /* Unlink it. */ if (!pMod->fExe) { PKWMODULE pPrev = NULL; unsigned idx = pMod->uHashPath % K_ELEMENTS(g_apModules); if (g_apModules[idx] == pMod) g_apModules[idx] = pMod->pNext; else { PKWMODULE pPrev = g_apModules[idx]; kHlpAssert(pPrev != NULL); while (pPrev->pNext != pMod) { pPrev = pPrev->pNext; kHlpAssert(pPrev != NULL); } pPrev->pNext = pMod->pNext; } } /* Release import modules. */ if (!pMod->fNative) { KSIZE idx = pMod->u.Manual.cImpMods; while (idx-- > 0) if (pMod->u.Manual.apImpMods[idx]) { kwLdrModuleRelease(pMod->u.Manual.apImpMods[idx]); pMod->u.Manual.apImpMods[idx] = NULL; } } /* Free our resources. */ kLdrModClose(pMod->pLdrMod); pMod->pLdrMod = NULL; if (!pMod->fNative) { kHlpPageFree(pMod->u.Manual.pbCopy, pMod->cbImage); kHlpPageFree(pMod->u.Manual.pbLoad, pMod->cbImage); } kHlpFree(pMod); } else kHlpAssert(pMod->cRefs < 64); } /** * Links the module into the module hash table. * * @returns pMod * @param pMod The module to link. */ static PKWMODULE kwLdrModuleLink(PKWMODULE pMod) { unsigned idx = pMod->uHashPath % K_ELEMENTS(g_apModules); pMod->pNext = g_apModules[idx]; g_apModules[idx] = pMod; return pMod; } /** * Replaces imports for this module according to g_aSandboxNativeReplacements. * * @param pMod The natively loaded module to process. */ static void kwLdrModuleDoNativeImportReplacements(PKWMODULE pMod) { KSIZE const cbImage = (KSIZE)kLdrModSize(pMod->pLdrMod); KU8 const * const pbImage = (KU8 const *)pMod->hOurMod; IMAGE_DOS_HEADER const *pMzHdr = (IMAGE_DOS_HEADER const *)pbImage; IMAGE_NT_HEADERS const *pNtHdrs; IMAGE_DATA_DIRECTORY const *pDirEnt; kHlpAssert(pMod->fNative); /* * Locate the export descriptors. */ /* MZ header. */ if (pMzHdr->e_magic == IMAGE_DOS_SIGNATURE) { kHlpAssertReturnVoid((KU32)pMzHdr->e_lfanew <= cbImage - sizeof(*pNtHdrs)); pNtHdrs = (IMAGE_NT_HEADERS const *)&pbImage[pMzHdr->e_lfanew]; } else pNtHdrs = (IMAGE_NT_HEADERS const *)pbImage; /* Check PE header. */ kHlpAssertReturnVoid(pNtHdrs->Signature == IMAGE_NT_SIGNATURE); kHlpAssertReturnVoid(pNtHdrs->FileHeader.SizeOfOptionalHeader == sizeof(pNtHdrs->OptionalHeader)); /* Locate the import descriptor array. */ pDirEnt = (IMAGE_DATA_DIRECTORY const *)&pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; if ( pDirEnt->Size > 0 && pDirEnt->VirtualAddress != 0) { const IMAGE_IMPORT_DESCRIPTOR *pImpDesc = (const IMAGE_IMPORT_DESCRIPTOR *)&pbImage[pDirEnt->VirtualAddress]; KU32 cLeft = pDirEnt->Size / sizeof(*pImpDesc); MEMORY_BASIC_INFORMATION ProtInfo = { NULL, NULL, 0, 0, 0, 0, 0 }; KU8 *pbProtRange = NULL; SIZE_T cbProtRange = 0; DWORD fOldProt = 0; KU32 const cbPage = 0x1000; BOOL fRc; kHlpAssertReturnVoid(pDirEnt->VirtualAddress < cbImage); kHlpAssertReturnVoid(pDirEnt->Size < cbImage); kHlpAssertReturnVoid(pDirEnt->VirtualAddress + pDirEnt->Size <= cbImage); /* * Walk the import descriptor array. * Note! This only works if there's a backup thunk array, otherwise we cannot get at the name. */ while ( cLeft-- > 0 && pImpDesc->Name > 0 && pImpDesc->FirstThunk > 0) { KU32 iThunk; const char * const pszImport = (const char *)&pbImage[pImpDesc->Name]; PIMAGE_THUNK_DATA paThunks = (PIMAGE_THUNK_DATA)&pbImage[pImpDesc->FirstThunk]; PIMAGE_THUNK_DATA paOrgThunks = (PIMAGE_THUNK_DATA)&pbImage[pImpDesc->OriginalFirstThunk]; kHlpAssertReturnVoid(pImpDesc->Name < cbImage); kHlpAssertReturnVoid(pImpDesc->FirstThunk < cbImage); kHlpAssertReturnVoid(pImpDesc->OriginalFirstThunk < cbImage); kHlpAssertReturnVoid(pImpDesc->OriginalFirstThunk != pImpDesc->FirstThunk); kHlpAssertReturnVoid(pImpDesc->OriginalFirstThunk); /* Iterate the thunks. */ for (iThunk = 0; paOrgThunks[iThunk].u1.Ordinal != 0; iThunk++) { KUPTR const off = paOrgThunks[iThunk].u1.Function; kHlpAssertReturnVoid(off < cbImage); if (!IMAGE_SNAP_BY_ORDINAL(off)) { IMAGE_IMPORT_BY_NAME const *pName = (IMAGE_IMPORT_BY_NAME const *)&pbImage[off]; KSIZE const cchSymbol = kHlpStrLen(pName->Name); KU32 i = g_cSandboxNativeReplacements; while (i-- > 0) if ( g_aSandboxNativeReplacements[i].cchFunction == cchSymbol && kHlpMemComp(g_aSandboxNativeReplacements[i].pszFunction, pName->Name, cchSymbol) == 0) { if ( !g_aSandboxNativeReplacements[i].pszModule || kHlpStrICompAscii(g_aSandboxNativeReplacements[i].pszModule, pszImport) == 0) { KW_LOG(("%s: replacing %s!%s\n", pMod->pLdrMod->pszName, pszImport, pName->Name)); /* The .rdata section is normally read-only, so we need to make it writable first. */ if ((KUPTR)&paThunks[iThunk] - (KUPTR)pbProtRange >= cbPage) { /* Restore previous .rdata page. */ if (fOldProt) { fRc = VirtualProtect(pbProtRange, cbProtRange, fOldProt, NULL /*pfOldProt*/); kHlpAssert(fRc); fOldProt = 0; } /* Query attributes for the current .rdata page. */ pbProtRange = (KU8 *)((KUPTR)&paThunks[iThunk] & ~(KUPTR)(cbPage - 1)); cbProtRange = VirtualQuery(pbProtRange, &ProtInfo, sizeof(ProtInfo)); kHlpAssert(cbProtRange); if (cbProtRange) { switch (ProtInfo.Protect) { case PAGE_READWRITE: case PAGE_WRITECOPY: case PAGE_EXECUTE_READWRITE: case PAGE_EXECUTE_WRITECOPY: /* Already writable, nothing to do. */ break; default: kHlpAssertMsgFailed(("%#x\n", ProtInfo.Protect)); case PAGE_READONLY: cbProtRange = cbPage; fRc = VirtualProtect(pbProtRange, cbProtRange, PAGE_READWRITE, &fOldProt); break; case PAGE_EXECUTE: case PAGE_EXECUTE_READ: cbProtRange = cbPage; fRc = VirtualProtect(pbProtRange, cbProtRange, PAGE_EXECUTE_READWRITE, &fOldProt); break; } kHlpAssertStmt(fRc, fOldProt = 0); } } paThunks[iThunk].u1.AddressOfData = g_aSandboxNativeReplacements[i].pfnReplacement; break; } } } } /* Next import descriptor. */ pImpDesc++; } if (fOldProt) { DWORD fIgnore = 0; fRc = VirtualProtect(pbProtRange, cbProtRange, fOldProt, &fIgnore); kHlpAssertMsg(fRc, ("%u\n", GetLastError())); K_NOREF(fRc); } } } /** * Creates a module from a native kLdr module handle. * * @returns Module w/ 1 reference on success, NULL on failure. * @param pLdrMod The native kLdr module. * @param pszPath The normalized path to the module. * @param cbPath The module path length with terminator. * @param uHashPath The module path hash. * @param fDoReplacements Whether to do import replacements on this * module. */ static PKWMODULE kwLdrModuleCreateForNativekLdrModule(PKLDRMOD pLdrMod, const char *pszPath, KSIZE cbPath, KU32 uHashPath, KBOOL fDoReplacements) { /* * Create the entry. */ PKWMODULE pMod = (PKWMODULE)kHlpAllocZ(sizeof(*pMod) + cbPath + cbPath * 2 * sizeof(wchar_t)); if (pMod) { pMod->pwszPath = (wchar_t *)(pMod + 1); kwStrToUtf16(pszPath, (wchar_t *)pMod->pwszPath, cbPath * 2); pMod->pszPath = (char *)kHlpMemCopy((char *)&pMod->pwszPath[cbPath * 2], pszPath, cbPath); pMod->uHashPath = uHashPath; pMod->cRefs = 1; pMod->offFilename = (KU16)(kHlpGetFilename(pszPath) - pszPath); pMod->fExe = K_FALSE; pMod->fNative = K_TRUE; pMod->pLdrMod = pLdrMod; pMod->hOurMod = (HMODULE)(KUPTR)pLdrMod->aSegments[0].MapAddress; pMod->cbImage = (KSIZE)kLdrModSize(pLdrMod); if (fDoReplacements) { DWORD const dwSavedErr = GetLastError(); kwLdrModuleDoNativeImportReplacements(pMod); SetLastError(dwSavedErr); } KW_LOG(("New module: %p LB %#010x %s (native)\n", (KUPTR)pMod->pLdrMod->aSegments[0].MapAddress, kLdrModSize(pMod->pLdrMod), pMod->pszPath)); g_cModules++; return kwLdrModuleLink(pMod); } return NULL; } /** * Creates a module using the native loader. * * @returns Module w/ 1 reference on success, NULL on failure. * @param pszPath The normalized path to the module. * @param uHashPath The module path hash. * @param fDoReplacements Whether to do import replacements on this * module. */ static PKWMODULE kwLdrModuleCreateNative(const char *pszPath, KU32 uHashPath, KBOOL fDoReplacements) { /* * Open the module and check the type. */ PKLDRMOD pLdrMod; int rc = kLdrModOpenNative(pszPath, &pLdrMod); if (rc == 0) { PKWMODULE pMod = kwLdrModuleCreateForNativekLdrModule(pLdrMod, pszPath, kHlpStrLen(pszPath) + 1, uHashPath, fDoReplacements); if (pMod) return pMod; kLdrModClose(pLdrMod); } return NULL; } /** * Sets up the quick zero & copy tables for the non-native module. * * This is a worker for kwLdrModuleCreateNonNative. * * @param pMod The module. */ static void kwLdrModuleCreateNonNativeSetupQuickZeroAndCopy(PKWMODULE pMod) { PCKLDRSEG paSegs = pMod->pLdrMod->aSegments; KU32 cSegs = pMod->pLdrMod->cSegments; KU32 iSeg; KWLDR_LOG(("Setting up quick zero & copy for %s:\n", pMod->pszPath)); pMod->u.Manual.cQuickCopyChunks = 0; pMod->u.Manual.cQuickZeroChunks = 0; for (iSeg = 0; iSeg < cSegs; iSeg++) switch (paSegs[iSeg].enmProt) { case KPROT_READWRITE: case KPROT_WRITECOPY: case KPROT_EXECUTE_READWRITE: case KPROT_EXECUTE_WRITECOPY: if (paSegs[iSeg].cbMapped) { KU32 iChunk = pMod->u.Manual.cQuickCopyChunks; if (iChunk < K_ELEMENTS(pMod->u.Manual.aQuickCopyChunks)) { /* * Check for trailing zero words. */ KSIZE cbTrailingZeros; if ( paSegs[iSeg].cbMapped >= 64 * 2 * sizeof(KSIZE) && (paSegs[iSeg].cbMapped & 7) == 0 && pMod->u.Manual.cQuickZeroChunks < K_ELEMENTS(pMod->u.Manual.aQuickZeroChunks) ) { KSIZE const *pauNatural = (KSIZE const *)&pMod->u.Manual.pbCopy[(KSIZE)paSegs[iSeg].RVA]; KSIZE cNatural = paSegs[iSeg].cbMapped / sizeof(KSIZE); KSIZE idxFirstZero = cNatural; while (idxFirstZero > 0) if (pauNatural[--idxFirstZero] == 0) { /* likely */ } else { idxFirstZero++; break; } cbTrailingZeros = (cNatural - idxFirstZero) * sizeof(KSIZE); if (cbTrailingZeros < 128) cbTrailingZeros = 0; } else cbTrailingZeros = 0; /* * Add quick copy entry. */ if (cbTrailingZeros < paSegs[iSeg].cbMapped) { pMod->u.Manual.aQuickCopyChunks[iChunk].pbDst = &pMod->u.Manual.pbLoad[(KSIZE)paSegs[iSeg].RVA]; pMod->u.Manual.aQuickCopyChunks[iChunk].pbSrc = &pMod->u.Manual.pbCopy[(KSIZE)paSegs[iSeg].RVA]; pMod->u.Manual.aQuickCopyChunks[iChunk].cbToCopy = paSegs[iSeg].cbMapped - cbTrailingZeros; pMod->u.Manual.cQuickCopyChunks = iChunk + 1; KWLDR_LOG(("aQuickCopyChunks[%u]: %#p LB %#" KSIZE_PRI " <- %p (%*.*s)\n", iChunk, pMod->u.Manual.aQuickCopyChunks[iChunk].pbDst, pMod->u.Manual.aQuickCopyChunks[iChunk].cbToCopy, pMod->u.Manual.aQuickCopyChunks[iChunk].pbSrc, paSegs[iSeg].cchName, paSegs[iSeg].cchName, paSegs[iSeg].pchName)); } /* * Add quick zero entry. */ if (cbTrailingZeros) { KU32 iZero = pMod->u.Manual.cQuickZeroChunks; pMod->u.Manual.aQuickZeroChunks[iZero].pbDst = pMod->u.Manual.aQuickCopyChunks[iChunk].pbDst + pMod->u.Manual.aQuickCopyChunks[iChunk].cbToCopy; pMod->u.Manual.aQuickZeroChunks[iZero].cbToZero = cbTrailingZeros; pMod->u.Manual.cQuickZeroChunks = iZero + 1; KWLDR_LOG(("aQuickZeroChunks[%u]: %#p LB %#" KSIZE_PRI " <- zero (%*.*s)\n", iZero, pMod->u.Manual.aQuickZeroChunks[iZero].pbDst, pMod->u.Manual.aQuickZeroChunks[iZero].cbToZero, paSegs[iSeg].cchName, paSegs[iSeg].cchName, paSegs[iSeg].pchName)); } } else { /* * We're out of quick copy table entries, so just copy the whole darn thing. * We cannot 104% guarantee that the segments are in mapping order, so this is simpler. */ kHlpAssertFailed(); pMod->u.Manual.aQuickCopyChunks[0].pbDst = pMod->u.Manual.pbLoad; pMod->u.Manual.aQuickCopyChunks[0].pbSrc = pMod->u.Manual.pbCopy; pMod->u.Manual.aQuickCopyChunks[0].cbToCopy = pMod->cbImage; pMod->u.Manual.cQuickCopyChunks = 1; KWLDR_LOG(("Quick copy not possible!\n")); return; } } break; default: break; } } /** * Called from TLS allocation DLL during DLL_PROCESS_ATTACH. * * @param hDll The DLL handle. * @param idxTls The allocated TLS index. * @param ppfnTlsCallback Pointer to the TLS callback table entry. */ __declspec(dllexport) void kwLdrTlsAllocationHook(void *hDll, ULONG idxTls, PIMAGE_TLS_CALLBACK *ppfnTlsCallback) { /* * Do the module initialization thing first. */ PKWMODULE pMod = g_pModPendingTlsAlloc; if (pMod) { PPEB pPeb = kwSandboxGetProcessEnvironmentBlock(); LIST_ENTRY *pHead; LIST_ENTRY *pCur; pMod->u.Manual.idxTls = idxTls; KWLDR_LOG(("kwLdrTlsAllocationHook: idxTls=%d (%#x) for %s\n", idxTls, idxTls, pMod->pszPath)); /* * Try sabotage the DLL name so we can load this module again. */ pHead = &pPeb->Ldr->InMemoryOrderModuleList; for (pCur = pHead->Blink; pCur != pHead; pCur = pCur->Blink) { LDR_DATA_TABLE_ENTRY *pMte; pMte = (LDR_DATA_TABLE_ENTRY *)((KUPTR)pCur - K_OFFSETOF(LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks)); if (((KUPTR)pMte->DllBase & ~(KUPTR)31) == ((KUPTR)hDll & ~(KUPTR)31)) { PUNICODE_STRING pStr = &pMte->FullDllName; KSIZE off = pStr->Length / sizeof(pStr->Buffer[0]); pStr->Buffer[--off]++; pStr->Buffer[--off]++; pStr->Buffer[--off]++; KWLDR_LOG(("kwLdrTlsAllocationHook: patched the MTE (%p) for %p\n", pMte, hDll)); break; } } } } /** * Allocates and initializes TLS variables. * * @returns 0 on success, non-zero failure. * @param pMod The module. */ static int kwLdrModuleCreateNonNativeSetupTls(PKWMODULE pMod) { KU8 *pbImg = (KU8 *)pMod->u.Manual.pbCopy; IMAGE_NT_HEADERS const *pNtHdrs; IMAGE_DATA_DIRECTORY const *pTlsDir; if (((PIMAGE_DOS_HEADER)pbImg)->e_magic == IMAGE_DOS_SIGNATURE) pNtHdrs = (PIMAGE_NT_HEADERS)&pbImg[((PIMAGE_DOS_HEADER)pbImg)->e_lfanew]; else pNtHdrs = (PIMAGE_NT_HEADERS)pbImg; kHlpAssert(pNtHdrs->Signature == IMAGE_NT_SIGNATURE); pTlsDir = &pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_TLS]; if (pTlsDir->Size >= sizeof(IMAGE_TLS_DIRECTORY)) { PIMAGE_TLS_DIRECTORY const paEntries = (PIMAGE_TLS_DIRECTORY)&pbImg[pTlsDir->VirtualAddress]; KU32 const cEntries = pTlsDir->Size / sizeof(IMAGE_TLS_DIRECTORY); KU32 iEntry; KUPTR offIndex; KUPTR offCallbacks; KUPTR const *puCallbacks; KSIZE cbData; const wchar_t *pwszTlsDll; HMODULE hmodTlsDll; /* * Check and log. */ for (iEntry = 0; iEntry < cEntries; iEntry++) { KUPTR offIndex = (KUPTR)paEntries[iEntry].AddressOfIndex - (KUPTR)pMod->u.Manual.pbLoad; KUPTR offCallbacks = (KUPTR)paEntries[iEntry].AddressOfCallBacks - (KUPTR)pMod->u.Manual.pbLoad; KUPTR const *puCallbacks = (KUPTR const *)&pbImg[offCallbacks]; KWLDR_LOG(("TLS DIR #%u: %#x-%#x idx=@%#x (%#x) callbacks=@%#x (%#x) cbZero=%#x flags=%#x\n", iEntry, paEntries[iEntry].StartAddressOfRawData, paEntries[iEntry].EndAddressOfRawData, paEntries[iEntry].AddressOfIndex, offIndex, paEntries[iEntry].AddressOfCallBacks, offCallbacks, paEntries[iEntry].SizeOfZeroFill, paEntries[iEntry].Characteristics)); if (offIndex >= pMod->cbImage) { kwErrPrintf("TLS entry #%u in %s has an invalid index address: %p, RVA %p, image size %#x\n", iEntry, pMod->pszPath, paEntries[iEntry].AddressOfIndex, offIndex, pMod->cbImage); return -1; } if (offCallbacks >= pMod->cbImage) { kwErrPrintf("TLS entry #%u in %s has an invalid callbacks address: %p, RVA %p, image size %#x\n", iEntry, pMod->pszPath, paEntries[iEntry].AddressOfCallBacks, offCallbacks, pMod->cbImage); return -1; } while (*puCallbacks != 0) { KWLDR_LOG(("TLS DIR #%u: callback %p, RVA %#x\n", iEntry, *puCallbacks, *puCallbacks - (KUPTR)pMod->u.Manual.pbLoad)); puCallbacks++; } if (paEntries[iEntry].Characteristics > IMAGE_SCN_ALIGN_16BYTES) { kwErrPrintf("TLS entry #%u in %s has an unsupported alignment restriction: %#x\n", iEntry, pMod->pszPath, paEntries[iEntry].Characteristics); return -1; } } if (cEntries > 1) { kwErrPrintf("More than one TLS directory entry in %s: %u\n", pMod->pszPath, cEntries); return -1; } /* * Make the allocation by loading a new instance of one of the TLS dlls. * The DLL will make a call to */ offIndex = (KUPTR)paEntries[0].AddressOfIndex - (KUPTR)pMod->u.Manual.pbLoad; offCallbacks = (KUPTR)paEntries[0].AddressOfCallBacks - (KUPTR)pMod->u.Manual.pbLoad; puCallbacks = (KUPTR const *)&pbImg[offCallbacks]; cbData = paEntries[0].SizeOfZeroFill + (paEntries[0].EndAddressOfRawData - paEntries[0].StartAddressOfRawData); if (cbData <= 1024) pwszTlsDll = L"kWorkerTls1K.dll"; else if (cbData <= 65536) pwszTlsDll = L"kWorkerTls64K.dll"; else if (cbData <= 524288) pwszTlsDll = L"kWorkerTls512K.dll"; else { kwErrPrintf("TLS data size in %s is too big: %u (%#p), max 512KB\n", pMod->pszPath, (unsigned)cbData, cbData); return -1; } pMod->u.Manual.idxTls = KU32_MAX; pMod->u.Manual.offTlsInitData = (KU32)((KUPTR)paEntries[0].StartAddressOfRawData - (KUPTR)pMod->u.Manual.pbLoad); pMod->u.Manual.cbTlsInitData = (KU32)(paEntries[0].EndAddressOfRawData - paEntries[0].StartAddressOfRawData); pMod->u.Manual.cbTlsAlloc = (KU32)cbData; pMod->u.Manual.cTlsCallbacks = 0; while (puCallbacks[pMod->u.Manual.cTlsCallbacks] != 0) pMod->u.Manual.cTlsCallbacks++; pMod->u.Manual.offTlsCallbacks = pMod->u.Manual.cTlsCallbacks ? (KU32)offCallbacks : KU32_MAX; g_pModPendingTlsAlloc = pMod; hmodTlsDll = LoadLibraryExW(pwszTlsDll, NULL /*hFile*/, 0); g_pModPendingTlsAlloc = NULL; if (hmodTlsDll == NULL) { kwErrPrintf("TLS allocation failed for '%s': LoadLibraryExW(%ls) -> %u\n", pMod->pszPath, pwszTlsDll, GetLastError()); return -1; } if (pMod->u.Manual.idxTls == KU32_MAX) { kwErrPrintf("TLS allocation failed for '%s': idxTls = KU32_MAX\n", pMod->pszPath, GetLastError()); return -1; } *(KU32 *)&pMod->u.Manual.pbCopy[offIndex] = pMod->u.Manual.idxTls; KWLDR_LOG(("kwLdrModuleCreateNonNativeSetupTls: idxTls=%d hmodTlsDll=%p (%ls) cbData=%#x\n", pMod->u.Manual.idxTls, hmodTlsDll, pwszTlsDll, cbData)); } return 0; } /** * Creates a module using the our own loader. * * @returns Module w/ 1 reference on success, NULL on failure. * @param pszPath The normalized path to the module. * @param uHashPath The module path hash. * @param fExe K_TRUE if this is an executable image, K_FALSE * if not. Executable images does not get entered * into the global module table. * @param pExeMod The executable module of the process (for * resolving imports). NULL if fExe is set. * @param pszSearchPath The PATH to search for imports. Can be NULL. */ static PKWMODULE kwLdrModuleCreateNonNative(const char *pszPath, KU32 uHashPath, KBOOL fExe, PKWMODULE pExeMod, const char *pszSearchPath) { /* * Open the module and check the type. */ PKLDRMOD pLdrMod; int rc = kLdrModOpen(pszPath, 0 /*fFlags*/, (KCPUARCH)K_ARCH, &pLdrMod); if (rc == 0) { switch (pLdrMod->enmType) { case KLDRTYPE_EXECUTABLE_FIXED: case KLDRTYPE_EXECUTABLE_RELOCATABLE: case KLDRTYPE_EXECUTABLE_PIC: if (!fExe) rc = KERR_GENERAL_FAILURE; break; case KLDRTYPE_SHARED_LIBRARY_RELOCATABLE: case KLDRTYPE_SHARED_LIBRARY_PIC: case KLDRTYPE_SHARED_LIBRARY_FIXED: if (fExe) rc = KERR_GENERAL_FAILURE; break; default: rc = KERR_GENERAL_FAILURE; break; } if (rc == 0) { KI32 cImports = kLdrModNumberOfImports(pLdrMod, NULL /*pvBits*/); if (cImports >= 0) { /* * Create the entry. */ KSIZE cbPath = kHlpStrLen(pszPath) + 1; PKWMODULE pMod = (PKWMODULE)kHlpAllocZ(sizeof(*pMod) + sizeof(pMod) * cImports + cbPath + cbPath * 2 * sizeof(wchar_t)); if (pMod) { KBOOL fFixed; pMod->cRefs = 1; pMod->offFilename = (KU16)(kHlpGetFilename(pszPath) - pszPath); pMod->uHashPath = uHashPath; pMod->fExe = fExe; pMod->fNative = K_FALSE; pMod->pLdrMod = pLdrMod; pMod->u.Manual.cImpMods = (KU32)cImports; #if defined(KBUILD_OS_WINDOWS) && defined(KBUILD_ARCH_AMD64) pMod->u.Manual.fRegisteredFunctionTable = K_FALSE; #endif pMod->u.Manual.fUseLdBuf = K_FALSE; pMod->u.Manual.fCanDoQuick = K_FALSE; pMod->u.Manual.cQuickZeroChunks = 0; pMod->u.Manual.cQuickCopyChunks = 0; pMod->u.Manual.idxTls = KU32_MAX; pMod->u.Manual.offTlsInitData = KU32_MAX; pMod->u.Manual.cbTlsInitData = 0; pMod->u.Manual.cbTlsAlloc = 0; pMod->u.Manual.cTlsCallbacks = 0; pMod->u.Manual.offTlsCallbacks = 0; pMod->pszPath = (char *)kHlpMemCopy(&pMod->u.Manual.apImpMods[cImports + 1], pszPath, cbPath); pMod->pwszPath = (wchar_t *)(pMod->pszPath + cbPath + (cbPath & 1)); kwStrToUtf16(pMod->pszPath, (wchar_t *)pMod->pwszPath, cbPath * 2); /* * Figure out where to load it and get memory there. */ fFixed = pLdrMod->enmType == KLDRTYPE_EXECUTABLE_FIXED || pLdrMod->enmType == KLDRTYPE_SHARED_LIBRARY_FIXED; pMod->u.Manual.pbLoad = fFixed ? (KU8 *)(KUPTR)pLdrMod->aSegments[0].LinkAddress : NULL; pMod->cbImage = (KSIZE)kLdrModSize(pLdrMod); if ( !fFixed || pLdrMod->enmType != KLDRTYPE_EXECUTABLE_FIXED /* only allow fixed executables */ || (KUPTR)pMod->u.Manual.pbLoad - (KUPTR)g_abDefLdBuf >= sizeof(g_abDefLdBuf) || sizeof(g_abDefLdBuf) - (KUPTR)pMod->u.Manual.pbLoad - (KUPTR)g_abDefLdBuf < pMod->cbImage) rc = kHlpPageAlloc((void **)&pMod->u.Manual.pbLoad, pMod->cbImage, KPROT_EXECUTE_READWRITE, fFixed); else pMod->u.Manual.fUseLdBuf = K_TRUE; if (rc == 0) { rc = kHlpPageAlloc(&pMod->u.Manual.pbCopy, pMod->cbImage, KPROT_READWRITE, K_FALSE); if (rc == 0) { KI32 iImp; /* * Link the module (unless it's an executable image) and process the imports. */ pMod->hOurMod = (HMODULE)pMod->u.Manual.pbLoad; if (!fExe) kwLdrModuleLink(pMod); KW_LOG(("New module: %p LB %#010x %s (kLdr)\n", pMod->u.Manual.pbLoad, pMod->cbImage, pMod->pszPath)); KW_LOG(("TODO: .reload /f %s=%p\n", pMod->pszPath, pMod->u.Manual.pbLoad)); kwDebuggerPrintf("TODO: .reload /f %s=%p\n", pMod->pszPath, pMod->u.Manual.pbLoad); for (iImp = 0; iImp < cImports; iImp++) { char szName[1024]; rc = kLdrModGetImport(pMod->pLdrMod, NULL /*pvBits*/, iImp, szName, sizeof(szName)); if (rc == 0) { rc = kwLdrModuleResolveAndLookup(szName, pExeMod, pMod, pszSearchPath, &pMod->u.Manual.apImpMods[iImp]); if (rc == 0) continue; } break; } if (rc == 0) { rc = kLdrModGetBits(pLdrMod, pMod->u.Manual.pbCopy, (KUPTR)pMod->u.Manual.pbLoad, kwLdrModuleGetImportCallback, pMod); if (rc == 0) { #if defined(KBUILD_OS_WINDOWS) && defined(KBUILD_ARCH_AMD64) /* * Find the function table. No validation here because the * loader did that already, right... */ KU8 *pbImg = (KU8 *)pMod->u.Manual.pbCopy; IMAGE_NT_HEADERS const *pNtHdrs; IMAGE_DATA_DIRECTORY const *pXcptDir; if (((PIMAGE_DOS_HEADER)pbImg)->e_magic == IMAGE_DOS_SIGNATURE) pNtHdrs = (PIMAGE_NT_HEADERS)&pbImg[((PIMAGE_DOS_HEADER)pbImg)->e_lfanew]; else pNtHdrs = (PIMAGE_NT_HEADERS)pbImg; pXcptDir = &pNtHdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION]; kHlpAssert(pNtHdrs->Signature == IMAGE_NT_SIGNATURE); if (pXcptDir->Size > 0) { pMod->u.Manual.cFunctions = pXcptDir->Size / sizeof(pMod->u.Manual.paFunctions[0]); kHlpAssert( pMod->u.Manual.cFunctions * sizeof(pMod->u.Manual.paFunctions[0]) == pXcptDir->Size); pMod->u.Manual.paFunctions = (PRUNTIME_FUNCTION)&pbImg[pXcptDir->VirtualAddress]; } else { pMod->u.Manual.cFunctions = 0; pMod->u.Manual.paFunctions = NULL; } #endif kwLdrModuleCreateNonNativeSetupQuickZeroAndCopy(pMod); rc = kwLdrModuleCreateNonNativeSetupTls(pMod); if (rc == 0) { /* * Final finish. */ pMod->u.Manual.pvBits = pMod->u.Manual.pbCopy; pMod->u.Manual.enmState = KWMODSTATE_NEEDS_BITS; g_cModules++; g_cNonNativeModules++; return pMod; } } else kwErrPrintf("kLdrModGetBits failed for %s: %#x (%d)\n", pszPath, rc, rc); } kwLdrModuleRelease(pMod); return NULL; } kHlpPageFree(pMod->u.Manual.pbLoad, pMod->cbImage); kwErrPrintf("Failed to allocate %#x bytes\n", pMod->cbImage); } else if (fFixed) kwErrPrintf("Failed to allocate %#x bytes at %p\n", pMod->cbImage, (void *)(KUPTR)pLdrMod->aSegments[0].LinkAddress); else kwErrPrintf("Failed to allocate %#x bytes\n", pMod->cbImage); } } } kLdrModClose(pLdrMod); } else kwErrPrintf("kLdrOpen failed with %#x (%d) for %s\n", rc, rc, pszPath); return NULL; } /** Implements FNKLDRMODGETIMPORT, used by kwLdrModuleCreate. */ static int kwLdrModuleGetImportCallback(PKLDRMOD pMod, KU32 iImport, KU32 iSymbol, const char *pchSymbol, KSIZE cchSymbol, const char *pszVersion, PKLDRADDR puValue, KU32 *pfKind, void *pvUser) { PKWMODULE pCurMod = (PKWMODULE)pvUser; PKWMODULE pImpMod = pCurMod->u.Manual.apImpMods[iImport]; int rc; K_NOREF(pMod); if (pImpMod->fNative) rc = kLdrModQuerySymbol(pImpMod->pLdrMod, NULL /*pvBits*/, KLDRMOD_BASEADDRESS_MAP, iSymbol, pchSymbol, cchSymbol, pszVersion, NULL /*pfnGetForwarder*/, NULL /*pvUSer*/, puValue, pfKind); else rc = kLdrModQuerySymbol(pImpMod->pLdrMod, pImpMod->u.Manual.pvBits, (KUPTR)pImpMod->u.Manual.pbLoad, iSymbol, pchSymbol, cchSymbol, pszVersion, NULL /*pfnGetForwarder*/, NULL /*pvUSer*/, puValue, pfKind); if (rc == 0) { KU32 i = g_cSandboxReplacements; while (i-- > 0) if ( g_aSandboxReplacements[i].cchFunction == cchSymbol && kHlpMemComp(g_aSandboxReplacements[i].pszFunction, pchSymbol, cchSymbol) == 0) { if ( !g_aSandboxReplacements[i].pszModule || kHlpStrICompAscii(g_aSandboxReplacements[i].pszModule, &pImpMod->pszPath[pImpMod->offFilename]) == 0) { if ( pCurMod->fExe || !g_aSandboxReplacements[i].fOnlyExe) { KW_LOG(("replacing %s!%s\n",&pImpMod->pszPath[pImpMod->offFilename], g_aSandboxReplacements[i].pszFunction)); *puValue = g_aSandboxReplacements[i].pfnReplacement; } break; } } } //printf("iImport=%u (%s) %*.*s rc=%d\n", iImport, &pImpMod->pszPath[pImpMod->offFilename], cchSymbol, cchSymbol, pchSymbol, rc); KW_LOG(("iImport=%u (%s) %*.*s rc=%d\n", iImport, &pImpMod->pszPath[pImpMod->offFilename], cchSymbol, cchSymbol, pchSymbol, rc)); return rc; } /** * Gets the main entrypoint for a module. * * @returns 0 on success, KERR on failure * @param pMod The module. * @param puAddrMain Where to return the address. */ static int kwLdrModuleQueryMainEntrypoint(PKWMODULE pMod, KUPTR *puAddrMain) { KLDRADDR uLdrAddrMain; int rc = kLdrModQueryMainEntrypoint(pMod->pLdrMod, pMod->u.Manual.pvBits, (KUPTR)pMod->u.Manual.pbLoad, &uLdrAddrMain); if (rc == 0) { *puAddrMain = (KUPTR)uLdrAddrMain; return 0; } return rc; } /** * Whether to apply g_aSandboxNativeReplacements to the imports of this module. * * @returns K_TRUE/K_FALSE. * @param pszFilename The filename (no path). * @param enmLocation The location. */ static KBOOL kwLdrModuleShouldDoNativeReplacements(const char *pszFilename, KWLOCATION enmLocation) { if (enmLocation != KWLOCATION_SYSTEM32) return K_TRUE; return kHlpStrNICompAscii(pszFilename, TUPLE("msvc")) == 0 || kHlpStrNICompAscii(pszFilename, TUPLE("msdis")) == 0 || kHlpStrNICompAscii(pszFilename, TUPLE("mspdb")) == 0; } /** * Lazily initializes the g_pWinSys32 variable. */ static PKFSDIR kwLdrResolveWinSys32(void) { KFSLOOKUPERROR enmError; PKFSDIR pWinSys32; /* Get the path first. */ char szSystem32[MAX_PATH]; if (GetSystemDirectoryA(szSystem32, sizeof(szSystem32)) >= sizeof(szSystem32)) { kwErrPrintf("GetSystemDirectory failed: %u\n", GetLastError()); strcpy(szSystem32, "C:\\Windows\\System32"); } /* Look it up and verify it. */ pWinSys32 = (PKFSDIR)kFsCacheLookupA(g_pFsCache, szSystem32, &enmError); if (pWinSys32) { if (pWinSys32->Obj.bObjType == KFSOBJ_TYPE_DIR) { g_pWinSys32 = pWinSys32; return pWinSys32; } kwErrPrintf("System directory '%s' isn't of 'DIR' type: %u\n", szSystem32, g_pWinSys32->Obj.bObjType); } else kwErrPrintf("Failed to lookup system directory '%s': %u\n", szSystem32, enmError); return NULL; } /** * Whether we can load this DLL natively or not. * * @returns K_TRUE/K_FALSE. * @param pszFilename The filename (no path). * @param enmLocation The location. * @param pszFullPath The full filename and path. */ static KBOOL kwLdrModuleCanLoadNatively(const char *pszFilename, KWLOCATION enmLocation, const char *pszFullPath) { if (enmLocation == KWLOCATION_SYSTEM32) return K_TRUE; if (enmLocation == KWLOCATION_UNKNOWN_NATIVE) return K_TRUE; /* If the location is unknown, we must check if it's some dynamic loading of a SYSTEM32 DLL with a full path. We do not want to load these ourselves! */ if (enmLocation == KWLOCATION_UNKNOWN) { PKFSDIR pWinSys32 = g_pWinSys32; if (!pWinSys32) pWinSys32 = kwLdrResolveWinSys32(); if (pWinSys32) { KFSLOOKUPERROR enmError; PKFSOBJ pFsObj = kFsCacheLookupA(g_pFsCache, pszFullPath, &enmError); if (pFsObj) { KBOOL fInWinSys32 = pFsObj->pParent == pWinSys32; kFsCacheObjRelease(g_pFsCache, pFsObj); if (fInWinSys32) return K_TRUE; } } } return kHlpStrNICompAscii(pszFilename, TUPLE("msvc")) == 0 || kHlpStrNICompAscii(pszFilename, TUPLE("msdis")) == 0 || kHlpStrNICompAscii(pszFilename, TUPLE("mspdb")) == 0; } /** * Check if the path leads to a regular file (that exists). * * @returns K_TRUE / K_FALSE * @param pszPath Path to the file to check out. */ static KBOOL kwLdrModuleIsRegularFile(const char *pszPath) { /* For stuff with .DLL extensions, we can use the GetFileAttribute cache to speed this up! */ KSIZE cchPath = kHlpStrLen(pszPath); if ( cchPath > 3 && pszPath[cchPath - 4] == '.' && (pszPath[cchPath - 3] == 'd' || pszPath[cchPath - 3] == 'D') && (pszPath[cchPath - 2] == 'l' || pszPath[cchPath - 2] == 'L') && (pszPath[cchPath - 1] == 'l' || pszPath[cchPath - 1] == 'L') ) { KFSLOOKUPERROR enmError; PKFSOBJ pFsObj = kFsCacheLookupNoMissingA(g_pFsCache, pszPath, &enmError); if (pFsObj) { KBOOL fRc = pFsObj->bObjType == KFSOBJ_TYPE_FILE; kFsCacheObjRelease(g_pFsCache, pFsObj); return fRc; } } else { BirdStat_T Stat; int rc = birdStatFollowLink(pszPath, &Stat); if (rc == 0) { if (S_ISREG(Stat.st_mode)) return K_TRUE; } } return K_FALSE; } /** * Worker for kwLdrModuleResolveAndLookup that checks out one possibility. * * If the file exists, we consult the module hash table before trying to load it * off the disk. * * @returns Pointer to module on success, NULL if not found, ~(KUPTR)0 on * failure. * @param pszPath The name of the import module. * @param enmLocation The location we're searching. This is used in * the heuristics for determining if we can use the * native loader or need to sandbox the DLL. * @param pExe The executable (optional). * @param pszSearchPath The PATH to search (optional). */ static PKWMODULE kwLdrModuleTryLoadDll(const char *pszPath, KWLOCATION enmLocation, PKWMODULE pExeMod, const char *pszSearchPath) { /* * Does the file exists and is it a regular file? */ if (kwLdrModuleIsRegularFile(pszPath)) { /* * Yes! Normalize it and look it up in the hash table. */ char szNormPath[1024]; int rc = kwPathNormalize(pszPath, szNormPath, sizeof(szNormPath)); if (rc == 0) { const char *pszName; KU32 const uHashPath = kwStrHash(szNormPath); unsigned idxHash = uHashPath % K_ELEMENTS(g_apModules); PKWMODULE pMod = g_apModules[idxHash]; if (pMod) { do { if ( pMod->uHashPath == uHashPath && kHlpStrComp(pMod->pszPath, szNormPath) == 0) return kwLdrModuleRetain(pMod); pMod = pMod->pNext; } while (pMod); } /* * Not in the hash table, so we have to load it from scratch. */ pszName = kHlpGetFilename(szNormPath); if (kwLdrModuleCanLoadNatively(pszName, enmLocation, szNormPath)) pMod = kwLdrModuleCreateNative(szNormPath, uHashPath, kwLdrModuleShouldDoNativeReplacements(pszName, enmLocation)); else pMod = kwLdrModuleCreateNonNative(szNormPath, uHashPath, K_FALSE /*fExe*/, pExeMod, pszSearchPath); if (pMod) return pMod; return (PKWMODULE)~(KUPTR)0; } } return NULL; } /** * Gets a reference to the module by the given name. * * We must do the search path thing, as our hash table may multiple DLLs with * the same base name due to different tools version and similar. We'll use a * modified search sequence, though. No point in searching the current * directory for instance. * * @returns 0 on success, KERR on failure. * @param pszName The name of the import module. * @param pExe The executable (optional). * @param pImporter The module doing the importing (optional). * @param pszSearchPath The PATH to search (optional). * @param ppMod Where to return the module pointer w/ reference. */ static int kwLdrModuleResolveAndLookup(const char *pszName, PKWMODULE pExe, PKWMODULE pImporter, const char *pszSearchPath, PKWMODULE *ppMod) { KSIZE const cchName = kHlpStrLen(pszName); char szPath[1024]; char *psz; PKWMODULE pMod = NULL; KBOOL fNeedSuffix = *kHlpGetExt(pszName) == '\0' && kHlpGetFilename(pszName) == pszName; KSIZE cchSuffix = fNeedSuffix ? 4 : 0; /* The import path. */ if (pMod == NULL && pImporter != NULL) { if (pImporter->offFilename + cchName + cchSuffix >= sizeof(szPath)) return KERR_BUFFER_OVERFLOW; psz = (char *)kHlpMemPCopy(kHlpMemPCopy(szPath, pImporter->pszPath, pImporter->offFilename), pszName, cchName + 1); if (fNeedSuffix) kHlpMemCopy(psz - 1, ".dll", sizeof(".dll")); pMod = kwLdrModuleTryLoadDll(szPath, KWLOCATION_IMPORTER_DIR, pExe, pszSearchPath); } /* Application directory first. */ if (pMod == NULL && pExe != NULL && pExe != pImporter) { if (pExe->offFilename + cchName + cchSuffix >= sizeof(szPath)) return KERR_BUFFER_OVERFLOW; psz = (char *)kHlpMemPCopy(kHlpMemPCopy(szPath, pExe->pszPath, pExe->offFilename), pszName, cchName + 1); if (fNeedSuffix) kHlpMemCopy(psz - 1, ".dll", sizeof(".dll")); pMod = kwLdrModuleTryLoadDll(szPath, KWLOCATION_EXE_DIR, pExe, pszSearchPath); } /* The windows directory. */ if (pMod == NULL) { UINT cchDir = GetSystemDirectoryA(szPath, sizeof(szPath)); if ( cchDir <= 2 || cchDir + 1 + cchName + cchSuffix >= sizeof(szPath)) return KERR_BUFFER_OVERFLOW; szPath[cchDir++] = '\\'; psz = (char *)kHlpMemPCopy(&szPath[cchDir], pszName, cchName + 1); if (fNeedSuffix) kHlpMemCopy(psz - 1, ".dll", sizeof(".dll")); pMod = kwLdrModuleTryLoadDll(szPath, KWLOCATION_SYSTEM32, pExe, pszSearchPath); } /* The path. */ if ( pMod == NULL && pszSearchPath) { const char *pszCur = pszSearchPath; while (*pszCur != '\0') { /* Find the end of the component */ KSIZE cch = 0; while (pszCur[cch] != ';' && pszCur[cch] != '\0') cch++; if ( cch > 0 /* wrong, but whatever */ && cch + 1 + cchName + cchSuffix < sizeof(szPath)) { char *pszDst = kHlpMemPCopy(szPath, pszCur, cch); if ( szPath[cch - 1] != ':' && szPath[cch - 1] != '/' && szPath[cch - 1] != '\\') *pszDst++ = '\\'; pszDst = kHlpMemPCopy(pszDst, pszName, cchName); if (fNeedSuffix) pszDst = kHlpMemPCopy(pszDst, ".dll", 4); *pszDst = '\0'; pMod = kwLdrModuleTryLoadDll(szPath, KWLOCATION_SYSTEM32, pExe, pszSearchPath); if (pMod) break; } /* Advance */ pszCur += cch; while (*pszCur == ';') pszCur++; } } /* Return. */ if (pMod != NULL && pMod != (PKWMODULE)~(KUPTR)0) { *ppMod = pMod; return 0; } *ppMod = NULL; return KERR_GENERAL_FAILURE; } /** * Does the TLS memory initialization for a module on the current thread. * * @returns 0 on success, error on failure. * @param pMod The module. */ static int kwLdrCallTlsAllocateAndInit(PKWMODULE pMod) { if (pMod->u.Manual.idxTls != KU32_MAX) { PTEB pTeb = NtCurrentTeb(); void **ppvTls = *(void ***)( (KUPTR)pTeb + (sizeof(void *) == 4 ? 0x2c : 0x58) ); KU8 *pbData = (KU8 *)ppvTls[pMod->u.Manual.idxTls]; KWLDR_LOG(("%s: TLS: Initializing %#x (%#x), idxTls=%d\n", pMod->pszPath, pbData, pMod->u.Manual.cbTlsAlloc, pMod->u.Manual.cbTlsInitData, pMod->u.Manual.idxTls)); if (pMod->u.Manual.cbTlsInitData < pMod->u.Manual.cbTlsAlloc) kHlpMemSet(&pbData[pMod->u.Manual.cbTlsInitData], 0, pMod->u.Manual.cbTlsAlloc); if (pMod->u.Manual.cbTlsInitData) kHlpMemCopy(pbData, &pMod->u.Manual.pbCopy[pMod->u.Manual.offTlsInitData], pMod->u.Manual.cbTlsInitData); } return 0; } /** * Does the TLS callbacks for a module. * * @param pMod The module. * @param dwReason The callback reason. */ static void kwLdrCallTlsCallbacks(PKWMODULE pMod, DWORD dwReason) { if (pMod->u.Manual.cTlsCallbacks) { PIMAGE_TLS_CALLBACK *pCallback = (PIMAGE_TLS_CALLBACK *)&pMod->u.Manual.pbLoad[pMod->u.Manual.offTlsCallbacks]; do { KWLDR_LOG(("%s: Calling TLS callback %p(%p,%#x,0)\n", pMod->pszPath, *pCallback, pMod->hOurMod, dwReason)); (*pCallback)(pMod->hOurMod, dwReason, 0); } while (*++pCallback); } } /** * Does module initialization starting at @a pMod. * * This is initially used on the executable. Later it is used by the * LoadLibrary interceptor. * * @returns 0 on success, error on failure. * @param pMod The module to initialize. */ static int kwLdrModuleInitTree(PKWMODULE pMod) { int rc = 0; if (!pMod->fNative) { KWLDR_LOG(("kwLdrModuleInitTree: enmState=%#x idxTls=%u %s\n", pMod->u.Manual.enmState, pMod->u.Manual.idxTls, pMod->pszPath)); /* * Need to copy bits? */ if (pMod->u.Manual.enmState == KWMODSTATE_NEEDS_BITS) { if (pMod->u.Manual.fUseLdBuf) { #if defined(KBUILD_OS_WINDOWS) && defined(KBUILD_ARCH_AMD64) if (g_pModInLdBuf != NULL && g_pModInLdBuf != pMod && pMod->u.Manual.fRegisteredFunctionTable) { BOOLEAN fRc = RtlDeleteFunctionTable(pMod->u.Manual.paFunctions); kHlpAssert(fRc); K_NOREF(fRc); } #endif g_pModPrevInLdBuf = g_pModInLdBuf; g_pModInLdBuf = pMod; } /* Do quick zeroing and copying when we can. */ pMod->u.Manual.fCanDoQuick = K_FALSE; if ( pMod->u.Manual.fCanDoQuick && ( !pMod->u.Manual.fUseLdBuf || g_pModPrevInLdBuf == pMod)) { /* Zero first. */ kHlpAssert(pMod->u.Manual.cQuickZeroChunks <= 3); switch (pMod->u.Manual.cQuickZeroChunks) { case 3: kHlpMemSet(pMod->u.Manual.aQuickZeroChunks[2].pbDst, 0, pMod->u.Manual.aQuickZeroChunks[2].cbToZero); case 2: kHlpMemSet(pMod->u.Manual.aQuickZeroChunks[1].pbDst, 0, pMod->u.Manual.aQuickZeroChunks[1].cbToZero); case 1: kHlpMemSet(pMod->u.Manual.aQuickZeroChunks[0].pbDst, 0, pMod->u.Manual.aQuickZeroChunks[0].cbToZero); case 0: break; } /* Then copy. */ kHlpAssert(pMod->u.Manual.cQuickCopyChunks > 0); kHlpAssert(pMod->u.Manual.cQuickCopyChunks <= 3); switch (pMod->u.Manual.cQuickCopyChunks) { case 3: kHlpMemCopy(pMod->u.Manual.aQuickCopyChunks[2].pbDst, pMod->u.Manual.aQuickCopyChunks[2].pbSrc, pMod->u.Manual.aQuickCopyChunks[2].cbToCopy); case 2: kHlpMemCopy(pMod->u.Manual.aQuickCopyChunks[1].pbDst, pMod->u.Manual.aQuickCopyChunks[1].pbSrc, pMod->u.Manual.aQuickCopyChunks[1].cbToCopy); case 1: kHlpMemCopy(pMod->u.Manual.aQuickCopyChunks[0].pbDst, pMod->u.Manual.aQuickCopyChunks[0].pbSrc, pMod->u.Manual.aQuickCopyChunks[0].cbToCopy); case 0: break; } } /* Must copy the whole image. */ else { kHlpMemCopy(pMod->u.Manual.pbLoad, pMod->u.Manual.pbCopy, pMod->cbImage); pMod->u.Manual.fCanDoQuick = K_TRUE; } pMod->u.Manual.enmState = KWMODSTATE_NEEDS_INIT; } #if defined(KBUILD_OS_WINDOWS) && defined(KBUILD_ARCH_AMD64) /* * Need to register function table? */ if ( !pMod->u.Manual.fRegisteredFunctionTable && pMod->u.Manual.cFunctions > 0) { pMod->u.Manual.fRegisteredFunctionTable = RtlAddFunctionTable(pMod->u.Manual.paFunctions, pMod->u.Manual.cFunctions, (KUPTR)pMod->u.Manual.pbLoad) != FALSE; kHlpAssert(pMod->u.Manual.fRegisteredFunctionTable); } #endif if (pMod->u.Manual.enmState == KWMODSTATE_NEEDS_INIT) { /* * Must do imports first, but mark our module as being initialized to avoid * endless recursion should there be a dependency loop. */ KSIZE iImp; pMod->u.Manual.enmState = KWMODSTATE_BEING_INITED; for (iImp = 0; iImp < pMod->u.Manual.cImpMods; iImp++) { rc = kwLdrModuleInitTree(pMod->u.Manual.apImpMods[iImp]); if (rc != 0) return rc; } /* Do TLS allocations for module init? */ rc = kwLdrCallTlsAllocateAndInit(pMod); if (rc != 0) return rc; if (pMod->u.Manual.cTlsCallbacks > 0) kwLdrCallTlsCallbacks(pMod, DLL_PROCESS_ATTACH); /* Finally call the entry point. */ rc = kLdrModCallInit(pMod->pLdrMod, pMod->u.Manual.pbLoad, (KUPTR)pMod->hOurMod); if (rc == 0) pMod->u.Manual.enmState = KWMODSTATE_READY; else pMod->u.Manual.enmState = KWMODSTATE_INIT_FAILED; } } return rc; } /** * Looks up a module handle for a tool. * * @returns Referenced loader module on success, NULL on if not found. * @param pTool The tool. * @param hmod The module handle. */ static PKWMODULE kwToolLocateModuleByHandle(PKWTOOL pTool, HMODULE hmod) { KUPTR const uHMod = (KUPTR)hmod; PKWMODULE *papMods; KU32 iEnd; KU32 i; PKWDYNLOAD pDynLoad; /* The executable. */ if ( hmod == NULL || pTool->u.Sandboxed.pExe->hOurMod == hmod) return kwLdrModuleRetain(pTool->u.Sandboxed.pExe); /* * Binary lookup using the module table. */ papMods = pTool->u.Sandboxed.papModules; iEnd = pTool->u.Sandboxed.cModules; if (iEnd) { KU32 iStart = 0; i = iEnd / 2; for (;;) { KUPTR const uHModThis = (KUPTR)papMods[i]->hOurMod; if (uHMod < uHModThis) { iEnd = i--; if (iStart <= i) { } else break; } else if (uHMod != uHModThis) { iStart = ++i; if (i < iEnd) { } else break; } else return kwLdrModuleRetain(papMods[i]); i = iStart + (iEnd - iStart) / 2; } #ifndef NDEBUG iStart = pTool->u.Sandboxed.cModules; while (--iStart > 0) kHlpAssert((KUPTR)papMods[iStart]->hOurMod != uHMod); kHlpAssert(i == 0 || (KUPTR)papMods[i - 1]->hOurMod < uHMod); kHlpAssert(i == pTool->u.Sandboxed.cModules || (KUPTR)papMods[i]->hOurMod > uHMod); #endif } /* * Dynamically loaded images. */ for (pDynLoad = pTool->u.Sandboxed.pDynLoadHead; pDynLoad != NULL; pDynLoad = pDynLoad->pNext) if (pDynLoad->hmod == hmod) { if (pDynLoad->pMod) return kwLdrModuleRetain(pDynLoad->pMod); KWFS_TODO(); return NULL; } return NULL; } /** * Adds the given module to the tool import table. * * @returns 0 on success, non-zero on failure. * @param pTool The tool. * @param pMod The module. */ static int kwToolAddModule(PKWTOOL pTool, PKWMODULE pMod) { /* * Binary lookup. Locating the right slot for it, return if already there. */ KUPTR const uHMod = (KUPTR)pMod->hOurMod; PKWMODULE *papMods = pTool->u.Sandboxed.papModules; KU32 iEnd = pTool->u.Sandboxed.cModules; KU32 i; if (iEnd) { KU32 iStart = 0; i = iEnd / 2; for (;;) { KUPTR const uHModThis = (KUPTR)papMods[i]->hOurMod; if (uHMod < uHModThis) { iEnd = i; if (iStart < i) { } else break; } else if (uHMod != uHModThis) { iStart = ++i; if (i < iEnd) { } else break; } else { /* Already there in the table. */ return 0; } i = iStart + (iEnd - iStart) / 2; } #ifndef NDEBUG iStart = pTool->u.Sandboxed.cModules; while (--iStart > 0) { kHlpAssert(papMods[iStart] != pMod); kHlpAssert((KUPTR)papMods[iStart]->hOurMod != uHMod); } kHlpAssert(i == 0 || (KUPTR)papMods[i - 1]->hOurMod < uHMod); kHlpAssert(i == pTool->u.Sandboxed.cModules || (KUPTR)papMods[i]->hOurMod > uHMod); #endif } else i = 0; /* * Grow the table? */ if ((pTool->u.Sandboxed.cModules % 16) == 0) { void *pvNew = kHlpRealloc(papMods, sizeof(papMods[0]) * (pTool->u.Sandboxed.cModules + 16)); if (!pvNew) return KERR_NO_MEMORY; pTool->u.Sandboxed.papModules = papMods = (PKWMODULE *)pvNew; } /* Insert it. */ if (i != pTool->u.Sandboxed.cModules) kHlpMemMove(&papMods[i + 1], &papMods[i], (pTool->u.Sandboxed.cModules - i) * sizeof(papMods[0])); papMods[i] = kwLdrModuleRetain(pMod); pTool->u.Sandboxed.cModules++; KW_LOG(("kwToolAddModule: %u modules after adding %p=%s\n", pTool->u.Sandboxed.cModules, uHMod, pMod->pszPath)); return 0; } /** * Adds the given module and all its imports to the * * @returns 0 on success, non-zero on failure. * @param pTool The tool. * @param pMod The module. */ static int kwToolAddModuleAndImports(PKWTOOL pTool, PKWMODULE pMod) { int rc = kwToolAddModule(pTool, pMod); if (!pMod->fNative && rc == 0) { KSIZE iImp = pMod->u.Manual.cImpMods; while (iImp-- > 0) { rc = kwToolAddModuleAndImports(pTool, pMod->u.Manual.apImpMods[iImp]); if (rc == 0) { } else break; } } return 0; } /** * Creates a tool entry and inserts it. * * @returns Pointer to the tool entry. NULL on failure. * @param pToolFsObj The file object of the tool. The created tool * will be associated with it. * * A reference is donated by the caller and must be * released. * @param pszSearchPath The PATH environment variable value, or NULL. */ static PKWTOOL kwToolEntryCreate(PKFSOBJ pToolFsObj, const char *pszSearchPath) { KSIZE cwcPath = pToolFsObj->cwcParent + pToolFsObj->cwcName + 1; KSIZE cbPath = pToolFsObj->cchParent + pToolFsObj->cchName + 1; PKWTOOL pTool = (PKWTOOL)kFsCacheObjAddUserData(g_pFsCache, pToolFsObj, KW_DATA_KEY_TOOL, sizeof(*pTool) + cwcPath * sizeof(wchar_t) + cbPath); if (pTool) { KBOOL fRc; pTool->pwszPath = (wchar_t const *)(pTool + 1); fRc = kFsCacheObjGetFullPathW(pToolFsObj, (wchar_t *)pTool->pwszPath, cwcPath, '\\'); kHlpAssert(fRc); K_NOREF(fRc); pTool->pszPath = (char const *)&pTool->pwszPath[cwcPath]; fRc = kFsCacheObjGetFullPathA(pToolFsObj, (char *)pTool->pszPath, cbPath, '\\'); kHlpAssert(fRc); pTool->enmType = KWTOOLTYPE_SANDBOXED; pTool->u.Sandboxed.pExe = kwLdrModuleCreateNonNative(pTool->pszPath, kwStrHash(pTool->pszPath), K_TRUE /*fExe*/, NULL /*pEexeMod*/, pszSearchPath); if (pTool->u.Sandboxed.pExe) { int rc = kwLdrModuleQueryMainEntrypoint(pTool->u.Sandboxed.pExe, &pTool->u.Sandboxed.uMainAddr); if (rc == 0) { if (kHlpStrICompAscii(pToolFsObj->pszName, "cl.exe") == 0) pTool->u.Sandboxed.enmHint = KWTOOLHINT_VISUAL_CPP_CL; else if (kHlpStrICompAscii(pToolFsObj->pszName, "link.exe") == 0) pTool->u.Sandboxed.enmHint = KWTOOLHINT_VISUAL_CPP_LINK; else pTool->u.Sandboxed.enmHint = KWTOOLHINT_NONE; kwToolAddModuleAndImports(pTool, pTool->u.Sandboxed.pExe); } else { kwErrPrintf("Failed to get entrypoint for '%s': %u\n", pTool->pszPath, rc); kwLdrModuleRelease(pTool->u.Sandboxed.pExe); pTool->u.Sandboxed.pExe = NULL; pTool->enmType = KWTOOLTYPE_EXEC; } } else pTool->enmType = KWTOOLTYPE_EXEC; kFsCacheObjRelease(g_pFsCache, pToolFsObj); g_cTools++; return pTool; } kFsCacheObjRelease(g_pFsCache, pToolFsObj); return NULL; } /** * Looks up the given tool, creating a new tool table entry if necessary. * * @returns Pointer to the tool entry. NULL on failure (fully bitched). * @param pszExe The executable for the tool (not normalized). * @param cEnvVars Number of environment varibles. * @param papszEnvVars Environment variables. For getting the PATH. */ static PKWTOOL kwToolLookup(const char *pszExe, KU32 cEnvVars, const char **papszEnvVars) { /* * We associate the tools instances with the file system objects. * * We'd like to do the lookup without invaliding the volatile parts of the * cache, thus the double lookup here. The cache gets invalidate later on. */ KFSLOOKUPERROR enmError; PKFSOBJ pToolFsObj = kFsCacheLookupA(g_pFsCache, pszExe, &enmError); if ( !pToolFsObj || pToolFsObj->bObjType != KFSOBJ_TYPE_FILE) { kFsCacheInvalidateCustomBoth(g_pFsCache); pToolFsObj = kFsCacheLookupA(g_pFsCache, pszExe, &enmError); } if (pToolFsObj) { if (pToolFsObj->bObjType == KFSOBJ_TYPE_FILE) { const char *pszSearchPath; PKWTOOL pTool = (PKWTOOL)kFsCacheObjGetUserData(g_pFsCache, pToolFsObj, KW_DATA_KEY_TOOL); if (pTool) { kFsCacheObjRelease(g_pFsCache, pToolFsObj); return pTool; } /* * Need to create a new tool. */ pszSearchPath = NULL; while (cEnvVars-- > 0) if (_strnicmp(papszEnvVars[cEnvVars], "PATH=", 5) == 0) { pszSearchPath = &papszEnvVars[cEnvVars][5]; break; } pTool = kwToolEntryCreate(pToolFsObj, pszSearchPath); if (pTool) return pTool; kwErrPrintf("kwToolLookup(%s) -> NULL: kwToolEntryCreate failed\n", pszExe); } else { kFsCacheObjRelease(g_pFsCache, pToolFsObj); kwErrPrintf("kwToolLookup(%s) -> NULL: not file (%d)\n", pszExe, pToolFsObj->bObjType); } } else kwErrPrintf("kwToolLookup(%s) -> NULL: enmError=%d\n", pszExe, enmError); return NULL; } /* * * File system cache. * File system cache. * File system cache. * */ /** * This is for kDep. */ int kwFsPathExists(const char *pszPath) { BirdTimeSpec_T TsIgnored; KFSLOOKUPERROR enmError; PKFSOBJ pFsObj = kFsCacheLookupNoMissingA(g_pFsCache, pszPath, &enmError); if (pFsObj) { kFsCacheObjRelease(g_pFsCache, pFsObj); return 1; } return birdStatModTimeOnly(pszPath, &TsIgnored, 1) == 0; } /* duplicated in dir-nt-bird.c */ void nt_fullpath_cached(const char *pszPath, char *pszFull, size_t cbFull) { KFSLOOKUPERROR enmError; PKFSOBJ pPathObj = kFsCacheLookupA(g_pFsCache, pszPath, &enmError); if (pPathObj) { KSIZE off = pPathObj->cchParent; if (off > 0) { KSIZE offEnd = off + pPathObj->cchName; if (offEnd < cbFull) { PKFSDIR pAncestor; pszFull[off + pPathObj->cchName] = '\0'; memcpy(&pszFull[off], pPathObj->pszName, pPathObj->cchName); for (pAncestor = pPathObj->pParent; off > 0; pAncestor = pAncestor->Obj.pParent) { kHlpAssert(off > 1); kHlpAssert(pAncestor != NULL); kHlpAssert(pAncestor->Obj.cchName > 0); pszFull[--off] = '/'; off -= pAncestor->Obj.cchName; kHlpAssert(pAncestor->Obj.cchParent == off); memcpy(&pszFull[off], pAncestor->Obj.pszName, pAncestor->Obj.cchName); } kFsCacheObjRelease(g_pFsCache, pPathObj); return; } } else { if ((size_t)pPathObj->cchName + 1 < cbFull) { memcpy(pszFull, pPathObj->pszName, pPathObj->cchName); pszFull[pPathObj->cchName] = '/'; pszFull[pPathObj->cchName + 1] = '\0'; kFsCacheObjRelease(g_pFsCache, pPathObj); return; } } /* do fallback. */ kHlpAssertFailed(); kFsCacheObjRelease(g_pFsCache, pPathObj); } nt_fullpath(pszPath, pszFull, cbFull); } /** * Helper for getting the extension of a UTF-16 path. * * @returns Pointer to the extension or the terminator. * @param pwszPath The path. * @param pcwcExt Where to return the length of the extension. */ static wchar_t const *kwFsPathGetExtW(wchar_t const *pwszPath, KSIZE *pcwcExt) { wchar_t const *pwszName = pwszPath; wchar_t const *pwszExt = NULL; for (;;) { wchar_t const wc = *pwszPath++; if (wc == '.') pwszExt = pwszPath; else if (wc == '/' || wc == '\\' || wc == ':') { pwszName = pwszPath; pwszExt = NULL; } else if (wc == '\0') { if (pwszExt) { *pcwcExt = pwszPath - pwszExt - 1; return pwszExt; } *pcwcExt = 0; return pwszPath - 1; } } } /** * Parses the argument string passed in as pszSrc. * * @returns size of the processed arguments. * @param pszSrc Pointer to the commandline that's to be parsed. * @param pcArgs Where to return the number of arguments. * @param argv Pointer to argument vector to put argument pointers in. NULL allowed. * @param pchPool Pointer to memory pchPool to put the arguments into. NULL allowed. * * @remarks Lifted from startuphacks-win.c */ static int parse_args(const char *pszSrc, int *pcArgs, char **argv, char *pchPool) { int bs; char chQuote; char *pfFlags; int cbArgs; int cArgs; #define PUTC(c) do { ++cbArgs; if (pchPool != NULL) *pchPool++ = (c); } while (0) #define PUTV do { ++cArgs; if (argv != NULL) *argv++ = pchPool; } while (0) #define WHITE(c) ((c) == ' ' || (c) == '\t') #define _ARG_DQUOTE 0x01 /* Argument quoted (") */ #define _ARG_RESPONSE 0x02 /* Argument read from response file */ #define _ARG_WILDCARD 0x04 /* Argument expanded from wildcard */ #define _ARG_ENV 0x08 /* Argument from environment */ #define _ARG_NONZERO 0x80 /* Always set, to avoid end of string */ cArgs = 0; cbArgs = 0; #if 0 /* argv[0] */ PUTC((char)_ARG_NONZERO); PUTV; for (;;) { PUTC(*pszSrc); if (*pszSrc == 0) break; ++pszSrc; } ++pszSrc; #endif for (;;) { while (WHITE(*pszSrc)) ++pszSrc; if (*pszSrc == 0) break; pfFlags = pchPool; PUTC((char)_ARG_NONZERO); PUTV; bs = 0; chQuote = 0; for (;;) { if (!chQuote ? (*pszSrc == '"' /*|| *pszSrc == '\''*/) : *pszSrc == chQuote) { while (bs >= 2) { PUTC('\\'); bs -= 2; } if (bs & 1) PUTC(*pszSrc); else { chQuote = chQuote ? 0 : *pszSrc; if (pfFlags != NULL) *pfFlags |= _ARG_DQUOTE; } bs = 0; } else if (*pszSrc == '\\') ++bs; else { while (bs != 0) { PUTC('\\'); --bs; } if (*pszSrc == 0 || (WHITE(*pszSrc) && !chQuote)) break; PUTC(*pszSrc); } ++pszSrc; } PUTC(0); } *pcArgs = cArgs; return cbArgs; } /* * * Process and thread related APIs. * Process and thread related APIs. * Process and thread related APIs. * */ /** Common worker for ExitProcess(), exit() and friends. */ static void WINAPI kwSandboxDoExit(int uExitCode) { if (g_Sandbox.idMainThread == GetCurrentThreadId()) { PNT_TIB pTib = (PNT_TIB)NtCurrentTeb(); g_Sandbox.rcExitCode = (int)uExitCode; /* Before we jump, restore the TIB as we're not interested in any exception chain stuff installed by the sandboxed executable. */ *pTib = g_Sandbox.TibMainThread; pTib->ExceptionList = g_Sandbox.pOutXcptListHead; longjmp(g_Sandbox.JmpBuf, 1); } KWFS_TODO(); } /** ExitProcess replacement. */ static void WINAPI kwSandbox_Kernel32_ExitProcess(UINT uExitCode) { KW_LOG(("kwSandbox_Kernel32_ExitProcess: %u\n", uExitCode)); kwSandboxDoExit((int)uExitCode); } /** ExitProcess replacement. */ static BOOL WINAPI kwSandbox_Kernel32_TerminateProcess(HANDLE hProcess, UINT uExitCode) { if (hProcess == GetCurrentProcess()) kwSandboxDoExit(uExitCode); KWFS_TODO(); return TerminateProcess(hProcess, uExitCode); } /** Normal CRT exit(). */ static void __cdecl kwSandbox_msvcrt_exit(int rcExitCode) { KW_LOG(("kwSandbox_msvcrt_exit: %d\n", rcExitCode)); kwSandboxDoExit(rcExitCode); } /** Quick CRT _exit(). */ static void __cdecl kwSandbox_msvcrt__exit(int rcExitCode) { /* Quick. */ KW_LOG(("kwSandbox_msvcrt__exit %d\n", rcExitCode)); kwSandboxDoExit(rcExitCode); } /** Return to caller CRT _cexit(). */ static void __cdecl kwSandbox_msvcrt__cexit(int rcExitCode) { KW_LOG(("kwSandbox_msvcrt__cexit: %d\n", rcExitCode)); kwSandboxDoExit(rcExitCode); } /** Quick return to caller CRT _c_exit(). */ static void __cdecl kwSandbox_msvcrt__c_exit(int rcExitCode) { KW_LOG(("kwSandbox_msvcrt__c_exit: %d\n", rcExitCode)); kwSandboxDoExit(rcExitCode); } /** Runtime error and exit _amsg_exit(). */ static void __cdecl kwSandbox_msvcrt__amsg_exit(int iMsgNo) { KW_LOG(("\nRuntime error #%u!\n", iMsgNo)); kwSandboxDoExit(255); } /** CRT - terminate(). */ static void __cdecl kwSandbox_msvcrt_terminate(void) { KW_LOG(("\nRuntime - terminate!\n")); kwSandboxDoExit(254); } /** CRT - _onexit */ static _onexit_t __cdecl kwSandbox_msvcrt__onexit(_onexit_t pfnFunc) { //if (g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_LINK) { PKWEXITCALLACK pCallback; KW_LOG(("_onexit(%p)\n", pfnFunc)); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pCallback = kHlpAlloc(sizeof(*pCallback)); if (pCallback) { pCallback->pfnCallback = pfnFunc; pCallback->fAtExit = K_FALSE; pCallback->pNext = g_Sandbox.pExitCallbackHead; g_Sandbox.pExitCallbackHead = pCallback; return pfnFunc; } return NULL; } KW_LOG(("_onexit(%p) - IGNORED\n", pfnFunc)); return pfnFunc; } /** CRT - atexit */ static int __cdecl kwSandbox_msvcrt_atexit(int (__cdecl *pfnFunc)(void)) { //if (g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_LINK) { PKWEXITCALLACK pCallback; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); KW_LOG(("atexit(%p)\n", pfnFunc)); pCallback = kHlpAlloc(sizeof(*pCallback)); if (pCallback) { pCallback->pfnCallback = (_onexit_t)pfnFunc; pCallback->fAtExit = K_TRUE; pCallback->pNext = g_Sandbox.pExitCallbackHead; g_Sandbox.pExitCallbackHead = pCallback; return 0; } return -1; } KW_LOG(("atexit(%p) - IGNORED!\n", pfnFunc)); return 0; } /** Kernel32 - SetConsoleCtrlHandler(). */ static BOOL WINAPI kwSandbox_Kernel32_SetConsoleCtrlHandler(PHANDLER_ROUTINE pfnHandler, BOOL fAdd) { KW_LOG(("SetConsoleCtrlHandler(%p, %d) - ignoring\n")); return TRUE; } /** The CRT internal __getmainargs() API. */ static int __cdecl kwSandbox_msvcrt___getmainargs(int *pargc, char ***pargv, char ***penvp, int dowildcard, int const *piNewMode) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); *pargc = g_Sandbox.cArgs; *pargv = g_Sandbox.papszArgs; *penvp = g_Sandbox.environ; /** @todo startinfo points at a newmode (setmode) value. */ return 0; } /** The CRT internal __wgetmainargs() API. */ static int __cdecl kwSandbox_msvcrt___wgetmainargs(int *pargc, wchar_t ***pargv, wchar_t ***penvp, int dowildcard, int const *piNewMode) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); *pargc = g_Sandbox.cArgs; *pargv = g_Sandbox.papwszArgs; *penvp = g_Sandbox.wenviron; /** @todo startinfo points at a newmode (setmode) value. */ return 0; } /** Kernel32 - GetCommandLineA() */ static LPCSTR /*LPSTR*/ WINAPI kwSandbox_Kernel32_GetCommandLineA(VOID) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); return g_Sandbox.pszCmdLine; } /** Kernel32 - GetCommandLineW() */ static LPCWSTR /*LPWSTR*/ WINAPI kwSandbox_Kernel32_GetCommandLineW(VOID) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); return g_Sandbox.pwszCmdLine; } /** Kernel32 - GetStartupInfoA() */ static VOID WINAPI kwSandbox_Kernel32_GetStartupInfoA(LPSTARTUPINFOA pStartupInfo) { KW_LOG(("GetStartupInfoA\n")); GetStartupInfoA(pStartupInfo); pStartupInfo->lpReserved = NULL; pStartupInfo->lpTitle = NULL; pStartupInfo->lpReserved2 = NULL; pStartupInfo->cbReserved2 = 0; } /** Kernel32 - GetStartupInfoW() */ static VOID WINAPI kwSandbox_Kernel32_GetStartupInfoW(LPSTARTUPINFOW pStartupInfo) { KW_LOG(("GetStartupInfoW\n")); GetStartupInfoW(pStartupInfo); pStartupInfo->lpReserved = NULL; pStartupInfo->lpTitle = NULL; pStartupInfo->lpReserved2 = NULL; pStartupInfo->cbReserved2 = 0; } /** CRT - __p___argc(). */ static int * __cdecl kwSandbox_msvcrt___p___argc(void) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); return &g_Sandbox.cArgs; } /** CRT - __p___argv(). */ static char *** __cdecl kwSandbox_msvcrt___p___argv(void) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); return &g_Sandbox.papszArgs; } /** CRT - __p___sargv(). */ static wchar_t *** __cdecl kwSandbox_msvcrt___p___wargv(void) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); return &g_Sandbox.papwszArgs; } /** CRT - __p__acmdln(). */ static char ** __cdecl kwSandbox_msvcrt___p__acmdln(void) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); return (char **)&g_Sandbox.pszCmdLine; } /** CRT - __p__acmdln(). */ static wchar_t ** __cdecl kwSandbox_msvcrt___p__wcmdln(void) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); return &g_Sandbox.pwszCmdLine; } /** CRT - __p__pgmptr(). */ static char ** __cdecl kwSandbox_msvcrt___p__pgmptr(void) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); return &g_Sandbox.pgmptr; } /** CRT - __p__wpgmptr(). */ static wchar_t ** __cdecl kwSandbox_msvcrt___p__wpgmptr(void) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); return &g_Sandbox.wpgmptr; } /** CRT - _get_pgmptr(). */ static errno_t __cdecl kwSandbox_msvcrt__get_pgmptr(char **ppszValue) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); *ppszValue = g_Sandbox.pgmptr; return 0; } /** CRT - _get_wpgmptr(). */ static errno_t __cdecl kwSandbox_msvcrt__get_wpgmptr(wchar_t **ppwszValue) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); *ppwszValue = g_Sandbox.wpgmptr; return 0; } /** Just in case. */ static void kwSandbox_msvcrt__wincmdln(void) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); KWFS_TODO(); } /** Just in case. */ static void kwSandbox_msvcrt__wwincmdln(void) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); KWFS_TODO(); } /** CreateThread interceptor. */ static HANDLE WINAPI kwSandbox_Kernel32_CreateThread(LPSECURITY_ATTRIBUTES pSecAttr, SIZE_T cbStack, PTHREAD_START_ROUTINE pfnThreadProc, PVOID pvUser, DWORD fFlags, PDWORD pidThread) { HANDLE hThread = NULL; KW_LOG(("CreateThread: pSecAttr=%p (inh=%d) cbStack=%#x pfnThreadProc=%p pvUser=%p fFlags=%#x pidThread=%p\n", pSecAttr, pSecAttr ? pSecAttr->bInheritHandle : 0, cbStack, pfnThreadProc, pvUser, fFlags, pidThread)); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_LINK) { /* Allow link::DbgThread. */ hThread = CreateThread(pSecAttr, cbStack, pfnThreadProc, pvUser, fFlags, pidThread); KW_LOG(("CreateThread -> %p, *pidThread=%#x\n", hThread, pidThread ? *pidThread : 0)); } else KWFS_TODO(); return hThread; } /** _beginthread - create a new thread. */ static uintptr_t __cdecl kwSandbox_msvcrt__beginthread(void (__cdecl *pfnThreadProc)(void *), unsigned cbStack, void *pvUser) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); KWFS_TODO(); return 0; } /** _beginthreadex - create a new thread, msvcr120.dll hack for c2.dll. */ static uintptr_t __cdecl kwSandbox_msvcr120__beginthreadex(void *pvSecAttr, unsigned cbStack, unsigned (__stdcall *pfnThreadProc)(void *), void *pvUser, unsigned fCreate, unsigned *pidThread) { /* * The VC++ 12 (VS 2013) compiler pass two is now threaded. Let it do * whatever it needs to. */ KW_LOG(("kwSandbox_msvcr120__beginthreadex: pvSecAttr=%p (inh=%d) cbStack=%#x pfnThreadProc=%p pvUser=%p fCreate=%#x pidThread=%p\n", pvSecAttr, pvSecAttr ? ((LPSECURITY_ATTRIBUTES)pvSecAttr)->bInheritHandle : 0, cbStack, pfnThreadProc, pvUser, fCreate, pidThread)); if (g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_CL) { uintptr_t rcRet; static uintptr_t (__cdecl *s_pfnReal)(void *, unsigned , unsigned (__stdcall *)(void *), void *, unsigned , unsigned *); if (!s_pfnReal) { *(FARPROC *)&s_pfnReal = GetProcAddress(GetModuleHandleA("msvcr120.dll"), "_beginthreadex"); if (!s_pfnReal) { kwErrPrintf("kwSandbox_msvcr120__beginthreadex: Failed to resolve _beginthreadex in msvcr120.dll!\n"); __debugbreak(); } } rcRet = s_pfnReal(pvSecAttr, cbStack, pfnThreadProc, pvUser, fCreate, pidThread); KW_LOG(("kwSandbox_msvcr120__beginthreadex: returns %p *pidThread=%#x\n", rcRet, pidThread ? *pidThread : -1)); return rcRet; } kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); KWFS_TODO(); return 0; } /** _beginthreadex - create a new thread. */ static uintptr_t __cdecl kwSandbox_msvcrt__beginthreadex(void *pvSecAttr, unsigned cbStack, unsigned (__stdcall *pfnThreadProc)(void *), void *pvUser, unsigned fCreate, unsigned *pidThread) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); KWFS_TODO(); return 0; } /* * * Environment related APIs. * Environment related APIs. * Environment related APIs. * */ /** Kernel32 - GetEnvironmentStringsA (Watcom uses this one). */ static LPCH WINAPI kwSandbox_Kernel32_GetEnvironmentStringsA(void) { char *pszzEnv; char *pszCur; KSIZE cbNeeded = 1; KSIZE iVar = 0; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); /* Figure how space much we need first. */ while ((pszCur = g_Sandbox.papszEnvVars[iVar++]) != NULL) cbNeeded += kHlpStrLen(pszCur) + 1; /* Allocate it. */ pszzEnv = kHlpAlloc(cbNeeded); if (pszzEnv) { char *psz = pszzEnv; iVar = 0; while ((pszCur = g_Sandbox.papszEnvVars[iVar++]) != NULL) { KSIZE cbCur = kHlpStrLen(pszCur) + 1; kHlpAssert((KUPTR)(&psz[cbCur] - pszzEnv) < cbNeeded); psz = (char *)kHlpMemPCopy(psz, pszCur, cbCur); } *psz++ = '\0'; kHlpAssert(psz - pszzEnv == cbNeeded); } KW_LOG(("GetEnvironmentStringsA -> %p [%u]\n", pszzEnv, cbNeeded)); #if 0 fprintf(stderr, "GetEnvironmentStringsA: %p LB %#x\n", pszzEnv, cbNeeded); pszCur = pszzEnv; iVar = 0; while (*pszCur) { fprintf(stderr, " %u:%p=%s\n\n", iVar, pszCur, pszCur); iVar++; pszCur += kHlpStrLen(pszCur) + 1; } fprintf(stderr, " %u:%p=\n\n", iVar, pszCur); pszCur++; fprintf(stderr, "ended at %p, after %u bytes (exepcted %u)\n", pszCur, pszCur - pszzEnv, cbNeeded); #endif return pszzEnv; } /** Kernel32 - GetEnvironmentStrings */ static LPCH WINAPI kwSandbox_Kernel32_GetEnvironmentStrings(void) { KW_LOG(("GetEnvironmentStrings!\n")); return kwSandbox_Kernel32_GetEnvironmentStringsA(); } /** Kernel32 - GetEnvironmentStringsW */ static LPWCH WINAPI kwSandbox_Kernel32_GetEnvironmentStringsW(void) { wchar_t *pwszzEnv; wchar_t *pwszCur; KSIZE cwcNeeded = 1; KSIZE iVar = 0; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); /* Figure how space much we need first. */ while ((pwszCur = g_Sandbox.papwszEnvVars[iVar++]) != NULL) cwcNeeded += kwUtf16Len(pwszCur) + 1; /* Allocate it. */ pwszzEnv = kHlpAlloc(cwcNeeded * sizeof(wchar_t)); if (pwszzEnv) { wchar_t *pwsz = pwszzEnv; iVar = 0; while ((pwszCur = g_Sandbox.papwszEnvVars[iVar++]) != NULL) { KSIZE cwcCur = kwUtf16Len(pwszCur) + 1; kHlpAssert((KUPTR)(&pwsz[cwcCur] - pwszzEnv) < cwcNeeded); pwsz = (wchar_t *)kHlpMemPCopy(pwsz, pwszCur, cwcCur * sizeof(wchar_t)); } *pwsz++ = '\0'; kHlpAssert(pwsz - pwszzEnv == cwcNeeded); } KW_LOG(("GetEnvironmentStringsW -> %p [%u]\n", pwszzEnv, cwcNeeded)); return pwszzEnv; } /** Kernel32 - FreeEnvironmentStringsA */ static BOOL WINAPI kwSandbox_Kernel32_FreeEnvironmentStringsA(LPCH pszzEnv) { KW_LOG(("FreeEnvironmentStringsA: %p -> TRUE\n", pszzEnv)); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); kHlpFree(pszzEnv); return TRUE; } /** Kernel32 - FreeEnvironmentStringsW */ static BOOL WINAPI kwSandbox_Kernel32_FreeEnvironmentStringsW(LPWCH pwszzEnv) { KW_LOG(("FreeEnvironmentStringsW: %p -> TRUE\n", pwszzEnv)); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); kHlpFree(pwszzEnv); return TRUE; } /** * Grows the environment vectors (KWSANDBOX::environ, KWSANDBOX::papszEnvVars, * KWSANDBOX::wenviron, and KWSANDBOX::papwszEnvVars). * * @returns 0 on success, non-zero on failure. * @param pSandbox The sandbox. * @param cMin Minimum size, including terminator. */ static int kwSandboxGrowEnv(PKWSANDBOX pSandbox, KSIZE cMin) { void *pvNew; KSIZE const cOld = pSandbox->cEnvVarsAllocated; KSIZE cNew = cOld + 256; while (cNew < cMin) cNew += 256; pvNew = kHlpRealloc(pSandbox->environ, cNew * sizeof(pSandbox->environ[0])); if (pvNew) { pSandbox->environ = (char **)pvNew; pSandbox->environ[cOld] = NULL; pvNew = kHlpRealloc(pSandbox->papszEnvVars, cNew * sizeof(pSandbox->papszEnvVars[0])); if (pvNew) { pSandbox->papszEnvVars = (char **)pvNew; pSandbox->papszEnvVars[cOld] = NULL; pvNew = kHlpRealloc(pSandbox->wenviron, cNew * sizeof(pSandbox->wenviron[0])); if (pvNew) { pSandbox->wenviron = (wchar_t **)pvNew; pSandbox->wenviron[cOld] = NULL; pvNew = kHlpRealloc(pSandbox->papwszEnvVars, cNew * sizeof(pSandbox->papwszEnvVars[0])); if (pvNew) { pSandbox->papwszEnvVars = (wchar_t **)pvNew; pSandbox->papwszEnvVars[cOld] = NULL; pSandbox->cEnvVarsAllocated = cNew; KW_LOG(("kwSandboxGrowEnv: cNew=%d - crt: %p / %p; shadow: %p, %p\n", cNew, pSandbox->environ, pSandbox->wenviron, pSandbox->papszEnvVars, pSandbox->papwszEnvVars)); return 0; } } } } kwErrPrintf("kwSandboxGrowEnv ran out of memory! cNew=%u\n", cNew); return KERR_NO_MEMORY; } /** * Sets an environment variable, ANSI style. * * @returns 0 on success, non-zero on failure. * @param pSandbox The sandbox. * @param pchVar The variable name. * @param cchVar The length of the name. * @param pszValue The value. */ static int kwSandboxDoSetEnvA(PKWSANDBOX pSandbox, const char *pchVar, KSIZE cchVar, const char *pszValue) { /* Allocate and construct the new strings. */ KSIZE cchTmp = kHlpStrLen(pszValue); char *pszNew = (char *)kHlpAlloc(cchVar + 1 + cchTmp + 1); if (pszNew) { wchar_t *pwszNew; kHlpMemCopy(pszNew, pchVar, cchVar); pszNew[cchVar] = '='; kHlpMemCopy(&pszNew[cchVar + 1], pszValue, cchTmp); cchTmp += cchVar + 1; pszNew[cchTmp] = '\0'; pwszNew = kwStrToUtf16AllocN(pszNew, cchTmp); if (pwszNew) { /* Look it up. */ KSIZE iVar = 0; char *pszEnv; while ((pszEnv = pSandbox->papszEnvVars[iVar]) != NULL) { if ( _strnicmp(pszEnv, pchVar, cchVar) == 0 && pszEnv[cchVar] == '=') { KW_LOG(("kwSandboxDoSetEnvA: Replacing iVar=%d: %p='%s' and %p='%ls'\n" " iVar=%d: %p='%s' and %p='%ls'\n", iVar, pSandbox->papszEnvVars[iVar], pSandbox->papszEnvVars[iVar], pSandbox->papwszEnvVars[iVar], pSandbox->papwszEnvVars[iVar], iVar, pszNew, pszNew, pwszNew, pwszNew)); kHlpFree(pSandbox->papszEnvVars[iVar]); pSandbox->papszEnvVars[iVar] = pszNew; pSandbox->environ[iVar] = pszNew; kHlpFree(pSandbox->papwszEnvVars[iVar]); pSandbox->papwszEnvVars[iVar] = pwszNew; pSandbox->wenviron[iVar] = pwszNew; return 0; } iVar++; } /* Not found, do we need to grow the table first? */ if (iVar + 1 >= pSandbox->cEnvVarsAllocated) kwSandboxGrowEnv(pSandbox, iVar + 2); if (iVar + 1 < pSandbox->cEnvVarsAllocated) { KW_LOG(("kwSandboxDoSetEnvA: Adding iVar=%d: %p='%s' and %p='%ls'\n", iVar, pszNew, pszNew, pwszNew, pwszNew)); pSandbox->papszEnvVars[iVar + 1] = NULL; pSandbox->papszEnvVars[iVar] = pszNew; pSandbox->environ[iVar + 1] = NULL; pSandbox->environ[iVar] = pszNew; pSandbox->papwszEnvVars[iVar + 1] = NULL; pSandbox->papwszEnvVars[iVar] = pwszNew; pSandbox->wenviron[iVar + 1] = NULL; pSandbox->wenviron[iVar] = pwszNew; return 0; } kHlpFree(pwszNew); } kHlpFree(pszNew); } KW_LOG(("Out of memory!\n")); return 0; } /** * Sets an environment variable, UTF-16 style. * * @returns 0 on success, non-zero on failure. * @param pSandbox The sandbox. * @param pwcVar The variable name. * @param cwcVar The length of the name. * @param pwszValue The value. */ static int kwSandboxDoSetEnvW(PKWSANDBOX pSandbox, const wchar_t *pwchVar, KSIZE cwcVar, const wchar_t *pwszValue) { /* Allocate and construct the new strings. */ KSIZE cwcTmp = kwUtf16Len(pwszValue); wchar_t *pwszNew = (wchar_t *)kHlpAlloc((cwcVar + 1 + cwcTmp + 1) * sizeof(wchar_t)); if (pwszNew) { char *pszNew; kHlpMemCopy(pwszNew, pwchVar, cwcVar * sizeof(wchar_t)); pwszNew[cwcVar] = '='; kHlpMemCopy(&pwszNew[cwcVar + 1], pwszValue, cwcTmp * sizeof(wchar_t)); cwcTmp += cwcVar + 1; pwszNew[cwcVar] = '\0'; pszNew = kwUtf16ToStrAllocN(pwszNew, cwcVar); if (pszNew) { /* Look it up. */ KSIZE iVar = 0; wchar_t *pwszEnv; while ((pwszEnv = pSandbox->papwszEnvVars[iVar]) != NULL) { if ( _wcsnicmp(pwszEnv, pwchVar, cwcVar) == 0 && pwszEnv[cwcVar] == '=') { KW_LOG(("kwSandboxDoSetEnvW: Replacing iVar=%d: %p='%s' and %p='%ls'\n" " iVar=%d: %p='%s' and %p='%ls'\n", iVar, pSandbox->papszEnvVars[iVar], pSandbox->papszEnvVars[iVar], pSandbox->papwszEnvVars[iVar], pSandbox->papwszEnvVars[iVar], iVar, pszNew, pszNew, pwszNew, pwszNew)); kHlpFree(pSandbox->papszEnvVars[iVar]); pSandbox->papszEnvVars[iVar] = pszNew; pSandbox->environ[iVar] = pszNew; kHlpFree(pSandbox->papwszEnvVars[iVar]); pSandbox->papwszEnvVars[iVar] = pwszNew; pSandbox->wenviron[iVar] = pwszNew; return 0; } iVar++; } /* Not found, do we need to grow the table first? */ if (iVar + 1 >= pSandbox->cEnvVarsAllocated) kwSandboxGrowEnv(pSandbox, iVar + 2); if (iVar + 1 < pSandbox->cEnvVarsAllocated) { KW_LOG(("kwSandboxDoSetEnvW: Adding iVar=%d: %p='%s' and %p='%ls'\n", iVar, pszNew, pszNew, pwszNew, pwszNew)); pSandbox->papszEnvVars[iVar + 1] = NULL; pSandbox->papszEnvVars[iVar] = pszNew; pSandbox->environ[iVar + 1] = NULL; pSandbox->environ[iVar] = pszNew; pSandbox->papwszEnvVars[iVar + 1] = NULL; pSandbox->papwszEnvVars[iVar] = pwszNew; pSandbox->wenviron[iVar + 1] = NULL; pSandbox->wenviron[iVar] = pwszNew; return 0; } kHlpFree(pwszNew); } kHlpFree(pszNew); } KW_LOG(("Out of memory!\n")); return 0; } /** ANSI unsetenv worker. */ static int kwSandboxDoUnsetEnvA(PKWSANDBOX pSandbox, const char *pchVar, KSIZE cchVar) { KSIZE iVar = 0; char *pszEnv; while ((pszEnv = pSandbox->papszEnvVars[iVar]) != NULL) { if ( _strnicmp(pszEnv, pchVar, cchVar) == 0 && pszEnv[cchVar] == '=') { KSIZE cVars = iVar; while (pSandbox->papszEnvVars[cVars]) cVars++; kHlpAssert(pSandbox->papwszEnvVars[iVar] != NULL); kHlpAssert(pSandbox->papwszEnvVars[cVars] == NULL); KW_LOG(("kwSandboxDoUnsetEnvA: Removing iVar=%d: %p='%s' and %p='%ls'; new cVars=%d\n", iVar, pSandbox->papszEnvVars[iVar], pSandbox->papszEnvVars[iVar], pSandbox->papwszEnvVars[iVar], pSandbox->papwszEnvVars[iVar], cVars - 1)); kHlpFree(pSandbox->papszEnvVars[iVar]); pSandbox->papszEnvVars[iVar] = pSandbox->papszEnvVars[cVars]; pSandbox->environ[iVar] = pSandbox->papszEnvVars[cVars]; pSandbox->papszEnvVars[cVars] = NULL; pSandbox->environ[cVars] = NULL; kHlpFree(pSandbox->papwszEnvVars[iVar]); pSandbox->papwszEnvVars[iVar] = pSandbox->papwszEnvVars[cVars]; pSandbox->wenviron[iVar] = pSandbox->papwszEnvVars[cVars]; pSandbox->papwszEnvVars[cVars] = NULL; pSandbox->wenviron[cVars] = NULL; return 0; } iVar++; } return KERR_ENVVAR_NOT_FOUND; } /** UTF-16 unsetenv worker. */ static int kwSandboxDoUnsetEnvW(PKWSANDBOX pSandbox, const wchar_t *pwcVar, KSIZE cwcVar) { KSIZE iVar = 0; wchar_t *pwszEnv; while ((pwszEnv = pSandbox->papwszEnvVars[iVar]) != NULL) { if ( _wcsnicmp(pwszEnv, pwcVar, cwcVar) == 0 && pwszEnv[cwcVar] == '=') { KSIZE cVars = iVar; while (pSandbox->papwszEnvVars[cVars]) cVars++; kHlpAssert(pSandbox->papszEnvVars[iVar] != NULL); kHlpAssert(pSandbox->papszEnvVars[cVars] == NULL); KW_LOG(("kwSandboxDoUnsetEnvA: Removing iVar=%d: %p='%s' and %p='%ls'; new cVars=%d\n", iVar, pSandbox->papszEnvVars[iVar], pSandbox->papszEnvVars[iVar], pSandbox->papwszEnvVars[iVar], pSandbox->papwszEnvVars[iVar], cVars - 1)); kHlpFree(pSandbox->papszEnvVars[iVar]); pSandbox->papszEnvVars[iVar] = pSandbox->papszEnvVars[cVars]; pSandbox->environ[iVar] = pSandbox->papszEnvVars[cVars]; pSandbox->papszEnvVars[cVars] = NULL; pSandbox->environ[cVars] = NULL; kHlpFree(pSandbox->papwszEnvVars[iVar]); pSandbox->papwszEnvVars[iVar] = pSandbox->papwszEnvVars[cVars]; pSandbox->wenviron[iVar] = pSandbox->papwszEnvVars[cVars]; pSandbox->papwszEnvVars[cVars] = NULL; pSandbox->wenviron[cVars] = NULL; return 0; } iVar++; } return KERR_ENVVAR_NOT_FOUND; } /** ANSI getenv worker. */ static char *kwSandboxDoGetEnvA(PKWSANDBOX pSandbox, const char *pchVar, KSIZE cchVar) { KSIZE iVar = 0; char *pszEnv; while ((pszEnv = pSandbox->papszEnvVars[iVar++]) != NULL) if ( _strnicmp(pszEnv, pchVar, cchVar) == 0 && pszEnv[cchVar] == '=') return &pszEnv[cchVar + 1]; return NULL; } /** UTF-16 getenv worker. */ static wchar_t *kwSandboxDoGetEnvW(PKWSANDBOX pSandbox, const wchar_t *pwcVar, KSIZE cwcVar) { KSIZE iVar = 0; wchar_t *pwszEnv; while ((pwszEnv = pSandbox->papwszEnvVars[iVar++]) != NULL) if ( _wcsnicmp(pwszEnv, pwcVar, cwcVar) == 0 && pwszEnv[cwcVar] == '=') return &pwszEnv[cwcVar + 1]; return NULL; } /** Kernel32 - GetEnvironmentVariableA() */ static DWORD WINAPI kwSandbox_Kernel32_GetEnvironmentVariableA(LPCSTR pszVar, LPSTR pszValue, DWORD cbValue) { char *pszFoundValue; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pszFoundValue = kwSandboxDoGetEnvA(&g_Sandbox, pszVar, kHlpStrLen(pszVar)); if (pszFoundValue) { DWORD cchRet = kwStrCopyStyle1(pszFoundValue, pszValue, cbValue); KW_LOG(("GetEnvironmentVariableA: '%s' -> %u (%s)\n", pszVar, cchRet, pszFoundValue)); return cchRet; } KW_LOG(("GetEnvironmentVariableA: '%s' -> 0\n", pszVar)); SetLastError(ERROR_ENVVAR_NOT_FOUND); return 0; } /** Kernel32 - GetEnvironmentVariableW() */ static DWORD WINAPI kwSandbox_Kernel32_GetEnvironmentVariableW(LPCWSTR pwszVar, LPWSTR pwszValue, DWORD cwcValue) { wchar_t *pwszFoundValue; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pwszFoundValue = kwSandboxDoGetEnvW(&g_Sandbox, pwszVar, kwUtf16Len(pwszVar)); if (pwszFoundValue) { DWORD cchRet = kwUtf16CopyStyle1(pwszFoundValue, pwszValue, cwcValue); KW_LOG(("GetEnvironmentVariableW: '%ls' -> %u (%ls)\n", pwszVar, cchRet, pwszFoundValue)); return cchRet; } KW_LOG(("GetEnvironmentVariableW: '%ls' -> 0\n", pwszVar)); SetLastError(ERROR_ENVVAR_NOT_FOUND); return 0; } /** Kernel32 - SetEnvironmentVariableA() */ static BOOL WINAPI kwSandbox_Kernel32_SetEnvironmentVariableA(LPCSTR pszVar, LPCSTR pszValue) { int rc; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (pszValue) rc = kwSandboxDoSetEnvA(&g_Sandbox, pszVar, kHlpStrLen(pszVar), pszValue); else { kwSandboxDoUnsetEnvA(&g_Sandbox, pszVar, kHlpStrLen(pszVar)); rc = 0; //?? } if (rc == 0) { KW_LOG(("SetEnvironmentVariableA(%s,%s) -> TRUE\n", pszVar, pszValue)); return TRUE; } SetLastError(ERROR_NOT_ENOUGH_MEMORY); KW_LOG(("SetEnvironmentVariableA(%s,%s) -> FALSE!\n", pszVar, pszValue)); return FALSE; } /** Kernel32 - SetEnvironmentVariableW() */ static BOOL WINAPI kwSandbox_Kernel32_SetEnvironmentVariableW(LPCWSTR pwszVar, LPCWSTR pwszValue) { int rc; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (pwszValue) rc = kwSandboxDoSetEnvW(&g_Sandbox, pwszVar, kwUtf16Len(pwszVar), pwszValue); else { kwSandboxDoUnsetEnvW(&g_Sandbox, pwszVar, kwUtf16Len(pwszVar)); rc = 0; //?? } if (rc == 0) { KW_LOG(("SetEnvironmentVariableA(%ls,%ls) -> TRUE\n", pwszVar, pwszValue)); return TRUE; } SetLastError(ERROR_NOT_ENOUGH_MEMORY); KW_LOG(("SetEnvironmentVariableA(%ls,%ls) -> FALSE!\n", pwszVar, pwszValue)); return FALSE; } /** Kernel32 - ExpandEnvironmentStringsA() */ static DWORD WINAPI kwSandbox_Kernel32_ExpandEnvironmentStringsA(LPCSTR pszSrc, LPSTR pwszDst, DWORD cbDst) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); KWFS_TODO(); return 0; } /** Kernel32 - ExpandEnvironmentStringsW() */ static DWORD WINAPI kwSandbox_Kernel32_ExpandEnvironmentStringsW(LPCWSTR pwszSrc, LPWSTR pwszDst, DWORD cbDst) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); KWFS_TODO(); return 0; } /** CRT - _putenv(). */ static int __cdecl kwSandbox_msvcrt__putenv(const char *pszVarEqualValue) { int rc; char const *pszEqual; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pszEqual = kHlpStrChr(pszVarEqualValue, '='); if (pszEqual) { rc = kwSandboxDoSetEnvA(&g_Sandbox, pszVarEqualValue, pszEqual - pszVarEqualValue, pszEqual + 1); if (rc == 0) { } else rc = -1; } else { kwSandboxDoUnsetEnvA(&g_Sandbox, pszVarEqualValue, kHlpStrLen(pszVarEqualValue)); rc = 0; } KW_LOG(("_putenv(%s) -> %d\n", pszVarEqualValue, rc)); return rc; } /** CRT - _wputenv(). */ static int __cdecl kwSandbox_msvcrt__wputenv(const wchar_t *pwszVarEqualValue) { int rc; wchar_t const *pwszEqual; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pwszEqual = wcschr(pwszVarEqualValue, '='); if (pwszEqual) { rc = kwSandboxDoSetEnvW(&g_Sandbox, pwszVarEqualValue, pwszEqual - pwszVarEqualValue, pwszEqual + 1); if (rc == 0) { } else rc = -1; } else { kwSandboxDoUnsetEnvW(&g_Sandbox, pwszVarEqualValue, kwUtf16Len(pwszVarEqualValue)); rc = 0; } KW_LOG(("_wputenv(%ls) -> %d\n", pwszVarEqualValue, rc)); return rc; } /** CRT - _putenv_s(). */ static errno_t __cdecl kwSandbox_msvcrt__putenv_s(const char *pszVar, const char *pszValue) { char const *pszEqual; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pszEqual = kHlpStrChr(pszVar, '='); if (pszEqual == NULL) { if (pszValue) { int rc = kwSandboxDoSetEnvA(&g_Sandbox, pszVar, kHlpStrLen(pszVar), pszValue); if (rc == 0) { KW_LOG(("_putenv_s(%s,%s) -> 0\n", pszVar, pszValue)); return 0; } } else { kwSandboxDoUnsetEnvA(&g_Sandbox, pszVar, kHlpStrLen(pszVar)); KW_LOG(("_putenv_s(%ls,NULL) -> 0\n", pszVar)); return 0; } KW_LOG(("_putenv_s(%s,%s) -> ENOMEM\n", pszVar, pszValue)); return ENOMEM; } KW_LOG(("_putenv_s(%s,%s) -> EINVAL\n", pszVar, pszValue)); return EINVAL; } /** CRT - _wputenv_s(). */ static errno_t __cdecl kwSandbox_msvcrt__wputenv_s(const wchar_t *pwszVar, const wchar_t *pwszValue) { wchar_t const *pwszEqual; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pwszEqual = wcschr(pwszVar, '='); if (pwszEqual == NULL) { if (pwszValue) { int rc = kwSandboxDoSetEnvW(&g_Sandbox, pwszVar, kwUtf16Len(pwszVar), pwszValue); if (rc == 0) { KW_LOG(("_wputenv_s(%ls,%ls) -> 0\n", pwszVar, pwszValue)); return 0; } } else { kwSandboxDoUnsetEnvW(&g_Sandbox, pwszVar, kwUtf16Len(pwszVar)); KW_LOG(("_wputenv_s(%ls,NULL) -> 0\n", pwszVar)); return 0; } KW_LOG(("_wputenv_s(%ls,%ls) -> ENOMEM\n", pwszVar, pwszValue)); return ENOMEM; } KW_LOG(("_wputenv_s(%ls,%ls) -> EINVAL\n", pwszVar, pwszValue)); return EINVAL; } /** CRT - get pointer to the __initenv variable (initial environment). */ static char *** __cdecl kwSandbox_msvcrt___p___initenv(void) { KW_LOG(("__p___initenv\n")); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); KWFS_TODO(); return &g_Sandbox.initenv; } /** CRT - get pointer to the __winitenv variable (initial environment). */ static wchar_t *** __cdecl kwSandbox_msvcrt___p___winitenv(void) { KW_LOG(("__p___winitenv\n")); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); KWFS_TODO(); return &g_Sandbox.winitenv; } /** CRT - get pointer to the _environ variable (current environment). */ static char *** __cdecl kwSandbox_msvcrt___p__environ(void) { KW_LOG(("__p__environ\n")); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); return &g_Sandbox.environ; } /** CRT - get pointer to the _wenviron variable (current environment). */ static wchar_t *** __cdecl kwSandbox_msvcrt___p__wenviron(void) { KW_LOG(("__p__wenviron\n")); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); return &g_Sandbox.wenviron; } /** CRT - get the _environ variable (current environment). * @remarks Not documented or prototyped? */ static KUPTR /*void*/ __cdecl kwSandbox_msvcrt__get_environ(char ***ppapszEnviron) { KWFS_TODO(); /** @todo check the callers expectations! */ kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); *ppapszEnviron = g_Sandbox.environ; return 0; } /** CRT - get the _wenviron variable (current environment). * @remarks Not documented or prototyped? */ static KUPTR /*void*/ __cdecl kwSandbox_msvcrt__get_wenviron(wchar_t ***ppapwszEnviron) { KWFS_TODO(); /** @todo check the callers expectations! */ kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); *ppapwszEnviron = g_Sandbox.wenviron; return 0; } /* * * Loader related APIs * Loader related APIs * Loader related APIs * */ /** * Kernel32 - LoadLibraryExA() worker that loads resource files and such. */ static HMODULE WINAPI kwSandbox_Kernel32_LoadLibraryExA_Resource(PKWDYNLOAD pDynLoad, DWORD fFlags) { /* Load it first. */ HMODULE hmod = LoadLibraryExA(pDynLoad->szRequest, NULL /*hFile*/, fFlags); if (hmod) { pDynLoad->hmod = hmod; pDynLoad->pMod = NULL; /* indicates special */ pDynLoad->pNext = g_Sandbox.pTool->u.Sandboxed.pDynLoadHead; g_Sandbox.pTool->u.Sandboxed.pDynLoadHead = pDynLoad; KW_LOG(("LoadLibraryExA(%s,,[resource]) -> %p\n", pDynLoad->szRequest, pDynLoad->hmod)); } else kHlpFree(pDynLoad); return hmod; } /** * Kernel32 - LoadLibraryExA() worker that deals with the api-ms-xxx modules. */ static HMODULE WINAPI kwSandbox_Kernel32_LoadLibraryExA_VirtualApiModule(PKWDYNLOAD pDynLoad, DWORD fFlags) { HMODULE hmod; PKWMODULE pMod; KU32 uHashPath; KSIZE idxHash; char szNormPath[256]; KSIZE cbFilename = kHlpStrLen(pDynLoad->szRequest) + 1; /* * Lower case it. */ if (cbFilename <= sizeof(szNormPath)) { kHlpMemCopy(szNormPath, pDynLoad->szRequest, cbFilename); _strlwr(szNormPath); } else { SetLastError(ERROR_FILENAME_EXCED_RANGE); return NULL; } /* * Check if it has already been loaded so we don't create an unnecessary * loader module for it. */ uHashPath = kwStrHash(szNormPath); idxHash = uHashPath % K_ELEMENTS(g_apModules); pMod = g_apModules[idxHash]; if (pMod) { do { if ( pMod->uHashPath == uHashPath && kHlpStrComp(pMod->pszPath, szNormPath) == 0) { pDynLoad->pMod = kwLdrModuleRetain(pMod); pDynLoad->hmod = pMod->hOurMod; pDynLoad->pNext = g_Sandbox.pTool->u.Sandboxed.pDynLoadHead; g_Sandbox.pTool->u.Sandboxed.pDynLoadHead = pDynLoad; KW_LOG(("LoadLibraryExA(%s,,) -> %p [already loaded]\n", pDynLoad->szRequest, pDynLoad->hmod)); return pDynLoad->hmod; } pMod = pMod->pNext; } while (pMod); } /* * Try load it and make a kLdr module for it. */ hmod = LoadLibraryExA(szNormPath, NULL /*hFile*/, fFlags); if (hmod) { PKLDRMOD pLdrMod; int rc = kLdrModOpenNativeByHandle((KUPTR)hmod, &pLdrMod); if (rc == 0) { PKWMODULE pMod = kwLdrModuleCreateForNativekLdrModule(pLdrMod, szNormPath, cbFilename, uHashPath, K_FALSE /*fDoReplacements*/); if (pMod) { kwToolAddModuleAndImports(g_Sandbox.pTool, pMod); pDynLoad = (PKWDYNLOAD)kHlpAlloc(sizeof(*pDynLoad) + cbFilename + cbFilename * sizeof(wchar_t)); if (pDynLoad) { pDynLoad->pMod = pMod; pDynLoad->hmod = hmod; pDynLoad->pNext = g_Sandbox.pTool->u.Sandboxed.pDynLoadHead; g_Sandbox.pTool->u.Sandboxed.pDynLoadHead = pDynLoad; KW_LOG(("LoadLibraryExA(%s,,) -> %p\n", pDynLoad->szRequest, pDynLoad->hmod)); return hmod; } KWFS_TODO(); } else KWFS_TODO(); } else KWFS_TODO(); } kHlpFree(pDynLoad); return hmod; } /** Kernel32 - LoadLibraryExA() */ static HMODULE WINAPI kwSandbox_Kernel32_LoadLibraryExA(LPCSTR pszFilename, HANDLE hFile, DWORD fFlags) { KSIZE cchFilename = kHlpStrLen(pszFilename); const char *pszSearchPath; PKWDYNLOAD pDynLoad; PKWMODULE pMod; int rc; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); /* * Deal with a couple of extremely unlikely special cases right away. */ if ( ( !(fFlags & LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE) || (fFlags & LOAD_LIBRARY_AS_IMAGE_RESOURCE)) && (hFile == NULL || hFile == INVALID_HANDLE_VALUE) ) { /* likely */ } else { KWFS_TODO(); return LoadLibraryExA(pszFilename, hFile, fFlags); } /* * Check if we've already got a dynload entry for this one. */ for (pDynLoad = g_Sandbox.pTool->u.Sandboxed.pDynLoadHead; pDynLoad; pDynLoad = pDynLoad->pNext) if ( pDynLoad->cchRequest == cchFilename && kHlpMemComp(pDynLoad->szRequest, pszFilename, cchFilename) == 0) { if (pDynLoad->pMod) rc = kwLdrModuleInitTree(pDynLoad->pMod); else rc = 0; if (rc == 0) { KW_LOG(("LoadLibraryExA(%s,,) -> %p [cached]\n", pszFilename, pDynLoad->hmod)); return pDynLoad->hmod; } SetLastError(ERROR_DLL_INIT_FAILED); return NULL; } /* * Allocate a dynload entry for the request. */ pDynLoad = (PKWDYNLOAD)kHlpAlloc(sizeof(*pDynLoad) + cchFilename + 1); if (pDynLoad) { pDynLoad->cchRequest = cchFilename; kHlpMemCopy(pDynLoad->szRequest, pszFilename, cchFilename + 1); } else { KW_LOG(("LoadLibraryExA: Out of memory!\n")); SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } /* * Deal with resource / data DLLs. */ if (fFlags & ( DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE | LOAD_LIBRARY_AS_IMAGE_RESOURCE) ) return kwSandbox_Kernel32_LoadLibraryExA_Resource(pDynLoad, fFlags); /* * Special case: api-ms-win-core-synch-l1-2-0 and friends (32-bit yasm, built with VS2015). */ if ( strnicmp(pszFilename, TUPLE("api-ms-")) == 0 && kHlpIsFilenameOnly(pszFilename)) return kwSandbox_Kernel32_LoadLibraryExA_VirtualApiModule(pDynLoad, fFlags); /* * Normal library loading. * We start by being very lazy and reusing the code for resolving imports. */ pszSearchPath = kwSandboxDoGetEnvA(&g_Sandbox, "PATH", 4); if (!kHlpIsFilenameOnly(pszFilename)) pMod = kwLdrModuleTryLoadDll(pszFilename, KWLOCATION_UNKNOWN, g_Sandbox.pTool->u.Sandboxed.pExe, pszSearchPath); else { rc = kwLdrModuleResolveAndLookup(pszFilename, g_Sandbox.pTool->u.Sandboxed.pExe, NULL /*pImporter*/, pszSearchPath, &pMod); if (rc != 0) pMod = NULL; } if (pMod && pMod != (PKWMODULE)~(KUPTR)0) { /* Enter it into the tool module table and dynamic link request cache. */ kwToolAddModuleAndImports(g_Sandbox.pTool, pMod); pDynLoad->pMod = pMod; pDynLoad->hmod = pMod->hOurMod; pDynLoad->pNext = g_Sandbox.pTool->u.Sandboxed.pDynLoadHead; g_Sandbox.pTool->u.Sandboxed.pDynLoadHead = pDynLoad; /* * Make sure it's initialized (need to link it first since DllMain may * use loader APIs). */ rc = kwLdrModuleInitTree(pMod); if (rc == 0) { KW_LOG(("LoadLibraryExA(%s,,) -> %p\n", pszFilename, pDynLoad->hmod)); return pDynLoad->hmod; } SetLastError(ERROR_DLL_INIT_FAILED); } else { KWFS_TODO(); kHlpFree(pDynLoad); SetLastError(pMod ? ERROR_BAD_EXE_FORMAT : ERROR_MOD_NOT_FOUND); } return NULL; } /** Kernel32 - LoadLibraryExA() for native overloads */ static HMODULE WINAPI kwSandbox_Kernel32_Native_LoadLibraryExA(LPCSTR pszFilename, HANDLE hFile, DWORD fFlags) { char szPath[1024]; KWLDR_LOG(("kwSandbox_Kernel32_Native_LoadLibraryExA(%s, %p, %#x)\n", pszFilename, hFile, fFlags)); /* * We may have to help resolved unqualified DLLs living in the executable directory. */ if (kHlpIsFilenameOnly(pszFilename)) { KSIZE cchFilename = kHlpStrLen(pszFilename); KSIZE cchExePath = g_Sandbox.pTool->u.Sandboxed.pExe->offFilename; if (cchExePath + cchFilename + 1 <= sizeof(szPath)) { kHlpMemCopy(szPath, g_Sandbox.pTool->u.Sandboxed.pExe->pszPath, cchExePath); kHlpMemCopy(&szPath[cchExePath], pszFilename, cchFilename + 1); if (kwFsPathExists(szPath)) { KWLDR_LOG(("kwSandbox_Kernel32_Native_LoadLibraryExA: %s -> %s\n", pszFilename, szPath)); pszFilename = szPath; } } if (pszFilename != szPath) { KSIZE cchSuffix = 0; KBOOL fNeedSuffix = K_FALSE; const char *pszCur = kwSandboxDoGetEnvA(&g_Sandbox, "PATH", 4); while (*pszCur != '\0') { /* Find the end of the component */ KSIZE cch = 0; while (pszCur[cch] != ';' && pszCur[cch] != '\0') cch++; if ( cch > 0 /* wrong, but whatever */ && cch + 1 + cchFilename + cchSuffix < sizeof(szPath)) { char *pszDst = kHlpMemPCopy(szPath, pszCur, cch); if ( szPath[cch - 1] != ':' && szPath[cch - 1] != '/' && szPath[cch - 1] != '\\') *pszDst++ = '\\'; pszDst = kHlpMemPCopy(pszDst, pszFilename, cchFilename); if (fNeedSuffix) pszDst = kHlpMemPCopy(pszDst, ".dll", 4); *pszDst = '\0'; if (kwFsPathExists(szPath)) { KWLDR_LOG(("kwSandbox_Kernel32_Native_LoadLibraryExA: %s -> %s\n", pszFilename, szPath)); pszFilename = szPath; break; } } /* Advance */ pszCur += cch; while (*pszCur == ';') pszCur++; } } } return LoadLibraryExA(pszFilename, hFile, fFlags); } /** Kernel32 - LoadLibraryExW() */ static HMODULE WINAPI kwSandbox_Kernel32_LoadLibraryExW(LPCWSTR pwszFilename, HANDLE hFile, DWORD fFlags) { char szTmp[4096]; KSIZE cchTmp = kwUtf16ToStr(pwszFilename, szTmp, sizeof(szTmp)); if (cchTmp < sizeof(szTmp)) return kwSandbox_Kernel32_LoadLibraryExA(szTmp, hFile, fFlags); KWFS_TODO(); SetLastError(ERROR_FILENAME_EXCED_RANGE); return NULL; } /** Kernel32 - LoadLibraryA() */ static HMODULE WINAPI kwSandbox_Kernel32_LoadLibraryA(LPCSTR pszFilename) { return kwSandbox_Kernel32_LoadLibraryExA(pszFilename, NULL /*hFile*/, 0 /*fFlags*/); } /** Kernel32 - LoadLibraryW() */ static HMODULE WINAPI kwSandbox_Kernel32_LoadLibraryW(LPCWSTR pwszFilename) { char szTmp[4096]; KSIZE cchTmp = kwUtf16ToStr(pwszFilename, szTmp, sizeof(szTmp)); if (cchTmp < sizeof(szTmp)) return kwSandbox_Kernel32_LoadLibraryExA(szTmp, NULL /*hFile*/, 0 /*fFlags*/); KWFS_TODO(); SetLastError(ERROR_FILENAME_EXCED_RANGE); return NULL; } /** Kernel32 - FreeLibrary() */ static BOOL WINAPI kwSandbox_Kernel32_FreeLibrary(HMODULE hmod) { /* Ignored, we like to keep everything loaded. */ kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); return TRUE; } /** Kernel32 - GetModuleHandleA() */ static HMODULE WINAPI kwSandbox_Kernel32_GetModuleHandleA(LPCSTR pszModule) { KSIZE i; KSIZE cchModule; PKWDYNLOAD pDynLoad; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); /* * The executable. */ if (pszModule == NULL) return (HMODULE)g_Sandbox.pTool->u.Sandboxed.pExe->hOurMod; /* * Cache of system modules we've seen queried. */ cchModule = kHlpStrLen(pszModule); for (i = 0; i < K_ELEMENTS(g_aGetModuleHandleCache); i++) if ( g_aGetModuleHandleCache[i].cchName == cchModule && stricmp(pszModule, g_aGetModuleHandleCache[i].pszName) == 0) { if (g_aGetModuleHandleCache[i].hmod != NULL) return g_aGetModuleHandleCache[i].hmod; return g_aGetModuleHandleCache[i].hmod = GetModuleHandleA(pszModule); } /* * Modules we've dynamically loaded. */ for (pDynLoad = g_Sandbox.pTool->u.Sandboxed.pDynLoadHead; pDynLoad; pDynLoad = pDynLoad->pNext) if ( pDynLoad->pMod && ( stricmp(pDynLoad->pMod->pszPath, pszModule) == 0 || stricmp(&pDynLoad->pMod->pszPath[pDynLoad->pMod->offFilename], pszModule) == 0) ) { if ( pDynLoad->pMod->fNative || pDynLoad->pMod->u.Manual.enmState == KWMODSTATE_READY) { KW_LOG(("kwSandbox_Kernel32_GetModuleHandleA(%s,,) -> %p [dynload]\n", pszModule, pDynLoad->hmod)); return pDynLoad->hmod; } SetLastError(ERROR_MOD_NOT_FOUND); return NULL; } kwErrPrintf("pszModule=%s\n", pszModule); KWFS_TODO(); SetLastError(ERROR_MOD_NOT_FOUND); return NULL; } /** Kernel32 - GetModuleHandleW() */ static HMODULE WINAPI kwSandbox_Kernel32_GetModuleHandleW(LPCWSTR pwszModule) { KSIZE i; KSIZE cwcModule; PKWDYNLOAD pDynLoad; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); /* * The executable. */ if (pwszModule == NULL) return (HMODULE)g_Sandbox.pTool->u.Sandboxed.pExe->hOurMod; /* * Cache of system modules we've seen queried. */ cwcModule = kwUtf16Len(pwszModule); for (i = 0; i < K_ELEMENTS(g_aGetModuleHandleCache); i++) if ( g_aGetModuleHandleCache[i].cwcName == cwcModule && _wcsicmp(pwszModule, g_aGetModuleHandleCache[i].pwszName) == 0) { if (g_aGetModuleHandleCache[i].hmod != NULL) return g_aGetModuleHandleCache[i].hmod; return g_aGetModuleHandleCache[i].hmod = GetModuleHandleW(pwszModule); } /* * Modules we've dynamically loaded. */ for (pDynLoad = g_Sandbox.pTool->u.Sandboxed.pDynLoadHead; pDynLoad; pDynLoad = pDynLoad->pNext) if ( pDynLoad->pMod && ( _wcsicmp(pDynLoad->pMod->pwszPath, pwszModule) == 0 || _wcsicmp(&pDynLoad->pMod->pwszPath[pDynLoad->pMod->offFilename], pwszModule) == 0) ) /** @todo wrong offset */ { if ( pDynLoad->pMod->fNative || pDynLoad->pMod->u.Manual.enmState == KWMODSTATE_READY) { KW_LOG(("kwSandbox_Kernel32_GetModuleHandleW(%ls,,) -> %p [dynload]\n", pwszModule, pDynLoad->hmod)); return pDynLoad->hmod; } SetLastError(ERROR_MOD_NOT_FOUND); return NULL; } kwErrPrintf("pwszModule=%ls\n", pwszModule); KWFS_TODO(); SetLastError(ERROR_MOD_NOT_FOUND); return NULL; } /** Used to debug dynamically resolved procedures. */ static UINT WINAPI kwSandbox_BreakIntoDebugger(void *pv1, void *pv2, void *pv3, void *pv4) { #ifdef _MSC_VER __debugbreak(); #else KWFS_TODO(); #endif return -1; } #ifndef NDEBUG /* * This wraps up to three InvokeCompilerPassW functions and dumps their arguments to the log. */ # if K_ARCH == K_ARCH_X86_32 static char g_szInvokeCompilePassW[] = "_InvokeCompilerPassW@16"; # else static char g_szInvokeCompilePassW[] = "InvokeCompilerPassW"; # endif typedef KIPTR __stdcall FNINVOKECOMPILERPASSW(int cArgs, wchar_t **papwszArgs, KUPTR fFlags, void **phCluiInstance); typedef FNINVOKECOMPILERPASSW *PFNINVOKECOMPILERPASSW; typedef struct KWCXINTERCEPTORENTRY { PFNINVOKECOMPILERPASSW pfnOrg; PKWMODULE pModule; PFNINVOKECOMPILERPASSW pfnWrap; } KWCXINTERCEPTORENTRY; static KIPTR kwSandbox_Cx_InvokeCompilerPassW_Common(int cArgs, wchar_t **papwszArgs, KUPTR fFlags, void **phCluiInstance, KWCXINTERCEPTORENTRY *pEntry) { int i; KIPTR rcExit; KW_LOG(("%s!InvokeCompilerPassW(%d, %p, %#x, %p)\n", &pEntry->pModule->pszPath[pEntry->pModule->offFilename], cArgs, papwszArgs, fFlags, phCluiInstance)); for (i = 0; i < cArgs; i++) KW_LOG((" papwszArgs[%u]='%ls'\n", i, papwszArgs[i])); rcExit = pEntry->pfnOrg(cArgs, papwszArgs, fFlags, phCluiInstance); KW_LOG(("%s!InvokeCompilerPassW returns %d\n", &pEntry->pModule->pszPath[pEntry->pModule->offFilename], rcExit)); return rcExit; } static FNINVOKECOMPILERPASSW kwSandbox_Cx_InvokeCompilerPassW_0; static FNINVOKECOMPILERPASSW kwSandbox_Cx_InvokeCompilerPassW_1; static FNINVOKECOMPILERPASSW kwSandbox_Cx_InvokeCompilerPassW_2; static KWCXINTERCEPTORENTRY g_aCxInterceptorEntries[] = { { NULL, NULL, kwSandbox_Cx_InvokeCompilerPassW_0 }, { NULL, NULL, kwSandbox_Cx_InvokeCompilerPassW_1 }, { NULL, NULL, kwSandbox_Cx_InvokeCompilerPassW_2 }, }; static KIPTR __stdcall kwSandbox_Cx_InvokeCompilerPassW_0(int cArgs, wchar_t **papwszArgs, KUPTR fFlags, void **phCluiInstance) { return kwSandbox_Cx_InvokeCompilerPassW_Common(cArgs, papwszArgs, fFlags, phCluiInstance, &g_aCxInterceptorEntries[0]); } static KIPTR __stdcall kwSandbox_Cx_InvokeCompilerPassW_1(int cArgs, wchar_t **papwszArgs, KUPTR fFlags, void **phCluiInstance) { return kwSandbox_Cx_InvokeCompilerPassW_Common(cArgs, papwszArgs, fFlags, phCluiInstance, &g_aCxInterceptorEntries[1]); } static KIPTR __stdcall kwSandbox_Cx_InvokeCompilerPassW_2(int cArgs, wchar_t **papwszArgs, KUPTR fFlags, void **phCluiInstance) { return kwSandbox_Cx_InvokeCompilerPassW_Common(cArgs, papwszArgs, fFlags, phCluiInstance, &g_aCxInterceptorEntries[2]); } #endif /* !NDEBUG */ /** Kernel32 - GetProcAddress() */ static FARPROC WINAPI kwSandbox_Kernel32_GetProcAddress(HMODULE hmod, LPCSTR pszProc) { KSIZE i; PKWMODULE pMod; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); /* * Try locate the module. */ pMod = kwToolLocateModuleByHandle(g_Sandbox.pTool, hmod); if (pMod) { KLDRADDR uValue; int rc = kLdrModQuerySymbol(pMod->pLdrMod, pMod->fNative ? NULL : pMod->u.Manual.pvBits, pMod->fNative ? KLDRMOD_BASEADDRESS_MAP : (KUPTR)pMod->u.Manual.pbLoad, KU32_MAX /*iSymbol*/, pszProc, kHlpStrLen(pszProc), NULL /*pszVersion*/, NULL /*pfnGetForwarder*/, NULL /*pvUser*/, &uValue, NULL /*pfKind*/); if (rc == 0) { //static int s_cDbgGets = 0; KU32 cchProc = (KU32)kHlpStrLen(pszProc); KU32 i = g_cSandboxGetProcReplacements; while (i-- > 0) if ( g_aSandboxGetProcReplacements[i].cchFunction == cchProc && kHlpMemComp(g_aSandboxGetProcReplacements[i].pszFunction, pszProc, cchProc) == 0) { if ( !g_aSandboxGetProcReplacements[i].pszModule || kHlpStrICompAscii(g_aSandboxGetProcReplacements[i].pszModule, &pMod->pszPath[pMod->offFilename]) == 0) { if ( !g_aSandboxGetProcReplacements[i].fOnlyExe || (KUPTR)_ReturnAddress() - (KUPTR)g_Sandbox.pTool->u.Sandboxed.pExe->hOurMod < g_Sandbox.pTool->u.Sandboxed.pExe->cbImage) { uValue = g_aSandboxGetProcReplacements[i].pfnReplacement; KW_LOG(("GetProcAddress(%s, %s) -> %p replaced\n", pMod->pszPath, pszProc, (KUPTR)uValue)); } kwLdrModuleRelease(pMod); return (FARPROC)(KUPTR)uValue; } } #ifndef NDEBUG /* Intercept the compiler pass method, dumping arguments. */ if (kHlpStrComp(pszProc, g_szInvokeCompilePassW) == 0) { KU32 i; for (i = 0; i < K_ELEMENTS(g_aCxInterceptorEntries); i++) if ((KUPTR)g_aCxInterceptorEntries[i].pfnOrg == uValue) { uValue = (KUPTR)g_aCxInterceptorEntries[i].pfnWrap; KW_LOG(("GetProcAddress: intercepting InvokeCompilerPassW\n")); break; } if (i >= K_ELEMENTS(g_aCxInterceptorEntries)) while (i-- > 0) if (g_aCxInterceptorEntries[i].pfnOrg == NULL) { g_aCxInterceptorEntries[i].pfnOrg = (PFNINVOKECOMPILERPASSW)(KUPTR)uValue; g_aCxInterceptorEntries[i].pModule = pMod; uValue = (KUPTR)g_aCxInterceptorEntries[i].pfnWrap; KW_LOG(("GetProcAddress: intercepting InvokeCompilerPassW (new)\n")); break; } } #endif KW_LOG(("GetProcAddress(%s, %s) -> %p\n", pMod->pszPath, pszProc, (KUPTR)uValue)); kwLdrModuleRelease(pMod); //s_cDbgGets++; //if (s_cGets >= 3) // return (FARPROC)kwSandbox_BreakIntoDebugger; return (FARPROC)(KUPTR)uValue; } KWFS_TODO(); SetLastError(ERROR_PROC_NOT_FOUND); kwLdrModuleRelease(pMod); return NULL; } /* * Hmm... could be a cached module-by-name. */ for (i = 0; i < K_ELEMENTS(g_aGetModuleHandleCache); i++) if (g_aGetModuleHandleCache[i].hmod == hmod) return GetProcAddress(hmod, pszProc); KWFS_TODO(); return GetProcAddress(hmod, pszProc); } /** Kernel32 - GetModuleFileNameA() */ static DWORD WINAPI kwSandbox_Kernel32_GetModuleFileNameA(HMODULE hmod, LPSTR pszFilename, DWORD cbFilename) { PKWMODULE pMod; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pMod = kwToolLocateModuleByHandle(g_Sandbox.pTool, hmod); if (pMod != NULL) { DWORD cbRet = kwStrCopyStyle1(pMod->pszPath, pszFilename, cbFilename); kwLdrModuleRelease(pMod); return cbRet; } KWFS_TODO(); return 0; } /** Kernel32 - GetModuleFileNameW() */ static DWORD WINAPI kwSandbox_Kernel32_GetModuleFileNameW(HMODULE hmod, LPWSTR pwszFilename, DWORD cbFilename) { PKWMODULE pMod; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pMod = kwToolLocateModuleByHandle(g_Sandbox.pTool, hmod); if (pMod) { DWORD cwcRet = kwUtf16CopyStyle1(pMod->pwszPath, pwszFilename, cbFilename); kwLdrModuleRelease(pMod); return cwcRet; } KWFS_TODO(); return 0; } /** NtDll - RtlPcToFileHeader * This is necessary for msvcr100.dll!CxxThrowException. */ static PVOID WINAPI kwSandbox_ntdll_RtlPcToFileHeader(PVOID pvPC, PVOID *ppvImageBase) { PVOID pvRet; /* * Do a binary lookup of the module table for the current tool. * This will give us a */ if (g_Sandbox.fRunning) { KUPTR const uPC = (KUPTR)pvPC; PKWMODULE *papMods = g_Sandbox.pTool->u.Sandboxed.papModules; KU32 iEnd = g_Sandbox.pTool->u.Sandboxed.cModules; KU32 i; if (iEnd) { KU32 iStart = 0; i = iEnd / 2; for (;;) { KUPTR const uHModThis = (KUPTR)papMods[i]->hOurMod; if (uPC < uHModThis) { iEnd = i; if (iStart < i) { } else break; } else if (uPC != uHModThis) { iStart = ++i; if (i < iEnd) { } else break; } else { /* This isn't supposed to happen. */ break; } i = iStart + (iEnd - iStart) / 2; } /* For reasons of simplicity (= copy & paste), we end up with the module after the one we're interested in here. */ i--; if (i < g_Sandbox.pTool->u.Sandboxed.cModules && papMods[i]->pLdrMod) { KSIZE uRvaPC = uPC - (KUPTR)papMods[i]->hOurMod; if (uRvaPC < papMods[i]->cbImage) { *ppvImageBase = papMods[i]->hOurMod; pvRet = papMods[i]->hOurMod; KW_LOG(("RtlPcToFileHeader(PC=%p) -> %p, *ppvImageBase=%p [our]\n", pvPC, pvRet, *ppvImageBase)); return pvRet; } } } else i = 0; } /* * Call the regular API. */ pvRet = RtlPcToFileHeader(pvPC, ppvImageBase); KW_LOG(("RtlPcToFileHeader(PC=%p) -> %p, *ppvImageBase=%p \n", pvPC, pvRet, *ppvImageBase)); return pvRet; } /* * * File access APIs (for speeding them up). * File access APIs (for speeding them up). * File access APIs (for speeding them up). * */ /** * Converts a lookup error to a windows error code. * * @returns The windows error code. * @param enmError The lookup error. */ static DWORD kwFsLookupErrorToWindowsError(KFSLOOKUPERROR enmError) { switch (enmError) { case KFSLOOKUPERROR_NOT_FOUND: case KFSLOOKUPERROR_NOT_DIR: return ERROR_FILE_NOT_FOUND; case KFSLOOKUPERROR_PATH_COMP_NOT_FOUND: case KFSLOOKUPERROR_PATH_COMP_NOT_DIR: return ERROR_PATH_NOT_FOUND; case KFSLOOKUPERROR_PATH_TOO_LONG: return ERROR_FILENAME_EXCED_RANGE; case KFSLOOKUPERROR_OUT_OF_MEMORY: return ERROR_NOT_ENOUGH_MEMORY; default: return ERROR_PATH_NOT_FOUND; } } #ifdef WITH_TEMP_MEMORY_FILES /** * Checks for a cl.exe temporary file. * * There are quite a bunch of these. They seems to be passing data between the * first and second compiler pass. Since they're on disk, they get subjected to * AV software screening and normal file consistency rules. So, not necessarily * a very efficient way of handling reasonably small amounts of data. * * We make the files live in virtual memory by intercepting their opening, * writing, reading, closing , mapping, unmapping, and maybe some more stuff. * * @returns K_TRUE / K_FALSE * @param pwszFilename The file name being accessed. */ static KBOOL kwFsIsClTempFileW(const wchar_t *pwszFilename) { wchar_t const *pwszName = kwPathGetFilenameW(pwszFilename); if (pwszName) { /* The name starts with _CL_... */ if ( pwszName[0] == '_' && pwszName[1] == 'C' && pwszName[2] == 'L' && pwszName[3] == '_' ) { /* ... followed by 8 xdigits and ends with a two letter file type. Simplify this check by just checking that it's alpha numerical ascii from here on. */ wchar_t wc; pwszName += 4; while ((wc = *pwszName++) != '\0') { if (wc < 127 && iswalnum(wc)) { /* likely */ } else return K_FALSE; } return K_TRUE; } } return K_FALSE; } /** * Creates a handle to a temporary file. * * @returns The handle on success. * INVALID_HANDLE_VALUE and SetLastError on failure. * @param pTempFile The temporary file. * @param dwDesiredAccess The desired access to the handle. * @param fMapping Whether this is a mapping (K_TRUE) or file * (K_FALSE) handle type. */ static HANDLE kwFsTempFileCreateHandle(PKWFSTEMPFILE pTempFile, DWORD dwDesiredAccess, KBOOL fMapping) { /* * Create a handle to the temporary file. */ HANDLE hFile = INVALID_HANDLE_VALUE; HANDLE hProcSelf = GetCurrentProcess(); if (DuplicateHandle(hProcSelf, hProcSelf, hProcSelf, &hFile, SYNCHRONIZE, FALSE, 0 /*dwOptions*/)) { PKWHANDLE pHandle = (PKWHANDLE)kHlpAlloc(sizeof(*pHandle)); if (pHandle) { pHandle->enmType = !fMapping ? KWHANDLETYPE_TEMP_FILE : KWHANDLETYPE_TEMP_FILE_MAPPING; pHandle->cRefs = 1; pHandle->offFile = 0; pHandle->hHandle = hFile; pHandle->dwDesiredAccess = dwDesiredAccess; pHandle->u.pTempFile = pTempFile; if (kwSandboxHandleTableEnter(&g_Sandbox, pHandle, hFile)) { pTempFile->cActiveHandles++; kHlpAssert(pTempFile->cActiveHandles >= 1); kHlpAssert(pTempFile->cActiveHandles <= 2); KWFS_LOG(("kwFsTempFileCreateHandle: Temporary file '%ls' -> %p\n", pTempFile->pwszPath, hFile)); return hFile; } kHlpFree(pHandle); } else KWFS_LOG(("kwFsTempFileCreateHandle: Out of memory!\n")); SetLastError(ERROR_NOT_ENOUGH_MEMORY); } else KWFS_LOG(("kwFsTempFileCreateHandle: DuplicateHandle failed: err=%u\n", GetLastError())); return INVALID_HANDLE_VALUE; } static HANDLE kwFsTempFileCreateW(const wchar_t *pwszFilename, DWORD dwDesiredAccess, DWORD dwCreationDisposition) { HANDLE hFile; DWORD dwErr; /* * Check if we've got an existing temp file. * ASSUME exact same path for now. */ KSIZE const cwcFilename = kwUtf16Len(pwszFilename); PKWFSTEMPFILE pTempFile; for (pTempFile = g_Sandbox.pTempFileHead; pTempFile != NULL; pTempFile = pTempFile->pNext) { /* Since the last two chars are usually the only difference, we check them manually before calling memcmp. */ if ( pTempFile->cwcPath == cwcFilename && pTempFile->pwszPath[cwcFilename - 1] == pwszFilename[cwcFilename - 1] && pTempFile->pwszPath[cwcFilename - 2] == pwszFilename[cwcFilename - 2] && kHlpMemComp(pTempFile->pwszPath, pwszFilename, cwcFilename) == 0) break; } /* * Create a new temporary file instance if not found. */ if (pTempFile == NULL) { KSIZE cbFilename; switch (dwCreationDisposition) { case CREATE_ALWAYS: case OPEN_ALWAYS: dwErr = NO_ERROR; break; case CREATE_NEW: kHlpAssertFailed(); SetLastError(ERROR_ALREADY_EXISTS); return INVALID_HANDLE_VALUE; case OPEN_EXISTING: case TRUNCATE_EXISTING: kHlpAssertFailed(); SetLastError(ERROR_FILE_NOT_FOUND); return INVALID_HANDLE_VALUE; default: kHlpAssertFailed(); SetLastError(ERROR_INVALID_PARAMETER); return INVALID_HANDLE_VALUE; } cbFilename = (cwcFilename + 1) * sizeof(wchar_t); pTempFile = (PKWFSTEMPFILE)kHlpAlloc(sizeof(*pTempFile) + cbFilename); if (pTempFile) { pTempFile->cwcPath = (KU16)cwcFilename; pTempFile->cbFile = 0; pTempFile->cbFileAllocated = 0; pTempFile->cActiveHandles = 0; pTempFile->cMappings = 0; pTempFile->cSegs = 0; pTempFile->paSegs = NULL; pTempFile->pwszPath = (wchar_t const *)kHlpMemCopy(pTempFile + 1, pwszFilename, cbFilename); pTempFile->pNext = g_Sandbox.pTempFileHead; g_Sandbox.pTempFileHead = pTempFile; KWFS_LOG(("kwFsTempFileCreateW: Created new temporary file '%ls'\n", pwszFilename)); } else { KWFS_LOG(("kwFsTempFileCreateW: Out of memory!\n")); SetLastError(ERROR_NOT_ENOUGH_MEMORY); return INVALID_HANDLE_VALUE; } } else { switch (dwCreationDisposition) { case OPEN_EXISTING: dwErr = NO_ERROR; break; case OPEN_ALWAYS: dwErr = ERROR_ALREADY_EXISTS ; break; case TRUNCATE_EXISTING: case CREATE_ALWAYS: kHlpAssertFailed(); pTempFile->cbFile = 0; dwErr = ERROR_ALREADY_EXISTS; break; case CREATE_NEW: kHlpAssertFailed(); SetLastError(ERROR_FILE_EXISTS); return INVALID_HANDLE_VALUE; default: kHlpAssertFailed(); SetLastError(ERROR_INVALID_PARAMETER); return INVALID_HANDLE_VALUE; } } /* * Create a handle to the temporary file. */ hFile = kwFsTempFileCreateHandle(pTempFile, dwDesiredAccess, K_FALSE /*fMapping*/); if (hFile != INVALID_HANDLE_VALUE) SetLastError(dwErr); return hFile; } #endif /* WITH_TEMP_MEMORY_FILES */ /** * Worker for kwFsIsCacheableExtensionA and kwFsIsCacheableExtensionW * * @returns K_TRUE if cacheable, K_FALSE if not. * @param wcFirst The first extension character. * @param wcSecond The second extension character. * @param wcThird The third extension character. * @param fAttrQuery Set if it's for an attribute query, clear if for * file creation. */ static KBOOL kwFsIsCacheableExtensionCommon(wchar_t wcFirst, wchar_t wcSecond, wchar_t wcThird, KBOOL fAttrQuery) { /* C++ header without an extension or a directory. */ if (wcFirst == '\0') { /** @todo exclude temporary files... */ return K_TRUE; } /* C Header: .h */ if (wcFirst == 'h' || wcFirst == 'H') { if (wcSecond == '\0') return K_TRUE; /* C++ Header: .hpp, .hxx */ if ( (wcSecond == 'p' || wcSecond == 'P') && (wcThird == 'p' || wcThird == 'P')) return K_TRUE; if ( (wcSecond == 'x' || wcSecond == 'X') && (wcThird == 'x' || wcThird == 'X')) return K_TRUE; } /* Misc starting with i. */ else if (wcFirst == 'i' || wcFirst == 'I') { if (wcSecond != '\0') { if (wcSecond == 'n' || wcSecond == 'N') { /* C++ inline header: .inl */ if (wcThird == 'l' || wcThird == 'L') return K_TRUE; /* Assembly include file: .inc */ if (wcThird == 'c' || wcThird == 'C') return K_TRUE; } } } /* Assembly header: .mac */ else if (wcFirst == 'm' || wcFirst == 'M') { if (wcSecond == 'a' || wcSecond == 'A') { if (wcThird == 'c' || wcThird == 'C') return K_TRUE; } } #ifdef WITH_PCH_CACHING /* Precompiled header: .pch */ else if (wcFirst == 'p' || wcFirst == 'P') { if (wcSecond == 'c' || wcSecond == 'C') { if (wcThird == 'h' || wcThird == 'H') return !g_Sandbox.fNoPchCaching; } } #endif #if 0 /* Experimental - need to flush these afterwards as they're highly unlikely to be used after the link is done. */ /* Linker - Object file: .obj */ if ((wcFirst == 'o' || wcFirst == 'O') && g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_LINK) { if (wcSecond == 'b' || wcSecond == 'B') { if (wcThird == 'j' || wcThird == 'J') return K_TRUE; } } #endif else if (fAttrQuery) { /* Dynamic link library: .dll */ if (wcFirst == 'd' || wcFirst == 'D') { if (wcSecond == 'l' || wcSecond == 'L') { if (wcThird == 'l' || wcThird == 'L') return K_TRUE; } } /* Executable file: .exe */ else if (wcFirst == 'e' || wcFirst == 'E') { if (wcSecond == 'x' || wcSecond == 'X') { if (wcThird == 'e' || wcThird == 'E') return K_TRUE; } } /* Response file: .rsp */ else if (wcFirst == 'r' || wcFirst == 'R') { if (wcSecond == 's' || wcSecond == 'S') { if (wcThird == 'p' || wcThird == 'P') return !g_Sandbox.fNoPchCaching; } } /* Linker: */ if (g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_LINK) { /* Object file: .obj */ if (wcFirst == 'o' || wcFirst == 'O') { if (wcSecond == 'b' || wcSecond == 'B') { if (wcThird == 'j' || wcThird == 'J') return K_TRUE; } } /* Library file: .lib */ else if (wcFirst == 'l' || wcFirst == 'L') { if (wcSecond == 'i' || wcSecond == 'I') { if (wcThird == 'b' || wcThird == 'B') return K_TRUE; } } /* Linker definition file: .def */ else if (wcFirst == 'd' || wcFirst == 'D') { if (wcSecond == 'e' || wcSecond == 'E') { if (wcThird == 'f' || wcThird == 'F') return K_TRUE; } } } } return K_FALSE; } /** * Checks if the file extension indicates that the file/dir is something we * ought to cache. * * @returns K_TRUE if cachable, K_FALSE if not. * @param pszExt The kHlpGetExt result. * @param fAttrQuery Set if it's for an attribute query, clear if for * file creation. */ static KBOOL kwFsIsCacheableExtensionA(const char *pszExt, KBOOL fAttrQuery) { wchar_t const wcFirst = *pszExt; if (wcFirst) { wchar_t const wcSecond = pszExt[1]; if (wcSecond) { wchar_t const wcThird = pszExt[2]; if (pszExt[3] == '\0') return kwFsIsCacheableExtensionCommon(wcFirst, wcSecond, wcThird, fAttrQuery); return K_FALSE; } return kwFsIsCacheableExtensionCommon(wcFirst, 0, 0, fAttrQuery); } return kwFsIsCacheableExtensionCommon(0, 0, 0, fAttrQuery); } /** * Checks if the extension of the given UTF-16 path indicates that the file/dir * should be cached. * * @returns K_TRUE if cachable, K_FALSE if not. * @param pwszPath The UTF-16 path to examine. * @param fAttrQuery Set if it's for an attribute query, clear if for * file creation. */ static KBOOL kwFsIsCacheablePathExtensionW(const wchar_t *pwszPath, KBOOL fAttrQuery) { KSIZE cwcExt; wchar_t const *pwszExt = kwFsPathGetExtW(pwszPath, &cwcExt); switch (cwcExt) { case 3: return kwFsIsCacheableExtensionCommon(pwszExt[0], pwszExt[1], pwszExt[2], fAttrQuery); case 2: return kwFsIsCacheableExtensionCommon(pwszExt[0], pwszExt[1], 0, fAttrQuery); case 1: return kwFsIsCacheableExtensionCommon(pwszExt[0], 0, 0, fAttrQuery); case 0: return kwFsIsCacheableExtensionCommon(0, 0, 0, fAttrQuery); } return K_FALSE; } /** * Creates a new * * @returns * @param pFsObj . * @param pwszFilename . */ static PKFSWCACHEDFILE kwFsObjCacheNewFile(PKFSOBJ pFsObj) { HANDLE hFile; MY_IO_STATUS_BLOCK Ios; MY_OBJECT_ATTRIBUTES ObjAttr; MY_UNICODE_STRING UniStr; MY_NTSTATUS rcNt; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); /* * Open the file relative to the parent directory. */ kHlpAssert(pFsObj->bObjType == KFSOBJ_TYPE_FILE); kHlpAssert(pFsObj->pParent); kHlpAssertReturn(pFsObj->pParent->hDir != INVALID_HANDLE_VALUE, NULL); Ios.Information = -1; Ios.u.Status = -1; UniStr.Buffer = (wchar_t *)pFsObj->pwszName; UniStr.Length = (USHORT)(pFsObj->cwcName * sizeof(wchar_t)); UniStr.MaximumLength = UniStr.Length + sizeof(wchar_t); MyInitializeObjectAttributes(&ObjAttr, &UniStr, OBJ_CASE_INSENSITIVE, pFsObj->pParent->hDir, NULL /*pSecAttr*/); rcNt = g_pfnNtCreateFile(&hFile, GENERIC_READ | SYNCHRONIZE, &ObjAttr, &Ios, NULL, /*cbFileInitialAlloc */ FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, /*pEaBuffer*/ 0); /*cbEaBuffer*/ if (MY_NT_SUCCESS(rcNt)) { /* * Read the whole file into memory. */ LARGE_INTEGER cbFile; if (GetFileSizeEx(hFile, &cbFile)) { if ( cbFile.QuadPart >= 0 #ifdef WITH_PCH_CACHING && ( cbFile.QuadPart < 16*1024*1024 || ( cbFile.QuadPart < 96*1024*1024 && pFsObj->cchName > 4 && !g_Sandbox.fNoPchCaching && kHlpStrICompAscii(&pFsObj->pszName[pFsObj->cchName - 4], ".pch") == 0) ) #endif ) { KU32 cbCache = (KU32)cbFile.QuadPart; HANDLE hMapping = CreateFileMappingW(hFile, NULL /*pSecAttrs*/, PAGE_READONLY, 0 /*cbMaxLow*/, 0 /*cbMaxHigh*/, NULL /*pwszName*/); if (hMapping != NULL) { KU8 *pbCache = (KU8 *)MapViewOfFile(hMapping, FILE_MAP_READ, 0 /*offFileHigh*/, 0 /*offFileLow*/, cbCache); if (pbCache) { /* * Create the cached file object. */ PKFSWCACHEDFILE pCachedFile; KU32 cbPath = pFsObj->cchParent + pFsObj->cchName + 2; pCachedFile = (PKFSWCACHEDFILE)kFsCacheObjAddUserData(g_pFsCache, pFsObj, KW_DATA_KEY_CACHED_FILE, sizeof(*pCachedFile) + cbPath); if (pCachedFile) { pCachedFile->hCached = hFile; pCachedFile->hSection = hMapping; pCachedFile->cbCached = cbCache; pCachedFile->pbCached = pbCache; pCachedFile->pFsObj = pFsObj; kFsCacheObjGetFullPathA(pFsObj, pCachedFile->szPath, cbPath, '/'); kFsCacheObjRetain(pFsObj); g_cReadCachedFiles++; g_cbReadCachedFiles += cbCache; KWFS_LOG(("Cached '%s': %p LB %#x, hCached=%p\n", pCachedFile->szPath, pbCache, cbCache, hFile)); return pCachedFile; } KWFS_LOG(("Failed to allocate KFSWCACHEDFILE structure!\n")); } else KWFS_LOG(("Failed to cache file: MapViewOfFile failed: %u\n", GetLastError())); CloseHandle(hMapping); } else KWFS_LOG(("Failed to cache file: CreateFileMappingW failed: %u\n", GetLastError())); } else KWFS_LOG(("File to big to cache! %#llx\n", cbFile.QuadPart)); } else KWFS_LOG(("File to get file size! err=%u\n", GetLastError())); g_pfnNtClose(hFile); } else KWFS_LOG(("Error opening '%ls' for caching: %#x\n", pFsObj->pwszName, rcNt)); return NULL; } /** * Kernel32 - Common code for kwFsObjCacheCreateFile and CreateFileMappingW/A. */ static KBOOL kwFsObjCacheCreateFileHandle(PKFSWCACHEDFILE pCachedFile, DWORD dwDesiredAccess, BOOL fInheritHandle, KBOOL fIsFileHandle, HANDLE *phFile) { HANDLE hProcSelf = GetCurrentProcess(); if (DuplicateHandle(hProcSelf, fIsFileHandle ? pCachedFile->hCached : pCachedFile->hSection, hProcSelf, phFile, dwDesiredAccess, fInheritHandle, 0 /*dwOptions*/)) { /* * Create handle table entry for the duplicate handle. */ PKWHANDLE pHandle = (PKWHANDLE)kHlpAlloc(sizeof(*pHandle)); if (pHandle) { pHandle->enmType = fIsFileHandle ? KWHANDLETYPE_FSOBJ_READ_CACHE : KWHANDLETYPE_FSOBJ_READ_CACHE_MAPPING; pHandle->cRefs = 1; pHandle->offFile = 0; pHandle->hHandle = *phFile; pHandle->dwDesiredAccess = dwDesiredAccess; pHandle->u.pCachedFile = pCachedFile; if (kwSandboxHandleTableEnter(&g_Sandbox, pHandle, pHandle->hHandle)) return K_TRUE; kHlpFree(pHandle); } else KWFS_LOG(("Out of memory for handle!\n")); CloseHandle(*phFile); *phFile = INVALID_HANDLE_VALUE; } else KWFS_LOG(("DuplicateHandle failed! err=%u\n", GetLastError())); return K_FALSE; } /** * Kernel32 - Common code for CreateFileW and CreateFileA. */ static KBOOL kwFsObjCacheCreateFile(PKFSOBJ pFsObj, DWORD dwDesiredAccess, BOOL fInheritHandle, HANDLE *phFile) { *phFile = INVALID_HANDLE_VALUE; /* * At the moment we only handle existing files. */ if (pFsObj->bObjType == KFSOBJ_TYPE_FILE) { PKFSWCACHEDFILE pCachedFile = (PKFSWCACHEDFILE)kFsCacheObjGetUserData(g_pFsCache, pFsObj, KW_DATA_KEY_CACHED_FILE); kHlpAssert(pFsObj->fHaveStats); if ( pCachedFile != NULL || (pCachedFile = kwFsObjCacheNewFile(pFsObj)) != NULL) { if (kwFsObjCacheCreateFileHandle(pCachedFile, dwDesiredAccess, fInheritHandle, K_TRUE /*fIsFileHandle*/, phFile)) return K_TRUE; } } /** @todo Deal with non-existing files if it becomes necessary (it's not for VS2010). */ /* Do fallback, please. */ return K_FALSE; } /** Kernel32 - CreateFileA */ static HANDLE WINAPI kwSandbox_Kernel32_CreateFileA(LPCSTR pszFilename, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES pSecAttrs, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { HANDLE hFile; /* * Check for include files and similar that we do read-only caching of. */ if (dwCreationDisposition == FILE_OPEN_IF) { if ( dwDesiredAccess == GENERIC_READ || dwDesiredAccess == FILE_GENERIC_READ) { if (dwShareMode & FILE_SHARE_READ) { if ( !pSecAttrs || ( pSecAttrs->nLength == sizeof(*pSecAttrs) && pSecAttrs->lpSecurityDescriptor == NULL ) ) { const char *pszExt = kHlpGetExt(pszFilename); if (kwFsIsCacheableExtensionA(pszExt, K_FALSE /*fAttrQuery*/)) { KFSLOOKUPERROR enmError; PKFSOBJ pFsObj; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pFsObj = kFsCacheLookupNoMissingA(g_pFsCache, pszFilename, &enmError); if (pFsObj) { KBOOL fRc = kwFsObjCacheCreateFile(pFsObj, dwDesiredAccess, pSecAttrs && pSecAttrs->bInheritHandle, &hFile); kFsCacheObjRelease(g_pFsCache, pFsObj); if (fRc) { KWFS_LOG(("CreateFileA(%s) -> %p [cached]\n", pszFilename, hFile)); return hFile; } } /* These are for nasm and yasm header searching. Cache will already have checked the directories for the file, no need to call CreateFile to do it again. */ else if (enmError == KFSLOOKUPERROR_NOT_FOUND) { KWFS_LOG(("CreateFileA(%s) -> INVALID_HANDLE_VALUE, ERROR_FILE_NOT_FOUND\n", pszFilename)); return INVALID_HANDLE_VALUE; } else if ( enmError == KFSLOOKUPERROR_PATH_COMP_NOT_FOUND || enmError == KFSLOOKUPERROR_PATH_COMP_NOT_DIR) { KWFS_LOG(("CreateFileA(%s) -> INVALID_HANDLE_VALUE, ERROR_PATH_NOT_FOUND\n", pszFilename)); return INVALID_HANDLE_VALUE; } /* fallback */ hFile = CreateFileA(pszFilename, dwDesiredAccess, dwShareMode, pSecAttrs, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); KWFS_LOG(("CreateFileA(%s) -> %p (err=%u) [fallback]\n", pszFilename, hFile, GetLastError())); return hFile; } } } } } /* * Okay, normal. */ hFile = CreateFileA(pszFilename, dwDesiredAccess, dwShareMode, pSecAttrs, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); if (hFile != INVALID_HANDLE_VALUE) { kHlpAssert( KW_HANDLE_TO_INDEX(hFile) >= g_Sandbox.cHandles || g_Sandbox.papHandles[KW_HANDLE_TO_INDEX(hFile)] == NULL); } KWFS_LOG(("CreateFileA(%s) -> %p\n", pszFilename, hFile)); return hFile; } /** Kernel32 - CreateFileW */ static HANDLE WINAPI kwSandbox_Kernel32_CreateFileW(LPCWSTR pwszFilename, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES pSecAttrs, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { HANDLE hFile; #ifdef WITH_TEMP_MEMORY_FILES /* * Check for temporary files (cl.exe only). */ if ( g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_CL && !(dwFlagsAndAttributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE | FILE_FLAG_BACKUP_SEMANTICS)) && !(dwDesiredAccess & (GENERIC_EXECUTE | FILE_EXECUTE)) && kwFsIsClTempFileW(pwszFilename)) { hFile = kwFsTempFileCreateW(pwszFilename, dwDesiredAccess, dwCreationDisposition); KWFS_LOG(("CreateFileW(%ls) -> %p [temp]\n", pwszFilename, hFile)); return hFile; } #endif /* * Check for include files and similar that we do read-only caching of. */ if (dwCreationDisposition == FILE_OPEN_IF) { if ( dwDesiredAccess == GENERIC_READ || dwDesiredAccess == FILE_GENERIC_READ) { if (dwShareMode & FILE_SHARE_READ) { if ( !pSecAttrs || ( pSecAttrs->nLength == sizeof(*pSecAttrs) && pSecAttrs->lpSecurityDescriptor == NULL ) ) { if (kwFsIsCacheablePathExtensionW(pwszFilename, K_FALSE /*fAttrQuery*/)) { KFSLOOKUPERROR enmError; PKFSOBJ pFsObj; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pFsObj = kFsCacheLookupNoMissingW(g_pFsCache, pwszFilename, &enmError); if (pFsObj) { KBOOL fRc = kwFsObjCacheCreateFile(pFsObj, dwDesiredAccess, pSecAttrs && pSecAttrs->bInheritHandle, &hFile); kFsCacheObjRelease(g_pFsCache, pFsObj); if (fRc) { KWFS_LOG(("CreateFileW(%ls) -> %p [cached]\n", pwszFilename, hFile)); return hFile; } } /* These are for nasm and yasm style header searching. Cache will already have checked the directories for the file, no need to call CreateFile to do it again. */ else if (enmError == KFSLOOKUPERROR_NOT_FOUND) { KWFS_LOG(("CreateFileW(%ls) -> INVALID_HANDLE_VALUE, ERROR_FILE_NOT_FOUND\n", pwszFilename)); return INVALID_HANDLE_VALUE; } else if ( enmError == KFSLOOKUPERROR_PATH_COMP_NOT_FOUND || enmError == KFSLOOKUPERROR_PATH_COMP_NOT_DIR) { KWFS_LOG(("CreateFileW(%ls) -> INVALID_HANDLE_VALUE, ERROR_PATH_NOT_FOUND\n", pwszFilename)); return INVALID_HANDLE_VALUE; } /* fallback */ hFile = CreateFileW(pwszFilename, dwDesiredAccess, dwShareMode, pSecAttrs, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); KWFS_LOG(("CreateFileW(%ls) -> %p (err=%u) [fallback]\n", pwszFilename, hFile, GetLastError())); return hFile; } } else KWFS_LOG(("CreateFileW: incompatible security attributes (nLength=%#x pDesc=%p)\n", pSecAttrs->nLength, pSecAttrs->lpSecurityDescriptor)); } else KWFS_LOG(("CreateFileW: incompatible sharing mode %#x\n", dwShareMode)); } else KWFS_LOG(("CreateFileW: incompatible desired access %#x\n", dwDesiredAccess)); } else KWFS_LOG(("CreateFileW: incompatible disposition %u\n", dwCreationDisposition)); /* * Okay, normal. */ hFile = CreateFileW(pwszFilename, dwDesiredAccess, dwShareMode, pSecAttrs, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); if (hFile != INVALID_HANDLE_VALUE) { kHlpAssert( KW_HANDLE_TO_INDEX(hFile) >= g_Sandbox.cHandles || g_Sandbox.papHandles[KW_HANDLE_TO_INDEX(hFile)] == NULL); } KWFS_LOG(("CreateFileW(%ls) -> %p\n", pwszFilename, hFile)); return hFile; } /** Kernel32 - SetFilePointer */ static DWORD WINAPI kwSandbox_Kernel32_SetFilePointer(HANDLE hFile, LONG cbMove, PLONG pcbMoveHi, DWORD dwMoveMethod) { KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hFile); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle != NULL) { KU32 cbFile; KI64 offMove = pcbMoveHi ? ((KI64)*pcbMoveHi << 32) | cbMove : cbMove; switch (pHandle->enmType) { case KWHANDLETYPE_FSOBJ_READ_CACHE: cbFile = pHandle->u.pCachedFile->cbCached; break; #ifdef WITH_TEMP_MEMORY_FILES case KWHANDLETYPE_TEMP_FILE: cbFile = pHandle->u.pTempFile->cbFile; break; #endif case KWHANDLETYPE_TEMP_FILE_MAPPING: case KWHANDLETYPE_OUTPUT_BUF: default: kHlpAssertFailed(); SetLastError(ERROR_INVALID_FUNCTION); return INVALID_SET_FILE_POINTER; } switch (dwMoveMethod) { case FILE_BEGIN: break; case FILE_CURRENT: offMove += pHandle->offFile; break; case FILE_END: offMove += cbFile; break; default: KWFS_LOG(("SetFilePointer(%p) - invalid seek method %u! [cached]\n", hFile)); SetLastError(ERROR_INVALID_PARAMETER); return INVALID_SET_FILE_POINTER; } if (offMove >= 0) { if (offMove >= (KSSIZE)cbFile) { /* For read-only files, seeking beyond the end isn't useful to us, so clamp it. */ if (pHandle->enmType != KWHANDLETYPE_TEMP_FILE) offMove = (KSSIZE)cbFile; /* For writable files, seeking beyond the end is fine, but check that we've got the type range for the request. */ else if (((KU64)offMove & KU32_MAX) != (KU64)offMove) { kHlpAssertMsgFailed(("%#llx\n", offMove)); SetLastError(ERROR_SEEK); return INVALID_SET_FILE_POINTER; } } pHandle->offFile = (KU32)offMove; } else { KWFS_LOG(("SetFilePointer(%p) - negative seek! [cached]\n", hFile)); SetLastError(ERROR_NEGATIVE_SEEK); return INVALID_SET_FILE_POINTER; } if (pcbMoveHi) *pcbMoveHi = (KU64)offMove >> 32; KWFS_LOG(("SetFilePointer(%p,%#x,?,%u) -> %#llx [%s]\n", hFile, cbMove, dwMoveMethod, offMove, pHandle->enmType == KWHANDLETYPE_FSOBJ_READ_CACHE ? "cached" : "temp")); SetLastError(NO_ERROR); return (KU32)offMove; } } KWFS_LOG(("SetFilePointer(%p, %d, %p=%d, %d)\n", hFile, cbMove, pcbMoveHi ? *pcbMoveHi : 0, dwMoveMethod)); return SetFilePointer(hFile, cbMove, pcbMoveHi, dwMoveMethod); } /** Kernel32 - SetFilePointerEx */ static BOOL WINAPI kwSandbox_Kernel32_SetFilePointerEx(HANDLE hFile, LARGE_INTEGER offMove, PLARGE_INTEGER poffNew, DWORD dwMoveMethod) { KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hFile); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle != NULL) { KI64 offMyMove = offMove.QuadPart; KU32 cbFile; switch (pHandle->enmType) { case KWHANDLETYPE_FSOBJ_READ_CACHE: cbFile = pHandle->u.pCachedFile->cbCached; break; #ifdef WITH_TEMP_MEMORY_FILES case KWHANDLETYPE_TEMP_FILE: cbFile = pHandle->u.pTempFile->cbFile; break; #endif case KWHANDLETYPE_TEMP_FILE_MAPPING: case KWHANDLETYPE_OUTPUT_BUF: default: kHlpAssertFailed(); SetLastError(ERROR_INVALID_FUNCTION); return INVALID_SET_FILE_POINTER; } switch (dwMoveMethod) { case FILE_BEGIN: break; case FILE_CURRENT: offMyMove += pHandle->offFile; break; case FILE_END: offMyMove += cbFile; break; default: KWFS_LOG(("SetFilePointer(%p) - invalid seek method %u! [cached]\n", hFile)); SetLastError(ERROR_INVALID_PARAMETER); return INVALID_SET_FILE_POINTER; } if (offMyMove >= 0) { if (offMyMove >= (KSSIZE)cbFile) { /* For read-only files, seeking beyond the end isn't useful to us, so clamp it. */ if (pHandle->enmType != KWHANDLETYPE_TEMP_FILE) offMyMove = (KSSIZE)cbFile; /* For writable files, seeking beyond the end is fine, but check that we've got the type range for the request. */ else if (((KU64)offMyMove & KU32_MAX) != (KU64)offMyMove) { kHlpAssertMsgFailed(("%#llx\n", offMyMove)); SetLastError(ERROR_SEEK); return INVALID_SET_FILE_POINTER; } } pHandle->offFile = (KU32)offMyMove; } else { KWFS_LOG(("SetFilePointerEx(%p) - negative seek! [cached]\n", hFile)); SetLastError(ERROR_NEGATIVE_SEEK); return INVALID_SET_FILE_POINTER; } if (poffNew) poffNew->QuadPart = offMyMove; KWFS_LOG(("SetFilePointerEx(%p,%#llx,,%u) -> TRUE, %#llx [%s]\n", hFile, offMove.QuadPart, dwMoveMethod, offMyMove, pHandle->enmType == KWHANDLETYPE_FSOBJ_READ_CACHE ? "cached" : "temp")); return TRUE; } } KWFS_LOG(("SetFilePointerEx(%p)\n", hFile)); return SetFilePointerEx(hFile, offMove, poffNew, dwMoveMethod); } /** Kernel32 - ReadFile */ static BOOL WINAPI kwSandbox_Kernel32_ReadFile(HANDLE hFile, LPVOID pvBuffer, DWORD cbToRead, LPDWORD pcbActuallyRead, LPOVERLAPPED pOverlapped) { BOOL fRet; KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hFile); g_cReadFileCalls++; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle != NULL) { switch (pHandle->enmType) { case KWHANDLETYPE_FSOBJ_READ_CACHE: { PKFSWCACHEDFILE pCachedFile = pHandle->u.pCachedFile; KU32 cbActually = pCachedFile->cbCached - pHandle->offFile; if (cbActually > cbToRead) cbActually = cbToRead; #ifdef WITH_HASH_MD5_CACHE if (g_Sandbox.pHashHead) { g_Sandbox.LastHashRead.pCachedFile = pCachedFile; g_Sandbox.LastHashRead.offRead = pHandle->offFile; g_Sandbox.LastHashRead.cbRead = cbActually; g_Sandbox.LastHashRead.pvRead = pvBuffer; } #endif kHlpMemCopy(pvBuffer, &pCachedFile->pbCached[pHandle->offFile], cbActually); pHandle->offFile += cbActually; kHlpAssert(!pOverlapped); kHlpAssert(pcbActuallyRead); *pcbActuallyRead = cbActually; g_cbReadFileFromReadCached += cbActually; g_cbReadFileTotal += cbActually; g_cReadFileFromReadCached++; KWFS_LOG(("ReadFile(%p,,%#x) -> TRUE, %#x bytes [cached]\n", hFile, cbToRead, cbActually)); return TRUE; } #ifdef WITH_TEMP_MEMORY_FILES case KWHANDLETYPE_TEMP_FILE: { PKWFSTEMPFILE pTempFile = pHandle->u.pTempFile; KU32 cbActually; if (pHandle->offFile < pTempFile->cbFile) { cbActually = pTempFile->cbFile - pHandle->offFile; if (cbActually > cbToRead) cbActually = cbToRead; /* Copy the data. */ if (cbActually > 0) { KU32 cbLeft; KU32 offSeg; KWFSTEMPFILESEG const *paSegs = pTempFile->paSegs; /* Locate the segment containing the byte at offFile. */ KU32 iSeg = pTempFile->cSegs - 1; kHlpAssert(pTempFile->cSegs > 0); while (paSegs[iSeg].offData > pHandle->offFile) iSeg--; /* Copy out the data. */ cbLeft = cbActually; offSeg = (pHandle->offFile - paSegs[iSeg].offData); for (;;) { KU32 cbAvail = paSegs[iSeg].cbDataAlloc - offSeg; if (cbAvail >= cbLeft) { kHlpMemCopy(pvBuffer, &paSegs[iSeg].pbData[offSeg], cbLeft); break; } pvBuffer = kHlpMemPCopy(pvBuffer, &paSegs[iSeg].pbData[offSeg], cbAvail); cbLeft -= cbAvail; offSeg = 0; iSeg++; kHlpAssert(iSeg < pTempFile->cSegs); } /* Update the file offset. */ pHandle->offFile += cbActually; } } /* Read does not commit file space, so return zero bytes. */ else cbActually = 0; kHlpAssert(!pOverlapped); kHlpAssert(pcbActuallyRead); *pcbActuallyRead = cbActually; g_cbReadFileTotal += cbActually; g_cbReadFileFromInMemTemp += cbActually; g_cReadFileFromInMemTemp++; KWFS_LOG(("ReadFile(%p,,%#x) -> TRUE, %#x bytes [temp]\n", hFile, cbToRead, (KU32)cbActually)); return TRUE; } #endif /* WITH_TEMP_MEMORY_FILES */ case KWHANDLETYPE_TEMP_FILE_MAPPING: case KWHANDLETYPE_OUTPUT_BUF: default: kHlpAssertFailed(); SetLastError(ERROR_INVALID_FUNCTION); *pcbActuallyRead = 0; return FALSE; } } } fRet = ReadFile(hFile, pvBuffer, cbToRead, pcbActuallyRead, pOverlapped); if (fRet && pcbActuallyRead) g_cbReadFileTotal += *pcbActuallyRead; KWFS_LOG(("ReadFile(%p,%p,%#x,,) -> %d, %#x\n", hFile, pvBuffer, cbToRead, fRet, pcbActuallyRead ? *pcbActuallyRead : 0)); return fRet; } /** Kernel32 - ReadFileEx */ static BOOL WINAPI kwSandbox_Kernel32_ReadFileEx(HANDLE hFile, LPVOID pvBuffer, DWORD cbToRead, LPOVERLAPPED pOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE pfnCompletionRoutine) { KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hFile); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle != NULL) { kHlpAssertFailed(); } } KWFS_LOG(("ReadFile(%p)\n", hFile)); return ReadFileEx(hFile, pvBuffer, cbToRead, pOverlapped, pfnCompletionRoutine); } #ifdef WITH_STD_OUT_ERR_BUFFERING /** * Write something to a handle, making sure everything is actually written. * * @param hHandle Where to write it to. * @param pchBuf What to write * @param cchToWrite How much to write. */ static void kwSandboxOutBufWriteIt(HANDLE hFile, char const *pchBuf, KU32 cchToWrite) { if (cchToWrite > 0) { DWORD cchWritten = 0; if (WriteFile(hFile, pchBuf, cchToWrite, &cchWritten, NULL)) { if (cchWritten == cchToWrite) { /* likely */ } else { do { pchBuf += cchWritten; cchToWrite -= cchWritten; cchWritten = 0; } while ( cchToWrite > 0 && WriteFile(hFile, pchBuf, cchToWrite, &cchWritten, NULL)); } } else kHlpAssertFailed(); } } /** * Worker for WriteFile when the output isn't going to the console. * * @param pSandbox The sandbox. * @param pOutBuf The output buffer. * @param pchBuffer What to write. * @param cchToWrite How much to write. */ static void kwSandboxOutBufWrite(PKWSANDBOX pSandbox, PKWOUTPUTSTREAMBUF pOutBuf, const char *pchBuffer, KU32 cchToWrite) { if (pOutBuf->u.Fully.cchBufAlloc > 0) { /* likely */ } else { /* No realloc, max size is 64KB. */ pOutBuf->u.Fully.cchBufAlloc = 0x10000; pOutBuf->u.Fully.pchBuf = (char *)kHlpAlloc(pOutBuf->u.Fully.cchBufAlloc); if (!pOutBuf->u.Fully.pchBuf) { while ( !pOutBuf->u.Fully.pchBuf && pOutBuf->u.Fully.cchBufAlloc > 64) { pOutBuf->u.Fully.cchBufAlloc /= 2; pOutBuf->u.Fully.pchBuf = (char *)kHlpAlloc(pOutBuf->u.Fully.cchBufAlloc); } if (!pOutBuf->u.Fully.pchBuf) { pOutBuf->u.Fully.cchBufAlloc = sizeof(pOutBuf->abPadding); pOutBuf->u.Fully.pchBuf = pOutBuf->abPadding; } } } /* * Special case: Output ends with newline and fits in the buffer. */ if ( cchToWrite > 1 && pchBuffer[cchToWrite - 1] == '\n' && cchToWrite <= pOutBuf->u.Fully.cchBufAlloc - pOutBuf->u.Fully.cchBuf) { kHlpMemCopy(&pOutBuf->u.Fully.pchBuf[pOutBuf->u.Fully.cchBuf], pchBuffer, cchToWrite); pOutBuf->u.Fully.cchBuf += cchToWrite; } else { /* * Work thru the text line by line, flushing the buffer when * appropriate. The buffer is not a line buffer here, it's a * full buffer. */ while (cchToWrite > 0) { char const *pchNewLine = (const char *)memchr(pchBuffer, '\n', cchToWrite); KU32 cchLine = pchNewLine ? (KU32)(pchNewLine - pchBuffer) + 1 : cchToWrite; if (cchLine <= pOutBuf->u.Fully.cchBufAlloc - pOutBuf->u.Fully.cchBuf) { kHlpMemCopy(&pOutBuf->u.Fully.pchBuf[pOutBuf->u.Fully.cchBuf], pchBuffer, cchLine); pOutBuf->u.Fully.cchBuf += cchLine; } /* * Option one: Flush the buffer and the current line. * * We choose this one when the line won't ever fit, or when we have * an incomplete line in the buffer. */ else if ( cchLine >= pOutBuf->u.Fully.cchBufAlloc || pOutBuf->u.Fully.cchBuf == 0 || pOutBuf->u.Fully.pchBuf[pOutBuf->u.Fully.cchBuf - 1] != '\n') { KWOUT_LOG(("kwSandboxOutBufWrite: flushing %u bytes, writing %u bytes\n", pOutBuf->u.Fully.cchBuf, cchLine)); if (pOutBuf->u.Fully.cchBuf > 0) { kwSandboxOutBufWriteIt(pOutBuf->hBackup, pOutBuf->u.Fully.pchBuf, pOutBuf->u.Fully.cchBuf); pOutBuf->u.Fully.cchBuf = 0; } kwSandboxOutBufWriteIt(pOutBuf->hBackup, pchBuffer, cchLine); } /* * Option two: Only flush the lines in the buffer. */ else { KWOUT_LOG(("kwSandboxOutBufWrite: flushing %u bytes\n", pOutBuf->u.Fully.cchBuf)); kwSandboxOutBufWriteIt(pOutBuf->hBackup, pOutBuf->u.Fully.pchBuf, pOutBuf->u.Fully.cchBuf); kHlpMemCopy(&pOutBuf->u.Fully.pchBuf[0], pchBuffer, cchLine); pOutBuf->u.Fully.cchBuf = cchLine; } /* advance */ pchBuffer += cchLine; cchToWrite -= cchLine; } } } #endif /* WITH_STD_OUT_ERR_BUFFERING */ #ifdef WITH_TEMP_MEMORY_FILES static KBOOL kwFsTempFileEnsureSpace(PKWFSTEMPFILE pTempFile, KU32 offFile, KU32 cbNeeded) { KU32 cbMinFile = offFile + cbNeeded; if (cbMinFile >= offFile) { /* Calc how much space we've already allocated and */ if (cbMinFile <= pTempFile->cbFileAllocated) return K_TRUE; /* Grow the file. */ if (cbMinFile <= KWFS_TEMP_FILE_MAX) { int rc; KU32 cSegs = pTempFile->cSegs; KU32 cbNewSeg = cbMinFile > 4*1024*1024 ? 256*1024 : 4*1024*1024; do { /* grow the segment array? */ if ((cSegs % 16) == 0) { void *pvNew = kHlpRealloc(pTempFile->paSegs, (cSegs + 16) * sizeof(pTempFile->paSegs[0])); if (!pvNew) return K_FALSE; pTempFile->paSegs = (PKWFSTEMPFILESEG)pvNew; } /* Use page alloc here to simplify mapping later. */ rc = kHlpPageAlloc((void **)&pTempFile->paSegs[cSegs].pbData, cbNewSeg, KPROT_READWRITE, K_FALSE); if (rc == 0) { /* likely */ } else { cbNewSeg = 64*1024; rc = kHlpPageAlloc((void **)&pTempFile->paSegs[cSegs].pbData, cbNewSeg, KPROT_READWRITE, K_FALSE); if (rc != 0) { kHlpAssertFailed(); return K_FALSE; } } pTempFile->paSegs[cSegs].offData = pTempFile->cbFileAllocated; pTempFile->paSegs[cSegs].cbDataAlloc = cbNewSeg; pTempFile->cbFileAllocated += cbNewSeg; pTempFile->cSegs = ++cSegs; } while (pTempFile->cbFileAllocated < cbMinFile); return K_TRUE; } } kHlpAssertMsgFailed(("Out of bounds offFile=%#x + cbNeeded=%#x = %#x\n", offFile, cbNeeded, offFile + cbNeeded)); return K_FALSE; } #endif /* WITH_TEMP_MEMORY_FILES */ #if defined(WITH_TEMP_MEMORY_FILES) || defined(WITH_STD_OUT_ERR_BUFFERING) /** Kernel32 - WriteFile */ static BOOL WINAPI kwSandbox_Kernel32_WriteFile(HANDLE hFile, LPCVOID pvBuffer, DWORD cbToWrite, LPDWORD pcbActuallyWritten, LPOVERLAPPED pOverlapped) { BOOL fRet; KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hFile); g_cWriteFileCalls++; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread || g_fCtrlC); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle != NULL) { switch (pHandle->enmType) { # ifdef WITH_TEMP_MEMORY_FILES case KWHANDLETYPE_TEMP_FILE: { PKWFSTEMPFILE pTempFile = pHandle->u.pTempFile; kHlpAssert(!pOverlapped); kHlpAssert(pcbActuallyWritten); if (kwFsTempFileEnsureSpace(pTempFile, pHandle->offFile, cbToWrite)) { KU32 cbLeft; KU32 offSeg; /* Locate the segment containing the byte at offFile. */ KWFSTEMPFILESEG const *paSegs = pTempFile->paSegs; KU32 iSeg = pTempFile->cSegs - 1; kHlpAssert(pTempFile->cSegs > 0); while (paSegs[iSeg].offData > pHandle->offFile) iSeg--; /* Copy in the data. */ cbLeft = cbToWrite; offSeg = (pHandle->offFile - paSegs[iSeg].offData); for (;;) { KU32 cbAvail = paSegs[iSeg].cbDataAlloc - offSeg; if (cbAvail >= cbLeft) { kHlpMemCopy(&paSegs[iSeg].pbData[offSeg], pvBuffer, cbLeft); break; } kHlpMemCopy(&paSegs[iSeg].pbData[offSeg], pvBuffer, cbAvail); pvBuffer = (KU8 const *)pvBuffer + cbAvail; cbLeft -= cbAvail; offSeg = 0; iSeg++; kHlpAssert(iSeg < pTempFile->cSegs); } /* Update the file offset. */ pHandle->offFile += cbToWrite; if (pHandle->offFile > pTempFile->cbFile) pTempFile->cbFile = pHandle->offFile; *pcbActuallyWritten = cbToWrite; g_cbWriteFileTotal += cbToWrite; g_cbWriteFileToInMemTemp += cbToWrite; g_cWriteFileToInMemTemp++; KWFS_LOG(("WriteFile(%p,,%#x) -> TRUE [temp]\n", hFile, cbToWrite)); return TRUE; } kHlpAssertFailed(); *pcbActuallyWritten = 0; SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } # endif case KWHANDLETYPE_FSOBJ_READ_CACHE: kHlpAssertFailed(); SetLastError(ERROR_ACCESS_DENIED); *pcbActuallyWritten = 0; return FALSE; # if defined(WITH_STD_OUT_ERR_BUFFERING) || defined(WITH_CONSOLE_OUTPUT_BUFFERING) /* * Standard output & error. */ case KWHANDLETYPE_OUTPUT_BUF: { PKWOUTPUTSTREAMBUF pOutBuf = pHandle->u.pOutBuf; if (pOutBuf->fIsConsole) { kwSandboxConsoleWriteA(&g_Sandbox, pOutBuf, (const char *)pvBuffer, cbToWrite); KWOUT_LOG(("WriteFile(%p [console]) -> TRUE\n", hFile)); } else { # ifdef WITH_STD_OUT_ERR_BUFFERING kwSandboxOutBufWrite(&g_Sandbox, pOutBuf, (const char *)pvBuffer, cbToWrite); KWOUT_LOG(("WriteFile(%p [std%s], 's*.*', %#x) -> TRUE\n", hFile, pOutBuf == &g_Sandbox.StdErr ? "err" : "out", cbToWrite, cbToWrite, pvBuffer, cbToWrite)); # else kHlpAssertFailed(); # endif } if (pcbActuallyWritten) *pcbActuallyWritten = cbToWrite; g_cbWriteFileTotal += cbToWrite; return TRUE; } # endif default: case KWHANDLETYPE_TEMP_FILE_MAPPING: kHlpAssertFailed(); SetLastError(ERROR_INVALID_FUNCTION); *pcbActuallyWritten = 0; return FALSE; } } } fRet = WriteFile(hFile, pvBuffer, cbToWrite, pcbActuallyWritten, pOverlapped); if (fRet && pcbActuallyWritten) g_cbWriteFileTotal += *pcbActuallyWritten; KWFS_LOG(("WriteFile(%p,,%#x) -> %d, %#x\n", hFile, cbToWrite, fRet, pcbActuallyWritten ? *pcbActuallyWritten : 0)); return fRet; } /** Kernel32 - WriteFileEx */ static BOOL WINAPI kwSandbox_Kernel32_WriteFileEx(HANDLE hFile, LPCVOID pvBuffer, DWORD cbToWrite, LPOVERLAPPED pOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE pfnCompletionRoutine) { KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hFile); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle != NULL) { kHlpAssertFailed(); } } KWFS_LOG(("WriteFileEx(%p)\n", hFile)); return WriteFileEx(hFile, pvBuffer, cbToWrite, pOverlapped, pfnCompletionRoutine); } #endif /* WITH_TEMP_MEMORY_FILES || WITH_STD_OUT_ERR_BUFFERING */ #ifdef WITH_TEMP_MEMORY_FILES /** Kernel32 - SetEndOfFile; */ static BOOL WINAPI kwSandbox_Kernel32_SetEndOfFile(HANDLE hFile) { KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hFile); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle != NULL) { switch (pHandle->enmType) { case KWHANDLETYPE_TEMP_FILE: { PKWFSTEMPFILE pTempFile = pHandle->u.pTempFile; if ( pHandle->offFile > pTempFile->cbFile && !kwFsTempFileEnsureSpace(pTempFile, pHandle->offFile, 0)) { kHlpAssertFailed(); SetLastError(ERROR_NOT_ENOUGH_MEMORY); return FALSE; } pTempFile->cbFile = pHandle->offFile; KWFS_LOG(("SetEndOfFile(%p) -> TRUE (cbFile=%#x)\n", hFile, pTempFile->cbFile)); return TRUE; } case KWHANDLETYPE_FSOBJ_READ_CACHE: kHlpAssertFailed(); SetLastError(ERROR_ACCESS_DENIED); return FALSE; case KWHANDLETYPE_OUTPUT_BUF: kHlpAssertFailed(); SetLastError(pHandle->u.pOutBuf->fIsConsole ? ERROR_INVALID_OPERATION : ERROR_ACCESS_DENIED); return FALSE; default: case KWHANDLETYPE_TEMP_FILE_MAPPING: kHlpAssertFailed(); SetLastError(ERROR_INVALID_FUNCTION); return FALSE; } } } KWFS_LOG(("SetEndOfFile(%p)\n", hFile)); return SetEndOfFile(hFile); } /** Kernel32 - GetFileType */ static BOOL WINAPI kwSandbox_Kernel32_GetFileType(HANDLE hFile) { KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hFile); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle != NULL) { switch (pHandle->enmType) { case KWHANDLETYPE_FSOBJ_READ_CACHE: KWFS_LOG(("GetFileType(%p) -> FILE_TYPE_DISK [cached]\n", hFile)); return FILE_TYPE_DISK; case KWHANDLETYPE_TEMP_FILE: KWFS_LOG(("GetFileType(%p) -> FILE_TYPE_DISK [temp]\n", hFile)); return FILE_TYPE_DISK; case KWHANDLETYPE_OUTPUT_BUF: { PKWOUTPUTSTREAMBUF pOutBuf = pHandle->u.pOutBuf; DWORD fRet; if (pOutBuf->fFileType != KU8_MAX) { fRet = (pOutBuf->fFileType & 0xf) | ((pOutBuf->fFileType & (FILE_TYPE_REMOTE >> 8)) << 8); KWFS_LOG(("GetFileType(%p) -> %#x [outbuf]\n", hFile, fRet)); } else { fRet = GetFileType(hFile); KWFS_LOG(("GetFileType(%p) -> %#x [outbuf, fallback]\n", hFile, fRet)); } return fRet; } } } } KWFS_LOG(("GetFileType(%p)\n", hFile)); return GetFileType(hFile); } /** Kernel32 - GetFileSize */ static DWORD WINAPI kwSandbox_Kernel32_GetFileSize(HANDLE hFile, LPDWORD pcbHighDword) { KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hFile); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle != NULL) { if (pcbHighDword) *pcbHighDword = 0; SetLastError(NO_ERROR); switch (pHandle->enmType) { case KWHANDLETYPE_FSOBJ_READ_CACHE: KWFS_LOG(("GetFileSize(%p) -> %#x [cached]\n", hFile, pHandle->u.pCachedFile->cbCached)); return pHandle->u.pCachedFile->cbCached; case KWHANDLETYPE_TEMP_FILE: KWFS_LOG(("GetFileSize(%p) -> %#x [temp]\n", hFile, pHandle->u.pTempFile->cbFile)); return pHandle->u.pTempFile->cbFile; case KWHANDLETYPE_OUTPUT_BUF: /* do default */ break; default: kHlpAssertFailed(); SetLastError(ERROR_INVALID_FUNCTION); return INVALID_FILE_SIZE; } } } KWFS_LOG(("GetFileSize(%p,)\n", hFile)); return GetFileSize(hFile, pcbHighDword); } /** Kernel32 - GetFileSizeEx */ static BOOL WINAPI kwSandbox_Kernel32_GetFileSizeEx(HANDLE hFile, PLARGE_INTEGER pcbFile) { KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hFile); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle != NULL) { switch (pHandle->enmType) { case KWHANDLETYPE_FSOBJ_READ_CACHE: KWFS_LOG(("GetFileSizeEx(%p) -> TRUE, %#x [cached]\n", hFile, pHandle->u.pCachedFile->cbCached)); pcbFile->QuadPart = pHandle->u.pCachedFile->cbCached; return TRUE; case KWHANDLETYPE_TEMP_FILE: KWFS_LOG(("GetFileSizeEx(%p) -> TRUE, %#x [temp]\n", hFile, pHandle->u.pTempFile->cbFile)); pcbFile->QuadPart = pHandle->u.pTempFile->cbFile; return TRUE; case KWHANDLETYPE_OUTPUT_BUF: /* do default */ break; default: kHlpAssertFailed(); SetLastError(ERROR_INVALID_FUNCTION); return INVALID_FILE_SIZE; } } } KWFS_LOG(("GetFileSizeEx(%p,)\n", hFile)); return GetFileSizeEx(hFile, pcbFile); } /** Kernel32 - CreateFileMappingW */ static HANDLE WINAPI kwSandbox_Kernel32_CreateFileMappingW(HANDLE hFile, LPSECURITY_ATTRIBUTES pSecAttrs, DWORD fProtect, DWORD dwMaximumSizeHigh, DWORD dwMaximumSizeLow, LPCWSTR pwszName) { HANDLE hMapping; KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hFile); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle != NULL) { switch (pHandle->enmType) { case KWHANDLETYPE_TEMP_FILE: { PKWFSTEMPFILE pTempFile = pHandle->u.pTempFile; if ( ( fProtect == PAGE_READONLY || fProtect == PAGE_EXECUTE_READ) && dwMaximumSizeHigh == 0 && ( dwMaximumSizeLow == 0 || dwMaximumSizeLow == pTempFile->cbFile) && pwszName == NULL) { hMapping = kwFsTempFileCreateHandle(pHandle->u.pTempFile, GENERIC_READ, K_TRUE /*fMapping*/); KWFS_LOG(("CreateFileMappingW(%p, %u) -> %p [temp]\n", hFile, fProtect, hMapping)); return hMapping; } kHlpAssertMsgFailed(("fProtect=%#x cb=%#x'%08x name=%p\n", fProtect, dwMaximumSizeHigh, dwMaximumSizeLow, pwszName)); SetLastError(ERROR_ACCESS_DENIED); return INVALID_HANDLE_VALUE; } /* moc.exe benefits from this. */ case KWHANDLETYPE_FSOBJ_READ_CACHE: { PKFSWCACHEDFILE pCachedFile = pHandle->u.pCachedFile; if ( ( fProtect == PAGE_READONLY || fProtect == PAGE_EXECUTE_READ) && dwMaximumSizeHigh == 0 && ( dwMaximumSizeLow == 0 || dwMaximumSizeLow == pCachedFile->cbCached) && pwszName == NULL) { if (kwFsObjCacheCreateFileHandle(pCachedFile, GENERIC_READ, FALSE /*fInheritHandle*/, K_FALSE /*fIsFileHandle*/, &hMapping)) { /* likely */ } else hMapping = NULL; KWFS_LOG(("CreateFileMappingW(%p, %u) -> %p [cached]\n", hFile, fProtect, hMapping)); return hMapping; } /* Do fallback (for .pch). */ kHlpAssertMsg(fProtect == PAGE_WRITECOPY, ("fProtect=%#x cb=%#x'%08x name=%p\n", fProtect, dwMaximumSizeHigh, dwMaximumSizeLow, pwszName)); hMapping = CreateFileMappingW(hFile, pSecAttrs, fProtect, dwMaximumSizeHigh, dwMaximumSizeLow, pwszName); KWFS_LOG(("CreateFileMappingW(%p, %p, %#x, %#x, %#x, %p) -> %p [cached-fallback]\n", hFile, pSecAttrs, fProtect, dwMaximumSizeHigh, dwMaximumSizeLow, pwszName, hMapping)); return hMapping; } /** @todo read cached memory mapped files too for moc. */ } } } hMapping = CreateFileMappingW(hFile, pSecAttrs, fProtect, dwMaximumSizeHigh, dwMaximumSizeLow, pwszName); KWFS_LOG(("CreateFileMappingW(%p, %p, %#x, %#x, %#x, %p) -> %p\n", hFile, pSecAttrs, fProtect, dwMaximumSizeHigh, dwMaximumSizeLow, pwszName, hMapping)); return hMapping; } /** Kernel32 - MapViewOfFile */ static PVOID WINAPI kwSandbox_Kernel32_MapViewOfFile(HANDLE hSection, DWORD dwDesiredAccess, DWORD offFileHigh, DWORD offFileLow, SIZE_T cbToMap) { PVOID pvRet; KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hSection); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle != NULL) { KU32 idxMapping; /* * Ensure one free entry in the mapping tracking table first, * since this is common to both temporary and cached files. */ if (g_Sandbox.cMemMappings + 1 <= g_Sandbox.cMemMappingsAlloc) { /* likely */ } else { void *pvNew; KU32 cNew = g_Sandbox.cMemMappingsAlloc; if (cNew) cNew *= 2; else cNew = 32; pvNew = kHlpRealloc(g_Sandbox.paMemMappings, cNew * sizeof(g_Sandbox.paMemMappings)); if (pvNew) g_Sandbox.paMemMappings = (PKWMEMMAPPING)pvNew; else { kwErrPrintf("Failed to grow paMemMappings from %#x to %#x!\n", g_Sandbox.cMemMappingsAlloc, cNew); SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } g_Sandbox.cMemMappingsAlloc = cNew; } /* * Type specific work. */ switch (pHandle->enmType) { case KWHANDLETYPE_FSOBJ_READ_CACHE: case KWHANDLETYPE_TEMP_FILE: case KWHANDLETYPE_OUTPUT_BUF: default: kHlpAssertFailed(); SetLastError(ERROR_INVALID_OPERATION); return NULL; case KWHANDLETYPE_TEMP_FILE_MAPPING: { PKWFSTEMPFILE pTempFile = pHandle->u.pTempFile; if ( dwDesiredAccess == FILE_MAP_READ && offFileHigh == 0 && offFileLow == 0 && (cbToMap == 0 || cbToMap == pTempFile->cbFile) ) { kHlpAssert(pTempFile->cMappings == 0 || pTempFile->cSegs == 1); if (pTempFile->cSegs != 1) { KU32 iSeg; KU32 cbLeft; KU32 cbAll = pTempFile->cbFile ? (KU32)K_ALIGN_Z(pTempFile->cbFile, 0x2000) : 0x1000; KU8 *pbAll = NULL; int rc = kHlpPageAlloc((void **)&pbAll, cbAll, KPROT_READWRITE, K_FALSE); if (rc != 0) { kHlpAssertFailed(); SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } cbLeft = pTempFile->cbFile; for (iSeg = 0; iSeg < pTempFile->cSegs && cbLeft > 0; iSeg++) { KU32 cbToCopy = K_MIN(cbLeft, pTempFile->paSegs[iSeg].cbDataAlloc); kHlpMemCopy(&pbAll[pTempFile->paSegs[iSeg].offData], pTempFile->paSegs[iSeg].pbData, cbToCopy); cbLeft -= cbToCopy; } for (iSeg = 0; iSeg < pTempFile->cSegs; iSeg++) { kHlpPageFree(pTempFile->paSegs[iSeg].pbData, pTempFile->paSegs[iSeg].cbDataAlloc); pTempFile->paSegs[iSeg].pbData = NULL; pTempFile->paSegs[iSeg].cbDataAlloc = 0; } pTempFile->cSegs = 1; pTempFile->cbFileAllocated = cbAll; pTempFile->paSegs[0].cbDataAlloc = cbAll; pTempFile->paSegs[0].pbData = pbAll; pTempFile->paSegs[0].offData = 0; } pTempFile->cMappings++; kHlpAssert(pTempFile->cMappings == 1); pvRet = pTempFile->paSegs[0].pbData; KWFS_LOG(("CreateFileMappingW(%p) -> %p [temp]\n", hSection, pvRet)); break; } kHlpAssertMsgFailed(("dwDesiredAccess=%#x offFile=%#x'%08x cbToMap=%#x (cbFile=%#x)\n", dwDesiredAccess, offFileHigh, offFileLow, cbToMap, pTempFile->cbFile)); SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } /* * This is simple in comparison to the above temporary file code. */ case KWHANDLETYPE_FSOBJ_READ_CACHE_MAPPING: { PKFSWCACHEDFILE pCachedFile = pHandle->u.pCachedFile; if ( dwDesiredAccess == FILE_MAP_READ && offFileHigh == 0 && offFileLow == 0 && (cbToMap == 0 || cbToMap == pCachedFile->cbCached) ) { pvRet = pCachedFile->pbCached; KWFS_LOG(("CreateFileMappingW(%p) -> %p [cached]\n", hSection, pvRet)); break; } kHlpAssertMsgFailed(("dwDesiredAccess=%#x offFile=%#x'%08x cbToMap=%#x (cbFile=%#x)\n", dwDesiredAccess, offFileHigh, offFileLow, cbToMap, pCachedFile->cbCached)); SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; } } /* * Insert into the mapping tracking table. This is common * and we should only get here with a non-NULL pvRet. * * Note! We could look for duplicates and do ref counting, but it's * easier to just append for now. */ kHlpAssert(pvRet != NULL); idxMapping = g_Sandbox.cMemMappings; kHlpAssert(idxMapping < g_Sandbox.cMemMappingsAlloc); g_Sandbox.paMemMappings[idxMapping].cRefs = 1; g_Sandbox.paMemMappings[idxMapping].pvMapping = pvRet; g_Sandbox.paMemMappings[idxMapping].enmType = pHandle->enmType; g_Sandbox.paMemMappings[idxMapping].u.pCachedFile = pHandle->u.pCachedFile; g_Sandbox.cMemMappings++; return pvRet; } } pvRet = MapViewOfFile(hSection, dwDesiredAccess, offFileHigh, offFileLow, cbToMap); KWFS_LOG(("MapViewOfFile(%p, %#x, %#x, %#x, %#x) -> %p\n", hSection, dwDesiredAccess, offFileHigh, offFileLow, cbToMap, pvRet)); return pvRet; } /** Kernel32 - MapViewOfFileEx */ static PVOID WINAPI kwSandbox_Kernel32_MapViewOfFileEx(HANDLE hSection, DWORD dwDesiredAccess, DWORD offFileHigh, DWORD offFileLow, SIZE_T cbToMap, PVOID pvMapAddr) { PVOID pvRet; KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hSection); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle != NULL) { switch (pHandle->enmType) { case KWHANDLETYPE_TEMP_FILE_MAPPING: KWFS_LOG(("MapViewOfFileEx(%p, %#x, %#x, %#x, %#x, %p) - temporary file!\n", hSection, dwDesiredAccess, offFileHigh, offFileLow, cbToMap, pvMapAddr)); if (!pvMapAddr) return kwSandbox_Kernel32_MapViewOfFile(hSection, dwDesiredAccess, offFileHigh, offFileLow, cbToMap); kHlpAssertFailed(); SetLastError(ERROR_NOT_ENOUGH_MEMORY); return NULL; case KWHANDLETYPE_FSOBJ_READ_CACHE_MAPPING: KWFS_LOG(("MapViewOfFileEx(%p, %#x, %#x, %#x, %#x, %p) - read cached file!\n", hSection, dwDesiredAccess, offFileHigh, offFileLow, cbToMap, pvMapAddr)); if (!pvMapAddr) return kwSandbox_Kernel32_MapViewOfFile(hSection, dwDesiredAccess, offFileHigh, offFileLow, cbToMap); /* We can use fallback here as the handle is an actual section handle. */ break; case KWHANDLETYPE_FSOBJ_READ_CACHE: case KWHANDLETYPE_TEMP_FILE: case KWHANDLETYPE_OUTPUT_BUF: default: kHlpAssertFailed(); SetLastError(ERROR_INVALID_OPERATION); return NULL; } } } pvRet = MapViewOfFileEx(hSection, dwDesiredAccess, offFileHigh, offFileLow, cbToMap, pvMapAddr); KWFS_LOG(("MapViewOfFileEx(%p, %#x, %#x, %#x, %#x, %p) -> %p\n", hSection, dwDesiredAccess, offFileHigh, offFileLow, cbToMap, pvMapAddr, pvRet)); return pvRet; } /** Kernel32 - UnmapViewOfFile */ static BOOL WINAPI kwSandbox_Kernel32_UnmapViewOfFile(LPCVOID pvBase) { /* * Consult the memory mapping tracker. */ PKWMEMMAPPING paMemMappings = g_Sandbox.paMemMappings; KU32 idxMapping = g_Sandbox.cMemMappings; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); while (idxMapping-- > 0) if (paMemMappings[idxMapping].pvMapping == pvBase) { /* Type specific stuff. */ if (paMemMappings[idxMapping].enmType == KWHANDLETYPE_TEMP_FILE_MAPPING) { KWFS_LOG(("UnmapViewOfFile(%p) -> TRUE [temp]\n", pvBase)); paMemMappings[idxMapping].u.pTempFile->cMappings--; } else KWFS_LOG(("UnmapViewOfFile(%p) -> TRUE [cached]\n", pvBase)); /* Deref and probably free it. */ if (--paMemMappings[idxMapping].cRefs == 0) { g_Sandbox.cMemMappings--; if (idxMapping != g_Sandbox.cMemMappings) paMemMappings[idxMapping] = paMemMappings[g_Sandbox.cMemMappings]; } return TRUE; } KWFS_LOG(("UnmapViewOfFile(%p)\n", pvBase)); return UnmapViewOfFile(pvBase); } /** @todo UnmapViewOfFileEx */ #endif /* WITH_TEMP_MEMORY_FILES */ /** Kernel32 - DuplicateHandle */ static BOOL WINAPI kwSandbox_Kernel32_DuplicateHandle(HANDLE hSrcProc, HANDLE hSrc, HANDLE hDstProc, PHANDLE phNew, DWORD dwDesiredAccess, BOOL fInheritHandle, DWORD dwOptions) { BOOL fRet; /* * We must catch our handles being duplicated. */ if (hSrcProc == GetCurrentProcess()) { KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hSrc); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle) { fRet = DuplicateHandle(hSrcProc, hSrc, hDstProc, phNew, dwDesiredAccess, fInheritHandle, dwOptions); if (fRet) { if (kwSandboxHandleTableEnter(&g_Sandbox, pHandle, *phNew)) { pHandle->cRefs++; KWFS_LOG(("DuplicateHandle(%p, %p, %p, , %#x, %d, %#x) -> TRUE, %p [intercepted handle] enmType=%d cRef=%d\n", hSrcProc, hSrc, hDstProc, dwDesiredAccess, fInheritHandle, dwOptions, *phNew, pHandle->enmType, pHandle->cRefs)); } else { fRet = FALSE; SetLastError(ERROR_NOT_ENOUGH_MEMORY); KWFS_LOG(("DuplicateHandle(%p, %p, %p, , %#x, %d, %#x) -> !FALSE!, %p [intercepted handle] enmType=%d\n", hSrcProc, hSrc, hDstProc, dwDesiredAccess, fInheritHandle, dwOptions, *phNew, pHandle->enmType)); } } else KWFS_LOG(("DuplicateHandle(%p, %p, %p, , %#x, %d, %#x) -> FALSE [intercepted handle] enmType=%d\n", hSrcProc, hSrc, hDstProc, dwDesiredAccess, fInheritHandle, dwOptions, pHandle->enmType)); return fRet; } } } /* * Not one of ours, just do what the caller asks and log it. */ fRet = DuplicateHandle(hSrcProc, hSrc, hDstProc, phNew, dwDesiredAccess, fInheritHandle, dwOptions); KWFS_LOG(("DuplicateHandle(%p, %p, %p, , %#x, %d, %#x) -> %d, %p\n", hSrcProc, hSrc, hDstProc, dwDesiredAccess, fInheritHandle, dwOptions, fRet, *phNew)); return fRet; } /** Kernel32 - CloseHandle */ static BOOL WINAPI kwSandbox_Kernel32_CloseHandle(HANDLE hObject) { BOOL fRet; KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hObject); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread || g_fCtrlC); if (idxHandle < g_Sandbox.cHandles) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; if (pHandle) { /* Prevent the closing of the standard output and error handles. */ if ( pHandle->enmType != KWHANDLETYPE_OUTPUT_BUF || idxHandle != KW_HANDLE_TO_INDEX(pHandle->hHandle)) { fRet = CloseHandle(hObject); if (fRet) { PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; g_Sandbox.papHandles[idxHandle] = NULL; g_Sandbox.cActiveHandles--; kHlpAssert(g_Sandbox.cActiveHandles >= g_Sandbox.cFixedHandles); if (--pHandle->cRefs == 0) { #ifdef WITH_TEMP_MEMORY_FILES if (pHandle->enmType == KWHANDLETYPE_TEMP_FILE) { kHlpAssert(pHandle->u.pTempFile->cActiveHandles > 0); pHandle->u.pTempFile->cActiveHandles--; } #endif kHlpFree(pHandle); KWFS_LOG(("CloseHandle(%p) -> TRUE [intercepted handle, freed]\n", hObject)); } else KWFS_LOG(("CloseHandle(%p) -> TRUE [intercepted handle, not freed]\n", hObject)); } else KWFS_LOG(("CloseHandle(%p) -> FALSE [intercepted handle] err=%u!\n", hObject, GetLastError())); } else { KWFS_LOG(("CloseHandle(%p) -> TRUE [intercepted handle] Ignored closing of std%s!\n", hObject, hObject == g_Sandbox.StdErr.hOutput ? "err" : "out")); fRet = TRUE; } return fRet; } } fRet = CloseHandle(hObject); KWFS_LOG(("CloseHandle(%p) -> %d\n", hObject, fRet)); return fRet; } /** Kernel32 - GetFileAttributesA. */ static DWORD WINAPI kwSandbox_Kernel32_GetFileAttributesA(LPCSTR pszFilename) { DWORD fRet; const char *pszExt = kHlpGetExt(pszFilename); if (kwFsIsCacheableExtensionA(pszExt, K_TRUE /*fAttrQuery*/)) { KFSLOOKUPERROR enmError; PKFSOBJ pFsObj; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pFsObj = kFsCacheLookupNoMissingA(g_pFsCache, pszFilename, &enmError); if (pFsObj) { kHlpAssert(pFsObj->fHaveStats); fRet = pFsObj->Stats.st_attribs; kFsCacheObjRelease(g_pFsCache, pFsObj); } else { SetLastError(kwFsLookupErrorToWindowsError(enmError)); fRet = INVALID_FILE_ATTRIBUTES; } KWFS_LOG(("GetFileAttributesA(%s) -> %#x [cached]\n", pszFilename, fRet)); return fRet; } fRet = GetFileAttributesA(pszFilename); KWFS_LOG(("GetFileAttributesA(%s) -> %#x\n", pszFilename, fRet)); return fRet; } /** Kernel32 - GetFileAttributesW. */ static DWORD WINAPI kwSandbox_Kernel32_GetFileAttributesW(LPCWSTR pwszFilename) { DWORD fRet; if (kwFsIsCacheablePathExtensionW(pwszFilename, K_TRUE /*fAttrQuery*/)) { KFSLOOKUPERROR enmError; PKFSOBJ pFsObj; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pFsObj = kFsCacheLookupNoMissingW(g_pFsCache, pwszFilename, &enmError); if (pFsObj) { kHlpAssert(pFsObj->fHaveStats); fRet = pFsObj->Stats.st_attribs; kFsCacheObjRelease(g_pFsCache, pFsObj); } else { SetLastError(kwFsLookupErrorToWindowsError(enmError)); fRet = INVALID_FILE_ATTRIBUTES; } KWFS_LOG(("GetFileAttributesW(%ls) -> %#x [cached]\n", pwszFilename, fRet)); return fRet; } fRet = GetFileAttributesW(pwszFilename); KWFS_LOG(("GetFileAttributesW(%ls) -> %#x\n", pwszFilename, fRet)); return fRet; } /** Kernel32 - GetShortPathNameW - c1[xx].dll of VS2010 does this to the * directory containing each include file. We cache the result to speed * things up a little. */ static DWORD WINAPI kwSandbox_Kernel32_GetShortPathNameW(LPCWSTR pwszLongPath, LPWSTR pwszShortPath, DWORD cwcShortPath) { DWORD cwcRet; if (kwFsIsCacheablePathExtensionW(pwszLongPath, K_TRUE /*fAttrQuery*/)) { KFSLOOKUPERROR enmError; PKFSOBJ pObj; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pObj = kFsCacheLookupW(g_pFsCache, pwszLongPath, &enmError); if (pObj) { if (pObj->bObjType != KFSOBJ_TYPE_MISSING) { if (kFsCacheObjGetFullShortPathW(pObj, pwszShortPath, cwcShortPath, '\\')) { cwcRet = (DWORD)kwUtf16Len(pwszShortPath); /* Should preserve trailing slash on directory paths. */ if (pObj->bObjType == KFSOBJ_TYPE_DIR) { if ( cwcRet + 1 < cwcShortPath && pwszShortPath[cwcRet - 1] != '\\') { KSIZE cwcIn = kwUtf16Len(pwszLongPath); if ( cwcIn > 0 && (pwszLongPath[cwcIn - 1] == '\\' || pwszLongPath[cwcIn - 1] == '/') ) { pwszShortPath[cwcRet++] = '\\'; pwszShortPath[cwcRet] = '\0'; } } } KWFS_LOG(("GetShortPathNameW(%ls) -> '%*.*ls' & %#x [cached]\n", pwszLongPath, K_MIN(cwcShortPath, cwcRet), K_MIN(cwcShortPath, cwcRet), pwszShortPath, cwcRet)); kFsCacheObjRelease(g_pFsCache, pObj); return cwcRet; } /* fall back for complicated cases. */ } kFsCacheObjRelease(g_pFsCache, pObj); } } cwcRet = GetShortPathNameW(pwszLongPath, pwszShortPath, cwcShortPath); KWFS_LOG(("GetShortPathNameW(%ls) -> '%*.*ls' & %#x\n", pwszLongPath, K_MIN(cwcShortPath, cwcRet), K_MIN(cwcShortPath, cwcRet), pwszShortPath, cwcRet)); return cwcRet; } #ifdef WITH_TEMP_MEMORY_FILES /** Kernel32 - DeleteFileW * Skip deleting the in-memory files. */ static BOOL WINAPI kwSandbox_Kernel32_DeleteFileW(LPCWSTR pwszFilename) { BOOL fRc; if ( g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_CL && kwFsIsClTempFileW(pwszFilename)) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); KWFS_LOG(("DeleteFileW(%s) -> TRUE [temp]\n", pwszFilename)); fRc = TRUE; } else { fRc = DeleteFileW(pwszFilename); KWFS_LOG(("DeleteFileW(%s) -> %d (%d)\n", pwszFilename, fRc, GetLastError())); } return fRc; } #endif /* WITH_TEMP_MEMORY_FILES */ #ifdef WITH_CONSOLE_OUTPUT_BUFFERING /* * * Console output buffering. * Console output buffering. * Console output buffering. * */ /** * Write a wide char string to the console. * * @param pSandbox The sandbox which output buffer to flush. */ static void kwSandboxConsoleWriteIt(PKWSANDBOX pSandbox, wchar_t const *pwcBuf, KU32 cwcToWrite) { if (cwcToWrite > 0) { DWORD cwcWritten = 0; if (WriteConsoleW(pSandbox->Combined.hOutput, pwcBuf, cwcToWrite, &cwcWritten, NULL)) { if (cwcWritten == cwcToWrite) { /* likely */ } else { DWORD off = 0; do { off += cwcWritten; cwcWritten = 0; } while ( off < cwcToWrite && WriteConsoleW(pSandbox->Combined.hOutput, &pwcBuf[off], cwcToWrite - off, &cwcWritten, NULL)); kHlpAssert(off == cwcWritten); } } else kHlpAssertFailed(); pSandbox->Combined.cFlushes++; } } /** * Flushes the combined console output buffer. * * @param pSandbox The sandbox which output buffer to flush. */ static void kwSandboxConsoleFlushCombined(PKWSANDBOX pSandbox) { if (pSandbox->Combined.cwcBuf > 0) { KWOUT_LOG(("kwSandboxConsoleFlushCombined: %u wchars\n", pSandbox->Combined.cwcBuf)); kwSandboxConsoleWriteIt(pSandbox, pSandbox->Combined.wszBuf, pSandbox->Combined.cwcBuf); pSandbox->Combined.cwcBuf = 0; } } /** * For handling combined buffer overflow cases line by line. * * @param pSandbox The sandbox. * @param pwcBuf What to add to the combined buffer. Usually a * line, unless we're really low on buffer space. * @param cwcBuf The length of what to add. * @param fBrokenLine Whether this is a broken line. */ static void kwSandboxConsoleAddToCombined(PKWSANDBOX pSandbox, wchar_t const *pwcBuf, KU32 cwcBuf, KBOOL fBrokenLine) { if (fBrokenLine) kwSandboxConsoleFlushCombined(pSandbox); if (pSandbox->Combined.cwcBuf + cwcBuf > K_ELEMENTS(pSandbox->Combined.wszBuf)) { kwSandboxConsoleFlushCombined(pSandbox); kwSandboxConsoleWriteIt(pSandbox, pwcBuf, cwcBuf); } else { kHlpMemCopy(&pSandbox->Combined.wszBuf[pSandbox->Combined.cwcBuf], pwcBuf, cwcBuf * sizeof(wchar_t)); pSandbox->Combined.cwcBuf += cwcBuf; } } /** * Called to final flush a line buffer via the combined buffer (if applicable). * * @param pSandbox The sandbox. * @param pLineBuf The line buffer. * @param pszName The line buffer name (for logging) */ static void kwSandboxConsoleFinalFlushLineBuf(PKWSANDBOX pSandbox, PKWOUTPUTSTREAMBUF pLineBuf, const char *pszName) { if (pLineBuf->fIsConsole) { if (pLineBuf->u.Con.cwcBuf > 0) { KWOUT_LOG(("kwSandboxConsoleFinalFlushLineBuf: %s: %u wchars\n", pszName, pLineBuf->u.Con.cwcBuf)); if (pLineBuf->u.Con.cwcBuf < pLineBuf->u.Con.cwcBufAlloc) { pLineBuf->u.Con.pwcBuf[pLineBuf->u.Con.cwcBuf++] = '\n'; kwSandboxConsoleAddToCombined(pSandbox, pLineBuf->u.Con.pwcBuf, pLineBuf->u.Con.cwcBuf, K_FALSE /*fBrokenLine*/); } else { kwSandboxConsoleAddToCombined(pSandbox, pLineBuf->u.Con.pwcBuf, pLineBuf->u.Con.cwcBuf, K_TRUE /*fBrokenLine*/); kwSandboxConsoleAddToCombined(pSandbox, L"\n", 1, K_TRUE /*fBrokenLine*/); } pLineBuf->u.Con.cwcBuf = 0; } } #ifdef WITH_STD_OUT_ERR_BUFFERING else if (pLineBuf->u.Fully.cchBuf > 0) { KWOUT_LOG(("kwSandboxConsoleFinalFlushLineBuf: %s: %u bytes\n", pszName, pLineBuf->u.Fully.cchBuf)); kwSandboxOutBufWriteIt(pLineBuf->hBackup, pLineBuf->u.Fully.pchBuf, pLineBuf->u.Fully.cchBuf); pLineBuf->u.Fully.cchBuf = 0; } #endif } /** * Called at the end of sandboxed execution to flush both stream buffers. * * @param pSandbox The sandbox. */ static void kwSandboxConsoleFlushAll(PKWSANDBOX pSandbox) { /* * First do the cl.exe source file supression trick, if applicable. * The output ends up on CONOUT$ if either StdOut or StdErr is a console * handle. */ if ( pSandbox->pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_CL && pSandbox->Combined.cFlushes == 0) { if ( pSandbox->StdOut.fIsConsole || pSandbox->StdErr.fIsConsole) { if ( pSandbox->Combined.cwcBuf >= 3 && (pSandbox->StdOut.fIsConsole ? pSandbox->StdOut.u.Con.cwcBuf : pSandbox->StdOut.u.Fully.cchBuf) == 0 && (pSandbox->StdErr.fIsConsole ? pSandbox->StdErr.u.Con.cwcBuf : pSandbox->StdErr.u.Fully.cchBuf) == 0 ) { KI32 off = pSandbox->Combined.cwcBuf - 1; if (pSandbox->Combined.wszBuf[off] == '\n') { KBOOL fOk = K_TRUE; while (off-- > 0) { wchar_t const wc = pSandbox->Combined.wszBuf[off]; if (iswalnum(wc) || wc == '.' || wc == ' ' || wc == '_' || wc == '-') { /* likely */ } else { fOk = K_FALSE; break; } } if (fOk) { KWOUT_LOG(("kwSandboxConsoleFlushAll: Dropping '%*.*ls in combined console buffer\n", pSandbox->Combined.cwcBuf, pSandbox->Combined.cwcBuf, pSandbox->Combined.wszBuf)); pSandbox->Combined.cwcBuf = 0; return; } } KWOUT_LOG(("kwSandboxConsoleFlushAll: Unable to drop '%*.*ls in combined console buffer\n", pSandbox->Combined.cwcBuf, pSandbox->Combined.cwcBuf, pSandbox->Combined.wszBuf)); } } #ifdef WITH_STD_OUT_ERR_BUFFERING /* * Otherwise, it goes to standard output (redirected). */ else if ( pSandbox->StdErr.u.Fully.cchBuf == 0 && pSandbox->StdOut.u.Fully.cchBuf >= 3) { char const *pchBuf = pSandbox->StdOut.u.Fully.pchBuf; KI32 off = pSandbox->StdOut.u.Fully.cchBuf - 1; kHlpAssert(pSandbox->Combined.cFlushes == 0 && pSandbox->Combined.cwcBuf == 0); /* unused! */ if (pchBuf[off] == '\n') { KBOOL fOk = K_TRUE; if (pchBuf[off - 1] == '\r') off--; while (off-- > 0) { char const ch = pchBuf[off]; if (isalnum(ch) || ch == '.' || ch == ' ' || ch == '_' || ch == '-') { /* likely */ } else { fOk = K_FALSE; break; } } if (fOk) { KWOUT_LOG(("kwSandboxConsoleFlushAll: Dropping '%*.*s in stdout buffer\n", pSandbox->StdOut.u.Fully.cchBuf, pSandbox->StdOut.u.Fully.cchBuf, pchBuf)); pSandbox->StdOut.u.Fully.cchBuf = 0; return; } } KWOUT_LOG(("kwSandboxConsoleFlushAll: Unable to drop '%*.*s in stdout buffer\n", pSandbox->StdOut.u.Fully.cchBuf, pSandbox->StdOut.u.Fully.cchBuf, pchBuf)); } #endif } /* * Flush the two line buffer, the the combined buffer. */ kwSandboxConsoleFinalFlushLineBuf(pSandbox, &pSandbox->StdErr, "StdErr"); kwSandboxConsoleFinalFlushLineBuf(pSandbox, &pSandbox->StdOut, "StdOut"); kwSandboxConsoleFlushCombined(pSandbox); } /** * Writes a string to the given output stream. * * @param pSandbox The sandbox. * @param pLineBuf The line buffer for the output stream. * @param pwcBuffer The buffer to write. * @param cwcToWrite The number of wchar_t's in the buffer. */ static void kwSandboxConsoleWriteW(PKWSANDBOX pSandbox, PKWOUTPUTSTREAMBUF pLineBuf, wchar_t const *pwcBuffer, KU32 cwcToWrite) { kHlpAssert(pLineBuf->fIsConsole); if (cwcToWrite > 0) { /* * First, find the start of the last incomplete line so we can figure * out how much line buffering we need to do. */ KU32 cchLastIncompleteLine; KU32 offLastIncompleteLine = cwcToWrite; while ( offLastIncompleteLine > 0 && pwcBuffer[offLastIncompleteLine - 1] != '\n') offLastIncompleteLine--; cchLastIncompleteLine = cwcToWrite - offLastIncompleteLine; /* Was there anything to line buffer? */ if (offLastIncompleteLine < cwcToWrite) { /* Need to grow the line buffer? */ KU32 cwcNeeded = offLastIncompleteLine == 0 ? pLineBuf->u.Con.cwcBuf + cchLastIncompleteLine /* incomplete line, append to line buffer */ : cchLastIncompleteLine; /* Only the final incomplete line (if any) goes to the line buffer. */ if (cwcNeeded > pLineBuf->u.Con.cwcBufAlloc) { void *pvNew; KU32 cwcNew = !pLineBuf->u.Con.cwcBufAlloc ? 1024 : pLineBuf->u.Con.cwcBufAlloc * 2; while (cwcNew < cwcNeeded) cwcNew *= 2; pvNew = kHlpRealloc(pLineBuf->u.Con.pwcBuf, cwcNew * sizeof(wchar_t)); if (pvNew) { pLineBuf->u.Con.pwcBuf = (wchar_t *)pvNew; pLineBuf->u.Con.cwcBufAlloc = cwcNew; } else { pvNew = kHlpRealloc(pLineBuf->u.Con.pwcBuf, cwcNeeded * sizeof(wchar_t)); if (pvNew) { pLineBuf->u.Con.pwcBuf = (wchar_t *)pvNew; pLineBuf->u.Con.cwcBufAlloc = cwcNeeded; } else { /* This isn't perfect, but it will have to do for now. */ if (pLineBuf->u.Con.cwcBuf > 0) { kwSandboxConsoleAddToCombined(pSandbox, pLineBuf->u.Con.pwcBuf, pLineBuf->u.Con.cwcBuf, K_TRUE /*fBrokenLine*/); pLineBuf->u.Con.cwcBuf = 0; } kwSandboxConsoleAddToCombined(pSandbox, pwcBuffer, cwcToWrite, K_TRUE /*fBrokenLine*/); return; } } } /* * Handle the case where we only add to the line buffer. */ if (offLastIncompleteLine == 0) { kHlpMemCopy(&pLineBuf->u.Con.pwcBuf[pLineBuf->u.Con.cwcBuf], pwcBuffer, cwcToWrite * sizeof(wchar_t)); pLineBuf->u.Con.cwcBuf += cwcToWrite; return; } } /* * If there is sufficient combined buffer to handle this request, this is rather simple. */ kHlpAssert(pSandbox->Combined.cwcBuf <= K_ELEMENTS(pSandbox->Combined.wszBuf)); if (pSandbox->Combined.cwcBuf + pLineBuf->u.Con.cwcBuf + offLastIncompleteLine <= K_ELEMENTS(pSandbox->Combined.wszBuf)) { if (pLineBuf->u.Con.cwcBuf > 0) { kHlpMemCopy(&pSandbox->Combined.wszBuf[pSandbox->Combined.cwcBuf], pLineBuf->u.Con.pwcBuf, pLineBuf->u.Con.cwcBuf * sizeof(wchar_t)); pSandbox->Combined.cwcBuf += pLineBuf->u.Con.cwcBuf; pLineBuf->u.Con.cwcBuf = 0; } kHlpMemCopy(&pSandbox->Combined.wszBuf[pSandbox->Combined.cwcBuf], pwcBuffer, offLastIncompleteLine * sizeof(wchar_t)); pSandbox->Combined.cwcBuf += offLastIncompleteLine; } else { /* * Do line-by-line processing of the input, flusing the combined buffer * when it becomes necessary. We may have to write lines */ KU32 off = 0; KU32 offNextLine = 0; /* If there are buffered chars, we handle the first line outside the main loop. We must try our best outputting it as a complete line. */ if (pLineBuf->u.Con.cwcBuf > 0) { while (offNextLine < cwcToWrite && pwcBuffer[offNextLine] != '\n') offNextLine++; offNextLine++; kHlpAssert(offNextLine <= offLastIncompleteLine); if (pSandbox->Combined.cwcBuf + pLineBuf->u.Con.cwcBuf + offNextLine <= K_ELEMENTS(pSandbox->Combined.wszBuf)) { kHlpMemCopy(&pSandbox->Combined.wszBuf[pSandbox->Combined.cwcBuf], pLineBuf->u.Con.pwcBuf, pLineBuf->u.Con.cwcBuf * sizeof(wchar_t)); pSandbox->Combined.cwcBuf += pLineBuf->u.Con.cwcBuf; pLineBuf->u.Con.cwcBuf = 0; kHlpMemCopy(&pSandbox->Combined.wszBuf[pSandbox->Combined.cwcBuf], pwcBuffer, offNextLine * sizeof(wchar_t)); pSandbox->Combined.cwcBuf += offNextLine; } else { KU32 cwcLeft = pLineBuf->u.Con.cwcBufAlloc - pLineBuf->u.Con.cwcBuf; if (cwcLeft > 0) { KU32 cwcCopy = K_MIN(cwcLeft, offNextLine); kHlpMemCopy(&pLineBuf->u.Con.pwcBuf[pLineBuf->u.Con.cwcBuf], pwcBuffer, cwcCopy * sizeof(wchar_t)); pLineBuf->u.Con.cwcBuf += cwcCopy; off += cwcCopy; } if (pLineBuf->u.Con.cwcBuf > 0) { kwSandboxConsoleAddToCombined(pSandbox, pLineBuf->u.Con.pwcBuf, pLineBuf->u.Con.cwcBuf, K_TRUE /*fBrokenLine*/); pLineBuf->u.Con.cwcBuf = 0; } if (off < offNextLine) kwSandboxConsoleAddToCombined(pSandbox, &pwcBuffer[off], offNextLine - off, K_TRUE /*fBrokenLine*/); } off = offNextLine; } /* Deal with the remaining lines */ while (off < offLastIncompleteLine) { while (offNextLine < offLastIncompleteLine && pwcBuffer[offNextLine] != '\n') offNextLine++; offNextLine++; kHlpAssert(offNextLine <= offLastIncompleteLine); kwSandboxConsoleAddToCombined(pSandbox, &pwcBuffer[off], offNextLine - off, K_FALSE /*fBrokenLine*/); off = offNextLine; } } /* * Buffer any remaining incomplete line chars. */ if (cchLastIncompleteLine) { kHlpMemCopy(&pLineBuf->u.Con.pwcBuf[0], &pwcBuffer[offLastIncompleteLine], cchLastIncompleteLine * sizeof(wchar_t)); pLineBuf->u.Con.cwcBuf = cchLastIncompleteLine; } } } /** * Worker for WriteConsoleA and WriteFile. * * @param pSandbox The sandbox. * @param pLineBuf The line buffer. * @param pchBuffer What to write. * @param cchToWrite How much to write. */ static void kwSandboxConsoleWriteA(PKWSANDBOX pSandbox, PKWOUTPUTSTREAMBUF pLineBuf, const char *pchBuffer, KU32 cchToWrite) { /* * Convert it to wide char and use the 'W' to do the work. */ int cwcRet; KU32 cwcBuf = cchToWrite * 2 + 1; wchar_t *pwcBufFree = NULL; wchar_t *pwcBuf; kHlpAssert(pLineBuf->fIsConsole); if (cwcBuf <= 4096) pwcBuf = alloca(cwcBuf * sizeof(wchar_t)); else pwcBuf = pwcBufFree = kHlpAlloc(cwcBuf * sizeof(wchar_t)); cwcRet = MultiByteToWideChar(pSandbox->Combined.uCodepage, 0/*dwFlags*/, pchBuffer, cchToWrite, pwcBuf, cwcBuf); if (cwcRet > 0) kwSandboxConsoleWriteW(pSandbox, pLineBuf, pwcBuf, cwcRet); else { DWORD cchWritten; kHlpAssertFailed(); /* Flush the line buffer and combined buffer before calling WriteConsoleA. */ if (pLineBuf->u.Con.cwcBuf > 0) { kwSandboxConsoleAddToCombined(pSandbox, pLineBuf->u.Con.pwcBuf, pLineBuf->u.Con.cwcBuf, K_TRUE /*fBroken*/); pLineBuf->u.Con.cwcBuf = 0; } kwSandboxConsoleFlushCombined(pSandbox); if (WriteConsoleA(pLineBuf->hBackup, pchBuffer, cchToWrite, &cchWritten, NULL /*pvReserved*/)) { if (cchWritten >= cchToWrite) { /* likely */ } else { KU32 off = 0; do { off += cchWritten; cchWritten = 0; } while ( off < cchToWrite && WriteConsoleA(pLineBuf->hBackup, &pchBuffer[off], cchToWrite - off, &cchWritten, NULL)); } } } if (pwcBufFree) kHlpFree(pwcBufFree); } /** Kernel32 - WriteConsoleA */ BOOL WINAPI kwSandbox_Kernel32_WriteConsoleA(HANDLE hConOutput, CONST VOID *pvBuffer, DWORD cbToWrite, PDWORD pcbWritten, PVOID pvReserved) { BOOL fRc; PKWOUTPUTSTREAMBUF pLineBuf; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (hConOutput == g_Sandbox.StdErr.hOutput) pLineBuf = &g_Sandbox.StdErr; else pLineBuf = &g_Sandbox.StdOut; if (pLineBuf->fIsConsole) { kwSandboxConsoleWriteA(&g_Sandbox, pLineBuf, (char const *)pvBuffer, cbToWrite); KWOUT_LOG(("WriteConsoleA: %p, %p LB %#x (%*.*s), %p, %p -> TRUE [cached]\n", hConOutput, pvBuffer, cbToWrite, cbToWrite, cbToWrite, pvBuffer, pcbWritten, pvReserved)); if (pcbWritten) *pcbWritten = cbToWrite; fRc = TRUE; } else { fRc = WriteConsoleA(hConOutput, pvBuffer, cbToWrite, pcbWritten, pvReserved); KWOUT_LOG(("WriteConsoleA: %p, %p LB %#x (%*.*s), %p, %p -> %d !fallback!\n", hConOutput, pvBuffer, cbToWrite, cbToWrite, cbToWrite, pvBuffer, pcbWritten, pvReserved, fRc)); } return fRc; } /** Kernel32 - WriteConsoleW */ BOOL WINAPI kwSandbox_Kernel32_WriteConsoleW(HANDLE hConOutput, CONST VOID *pvBuffer, DWORD cwcToWrite, PDWORD pcwcWritten, PVOID pvReserved) { BOOL fRc; PKWOUTPUTSTREAMBUF pLineBuf; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (hConOutput == g_Sandbox.StdErr.hOutput) pLineBuf = &g_Sandbox.StdErr; else if (hConOutput == g_Sandbox.StdOut.hOutput) pLineBuf = &g_Sandbox.StdOut; else pLineBuf = g_Sandbox.StdErr.fIsConsole ? &g_Sandbox.StdErr : &g_Sandbox.StdOut; if (pLineBuf->fIsConsole) { kwSandboxConsoleWriteW(&g_Sandbox, pLineBuf, (wchar_t const *)pvBuffer, cwcToWrite); KWOUT_LOG(("WriteConsoleW: %p, %p LB %#x (%*.*ls), %p, %p -> TRUE [cached]\n", hConOutput, pvBuffer, cwcToWrite, cwcToWrite, cwcToWrite, pvBuffer, pcwcWritten, pvReserved)); if (pcwcWritten) *pcwcWritten = cwcToWrite; fRc = TRUE; } else { fRc = WriteConsoleW(hConOutput, pvBuffer, cwcToWrite, pcwcWritten, pvReserved); KWOUT_LOG(("WriteConsoleW: %p, %p LB %#x (%*.*ls), %p, %p -> %d !fallback!\n", hConOutput, pvBuffer, cwcToWrite, cwcToWrite, cwcToWrite, pvBuffer, pcwcWritten, pvReserved, fRc)); } return fRc; } #endif /* WITH_CONSOLE_OUTPUT_BUFFERING */ /* * * Virtual memory leak prevension. * Virtual memory leak prevension. * Virtual memory leak prevension. * */ #ifdef WITH_FIXED_VIRTUAL_ALLOCS /** For debug logging. */ # ifndef NDEBUG static void kwSandboxLogFixedAllocation(KU32 idxFixed, const char *pszWhere) { MEMORY_BASIC_INFORMATION MemInfo = { NULL, NULL, 0, 0, 0, 0, 0}; SIZE_T cbMemInfo = VirtualQuery(g_aFixedVirtualAllocs[idxFixed].pvReserved, &MemInfo, sizeof(MemInfo)); kHlpAssert(cbMemInfo == sizeof(MemInfo)); if (cbMemInfo != 0) KW_LOG(("%s: #%u %p LB %#x: base=%p alloc=%p region=%#x state=%#x prot=%#x type=%#x\n", pszWhere, idxFixed, g_aFixedVirtualAllocs[idxFixed].pvReserved, g_aFixedVirtualAllocs[idxFixed].cbFixed, MemInfo.BaseAddress, MemInfo.AllocationBase, MemInfo.RegionSize, MemInfo.State, MemInfo.Protect, MemInfo.Type)); } # else # define kwSandboxLogFixedAllocation(idxFixed, pszWhere) do { } while (0) # endif /** * Used by both kwSandbox_Kernel32_VirtualFree and kwSandboxCleanupLate * * @param idxFixed The fixed allocation index to "free". */ static void kwSandboxResetFixedAllocation(KU32 idxFixed) { BOOL fRc; kwSandboxLogFixedAllocation(idxFixed, "kwSandboxResetFixedAllocation[pre]"); fRc = VirtualFree(g_aFixedVirtualAllocs[idxFixed].pvReserved, g_aFixedVirtualAllocs[idxFixed].cbFixed, MEM_DECOMMIT); kHlpAssert(fRc); K_NOREF(fRc); kwSandboxLogFixedAllocation(idxFixed, "kwSandboxResetFixedAllocation[pst]"); g_aFixedVirtualAllocs[idxFixed].fInUse = K_FALSE; } #endif /* WITH_FIXED_VIRTUAL_ALLOCS */ /** Kernel32 - VirtualAlloc - for managing cl.exe / c1[xx].dll heap with fixed * location (~78MB in 32-bit 2010 compiler). */ static PVOID WINAPI kwSandbox_Kernel32_VirtualAlloc(PVOID pvAddr, SIZE_T cb, DWORD fAllocType, DWORD fProt) { PVOID pvMem; if (g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_CL) { KU32 idxPreAllocated = KU32_MAX; #ifdef WITH_FIXED_VIRTUAL_ALLOCS /* * Look for a pre-reserved CL.exe heap allocation. */ pvMem = NULL; if ( pvAddr != 0 && (fAllocType & MEM_RESERVE)) { KU32 idxFixed = K_ELEMENTS(g_aFixedVirtualAllocs); kHlpAssert(!(fAllocType & ~(MEM_RESERVE | MEM_TOP_DOWN))); while (idxFixed-- > 0) if ( g_aFixedVirtualAllocs[idxFixed].uFixed == (KUPTR)pvAddr && g_aFixedVirtualAllocs[idxFixed].pvReserved) { if (g_aFixedVirtualAllocs[idxFixed].cbFixed >= cb) { if (!g_aFixedVirtualAllocs[idxFixed].fInUse) { g_aFixedVirtualAllocs[idxFixed].fInUse = K_TRUE; pvMem = pvAddr; idxPreAllocated = idxFixed; KW_LOG(("VirtualAlloc: pvAddr=%p cb=%p type=%#x prot=%#x -> %p [pre allocated]\n", pvAddr, cb, fAllocType, fProt, pvMem)); kwSandboxLogFixedAllocation(idxFixed, "kwSandbox_Kernel32_VirtualAlloc"); SetLastError(NO_ERROR); break; } kwErrPrintf("VirtualAlloc: Fixed allocation at %p is already in use!\n", pvAddr); } else kwErrPrintf("VirtualAlloc: Fixed allocation at %p LB %#x not large enough: %#x\n", pvAddr, g_aFixedVirtualAllocs[idxFixed].cbFixed, cb); } } if (!pvMem) #endif { pvMem = VirtualAlloc(pvAddr, cb, fAllocType, fProt); KW_LOG(("VirtualAlloc: pvAddr=%p cb=%p type=%#x prot=%#x -> %p (last=%d)\n", pvAddr, cb, fAllocType, fProt, pvMem, GetLastError())); if (pvAddr && pvAddr != pvMem) kwErrPrintf("VirtualAlloc %p LB %#x (%#x,%#x) failed: %p / %u\n", pvAddr, cb, fAllocType, fProt, pvMem, GetLastError()); } if (pvMem) { /* * Track it. */ PKWVIRTALLOC pTracker; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pTracker = g_Sandbox.pVirtualAllocHead; while ( pTracker && (KUPTR)pvMem - (KUPTR)pTracker->pvAlloc >= pTracker->cbAlloc) pTracker = pTracker->pNext; if (!pTracker) { DWORD dwErr = GetLastError(); PKWVIRTALLOC pTracker = (PKWVIRTALLOC)kHlpAlloc(sizeof(*pTracker)); if (pTracker) { pTracker->pvAlloc = pvMem; pTracker->cbAlloc = cb; pTracker->idxPreAllocated = idxPreAllocated; pTracker->pNext = g_Sandbox.pVirtualAllocHead; g_Sandbox.pVirtualAllocHead = pTracker; } SetLastError(dwErr); } } } else pvMem = VirtualAlloc(pvAddr, cb, fAllocType, fProt); KW_LOG(("VirtualAlloc: pvAddr=%p cb=%p type=%#x prot=%#x -> %p (last=%d)\n", pvAddr, cb, fAllocType, fProt, pvMem, GetLastError())); return pvMem; } /** Kernel32 - VirtualFree. */ static BOOL WINAPI kwSandbox_Kernel32_VirtualFree(PVOID pvAddr, SIZE_T cb, DWORD dwFreeType) { BOOL fRc; if (g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_CL) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (dwFreeType & MEM_RELEASE) { PKWVIRTALLOC pTracker = g_Sandbox.pVirtualAllocHead; if (pTracker) { if (pTracker->pvAlloc == pvAddr) g_Sandbox.pVirtualAllocHead = pTracker->pNext; else { PKWVIRTALLOC pPrev; do { pPrev = pTracker; pTracker = pTracker->pNext; } while (pTracker && pTracker->pvAlloc != pvAddr); if (pTracker) pPrev->pNext = pTracker->pNext; } if (pTracker) { #ifdef WITH_FIXED_VIRTUAL_ALLOCS if (pTracker->idxPreAllocated != KU32_MAX) { kwSandboxResetFixedAllocation(pTracker->idxPreAllocated); KW_LOG(("VirtualFree: pvAddr=%p cb=%p type=%#x -> TRUE [pre allocated #%u]\n", pvAddr, cb, dwFreeType, pTracker->idxPreAllocated)); kHlpFree(pTracker); return TRUE; } #endif fRc = VirtualFree(pvAddr, cb, dwFreeType); if (fRc) kHlpFree(pTracker); else { pTracker->pNext = g_Sandbox.pVirtualAllocHead; g_Sandbox.pVirtualAllocHead = pTracker; } KW_LOG(("VirtualFree: pvAddr=%p cb=%p type=%#x -> %d\n", pvAddr, cb, dwFreeType, fRc)); return fRc; } KW_LOG(("VirtualFree: pvAddr=%p not found!\n", pvAddr)); } } } #ifdef WITH_FIXED_VIRTUAL_ALLOCS /* * Protect our fixed allocations (this isn't just paranoia, btw.). */ if (dwFreeType & MEM_RELEASE) { KU32 idxFixed = K_ELEMENTS(g_aFixedVirtualAllocs); while (idxFixed-- > 0) if (g_aFixedVirtualAllocs[idxFixed].pvReserved == pvAddr) { KW_LOG(("VirtualFree: Damn it! Don't free g_aFixedVirtualAllocs[#%u]: %p LB %#x\n", idxFixed, g_aFixedVirtualAllocs[idxFixed].pvReserved, g_aFixedVirtualAllocs[idxFixed].cbFixed)); return TRUE; } } #endif /* * Not tracker or not actually free the virtual range. */ fRc = VirtualFree(pvAddr, cb, dwFreeType); KW_LOG(("VirtualFree: pvAddr=%p cb=%p type=%#x -> %d\n", pvAddr, cb, dwFreeType, fRc)); return fRc; } /** Kernel32 - HeapCreate / NtDll - RTlCreateHeap */ HANDLE WINAPI kwSandbox_Kernel32_HeapCreate(DWORD fOptions, SIZE_T cbInitial, SIZE_T cbMax) { HANDLE hHeap; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); hHeap = HeapCreate(fOptions, cbInitial, cbMax); if (hHeap != NULL) { DWORD dwErr = GetLastError(); PKWHEAP pTracker = (PKWHEAP)kHlpAlloc(sizeof(*pTracker)); if (pTracker) { pTracker->hHeap = hHeap; pTracker->pNext = g_Sandbox.pHeapHead; g_Sandbox.pHeapHead = pTracker; } SetLastError(dwErr); } return hHeap; } /** Kernel32 - HeapDestroy / NtDll - RTlDestroyHeap */ BOOL WINAPI kwSandbox_Kernel32_HeapDestroy(HANDLE hHeap) { BOOL fRc = HeapDestroy(hHeap); KW_LOG(("HeapDestroy: hHeap=%p -> %d\n", hHeap, fRc)); kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); if (fRc) { PKWHEAP pTracker = g_Sandbox.pHeapHead; if (pTracker) { if (pTracker->hHeap == hHeap) g_Sandbox.pHeapHead = pTracker->pNext; else { PKWHEAP pPrev; do { pPrev = pTracker; pTracker = pTracker->pNext; } while (pTracker && pTracker->hHeap == hHeap); if (pTracker) pPrev->pNext = pTracker->pNext; } if (pTracker) kHlpFree(pTracker); else KW_LOG(("HeapDestroy: pvAddr=%p not found!\n", hHeap)); } } return fRc; } /* * * Thread/Fiber local storage leak prevention. * Thread/Fiber local storage leak prevention. * Thread/Fiber local storage leak prevention. * * Note! The FlsAlloc/Free & TlsAlloc/Free causes problems for statically * linked VS2010 code like VBoxBs3ObjConverter.exe. One thing is that * we're leaking these indexes, but more importantely we crash during * worker exit since the callback is triggered multiple times. */ /** Kernel32 - FlsAlloc */ DWORD WINAPI kwSandbox_Kernel32_FlsAlloc(PFLS_CALLBACK_FUNCTION pfnCallback) { DWORD idxFls = FlsAlloc(pfnCallback); KW_LOG(("FlsAlloc(%p) -> %#x\n", pfnCallback, idxFls)); if (idxFls != FLS_OUT_OF_INDEXES) { PKWLOCALSTORAGE pTracker = (PKWLOCALSTORAGE)kHlpAlloc(sizeof(*pTracker)); if (pTracker) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pTracker->idx = idxFls; pTracker->pNext = g_Sandbox.pFlsAllocHead; g_Sandbox.pFlsAllocHead = pTracker; } } return idxFls; } /** Kernel32 - FlsFree */ BOOL WINAPI kwSandbox_Kernel32_FlsFree(DWORD idxFls) { BOOL fRc = FlsFree(idxFls); KW_LOG(("FlsFree(%#x) -> %d\n", idxFls, fRc)); if (fRc) { PKWLOCALSTORAGE pTracker; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pTracker = g_Sandbox.pFlsAllocHead; if (pTracker) { if (pTracker->idx == idxFls) g_Sandbox.pFlsAllocHead = pTracker->pNext; else { PKWLOCALSTORAGE pPrev; do { pPrev = pTracker; pTracker = pTracker->pNext; } while (pTracker && pTracker->idx != idxFls); if (pTracker) pPrev->pNext = pTracker->pNext; } if (pTracker) { pTracker->idx = FLS_OUT_OF_INDEXES; pTracker->pNext = NULL; kHlpFree(pTracker); } } } return fRc; } /** Kernel32 - TlsAlloc */ DWORD WINAPI kwSandbox_Kernel32_TlsAlloc(VOID) { DWORD idxTls = TlsAlloc(); KW_LOG(("TlsAlloc() -> %#x\n", idxTls)); if (idxTls != TLS_OUT_OF_INDEXES) { PKWLOCALSTORAGE pTracker = (PKWLOCALSTORAGE)kHlpAlloc(sizeof(*pTracker)); if (pTracker) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pTracker->idx = idxTls; pTracker->pNext = g_Sandbox.pTlsAllocHead; g_Sandbox.pTlsAllocHead = pTracker; } } return idxTls; } /** Kernel32 - TlsFree */ BOOL WINAPI kwSandbox_Kernel32_TlsFree(DWORD idxTls) { BOOL fRc = TlsFree(idxTls); KW_LOG(("TlsFree(%#x) -> %d\n", idxTls, fRc)); if (fRc) { PKWLOCALSTORAGE pTracker; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pTracker = g_Sandbox.pTlsAllocHead; if (pTracker) { if (pTracker->idx == idxTls) g_Sandbox.pTlsAllocHead = pTracker->pNext; else { PKWLOCALSTORAGE pPrev; do { pPrev = pTracker; pTracker = pTracker->pNext; } while (pTracker && pTracker->idx != idxTls); if (pTracker) pPrev->pNext = pTracker->pNext; } if (pTracker) { pTracker->idx = TLS_OUT_OF_INDEXES; pTracker->pNext = NULL; kHlpFree(pTracker); } } } return fRc; } /* * * Header file hashing. * Header file hashing. * Header file hashing. * * c1.dll / c1XX.dll hashes the input files. The Visual C++ 2010 profiler * indicated that ~12% of the time was spent doing MD5 caluclation when * rebuiling openssl. The hashing it done right after reading the source * via ReadFile, same buffers and sizes. */ #ifdef WITH_HASH_MD5_CACHE /** AdvApi32 - CryptCreateHash */ static BOOL WINAPI kwSandbox_Advapi32_CryptCreateHash(HCRYPTPROV hProv, ALG_ID idAlg, HCRYPTKEY hKey, DWORD dwFlags, HCRYPTHASH *phHash) { BOOL fRc; /* * Only do this for cl.exe when it request normal MD5. */ if (g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_CL) { if (idAlg == CALG_MD5) { if (hKey == 0) { if (dwFlags == 0) { PKWHASHMD5 pHash = (PKWHASHMD5)kHlpAllocZ(sizeof(*pHash)); if (pHash) { kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); pHash->uMagic = KWHASHMD5_MAGIC; pHash->cbHashed = 0; pHash->fGoneBad = K_FALSE; pHash->fFallbackMode = K_FALSE; pHash->fFinal = K_FALSE; /* link it. */ pHash->pNext = g_Sandbox.pHashHead; g_Sandbox.pHashHead = pHash; *phHash = (KUPTR)pHash; KWCRYPT_LOG(("CryptCreateHash(hProv=%p, idAlg=CALG_MD5, 0, 0, *phHash=%p) -> %d [cached]\n", hProv, *phHash, TRUE)); return TRUE; } kwErrPrintf("CryptCreateHash: out of memory!\n"); } else kwErrPrintf("CryptCreateHash: dwFlags=%p is not supported with CALG_MD5\n", hKey); } else kwErrPrintf("CryptCreateHash: hKey=%p is not supported with CALG_MD5\n", hKey); } else kwErrPrintf("CryptCreateHash: idAlg=%#x is not supported\n", idAlg); } /* * Fallback. */ fRc = CryptCreateHash(hProv, idAlg, hKey, dwFlags, phHash); KWCRYPT_LOG(("CryptCreateHash(hProv=%p, idAlg=%#x (%d), hKey=%p, dwFlags=%#x, *phHash=%p) -> %d\n", hProv, idAlg, idAlg, hKey, dwFlags, *phHash, fRc)); return fRc; } /** AdvApi32 - CryptHashData */ static BOOL WINAPI kwSandbox_Advapi32_CryptHashData(HCRYPTHASH hHash, CONST BYTE *pbData, DWORD cbData, DWORD dwFlags) { BOOL fRc; PKWHASHMD5 pHash = g_Sandbox.pHashHead; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); while (pHash && (KUPTR)pHash != hHash) pHash = pHash->pNext; KWCRYPT_LOG(("CryptHashData(hHash=%p/%p, pbData=%p, cbData=%#x, dwFlags=%#x)\n", hHash, pHash, pbData, cbData, dwFlags)); if (pHash) { /* * Validate the state. */ if ( pHash->uMagic == KWHASHMD5_MAGIC && !pHash->fFinal) { if (!pHash->fFallbackMode) { /* * Does this match the previous ReadFile call to a cached file? * If it doesn't, try falling back. */ if ( g_Sandbox.LastHashRead.cbRead == cbData && g_Sandbox.LastHashRead.pvRead == (void *)pbData) { PKFSWCACHEDFILE pCachedFile = g_Sandbox.LastHashRead.pCachedFile; if ( pCachedFile && kHlpMemComp(pbData, &pCachedFile->pbCached[g_Sandbox.LastHashRead.offRead], K_MIN(cbData, 64)) == 0) { if (g_Sandbox.LastHashRead.offRead == pHash->cbHashed) { if ( pHash->pCachedFile == NULL && pHash->cbHashed == 0) pHash->pCachedFile = pCachedFile; if (pHash->pCachedFile == pCachedFile) { pHash->cbHashed += cbData; g_Sandbox.LastHashRead.pCachedFile = NULL; g_Sandbox.LastHashRead.pvRead = NULL; g_Sandbox.LastHashRead.cbRead = 0; g_Sandbox.LastHashRead.offRead = 0; KWCRYPT_LOG(("CryptHashData(hHash=%p/%p/%s, pbData=%p, cbData=%#x, dwFlags=%#x) -> 1 [cached]\n", hHash, pCachedFile, pCachedFile->szPath, pbData, cbData, dwFlags)); return TRUE; } /* Note! it's possible to fall back here too, if necessary. */ kwErrPrintf("CryptHashData: Expected pCachedFile=%p, last read was made to %p!!\n", pHash->pCachedFile, g_Sandbox.LastHashRead.pCachedFile); } else kwErrPrintf("CryptHashData: Expected last read at %#x, instead it was made at %#x\n", pHash->cbHashed, g_Sandbox.LastHashRead.offRead); } else if (!pCachedFile) kwErrPrintf("CryptHashData: Last pCachedFile is NULL when buffer address and size matches!\n"); else kwErrPrintf("CryptHashData: First 64 bytes of the buffer doesn't match the cache.\n"); } else if (g_Sandbox.LastHashRead.cbRead != 0 && pHash->cbHashed != 0) kwErrPrintf("CryptHashData: Expected cbRead=%#x and pbData=%p, got %#x and %p instead\n", g_Sandbox.LastHashRead.cbRead, g_Sandbox.LastHashRead.pvRead, cbData, pbData); if (pHash->cbHashed == 0) pHash->fFallbackMode = K_TRUE; if (pHash->fFallbackMode) { /* Initiate fallback mode (file that we don't normally cache, like .c/.cpp). */ pHash->fFallbackMode = K_TRUE; MD5Init(&pHash->Md5Ctx); MD5Update(&pHash->Md5Ctx, pbData, cbData); pHash->cbHashed = cbData; KWCRYPT_LOG(("CryptHashData(hHash=%p/fallback, pbData=%p, cbData=%#x, dwFlags=%#x) -> 1 [fallback!]\n", hHash, pbData, cbData, dwFlags)); return TRUE; } pHash->fGoneBad = K_TRUE; SetLastError(ERROR_INVALID_PARAMETER); fRc = FALSE; } else { /* fallback. */ MD5Update(&pHash->Md5Ctx, pbData, cbData); pHash->cbHashed += cbData; fRc = TRUE; KWCRYPT_LOG(("CryptHashData(hHash=%p/fallback, pbData=%p, cbData=%#x, dwFlags=%#x) -> 1 [fallback]\n", hHash, pbData, cbData, dwFlags)); } } /* * Bad handle state. */ else { if (pHash->uMagic != KWHASHMD5_MAGIC) kwErrPrintf("CryptHashData: Invalid cached hash handle!!\n"); else kwErrPrintf("CryptHashData: Hash is already finalized!!\n"); SetLastError(NTE_BAD_HASH); fRc = FALSE; } } else { fRc = CryptHashData(hHash, pbData, cbData, dwFlags); KWCRYPT_LOG(("CryptHashData(hHash=%p, pbData=%p, cbData=%#x, dwFlags=%#x) -> %d\n", hHash, pbData, cbData, dwFlags, fRc)); } return fRc; } /** AdvApi32 - CryptGetHashParam */ static BOOL WINAPI kwSandbox_Advapi32_CryptGetHashParam(HCRYPTHASH hHash, DWORD dwParam, BYTE *pbData, DWORD *pcbData, DWORD dwFlags) { BOOL fRc; PKWHASHMD5 pHash = g_Sandbox.pHashHead; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); while (pHash && (KUPTR)pHash != hHash) pHash = pHash->pNext; if (pHash) { if (pHash->uMagic == KWHASHMD5_MAGIC) { if (dwFlags == 0) { DWORD cbRet; void *pvRet; union { DWORD dw; } uBuf; switch (dwParam) { case HP_HASHVAL: { /* Check the hash progress. */ PKFSWCACHEDFILE pCachedFile = pHash->pCachedFile; if (pCachedFile) { if ( pCachedFile->cbCached == pHash->cbHashed && !pHash->fGoneBad) { if (pCachedFile->fValidMd5) KWCRYPT_LOG(("Already calculated hash for %p/%s! [hit]\n", pCachedFile, pCachedFile->szPath)); else { MD5Init(&pHash->Md5Ctx); MD5Update(&pHash->Md5Ctx, pCachedFile->pbCached, pCachedFile->cbCached); MD5Final(pCachedFile->abMd5Digest, &pHash->Md5Ctx); pCachedFile->fValidMd5 = K_TRUE; KWCRYPT_LOG(("Calculated hash for %p/%s.\n", pCachedFile, pCachedFile->szPath)); } pvRet = pCachedFile->abMd5Digest; } else { /* This actually happens (iprt/string.h + common/alloc/alloc.cpp), at least from what I can tell, so just deal with it. */ KWCRYPT_LOG(("CryptGetHashParam/HP_HASHVAL: Not at end of cached file! cbCached=%#x cbHashed=%#x fGoneBad=%d (%p/%p/%s)\n", pHash->pCachedFile->cbCached, pHash->cbHashed, pHash->fGoneBad, pHash, pCachedFile, pCachedFile->szPath)); pHash->fFallbackMode = K_TRUE; pHash->pCachedFile = NULL; MD5Init(&pHash->Md5Ctx); MD5Update(&pHash->Md5Ctx, pCachedFile->pbCached, pHash->cbHashed); MD5Final(pHash->abDigest, &pHash->Md5Ctx); pvRet = pHash->abDigest; } pHash->fFinal = K_TRUE; cbRet = 16; break; } else if (pHash->fFallbackMode) { if (!pHash->fFinal) { pHash->fFinal = K_TRUE; MD5Final(pHash->abDigest, &pHash->Md5Ctx); } pvRet = pHash->abDigest; cbRet = 16; break; } else { kwErrPrintf("CryptGetHashParam/HP_HASHVAL: pCachedFile is NULL!!\n"); SetLastError(ERROR_INVALID_SERVER_STATE); } return FALSE; } case HP_HASHSIZE: uBuf.dw = 16; pvRet = &uBuf; cbRet = sizeof(DWORD); break; case HP_ALGID: uBuf.dw = CALG_MD5; pvRet = &uBuf; cbRet = sizeof(DWORD); break; default: kwErrPrintf("CryptGetHashParam: Unknown dwParam=%#x\n", dwParam); SetLastError(NTE_BAD_TYPE); return FALSE; } /* * Copy out cbRet from pvRet. */ if (pbData) { if (*pcbData >= cbRet) { *pcbData = cbRet; kHlpMemCopy(pbData, pvRet, cbRet); if (cbRet == 4) KWCRYPT_LOG(("CryptGetHashParam/%#x/%p/%p: TRUE, cbRet=%#x data=%#x [cached]\n", dwParam, pHash, pHash->pCachedFile, cbRet, (DWORD *)pbData)); else if (cbRet == 16) KWCRYPT_LOG(("CryptGetHashParam/%#x/%p/%p: TRUE, cbRet=%#x data=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x [cached]\n", dwParam, pHash, pHash->pCachedFile, cbRet, pbData[0], pbData[1], pbData[2], pbData[3], pbData[4], pbData[5], pbData[6], pbData[7], pbData[8], pbData[9], pbData[10], pbData[11], pbData[12], pbData[13], pbData[14], pbData[15])); else KWCRYPT_LOG(("CryptGetHashParam/%#x%/p%/%p: TRUE, cbRet=%#x [cached]\n", dwParam, pHash, pHash->pCachedFile, cbRet)); return TRUE; } kHlpMemCopy(pbData, pvRet, *pcbData); } SetLastError(ERROR_MORE_DATA); *pcbData = cbRet; KWCRYPT_LOG(("CryptGetHashParam/%#x: ERROR_MORE_DATA\n")); } else { kwErrPrintf("CryptGetHashParam: dwFlags is not zero: %#x!\n", dwFlags); SetLastError(NTE_BAD_FLAGS); } } else { kwErrPrintf("CryptGetHashParam: Invalid cached hash handle!!\n"); SetLastError(NTE_BAD_HASH); } fRc = FALSE; } /* * Regular handle. */ else { fRc = CryptGetHashParam(hHash, dwParam, pbData, pcbData, dwFlags); KWCRYPT_LOG(("CryptGetHashParam(hHash=%p, dwParam=%#x (%d), pbData=%p, *pcbData=%#x, dwFlags=%#x) -> %d\n", hHash, dwParam, pbData, *pcbData, dwFlags, fRc)); } return fRc; } /** AdvApi32 - CryptDestroyHash */ static BOOL WINAPI kwSandbox_Advapi32_CryptDestroyHash(HCRYPTHASH hHash) { BOOL fRc; PKWHASHMD5 pPrev = NULL; PKWHASHMD5 pHash = g_Sandbox.pHashHead; kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); while (pHash && (KUPTR)pHash != hHash) { pPrev = pHash; pHash = pHash->pNext; } if (pHash) { if (pHash->uMagic == KWHASHMD5_MAGIC) { pHash->uMagic = 0; if (!pPrev) g_Sandbox.pHashHead = pHash->pNext; else pPrev->pNext = pHash->pNext; kHlpFree(pHash); KWCRYPT_LOG(("CryptDestroyHash(hHash=%p) -> 1 [cached]\n", hHash)); fRc = TRUE; } else { kwErrPrintf("CryptDestroyHash: Invalid cached hash handle!!\n"); KWCRYPT_LOG(("CryptDestroyHash(hHash=%p) -> FALSE! [cached]\n", hHash)); SetLastError(ERROR_INVALID_HANDLE); fRc = FALSE; } } /* * Regular handle. */ else { fRc = CryptDestroyHash(hHash); KWCRYPT_LOG(("CryptDestroyHash(hHash=%p) -> %d\n", hHash, fRc)); } return fRc; } #endif /* WITH_HASH_MD5_CACHE */ /* * * Reuse crypt context. * Reuse crypt context. * Reuse crypt context. * * * This saves a little bit of time and registry accesses each time CL, C1 or C1XX runs. * */ #ifdef WITH_CRYPT_CTX_REUSE /** AdvApi32 - CryptAcquireContextW. */ static BOOL WINAPI kwSandbox_Advapi32_CryptAcquireContextW(HCRYPTPROV *phProv, LPCWSTR pwszContainer, LPCWSTR pwszProvider, DWORD dwProvType, DWORD dwFlags) { BOOL fRet; /* * Lookup reusable context based on the input. */ KSIZE const cwcContainer = pwszContainer ? kwUtf16Len(pwszContainer) : 0; KSIZE const cwcProvider = pwszProvider ? kwUtf16Len(pwszProvider) : 0; KU32 iCtx = g_Sandbox.cCryptCtxs; while (iCtx-- > 0) { if ( g_Sandbox.aCryptCtxs[iCtx].cwcContainer == cwcContainer && g_Sandbox.aCryptCtxs[iCtx].cwcProvider == cwcProvider && g_Sandbox.aCryptCtxs[iCtx].dwProvType == dwProvType && g_Sandbox.aCryptCtxs[iCtx].dwFlags == dwFlags && kHlpMemComp(g_Sandbox.aCryptCtxs[iCtx].pwszContainer, pwszContainer, cwcContainer * sizeof(wchar_t)) == 0 && kHlpMemComp(g_Sandbox.aCryptCtxs[iCtx].pwszProvider, pwszProvider, cwcProvider * sizeof(wchar_t)) == 0) { if (CryptContextAddRef(g_Sandbox.aCryptCtxs[iCtx].hProv, NULL, 0)) { *phProv = g_Sandbox.aCryptCtxs[iCtx].hProv; KWCRYPT_LOG(("CryptAcquireContextW(,%ls, %ls, %#x, %#x) -> TRUE, %p [reused]\n", pwszContainer, pwszProvider, dwProvType, dwFlags, *phProv)); return TRUE; } } } /* * Create it and enter it into the reused array if possible. */ fRet = CryptAcquireContextW(phProv, pwszContainer, pwszProvider, dwProvType, dwFlags); if (fRet) { iCtx = g_Sandbox.cCryptCtxs; if (iCtx < K_ELEMENTS(g_Sandbox.aCryptCtxs)) { /* Try duplicate the input strings. */ g_Sandbox.aCryptCtxs[iCtx].pwszContainer = kHlpDup(pwszContainer ? pwszContainer : L"", (cwcContainer + 1) * sizeof(wchar_t)); if (g_Sandbox.aCryptCtxs[iCtx].pwszContainer) { g_Sandbox.aCryptCtxs[iCtx].pwszProvider = kHlpDup(pwszProvider ? pwszProvider : L"", (cwcProvider + 1) * sizeof(wchar_t)); if (g_Sandbox.aCryptCtxs[iCtx].pwszProvider) { /* Add a couple of references just to be on the safe side and all that. */ HCRYPTPROV hProv = *phProv; if (CryptContextAddRef(hProv, NULL, 0)) { if (CryptContextAddRef(hProv, NULL, 0)) { /* Okay, finish the entry and return success */ g_Sandbox.aCryptCtxs[iCtx].hProv = hProv; g_Sandbox.aCryptCtxs[iCtx].dwProvType = dwProvType; g_Sandbox.aCryptCtxs[iCtx].dwFlags = dwFlags; g_Sandbox.cCryptCtxs = iCtx + 1; KWCRYPT_LOG(("CryptAcquireContextW(,%ls, %ls, %#x, %#x) -> TRUE, %p [new]\n", pwszContainer, pwszProvider, dwProvType, dwFlags, *phProv)); return TRUE; } CryptReleaseContext(hProv, 0); } KWCRYPT_LOG(("CryptAcquireContextW: CryptContextAddRef failed!\n")); kHlpFree(g_Sandbox.aCryptCtxs[iCtx].pwszProvider); g_Sandbox.aCryptCtxs[iCtx].pwszProvider = NULL; } kHlpFree(g_Sandbox.aCryptCtxs[iCtx].pwszContainer); g_Sandbox.aCryptCtxs[iCtx].pwszContainer = NULL; } } else KWCRYPT_LOG(("CryptAcquireContextW: Too many crypt contexts to keep and reuse!\n")); } KWCRYPT_LOG(("CryptAcquireContextW(,%ls, %ls, %#x, %#x) -> %d, %p\n", pwszContainer, pwszProvider, dwProvType, dwFlags, *phProv)); return fRet; } /** AdvApi32 - CryptReleaseContext */ static BOOL WINAPI kwSandbox_Advapi32_CryptReleaseContext(HCRYPTPROV hProv, DWORD dwFlags) { BOOL fRet = CryptReleaseContext(hProv, dwFlags); KWCRYPT_LOG(("CryptReleaseContext(%p,%#x) -> %d\n", hProv, dwFlags, fRet)); return fRet; } /** AdvApi32 - CryptContextAddRef */ static BOOL WINAPI kwSandbox_Advapi32_CryptContextAddRef(HCRYPTPROV hProv, DWORD *pdwReserved, DWORD dwFlags) { BOOL fRet = CryptContextAddRef(hProv, pdwReserved, dwFlags); KWCRYPT_LOG(("CryptContextAddRef(%p,%p,%#x) -> %d\n", hProv, pdwReserved, dwFlags, fRet)); return fRet; } #endif /* WITH_CRYPT_CTX_REUSE */ /* * * Structured exception handling. * Structured exception handling. * Structured exception handling. * */ #if defined(KBUILD_OS_WINDOWS) && defined(KBUILD_ARCH_X86) # define EH_NONCONTINUABLE KU32_C(0x00000001) # define EH_UNWINDING KU32_C(0x00000002) # define EH_EXIT_UNWIND KU32_C(0x00000004) # define EH_STACK_INVALID KU32_C(0x00000008) # define EH_NESTED_CALL KU32_C(0x00000010) typedef KU32 (__cdecl * volatile PFNXCPTHANDLER)(PEXCEPTION_RECORD, struct _EXCEPTION_REGISTRATION_RECORD*, PCONTEXT, struct _EXCEPTION_REGISTRATION_RECORD * volatile *); typedef struct _EXCEPTION_REGISTRATION_RECORD { struct _EXCEPTION_REGISTRATION_RECORD * volatile pPrevRegRec; PFNXCPTHANDLER pfnXcptHandler; }; /** * Calls @a pfnHandler. */ static KU32 kwSandboxXcptCallHandler(PEXCEPTION_RECORD pXcptRec, struct _EXCEPTION_REGISTRATION_RECORD *pRegRec, PCONTEXT pXcptCtx, struct _EXCEPTION_REGISTRATION_RECORD * volatile * ppRegRec, PFNXCPTHANDLER pfnHandler) { # if 1 /* This is a more robust version that isn't subject to calling convension cleanup disputes and such. */ KU32 uSavedEdi; KU32 uSavedEsi; KU32 uSavedEbx; KU32 rcHandler; __asm { mov [uSavedEdi], edi mov [uSavedEsi], esi mov [uSavedEbx], ebx mov esi, esp mov edi, esp mov edi, [pXcptRec] mov edx, [pRegRec] mov eax, [pXcptCtx] mov ebx, [ppRegRec] mov ecx, [pfnHandler] sub esp, 16 and esp, 0fffffff0h mov [esp ], edi mov [esp + 4], edx mov [esp + 8], eax mov [esp + 12], ebx mov edi, esi call ecx mov esp, esi cmp esp, edi je stack_ok int 3 stack_ok: mov edi, [uSavedEdi] mov esi, [uSavedEsi] mov ebx, [uSavedEbx] mov [rcHandler], eax } return rcHandler; # else return pfnHandler(pXcptRec, pRegRec, pXctpCtx, ppRegRec); # endif } /** * Vectored exception handler that emulates x86 chained exception handler. * * This is necessary because the RtlIsValidHandler check fails for self loaded * code and prevents cl.exe from working. (On AMD64 we can register function * tables, but on X86 cooking your own handling seems to be the only viabke * alternative.) * * @returns EXCEPTION_CONTINUE_SEARCH or EXCEPTION_CONTINUE_EXECUTION. * @param pXcptPtrs The exception details. */ static LONG CALLBACK kwSandboxVecXcptEmulateChained(PEXCEPTION_POINTERS pXcptPtrs) { PNT_TIB pTib = (PNT_TIB)NtCurrentTeb(); KW_LOG(("kwSandboxVecXcptEmulateChained: %#x\n", pXcptPtrs->ExceptionRecord->ExceptionCode)); if (g_Sandbox.fRunning) { HANDLE const hCurProc = GetCurrentProcess(); PEXCEPTION_RECORD pXcptRec = pXcptPtrs->ExceptionRecord; PCONTEXT pXcptCtx = pXcptPtrs->ContextRecord; struct _EXCEPTION_REGISTRATION_RECORD * pRegRec = pTib->ExceptionList; while (((KUPTR)pRegRec & (sizeof(void *) - 3)) == 0 && pRegRec != NULL) { /* Read the exception record in a safe manner. */ struct _EXCEPTION_REGISTRATION_RECORD RegRec; DWORD cbActuallyRead = 0; if ( ReadProcessMemory(hCurProc, pRegRec, &RegRec, sizeof(RegRec), &cbActuallyRead) && cbActuallyRead == sizeof(RegRec)) { struct _EXCEPTION_REGISTRATION_RECORD * volatile pDispRegRec = NULL; KU32 rcHandler; KW_LOG(("kwSandboxVecXcptEmulateChained: calling %p, pRegRec=%p, pPrevRegRec=%p\n", RegRec.pfnXcptHandler, pRegRec, RegRec.pPrevRegRec)); rcHandler = kwSandboxXcptCallHandler(pXcptRec, pRegRec, pXcptCtx, &pDispRegRec, RegRec.pfnXcptHandler); KW_LOG(("kwSandboxVecXcptEmulateChained: rcHandler=%#x pDispRegRec=%p\n", rcHandler, pDispRegRec)); if (rcHandler == ExceptionContinueExecution) { kHlpAssert(!(pXcptPtrs->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)); KW_LOG(("kwSandboxVecXcptEmulateChained: returning EXCEPTION_CONTINUE_EXECUTION!\n")); return EXCEPTION_CONTINUE_EXECUTION; } if (rcHandler == ExceptionContinueSearch) kHlpAssert(!(pXcptPtrs->ExceptionRecord->ExceptionFlags & 8 /*EXCEPTION_STACK_INVALID*/)); else if (rcHandler == ExceptionNestedException) kHlpAssertMsgFailed(("Nested exceptions.\n")); else kHlpAssertMsgFailed(("Invalid return %#x (%d).\n", rcHandler, rcHandler)); } else { KW_LOG(("kwSandboxVecXcptEmulateChained: Bad xcpt chain entry at %p! Stopping search.\n", pRegRec)); break; } /* * Next. */ pRegRec = RegRec.pPrevRegRec; } } return EXCEPTION_CONTINUE_SEARCH; } /** NtDll,Kernel32 - RtlUnwind */ static VOID WINAPI kwSandbox_ntdll_RtlUnwind(struct _EXCEPTION_REGISTRATION_RECORD *pStopXcptRec, PVOID pvTargetIp, PEXCEPTION_RECORD pXcptRec, PVOID pvReturnValue) { PNT_TIB pTib = (PNT_TIB)NtCurrentTeb(); KW_LOG(("kwSandbox_ntdll_RtlUnwind: pStopXcptRec=%p pvTargetIp=%p pXctpRec=%p pvReturnValue=%p%s\n", pStopXcptRec, pvTargetIp, pXcptRec, pvReturnValue, g_Sandbox.fRunning ? "" : " [sandbox not running]")); if (g_Sandbox.fRunning) { HANDLE const hCurProc = GetCurrentProcess(); PCONTEXT pXcptCtx = NULL; struct _EXCEPTION_REGISTRATION_RECORD * pRegRec = pTib->ExceptionList; /* * Update / create an exception record. */ if (pXcptRec) pXcptRec->ExceptionFlags |= EH_UNWINDING; else { pXcptRec = (PEXCEPTION_RECORD)alloca(sizeof(*pXcptRec)); kHlpMemSet(pXcptRec, 0, sizeof(*pXcptRec)); pXcptRec->ExceptionCode = STATUS_UNWIND; pXcptRec->ExceptionFlags = EH_UNWINDING; } if (!pStopXcptRec) pXcptRec->ExceptionFlags |= EH_EXIT_UNWIND; /* * Walk the chain till we find pStopXctpRec. */ while ( ((KUPTR)pRegRec & (sizeof(void *) - 3)) == 0 && pRegRec != NULL && pRegRec != pStopXcptRec) { /* Read the exception record in a safe manner. */ struct _EXCEPTION_REGISTRATION_RECORD RegRec; DWORD cbActuallyRead = 0; if ( ReadProcessMemory(hCurProc, pRegRec, &RegRec, sizeof(RegRec), &cbActuallyRead) && cbActuallyRead == sizeof(RegRec)) { struct _EXCEPTION_REGISTRATION_RECORD * volatile pDispRegRec = NULL; KU32 rcHandler; KW_LOG(("kwSandbox_ntdll_RtlUnwind: calling %p, pRegRec=%p, pPrevRegRec=%p\n", RegRec.pfnXcptHandler, pRegRec, RegRec.pPrevRegRec)); rcHandler = kwSandboxXcptCallHandler(pXcptRec, pRegRec, pXcptCtx, &pDispRegRec, RegRec.pfnXcptHandler); KW_LOG(("kwSandbox_ntdll_RtlUnwind: rcHandler=%#x pDispRegRec=%p\n", rcHandler, pDispRegRec)); if (rcHandler == ExceptionContinueSearch) kHlpAssert(!(pXcptRec->ExceptionFlags & 8 /*EXCEPTION_STACK_INVALID*/)); else if (rcHandler == ExceptionCollidedUnwind) kHlpAssertMsgFailed(("Implement collided unwind!\n")); else kHlpAssertMsgFailed(("Invalid return %#x (%d).\n", rcHandler, rcHandler)); } else { KW_LOG(("kwSandbox_ntdll_RtlUnwind: Bad xcpt chain entry at %p! Stopping search.\n", pRegRec)); break; } /* * Pop next. */ pTib->ExceptionList = RegRec.pPrevRegRec; pRegRec = RegRec.pPrevRegRec; } return; } RtlUnwind(pStopXcptRec, pvTargetIp, pXcptRec, pvReturnValue); } #endif /* WINDOWS + X86 */ /* * * Misc function only intercepted while debugging. * Misc function only intercepted while debugging. * Misc function only intercepted while debugging. * */ #ifndef NDEBUG /** CRT - memcpy */ static void * __cdecl kwSandbox_msvcrt_memcpy(void *pvDst, void const *pvSrc, size_t cb) { KU8 const *pbSrc = (KU8 const *)pvSrc; KU8 *pbDst = (KU8 *)pvDst; KSIZE cbLeft = cb; while (cbLeft-- > 0) *pbDst++ = *pbSrc++; return pvDst; } /** CRT - memset */ static void * __cdecl kwSandbox_msvcrt_memset(void *pvDst, int bFiller, size_t cb) { KU8 *pbDst = (KU8 *)pvDst; KSIZE cbLeft = cb; while (cbLeft-- > 0) *pbDst++ = bFiller; return pvDst; } #endif /* NDEBUG */ /** * Functions that needs replacing for sandboxed execution. */ KWREPLACEMENTFUNCTION const g_aSandboxReplacements[] = { /* * Kernel32.dll and friends. */ { TUPLE("ExitProcess"), NULL, (KUPTR)kwSandbox_Kernel32_ExitProcess }, { TUPLE("TerminateProcess"), NULL, (KUPTR)kwSandbox_Kernel32_TerminateProcess }, { TUPLE("LoadLibraryA"), NULL, (KUPTR)kwSandbox_Kernel32_LoadLibraryA }, { TUPLE("LoadLibraryW"), NULL, (KUPTR)kwSandbox_Kernel32_LoadLibraryW }, { TUPLE("LoadLibraryExA"), NULL, (KUPTR)kwSandbox_Kernel32_LoadLibraryExA }, { TUPLE("LoadLibraryExW"), NULL, (KUPTR)kwSandbox_Kernel32_LoadLibraryExW }, { TUPLE("FreeLibrary"), NULL, (KUPTR)kwSandbox_Kernel32_FreeLibrary }, { TUPLE("GetModuleHandleA"), NULL, (KUPTR)kwSandbox_Kernel32_GetModuleHandleA }, { TUPLE("GetModuleHandleW"), NULL, (KUPTR)kwSandbox_Kernel32_GetModuleHandleW }, { TUPLE("GetProcAddress"), NULL, (KUPTR)kwSandbox_Kernel32_GetProcAddress }, { TUPLE("GetModuleFileNameA"), NULL, (KUPTR)kwSandbox_Kernel32_GetModuleFileNameA }, { TUPLE("GetModuleFileNameW"), NULL, (KUPTR)kwSandbox_Kernel32_GetModuleFileNameW }, { TUPLE("RtlPcToFileHeader"), NULL, (KUPTR)kwSandbox_ntdll_RtlPcToFileHeader }, { TUPLE("GetCommandLineA"), NULL, (KUPTR)kwSandbox_Kernel32_GetCommandLineA }, { TUPLE("GetCommandLineW"), NULL, (KUPTR)kwSandbox_Kernel32_GetCommandLineW }, { TUPLE("GetStartupInfoA"), NULL, (KUPTR)kwSandbox_Kernel32_GetStartupInfoA }, { TUPLE("GetStartupInfoW"), NULL, (KUPTR)kwSandbox_Kernel32_GetStartupInfoW }, { TUPLE("CreateThread"), NULL, (KUPTR)kwSandbox_Kernel32_CreateThread }, { TUPLE("GetEnvironmentStrings"), NULL, (KUPTR)kwSandbox_Kernel32_GetEnvironmentStrings }, { TUPLE("GetEnvironmentStringsA"), NULL, (KUPTR)kwSandbox_Kernel32_GetEnvironmentStringsA }, { TUPLE("GetEnvironmentStringsW"), NULL, (KUPTR)kwSandbox_Kernel32_GetEnvironmentStringsW }, { TUPLE("FreeEnvironmentStringsA"), NULL, (KUPTR)kwSandbox_Kernel32_FreeEnvironmentStringsA }, { TUPLE("FreeEnvironmentStringsW"), NULL, (KUPTR)kwSandbox_Kernel32_FreeEnvironmentStringsW }, { TUPLE("GetEnvironmentVariableA"), NULL, (KUPTR)kwSandbox_Kernel32_GetEnvironmentVariableA }, { TUPLE("GetEnvironmentVariableW"), NULL, (KUPTR)kwSandbox_Kernel32_GetEnvironmentVariableW }, { TUPLE("SetEnvironmentVariableA"), NULL, (KUPTR)kwSandbox_Kernel32_SetEnvironmentVariableA }, { TUPLE("SetEnvironmentVariableW"), NULL, (KUPTR)kwSandbox_Kernel32_SetEnvironmentVariableW }, { TUPLE("ExpandEnvironmentStringsA"), NULL, (KUPTR)kwSandbox_Kernel32_ExpandEnvironmentStringsA }, { TUPLE("ExpandEnvironmentStringsW"), NULL, (KUPTR)kwSandbox_Kernel32_ExpandEnvironmentStringsW }, { TUPLE("CreateFileA"), NULL, (KUPTR)kwSandbox_Kernel32_CreateFileA }, { TUPLE("CreateFileW"), NULL, (KUPTR)kwSandbox_Kernel32_CreateFileW }, { TUPLE("ReadFile"), NULL, (KUPTR)kwSandbox_Kernel32_ReadFile }, { TUPLE("ReadFileEx"), NULL, (KUPTR)kwSandbox_Kernel32_ReadFileEx }, #ifdef WITH_TEMP_MEMORY_FILES { TUPLE("WriteFile"), NULL, (KUPTR)kwSandbox_Kernel32_WriteFile }, { TUPLE("WriteFileEx"), NULL, (KUPTR)kwSandbox_Kernel32_WriteFileEx }, { TUPLE("SetEndOfFile"), NULL, (KUPTR)kwSandbox_Kernel32_SetEndOfFile }, { TUPLE("GetFileType"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileType }, { TUPLE("GetFileSize"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileSize }, { TUPLE("GetFileSizeEx"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileSizeEx }, { TUPLE("CreateFileMappingW"), NULL, (KUPTR)kwSandbox_Kernel32_CreateFileMappingW }, { TUPLE("MapViewOfFile"), NULL, (KUPTR)kwSandbox_Kernel32_MapViewOfFile }, { TUPLE("MapViewOfFileEx"), NULL, (KUPTR)kwSandbox_Kernel32_MapViewOfFileEx }, { TUPLE("UnmapViewOfFile"), NULL, (KUPTR)kwSandbox_Kernel32_UnmapViewOfFile }, #endif { TUPLE("SetFilePointer"), NULL, (KUPTR)kwSandbox_Kernel32_SetFilePointer }, { TUPLE("SetFilePointerEx"), NULL, (KUPTR)kwSandbox_Kernel32_SetFilePointerEx }, { TUPLE("DuplicateHandle"), NULL, (KUPTR)kwSandbox_Kernel32_DuplicateHandle }, { TUPLE("CloseHandle"), NULL, (KUPTR)kwSandbox_Kernel32_CloseHandle }, { TUPLE("GetFileAttributesA"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileAttributesA }, { TUPLE("GetFileAttributesW"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileAttributesW }, { TUPLE("GetShortPathNameW"), NULL, (KUPTR)kwSandbox_Kernel32_GetShortPathNameW }, #ifdef WITH_TEMP_MEMORY_FILES { TUPLE("DeleteFileW"), NULL, (KUPTR)kwSandbox_Kernel32_DeleteFileW }, #endif { TUPLE("WriteConsoleA"), NULL, (KUPTR)kwSandbox_Kernel32_WriteConsoleA }, { TUPLE("WriteConsoleW"), NULL, (KUPTR)kwSandbox_Kernel32_WriteConsoleW }, { TUPLE("VirtualAlloc"), NULL, (KUPTR)kwSandbox_Kernel32_VirtualAlloc }, { TUPLE("VirtualFree"), NULL, (KUPTR)kwSandbox_Kernel32_VirtualFree }, { TUPLE("HeapCreate"), NULL, (KUPTR)kwSandbox_Kernel32_HeapCreate, K_TRUE /*fOnlyExe*/ }, { TUPLE("HeapDestroy"), NULL, (KUPTR)kwSandbox_Kernel32_HeapDestroy, K_TRUE /*fOnlyExe*/ }, { TUPLE("FlsAlloc"), NULL, (KUPTR)kwSandbox_Kernel32_FlsAlloc, K_TRUE /*fOnlyExe*/ }, { TUPLE("FlsFree"), NULL, (KUPTR)kwSandbox_Kernel32_FlsFree, K_TRUE /*fOnlyExe*/ }, { TUPLE("TlsAlloc"), NULL, (KUPTR)kwSandbox_Kernel32_TlsAlloc, K_TRUE /*fOnlyExe*/ }, { TUPLE("TlsFree"), NULL, (KUPTR)kwSandbox_Kernel32_TlsFree, K_TRUE /*fOnlyExe*/ }, { TUPLE("SetConsoleCtrlHandler"), NULL, (KUPTR)kwSandbox_Kernel32_SetConsoleCtrlHandler }, #if defined(KBUILD_OS_WINDOWS) && defined(KBUILD_ARCH_X86) { TUPLE("RtlUnwind"), NULL, (KUPTR)kwSandbox_ntdll_RtlUnwind }, #endif #ifdef WITH_HASH_MD5_CACHE { TUPLE("CryptCreateHash"), NULL, (KUPTR)kwSandbox_Advapi32_CryptCreateHash }, { TUPLE("CryptHashData"), NULL, (KUPTR)kwSandbox_Advapi32_CryptHashData }, { TUPLE("CryptGetHashParam"), NULL, (KUPTR)kwSandbox_Advapi32_CryptGetHashParam }, { TUPLE("CryptDestroyHash"), NULL, (KUPTR)kwSandbox_Advapi32_CryptDestroyHash }, #endif #ifdef WITH_CRYPT_CTX_REUSE { TUPLE("CryptAcquireContextW"), NULL, (KUPTR)kwSandbox_Advapi32_CryptAcquireContextW }, { TUPLE("CryptReleaseContext"), NULL, (KUPTR)kwSandbox_Advapi32_CryptReleaseContext }, { TUPLE("CryptContextAddRef"), NULL, (KUPTR)kwSandbox_Advapi32_CryptContextAddRef }, #endif /* * MS Visual C++ CRTs. */ { TUPLE("exit"), NULL, (KUPTR)kwSandbox_msvcrt_exit }, { TUPLE("_exit"), NULL, (KUPTR)kwSandbox_msvcrt__exit }, { TUPLE("_cexit"), NULL, (KUPTR)kwSandbox_msvcrt__cexit }, { TUPLE("_c_exit"), NULL, (KUPTR)kwSandbox_msvcrt__c_exit }, { TUPLE("_amsg_exit"), NULL, (KUPTR)kwSandbox_msvcrt__amsg_exit }, { TUPLE("terminate"), NULL, (KUPTR)kwSandbox_msvcrt_terminate }, { TUPLE("onexit"), NULL, (KUPTR)kwSandbox_msvcrt__onexit, K_TRUE /*fOnlyExe*/ }, { TUPLE("_onexit"), NULL, (KUPTR)kwSandbox_msvcrt__onexit, K_TRUE /*fOnlyExe*/ }, { TUPLE("atexit"), NULL, (KUPTR)kwSandbox_msvcrt_atexit, K_TRUE /*fOnlyExe*/ }, { TUPLE("_beginthread"), NULL, (KUPTR)kwSandbox_msvcrt__beginthread }, { TUPLE("_beginthreadex"), NULL, (KUPTR)kwSandbox_msvcrt__beginthreadex }, { TUPLE("_beginthreadex"), "msvcr120.dll", (KUPTR)kwSandbox_msvcr120__beginthreadex }, /* higher priority last */ { TUPLE("__argc"), NULL, (KUPTR)&g_Sandbox.cArgs }, { TUPLE("__argv"), NULL, (KUPTR)&g_Sandbox.papszArgs }, { TUPLE("__wargv"), NULL, (KUPTR)&g_Sandbox.papwszArgs }, { TUPLE("__p___argc"), NULL, (KUPTR)kwSandbox_msvcrt___p___argc }, { TUPLE("__p___argv"), NULL, (KUPTR)kwSandbox_msvcrt___p___argv }, { TUPLE("__p___wargv"), NULL, (KUPTR)kwSandbox_msvcrt___p___wargv }, { TUPLE("_acmdln"), NULL, (KUPTR)&g_Sandbox.pszCmdLine }, { TUPLE("_wcmdln"), NULL, (KUPTR)&g_Sandbox.pwszCmdLine }, { TUPLE("__p__acmdln"), NULL, (KUPTR)kwSandbox_msvcrt___p__acmdln }, { TUPLE("__p__wcmdln"), NULL, (KUPTR)kwSandbox_msvcrt___p__wcmdln }, { TUPLE("_pgmptr"), NULL, (KUPTR)&g_Sandbox.pgmptr }, { TUPLE("_wpgmptr"), NULL, (KUPTR)&g_Sandbox.wpgmptr }, { TUPLE("_get_pgmptr"), NULL, (KUPTR)kwSandbox_msvcrt__get_pgmptr }, { TUPLE("_get_wpgmptr"), NULL, (KUPTR)kwSandbox_msvcrt__get_wpgmptr }, { TUPLE("__p__pgmptr"), NULL, (KUPTR)kwSandbox_msvcrt___p__pgmptr }, { TUPLE("__p__wpgmptr"), NULL, (KUPTR)kwSandbox_msvcrt___p__wpgmptr }, { TUPLE("_wincmdln"), NULL, (KUPTR)kwSandbox_msvcrt__wincmdln }, { TUPLE("_wwincmdln"), NULL, (KUPTR)kwSandbox_msvcrt__wwincmdln }, { TUPLE("__getmainargs"), NULL, (KUPTR)kwSandbox_msvcrt___getmainargs}, { TUPLE("__wgetmainargs"), NULL, (KUPTR)kwSandbox_msvcrt___wgetmainargs}, { TUPLE("_putenv"), NULL, (KUPTR)kwSandbox_msvcrt__putenv}, { TUPLE("_wputenv"), NULL, (KUPTR)kwSandbox_msvcrt__wputenv}, { TUPLE("_putenv_s"), NULL, (KUPTR)kwSandbox_msvcrt__putenv_s}, { TUPLE("_wputenv_s"), NULL, (KUPTR)kwSandbox_msvcrt__wputenv_s}, { TUPLE("__initenv"), NULL, (KUPTR)&g_Sandbox.initenv }, { TUPLE("__winitenv"), NULL, (KUPTR)&g_Sandbox.winitenv }, { TUPLE("__p___initenv"), NULL, (KUPTR)kwSandbox_msvcrt___p___initenv}, { TUPLE("__p___winitenv"), NULL, (KUPTR)kwSandbox_msvcrt___p___winitenv}, { TUPLE("_environ"), NULL, (KUPTR)&g_Sandbox.environ }, { TUPLE("_wenviron"), NULL, (KUPTR)&g_Sandbox.wenviron }, { TUPLE("_get_environ"), NULL, (KUPTR)kwSandbox_msvcrt__get_environ }, { TUPLE("_get_wenviron"), NULL, (KUPTR)kwSandbox_msvcrt__get_wenviron }, { TUPLE("__p__environ"), NULL, (KUPTR)kwSandbox_msvcrt___p__environ }, { TUPLE("__p__wenviron"), NULL, (KUPTR)kwSandbox_msvcrt___p__wenviron }, #ifndef NDEBUG { TUPLE("memcpy"), NULL, (KUPTR)kwSandbox_msvcrt_memcpy }, { TUPLE("memset"), NULL, (KUPTR)kwSandbox_msvcrt_memset }, #endif }; /** Number of entries in g_aReplacements. */ KU32 const g_cSandboxReplacements = K_ELEMENTS(g_aSandboxReplacements); /** * Functions that needs replacing in natively loaded DLLs when doing sandboxed * execution. */ KWREPLACEMENTFUNCTION const g_aSandboxNativeReplacements[] = { /* * Kernel32.dll and friends. */ { TUPLE("ExitProcess"), NULL, (KUPTR)kwSandbox_Kernel32_ExitProcess }, { TUPLE("TerminateProcess"), NULL, (KUPTR)kwSandbox_Kernel32_TerminateProcess }, #if 0 { TUPLE("CreateThread"), NULL, (KUPTR)kwSandbox_Kernel32_CreateThread }, #endif { TUPLE("CreateFileA"), NULL, (KUPTR)kwSandbox_Kernel32_CreateFileA }, { TUPLE("CreateFileW"), NULL, (KUPTR)kwSandbox_Kernel32_CreateFileW }, { TUPLE("ReadFile"), NULL, (KUPTR)kwSandbox_Kernel32_ReadFile }, { TUPLE("ReadFileEx"), NULL, (KUPTR)kwSandbox_Kernel32_ReadFileEx }, #ifdef WITH_TEMP_MEMORY_FILES { TUPLE("WriteFile"), NULL, (KUPTR)kwSandbox_Kernel32_WriteFile }, { TUPLE("WriteFileEx"), NULL, (KUPTR)kwSandbox_Kernel32_WriteFileEx }, { TUPLE("SetEndOfFile"), NULL, (KUPTR)kwSandbox_Kernel32_SetEndOfFile }, { TUPLE("GetFileType"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileType }, { TUPLE("GetFileSize"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileSize }, { TUPLE("GetFileSizeEx"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileSizeEx }, { TUPLE("CreateFileMappingW"), NULL, (KUPTR)kwSandbox_Kernel32_CreateFileMappingW }, { TUPLE("MapViewOfFile"), NULL, (KUPTR)kwSandbox_Kernel32_MapViewOfFile }, { TUPLE("MapViewOfFileEx"), NULL, (KUPTR)kwSandbox_Kernel32_MapViewOfFileEx }, { TUPLE("UnmapViewOfFile"), NULL, (KUPTR)kwSandbox_Kernel32_UnmapViewOfFile }, #endif { TUPLE("SetFilePointer"), NULL, (KUPTR)kwSandbox_Kernel32_SetFilePointer }, { TUPLE("SetFilePointerEx"), NULL, (KUPTR)kwSandbox_Kernel32_SetFilePointerEx }, { TUPLE("DuplicateHandle"), NULL, (KUPTR)kwSandbox_Kernel32_DuplicateHandle }, { TUPLE("CloseHandle"), NULL, (KUPTR)kwSandbox_Kernel32_CloseHandle }, { TUPLE("GetFileAttributesA"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileAttributesA }, { TUPLE("GetFileAttributesW"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileAttributesW }, { TUPLE("GetShortPathNameW"), NULL, (KUPTR)kwSandbox_Kernel32_GetShortPathNameW }, #ifdef WITH_TEMP_MEMORY_FILES { TUPLE("DeleteFileW"), NULL, (KUPTR)kwSandbox_Kernel32_DeleteFileW }, #endif { TUPLE("SetConsoleCtrlHandler"), NULL, (KUPTR)kwSandbox_Kernel32_SetConsoleCtrlHandler }, { TUPLE("LoadLibraryExA"), NULL, (KUPTR)kwSandbox_Kernel32_Native_LoadLibraryExA }, { TUPLE("WriteConsoleA"), NULL, (KUPTR)kwSandbox_Kernel32_WriteConsoleA }, { TUPLE("WriteConsoleW"), NULL, (KUPTR)kwSandbox_Kernel32_WriteConsoleW }, #ifdef WITH_HASH_MD5_CACHE { TUPLE("CryptCreateHash"), NULL, (KUPTR)kwSandbox_Advapi32_CryptCreateHash }, { TUPLE("CryptHashData"), NULL, (KUPTR)kwSandbox_Advapi32_CryptHashData }, { TUPLE("CryptGetHashParam"), NULL, (KUPTR)kwSandbox_Advapi32_CryptGetHashParam }, { TUPLE("CryptDestroyHash"), NULL, (KUPTR)kwSandbox_Advapi32_CryptDestroyHash }, #endif { TUPLE("RtlPcToFileHeader"), NULL, (KUPTR)kwSandbox_ntdll_RtlPcToFileHeader }, #if defined(KBUILD_OS_WINDOWS) && defined(KBUILD_ARCH_X86) { TUPLE("RtlUnwind"), NULL, (KUPTR)kwSandbox_ntdll_RtlUnwind }, #endif /* * MS Visual C++ CRTs. */ { TUPLE("exit"), NULL, (KUPTR)kwSandbox_msvcrt_exit }, { TUPLE("_exit"), NULL, (KUPTR)kwSandbox_msvcrt__exit }, { TUPLE("_cexit"), NULL, (KUPTR)kwSandbox_msvcrt__cexit }, { TUPLE("_c_exit"), NULL, (KUPTR)kwSandbox_msvcrt__c_exit }, { TUPLE("_amsg_exit"), NULL, (KUPTR)kwSandbox_msvcrt__amsg_exit }, { TUPLE("terminate"), NULL, (KUPTR)kwSandbox_msvcrt_terminate }, #if 0 /* used by mspdbXXX.dll */ { TUPLE("_beginthread"), NULL, (KUPTR)kwSandbox_msvcrt__beginthread }, { TUPLE("_beginthreadex"), NULL, (KUPTR)kwSandbox_msvcrt__beginthreadex }, #endif }; /** Number of entries in g_aSandboxNativeReplacements. */ KU32 const g_cSandboxNativeReplacements = K_ELEMENTS(g_aSandboxNativeReplacements); /** * Functions that needs replacing when queried by GetProcAddress. */ KWREPLACEMENTFUNCTION const g_aSandboxGetProcReplacements[] = { /* * Kernel32.dll and friends. */ { TUPLE("FlsAlloc"), NULL, (KUPTR)kwSandbox_Kernel32_FlsAlloc, K_TRUE /*fOnlyExe*/ }, { TUPLE("FlsFree"), NULL, (KUPTR)kwSandbox_Kernel32_FlsFree, K_TRUE /*fOnlyExe*/ }, { TUPLE("TlsAlloc"), NULL, (KUPTR)kwSandbox_Kernel32_TlsAlloc, K_TRUE /*fOnlyExe*/ }, { TUPLE("TlsFree"), NULL, (KUPTR)kwSandbox_Kernel32_TlsFree, K_TRUE /*fOnlyExe*/ }, }; /** Number of entries in g_aSandboxGetProcReplacements. */ KU32 const g_cSandboxGetProcReplacements = K_ELEMENTS(g_aSandboxGetProcReplacements); /** * Control handler. * * @returns TRUE if handled, FALSE if not. * @param dwCtrlType The signal. */ static BOOL WINAPI kwSandboxCtrlHandler(DWORD dwCtrlType) { switch (dwCtrlType) { case CTRL_C_EVENT: fprintf(stderr, "kWorker: Ctrl-C\n"); g_fCtrlC = K_TRUE; exit(9); break; case CTRL_BREAK_EVENT: fprintf(stderr, "kWorker: Ctrl-Break\n"); g_fCtrlC = K_TRUE; exit(10); break; case CTRL_CLOSE_EVENT: fprintf(stderr, "kWorker: console closed\n"); g_fCtrlC = K_TRUE; exit(11); break; case CTRL_LOGOFF_EVENT: fprintf(stderr, "kWorker: logoff event\n"); g_fCtrlC = K_TRUE; exit(11); break; case CTRL_SHUTDOWN_EVENT: fprintf(stderr, "kWorker: shutdown event\n"); g_fCtrlC = K_TRUE; exit(11); break; default: fprintf(stderr, "kwSandboxCtrlHandler: %#x\n", dwCtrlType); break; } return TRUE; } /** * Used by kwSandboxExec to reset the state of the module tree. * * This is done recursively. * * @param pMod The root of the tree to consider. */ static void kwSandboxResetModuleState(PKWMODULE pMod) { if ( !pMod->fNative && pMod->u.Manual.enmState != KWMODSTATE_NEEDS_BITS) { KSIZE iImp; pMod->u.Manual.enmState = KWMODSTATE_NEEDS_BITS; iImp = pMod->u.Manual.cImpMods; while (iImp-- > 0) kwSandboxResetModuleState(pMod->u.Manual.apImpMods[iImp]); } } static PPEB kwSandboxGetProcessEnvironmentBlock(void) { #if K_ARCH == K_ARCH_X86_32 return (PPEB)__readfsdword(0x030 /* offset of ProcessEnvironmentBlock in TEB */); #elif K_ARCH == K_ARCH_AMD64 return (PPEB)__readgsqword(0x060 /* offset of ProcessEnvironmentBlock in TEB */); #else # error "Port me!" #endif } /** * Enters the given handle into the handle table. * * @returns K_TRUE on success, K_FALSE on failure. * @param pSandbox The sandbox. * @param pHandle The handle. * @param hHandle The handle value to enter it under (for the * duplicate handle API). */ static KBOOL kwSandboxHandleTableEnter(PKWSANDBOX pSandbox, PKWHANDLE pHandle, HANDLE hHandle) { KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hHandle); kHlpAssertReturn(idxHandle < KW_HANDLE_MAX, K_FALSE); /* * Grow handle table. */ if (idxHandle >= pSandbox->cHandles) { void *pvNew; KU32 cHandles = pSandbox->cHandles ? pSandbox->cHandles * 2 : 32; while (cHandles <= idxHandle) cHandles *= 2; pvNew = kHlpRealloc(pSandbox->papHandles, cHandles * sizeof(pSandbox->papHandles[0])); if (!pvNew) { KW_LOG(("Out of memory growing handle table to %u handles\n", cHandles)); return K_FALSE; } pSandbox->papHandles = (PKWHANDLE *)pvNew; kHlpMemSet(&pSandbox->papHandles[pSandbox->cHandles], 0, (cHandles - pSandbox->cHandles) * sizeof(pSandbox->papHandles[0])); pSandbox->cHandles = cHandles; } /* * Check that the entry is unused then insert it. */ kHlpAssertReturn(pSandbox->papHandles[idxHandle] == NULL, K_FALSE); pSandbox->papHandles[idxHandle] = pHandle; pSandbox->cActiveHandles++; return K_TRUE; } /** * Creates a correctly quoted ANSI command line string from the given argv. * * @returns Pointer to the command line. * @param cArgs Number of arguments. * @param papszArgs The argument vector. * @param fWatcomBrainDamange Whether to apply watcom rules while quoting. * @param pcbCmdLine Where to return the command line length, * including one terminator. */ static char *kwSandboxInitCmdLineFromArgv(KU32 cArgs, const char **papszArgs, KBOOL fWatcomBrainDamange, KSIZE *pcbCmdLine) { KU32 i; KSIZE cbCmdLine; char *pszCmdLine; /* Make a copy of the argument vector that we'll be quoting. */ char **papszQuotedArgs = alloca(sizeof(papszArgs[0]) * (cArgs + 1)); kHlpMemCopy(papszQuotedArgs, papszArgs, sizeof(papszArgs[0]) * (cArgs + 1)); /* Quote the arguments that need it. */ quote_argv(cArgs, papszQuotedArgs, fWatcomBrainDamange, 0 /*leak*/); /* figure out cmd line length. */ cbCmdLine = 0; for (i = 0; i < cArgs; i++) cbCmdLine += kHlpStrLen(papszQuotedArgs[i]) + 1; *pcbCmdLine = cbCmdLine; pszCmdLine = (char *)kHlpAlloc(cbCmdLine + 1); if (pszCmdLine) { char *psz = kHlpStrPCopy(pszCmdLine, papszQuotedArgs[0]); if (papszQuotedArgs[0] != papszArgs[0]) free(papszQuotedArgs[0]); for (i = 1; i < cArgs; i++) { *psz++ = ' '; psz = kHlpStrPCopy(psz, papszQuotedArgs[i]); if (papszQuotedArgs[i] != papszArgs[i]) free(papszQuotedArgs[i]); } kHlpAssert((KSIZE)(&psz[1] - pszCmdLine) == cbCmdLine); *psz++ = '\0'; *psz++ = '\0'; } return pszCmdLine; } static int kwSandboxInit(PKWSANDBOX pSandbox, PKWTOOL pTool, KU32 cArgs, const char **papszArgs, KBOOL fWatcomBrainDamange, KU32 cEnvVars, const char **papszEnvVars, KBOOL fNoPchCaching) { PPEB pPeb = kwSandboxGetProcessEnvironmentBlock(); PMY_RTL_USER_PROCESS_PARAMETERS pProcParams = (PMY_RTL_USER_PROCESS_PARAMETERS)pPeb->ProcessParameters; wchar_t *pwcPool; KSIZE cbStrings; KSIZE cwc; KSIZE cbCmdLine; KU32 i; /* Simple stuff. */ pSandbox->rcExitCode = 256; pSandbox->pTool = pTool; pSandbox->idMainThread = GetCurrentThreadId(); pSandbox->pgmptr = (char *)pTool->pszPath; pSandbox->wpgmptr = (wchar_t *)pTool->pwszPath; #ifdef WITH_CONSOLE_OUTPUT_BUFFERING if (pSandbox->StdOut.fIsConsole) pSandbox->StdOut.u.Con.cwcBuf = 0; else pSandbox->StdOut.u.Fully.cchBuf = 0; if (pSandbox->StdErr.fIsConsole) pSandbox->StdErr.u.Con.cwcBuf = 0; else pSandbox->StdErr.u.Fully.cchBuf = 0; pSandbox->Combined.cwcBuf = 0; pSandbox->Combined.cFlushes = 0; #endif pSandbox->fNoPchCaching = fNoPchCaching; pSandbox->cArgs = cArgs; pSandbox->papszArgs = (char **)papszArgs; pSandbox->pszCmdLine = kwSandboxInitCmdLineFromArgv(cArgs, papszArgs, fWatcomBrainDamange, &cbCmdLine); if (!pSandbox->pszCmdLine) return KERR_NO_MEMORY; /* * Convert command line and argv to UTF-16. * We assume each ANSI char requires a surrogate pair in the UTF-16 variant. */ pSandbox->papwszArgs = (wchar_t **)kHlpAlloc(sizeof(wchar_t *) * (pSandbox->cArgs + 2) + cbCmdLine * 2 * sizeof(wchar_t)); if (!pSandbox->papwszArgs) return KERR_NO_MEMORY; pwcPool = (wchar_t *)&pSandbox->papwszArgs[pSandbox->cArgs + 2]; for (i = 0; i < cArgs; i++) { *pwcPool++ = pSandbox->papszArgs[i][-1]; /* flags */ pSandbox->papwszArgs[i] = pwcPool; pwcPool += kwStrToUtf16(pSandbox->papszArgs[i], pwcPool, (kHlpStrLen(pSandbox->papszArgs[i]) + 1) * 2); pwcPool++; } pSandbox->papwszArgs[pSandbox->cArgs + 0] = NULL; pSandbox->papwszArgs[pSandbox->cArgs + 1] = NULL; /* * Convert the commandline string to UTF-16, same pessimistic approach as above. */ cbStrings = (cbCmdLine + 1) * 2 * sizeof(wchar_t); pSandbox->pwszCmdLine = kHlpAlloc(cbStrings); if (!pSandbox->pwszCmdLine) return KERR_NO_MEMORY; cwc = kwStrToUtf16(pSandbox->pszCmdLine, pSandbox->pwszCmdLine, cbStrings / sizeof(wchar_t)); pSandbox->SavedCommandLine = pProcParams->CommandLine; pProcParams->CommandLine.Buffer = pSandbox->pwszCmdLine; pProcParams->CommandLine.Length = (USHORT)cwc * sizeof(wchar_t); /* * Setup the environment. */ if ( cEnvVars + 2 <= pSandbox->cEnvVarsAllocated || kwSandboxGrowEnv(pSandbox, cEnvVars + 2) == 0) { KU32 iDst = 0; for (i = 0; i < cEnvVars; i++) { const char *pszVar = papszEnvVars[i]; KSIZE cchVar = kHlpStrLen(pszVar); const char *pszEqual; if ( cchVar > 0 && (pszEqual = kHlpMemChr(pszVar, '=', cchVar)) != NULL) { char *pszCopy = kHlpDup(pszVar, cchVar + 1); wchar_t *pwszCopy = kwStrToUtf16AllocN(pszVar, cchVar + 1); if (pszCopy && pwszCopy) { pSandbox->papszEnvVars[iDst] = pszCopy; pSandbox->environ[iDst] = pszCopy; pSandbox->papwszEnvVars[iDst] = pwszCopy; pSandbox->wenviron[iDst] = pwszCopy; /* When we see the path, we must tell the system or native exec and module loading won't work . */ if ( (pszEqual - pszVar) == 4 && ( pszCopy[0] == 'P' || pszCopy[0] == 'p') && ( pszCopy[1] == 'A' || pszCopy[1] == 'a') && ( pszCopy[2] == 'T' || pszCopy[2] == 't') && ( pszCopy[3] == 'H' || pszCopy[3] == 'h')) if (!SetEnvironmentVariableW(L"Path", &pwszCopy[5])) kwErrPrintf("kwSandboxInit: SetEnvironmentVariableW(Path,) failed: %u\n", GetLastError()); iDst++; } else { kHlpFree(pszCopy); kHlpFree(pwszCopy); return kwErrPrintfRc(KERR_NO_MEMORY, "Out of memory setting up env vars!\n"); } } else kwErrPrintf("kwSandboxInit: Skipping bad env var '%s'\n", pszVar); } pSandbox->papszEnvVars[iDst] = NULL; pSandbox->environ[iDst] = NULL; pSandbox->papwszEnvVars[iDst] = NULL; pSandbox->wenviron[iDst] = NULL; } else return kwErrPrintfRc(KERR_NO_MEMORY, "Error setting up environment variables: kwSandboxGrowEnv failed\n"); /* * Invalidate the volatile parts of cache (kBuild output directory, * temporary directory, whatever). */ kFsCacheInvalidateCustomBoth(g_pFsCache); #ifdef WITH_HISTORY /* * Record command line in debug history. */ kHlpFree(g_apszHistory[g_iHistoryNext]); g_apszHistory[g_iHistoryNext] = kHlpStrDup(pSandbox->pszCmdLine); g_iHistoryNext = (g_iHistoryNext + 1) % K_ELEMENTS(g_apszHistory); #endif return 0; } /** * Does sandbox cleanup between jobs. * * We postpone whatever isn't externally visible (i.e. files) and doesn't * influence the result, so that kmk can get on with things ASAP. * * @param pSandbox The sandbox. */ static void kwSandboxCleanupLate(PKWSANDBOX pSandbox) { PROCESS_MEMORY_COUNTERS MemInfo; PKWVIRTALLOC pTracker; PKWHEAP pHeap; PKWLOCALSTORAGE pLocalStorage; #ifdef WITH_HASH_MD5_CACHE PKWHASHMD5 pHash; #endif #ifdef WITH_TEMP_MEMORY_FILES PKWFSTEMPFILE pTempFile; #endif PKWEXITCALLACK pExitCallback; /* * First stuff that may cause code to run. */ /* Do exit callback first. */ pExitCallback = g_Sandbox.pExitCallbackHead; g_Sandbox.pExitCallbackHead = NULL; while (pExitCallback) { PKWEXITCALLACK pNext = pExitCallback->pNext; KW_LOG(("kwSandboxCleanupLate: calling %p %sexit handler\n", pExitCallback->pfnCallback, pExitCallback->fAtExit ? "at" : "_on")); __try { pExitCallback->pfnCallback(); } __except (EXCEPTION_EXECUTE_HANDLER) { KW_LOG(("kwSandboxCleanupLate: %sexit handler %p threw an exception!\n", pExitCallback->fAtExit ? "at" : "_on", pExitCallback->pfnCallback)); kHlpAssertFailed(); } kHlpFree(pExitCallback); pExitCallback = pNext; } /* Free left behind FlsAlloc leaks. */ pLocalStorage = g_Sandbox.pFlsAllocHead; g_Sandbox.pFlsAllocHead = NULL; while (pLocalStorage) { PKWLOCALSTORAGE pNext = pLocalStorage->pNext; KW_LOG(("Freeing leaked FlsAlloc index %#x\n", pLocalStorage->idx)); FlsFree(pLocalStorage->idx); kHlpFree(pLocalStorage); pLocalStorage = pNext; } /* Free left behind TlsAlloc leaks. */ pLocalStorage = g_Sandbox.pTlsAllocHead; g_Sandbox.pTlsAllocHead = NULL; while (pLocalStorage) { PKWLOCALSTORAGE pNext = pLocalStorage->pNext; KW_LOG(("Freeing leaked TlsAlloc index %#x\n", pLocalStorage->idx)); TlsFree(pLocalStorage->idx); kHlpFree(pLocalStorage); pLocalStorage = pNext; } /* * Then free resources associated with the sandbox run. */ /* Open handles, except fixed handles (stdout and stderr). */ if (pSandbox->cActiveHandles > pSandbox->cFixedHandles) { KU32 idxHandle = pSandbox->cHandles; while (idxHandle-- > 0) if (pSandbox->papHandles[idxHandle] == NULL) { /* likely */ } else { PKWHANDLE pHandle = pSandbox->papHandles[idxHandle]; if ( pHandle->enmType != KWHANDLETYPE_OUTPUT_BUF || idxHandle != KW_HANDLE_TO_INDEX(pHandle->hHandle) ) { pSandbox->papHandles[idxHandle] = NULL; pSandbox->cLeakedHandles++; switch (pHandle->enmType) { case KWHANDLETYPE_FSOBJ_READ_CACHE: KWFS_LOG(("Closing leaked read cache handle: %#x/%p cRefs=%d\n", idxHandle, pHandle->hHandle, pHandle->cRefs)); break; case KWHANDLETYPE_FSOBJ_READ_CACHE_MAPPING: KWFS_LOG(("Closing leaked read mapping handle: %#x/%p cRefs=%d\n", idxHandle, pHandle->hHandle, pHandle->cRefs)); break; case KWHANDLETYPE_OUTPUT_BUF: KWFS_LOG(("Closing leaked output buf handle: %#x/%p cRefs=%d\n", idxHandle, pHandle->hHandle, pHandle->cRefs)); break; case KWHANDLETYPE_TEMP_FILE: KWFS_LOG(("Closing leaked temp file handle: %#x/%p cRefs=%d\n", idxHandle, pHandle->hHandle, pHandle->cRefs)); pHandle->u.pTempFile->cActiveHandles--; break; case KWHANDLETYPE_TEMP_FILE_MAPPING: KWFS_LOG(("Closing leaked temp mapping handle: %#x/%p cRefs=%d\n", idxHandle, pHandle->hHandle, pHandle->cRefs)); pHandle->u.pTempFile->cActiveHandles--; break; default: kHlpAssertFailed(); } if (--pHandle->cRefs == 0) kHlpFree(pHandle); if (--pSandbox->cActiveHandles == pSandbox->cFixedHandles) break; } } kHlpAssert(pSandbox->cActiveHandles == pSandbox->cFixedHandles); } /* Reset memory mappings - This assumes none of the DLLs keeps any of our mappings open! */ g_Sandbox.cMemMappings = 0; #ifdef WITH_TEMP_MEMORY_FILES /* The temporary files aren't externally visible, they're all in memory. */ pTempFile = pSandbox->pTempFileHead; pSandbox->pTempFileHead = NULL; while (pTempFile) { PKWFSTEMPFILE pNext = pTempFile->pNext; KU32 iSeg = pTempFile->cSegs; while (iSeg-- > 0) kHlpPageFree(pTempFile->paSegs[iSeg].pbData, pTempFile->paSegs[iSeg].cbDataAlloc); kHlpFree(pTempFile->paSegs); pTempFile->pNext = NULL; kHlpFree(pTempFile); pTempFile = pNext; } #endif /* Free left behind HeapCreate leaks. */ pHeap = g_Sandbox.pHeapHead; g_Sandbox.pHeapHead = NULL; while (pHeap != NULL) { PKWHEAP pNext = pHeap->pNext; KW_LOG(("Freeing HeapCreate leak %p\n", pHeap->hHeap)); HeapDestroy(pHeap->hHeap); pHeap = pNext; } /* Free left behind VirtualAlloc leaks. */ pTracker = g_Sandbox.pVirtualAllocHead; g_Sandbox.pVirtualAllocHead = NULL; while (pTracker) { PKWVIRTALLOC pNext = pTracker->pNext; KW_LOG(("Freeing VirtualFree leak %p LB %#x\n", pTracker->pvAlloc, pTracker->cbAlloc)); #ifdef WITH_FIXED_VIRTUAL_ALLOCS if (pTracker->idxPreAllocated != KU32_MAX) kwSandboxResetFixedAllocation(pTracker->idxPreAllocated); else #endif VirtualFree(pTracker->pvAlloc, 0, MEM_RELEASE); kHlpFree(pTracker); pTracker = pNext; } /* Free the environment. */ if (pSandbox->papszEnvVars) { KU32 i; for (i = 0; pSandbox->papszEnvVars[i]; i++) kHlpFree(pSandbox->papszEnvVars[i]); pSandbox->environ[0] = NULL; pSandbox->papszEnvVars[0] = NULL; for (i = 0; pSandbox->papwszEnvVars[i]; i++) kHlpFree(pSandbox->papwszEnvVars[i]); pSandbox->wenviron[0] = NULL; pSandbox->papwszEnvVars[0] = NULL; } #ifdef WITH_HASH_MD5_CACHE /* * Hash handles. */ pHash = pSandbox->pHashHead; pSandbox->pHashHead = NULL; while (pHash) { PKWHASHMD5 pNext = pHash->pNext; KWCRYPT_LOG(("Freeing leaked hash instance %#p\n", pHash)); kHlpFree(pHash); pHash = pNext; } #endif /* * Check the memory usage. If it's getting high, trigger a respawn * after the next job. */ MemInfo.WorkingSetSize = 0; if (GetProcessMemoryInfo(GetCurrentProcess(), &MemInfo, sizeof(MemInfo))) { /* The first time thru, we figure out approximately when to restart based on installed RAM and CPU threads. */ static KU64 s_cbMaxWorkingSet = 0; if (s_cbMaxWorkingSet != 0) { /* likely */ } else { SYSTEM_INFO SysInfo; MEMORYSTATUSEX GlobalMemInfo; const char *pszValue; /* Calculate a reasonable estimate. */ kHlpMemSet(&SysInfo, 0, sizeof(SysInfo)); GetNativeSystemInfo(&SysInfo); kHlpMemSet(&GlobalMemInfo, 0, sizeof(GlobalMemInfo)); GlobalMemInfo.dwLength = sizeof(GlobalMemInfo); if (!GlobalMemoryStatusEx(&GlobalMemInfo)) #if K_ARCH_BITS >= 64 GlobalMemInfo.ullTotalPhys = KU64_C(0x000200000000); /* 8GB */ #else GlobalMemInfo.ullTotalPhys = KU64_C(0x000080000000); /* 2GB */ #endif s_cbMaxWorkingSet = GlobalMemInfo.ullTotalPhys / (K_MAX(SysInfo.dwNumberOfProcessors, 1) * 4); KW_LOG(("Raw estimate of s_cbMaxWorkingSet=%" KU64_PRI "\n", s_cbMaxWorkingSet)); /* User limit. */ pszValue = getenv("KWORKER_MEMORY_LIMIT"); if (pszValue != NULL) { char *pszNext; unsigned long ulValue = strtol(pszValue, &pszNext, 0); if (*pszNext == '\0' || *pszNext == 'M') s_cbMaxWorkingSet = ulValue * (KU64)1048576; else if (*pszNext == 'K') s_cbMaxWorkingSet = ulValue * (KU64)1024; else if (*pszNext == 'G') s_cbMaxWorkingSet = ulValue * (KU64)1073741824; else kwErrPrintf("Unable to grok KWORKER_MEMORY_LIMIT: %s\n", pszValue); KW_LOG(("User s_cbMaxWorkingSet=%" KU64_PRI "\n", s_cbMaxWorkingSet)); } /* Clamp it a little. */ if (s_cbMaxWorkingSet < 168*1024*1024) s_cbMaxWorkingSet = 168*1024*1024; #if K_ARCH_BITS < 64 else s_cbMaxWorkingSet = K_MIN(s_cbMaxWorkingSet, SysInfo.dwProcessorType != PROCESSOR_ARCHITECTURE_AMD64 ? 512*1024*1024 /* Only got 2 or 3 GB VA */ : 1536*1024*1024 /* got 4GB VA */); #endif if (s_cbMaxWorkingSet > GlobalMemInfo.ullTotalPhys) s_cbMaxWorkingSet = GlobalMemInfo.ullTotalPhys; KW_LOG(("Final s_cbMaxWorkingSet=%" KU64_PRI "\n", s_cbMaxWorkingSet)); } /* Finally the check. */ if (MemInfo.WorkingSetSize >= s_cbMaxWorkingSet) { KW_LOG(("WorkingSetSize = %#x - > restart next time.\n", MemInfo.WorkingSetSize)); g_fRestart = K_TRUE; } } /* * The CRT has a max of 8192 handles, so we better restart after a while if * someone is leaking handles or we risk running out of descriptors. * * Note! We only detect leaks for handles we intercept. In the case of CL.EXE * doing _dup2(1, 2) (stderr ==> stdout), there isn't actually a leak. */ if (pSandbox->cLeakedHandles > 6000) { KW_LOG(("LeakedHandles = %#x - > restart next time.\n", pSandbox->cLeakedHandles)); g_fRestart = K_TRUE; } } /** * Does essential cleanups and restoring, anything externally visible. * * All cleanups that aren't externally visible are postponed till after we've * informed kmk of the result, so it can be done in the dead time between jobs. * * @param pSandbox The sandbox. */ static void kwSandboxCleanup(PKWSANDBOX pSandbox) { /* * Restore the parent command line string. */ PPEB pPeb = kwSandboxGetProcessEnvironmentBlock(); PMY_RTL_USER_PROCESS_PARAMETERS pProcParams = (PMY_RTL_USER_PROCESS_PARAMETERS)pPeb->ProcessParameters; pProcParams->CommandLine = pSandbox->SavedCommandLine; pProcParams->StandardOutput = pSandbox->StdOut.hOutput; pProcParams->StandardError = pSandbox->StdErr.hOutput; /* CL.EXE messes with this one. */ } static int kwSandboxExec(PKWSANDBOX pSandbox, PKWTOOL pTool, KU32 cArgs, const char **papszArgs, KBOOL fWatcomBrainDamange, KU32 cEnvVars, const char **papszEnvVars, KBOOL fNoPchCaching) { int rcExit = 42; int rc; /* * Initialize the sandbox environment. */ rc = kwSandboxInit(&g_Sandbox, pTool, cArgs, papszArgs, fWatcomBrainDamange, cEnvVars, papszEnvVars, fNoPchCaching); if (rc == 0) { /* * Do module initialization. */ kwSandboxResetModuleState(pTool->u.Sandboxed.pExe); rc = kwLdrModuleInitTree(pTool->u.Sandboxed.pExe); if (rc == 0) { /* * Call the main function. */ #if K_ARCH == K_ARCH_AMD64 int (*pfnWin64Entrypoint)(void *pvPeb, void *, void *, void *); #elif K_ARCH == K_ARCH_X86_32 int (__cdecl *pfnWin32Entrypoint)(void *pvPeb); #else # error "Port me!" #endif /* Save the NT TIB first (should do that here, not in some other function). */ PNT_TIB pTib = (PNT_TIB)NtCurrentTeb(); pSandbox->TibMainThread = *pTib; /* Make the call in a guarded fashion. */ #if K_ARCH == K_ARCH_AMD64 /* AMD64 */ *(KUPTR *)&pfnWin64Entrypoint = pTool->u.Sandboxed.uMainAddr; __try { pSandbox->pOutXcptListHead = pTib->ExceptionList; if (setjmp(pSandbox->JmpBuf) == 0) { *(KU64*)(pSandbox->JmpBuf) = 0; /** @todo find other way to prevent longjmp from doing unwind! */ pSandbox->fRunning = K_TRUE; rcExit = pfnWin64Entrypoint(kwSandboxGetProcessEnvironmentBlock(), NULL, NULL, NULL); pSandbox->fRunning = K_FALSE; } else rcExit = pSandbox->rcExitCode; } #elif K_ARCH == K_ARCH_X86_32 /* x86 (see _tmainCRTStartup) */ *(KUPTR *)&pfnWin32Entrypoint = pTool->u.Sandboxed.uMainAddr; __try { pSandbox->pOutXcptListHead = pTib->ExceptionList; if (setjmp(pSandbox->JmpBuf) == 0) { //*(KU64*)(pSandbox->JmpBuf) = 0; /** @todo find other way to prevent longjmp from doing unwind! */ pSandbox->fRunning = K_TRUE; rcExit = pfnWin32Entrypoint(kwSandboxGetProcessEnvironmentBlock()); pSandbox->fRunning = K_FALSE; } else rcExit = pSandbox->rcExitCode; } #endif __except (EXCEPTION_EXECUTE_HANDLER) { kwErrPrintf("Caught exception %#x!\n", GetExceptionCode()); #ifdef WITH_HISTORY { KU32 cPrinted = 0; while (cPrinted++ < 5) { KU32 idx = (g_iHistoryNext + K_ELEMENTS(g_apszHistory) - cPrinted) % K_ELEMENTS(g_apszHistory); if (g_apszHistory[idx]) kwErrPrintf("cmd[%d]: %s\n", 1 - cPrinted, g_apszHistory[idx]); } } #endif rcExit = 512; } pSandbox->fRunning = K_FALSE; /* Now, restore the NT TIB. */ *pTib = pSandbox->TibMainThread; } else rcExit = 42 + 4; /* * Flush and clean up the essential bits only, postpone whatever we * can till after we've replied to kmk. */ #ifdef WITH_CONSOLE_OUTPUT_BUFFERING kwSandboxConsoleFlushAll(&g_Sandbox); #endif kwSandboxCleanup(&g_Sandbox); } else rcExit = 42 + 3; return rcExit; } /** * Does the post command part of a job (optional). * * @returns The exit code of the job. * @param cPostCmdArgs Number of post command arguments (includes cmd). * @param papszPostCmdArgs The post command and its argument. */ static int kSubmitHandleJobPostCmd(KU32 cPostCmdArgs, const char **papszPostCmdArgs) { const char *pszCmd = papszPostCmdArgs[0]; /* Allow the kmk builtin prefix. */ static const char s_szKmkBuiltinPrefix[] = "kmk_builtin_"; if (kHlpStrNComp(pszCmd, s_szKmkBuiltinPrefix, sizeof(s_szKmkBuiltinPrefix) - 1) == 0) pszCmd += sizeof(s_szKmkBuiltinPrefix) - 1; /* Command switch. */ if (kHlpStrComp(pszCmd, "kDepObj") == 0) return kmk_builtin_kDepObj(cPostCmdArgs, (char **)papszPostCmdArgs, NULL); return kwErrPrintfRc(42 + 5 , "Unknown post command: '%s'\n", pszCmd); } /** * Part 2 of the "JOB" command handler. * * @returns The exit code of the job. * @param pszExecutable The executable to execute. * @param pszCwd The current working directory of the job. * @param cArgs The number of arguments. * @param papszArgs The argument vector. * @param fWatcomBrainDamange Whether to apply watcom rules while quoting. * @param cEnvVars The number of environment variables. * @param papszEnvVars The environment vector. * @param fNoPchCaching Whether to disable precompiled header file * caching. Avoid trouble when creating them. * @param cPostCmdArgs Number of post command arguments (includes cmd). * @param papszPostCmdArgs The post command and its argument. */ static int kSubmitHandleJobUnpacked(const char *pszExecutable, const char *pszCwd, KU32 cArgs, const char **papszArgs, KBOOL fWatcomBrainDamange, KU32 cEnvVars, const char **papszEnvVars, KBOOL fNoPchCaching, KU32 cPostCmdArgs, const char **papszPostCmdArgs) { int rcExit; PKWTOOL pTool; KW_LOG(("\n\nkSubmitHandleJobUnpacked: '%s' in '%s' cArgs=%u cEnvVars=%u cPostCmdArgs=%u\n", pszExecutable, pszCwd, cArgs, cEnvVars, cPostCmdArgs)); #ifdef KW_LOG_ENABLED { KU32 i; for (i = 0; i < cArgs; i++) KW_LOG((" papszArgs[%u]=%s\n", i, papszArgs[i])); for (i = 0; i < cPostCmdArgs; i++) KW_LOG((" papszPostCmdArgs[%u]=%s\n", i, papszPostCmdArgs[i])); } #endif g_cJobs++; /* * Lookup the tool. */ pTool = kwToolLookup(pszExecutable, cEnvVars, papszEnvVars); if (pTool) { /* * Change the directory if we're going to execute the job inside * this process. Then invoke the tool type specific handler. */ switch (pTool->enmType) { case KWTOOLTYPE_SANDBOXED: case KWTOOLTYPE_WATCOM: { /* Change dir. */ KFSLOOKUPERROR enmError; PKFSOBJ pNewCurDir = kFsCacheLookupA(g_pFsCache, pszCwd, &enmError); if ( pNewCurDir == g_pCurDirObj && pNewCurDir->bObjType == KFSOBJ_TYPE_DIR) kFsCacheObjRelease(g_pFsCache, pNewCurDir); else if (SetCurrentDirectoryA(pszCwd)) { kFsCacheObjRelease(g_pFsCache, g_pCurDirObj); g_pCurDirObj = pNewCurDir; } else { kwErrPrintf("SetCurrentDirectory failed with %u on '%s'\n", GetLastError(), pszCwd); kFsCacheObjRelease(g_pFsCache, pNewCurDir); rcExit = 42 + 1; break; } /* Call specific handler. */ if (pTool->enmType == KWTOOLTYPE_SANDBOXED) { KW_LOG(("Sandboxing tool %s\n", pTool->pszPath)); rcExit = kwSandboxExec(&g_Sandbox, pTool, cArgs, papszArgs, fWatcomBrainDamange, cEnvVars, papszEnvVars, fNoPchCaching); } else { kwErrPrintf("TODO: Watcom style tool %s\n", pTool->pszPath); rcExit = 42 + 2; } break; } case KWTOOLTYPE_EXEC: kwErrPrintf("TODO: Direct exec tool %s\n", pTool->pszPath); rcExit = 42 + 2; break; default: kHlpAssertFailed(); kwErrPrintf("Internal tool type corruption!!\n"); rcExit = 42 + 2; g_fRestart = K_TRUE; break; } /* * Do the post command, if present. */ if (cPostCmdArgs && rcExit == 0) rcExit = kSubmitHandleJobPostCmd(cPostCmdArgs, papszPostCmdArgs); } else rcExit = 42 + 1; return rcExit; } /** * Handles a "JOB" command. * * @returns The exit code of the job. * @param pszMsg Points to the "JOB" command part of the message. * @param cbMsg Number of message bytes at @a pszMsg. There are * 4 more zero bytes after the message body to * simplify parsing. */ static int kSubmitHandleJob(const char *pszMsg, KSIZE cbMsg) { int rcExit = 42; /* * Unpack the message. */ const char *pszExecutable; KSIZE cbTmp; pszMsg += sizeof("JOB"); cbMsg -= sizeof("JOB"); /* Executable name. */ pszExecutable = pszMsg; cbTmp = kHlpStrLen(pszMsg) + 1; pszMsg += cbTmp; if ( cbTmp < cbMsg && cbTmp > 2) { const char *pszCwd; cbMsg -= cbTmp; /* Current working directory. */ pszCwd = pszMsg; cbTmp = kHlpStrLen(pszMsg) + 1; pszMsg += cbTmp; if ( cbTmp + sizeof(KU32) < cbMsg && cbTmp >= 2) { KU32 cArgs; cbMsg -= cbTmp; /* Argument count. */ kHlpMemCopy(&cArgs, pszMsg, sizeof(cArgs)); pszMsg += sizeof(cArgs); cbMsg -= sizeof(cArgs); if (cArgs > 0 && cArgs < 4096) { /* The argument vector. */ char const **papszArgs = kHlpAlloc((cArgs + 1) * sizeof(papszArgs[0])); if (papszArgs) { KU32 i; for (i = 0; i < cArgs; i++) { papszArgs[i] = pszMsg + 1; /* First byte is expansion flags for MSC & EMX. */ cbTmp = 1 + kHlpStrLen(pszMsg + 1) + 1; pszMsg += cbTmp; if (cbTmp < cbMsg) cbMsg -= cbTmp; else { cbMsg = 0; break; } } papszArgs[cArgs] = 0; /* Environment variable count. */ if (cbMsg > sizeof(KU32)) { KU32 cEnvVars; kHlpMemCopy(&cEnvVars, pszMsg, sizeof(cEnvVars)); pszMsg += sizeof(cEnvVars); cbMsg -= sizeof(cEnvVars); if (cEnvVars >= 0 && cEnvVars < 4096) { /* The argument vector. */ char const **papszEnvVars = kHlpAlloc((cEnvVars + 1) * sizeof(papszEnvVars[0])); if (papszEnvVars) { for (i = 0; i < cEnvVars; i++) { papszEnvVars[i] = pszMsg; cbTmp = kHlpStrLen(pszMsg) + 1; pszMsg += cbTmp; if (cbTmp < cbMsg) cbMsg -= cbTmp; else { cbMsg = 0; break; } } papszEnvVars[cEnvVars] = 0; /* Flags (currently just watcom argument brain damage and no precompiled header caching). */ if (cbMsg >= sizeof(KU8) * 2) { KBOOL fWatcomBrainDamange = *pszMsg++; KBOOL fNoPchCaching = *pszMsg++; cbMsg -= 2; /* Post command argument count (can be zero). */ if (cbMsg >= sizeof(KU32)) { KU32 cPostCmdArgs; kHlpMemCopy(&cPostCmdArgs, pszMsg, sizeof(cPostCmdArgs)); pszMsg += sizeof(cPostCmdArgs); cbMsg -= sizeof(cPostCmdArgs); if (cPostCmdArgs >= 0 && cPostCmdArgs < 32) { char const *apszPostCmdArgs[32+1]; for (i = 0; i < cPostCmdArgs; i++) { apszPostCmdArgs[i] = pszMsg; cbTmp = kHlpStrLen(pszMsg) + 1; pszMsg += cbTmp; if ( cbTmp < cbMsg || (cbTmp == cbMsg && i + 1 == cPostCmdArgs)) cbMsg -= cbTmp; else { cbMsg = KSIZE_MAX; break; } } if (cbMsg == 0) { apszPostCmdArgs[cPostCmdArgs] = NULL; /* * The next step. */ rcExit = kSubmitHandleJobUnpacked(pszExecutable, pszCwd, cArgs, papszArgs, fWatcomBrainDamange, cEnvVars, papszEnvVars, fNoPchCaching, cPostCmdArgs, apszPostCmdArgs); } else if (cbMsg == KSIZE_MAX) kwErrPrintf("Detected bogus message unpacking post command and its arguments!\n"); else kwErrPrintf("Message has %u bytes unknown trailing bytes\n", cbMsg); } else kwErrPrintf("Bogus post command argument count: %u %#x\n", cPostCmdArgs, cPostCmdArgs); } else kwErrPrintf("Detected bogus message looking for the post command argument count!\n"); } else kwErrPrintf("Detected bogus message unpacking environment variables!\n"); kHlpFree((void *)papszEnvVars); } else kwErrPrintf("Error allocating papszEnvVars for %u variables\n", cEnvVars); } else kwErrPrintf("Bogus environment variable count: %u (%#x)\n", cEnvVars, cEnvVars); } else kwErrPrintf("Detected bogus message unpacking arguments and environment variable count!\n"); kHlpFree((void *)papszArgs); } else kwErrPrintf("Error allocating argv for %u arguments\n", cArgs); } else kwErrPrintf("Bogus argument count: %u (%#x)\n", cArgs, cArgs); } else kwErrPrintf("Detected bogus message unpacking CWD path and argument count!\n"); } else kwErrPrintf("Detected bogus message unpacking executable path!\n"); return rcExit; } /** * Wrapper around WriteFile / write that writes the whole @a cbToWrite. * * @retval 0 on success. * @retval -1 on error (fully bitched). * * @param hPipe The pipe handle. * @param pvBuf The buffer to write out out. * @param cbToWrite The number of bytes to write. */ static int kSubmitWriteIt(HANDLE hPipe, const void *pvBuf, KU32 cbToWrite) { KU8 const *pbBuf = (KU8 const *)pvBuf; KU32 cbLeft = cbToWrite; for (;;) { DWORD cbActuallyWritten = 0; if (WriteFile(hPipe, pbBuf, cbLeft, &cbActuallyWritten, NULL /*pOverlapped*/)) { cbLeft -= cbActuallyWritten; if (!cbLeft) return 0; pbBuf += cbActuallyWritten; } else { DWORD dwErr = GetLastError(); if (cbLeft == cbToWrite) kwErrPrintf("WriteFile failed: %u\n", dwErr); else kwErrPrintf("WriteFile failed %u byte(s) in: %u\n", cbToWrite - cbLeft, dwErr); return -1; } } } /** * Wrapper around ReadFile / read that reads the whole @a cbToRead. * * @retval 0 on success. * @retval 1 on shut down (fShutdownOkay must be K_TRUE). * @retval -1 on error (fully bitched). * @param hPipe The pipe handle. * @param pvBuf The buffer to read into. * @param cbToRead The number of bytes to read. * @param fShutdownOkay Whether connection shutdown while reading the * first byte is okay or not. */ static int kSubmitReadIt(HANDLE hPipe, void *pvBuf, KU32 cbToRead, KBOOL fMayShutdown) { KU8 *pbBuf = (KU8 *)pvBuf; KU32 cbLeft = cbToRead; for (;;) { DWORD cbActuallyRead = 0; if (ReadFile(hPipe, pbBuf, cbLeft, &cbActuallyRead, NULL /*pOverlapped*/)) { cbLeft -= cbActuallyRead; if (!cbLeft) return 0; pbBuf += cbActuallyRead; } else { DWORD dwErr = GetLastError(); if (cbLeft == cbToRead) { if ( fMayShutdown && dwErr == ERROR_BROKEN_PIPE) return 1; kwErrPrintf("ReadFile failed: %u\n", dwErr); } else kwErrPrintf("ReadFile failed %u byte(s) in: %u\n", cbToRead - cbLeft, dwErr); return -1; } } } /** * Decimal formatting of a 64-bit unsigned value into a large enough buffer. * * @returns pszBuf * @param pszBuf The buffer (sufficiently large). * @param uValue The value. */ static const char *kwFmtU64(char *pszBuf, KU64 uValue) { char szTmp[64]; char *psz = &szTmp[63]; int cch = 4; *psz-- = '\0'; do { if (--cch == 0) { *psz-- = ' '; cch = 3; } *psz-- = (uValue % 10) + '0'; uValue /= 10; } while (uValue != 0); return strcpy(pszBuf, psz + 1); } /** * Prints statistics. */ static void kwPrintStats(void) { PROCESS_MEMORY_COUNTERS_EX MemInfo; MEMORYSTATUSEX MemStatus; IO_COUNTERS IoCounters; DWORD cHandles; KSIZE cMisses; char szBuf[16*1024]; int off = 0; char szPrf[24]; char sz1[64]; char sz2[64]; char sz3[64]; char sz4[64]; extern size_t maybe_con_fwrite(void const *pvBuf, size_t cbUnit, size_t cUnits, FILE *pFile); sprintf(szPrf, "%5d/%u:", getpid(), K_ARCH_BITS); szBuf[off++] = '\n'; off += sprintf(&szBuf[off], "%s %14s jobs, %s tools, %s modules, %s non-native ones\n", szPrf, kwFmtU64(sz1, g_cJobs), kwFmtU64(sz2, g_cTools), kwFmtU64(sz3, g_cModules), kwFmtU64(sz4, g_cNonNativeModules)); off += sprintf(&szBuf[off], "%s %14s bytes in %s read-cached files, avg %s bytes\n", szPrf, kwFmtU64(sz1, g_cbReadCachedFiles), kwFmtU64(sz2, g_cReadCachedFiles), kwFmtU64(sz3, g_cbReadCachedFiles / K_MAX(g_cReadCachedFiles, 1))); off += sprintf(&szBuf[off], "%s %14s bytes read in %s calls\n", szPrf, kwFmtU64(sz1, g_cbReadFileTotal), kwFmtU64(sz2, g_cReadFileCalls)); off += sprintf(&szBuf[off], "%s %14s bytes read (%u%%) in %s calls (%u%%) from read cached files\n", szPrf, kwFmtU64(sz1, g_cbReadFileFromReadCached), (unsigned)(g_cbReadFileFromReadCached * (KU64)100 / g_cbReadFileTotal), kwFmtU64(sz2, g_cReadFileFromReadCached), (unsigned)(g_cReadFileFromReadCached * (KU64)100 / g_cReadFileCalls)); off += sprintf(&szBuf[off], "%s %14s bytes read (%u%%) in %s calls (%u%%) from in-memory temporary files\n", szPrf, kwFmtU64(sz1, g_cbReadFileFromInMemTemp), (unsigned)(g_cbReadFileFromInMemTemp * (KU64)100 / K_MAX(g_cbReadFileTotal, 1)), kwFmtU64(sz2, g_cReadFileFromInMemTemp), (unsigned)(g_cReadFileFromInMemTemp * (KU64)100 / K_MAX(g_cReadFileCalls, 1))); off += sprintf(&szBuf[off], "%s %14s bytes written in %s calls\n", szPrf, kwFmtU64(sz1, g_cbWriteFileTotal), kwFmtU64(sz2, g_cWriteFileCalls)); off += sprintf(&szBuf[off], "%s %14s bytes written (%u%%) in %s calls (%u%%) to in-memory temporary files\n", szPrf, kwFmtU64(sz1, g_cbWriteFileToInMemTemp), (unsigned)(g_cbWriteFileToInMemTemp * (KU64)100 / K_MAX(g_cbWriteFileTotal, 1)), kwFmtU64(sz2, g_cWriteFileToInMemTemp), (unsigned)(g_cWriteFileToInMemTemp * (KU64)100 / K_MAX(g_cWriteFileCalls, 1))); off += sprintf(&szBuf[off], "%s %14s bytes for the cache\n", szPrf, kwFmtU64(sz1, g_pFsCache->cbObjects + g_pFsCache->cbAnsiPaths + g_pFsCache->cbUtf16Paths + sizeof(*g_pFsCache))); off += sprintf(&szBuf[off], "%s %14s objects, taking up %s bytes, avg %s bytes\n", szPrf, kwFmtU64(sz1, g_pFsCache->cObjects), kwFmtU64(sz2, g_pFsCache->cbObjects), kwFmtU64(sz3, g_pFsCache->cbObjects / g_pFsCache->cObjects)); off += sprintf(&szBuf[off], "%s %14s A path hashes, taking up %s bytes, avg %s bytes, %s collision\n", szPrf, kwFmtU64(sz1, g_pFsCache->cAnsiPaths), kwFmtU64(sz2, g_pFsCache->cbAnsiPaths), kwFmtU64(sz3, g_pFsCache->cbAnsiPaths / K_MAX(g_pFsCache->cAnsiPaths, 1)), kwFmtU64(sz4, g_pFsCache->cAnsiPathCollisions)); #ifdef KFSCACHE_CFG_UTF16 off += sprintf(&szBuf[off], "%s %14s W path hashes, taking up %s bytes, avg %s bytes, %s collisions\n", szPrf, kwFmtU64(sz1, g_pFsCache->cUtf16Paths), kwFmtU64(sz2, g_pFsCache->cbUtf16Paths), kwFmtU64(sz3, g_pFsCache->cbUtf16Paths / K_MAX(g_pFsCache->cUtf16Paths, 1)), kwFmtU64(sz4, g_pFsCache->cUtf16PathCollisions)); #endif off += sprintf(&szBuf[off], "%s %14s child hash tables, total of %s entries, %s children inserted, %s collisions\n", szPrf, kwFmtU64(sz1, g_pFsCache->cChildHashTabs), kwFmtU64(sz2, g_pFsCache->cChildHashEntriesTotal), kwFmtU64(sz3, g_pFsCache->cChildHashed), kwFmtU64(sz4, g_pFsCache->cChildHashCollisions)); cMisses = g_pFsCache->cLookups - g_pFsCache->cPathHashHits - g_pFsCache->cWalkHits; off += sprintf(&szBuf[off], "%s %14s lookups: %s (%u%%) path hash hits, %s (%u%%) walks hits, %s (%u%%) misses\n", szPrf, kwFmtU64(sz1, g_pFsCache->cLookups), kwFmtU64(sz2, g_pFsCache->cPathHashHits), (unsigned)(g_pFsCache->cPathHashHits * (KU64)100 / K_MAX(g_pFsCache->cLookups, 1)), kwFmtU64(sz3, g_pFsCache->cWalkHits), (unsigned)(g_pFsCache->cWalkHits * (KU64)100 / K_MAX(g_pFsCache->cLookups, 1)), kwFmtU64(sz4, cMisses), (unsigned)(cMisses * (KU64)100 / K_MAX(g_pFsCache->cLookups, 1))); off += sprintf(&szBuf[off], "%s %14s child searches, %s (%u%%) hash hits\n", szPrf, kwFmtU64(sz1, g_pFsCache->cChildSearches), kwFmtU64(sz2, g_pFsCache->cChildHashHits), (unsigned)(g_pFsCache->cChildHashHits * (KU64)100 / K_MAX(g_pFsCache->cChildSearches, 1))); off += sprintf(&szBuf[off], "%s %14s name changes, growing %s times (%u%%)\n", szPrf, kwFmtU64(sz1, g_pFsCache->cNameChanges), kwFmtU64(sz2, g_pFsCache->cNameGrowths), (unsigned)(g_pFsCache->cNameGrowths * 100 / K_MAX(g_pFsCache->cNameChanges, 1)) ); /* * Process & Memory details. */ if (!GetProcessHandleCount(GetCurrentProcess(), &cHandles)) cHandles = 0; MemInfo.cb = sizeof(MemInfo); if (!GetProcessMemoryInfo(GetCurrentProcess(), (PPROCESS_MEMORY_COUNTERS)&MemInfo, sizeof(MemInfo))) memset(&MemInfo, 0, sizeof(MemInfo)); off += sprintf(&szBuf[off], "%s %14s handles; %s page faults; %s bytes page file, peaking at %s bytes\n", szPrf, kwFmtU64(sz1, cHandles), kwFmtU64(sz2, MemInfo.PageFaultCount), kwFmtU64(sz3, MemInfo.PagefileUsage), kwFmtU64(sz4, MemInfo.PeakPagefileUsage)); off += sprintf(&szBuf[off], "%s %14s bytes working set, peaking at %s bytes; %s byte private\n", szPrf, kwFmtU64(sz1, MemInfo.WorkingSetSize), kwFmtU64(sz2, MemInfo.PeakWorkingSetSize), kwFmtU64(sz3, MemInfo.PrivateUsage)); off += sprintf(&szBuf[off], "%s %14s bytes non-paged pool, peaking at %s bytes; %s bytes paged pool, peaking at %s bytes\n", szPrf, kwFmtU64(sz1, MemInfo.QuotaNonPagedPoolUsage), kwFmtU64(sz2, MemInfo.QuotaPeakNonPagedPoolUsage), kwFmtU64(sz3, MemInfo.QuotaPagedPoolUsage), kwFmtU64(sz4, MemInfo.QuotaPeakPagedPoolUsage)); if (!GetProcessIoCounters(GetCurrentProcess(), &IoCounters)) memset(&IoCounters, 0, sizeof(IoCounters)); off += sprintf(&szBuf[off], "%s %14s bytes in %s reads [src: OS]\n", szPrf, kwFmtU64(sz1, IoCounters.ReadTransferCount), kwFmtU64(sz2, IoCounters.ReadOperationCount)); off += sprintf(&szBuf[off], "%s %14s bytes in %s writes [src: OS]\n", szPrf, kwFmtU64(sz1, IoCounters.WriteTransferCount), kwFmtU64(sz2, IoCounters.WriteOperationCount)); off += sprintf(&szBuf[off], "%s %14s bytes in %s other I/O operations [src: OS]\n", szPrf, kwFmtU64(sz1, IoCounters.OtherTransferCount), kwFmtU64(sz2, IoCounters.OtherOperationCount)); MemStatus.dwLength = sizeof(MemStatus); if (!GlobalMemoryStatusEx(&MemStatus)) memset(&MemStatus, 0, sizeof(MemStatus)); off += sprintf(&szBuf[off], "%s %14s bytes used VA, %#" KX64_PRI " bytes available\n", szPrf, kwFmtU64(sz1, MemStatus.ullTotalVirtual - MemStatus.ullAvailVirtual), MemStatus.ullAvailVirtual); off += sprintf(&szBuf[off], "%s %14u %% system memory load\n", szPrf, MemStatus.dwMemoryLoad); maybe_con_fwrite(szBuf, off, 1, stdout); fflush(stdout); } /** * Handles what comes after --test. * * @returns Exit code. * @param argc Number of arguments after --test. * @param argv Arguments after --test. */ static int kwTestRun(int argc, char **argv) { int i; int j; int rcExit; int cRepeats; char szCwd[MAX_PATH]; const char *pszCwd = getcwd(szCwd, sizeof(szCwd)); KU32 cEnvVars; KBOOL fWatcomBrainDamange = K_FALSE; KBOOL fNoPchCaching = K_FALSE; /* * Parse arguments. */ /* Repeat count. */ i = 0; if (i >= argc) return kwErrPrintfRc(2, "--test takes an repeat count argument or '--'!\n"); if (strcmp(argv[i], "--") != 0) { cRepeats = atoi(argv[i]); if (cRepeats <= 0) return kwErrPrintfRc(2, "The repeat count '%s' is zero, negative or invalid!\n", argv[i]); i++; /* Optional directory change. */ if ( i < argc && ( strcmp(argv[i], "--chdir") == 0 || strcmp(argv[i], "-C") == 0 ) ) { i++; if (i >= argc) return kwErrPrintfRc(2, "--chdir takes an argument!\n"); pszCwd = argv[i++]; } /* Optional watcom flag directory change. */ if ( i < argc && ( strcmp(argv[i], "--wcc-brain-damage") == 0 || strcmp(argv[i], "--watcom-brain-damage") == 0) ) { fWatcomBrainDamange = K_TRUE; i++; } /* Optional watcom flag directory change. */ if ( i < argc && strcmp(argv[i], "--no-pch-caching") == 0) { fNoPchCaching = K_TRUE; i++; } /* Trigger breakpoint */ if ( i < argc && strcmp(argv[i], "--breakpoint") == 0) { __debugbreak(); i++; } /* Check for '--'. */ if (i >= argc) return kwErrPrintfRc(2, "Missing '--'\n"); if (strcmp(argv[i], "--") != 0) return kwErrPrintfRc(2, "Expected '--' found '%s'\n", argv[i]); i++; } else { cRepeats = 1; i++; } if (i >= argc) return kwErrPrintfRc(2, "Nothing to execute after '--'!\n"); /* * Do the job. */ cEnvVars = 0; while (environ[cEnvVars] != NULL) cEnvVars++; for (j = 0; j < cRepeats; j++) { rcExit = kSubmitHandleJobUnpacked(argv[i], pszCwd, argc - i, &argv[i], fWatcomBrainDamange, cEnvVars, environ, fNoPchCaching, 0, NULL); KW_LOG(("rcExit=%d\n", rcExit)); kwSandboxCleanupLate(&g_Sandbox); } if (getenv("KWORKER_STATS") != NULL) kwPrintStats(); # ifdef WITH_LOG_FILE if (g_hLogFile != INVALID_HANDLE_VALUE && g_hLogFile != NULL) CloseHandle(g_hLogFile); # endif return rcExit; } int main(int argc, char **argv) { KSIZE cbMsgBuf = 0; KU8 *pbMsgBuf = NULL; int i; HANDLE hPipe = INVALID_HANDLE_VALUE; const char *pszTmp; KFSLOOKUPERROR enmIgnored; #if defined(KBUILD_OS_WINDOWS) && defined(KBUILD_ARCH_X86) PVOID pvVecXcptHandler = AddVectoredExceptionHandler(0 /*called last*/, kwSandboxVecXcptEmulateChained); #endif #ifdef WITH_CONSOLE_OUTPUT_BUFFERING HANDLE hCurProc = GetCurrentProcess(); PPEB pPeb = kwSandboxGetProcessEnvironmentBlock(); PMY_RTL_USER_PROCESS_PARAMETERS pProcessParams = (PMY_RTL_USER_PROCESS_PARAMETERS)pPeb->ProcessParameters; DWORD dwType; #endif #ifdef WITH_FIXED_VIRTUAL_ALLOCS /* * Reserve memory for cl.exe */ for (i = 0; i < K_ELEMENTS(g_aFixedVirtualAllocs); i++) { g_aFixedVirtualAllocs[i].fInUse = K_FALSE; g_aFixedVirtualAllocs[i].pvReserved = VirtualAlloc((void *)g_aFixedVirtualAllocs[i].uFixed, g_aFixedVirtualAllocs[i].cbFixed, MEM_RESERVE, PAGE_READWRITE); if ( !g_aFixedVirtualAllocs[i].pvReserved || g_aFixedVirtualAllocs[i].pvReserved != (void *)g_aFixedVirtualAllocs[i].uFixed) { kwErrPrintf("Failed to reserve %p LB %#x: %u\n", g_aFixedVirtualAllocs[i].uFixed, g_aFixedVirtualAllocs[i].cbFixed, GetLastError()); if (g_aFixedVirtualAllocs[i].pvReserved) { VirtualFree(g_aFixedVirtualAllocs[i].pvReserved, g_aFixedVirtualAllocs[i].cbFixed, MEM_RELEASE); g_aFixedVirtualAllocs[i].pvReserved = NULL; } } } #endif /* * Register our Control-C and Control-Break handlers. */ if (!SetConsoleCtrlHandler(kwSandboxCtrlHandler, TRUE /*fAdd*/)) return kwErrPrintfRc(3, "SetConsoleCtrlHandler failed: %u\n", GetLastError()); /* * Create the cache and mark the temporary directory as using the custom revision. */ g_pFsCache = kFsCacheCreate(KFSCACHE_F_MISSING_OBJECTS | KFSCACHE_F_MISSING_PATHS); if (!g_pFsCache) return kwErrPrintfRc(3, "kFsCacheCreate failed!\n"); pszTmp = getenv("TEMP"); if (pszTmp && *pszTmp != '\0') kFsCacheSetupCustomRevisionForTree(g_pFsCache, kFsCacheLookupA(g_pFsCache, pszTmp, &enmIgnored)); pszTmp = getenv("TMP"); if (pszTmp && *pszTmp != '\0') kFsCacheSetupCustomRevisionForTree(g_pFsCache, kFsCacheLookupA(g_pFsCache, pszTmp, &enmIgnored)); pszTmp = getenv("TMPDIR"); if (pszTmp && *pszTmp != '\0') kFsCacheSetupCustomRevisionForTree(g_pFsCache, kFsCacheLookupA(g_pFsCache, pszTmp, &enmIgnored)); /* * Make g_abDefLdBuf executable. */ if (!VirtualProtect(g_abDefLdBuf, sizeof(g_abDefLdBuf), PAGE_EXECUTE_READWRITE, &dwType)) return kwErrPrintfRc(3, "VirtualProtect(%p, %#x, PAGE_EXECUTE_READWRITE,NULL) failed: %u\n", g_abDefLdBuf, sizeof(g_abDefLdBuf), GetLastError()); #ifdef WITH_CONSOLE_OUTPUT_BUFFERING /* * Get and duplicate the console handles. */ /* Standard output. */ g_Sandbox.StdOut.hOutput = pProcessParams->StandardOutput; if (!DuplicateHandle(hCurProc, pProcessParams->StandardOutput, hCurProc, &g_Sandbox.StdOut.hBackup, GENERIC_WRITE, FALSE /*fInherit*/, DUPLICATE_SAME_ACCESS)) kHlpAssertFailedStmt(g_Sandbox.StdOut.hBackup = pProcessParams->StandardOutput); dwType = GetFileType(g_Sandbox.StdOut.hOutput); g_Sandbox.StdOut.fIsConsole = dwType == FILE_TYPE_CHAR; g_Sandbox.StdOut.fFileType = (dwType & ~FILE_TYPE_REMOTE) < 0xf ? (KU8)((dwType & ~FILE_TYPE_REMOTE) | (dwType >> 8)) : KU8_MAX; g_Sandbox.HandleStdOut.enmType = KWHANDLETYPE_OUTPUT_BUF; g_Sandbox.HandleStdOut.cRefs = 0x10001; g_Sandbox.HandleStdOut.dwDesiredAccess = GENERIC_WRITE; g_Sandbox.HandleStdOut.u.pOutBuf = &g_Sandbox.StdOut; g_Sandbox.HandleStdOut.hHandle = g_Sandbox.StdOut.hOutput; if (g_Sandbox.StdOut.hOutput != INVALID_HANDLE_VALUE) { if (kwSandboxHandleTableEnter(&g_Sandbox, &g_Sandbox.HandleStdOut, g_Sandbox.StdOut.hOutput)) g_Sandbox.cFixedHandles++; else return kwErrPrintfRc(3, "kwSandboxHandleTableEnter failed for StdOut (%p)!\n", g_Sandbox.StdOut.hOutput); } KWOUT_LOG(("StdOut: hOutput=%p (%p) fIsConsole=%d dwType=%#x\n", g_Sandbox.StdOut.hOutput, g_Sandbox.StdOut.hBackup, g_Sandbox.StdOut.fIsConsole, dwType)); /* Standard error. */ g_Sandbox.StdErr.hOutput = pProcessParams->StandardError; if (!DuplicateHandle(hCurProc, pProcessParams->StandardError, hCurProc, &g_Sandbox.StdErr.hBackup, GENERIC_WRITE, FALSE /*fInherit*/, DUPLICATE_SAME_ACCESS)) kHlpAssertFailedStmt(g_Sandbox.StdErr.hBackup = pProcessParams->StandardError); dwType = GetFileType(g_Sandbox.StdErr.hOutput); g_Sandbox.StdErr.fIsConsole = dwType == FILE_TYPE_CHAR; g_Sandbox.StdErr.fFileType = (dwType & ~FILE_TYPE_REMOTE) < 0xf ? (KU8)((dwType & ~FILE_TYPE_REMOTE) | (dwType >> 8)) : KU8_MAX; g_Sandbox.HandleStdErr.enmType = KWHANDLETYPE_OUTPUT_BUF; g_Sandbox.HandleStdErr.cRefs = 0x10001; g_Sandbox.HandleStdErr.dwDesiredAccess = GENERIC_WRITE; g_Sandbox.HandleStdErr.u.pOutBuf = &g_Sandbox.StdErr; g_Sandbox.HandleStdErr.hHandle = g_Sandbox.StdErr.hOutput; if ( g_Sandbox.StdErr.hOutput != INVALID_HANDLE_VALUE && g_Sandbox.StdErr.hOutput != g_Sandbox.StdOut.hOutput) { if (kwSandboxHandleTableEnter(&g_Sandbox, &g_Sandbox.HandleStdErr, g_Sandbox.StdErr.hOutput)) g_Sandbox.cFixedHandles++; else return kwErrPrintfRc(3, "kwSandboxHandleTableEnter failed for StdErr (%p)!\n", g_Sandbox.StdErr.hOutput); } KWOUT_LOG(("StdErr: hOutput=%p (%p) fIsConsole=%d dwType=%#x\n", g_Sandbox.StdErr.hOutput, g_Sandbox.StdErr.hBackup, g_Sandbox.StdErr.fIsConsole, dwType)); /* Combined console buffer. */ if (g_Sandbox.StdErr.fIsConsole) { g_Sandbox.Combined.hOutput = g_Sandbox.StdErr.hBackup; g_Sandbox.Combined.uCodepage = GetConsoleCP(); } else if (g_Sandbox.StdOut.fIsConsole) { g_Sandbox.Combined.hOutput = g_Sandbox.StdOut.hBackup; g_Sandbox.Combined.uCodepage = GetConsoleCP(); } else { g_Sandbox.Combined.hOutput = INVALID_HANDLE_VALUE; g_Sandbox.Combined.uCodepage = CP_ACP; } KWOUT_LOG(("Combined: hOutput=%p uCodepage=%d\n", g_Sandbox.Combined.hOutput, g_Sandbox.Combined.uCodepage)); #endif /* WITH_CONSOLE_OUTPUT_BUFFERING */ /* * Parse arguments. */ for (i = 1; i < argc; i++) { if (strcmp(argv[i], "--pipe") == 0) { i++; if (i < argc) { char *pszEnd = NULL; unsigned __int64 u64Value = _strtoui64(argv[i], &pszEnd, 16); if ( *argv[i] && pszEnd != NULL && *pszEnd == '\0' && u64Value != 0 && u64Value != (uintptr_t)INVALID_HANDLE_VALUE && (uintptr_t)u64Value == u64Value) hPipe = (HANDLE)(uintptr_t)u64Value; else return kwErrPrintfRc(2, "Invalid --pipe argument: %s\n", argv[i]); } else return kwErrPrintfRc(2, "--pipe takes an argument!\n"); } else if (strcmp(argv[i], "--volatile") == 0) { i++; if (i < argc) kFsCacheSetupCustomRevisionForTree(g_pFsCache, kFsCacheLookupA(g_pFsCache, argv[i], &enmIgnored)); else return kwErrPrintfRc(2, "--volatile takes an argument!\n"); } else if (strcmp(argv[i], "--test") == 0) return kwTestRun(argc - i - 1, &argv[i + 1]); else if (strcmp(argv[i], "--priority") == 0) { i++; if (i < argc) { char *pszEnd = NULL; unsigned long uValue = strtoul(argv[i], &pszEnd, 16); if ( *argv[i] && pszEnd != NULL && *pszEnd == '\0' && uValue >= 1 && uValue <= 5) { DWORD dwClass, dwPriority; switch (uValue) { case 1: dwClass = IDLE_PRIORITY_CLASS; dwPriority = THREAD_PRIORITY_IDLE; break; case 2: dwClass = BELOW_NORMAL_PRIORITY_CLASS; dwPriority = THREAD_PRIORITY_BELOW_NORMAL; break; case 3: dwClass = NORMAL_PRIORITY_CLASS; dwPriority = THREAD_PRIORITY_NORMAL; break; case 4: dwClass = HIGH_PRIORITY_CLASS; dwPriority = 0xffffffff; break; case 5: dwClass = REALTIME_PRIORITY_CLASS; dwPriority = 0xffffffff; break; } SetPriorityClass(GetCurrentProcess(), dwClass); if (dwPriority != 0xffffffff) SetThreadPriority(GetCurrentThread(), dwPriority); } else return kwErrPrintfRc(2, "Invalid --priority argument: %s\n", argv[i]); } else return kwErrPrintfRc(2, "--priority takes an argument!\n"); } else if ( strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-?") == 0) { printf("usage: kWorker [--volatile dir] [--priority <1-5>] --pipe \n" "usage: kWorker <--help|-h>\n" "usage: kWorker <--version|-V>\n" "usage: kWorker [--volatile dir] --test [ [--chdir ] [--breakpoint] -- args\n" "\n" "This is an internal kmk program that is used via the builtin_kSubmit.\n"); return 0; } else if ( strcmp(argv[i], "--version") == 0 || strcmp(argv[i], "-V") == 0) return kbuild_version(argv[0]); else return kwErrPrintfRc(2, "Unknown argument '%s'\n", argv[i]); } if (hPipe == INVALID_HANDLE_VALUE) return kwErrPrintfRc(2, "Missing --pipe argument!\n"); /* * Serve the pipe. */ for (;;) { KU32 cbMsg = 0; int rc = kSubmitReadIt(hPipe, &cbMsg, sizeof(cbMsg), K_TRUE /*fShutdownOkay*/); if (rc == 0) { /* Make sure the message length is within sane bounds. */ if ( cbMsg > 4 && cbMsg <= 256*1024*1024) { /* Reallocate the message buffer if necessary. We add 4 zero bytes. */ if (cbMsg + 4 <= cbMsgBuf) { /* likely */ } else { cbMsgBuf = K_ALIGN_Z(cbMsg + 4, 2048); pbMsgBuf = kHlpRealloc(pbMsgBuf, cbMsgBuf); if (!pbMsgBuf) return kwErrPrintfRc(1, "Failed to allocate %u bytes for a message buffer!\n", cbMsgBuf); } /* Read the whole message into the buffer, making sure there is are a 4 zero bytes following it. */ *(KU32 *)pbMsgBuf = cbMsg; rc = kSubmitReadIt(hPipe, &pbMsgBuf[sizeof(cbMsg)], cbMsg - sizeof(cbMsg), K_FALSE /*fShutdownOkay*/); if (rc == 0) { const char *psz; pbMsgBuf[cbMsg] = '\0'; pbMsgBuf[cbMsg + 1] = '\0'; pbMsgBuf[cbMsg + 2] = '\0'; pbMsgBuf[cbMsg + 3] = '\0'; /* The first string after the header is the command. */ psz = (const char *)&pbMsgBuf[sizeof(cbMsg)]; if (strcmp(psz, "JOB") == 0) { struct { KI32 rcExitCode; KU8 bExiting; KU8 abZero[3]; } Reply; Reply.rcExitCode = kSubmitHandleJob(psz, cbMsg - sizeof(cbMsg)); Reply.bExiting = g_fRestart; Reply.abZero[0] = 0; Reply.abZero[1] = 0; Reply.abZero[2] = 0; rc = kSubmitWriteIt(hPipe, &Reply, sizeof(Reply)); if ( rc == 0 && !g_fRestart) { kwSandboxCleanupLate(&g_Sandbox); continue; } } else rc = kwErrPrintfRc(-1, "Unknown command: '%s'\n", psz); } } else rc = kwErrPrintfRc(-1, "Bogus message length: %u (%#x)\n", cbMsg, cbMsg); } /* * If we're exitting because we're restarting, we need to delay till * kmk/kSubmit has read the result. Windows documentation says it * immediately discards pipe buffers once the pipe is broken by the * server (us). So, We flush the buffer and queues a 1 byte read * waiting for kSubmit to close the pipe when it receives the * bExiting = K_TRUE result. */ if (g_fRestart) { KU8 b; FlushFileBuffers(hPipe); ReadFile(hPipe, &b, 1, &cbMsg, NULL); } CloseHandle(hPipe); #ifdef WITH_LOG_FILE if (g_hLogFile != INVALID_HANDLE_VALUE && g_hLogFile != NULL) CloseHandle(g_hLogFile); #endif if (getenv("KWORKER_STATS") != NULL) kwPrintStats(); return rc > 0 ? 0 : 1; } } /** @page pg_kWorker kSubmit / kWorker * * @section sec_kWorker_Motivation Motivation / Inspiration * * The kSubmit / kWorker combo was conceived as a way to speed up VirtualBox * builds on machines "infested" by Anti Virus protection and disk encryption * software. Build times jumping from 35-40 min to 77-82 min after the machine * got "infected". * * Speeing up builting of Boot Sector Kit \#3 was also hightly desirable. It is * mainly a bunch of tiny assembly and C files being compiler a million times. * As some of us OS/2 users maybe recalls, the Watcom make program can run its * own toolchain from within the same process, saving a lot of process creation * and teardown overhead. * * * @section sec_kWorker_kSubmit About kSubmit * * When wanting to execute a job in a kWorker instance, it must be submitted * using the kmk_builtin_kSubmit command in kmk. As the name suggest, this is * built into kmk and does not exist as an external program. The reason for * this is that it keep track of the kWorker instances. * * The kSubmit command has the --32-bit and --64-bit options for selecting * between 32-bit and 64-bit worker instance. We generally assume the user of * the command knows which bit count the executable has, so kSubmit is spared * the extra work of finding out. * * The kSubmit command shares a environment and current directory manipulation * with the kRedirect command, but not the file redirection. So long no file * operation is involed, kSubmit is a drop in kRedirect replacement. This is * hand for tools like OpenWatcom, NASM and YASM which all require environment * and/or current directory changes to work. * * Unlike the kRedirect command, the kSubmit command can also specify an * internall post command to be executed after the main command succeeds. * Currently only kmk_builtin_kDepObj is supported. kDepObj gathers dependency * information from Microsoft COFF object files and Watcom OMF object files and * is scheduled to replace kDepIDB. * * * @section sec_kWorker_Interaction kSubmit / kWorker interaction * * The kmk_builtin_kSubmit communicates with the kWorker instances over pipes. * A job request is written by kSubmit and kWorker read, unpacks it and executes * it. When the job is completed, kWorker writes a short reply with the exit * code and an internal status indicating whether it is going to restart. * * The kWorker intance will reply to kSubmit before completing all the internal * cleanup work, so as not to delay the next job execution unnecessarily. This * includes checking its own memory consumption and checking whether it needs * restarting. So, a decision to restart unfortunately have to wait till after * the next job has completed. This is a little bit unfortunate if the next job * requires a lot of memory and kWorker has already leaked/used a lot. * * * @section sec_kWorker_How_Works How kWorker Works * * kWorker will load the executable specified by kSubmit into memory and call * it's entrypoint in a lightly sandbox'ed environment. * * * @subsection ssec_kWorker_Loaing Image loading * * kWorker will manually load all the executable images into memory, fix them * up, and make a copy of the virgin image so it can be restored using memcpy * the next time it is used. * * Imported functions are monitored and replacements used for a few of them. * These replacements are serve the following purposes: * - Provide a different command line. * - Provide a different environment. * - Intercept process termination. * - Intercept thread creation (only linker is allowed to create threads). * - Intercept file reading for caching (header files, ++) as file system * access is made even slower by anti-virus software. * - Intercept crypto hash APIs to cache MD5 digests of header files * (c1.dll / c1xx.dll spends a noticable bit of time doing MD5). * - Intercept temporary files (%TEMP%/_CL_XXXXXXyy) to keep the entirely * in memory as writing files grows expensive with encryption and * anti-virus software active. * - Intercept some file system queries to use the kFsCache instead of * going to the kernel and slowly worm thru the AV filter driver. * - Intercept standard output/error and console writes to aggressivly * buffer the output. The MS CRT does not buffer either when it goes to * the console, resulting in terrible performance and mixing up output * with other compile jobs. * This also allows us to filter out the annoying source file announcements * by cl.exe. * - Intercept VirtualAlloc and VirtualFree to prevent * CL.EXE/C1.DLL/C1XX.DLL from leaking some 72MB internal allocat area. * - Intercept FlsAlloc/FlsFree to make sure the allocations are freed and * the callbacks run after each job. * - Intercept HeapCreate/HeapFree to reduce leaks from statically linked * executables and tools using custom heaps (like the microsoft linker). * [exectuable images only] * - Intercept atexit and _onexit registration to be able run them after * each job instead of crashing as kWorker exits. This also helps avoid * some leaks. [executable image only] * * DLLs falls into two categories, system DLLs which we always load using the * native loader, and tool DLLs which can be handled like the executable or * optionally using the native loader. We maintain a hardcoded white listing of * tool DLLs we trust to load using the native loader. * * Imports of natively loaded DLLs are processed too, but we only replace a * subset of the functions compared to natively loaded excutable and DLL images. * * DLLs are never unloaded and we cache LoadLibrary requests (hash the input). * This is to speed up job execution. * * It was thought that we needed to restore (memcpy) natively loaded tool DLLs * for each job run, but so far this hasn't been necessary. * * * @subsection ssec_kWorker_Optimizing Optimizing the Compiler * * The Visual Studio 2010 C/C++ compiler does a poor job at processing header * files and uses a whole bunch of temporary files (in %TEMP%) for passing * intermediate representation between the first (c1/c1xx.dll) and second pass * (c2.dll). * * kWorker helps the compiler as best as it can. Given a little knowledge about * stable and volatile file system areas, it can do a lot of caching that a * normal compiler driver cannot easily do when given a single file. * * * @subsubsection sssec_kWorker_Headers Cache Headers Files and Searches * * The preprocessor part will open and process header files exactly as they are * encountered in the source files. If string.h is included by the main source * and five other header files, it will be searched for (include path), opened, * read, MD5-summed, and pre-processed six times. The last five times is just a * waste of time because of the guards or \#pragma once. A smart compiler would * make a little extra effort and realize this. * * kWorker will cache help the preprocessor by remembering places where the * header was not found with help of kFsCache, and cache the file in memory when * found. The first part is taken care of by intercepting GetFileAttributesW, * and the latter by intercepting CreateFileW, ReadFile and CloseFile. Once * cached, the file is kept open and the CreateFileW call returns a duplicate of * that handle. An internal handle table is used by ReadFile and CloseFile to * keep track of intercepted handles (also used for temporary file, temporary * file mappings, console buffering, and standard out/err buffering). * * PS. The header search optimization also comes in handy when cl.exe goes on * thru the whole PATH looking for c1/c1xx.exe and c2.exe after finding * c1/c1xx.dll and c2.dll. My guess is that the compiler team can * optionally compile the three pass DLLs as executables during development * and problem analysis. * * * @subsubsection sssec_kWorker_Temp_Files Temporary Files In Memory * * The issues of the temporary files is pretty severe on the Dell machine used * for benchmarking with full AV and encryption. The synthetic benchmark * improved by 30% when kWorker implemented measures to keep them entirely in * memory. * * kWorker implement these by recognizing the filename pattern in CreateFileW * and creating/opening the given file as needed. The handle returned is a * duplicate of the current process, thus giving us a good chance of catching * API calls we're not intercepting. * * In addition to CreateFileW, we also need to intercept GetFileType, ReadFile, * WriteFile, SetFilePointer+Ex, SetEndOfFile, and CloseFile. The 2nd pass * additionally requires GetFileSize+Ex, CreateFileMappingW, MapViewOfFile and * UnmapViewOfFile. * * * @section sec_kWorker_Numbers Some measurements. * * - r2881 building src/VBox/Runtime: * - without: 2m01.016388s = 120.016388 s * - with: 1m15.165069s = 75.165069 s => 120.016388s - 75.165069s = 44.851319s => 44.85/120.02 = 37% speed up. * - r2884 building vbox/debug (r110512): * - without: 11m14.446609s = 674.446609 s * - with: 9m01.017344s = 541.017344 s => 674.446609s - 541.017344s = 133.429265 => 133.43/674.45 = 19% speed up * - r2896 building vbox/debug (r110577): * - with: 8m31.182384s = 511.182384 s => 674.446609s - 511.182384s = 163.264225 = 163.26/674.45 = 24% speed up * - r2920 building vbox/debug (r110702) on Skylake (W10/amd64, only standard * MS Defender as AV): * - without: 10m24.990389s = 624.990389s * - with: 08m04.738184s = 484.738184s * - delta: 624.99s - 484.74s = 140.25s * - saved: 140.25/624.99 = 22% faster * * * @subsection subsec_kWorker_Early_Numbers Early Experiments * * These are some early experiments doing 1024 compilations of * VBoxBS2Linker.cpp using a hard coded command line and looping in kWorker's * main function: * * Skylake (W10/amd64, only stdandard MS defender): * - cmd 1: 48 /1024 = 0x0 (0.046875) [for /l %i in (1,1,1024) do ...] * - kmk 1: 44 /1024 = 0x0 (0.04296875) [all: ; 1024 x cl.exe] * - run 1: 37 /1024 = 0x0 (0.0361328125) [just process creation gain] * - run 2: 34 /1024 = 0x0 (0.033203125) [get file attribs] * - run 3: 32.77 /1024 = 0x0 (0.032001953125) [read caching of headers] * - run 4: 32.67 /1024 = 0x0 (0.031904296875) [loader tweaking] * - run 5: 29.144/1024 = 0x0 (0.0284609375) [with temp files in memory] * * Dell (W7/amd64, infected by mcafee): * - kmk 1: 285.278/1024 = 0x0 (0.278591796875) * - run 1: 134.503/1024 = 0x0 (0.1313505859375) [w/o temp files in memory] * - run 2: 78.161/1024 = 0x0 (0.0763291015625) [with temp files in memory] * * The command line: * @code{.cpp} "\"E:/vbox/svn/trunk/tools/win.x86/vcc/v10sp1/bin/amd64/cl.exe\" -c -c -TP -nologo -Zi -Zi -Zl -GR- -EHsc -GF -Zc:wchar_t- -Oy- -MT -W4 -Wall -wd4065 -wd4996 -wd4127 -wd4706 -wd4201 -wd4214 -wd4510 -wd4512 -wd4610 -wd4514 -wd4820 -wd4365 -wd4987 -wd4710 -wd4061 -wd4986 -wd4191 -wd4574 -wd4917 -wd4711 -wd4611 -wd4571 -wd4324 -wd4505 -wd4263 -wd4264 -wd4738 -wd4242 -wd4244 -WX -RTCsu -IE:/vbox/svn/trunk/tools/win.x86/vcc/v10sp1/include -IE:/vbox/svn/trunk/tools/win.x86/vcc/v10sp1/atlmfc/include -IE:/vbox/svn/trunk/tools/win.x86/sdk/v7.1/Include -IE:/vbox/svn/trunk/include -IE:/vbox/svn/trunk/out/win.amd64/debug -IE:/vbox/svn/trunk/tools/win.x86/vcc/v10sp1/include -IE:/vbox/svn/trunk/tools/win.x86/vcc/v10sp1/atlmfc/include -DVBOX -DVBOX_WITH_64_BITS_GUESTS -DVBOX_WITH_REM -DVBOX_WITH_RAW_MODE -DDEBUG -DDEBUG_bird -DDEBUG_USERNAME=bird -DRT_OS_WINDOWS -D__WIN__ -DRT_ARCH_AMD64 -D__AMD64__ -D__WIN64__ -DVBOX_WITH_DEBUGGER -DRT_LOCK_STRICT -DRT_LOCK_STRICT_ORDER -DIN_RING3 -DLOG_DISABLED -DIN_BLD_PROG -D_CRT_SECURE_NO_DEPRECATE -FdE:/vbox/svn/trunk/out/win.amd64/debug/obj/VBoxBs2Linker/VBoxBs2Linker-obj.pdb -FD -FoE:/vbox/svn/trunk/out/win.amd64/debug/obj/VBoxBs2Linker/VBoxBs2Linker.obj E:\\vbox\\svn\\trunk\\src\\VBox\\ValidationKit\\bootsectors\\VBoxBs2Linker.cpp" * @endcode */