diff options
Diffstat (limited to 'src/kWorker')
-rw-r--r-- | src/kWorker/Makefile.kmk | 238 | ||||
-rw-r--r-- | src/kWorker/kWorker.c | 14100 | ||||
-rw-r--r-- | src/kWorker/kWorkerTlsXxxK.c | 138 |
3 files changed, 14476 insertions, 0 deletions
diff --git a/src/kWorker/Makefile.kmk b/src/kWorker/Makefile.kmk new file mode 100644 index 0000000..45a8bf2 --- /dev/null +++ b/src/kWorker/Makefile.kmk @@ -0,0 +1,238 @@ +# $Id: Makefile.kmk 3549 2022-01-29 02:41:36Z bird $ +## @file +# Sub-makefile for kWorker. +# + +# +# Copyright (c) 2016 knut st. osmundsen <bird-kBuild-spamx@anduin.net> +# +# 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 <http://www.gnu.org/licenses/> +# +# + + +SUB_DEPTH = ../.. +include $(PATH_KBUILD)/subheader.kmk + + +PROGRAMS += kWorker +kWorker_TEMPLATE = BIN-STATIC-THREADED +kWorker_DEFS := KWORKER +kWorker_DEFS.debug = K_STRICT +kWorker_DEFS.release = NASSERT +ifeq ($(USERNAME),bird) +kWorker_CFLAGS = -W4 -wd4127 -wd4100 -wd4214 -wd4201 -wd4204 +endif +kWorker_SOURCES = \ + kWorker.c \ + ../kmk/kmkbuiltin/kDepObj.c \ + ../kmk/kmkbuiltin/err.c +kWorker_INCS = \ + ../kmk/ \ + ../kmk/kmkbuiltin +kWorker_LIBS = \ + $(kStuff_1_TARGET) \ + $(kWorkerLib_1_TARGET) +include $(KBUILD_PATH)/sdks/WINDDK71.kmk +kWorker_LIBS.win = \ + $(TEMPLATE_BIN-STATIC-THREADED_LIBS) \ + $(PATH_SDK_WINDDK71_LIB_WNET)/ntdll.lib \ + $(PATH_SDK_WINDDK71_LIB_WNET)/psapi.lib +kWorker_LDFLAGS.win = \ + /DYNAMICBASE:NO /FIXED +kWorker_LDFLAGS.win.x86 = /BASE:0x00010000 +kWorker_LDFLAGS.win.amd64 = /BASE:0x0000000420000000 /STACK:16777216,262144 + +#kWorker_LDFLAGS.win.x86 = \ +# /SAFESEH:NO - doesn't help anyone. + + +# +# Stuff from ../libs. Need to rebuilt it with static CRT. +# +LIBRARIES += kWorkerLib +kWorkerLib_TEMPLATE = LIB-STATIC-THREADED +kWorkerLib_DEFPATH = ../lib # Need fix from r2837. +kWorkerLib_DEFPATH := $(PATH_SUB_CURRENT)/../lib +kWorkerLib_DEFS := KWORKER +kWorkerLib_SOURCES = \ + crc32.c \ + md5.c \ + kbuild_version.c \ + kDep.c +kWorkerLib_SOURCES.win = \ + nt_fullpath.c \ + nt_fullpath_cached.c \ + quoted_spawn.c \ + nt/nthlpcore.c \ + nt/nthlpfs.c \ + nt/ntdir.c \ + nt/ntstat.c \ + nt/ntunlink.c \ + nt/kFsCache.c \ + win_get_processor_group_active_mask.c \ + quote_argv.c \ + get_codepage.c \ + is_console.c \ + maybe_con_write.c \ + maybe_con_fwrite.c \ + msc_buffered_printf.c +kbuild_version.c_DEFS = KBUILD_SVN_REV=$(KBUILD_SVN_REV) + +# +# kStuff library. +# +LIBRARIES += kStuff +kStuff_TEMPLATE = LIB-STATIC-THREADED +kStuff_DEFS.debug = K_STRICT +kStuff_INCS = kStuff/include +kStuff_DEFPATH = $(PATH_ROOT)/src/lib + +# kLdr +kStuff_SOURCES += \ + kStuff/kLdr/kLdr.c \ + kStuff/kLdr/kLdrDyld.c \ + kStuff/kLdr/kLdrDyldFind.c \ + kStuff/kLdr/kLdrDyldMod.c \ + kStuff/kLdr/kLdrDyldOS.c \ + kStuff/kLdr/kLdrDyLdSem.c \ + kStuff/kLdr/kLdrMod.c \ + kStuff/kLdr/kLdrModLX.c \ + kStuff/kLdr/kLdrModMachO.c \ + kStuff/kLdr/kLdrModNative.c \ + kStuff/kLdr/kLdrModPE.c +kLdr_SOURCES.os2 += \ + kStuff/kLdr/kLdr-os2.c \ + kStuff/kLdr/kLdrA-os2.asm +kLdr_SOURCES.win += \ + kStuff/kLdr/kLdr-win.c + +# kRdr +kStuff_SOURCES += \ + kStuff/kRdr/kRdr.cpp \ + kStuff/kRdr/kRdrFile.cpp \ + kStuff/kRdr/kRdrBuffered.cpp + +# kCpu +kStuff_SOURCES += \ + kStuff/kCpu/kCpuCompare.c \ + kStuff/kCpu/kCpuGetArchAndCpu.c + +# kHlp (CRT) +kStuff_SOURCES += \ + kStuff/kHlp/Generic/kHlpMemPComp.c \ + kStuff/kHlp/Generic/kHlpMemICompAscii.c \ + kStuff/kHlp/Generic/kHlpStrPCat.c \ + kStuff/kHlp/Generic/kHlpStrNPCat.c \ + kStuff/kHlp/Generic/kHlpStrPComp.c \ + kStuff/kHlp/Generic/kHlpStrNPComp.c \ + kStuff/kHlp/Generic/kHlpStrICompAscii.c \ + kStuff/kHlp/Generic/kHlpStrIPCompAscii.c \ + kStuff/kHlp/Generic/kHlpStrNICompAscii.c \ + kStuff/kHlp/Generic/kHlpStrNIPCompAscii.c \ + kStuff/kHlp/Generic/kHlpStrPCopy.c \ + kStuff/kHlp/Generic/kHlpStrNLen.c \ + kStuff/kHlp/Generic/kHlpInt2Ascii.c \ + \ + kStuff/kHlp/Generic/kHlpGetEnvUZ.c \ + \ + kStuff/kHlp/Generic/kHlpGetExt.c \ + kStuff/kHlp/Generic/kHlpGetFilename.c \ + kStuff/kHlp/Generic/kHlpIsFilenameOnly.c \ + \ + kStuff/kHlp/Generic/kHlpPage.c \ + \ + kStuff/kHlp/CRT/kHlpCRTAlloc.cpp \ + kStuff/kHlp/CRT/kHlpCRTEnv.cpp \ + kStuff/kHlp/CRT/kHlpCRTString.cpp +kStuff_SOURCES.darwin += \ + kStuff/kHlp/Bare/kHlpSys-darwin.c + + +# +# A couple of dummy DLLs we use for grabbing LDR TLS entries. +# +DLLS += kWorkerTls1K kWorkerTls1K01 kWorkerTls1K02 kWorkerTls1K03 kWorkerTls1K04 kWorkerTls1K05 kWorkerTls1K06 kWorkerTls1K07 \ + kWorkerTls1K08 kWorkerTls1K09 kWorkerTls1K10 kWorkerTls1K11 kWorkerTls1K12 kWorkerTls1K13 kWorkerTls1K14 kWorkerTls1K15 +kWorkerTls1K_TEMPLATE = BIN-STATIC-THREADED +kWorkerTls1K_DEFS = TLS_SIZE=1024 +kWorkerTls1K_SOURCES = kWorkerTlsXxxK.c +kWorkerTls1K_LDFLAGS = /Entry:DummyDllEntry + +kWorkerTls1K01_EXTENDS = kWorkerTls1K +kWorkerTls1K02_EXTENDS = kWorkerTls1K +kWorkerTls1K03_EXTENDS = kWorkerTls1K +kWorkerTls1K04_EXTENDS = kWorkerTls1K +kWorkerTls1K05_EXTENDS = kWorkerTls1K +kWorkerTls1K06_EXTENDS = kWorkerTls1K +kWorkerTls1K07_EXTENDS = kWorkerTls1K +kWorkerTls1K08_EXTENDS = kWorkerTls1K +kWorkerTls1K09_EXTENDS = kWorkerTls1K +kWorkerTls1K10_EXTENDS = kWorkerTls1K +kWorkerTls1K11_EXTENDS = kWorkerTls1K +kWorkerTls1K12_EXTENDS = kWorkerTls1K +kWorkerTls1K13_EXTENDS = kWorkerTls1K +kWorkerTls1K14_EXTENDS = kWorkerTls1K +kWorkerTls1K15_EXTENDS = kWorkerTls1K + + +DLLS += kWorkerTls64K kWorkerTls64K01 kWorkerTls64K02 kWorkerTls64K03 kWorkerTls64K04 kWorkerTls64K05 kWorkerTls64K06 kWorkerTls64K07 +kWorkerTls64K_TEMPLATE = BIN-STATIC-THREADED +kWorkerTls64K_DEFS = TLS_SIZE=65536 +kWorkerTls64K_SOURCES = kWorkerTlsXxxK.c +kWorkerTls64K_LDFLAGS = /Entry:DummyDllEntry + +kWorkerTls64K01_EXTENDS = kWorkerTls64K +kWorkerTls64K02_EXTENDS = kWorkerTls64K +kWorkerTls64K03_EXTENDS = kWorkerTls64K +kWorkerTls64K04_EXTENDS = kWorkerTls64K +kWorkerTls64K05_EXTENDS = kWorkerTls64K +kWorkerTls64K06_EXTENDS = kWorkerTls64K +kWorkerTls64K07_EXTENDS = kWorkerTls64K + + +DLLS += kWorkerTls128K kWorkerTls128K01 kWorkerTls128K02 kWorkerTls128K03 kWorkerTls128K04 kWorkerTls128K05 kWorkerTls128K06 kWorkerTls128K07 +kWorkerTls128K_TEMPLATE = BIN-STATIC-THREADED +kWorkerTls128K_DEFS = TLS_SIZE=131072 +kWorkerTls128K_SOURCES = kWorkerTlsXxxK.c +kWorkerTls128K_LDFLAGS = /Entry:DummyDllEntry + +kWorkerTls128K01_EXTENDS = kWorkerTls128K +kWorkerTls128K02_EXTENDS = kWorkerTls128K +kWorkerTls128K03_EXTENDS = kWorkerTls128K +kWorkerTls128K04_EXTENDS = kWorkerTls128K +kWorkerTls128K05_EXTENDS = kWorkerTls128K +kWorkerTls128K06_EXTENDS = kWorkerTls128K +kWorkerTls128K07_EXTENDS = kWorkerTls128K + + +DLLS += kWorkerTls512K kWorkerTls512K01 kWorkerTls512K02 kWorkerTls512K03 kWorkerTls512K04 kWorkerTls512K05 kWorkerTls512K06 kWorkerTls512K07 +kWorkerTls512K_TEMPLATE = BIN-STATIC-THREADED +kWorkerTls512K_DEFS = TLS_SIZE=524288 +kWorkerTls512K_SOURCES = kWorkerTlsXxxK.c +kWorkerTls512K_LDFLAGS = /Entry:DummyDllEntry + +kWorkerTls512K01_EXTENDS = kWorkerTls512K +kWorkerTls512K02_EXTENDS = kWorkerTls512K +kWorkerTls512K03_EXTENDS = kWorkerTls512K +kWorkerTls512K04_EXTENDS = kWorkerTls512K +kWorkerTls512K05_EXTENDS = kWorkerTls512K +kWorkerTls512K06_EXTENDS = kWorkerTls512K +kWorkerTls512K07_EXTENDS = kWorkerTls512K + + +include $(KBUILD_PATH)/subfooter.kmk + diff --git a/src/kWorker/kWorker.c b/src/kWorker/kWorker.c new file mode 100644 index 0000000..6351e9d --- /dev/null +++ b/src/kWorker/kWorker.c @@ -0,0 +1,14100 @@ +/* $Id: kWorker.c 3522 2021-12-19 12:11:21Z 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 <bird-kBuild-spamx@anduin.net> + * + * 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 <http://www.gnu.org/licenses/> + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +//#undef NDEBUG +//#define K_STRICT 1 +//#define KW_LOG_ENABLED + +#define PSAPI_VERSION 1 +#include <k/kHlp.h> +#include <k/kLdr.h> + +#include <stdio.h> +#include <intrin.h> +#include <setjmp.h> +#include <ctype.h> +#include <errno.h> +#include <process.h> + +#include "nt/ntstat.h" +#include "kbuild_version.h" + +#include "nt/ntstuff.h" +#include "nt/nthlp.h" +#include <psapi.h> + +#include "nt/kFsCache.h" +#include "nt_fullpath.h" +#include "win_get_processor_group_active_mask.h" +#include "quote_argv.h" +#include "md5.h" +#include "console.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_CACHE + * Enables caching of MD5, SHA-1, SHA-256 and SHA-512 hashes for cl.exe. + * This prevents wasting time on rehashing common headers each time + * they are included. */ +#define WITH_HASH_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 + + +/** + * Generate CRT slot wrapper functions. + */ +#define CRT_SLOT_FUNCTION_WRAPPER(a_RetTypeAndCallConv, a_FnName, a_aArgsDecl, a_aArgCall) \ + static a_RetTypeAndCallConv a_FnName##00 a_aArgsDecl { const unsigned iCrtSlot = 0; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##01 a_aArgsDecl { const unsigned iCrtSlot = 1; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##02 a_aArgsDecl { const unsigned iCrtSlot = 2; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##03 a_aArgsDecl { const unsigned iCrtSlot = 3; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##04 a_aArgsDecl { const unsigned iCrtSlot = 4; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##05 a_aArgsDecl { const unsigned iCrtSlot = 5; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##06 a_aArgsDecl { const unsigned iCrtSlot = 6; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##07 a_aArgsDecl { const unsigned iCrtSlot = 7; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##08 a_aArgsDecl { const unsigned iCrtSlot = 8; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##09 a_aArgsDecl { const unsigned iCrtSlot = 9; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##10 a_aArgsDecl { const unsigned iCrtSlot = 10; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##11 a_aArgsDecl { const unsigned iCrtSlot = 11; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##12 a_aArgsDecl { const unsigned iCrtSlot = 12; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##13 a_aArgsDecl { const unsigned iCrtSlot = 13; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##14 a_aArgsDecl { const unsigned iCrtSlot = 14; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##15 a_aArgsDecl { const unsigned iCrtSlot = 15; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##16 a_aArgsDecl { const unsigned iCrtSlot = 16; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##17 a_aArgsDecl { const unsigned iCrtSlot = 17; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##18 a_aArgsDecl { const unsigned iCrtSlot = 18; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##19 a_aArgsDecl { const unsigned iCrtSlot = 19; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##20 a_aArgsDecl { const unsigned iCrtSlot = 20; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##21 a_aArgsDecl { const unsigned iCrtSlot = 21; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##22 a_aArgsDecl { const unsigned iCrtSlot = 22; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##23 a_aArgsDecl { const unsigned iCrtSlot = 23; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##24 a_aArgsDecl { const unsigned iCrtSlot = 24; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##25 a_aArgsDecl { const unsigned iCrtSlot = 25; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##26 a_aArgsDecl { const unsigned iCrtSlot = 26; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##27 a_aArgsDecl { const unsigned iCrtSlot = 27; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##28 a_aArgsDecl { const unsigned iCrtSlot = 28; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##29 a_aArgsDecl { const unsigned iCrtSlot = 29; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##30 a_aArgsDecl { const unsigned iCrtSlot = 30; return a_FnName##_wrapped a_aArgCall; } \ + static a_RetTypeAndCallConv a_FnName##31 a_aArgsDecl { const unsigned iCrtSlot = 31; return a_FnName##_wrapped a_aArgCall; } \ + static const KUPTR a_FnName[] = \ + { \ + (KUPTR)a_FnName##00, \ + (KUPTR)a_FnName##01, \ + (KUPTR)a_FnName##02, \ + (KUPTR)a_FnName##03, \ + (KUPTR)a_FnName##04, \ + (KUPTR)a_FnName##05, \ + (KUPTR)a_FnName##06, \ + (KUPTR)a_FnName##07, \ + (KUPTR)a_FnName##08, \ + (KUPTR)a_FnName##09, \ + (KUPTR)a_FnName##10, \ + (KUPTR)a_FnName##11, \ + (KUPTR)a_FnName##12, \ + (KUPTR)a_FnName##13, \ + (KUPTR)a_FnName##14, \ + (KUPTR)a_FnName##15, \ + (KUPTR)a_FnName##16, \ + (KUPTR)a_FnName##17, \ + (KUPTR)a_FnName##18, \ + (KUPTR)a_FnName##19, \ + (KUPTR)a_FnName##20, \ + (KUPTR)a_FnName##21, \ + (KUPTR)a_FnName##22, \ + (KUPTR)a_FnName##23, \ + (KUPTR)a_FnName##24, \ + (KUPTR)a_FnName##25, \ + (KUPTR)a_FnName##26, \ + (KUPTR)a_FnName##27, \ + (KUPTR)a_FnName##28, \ + (KUPTR)a_FnName##29, \ + (KUPTR)a_FnName##30, \ + (KUPTR)a_FnName##31, \ + } + + +/********************************************************************************************************************************* +* 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 withe the same hash. */ + PKWMODULE pNextHash; + /** Pointer to the next image in the global list. */ + PKWMODULE pNextList; + /** 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; + /** The offset of the filename in pwszPath. */ + KU16 offFilenameW; + /** Set if executable. */ + KBOOL fExe; + /** Set if native module entry. */ + KBOOL fNative; + /** Loader module handle. */ + PKLDRMOD pLdrMod; + /** The windows module handle. */ + HMODULE hOurMod; + /** Parent (real) module if this is a virtual API module (api-ms-*.dll or + * ext-ms-*.dll). Referenced. */ + PKWMODULE pVirtualApiMod; + /** The of the loaded image bits. */ + KSIZE cbImage; + /** The CRT slot for this module, if applicable (KU8_MAX when not). */ + KU8 iCrtSlot; + /** Loop prevention when working the tree. */ + KBOOL fVisited; + /** HACK: Set if re-init is needed (fReInitOnMsPdbSrvEndpointChange). */ + KBOOL fNeedReInit; + /** HACK: Reinit when _MSPDBSRV_ENDPOINT_ changes, K_FALSE if not applicable. + * 1 if applicable but not yet used, 2 if used and have pszMsPdbSrvEndpoint. */ + KU8 fReInitOnMsPdbSrvEndpointChange; + /** HACK: The old _MSPDBSRV_ENDPOINT_ value. */ + char *pszMsPdbSrvEndpoint; + + 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; + /** The re-init state. */ + KWMODSTATE enmReInitState; +#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]; + + /** Pointer to g_abInitData of the kWorkerTlsXxxK.c instance. + * This member is set by kwLdrTlsAllocationHook. */ + KU8 *pabTlsInitData; + /** Pointer to the g_pvWorkerModule variable in kWorkerTlsXxxK.c (our instance + * of it). This member is set by kwLdrTlsAllocationHook. Used by our + * destructor to prevent after-free references. */ + PKWMODULE *ppTlsWorkerModuleVar; + /** TLS index if one was allocated, otherwise KU32_MAX. + * This member is set by kwLdrTlsAllocationHook. */ + 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; + const wchar_t *pwszName; + KU8 cchName; + KU8 cwcName; + KBOOL fAlwaysPresent; + HANDLE hmod; +} KWGETMODULEHANDLECACHE; +typedef KWGETMODULEHANDLECACHE *PKWGETMODULEHANDLECACHE; + + +/** One TLS DLL. */ +typedef struct KWTLSDLL +{ + const wchar_t *pwszName; /**< The DLL name. */ + KBOOL fUsed; /**< Set if used, clear if not. */ +} KWTLSDLL; +typedef KWTLSDLL *PKWTLSDLL; + +/** + * TLS DLL tracker. + */ +typedef struct KWTLSDLLENTRY +{ + KU32 cbTls; /**< Max TLS size. */ + KU32 cDlls; /**< Number of DLLs we ship (paDlls). */ + PKWTLSDLL paDlls; /**< Array of DLLs we ship. */ +} KWTLSDLLENTRY; +typedef KWTLSDLLENTRY *PKWTLSDLLENTRY; + + +/** + * 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_CACHE + /** Set if we've got a valid MD5 hash in abMd5Digest. */ + KBOOL fValidMd5; + /** Set if we've got a valid SHA-1 hash in abMd5Digest. */ + KBOOL fValidSha1; + /** Set if we've got a valid SHA-256 hash in abMd5Digest. */ + KBOOL fValidSha256; + /** Set if we've got a valid SHA-512 hash in abMd5Digest. */ + KBOOL fValidSha512; + /** The MD5 digest if fValidMd5 is set. */ + KU8 abMd5Digest[16]; + /** The SHA-1 digest if fValidSha1 is set. */ + KU8 abSha1Digest[20]; + /** The SHA-256 digest if fValidSha256 is set. */ + KU8 abSha256Digest[32]; + /** The SHA-512 digest if fValidSha256 is set. */ + KU8 abSha512Digest[64]; +#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_CACHE + +/** Pointer to a MD5 hash instance. */ +typedef struct KWCRYPTHASH *PKWCRYPTHASH; +/** + * A MD5 hash instance. + */ +typedef struct KWCRYPTHASH +{ + /** The magic value. */ + KUPTR uMagic; + /** Pointer to the next hash handle. */ + PKWCRYPTHASH 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've already finalized the digest. */ + KBOOL fFinal; + /** If in fallback mode. */ + HCRYPTHASH hFallback; + /** The algorithm. */ + ALG_ID idAlg; + /** The hash name. */ + const char *pszAlgName; + /** The digest size. */ + KU32 cbDigest; + /** The finalized digest. */ + KU8 abDigest[64]; +} KWCRYPTHASH; +/** Magic value for KWCRYPTHASH::uMagic (Les McCann). */ +# define KWCRYPTHASH_MAGIC KUPTR_C(0x19350923) + +#endif /* WITH_HASH_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, +#ifdef WITH_TEMP_MEMORY_FILES + KWHANDLETYPE_TEMP_FILE, + KWHANDLETYPE_TEMP_FILE_MAPPING, +#endif + 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; + /** The current owner (GetCurrentThreadId). */ + KU32 tidOwner; + + /** Type specific data. */ + union + { + /** The file system object. */ + PKFSWCACHEDFILE pCachedFile; +#ifdef WITH_TEMP_MEMORY_FILES + /** Temporary file handle or mapping handle. */ + PKWFSTEMPFILE pTempFile; +#endif +#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; +#ifdef WITH_TEMP_MEMORY_FILES + /** Temporary file handle or mapping handle. */ + PKWFSTEMPFILE pTempFile; +#endif + } 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 +{ + /** Jump buffer (first for alignment reasons). */ + jmp_buf JmpBuf; + /** The tool currently running in the sandbox. */ + PKWTOOL pTool; + /** 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; + + + /** Critical section protecting the below handle members below. + * @note Does not protect the individual handles. */ + CRITICAL_SECTION HandlesLock; + /** 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; + +#ifdef WITH_TEMP_MEMORY_FILES + /** Head of the list of temporary file. */ + PKWFSTEMPFILE pTempFileHead; +#endif + + /** Critical section protecting pVirtualAllocHead. */ + CRITICAL_SECTION VirtualAllocLock; + /** 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_CACHE + /** The crypto provider instance we use for hashes. */ + HCRYPTPROV hCryptProvRsa; + /** The crypto provider instance we use for hashes. */ + HCRYPTPROV hCryptProvAes; + /** List of crypto hash instances. */ + PKWCRYPTHASH pHashHead; + /** ReadFile sets these while CryptHashData claims and clears them. + * + * This is part of the heuristics we use for MD5/SHA1/SHA256 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; + + +/** A CRT slot. */ +typedef struct KWCRTSLOT +{ + KU32 iSlot; + + /** The CRT module data. */ + PKWMODULE pModule; + /** Pointer to the malloc function. */ + void * (__cdecl *pfnMalloc)(size_t); + /** Pointer to the beginthreadex function. */ + uintptr_t (__cdecl *pfnBeginThreadEx)(void *, unsigned, unsigned (__stdcall *)(void *), void *, unsigned, unsigned *); + +} KWCRTSLOT; +typedef KWCRTSLOT *PKWCRTSLOT; + + +/** 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, data address or CRT slot function array. */ + KUPTR pfnReplacement; + /** Only replace in the executable. + * @todo fix the reinitialization of non-native DLLs! */ + KBOOL fOnlyExe; + /** Set if pfnReplacement points to a CRT slot function array. */ + KBOOL fCrtSlotArray; +} 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 + +/** + * One test job (--full-test). + */ +typedef struct KWONETEST +{ + /** Where this job originated. */ + const char *pszJobSrc; + /** The argument number it started with. */ + unsigned iJobSrc; + /** Set if virgin, clear if modified. */ + KBOOL fVirgin; + + /** Number of runs to give it. */ + unsigned cRuns; + + /** @name kSubmitHandleJobUnpacked arguments + * @{ */ + const char *pszExecutable; + const char *pszCwd; + KU32 cArgs; + const char **papszArgs; + KU32 cEnvVars; + const char **papszEnvVars; + const char *pszSpecialEnv; + KBOOL fWatcomBrainDamange; + KBOOL fNoPchCaching; + KU32 cPostCmdArgs; + const char **papszPostCmdArgs; + /** @} */ + + /** Pointer to the next one. */ + struct KWONETEST *pNext; +} KWONETEST; +/** Pointer to one test job. */ +typedef KWONETEST *PKWONETEST; + + +/********************************************************************************************************************************* +* 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 list head. */ +static PKWMODULE g_pModuleHead = NULL; +/** Where to insert the next module. */ +static PKWMODULE *g_ppModuleNext = &g_pModuleHead; + +/** Module hash table. */ +static PKWMODULE g_apModules[127]; + +/** GetModuleHandle cache. */ +static KWGETMODULEHANDLECACHE g_aGetModuleHandleCache[] = +{ +#define MOD_CACHE_STRINGS(str) str, L##str, sizeof(str) - 1, (sizeof(L##str) / sizeof(wchar_t)) - 1 + { MOD_CACHE_STRINGS("KERNEL32.DLL"), K_TRUE, NULL }, +#if 1 + { MOD_CACHE_STRINGS("KERNELBASE.DLL"), K_TRUE, NULL }, + { MOD_CACHE_STRINGS("NTDLL.DLL"), K_TRUE, NULL }, +#endif + { MOD_CACHE_STRINGS("mscoree.dll"), K_FALSE, NULL }, +}; + +/** Module pending TLS allocation. See kwLdrModuleCreateNonNativeSetupTls. */ +static PKWMODULE g_pModPendingTlsAlloc = NULL; + +/** The 1KB TLS DLLs. */ +static KWTLSDLL g_aTls1KDlls[] = +{ + { L"kWorkerTls1K.dll", K_FALSE }, + { L"kWorkerTls1K01.dll", K_FALSE }, + { L"kWorkerTls1K02.dll", K_FALSE }, + { L"kWorkerTls1K03.dll", K_FALSE }, + { L"kWorkerTls1K04.dll", K_FALSE }, + { L"kWorkerTls1K05.dll", K_FALSE }, + { L"kWorkerTls1K06.dll", K_FALSE }, + { L"kWorkerTls1K07.dll", K_FALSE }, + { L"kWorkerTls1K08.dll", K_FALSE }, + { L"kWorkerTls1K09.dll", K_FALSE }, + { L"kWorkerTls1K10.dll", K_FALSE }, + { L"kWorkerTls1K11.dll", K_FALSE }, + { L"kWorkerTls1K12.dll", K_FALSE }, + { L"kWorkerTls1K13.dll", K_FALSE }, + { L"kWorkerTls1K14.dll", K_FALSE }, + { L"kWorkerTls1K15.dll", K_FALSE }, +}; + +/** The 64KB TLS DLLs. */ +static KWTLSDLL g_aTls64KDlls[] = +{ + { L"kWorkerTls64K.dll", K_FALSE }, + { L"kWorkerTls64K01.dll", K_FALSE }, + { L"kWorkerTls64K02.dll", K_FALSE }, + { L"kWorkerTls64K03.dll", K_FALSE }, + { L"kWorkerTls64K04.dll", K_FALSE }, + { L"kWorkerTls64K05.dll", K_FALSE }, + { L"kWorkerTls64K06.dll", K_FALSE }, + { L"kWorkerTls64K07.dll", K_FALSE }, +}; + +/** The 128KB TLS DLLs. */ +static KWTLSDLL g_aTls128KDlls[] = +{ + { L"kWorkerTls128K.dll", K_FALSE }, + { L"kWorkerTls128K01.dll", K_FALSE }, + { L"kWorkerTls128K02.dll", K_FALSE }, + { L"kWorkerTls128K03.dll", K_FALSE }, + { L"kWorkerTls128K04.dll", K_FALSE }, + { L"kWorkerTls128K05.dll", K_FALSE }, + { L"kWorkerTls128K06.dll", K_FALSE }, + { L"kWorkerTls128K07.dll", K_FALSE }, +}; + +/** The 512KB TLS DLLs. */ +static KWTLSDLL g_aTls512KDlls[] = +{ + { L"kWorkerTls512K.dll", K_FALSE }, + { L"kWorkerTls512K01.dll", K_FALSE }, + { L"kWorkerTls512K02.dll", K_FALSE }, + { L"kWorkerTls512K03.dll", K_FALSE }, + { L"kWorkerTls512K04.dll", K_FALSE }, + { L"kWorkerTls512K05.dll", K_FALSE }, + { L"kWorkerTls512K06.dll", K_FALSE }, + { L"kWorkerTls512K07.dll", K_FALSE }, +}; + +/** The TLS DLLs grouped by size. */ +static KWTLSDLLENTRY const g_aTlsDlls[] = +{ + { 1024, K_ELEMENTS(g_aTls1KDlls), g_aTls1KDlls }, + { 64*1024, K_ELEMENTS(g_aTls64KDlls), g_aTls64KDlls }, + { 128*1024, K_ELEMENTS(g_aTls128KDlls), g_aTls128KDlls }, + { 512*1024, K_ELEMENTS(g_aTls512KDlls), g_aTls512KDlls }, +}; + +/** CRT slots. + * @note The number of entires here must match CRT_SLOT_FUNCTION_WRAPPER. */ +static KWCRTSLOT g_aCrtSlots[32]; + +/** windbg .reload statements. vs */ +char g_szReloads[4096]; +/** Current offset into g_szReloads. */ +KU32 volatile g_cchReloads; + +/** 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; + +/** The process group this worker is tied to (--group option), -1 if none. */ +static KI32 g_iProcessGroup = -1; + +/** Whether control-C/SIGINT or Control-Break/SIGBREAK have been seen. */ +static int volatile g_rcCtrlC = 0; + +/** The communication pipe handle. We break this when we see Ctrl-C such. */ +#ifdef KBUILD_OS_WINDOWS +static HANDLE g_hPipe = INVALID_HANDLE_VALUE; +#else +static int g_hPipe = -1; +#endif + + +/* 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; + +#ifdef WITH_HASH_CACHE +/** Total number of hashes. */ +static KSIZE g_cHashes; +/** Number of cached hash hits. */ +static KSIZE g_cHashesCached; +/** Number of fallbacks. */ +static KSIZE g_cHashesFallbacks; +/** Number of partial cached file hashes. */ +static KSIZE g_cHashesPartial; +/** Total number of MD5 hashes. */ +static KSIZE g_cHashesMd5; +/** Total number of SHA-1 hashes. */ +static KSIZE g_cHashesSha1; +/** Total number of SHA-256 hashes. */ +static KSIZE g_cHashesSha256; +/** Total number of SHA-512 hashes. */ +static KSIZE g_cHashesSha512; +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static FNKLDRMODGETIMPORT kwLdrModuleGetImportCallback; +static int kwLdrModuleResolveAndLookup(const char *pszName, PKWMODULE pExe, PKWMODULE pImporter, + const char *pszSearchPath, PKWMODULE *ppMod); +static PKWMODULE kwLdrModuleForLoadedNative(const char *pszName, KBOOL fEnsureCrtSlot, KBOOL fAlwaysPresent); +static PKWMODULE kwLdrModuleForLoadedNativeByHandle(HMODULE hModule, KBOOL fEnsureCrtSlot, const char *pszLogName); +static int kwLdrModuleCreateCrtSlot(PKWMODULE pModule); +static PKWMODULE kwToolLocateModuleByHandle(PKWTOOL pTool, HMODULE hmod); +static char *kwSandboxDoGetEnvA(PKWSANDBOX pSandbox, const char *pchVar, KSIZE cchVar); +static KBOOL kwSandboxHandleTableEnter(PKWSANDBOX pSandbox, PKWHANDLE pHandle, HANDLE hHandle); +static PKWHANDLE kwSandboxHandleLookup(HANDLE hFile); +static PKWHANDLE kwSandboxHandleGet(HANDLE hFile); +K_INLINE void kwSandboxHandlePut(PKWHANDLE pHandle); +#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(); + +#if defined(KW_LOG_ENABLED) && defined(WITH_LOG_FILE) + va_list vaCopy; +# if defined(va_copy) || !defined(_MSC_VER) || _MSC_VER >= 1700 /*??*/ + va_copy(vaCopy, va); +# else + vaCopy = va; +# endif + kwDebuggerPrintf("kWorker: error: "); + kwDebuggerPrintfV(pszFormat, vaCopy); +#endif + + fprintf(stderr, "kWorker: error: "); + vfprintf(stderr, pszFormat, va); + fflush(stderr); /* In case it's a pipe. */ + + 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->fNative /* kernelbase.dll and VC++ 14.2 */); + pMod->cRefs++; + return pMod; +} + + +/** + * Releases a module reference. + * + * @param pMod The module to release. + */ +static void kwLdrModuleRelease(PKWMODULE pMod) +{ + if (--pMod->cRefs == 0) + { + /* Make sure it doesn't receive any more native TLS callbacks.if non-native. */ + if (!pMod->fNative && pMod->u.Manual.ppTlsWorkerModuleVar) + { + *pMod->u.Manual.ppTlsWorkerModuleVar = NULL; + pMod->u.Manual.ppTlsWorkerModuleVar = NULL; + } + + /* Unlink it from the hash table. */ + if (!pMod->fExe) + { + unsigned idx = pMod->uHashPath % K_ELEMENTS(g_apModules); + if (g_apModules[idx] == pMod) + g_apModules[idx] = pMod->pNextHash; + else + { + PKWMODULE pPrev = g_apModules[idx]; + kHlpAssert(pPrev != NULL); + while (pPrev->pNextHash != pMod) + { + pPrev = pPrev->pNextHash; + kHlpAssert(pPrev != NULL); + } + pPrev->pNextHash = pMod->pNextHash; + } + } + + /* Unlink it from the list. */ + if (pMod != g_pModuleHead) + { + PKWMODULE pPrev = g_pModuleHead; + while (pPrev) + { + if (pPrev->pNextList == pMod) + { + pPrev->pNextList = pMod->pNextList; + if (!pMod->pNextList) + g_ppModuleNext = &pPrev->pNextList; + break; + } + pPrev = pPrev->pNextList; + } + kHlpAssert(pPrev != NULL); + } + else + { + g_pModuleHead = pMod->pNextList; + if (!pMod->pNextList) + g_ppModuleNext = &g_pModuleHead; + } + + /* 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); + } + + if (pMod->iCrtSlot != KU8_MAX) + g_aCrtSlots[pMod->iCrtSlot].pModule = NULL; + + if (pMod->pszMsPdbSrvEndpoint) + { + kHlpFree(pMod->pszMsPdbSrvEndpoint); + pMod->pszMsPdbSrvEndpoint = NULL; + } + + kHlpFree(pMod); + } + else + kHlpAssert(pMod->cRefs < 64 || pMod->fNative /* kernelbase.dll and VC++ 14.2 */); +} + + +/** + * Links the module into the module hash table. + * + * @returns pMod + * @param pMod The module to link. + */ +static PKWMODULE kwLdrModuleLink(PKWMODULE pMod) +{ + if (!pMod->fExe) + { + unsigned idx = pMod->uHashPath % K_ELEMENTS(g_apModules); + pMod->pNextHash = g_apModules[idx]; + g_apModules[idx] = pMod; + } + + pMod->pNextList = NULL; + *g_ppModuleNext = pMod; + g_ppModuleNext = &pMod->pNextList; + + 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]; + PKWMODULE pImportMod = NULL; + 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((const char *)&pName->Name[0]); + 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) + { + KWLDR_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 || GetLastError() == ERROR_NOACCESS /*tinderwin2*/); + 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. */ + fRc = TRUE; + 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); + } + } + + /* + * Unslotted replacements are simple. + */ + if (!g_aSandboxNativeReplacements[i].fCrtSlotArray) + paThunks[iThunk].u1.AddressOfData = g_aSandboxNativeReplacements[i].pfnReplacement; + else + { + /* + * Must find our module entry for this module, possibly creating one. + */ + if (!pImportMod) + { + pImportMod = kwLdrModuleForLoadedNative(pszImport, K_TRUE /*fEnsureCrtSlot*/, + K_TRUE /*fAlwaysPresent*/); + if (!pImportMod) + { + kwErrPrintf("Failed to get module '%s' when performing replacements on module '%s'!\n", + pszImport, pMod->pszPath); + break; + } + } + paThunks[iThunk].u1.AddressOfData + = ((KUPTR *)g_aSandboxNativeReplacements[i].pfnReplacement)[pImportMod->iCrtSlot]; + } + 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, PKWMODULE pVirtualApiMod) +{ + /* + * 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->offFilenameW = (KU16)(kwPathGetFilenameW(pMod->pwszPath) - pMod->pwszPath); + pMod->fExe = K_FALSE; + pMod->fNative = K_TRUE; + pMod->pLdrMod = pLdrMod; + pMod->hOurMod = (HMODULE)(KUPTR)pLdrMod->aSegments[0].MapAddress; + pMod->cbImage = (KSIZE)kLdrModSize(pLdrMod); + pMod->iCrtSlot = KU8_MAX; + pMod->fNeedReInit = K_FALSE; + pMod->pszMsPdbSrvEndpoint = NULL; + pMod->fReInitOnMsPdbSrvEndpointChange = kHlpStrNICompAscii(&pMod->pszPath[pMod->offFilename], TUPLE("mspdb")) == 0; + pMod->pVirtualApiMod = pVirtualApiMod; + if (pVirtualApiMod) + kwLdrModuleRetain(pVirtualApiMod); + + if (fDoReplacements) + { + DWORD const dwSavedErr = GetLastError(); + kwLdrModuleDoNativeImportReplacements(pMod); + SetLastError(dwSavedErr); + } + + KWLDR_LOG(("New module: %p LB %#010x %s (native%s%s)\n", + (KUPTR)pMod->pLdrMod->aSegments[0].MapAddress, kLdrModSize(pMod->pLdrMod), pMod->pszPath, + pVirtualApiMod ? ", virtual api => " : "", pVirtualApiMod ? pVirtualApiMod->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) +{ + PKLDRMOD pLdrMod; + int rc; + + /* + * HACK ALERT! Make sure the application path is searched when looking for + * imports in the module we're loading. + */ + /** @todo improve on this hack! */ + PKWMODULE pExe = g_Sandbox.pTool ? g_Sandbox.pTool->u.Sandboxed.pExe : NULL; + if (pExe) + { + /* HACK ALERT! */ + wchar_t *pwzFilename = (wchar_t *)&pExe->pwszPath[pExe->offFilenameW]; + wchar_t wcSaved = pExe->pwszPath[pExe->offFilenameW]; + *pwzFilename = '\0'; + if (!SetDllDirectoryW(pExe->pwszPath)) + kwErrPrintf("SetDllDirectoryW failed: %u\n", GetLastError()); + KW_LOG(("kwLdrModuleCreateNative: Applied SetDllDirectoryW hack (%ls)\n", pExe->pwszPath)); + *pwzFilename = wcSaved; + } + else + KW_LOG(("kwLdrModuleCreateNative: Warning! Too early for SetDllDirectoryW hack\n")); + + + /* + * Load the library and create a module structure for it. + */ + rc = kLdrModOpenNative(pszPath, KLDRMOD_OPEN_FLAGS_NATIVE_ALLOW_INIT_TERM, &pLdrMod); + if (rc == 0) + { + KSIZE cchPath = kHlpStrLen(pszPath); + PKWMODULE pMod = kwLdrModuleCreateForNativekLdrModule(pLdrMod, pszPath, cchPath + 1, uHashPath, + fDoReplacements, NULL /*pVirtualApiMod*/); + if (pMod) + return pMod; + kLdrModClose(pLdrMod); + } + return NULL; +} + + +/** + * Checks if the given name could be a virtual API module or not. + */ +static KBOOL kwLdrIsVirtualApiModule(const char *pszName, KSIZE cchName) +{ + if (cchName <= 7) + return K_FALSE; + switch (*pszName) + { + default: + return K_FALSE; + case 'a': + case 'A': + if (pszName[1] != 'p' && pszName[1] != 'P') + return K_FALSE; + if (pszName[2] != 'i' && pszName[2] != 'I') + return K_FALSE; + break; + case 'e': + case 'E': + if (pszName[1] != 'x' && pszName[1] != 'X') + return K_FALSE; + if (pszName[2] != 't' && pszName[2] != 'T') + return K_FALSE; + break; + } + if (pszName[3] != '-') + return K_FALSE; + if (pszName[4] != 'm' && pszName[4] != 'M') + return K_FALSE; + if (pszName[5] != 's' && pszName[5] != 'S') + return K_FALSE; + if (pszName[6] != '-') + return K_FALSE; + return K_TRUE; +} + + +/** + * Try load what seems to be a virtual API DLL. + * + * This is a worker for kwLdrModuleResolveAndLookup and + * kwSandbox_Kernel32_LoadLibraryExA_VirtualApiModule. + * + * @returns Pointer to module on success, NULL on failure. + * @param pszName The name of the module. This must be + * normalized already! + * @param cchName The length of the name. + */ +static PKWMODULE kwLdrModuleTryLoadVirtualDll(const char *pszName, KSIZE cchName) +{ + HMODULE hModule; + + /* + * Look it up in the hash table. + */ + KU32 const uHashPath = kwStrHash(pszName); + unsigned idxHash = uHashPath % K_ELEMENTS(g_apModules); + PKWMODULE pMod = g_apModules[idxHash]; + if (pMod) + { + do + { + if ( pMod->uHashPath == uHashPath + && kHlpStrComp(pMod->pszPath, pszName) == 0) + return kwLdrModuleRetain(pMod); + pMod = pMod->pNextHash; + } while (pMod); + } + + /* + * Not found. Try load it. + */ + hModule = LoadLibraryA(pszName); + if (!hModule) + { + KWLDR_LOG(("kwLdrModuleTryLoadVirtualDll: %s failed (%u)\n", pszName, GetLastError())); + return NULL; + } + + /* + * Loaded successfully. Create a module for the real module. + */ + pMod = kwLdrModuleForLoadedNativeByHandle(hModule, K_FALSE /*fEnsureCrtSlot*/, pszName); + if (pMod) + { + /* Create a module for the virtual API name too, unless it is actually a real DLL. */ + if (stricmp(&pMod->pszPath[pMod->offFilename], pszName) != 0) + { + PKLDRMOD pLdrMod; + int rc = kLdrModOpenNativeByHandle((KUPTR)hModule, KLDRMOD_OPEN_FLAGS_NATIVE_ALLOW_INIT_TERM, &pLdrMod); + if (rc == 0) + { + PKWMODULE pVirtMod = kwLdrModuleCreateForNativekLdrModule(pLdrMod, pszName, cchName + 1, kwStrHash(pszName), + K_FALSE /*fDoReplacements*/, pMod /*pVirtualApiMod*/); + if (pVirtMod) + { + kwLdrModuleRelease(pMod); + pMod = pVirtMod; + } + else + { + kLdrModClose(pLdrMod); + kwErrPrintf("out of memory\n"); + } + } + else + kwErrPrintf("kLdrModOpenNativeByHandle failed for %p / '%s': %d\n", hModule, pszName, rc); + } + else + { + KWLDR_LOG(("kwLdrModuleTryLoadVirtualDll: %s -> %s - A real DLL!\n", pszName, pMod->pszPath)); + /* HACK ALERT! If api-ms-win-crt-* find ucrtbase.dll and attach it as the + real module as we cannot make replacements in the virtual + API set forward DLLs. */ + /** @todo Find a way of scanning the exports and collect forwarder DLLs and + * imported DLLs. kLdrModEnumSymbols()? */ + if ( pMod->pVirtualApiMod == NULL + && kHlpStrNICompAscii(pszName, TUPLE("api-ms-win-crt-")) == 0) + { + HMODULE hModReal = GetModuleHandleW(L"ucrtbase.dll"); + if (hModReal) + { + PKWMODULE pRealMod = kwLdrModuleForLoadedNativeByHandle(hModReal, K_TRUE /*fEnsureCrtSlot*/, "ucrtbase.dll"); + if (pRealMod) + { + KWLDR_LOG(("kwLdrModuleTryLoadVirtualDll: Linking %s to '%s'.\n", pszName, pRealMod->pszPath)); + pMod->pVirtualApiMod = pRealMod; + } + else + KWLDR_LOG(("kwLdrModuleTryLoadVirtualDll: kwLdrModuleForLoadedNativeByHandle failed for ucrtbase.dll/%s!\n", pszName)); + } + else + KWLDR_LOG(("kwLdrModuleTryLoadVirtualDll: no ucrtbase.dll found for %s!\n", pszName)); + } + } + } + + return pMod; +} + + +/** + * 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 = (KU8)(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 = (KU8)(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 the TLS allocation DLL when ever the native loader wants to issue + * a TLS callback after the initial kwLdrTlsAllocationHook callout. + * + * @param hDll The DLL handle. + * @param dwReason The callback reason. + * @param pvContext Some context value that seems to always be NULL. + * @param pMod Out internal module. + */ +static void kwLdrTlsNativeLoaderCallback(void *hDll, DWORD dwReason, void *pvContext, PKWMODULE pMod) +{ + if ( pMod + && pMod->u.Manual.enmState == KWMODSTATE_READY) + { + KWLDR_LOG(("kwLdrTlsNativeLoaderCallback: hDll=%p dwReason=%#x pvContext=%p pMod=%p\n", + hDll, dwReason, pvContext, pMod)); + if (pMod->u.Manual.cTlsCallbacks) + { + PIMAGE_TLS_CALLBACK *ppfnCallback = (PIMAGE_TLS_CALLBACK *)&pMod->u.Manual.pbLoad[pMod->u.Manual.offTlsCallbacks]; + do + { + KWLDR_LOG(("kwLdrTlsNativeLoaderCallback: Calling TLS callback %p(%p, %#x, %p) - %s\n", + *ppfnCallback, pMod->hOurMod, dwReason, pvContext, pMod->pszPath)); + (*ppfnCallback)(pMod->hOurMod, dwReason, pvContext); + ppfnCallback++; + } while (*ppfnCallback); + } + } + else + KWLDR_LOG(("kwLdrTlsNativeLoaderCallback: hDll=%p dwReason=%#x pvContext=%p pMod=%p - skipped\n", + hDll, dwReason, pvContext, pMod)); +} + + +/** + * Called from TLS allocation DLL during DLL_PROCESS_ATTACH. + * + * @returns Address of the callback function (kwLdrTlsNativeLoaderCallback). + * @param hDll The DLL handle. + * @param idxTls The allocated TLS index. + * @param pabInitData The init data in the TLS allocation DLL + * (g_abInitData). + * @param ppWorkerModuleVar Pointer to the variable holding the pMod + * callback parameter value (g_pvWorkerModule). + * + * @see KWLDRTLSALLOCATIONHOOK in kWorkerTlsXxxxK.c + */ +__declspec(dllexport) KUPTR kwLdrTlsAllocationHook(void *hDll, ULONG idxTls, KU8 *pabInitData, PKWMODULE *ppWorkerModuleVar) +{ + /* + * Do the module initialization thing first. + */ + PKWMODULE pMod = g_pModPendingTlsAlloc; + if (pMod) + { + if ( pMod->u.Manual.idxTls == KU32_MAX + && pMod->u.Manual.pabTlsInitData == NULL) + { + pMod->u.Manual.idxTls = idxTls; + pMod->u.Manual.pabTlsInitData = pabInitData; + pMod->u.Manual.ppTlsWorkerModuleVar = ppWorkerModuleVar; + KWLDR_LOG(("kwLdrTlsAllocationHook: idxTls=%d (%#x) for %s\n", idxTls, idxTls, pMod->pszPath)); + +#if 0 /** @todo this doesn't work W10 18363 */ + { + /* + * Try sabotage the DLL name so we can load this module again. + */ + PPEB pPeb = kwSandboxGetProcessEnvironmentBlock(); + LIST_ENTRY *pHead; + LIST_ENTRY *pCur; + + 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; + } + } + } +#endif + + /* + * Don't return a callback function unless the module has callbacks to service. + */ + if (pMod->u.Manual.cTlsCallbacks > 0) + { + *ppWorkerModuleVar = pMod; + return (KUPTR)kwLdrTlsNativeLoaderCallback; + } + return 0; + } + KWLDR_LOG(("kwLdrTlsAllocationHook: WTF? pMod=%p: idxTls=%#x pabTlsInitData=%p\n", + pMod, pMod->u.Manual.idxTls, pMod->u.Manual.pabTlsInitData)); + } + return 0; +} + + +/** + * 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; + KU32 iTlsDll; + KU32 iTlsDllSub; + 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 kwLdrTlsAllocationHook. + */ + 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); + + /** @todo find better strategy here. Like temporary copy or whatever when + * there is more than a single user. */ + for (iTlsDll = 0; cbData > g_aTlsDlls[iTlsDll].cbTls;) + if (++iTlsDll >= K_ELEMENTS(g_aTlsDlls)) + { + kwErrPrintf("TLS data size in %s is too big: %u (%#p), max 512KB\n", pMod->pszPath, (unsigned)cbData, cbData); + return -1; + } + for (iTlsDllSub = 0; g_aTlsDlls[iTlsDll].paDlls[iTlsDllSub].fUsed;) + if (++iTlsDllSub >= g_aTlsDlls[iTlsDll].cDlls) + { + kwErrPrintf("No unused TLS DLLs for %s of size %u!\n", pMod->pszPath, (unsigned)cbData); + return -1; + } + + g_aTlsDlls[iTlsDll].paDlls[iTlsDllSub].fUsed = K_TRUE; + pwszTlsDll = g_aTlsDlls[iTlsDll].paDlls[iTlsDllSub].pwszName; + + pMod->u.Manual.pabTlsInitData = NULL; + pMod->u.Manual.ppTlsWorkerModuleVar = NULL; + 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 pabTlsInitData=%p\n", + pMod->u.Manual.idxTls, hmodTlsDll, pwszTlsDll, cbData, pMod->u.Manual.pabTlsInitData)); + + kHlpAssert(pMod->u.Manual.pabTlsInitData); + if (pMod->u.Manual.pabTlsInitData && pMod->u.Manual.cbTlsInitData) + kHlpMemCopy(pMod->u.Manual.pabTlsInitData, &pMod->u.Manual.pbCopy[pMod->u.Manual.offTlsInitData], + pMod->u.Manual.cbTlsInitData); + } + 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; + kwErrPrintf("kwLdrModuleCreateNonNative: Unsupported module type %d (%s)!\n", pLdrMod->enmType, pszPath); + 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->iCrtSlot = KU8_MAX; + pMod->fNeedReInit = K_FALSE; + pMod->fReInitOnMsPdbSrvEndpointChange = K_FALSE; + pMod->pszMsPdbSrvEndpoint = NULL; + 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.pabTlsInitData = NULL; + pMod->u.Manual.ppTlsWorkerModuleVar = NULL; + 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); + pMod->offFilenameW = (KU16)(kwPathGetFilenameW(pMod->pwszPath) - pMod->pwszPath); + + /* + * 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; + KU32 cchReloads; + + /* + * Link the module (unless it's an executable image) and process the imports. + */ + pMod->hOurMod = (HMODULE)pMod->u.Manual.pbLoad; + kwLdrModuleLink(pMod); + KWLDR_LOG(("New module: %p LB %#010x %s (kLdr)\n", + pMod->u.Manual.pbLoad, pMod->cbImage, pMod->pszPath)); + KWLDR_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); + cchReloads = g_cchReloads; + if (cchReloads + 80 < sizeof(g_szReloads)) + { + cchReloads += _snprintf(&g_szReloads[cchReloads], sizeof(g_szReloads) - cchReloads, + "%s.reload /f %s=%p\n", cchReloads ? "; " : "", + pMod->pszPath, pMod->u.Manual.pbLoad); + g_cchReloads = cchReloads; + } + + 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; + } + kwErrPrintf("Error getting import '%s' for '%s': %d (%u)\n", + szName, pMod->pszPath, rc, GetLastError()); + 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; + pMod->u.Manual.enmReInitState = KWMODSTATE_NEEDS_BITS; + if ( g_Sandbox.pTool + && ( g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_CL + || g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_LINK) + && !pMod->fExe) + pMod->u.Manual.enmReInitState = KWMODSTATE_READY; + g_cModules++; + g_cNonNativeModules++; + return pMod; + } + kwErrPrintf("kwLdrModuleCreateNonNativeSetupTls failed with %d for %s\n", rc, pMod->pszPath); + } + 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); + } + else + kwErrPrintf("kwLdrModuleCreateNonNative: out of memory!\n"); + } + else + kwErrPrintf("kwLdrModuleCreateNonNative: kLdrModNumberOfImports failed for '%s'\n", pszPath); + } + 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) + { + KWLDR_LOG(("replacing %s!%s\n",&pImpMod->pszPath[pImpMod->offFilename], g_aSandboxReplacements[i].pszFunction)); + if (!g_aSandboxReplacements[i].fCrtSlotArray) + *puValue = g_aSandboxReplacements[i].pfnReplacement; + else + { + if (pImpMod->iCrtSlot == KU8_MAX) + { + rc = kwLdrModuleCreateCrtSlot(pImpMod); + if (rc) + KWLDR_LOG(("kwLdrModuleGetImportCallback: kwLdrModuleCreateCrtSlot failed: %d\n", rc)); + } + *puValue = ((KUPTR *)g_aSandboxReplacements[i].pfnReplacement)[pImpMod->iCrtSlot]; + } + } + break; + } + } + } + + //printf("iImport=%u (%s) %*.*s rc=%d\n", iImport, &pImpMod->pszPath[pImpMod->offFilename], cchSymbol, cchSymbol, pchSymbol, rc); + KWLDR_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 + || kHlpStrNICompAscii(pszFilename, TUPLE("vcruntime")) == 0 + || kHlpStrNICompAscii(pszFilename, TUPLE("ucrtbase")) == 0 + || kHlpStrNICompAscii(pszFilename, TUPLE("api-ms-win-crt-")) == 0 +#if 0 /* for debugging, only for debugging. */ + || kHlpStrICompAscii(pszFilename, "c1.dll") == 0 + || kHlpStrICompAscii(pszFilename, "c1xx.dll") == 0 + || kHlpStrICompAscii(pszFilename, "c2.dll") == 0 +#endif + ; +} + + +/** + * 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 ( enmLocation == KWLOCATION_UNKNOWN + && kwLdrIsVirtualApiModule(pszFilename, kHlpStrLen(pszFilename))) + 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 + || kHlpStrNICompAscii(pszFilename, TUPLE("tbbmalloc")) == 0 + || kHlpStrNICompAscii(pszFilename, TUPLE("ucrtbase")) == 0 + || kHlpStrNICompAscii(pszFilename, TUPLE("vcruntime")) == 0 + || ( enmLocation != KWLOCATION_UNKNOWN + && kwLdrIsVirtualApiModule(pszFilename, kHlpStrLen(pszFilename))) +#if 0 /* for debugging, only for debugging. */ + //|| kHlpStrICompAscii(pszFilename, "c1.dll") == 0 + //|| kHlpStrICompAscii(pszFilename, "c1xx.dll") == 0 + //|| kHlpStrICompAscii(pszFilename, "c2.dll") == 0 +#endif + ; +} + + +/** + * 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->pNextHash; + } 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; + + /* Virtual API module. Normalize and try load it. */ + if (pMod == NULL && cchName > 7 && kwLdrIsVirtualApiModule(pszName, cchName)) + { + if (cchName + cchSuffix >= sizeof(szPath)) + return KERR_BUFFER_OVERFLOW; + kHlpMemCopy(szPath, pszName, cchName); + if (fNeedSuffix) + kHlpMemCopy(&szPath[cchName], ".dll", sizeof(".dll")); + szPath[cchName + cchSuffix] = '\0'; + _strlwr(szPath); + pMod = kwLdrModuleTryLoadVirtualDll(szPath, cchName + cchSuffix); + } + + /* 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; +} + + +/** + * Creates a CRT slot for the given module. + * + * @returns 0 on success, non-zero on failure. + * @param pModule The module. + */ +static int kwLdrModuleCreateCrtSlot(PKWMODULE pModule) +{ + KSIZE iSlot; + kHlpAssert(pModule->iCrtSlot == KU8_MAX); + for (iSlot = 0; iSlot < K_ELEMENTS(g_aCrtSlots); iSlot++) + if (g_aCrtSlots[iSlot].pModule == NULL) + { + KLDRADDR uAddr; + int rc; + + /* Do the linking: */ + g_aCrtSlots[iSlot].pModule = pModule; + g_aCrtSlots[iSlot].iSlot = (KU32)iSlot; + pModule->iCrtSlot = (KU8)iSlot; + + /* resolve symbols: */ + rc = kLdrModQuerySymbol(pModule->pLdrMod, NULL /*pvBits*/, KLDRMOD_BASEADDRESS_MAP, KU32_MAX, "malloc", 6, + NULL /*pvszVersion*/, NULL /*pfnGetForwarder*/, NULL /*pvUser*/, &uAddr, NULL); + *(KUPTR *)&g_aCrtSlots[iSlot].pfnMalloc = rc == 0 ? (KUPTR)uAddr : 0; + if (rc != 0) + kwErrPrintf("Failed to resolved 'malloc' in '%s': %d\n", pModule->pszPath, rc); + + rc = kLdrModQuerySymbol(pModule->pLdrMod, NULL /*pvBits*/, KLDRMOD_BASEADDRESS_MAP, KU32_MAX, "_beginthreadex", 14, + NULL /*pvszVersion*/, NULL /*pfnGetForwarder*/, NULL /*pvUser*/, &uAddr, NULL); + *(KUPTR *)&g_aCrtSlots[iSlot].pfnBeginThreadEx = rc == 0 ? (KUPTR)uAddr : 0; + //if (rc != 0) + // kwErrPrintf("Failed to resolved '_beginthreadex' in '%s': %d\n", pModule->pszPath, rc); + + return 0; + } + kwErrPrintf("Out of CRT slots!\n"); + return KERR_NO_MEMORY; +} + + +/** + * Locates the module structure for an already loaded native module. + * + * This will create a module structure if needed. + * + * @returns Pointer to the module structure on success, NULL on failure. + * @param hModule The native module handle. + * @param fEnsureCrtSlot Whether to ensure that it has a valid CRT slot. + * @param pszLogName The name to use for logging/errors. + */ +static PKWMODULE kwLdrModuleForLoadedNativeByHandle(HMODULE hModule, KBOOL fEnsureCrtSlot, const char *pszLogName) +{ + /* + * Get a normalized path for it. + */ + char szModPath[1024]; + if (GetModuleFileNameA(hModule, szModPath, sizeof(szModPath)) > 0) + { + char szNormPath[1024]; + int rc = kwPathNormalize(szModPath, szNormPath, sizeof(szNormPath)); + if (rc == 0) + { + /* + * Hash the path and look it up. + */ + KU32 uHashPath; + KSIZE const cchPath = kwStrHashEx(szNormPath, &uHashPath); + unsigned idxHash = uHashPath % K_ELEMENTS(g_apModules); + PKWMODULE pMod = g_apModules[idxHash]; + if (pMod) + { + do + { + if ( pMod->uHashPath == uHashPath + && kHlpStrComp(pMod->pszPath, szNormPath) == 0) + { + kwLdrModuleRetain(pMod); + break; + } + pMod = pMod->pNextHash; + } while (pMod); + } + + /* + * If not in the hash table, so create a module entry. + */ + if (!pMod) + { + PKLDRMOD pLdrMod; + rc = kLdrModOpenNativeByHandle((KUPTR)hModule, KLDRMOD_OPEN_FLAGS_NATIVE_ALLOW_INIT_TERM, &pLdrMod); + if (rc == 0) + { + /** @todo more accurately determine location */ + const char *pszFilename = kHlpGetFilename(szNormPath); + KBOOL fDoReplacements = kwLdrModuleShouldDoNativeReplacements(pszFilename, KWLOCATION_SYSTEM32); + pMod = kwLdrModuleCreateForNativekLdrModule(pLdrMod, szNormPath, cchPath + 1, uHashPath, + fDoReplacements, NULL /*pVirtualApiMod*/); + if (!pMod) + { + kLdrModClose(pLdrMod); + kwErrPrintf("out of memory\n"); + } + } + else + kwErrPrintf("kLdrModOpenNativeByHandle failed for %p / '%s': %d\n", hModule, pszLogName, rc); + } + if (pMod) + { + /* + * Create a CRT slot for the module if necessary. + */ + if (!fEnsureCrtSlot || pMod->iCrtSlot != KU8_MAX) + return pMod; + rc = kwLdrModuleCreateCrtSlot(pMod); + if (rc == 0) + return pMod; + kwLdrModuleRelease(pMod); + } + } + else + kwErrPrintf("kwPathNormalize failed for '%s' (%s): %u!\n", szModPath, pszLogName, GetLastError()); + } + else + kwErrPrintf("GetModuleFileNameA failed for '%s': %u!\n", pszLogName, GetLastError()); + return NULL; +} + + +/** + * Locates the module structure for an already loaded native module. + * + * This will create a module structure if needed. + * + * @returns Pointer to the module structure on success, NULL on failure. + * @param pszName The name of the module. + * @param fEnsureCrtSlot Whether to ensure that it has a valid CRT slot. + * @param fAlwaysPresent Whether the module is expected to always be present, + * or not. If not, complain less. + */ +static PKWMODULE kwLdrModuleForLoadedNative(const char *pszName, KBOOL fEnsureCrtSlot, KBOOL fAlwaysPresent) +{ + /* + * Locate the module handle and pass it to kwLdrModuleForLoadedNativeByHandle. + */ + HANDLE hModule = GetModuleHandleA(pszName); + if (hModule) + return kwLdrModuleForLoadedNativeByHandle(hModule, fEnsureCrtSlot, pszName); + if (fAlwaysPresent) + kwErrPrintf("Module '%s' was not found by GetModuleHandleA/W!\n", pszName); + return NULL; +} + + +/** + * 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 *ppfnCallback = (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, *ppfnCallback, pMod->hOurMod, dwReason)); + (*ppfnCallback)(pMod->hOurMod, dwReason, 0); + } while (*++ppfnCallback); + } +} + + +/** + * 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; + } + } + /* + * Special hack to disconnect mspdbXXX.dll from mspdbsrv.exe when + * _MSPDBSRV_ENDPOINT_ changes value. + */ + else if (pMod->fNeedReInit) + { + int rc2; + KWLDR_LOG(("kwLdrModuleInitTree: mspdb re-init hack: %s\n", pMod->pszPath)); + //fprintf(stderr, "%d: kwLdrModuleInitTree: mspdb re-init hack: %s\n", getpid(), kwSandboxDoGetEnvA(&g_Sandbox, TUPLE("_MSPDBSRV_ENDPOINT_"))); fflush(stderr); + rc = kLdrModCallTerm(pMod->pLdrMod, pMod->u.Manual.pbLoad, (KUPTR)pMod->hOurMod); + rc2 = kLdrModCallInit(pMod->pLdrMod, pMod->u.Manual.pbLoad, (KUPTR)pMod->hOurMod); + if (!rc && !rc2) + { /* likely */ } + else + { + kwErrPrintf("Re-init of '%s' failed: rc=%d rc2=%d\n", pMod->pszPath, rc, rc2); + if (rc2 && !rc) + rc = rc2; + } + pMod->fNeedReInit = K_FALSE; + } + 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; + + if (pTool) + { /* likely */ } + else + return NULL; + + /* The executable. */ + if ( hmod == NULL + || (pTool->u.Sandboxed.pExe && pTool->u.Sandboxed.pExe->hOurMod == hmod)) + { + if (pTool->u.Sandboxed.pExe) + return kwLdrModuleRetain(pTool->u.Sandboxed.pExe); + return NULL; + } + + /* + * 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 uHModCur = (KUPTR)papMods[i]->hOurMod; + if (uHMod < uHModCur) + { + iEnd = i--; + if (iStart <= i) + { } + else + break; + } + else if (uHMod != uHModCur) + { + iStart = ++i; + if (i < iEnd) + { } + else + break; + } + /* We've got a match. Always return the non-virtual module (first) when there is one. */ + else if (!papMods[i]->pVirtualApiMod) + return kwLdrModuleRetain(papMods[i]); + else + { + while (i > 0 && papMods[i - 1]->pVirtualApiMod && papMods[i - 1]->hOurMod == hmod) + i--; + 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); +#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 (;;) + { + PKWMODULE pCurMod = papMods[i]; + KUPTR const uHModCur = (KUPTR)pCurMod->hOurMod; + if (uHMod < uHModCur) + { + iEnd = i; + if (iStart < i) + { } + else + break; + } + else if (uHMod != uHModCur) + { + iStart = ++i; + if (i < iEnd) + { } + else + break; + } + else + { + /* Already there in the table. The non-virtual module must be the first + entry if we've got duplicate hmod values because of virtual modules. */ + if (pMod != pCurMod) + { + /* Skip to the last module with the same hmod. */ + while (i + 1 < iEnd && (KUPTR)(pCurMod = papMods[i + 1])->hOurMod == uHMod) + { + if (pMod == pCurMod) + return 0; + i++; + } + + /* Then scan backwards till the first one. */ + while (i > 0 && (KUPTR)(pCurMod = papMods[i - 1])->hOurMod == uHMod) + { + if (pMod == pCurMod) + return 0; + i--; + } + pCurMod = papMods[i]; + if (pMod != pCurMod) + { + if (pMod->pVirtualApiMod && !pCurMod->pVirtualApiMod) + i++; + break; + } + } + 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 + || pMod->pVirtualApiMod + || papMods[iStart]->pVirtualApiMod); + } + 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++; + KWLDR_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->pVirtualApiMod && rc == 0) + rc = kwToolAddModule(pTool, pMod->pVirtualApiMod); + 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; + wchar_t wcSaved; + wchar_t *pwcEnd; + 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); + + /* HACK ALERT! This is to help the loader search the application directory. */ + pwcEnd = (wchar_t *)&pTool->pwszPath[pToolFsObj->cwcParent]; + wcSaved = *pwcEnd; + *pwcEnd = '\0'; + if (!SetDllDirectoryW(pTool->pwszPath)) + kwErrPrintf("SetDllDirectoryW(tool) failed: %u\n", GetLastError()); + *pwcEnd = wcSaved; + + pTool->enmType = KWTOOLTYPE_SANDBOXED; + pTool->u.Sandboxed.pExe = kwLdrModuleCreateNonNative(pTool->pszPath, kwStrHash(pTool->pszPath), K_TRUE /*fExe*/, + NULL /*pExeMod*/, 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 + { + kwErrPrintf("kwLdrModuleCreateNonNative failed!\n"); + pTool->enmType = KWTOOLTYPE_EXEC; + } + + kFsCacheObjRelease(g_pFsCache, pToolFsObj); + g_cTools++; + return pTool; + } + kwErrPrintf("kFsCacheObjAddUserData failed!\n"); + 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 (bObjType=%d fFlags=%#x uCacheGen=%u auGenerationsMissing=[%u,%u])\n", + pszExe, pToolFsObj->bObjType, pToolFsObj->fFlags, pToolFsObj->uCacheGen, + g_pFsCache->auGenerationsMissing[0], g_pFsCache->auGenerationsMissing[1]); + } + } + 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((unsigned 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_wrapped(void *pvSecAttr, unsigned cbStack, + unsigned (__stdcall *pfnThreadProc)(void *), void *pvUser, + unsigned fCreate, unsigned *pidThread, PKWCRTSLOT pSlot) +{ + /* + * Since the VC++ 12 (VS 2013) compiler, the 2nd pass is now threaded. + * Let it do whatever it needs to. + */ + KW_LOG(("kwSandbox_msvcrt__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 + && pSlot->pfnBeginThreadEx) + { + uintptr_t rcRet = pSlot->pfnBeginThreadEx(pvSecAttr, cbStack, pfnThreadProc, pvUser, fCreate, pidThread); + KW_LOG(("kwSandbox_msvcrt__beginthreadex: returns %p *pidThread=%#x\n", rcRet, pidThread ? *pidThread : -1)); + return rcRet; + } + + kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); + KWFS_TODO(); + return 0; +} + +CRT_SLOT_FUNCTION_WRAPPER(uintptr_t __cdecl, kwSandbox_msvcrt__beginthreadex, + (void *pvSecAttr, unsigned cbStack, unsigned (__stdcall *pfnThreadProc)(void *), + void *pvUser, unsigned fCreate, unsigned *pidThread), + (pvSecAttr, cbStack, pfnThreadProc, pvUser, fCreate, pidThread, &g_aCrtSlots[iCrtSlot])); + + + +/* + * + * 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((KUPTR)(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<eos>\n\n", iVar, pszCur, pszCur); + iVar++; + pszCur += kHlpStrLen(pszCur) + 1; + } + fprintf(stderr, " %u:%p=<eos>\n\n", iVar, pszCur); + pszCur++; + fprintf(stderr, "ended at %p, after %u bytes (expected %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((KUPTR)(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; +} + + +/** CRT - _wdupenv_s() (see _tdupenv_s(). */ +static errno_t __cdecl kwSandbox_msvcrt__wdupenv_s_wrapped(wchar_t **ppwszValue, size_t *pcwcValue, const wchar_t *pwszVarName, + PKWCRTSLOT pSlot) +{ + errno_t rc; + wchar_t *pwszValue; + kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); + + if (ppwszValue) + { + pwszValue = kwSandboxDoGetEnvW(&g_Sandbox, pwszVarName, wcslen(pwszVarName)); + if (pwszValue) + { + size_t cwcValue = wcslen(pwszValue); + wchar_t *pwszDst = pSlot->pfnMalloc ? (wchar_t *)pSlot->pfnMalloc((cwcValue + 1) * sizeof(wchar_t)) : NULL; + if (pwszDst) + { + memcpy(pwszDst, pwszValue, cwcValue * sizeof(wchar_t)); + pwszDst[cwcValue] = '\0'; + *ppwszValue = pwszDst; + if (pcwcValue) + *pcwcValue = cwcValue; + rc = 0; + } + else + { + *ppwszValue = NULL; + if (pcwcValue) + *pcwcValue = 0; + rc = ENOMEM; + } + } + else + { + *ppwszValue = NULL; + if (pcwcValue) + *pcwcValue = 0; + rc = 0; + } + KW_LOG(("_wdupenv_s(,,%ls) -> %d '%ls'\n", pwszVarName, rc, *ppwszValue ? *ppwszValue : L"<null>")); + //fprintf(stderr, "%d: _wdupenv_s(,,%ls) -> %d '%ls'\n", getpid(), pwszVarName, rc, *ppwszValue ? *ppwszValue : L"<null>"); fflush(stderr); // HACKING + } + else + { + /* + * Warning! If mspdb100.dll ends up here, it won't reinitialize the event name + * and continue to use the one it constructed when _MSPDBSRV_ENDPOINT_ + * was set to a value. + */ + if (pcwcValue) + *pcwcValue = 0; + rc = EINVAL; + KW_LOG(("_wdupenv_s(,,%ls) -> EINVAL\n", pwszVarName)); + //fprintf(stderr, "%d: _wdupenv_s(,,%ls) -> EINVAL\n", getpid(), pwszVarName); fflush(stderr); // HACKING + } + return rc; +} +CRT_SLOT_FUNCTION_WRAPPER(errno_t __cdecl, kwSandbox_msvcrt__wdupenv_s, + (wchar_t **ppwszValue, size_t *pcwcValue, const wchar_t *pwszVarName), + (ppwszValue, pcwcValue, pwszVarName, &g_aCrtSlots[iCrtSlot])); + + + +/* + * + * 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; + KWLDR_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) +{ + static const char s_szDll[] = ".dll"; + KSIZE cbFilename = kHlpStrLen(pDynLoad->szRequest) + 1; + PKWMODULE pMod; + char szNormPath[256]; + + /* + * Lower case it and make sure it ends with .dll. + */ + if (cbFilename > sizeof(szNormPath)) + { + SetLastError(ERROR_FILENAME_EXCED_RANGE); + return NULL; + } + kHlpMemCopy(szNormPath, pDynLoad->szRequest, cbFilename); + _strlwr(szNormPath); + kHlpAssert(cbFilename > 7 /* api-ms- */ ); + if (strcmp(&szNormPath[cbFilename - 5], s_szDll) != 0) + { + if (cbFilename + sizeof(s_szDll) - 1 > sizeof(szNormPath)) + { + SetLastError(ERROR_FILENAME_EXCED_RANGE); + return NULL; + } + + memcpy(&szNormPath[cbFilename - sizeof(s_szDll)], s_szDll, sizeof(s_szDll)); + cbFilename += sizeof(s_szDll) - 1; + } + + /* + * Try load it. + */ + pMod = kwLdrModuleTryLoadVirtualDll(szNormPath, cbFilename - 1); + if (pMod) + { + 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; + KWLDR_LOG(("LoadLibraryExA(%s,,) -> %p [virtual API module - new]\n", pDynLoad->szRequest, pDynLoad->hmod)); + return pDynLoad->hmod; + } + kHlpFree(pDynLoad); + return NULL; +} + + +/** 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); + //fprintf(stderr, "LoadLibraryExA: %s, %#x\n", pszFilename, fFlags); + + /* + * 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) + { + KWLDR_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 + { + KWLDR_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 ( kwLdrIsVirtualApiModule(pszFilename, cchFilename) + && 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); +#if 1 /* HACK ALERT! We run into trouble with a 2nd mspdb140.dll instance (x64 + x86), so use the one already loaded. A call + * to NdrClientCall2 at ConnectToServer+0x426 fails with E_INVALIDARG. Problems with multiple connections from same PID? */ + else if ( strcmp(pszFilename, "mspdb140.dll") == 0 + && GetModuleHandleA(pszFilename) != NULL) + { + pMod = kwLdrModuleForLoadedNativeByHandle(GetModuleHandleA(pszFilename), K_FALSE, pszFilename); + KWLDR_LOG(("LoadLibraryExA: mspdb140 hack: pMod=%p\n", pMod)); + } +#endif + 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) + { + KWLDR_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) + && g_Sandbox.pTool + && g_Sandbox.pTool->u.Sandboxed.pExe) + { + KSIZE const cchFilename = kHlpStrLen(pszFilename); +#define MY_IMATCH(a_szName) (cchFilename == sizeof(a_szName) - 1 && kHlpStrICompAscii(pszFilename, a_szName) == 0) + if ( !kwLdrIsVirtualApiModule(pszFilename, cchFilename) + && !MY_IMATCH("ntdll") + && !MY_IMATCH("kernel32") + && !MY_IMATCH("ntdll.dll") + && !MY_IMATCH("kernelbase") + && !MY_IMATCH("kernel32.dll") + && !MY_IMATCH("kernelbase.dll") + ) +#undef MY_IMATCH + { + 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); + kHlpAssert(pszCur); + if (pszCur) + { + 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() for native overloads */ +static HMODULE WINAPI kwSandbox_Kernel32_Native_LoadLibraryExW(LPCWSTR pwszFilename, HANDLE hFile, DWORD fFlags) +{ + char szTmp[4096]; + KSIZE cchTmp = kwUtf16ToStr(pwszFilename, szTmp, sizeof(szTmp)); + if (cchTmp < sizeof(szTmp)) + return kwSandbox_Kernel32_Native_LoadLibraryExA(szTmp, hFile, fFlags); + + KWFS_TODO(); + SetLastError(ERROR_FILENAME_EXCED_RANGE); + return NULL; +} + + +/** 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; +} + + +/** Worker for GetModuleHandleA/W for handling cached modules. */ +static HMODULE kwSandbox_Kernel32_GetModuleHandle_ReturnedCachedEntry(KSIZE i) +{ + HMODULE hmod = g_aGetModuleHandleCache[i].hmod; + if (hmod) + KWLDR_LOG(("kwSandbox_Kernel32_GetModuleHandle_ReturnedCachedEntry(%u/%s -> %p [cached]\n", + hmod, g_aGetModuleHandleCache[i].pszName)); + else + { + /* + * The first time around we have to make sure we have a module table + * entry for it, if not we add one. We need to add it to the tools + * module list to for it to work. + */ + PKWMODULE pMod = kwLdrModuleForLoadedNative(g_aGetModuleHandleCache[i].pszName, K_FALSE, + g_aGetModuleHandleCache[i].fAlwaysPresent); + if (pMod) + { + hmod = pMod->hOurMod; + if (!kwToolLocateModuleByHandle(g_Sandbox.pTool, hmod)) + { + kwToolAddModule(g_Sandbox.pTool, pMod); + KWLDR_LOG(("kwSandbox_Kernel32_GetModuleHandle_ReturnedCachedEntry(%u/%s -> %p [added to tool]\n", + hmod, g_aGetModuleHandleCache[i].pszName)); + } + else + KWLDR_LOG(("kwSandbox_Kernel32_GetModuleHandle_ReturnedCachedEntry(%u/%s -> %p [known to tool]\n", + hmod, g_aGetModuleHandleCache[i].pszName)); + + } + } + return hmod; +} + + +/** Kernel32 - GetModuleHandleA() */ +static HMODULE WINAPI kwSandbox_Kernel32_GetModuleHandleA(LPCSTR pszModule) +{ + KSIZE i; + KSIZE cchModule; + PKWDYNLOAD pDynLoad; + KSIZE cchSuffix; + DWORD dwErr = ERROR_MOD_NOT_FOUND; + kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); + + /* + * The executable. + */ + if (pszModule == NULL) + { + KWLDR_LOG(("kwSandbox_Kernel32_GetModuleHandleA(NULL) -> %p (exe)\n", g_Sandbox.pTool->u.Sandboxed.pExe->hOurMod)); + return (HMODULE)g_Sandbox.pTool->u.Sandboxed.pExe->hOurMod; + } + + /* + * If no path of suffix, pretend it ends with .DLL. + */ + cchSuffix = strpbrk(pszModule, ":/\\.") ? 0 : 4; + + /* + * 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) + || ( cchSuffix > 0 + && g_aGetModuleHandleCache[i].cchName == cchModule + cchSuffix + && strnicmp(pszModule, g_aGetModuleHandleCache[i].pszName, cchModule) + && stricmp(&g_aGetModuleHandleCache[i].pszName[cchModule], ".dll") == 0)) + return kwSandbox_Kernel32_GetModuleHandle_ReturnedCachedEntry(i); + + /* + * Modules we've dynamically loaded. + */ + for (pDynLoad = g_Sandbox.pTool->u.Sandboxed.pDynLoadHead; pDynLoad; pDynLoad = pDynLoad->pNext) + if (pDynLoad->pMod) + { + const char *pszPath = pDynLoad->pMod->pszPath; + const char *pszName = &pszPath[pDynLoad->pMod->offFilename]; + if ( stricmp(pszPath, pszModule) == 0 + || stricmp(pszName, pszModule) == 0 + || ( cchSuffix > 0 + && strnicmp(pszName, pszModule, cchModule) == 0 + && stricmp(&pszName[cchModule], ".dll") == 0)) + { + if ( pDynLoad->pMod->fNative + || pDynLoad->pMod->u.Manual.enmState == KWMODSTATE_READY) + { + KWLDR_LOG(("kwSandbox_Kernel32_GetModuleHandleA(%s,,) -> %p [dynload]\n", pszModule, pDynLoad->hmod)); + return pDynLoad->hmod; + } + KWLDR_LOG(("kwSandbox_Kernel32_GetModuleHandleA(%s) -> NULL (not read)\n", pszModule)); + SetLastError(ERROR_MOD_NOT_FOUND); + return NULL; + } + } + + /* + * Hack for the api-ms-win-xxxxx.dll modules. Find which module they map + * to and go via the g_aGetModuleHandleCache cache. + */ +/** @todo virtual api DLLs */ + if (kHlpStrNICompAscii(pszModule, "api-ms-win-", 11) == 0) + { + HMODULE hmod = GetModuleHandleA(pszModule); + KWLDR_LOG(("kwSandbox_Kernel32_GetModuleHandleA(%s); hmod=%p\n", pszModule, hmod)); + if (hmod) + { + if (hmod == GetModuleHandleW(L"KERNELBASE.DLL")) + return kwSandbox_Kernel32_GetModuleHandleA("KERNELBASE.DLL"); + if (hmod == GetModuleHandleW(L"KERNEL32.DLL")) + return kwSandbox_Kernel32_GetModuleHandleA("KERNEL32.DLL"); + if (hmod == GetModuleHandleW(L"NTDLL.DLL")) + return kwSandbox_Kernel32_GetModuleHandleA("NTDLL.DLL"); + if (hmod == GetModuleHandleW(L"UCRTBASE.DLL")) + return kwSandbox_Kernel32_GetModuleHandleA("UCRTBASE.DLL"); + } + else + dwErr = GetLastError(); + } + + 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; + KSIZE cwcSuffix; + DWORD dwErr = ERROR_MOD_NOT_FOUND; + kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); + + /* + * The executable. + */ + if (pwszModule == NULL) + { + KWLDR_LOG(("kwSandbox_Kernel32_GetModuleHandleW(NULL) -> %p (exe)\n", g_Sandbox.pTool->u.Sandboxed.pExe->hOurMod)); + return (HMODULE)g_Sandbox.pTool->u.Sandboxed.pExe->hOurMod; + } + + /* + * If no path of suffix, pretend it ends with .DLL. + */ + cwcSuffix = wcspbrk(pwszModule, L":/\\.") ? 0 : 4; + + /* + * 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) + || ( cwcSuffix > 0 + && g_aGetModuleHandleCache[i].cwcName == cwcModule + cwcSuffix + && _wcsnicmp(pwszModule, g_aGetModuleHandleCache[i].pwszName, cwcModule) == 0 + && _wcsicmp(&g_aGetModuleHandleCache[i].pwszName[cwcModule], L".dll") == 0)) + return kwSandbox_Kernel32_GetModuleHandle_ReturnedCachedEntry(i); + + /* + * Modules we've dynamically loaded. + */ + for (pDynLoad = g_Sandbox.pTool->u.Sandboxed.pDynLoadHead; pDynLoad; pDynLoad = pDynLoad->pNext) + if (pDynLoad->pMod) + { + const wchar_t *pwszPath = pDynLoad->pMod->pwszPath; + const wchar_t *pwszName = &pwszPath[pDynLoad->pMod->offFilenameW]; + if ( _wcsicmp(pwszPath, pwszModule) == 0 + || _wcsicmp(pwszName, pwszModule) == 0 + || ( cwcSuffix + && _wcsnicmp(pwszName, pwszModule, cwcModule) == 0 + && _wcsicmp(&pwszName[cwcModule], L".dll") == 0)) + { + if ( pDynLoad->pMod->fNative + || pDynLoad->pMod->u.Manual.enmState == KWMODSTATE_READY) + { + KWLDR_LOG(("kwSandbox_Kernel32_GetModuleHandleW(%ls,,) -> %p [dynload]\n", pwszModule, pDynLoad->hmod)); + return pDynLoad->hmod; + } + KWLDR_LOG(("kwSandbox_Kernel32_GetModuleHandleW(%ls) -> NULL (not read)\n", pwszModule)); + SetLastError(ERROR_MOD_NOT_FOUND); + return NULL; + } + } + + /* + * Hack for the api-ms-win-xxxxx.dll modules. Find which module they map + * to and go via the g_aGetModuleHandleCache cache. + */ + if (_wcsnicmp(pwszModule, L"api-ms-win-", 11) == 0) + { + HMODULE hmod = GetModuleHandleW(pwszModule); + KWLDR_LOG(("kwSandbox_Kernel32_GetModuleHandleW(%ls); hmod=%p\n", pwszModule, hmod)); + if (hmod) + { + if (hmod == GetModuleHandleW(L"KERNELBASE.DLL")) + return kwSandbox_Kernel32_GetModuleHandleW(L"KERNELBASE.DLL"); + if (hmod == GetModuleHandleW(L"KERNEL32.DLL")) + return kwSandbox_Kernel32_GetModuleHandleW(L"KERNEL32.DLL"); + if (hmod == GetModuleHandleW(L"NTDLL.DLL")) + return kwSandbox_Kernel32_GetModuleHandleW(L"NTDLL.DLL"); + } + else + dwErr = GetLastError(); + } + + kwErrPrintf("pwszModule=%ls\n", pwszModule); + KWFS_TODO(); + SetLastError(dwErr); + 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 ~(UINT)0; +} + + +#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) + { + if (!g_aSandboxReplacements[i].fCrtSlotArray) + uValue = g_aSandboxGetProcReplacements[i].pfnReplacement; + else + { + if (pMod->iCrtSlot == KU8_MAX) + { + int rc = kwLdrModuleCreateCrtSlot(pMod); + if (rc) + { + KW_LOG(("GetProcAddress: kwLdrModuleCreateCrtSlot failed: %d\n", rc)); + SetLastError(ERROR_INTERNAL_ERROR); + return NULL; + } + } + uValue = ((KUPTR *)g_aSandboxGetProcReplacements[i].pfnReplacement)[pMod->iCrtSlot]; + } + + 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); +} + + +#ifndef NDEBUG +/** Kernel32 - GetProcAddress() - native replacement for debugging only. */ +static FARPROC WINAPI kwSandbox_Kernel32_Native_GetProcAddress(HMODULE hmod, LPCSTR pszProc) +{ + FARPROC pfnRet = GetProcAddress(hmod, pszProc); + KWLDR_LOG(("kwSandbox_Kernel32_Native_GetProcAddress(%p, %s) -> %p\n", hmod, pszProc, pfnRet)); + return pfnRet; +} +#endif + + +/** 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 uHModCur = (KUPTR)papMods[i]->hOurMod; + if (uPC < uHModCur) + { + iEnd = i; + if (iStart < i) + { } + else + break; + } + else if (uPC != uHModCur) + { + 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: + case KFSLOOKUPERROR_PATH_TOO_SHORT: + 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; + } + + /* In VC2019 there is also one {UUID} file in temp: */ + if (pwszName[0] == '{') + { + KSIZE cwcName = kwUtf16Len(pwszName); + if ( cwcName == sizeof("{4465DDD9-E494-471B-996B-9B556E25AEF8}") - 1 + && pwszName[37] == '}' + && iswalnum(pwszName[1]) // 4 + && iswalnum(pwszName[2]) // 4 + && iswalnum(pwszName[3]) // 6 + && iswalnum(pwszName[4]) // 5 + && iswalnum(pwszName[5]) // d + && iswalnum(pwszName[6]) // d + && iswalnum(pwszName[7]) // d + && iswalnum(pwszName[8]) // 9 + && pwszName[9] == '-' // - + && iswalnum(pwszName[10]) // e + && iswalnum(pwszName[11]) // 4 + && iswalnum(pwszName[12]) // 9 + && iswalnum(pwszName[13]) // 4 + && pwszName[14] == '-' // - + && iswalnum(pwszName[15]) // 4 + && iswalnum(pwszName[16]) // 7 + && iswalnum(pwszName[17]) // 1 + && iswalnum(pwszName[18]) // b + && pwszName[19] == '-' // - + && iswalnum(pwszName[20]) // 9 + && iswalnum(pwszName[21]) // 9 + && iswalnum(pwszName[22]) // 6 + && iswalnum(pwszName[23]) // b + && pwszName[24] == '-' // - + && iswalnum(pwszName[25]) // 9 + && iswalnum(pwszName[26]) // b + && iswalnum(pwszName[27]) // 5 + && iswalnum(pwszName[28]) // 5 + && iswalnum(pwszName[29]) // 6 + && iswalnum(pwszName[30]) // e + && iswalnum(pwszName[31]) // 2 + && iswalnum(pwszName[32]) // 5 + && iswalnum(pwszName[33]) // a + && iswalnum(pwszName[34]) // 3 + && iswalnum(pwszName[35]) // f + && iswalnum(pwszName[36])) // 8 + 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->tidOwner = KU32_MAX; + 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, + KBOOL *pfFallback) +{ + 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. + */ + *pfFallback = K_FALSE; + if (pTempFile == NULL) + { + KSIZE cbFilename; + + switch (dwCreationDisposition) + { + case CREATE_ALWAYS: + case OPEN_ALWAYS: + case CREATE_NEW: + dwErr = NO_ERROR; + break; + + case OPEN_EXISTING: + case TRUNCATE_EXISTING: + *pfFallback = K_TRUE; + 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 = ~(ULONG_PTR)0; + 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->tidOwner = KU32_MAX; + 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 == OPEN_EXISTING) + { + 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 = kFsCacheLookupA(g_pFsCache, pszFilename, &enmError); + if (pFsObj) + { + if (pFsObj->bObjType != KFSOBJ_TYPE_MISSING) + { + 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; + } + } + else if (!(pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN)) + { + KWFS_LOG(("CreateFileA(%ls) -> INVALID_HANDLE_VALUE, ERROR_FILE_NOT_FOUND\n", pszFilename)); + SetLastError(ERROR_FILE_NOT_FOUND); + return INVALID_HANDLE_VALUE; + } + /* Always fall back on missing files in volatile areas. */ + } + /* 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)); + SetLastError(ERROR_FILE_NOT_FOUND); + 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)); + SetLastError(ERROR_PATH_NOT_FOUND); + 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); + kHlpAssert(hFile == INVALID_HANDLE_VALUE || kwSandboxHandleLookup(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)) + { + KBOOL fFallback = K_FALSE; + hFile = kwFsTempFileCreateW(pwszFilename, dwDesiredAccess, dwCreationDisposition, &fFallback); + if (!fFallback) + { + 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 == OPEN_EXISTING) + { + 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 = kFsCacheLookupW(g_pFsCache, pwszFilename, &enmError); + if (pFsObj) + { + if (pFsObj->bObjType != KFSOBJ_TYPE_MISSING) + { + 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; + } + } + else if (!(pFsObj->fFlags & KFSOBJ_F_USE_CUSTOM_GEN)) + { + KWFS_LOG(("CreateFileW(%ls) -> INVALID_HANDLE_VALUE, ERROR_FILE_NOT_FOUND\n", pwszFilename)); + SetLastError(ERROR_FILE_NOT_FOUND); +#if 0 + if ( pFsObj->cchName > sizeof("generated.h") + && kHlpStrICompAscii(&pFsObj->pszName[pFsObj->cchName - sizeof("generated.h") + 1], "generated.h") == 0) + kwErrPrintf("CreateFileW(%ls) -> ERROR_FILE_NOT_FOUND; pFsObj->fFlags=%#x\n", pwszFilename, pFsObj->fFlags); +#endif + return INVALID_HANDLE_VALUE; + } + /* Always fall back on missing files in volatile areas. */ + } + /* 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) + { +#if 0 + KSIZE cwcFilename = kwUtf16Len(pwszFilename); + if ( cwcFilename > sizeof("generated.h") + && memcmp(&pwszFilename[cwcFilename - sizeof("generated.h") + 1], + L"generated.h", sizeof(L"generated.h")) == 0) + kwErrPrintf("CreateFileW(%ls) -> ERROR_FILE_NOT_FOUND; (KFSLOOKUPERROR_NOT_FOUND)\n", pwszFilename, pFsObj->fFlags); +#endif + KWFS_LOG(("CreateFileW(%ls) -> INVALID_HANDLE_VALUE, ERROR_FILE_NOT_FOUND\n", pwszFilename)); + SetLastError(ERROR_FILE_NOT_FOUND); + return INVALID_HANDLE_VALUE; + } + else if ( enmError == KFSLOOKUPERROR_PATH_COMP_NOT_FOUND + || enmError == KFSLOOKUPERROR_PATH_COMP_NOT_DIR) + { +#if 0 + KSIZE cwcFilename = kwUtf16Len(pwszFilename); + if ( cwcFilename > sizeof("generated.h") + && memcmp(&pwszFilename[cwcFilename - sizeof("generated.h") + 1], + L"generated.h", sizeof(L"generated.h")) == 0) + kwErrPrintf("CreateFileW(%ls) -> ERROR_PATH_NOT_FOUND; (%d)\n", pwszFilename, enmError); +#endif + KWFS_LOG(("CreateFileW(%ls) -> INVALID_HANDLE_VALUE, ERROR_PATH_NOT_FOUND\n", pwszFilename)); + SetLastError(ERROR_PATH_NOT_FOUND); + 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); + kHlpAssert(hFile == INVALID_HANDLE_VALUE || kwSandboxHandleLookup(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) +{ + PKWHANDLE pHandle = kwSandboxHandleGet(hFile); + 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; + case KWHANDLETYPE_TEMP_FILE_MAPPING: +#endif + case KWHANDLETYPE_OUTPUT_BUF: + default: + kHlpAssertFailed(); + kwSandboxHandlePut(pHandle); + 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)); + kwSandboxHandlePut(pHandle); + SetLastError(ERROR_INVALID_PARAMETER); + return INVALID_SET_FILE_POINTER; + } + if (offMove >= 0) + { + if (offMove >= (KSSIZE)cbFile) + { +#ifdef WITH_TEMP_MEMORY_FILES + /* For read-only files, seeking beyond the end isn't useful to us, so clamp it. */ + if (pHandle->enmType != KWHANDLETYPE_TEMP_FILE) +#endif + offMove = (KSSIZE)cbFile; +#ifdef WITH_TEMP_MEMORY_FILES + /* 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)); + kwSandboxHandlePut(pHandle); + SetLastError(ERROR_SEEK); + return INVALID_SET_FILE_POINTER; + } +#endif + } + pHandle->offFile = (KU32)offMove; + } + else + { + KWFS_LOG(("SetFilePointer(%p) - negative seek! [cached]\n", hFile)); + kwSandboxHandlePut(pHandle); + 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")); + kwSandboxHandlePut(pHandle); + 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) +{ + PKWHANDLE pHandle = kwSandboxHandleGet(hFile); + 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; + case KWHANDLETYPE_TEMP_FILE_MAPPING: +#endif + case KWHANDLETYPE_OUTPUT_BUF: + default: + kHlpAssertFailed(); + kwSandboxHandlePut(pHandle); + SetLastError(ERROR_INVALID_FUNCTION); + return FALSE; + } + + 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)); + kwSandboxHandlePut(pHandle); + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + if (offMyMove >= 0) + { + if (offMyMove >= (KSSIZE)cbFile) + { +#ifdef WITH_TEMP_MEMORY_FILES + /* For read-only files, seeking beyond the end isn't useful to us, so clamp it. */ + if (pHandle->enmType != KWHANDLETYPE_TEMP_FILE) +#endif + offMyMove = (KSSIZE)cbFile; +#ifdef WITH_TEMP_MEMORY_FILES + /* 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)); + kwSandboxHandlePut(pHandle); + SetLastError(ERROR_SEEK); + return FALSE; + } +#endif + } + pHandle->offFile = (KU32)offMyMove; + } + else + { + KWFS_LOG(("SetFilePointerEx(%p) - negative seek! [cached]\n", hFile)); + kwSandboxHandlePut(pHandle); + SetLastError(ERROR_NEGATIVE_SEEK); + return FALSE; + } + 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")); + kwSandboxHandlePut(pHandle); + 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; + PKWHANDLE pHandle = kwSandboxHandleGet(hFile); + g_cReadFileCalls++; + 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_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)); + kwSandboxHandlePut(pHandle); + 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)); + kwSandboxHandlePut(pHandle); + return TRUE; + } + + case KWHANDLETYPE_TEMP_FILE_MAPPING: +#endif /* WITH_TEMP_MEMORY_FILES */ + case KWHANDLETYPE_OUTPUT_BUF: + default: + kHlpAssertFailed(); + kwSandboxHandlePut(pHandle); + 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) +{ + kHlpAssert(kwSandboxHandleLookup(hFile) == NULL); + + 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 = (char *)&pOutBuf->abPadding[0]; + } + } + } + + /* + * 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) +{ + PKWHANDLE pHandle = kwSandboxHandleGet(hFile); + BOOL fRet; + g_cWriteFileCalls++; + 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)); + kwSandboxHandlePut(pHandle); + return TRUE; + } + + kHlpAssertFailed(); + kwSandboxHandlePut(pHandle); + *pcbActuallyWritten = 0; + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return FALSE; + } +# endif + + case KWHANDLETYPE_FSOBJ_READ_CACHE: + kHlpAssertFailed(); + kwSandboxHandlePut(pHandle); + 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; + kwSandboxHandlePut(pHandle); + return TRUE; + } +# endif + + default: +#ifdef WITH_TEMP_MEMORY_FILES + case KWHANDLETYPE_TEMP_FILE_MAPPING: +#endif + kHlpAssertFailed(); + kwSandboxHandlePut(pHandle); + 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) +{ + kHlpAssert(kwSandboxHandleLookup(hFile) == NULL); + + 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) +{ + PKWHANDLE pHandle = kwSandboxHandleGet(hFile); + 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(); + kwSandboxHandlePut(pHandle); + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + return FALSE; + } + + pTempFile->cbFile = pHandle->offFile; + KWFS_LOG(("SetEndOfFile(%p) -> TRUE (cbFile=%#x)\n", hFile, pTempFile->cbFile)); + kwSandboxHandlePut(pHandle); + return TRUE; + } + + case KWHANDLETYPE_FSOBJ_READ_CACHE: + kHlpAssertFailed(); + kwSandboxHandlePut(pHandle); + SetLastError(ERROR_ACCESS_DENIED); + return FALSE; + +# ifdef WITH_CONSOLE_OUTPUT_BUFFERING + case KWHANDLETYPE_OUTPUT_BUF: + kHlpAssertFailed(); + kwSandboxHandlePut(pHandle); + SetLastError(pHandle->u.pOutBuf->fIsConsole ? ERROR_INVALID_OPERATION : ERROR_ACCESS_DENIED); + return FALSE; +# endif + + default: + case KWHANDLETYPE_TEMP_FILE_MAPPING: + kHlpAssertFailed(); + kwSandboxHandlePut(pHandle); + 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) +{ + PKWHANDLE pHandle = kwSandboxHandleGet(hFile); + if (pHandle != NULL) + { + switch (pHandle->enmType) + { + case KWHANDLETYPE_FSOBJ_READ_CACHE: + KWFS_LOG(("GetFileType(%p) -> FILE_TYPE_DISK [cached]\n", hFile)); + kwSandboxHandlePut(pHandle); + return FILE_TYPE_DISK; + + case KWHANDLETYPE_TEMP_FILE: + KWFS_LOG(("GetFileType(%p) -> FILE_TYPE_DISK [temp]\n", hFile)); + kwSandboxHandlePut(pHandle); + return FILE_TYPE_DISK; + +#ifdef WITH_CONSOLE_OUTPUT_BUFFERING + 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)); + } + kwSandboxHandlePut(pHandle); + return fRet; + } +#endif + } + kwSandboxHandlePut(pHandle); + } + + KWFS_LOG(("GetFileType(%p)\n", hFile)); + return GetFileType(hFile); +} + + +/** Kernel32 - GetFileSize */ +static DWORD WINAPI kwSandbox_Kernel32_GetFileSize(HANDLE hFile, LPDWORD pcbHighDword) +{ + PKWHANDLE pHandle = kwSandboxHandleGet(hFile); + 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)); + kwSandboxHandlePut(pHandle); + return pHandle->u.pCachedFile->cbCached; + + case KWHANDLETYPE_TEMP_FILE: + KWFS_LOG(("GetFileSize(%p) -> %#x [temp]\n", hFile, pHandle->u.pTempFile->cbFile)); + kwSandboxHandlePut(pHandle); + return pHandle->u.pTempFile->cbFile; + + case KWHANDLETYPE_OUTPUT_BUF: + /* do default */ + break; + + default: + kHlpAssertFailed(); + kwSandboxHandlePut(pHandle); + SetLastError(ERROR_INVALID_FUNCTION); + return INVALID_FILE_SIZE; + } + kwSandboxHandlePut(pHandle); + } + + KWFS_LOG(("GetFileSize(%p,)\n", hFile)); + return GetFileSize(hFile, pcbHighDword); +} + + +/** Kernel32 - GetFileSizeEx */ +static BOOL WINAPI kwSandbox_Kernel32_GetFileSizeEx(HANDLE hFile, PLARGE_INTEGER pcbFile) +{ + PKWHANDLE pHandle = kwSandboxHandleGet(hFile); + 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; + kwSandboxHandlePut(pHandle); + 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; + kwSandboxHandlePut(pHandle); + return TRUE; + + case KWHANDLETYPE_OUTPUT_BUF: + /* do default */ + break; + + default: + kHlpAssertFailed(); + kwSandboxHandlePut(pHandle); + SetLastError(ERROR_INVALID_FUNCTION); + return INVALID_FILE_SIZE; + } + kwSandboxHandlePut(pHandle); + } + + 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; + PKWHANDLE pHandle = kwSandboxHandleGet(hFile); + 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)); + kwSandboxHandlePut(pHandle); + return hMapping; + } + kHlpAssertMsgFailed(("fProtect=%#x cb=%#x'%08x name=%p\n", + fProtect, dwMaximumSizeHigh, dwMaximumSizeLow, pwszName)); + kwSandboxHandlePut(pHandle); + 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)); + kwSandboxHandlePut(pHandle); + 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)); + kwSandboxHandlePut(pHandle); + return hMapping; + } + + /** @todo read cached memory mapped files too for moc. */ + } + kwSandboxHandlePut(pHandle); + } + + 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; + PKWHANDLE pHandle = kwSandboxHandleGet(hSection); + 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[0])); + if (pvNew) + g_Sandbox.paMemMappings = (PKWMEMMAPPING)pvNew; + else + { + kwErrPrintf("Failed to grow paMemMappings from %#x to %#x!\n", g_Sandbox.cMemMappingsAlloc, cNew); + kwSandboxHandlePut(pHandle); + 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(); + kwSandboxHandlePut(pHandle); + 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)); + kwSandboxHandlePut(pHandle); + 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)); + kwSandboxHandlePut(pHandle); + 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++; + + kwSandboxHandlePut(pHandle); + 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; + PKWHANDLE pHandle = kwSandboxHandleGet(hSection); + 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) + pvRet = kwSandbox_Kernel32_MapViewOfFile(hSection, dwDesiredAccess, offFileHigh, offFileLow, cbToMap); + else + { + kHlpAssertFailed(); + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } + kwSandboxHandlePut(pHandle); + 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) + { + pvRet = kwSandbox_Kernel32_MapViewOfFile(hSection, dwDesiredAccess, offFileHigh, offFileLow, cbToMap); + kwSandboxHandlePut(pHandle); + return pvRet; + } + /* 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(); + kwSandboxHandlePut(pHandle); + SetLastError(ERROR_INVALID_OPERATION); + return NULL; + } + kwSandboxHandlePut(pHandle); + } + + 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()) + { + PKWHANDLE pHandle = kwSandboxHandleGet(hSrc); + 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)); + kwSandboxHandlePut(pHandle); + 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); + PKWHANDLE pHandle = kwSandboxHandleGet(hObject); + 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) /* why this?!? */) + { + fRet = CloseHandle(hObject); + if (fRet) + { + EnterCriticalSection(&g_Sandbox.HandlesLock); + 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)); + kwSandboxHandlePut(pHandle); + } + LeaveCriticalSection(&g_Sandbox.HandlesLock); + return fRet; + } + KWFS_LOG(("CloseHandle(%p) -> FALSE [intercepted handle] err=%u!\n", hObject, GetLastError())); + } + else + { +#ifdef WITH_CONSOLE_OUTPUT_BUFFERING + KWFS_LOG(("CloseHandle(%p) -> TRUE [intercepted handle] Ignored closing of std%s!\n", + hObject, hObject == g_Sandbox.StdErr.hOutput ? "err" : "out")); +#else + KWFS_LOG(("CloseHandle(%p) -> TRUE [intercepted handle] Ignored closing of stdXXX!\n", hObject)); +#endif + fRet = TRUE; + } + kwSandboxHandlePut(pHandle); + 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; + } +#ifndef NDEBUG + { + DWORD fCheck = GetFileAttributesW(pwszFilename); + kHlpAssertMsg(fCheck == fRet, ("fCheck=%x vs fRet=%#x diff=%#x; %ls\n", fCheck, fRet, fCheck ^ fRet, pwszFilename)); + } +#endif + 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 - GetFileAttributesExA. */ +static BOOL WINAPI kwSandbox_Kernel32_GetFileAttributesExA(LPCSTR pszFilename, GET_FILEEX_INFO_LEVELS enmLevel, + WIN32_FILE_ATTRIBUTE_DATA *pData) +{ + BOOL 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); + if (enmLevel == GetFileExInfoStandard) + { + pData->dwFileAttributes = pFsObj->Stats.st_attribs; + pData->nFileSizeHigh = (KU64)pFsObj->Stats.st_size >> 32; + pData->nFileSizeLow = (KU32)pFsObj->Stats.st_size; + *(KU64 *)&pData->ftCreationTime = birdNtTimeFromTimeSpec(&pFsObj->Stats.st_birthtim); + *(KU64 *)&pData->ftLastAccessTime = birdNtTimeFromTimeSpec(&pFsObj->Stats.st_atim); + *(KU64 *)&pData->ftLastWriteTime = birdNtTimeFromTimeSpec(&pFsObj->Stats.st_mtim); + kFsCacheObjRelease(g_pFsCache, pFsObj); + fRet = TRUE; + } + else + { + kFsCacheObjRelease(g_pFsCache, pFsObj); + fRet = GetFileAttributesExA(pszFilename, enmLevel, pData); + } + } + else + { + SetLastError(kwFsLookupErrorToWindowsError(enmError)); + fRet = FALSE; + } + +#ifdef K_STRICT + { + WIN32_FILE_ATTRIBUTE_DATA CheckData = { 0 }; + DWORD const dwErrSaved = GetLastError(); + BOOL const fRetCheck = GetFileAttributesExA(pszFilename, enmLevel, &CheckData); + kHlpAssertMsg(fRet == fRetCheck, ("fRet=%d fRetCheck=%d; %s\n", fRet, fRetCheck, pszFilename)); + if (fRetCheck && fRet) + { +# define ASSERT_FS_FIELD_EQUAL_A(pResult, pExpected, pszFilename, Field, szFmt) \ + kHlpAssertMsg((pResult)->Field == (pExpected)->Field, (#Field ": " szFmt " , expected " szFmt "; %s\n", (pResult)->Field, (pExpected)->Field, pszFilename)) + ASSERT_FS_FIELD_EQUAL_A(pData, &CheckData, pszFilename, dwFileAttributes, "%#x"); + ASSERT_FS_FIELD_EQUAL_A(pData, &CheckData, pszFilename, nFileSizeHigh, "%#x"); + ASSERT_FS_FIELD_EQUAL_A(pData, &CheckData, pszFilename, nFileSizeLow, "%#x"); + ASSERT_FS_FIELD_EQUAL_A(pData, &CheckData, pszFilename, ftCreationTime.dwHighDateTime, "%#x"); + ASSERT_FS_FIELD_EQUAL_A(pData, &CheckData, pszFilename, ftCreationTime.dwLowDateTime, "%#x"); + ASSERT_FS_FIELD_EQUAL_A(pData, &CheckData, pszFilename, ftLastWriteTime.dwHighDateTime, "%#x"); + ASSERT_FS_FIELD_EQUAL_A(pData, &CheckData, pszFilename, ftLastWriteTime.dwLowDateTime, "%#x"); + } + else + kHlpAssertMsg(dwErrSaved == GetLastError(), ("%u, expected %u; %s\n", dwErrSaved, GetLastError(), pszFilename)); + SetLastError(dwErrSaved); + } +#endif + KWFS_LOG(("GetFileAttributesA(%s,%d,) -> %d [cached]\n", pszFilename, enmLevel, fRet)); + return fRet; + } + + fRet = GetFileAttributesExA(pszFilename, enmLevel, pData); + KWFS_LOG(("GetFileAttributesExA(%s,%d,) -> %d\n", pszFilename, enmLevel, fRet)); + return fRet; +} + + +/** Kernel32 - GetFileAttributesExW. */ +static BOOL WINAPI kwSandbox_Kernel32_GetFileAttributesExW(LPCWSTR pwszFilename, GET_FILEEX_INFO_LEVELS enmLevel, + WIN32_FILE_ATTRIBUTE_DATA *pData) +{ + BOOL 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); + if (enmLevel == GetFileExInfoStandard) + { + pData->dwFileAttributes = pFsObj->Stats.st_attribs; + pData->nFileSizeHigh = (KU64)pFsObj->Stats.st_size >> 32; + pData->nFileSizeLow = (KU32)pFsObj->Stats.st_size; + *(KU64 *)&pData->ftCreationTime = birdNtTimeFromTimeSpec(&pFsObj->Stats.st_birthtim); + *(KU64 *)&pData->ftLastAccessTime = birdNtTimeFromTimeSpec(&pFsObj->Stats.st_atim); + *(KU64 *)&pData->ftLastWriteTime = birdNtTimeFromTimeSpec(&pFsObj->Stats.st_mtim); + kFsCacheObjRelease(g_pFsCache, pFsObj); + fRet = TRUE; + } + else + { + kFsCacheObjRelease(g_pFsCache, pFsObj); + fRet = GetFileAttributesExW(pwszFilename, enmLevel, pData); + } + } + else + { + SetLastError(kwFsLookupErrorToWindowsError(enmError)); + fRet = FALSE; + } + +#ifdef K_STRICT + { + WIN32_FILE_ATTRIBUTE_DATA CheckData = { 0 }; + DWORD const dwErrSaved = GetLastError(); + BOOL const fRetCheck = GetFileAttributesExW(pwszFilename, enmLevel, &CheckData); + kHlpAssertMsg(fRet == fRetCheck, ("fRet=%d fRetCheck=%d; %ls\n", fRet, fRetCheck, pwszFilename)); + if (fRetCheck && fRet) + { +# define ASSERT_FS_FIELD_EQUAL_W(pResult, pExpected, pszFilename, Field, szFmt) \ + kHlpAssertMsg((pResult)->Field == (pExpected)->Field, (#Field ": " szFmt " , expected " szFmt "; %ls\n", (pResult)->Field, (pExpected)->Field, pwszFilename)) + ASSERT_FS_FIELD_EQUAL_W(pData, &CheckData, pwszFilename, dwFileAttributes, "%#x"); + ASSERT_FS_FIELD_EQUAL_W(pData, &CheckData, pwszFilename, nFileSizeHigh, "%#x"); + ASSERT_FS_FIELD_EQUAL_W(pData, &CheckData, pwszFilename, nFileSizeLow, "%#x"); + ASSERT_FS_FIELD_EQUAL_W(pData, &CheckData, pwszFilename, ftCreationTime.dwHighDateTime, "%#x"); + ASSERT_FS_FIELD_EQUAL_W(pData, &CheckData, pwszFilename, ftCreationTime.dwLowDateTime, "%#x"); + ASSERT_FS_FIELD_EQUAL_W(pData, &CheckData, pwszFilename, ftLastWriteTime.dwHighDateTime, "%#x"); + ASSERT_FS_FIELD_EQUAL_W(pData, &CheckData, pwszFilename, ftLastWriteTime.dwLowDateTime, "%#x"); + } + else + kHlpAssertMsg(dwErrSaved == GetLastError(), ("%u, expected %u; %ls\n", dwErrSaved, GetLastError(), pwszFilename)); + SetLastError(dwErrSaved); + } +#endif + KWFS_LOG(("GetFileAttributesExW(%ls,%d,) -> %d [cached]\n", pwszFilename, enmLevel, fRet)); + return fRet; + } + + fRet = GetFileAttributesExW(pwszFilename, enmLevel, pData); + KWFS_LOG(("GetFileAttributesExW(%ls,%d,) -> %d\n", pwszFilename, enmLevel, 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, then 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 + && !( fAllocType == MEM_RESERVE /* After mapping the PCH, VS2019 ends up here (happens */ + && fProt == PAGE_READWRITE /* in real cl.exe runs too). Just shut it up to avoid confusion. */ +#if K_ARCH_BITS >= 64 + && cb > 0x10000000 /* seen 67c00000, 33e00000, ++ */ +#else + && cb > 0x04000000 /* no idea */ +#endif + ) + ) + kwErrPrintf("VirtualAlloc %p LB %#x (%#x,%#x) failed: %p / %u\n", + pvAddr, cb, fAllocType, fProt, pvMem, GetLastError()); + } + + if (pvMem) + { + /* + * Track it. + */ + PKWVIRTALLOC pTracker; + + EnterCriticalSection(&g_Sandbox.VirtualAllocLock); + pTracker = g_Sandbox.pVirtualAllocHead; + while ( pTracker + && (KUPTR)pvMem - (KUPTR)pTracker->pvAlloc >= pTracker->cbAlloc) + pTracker = pTracker->pNext; + LeaveCriticalSection(&g_Sandbox.VirtualAllocLock); + if (!pTracker) + { + DWORD dwErr = GetLastError(); + PKWVIRTALLOC pTracker = (PKWVIRTALLOC)kHlpAlloc(sizeof(*pTracker)); + if (pTracker) + { + pTracker->pvAlloc = pvMem; + pTracker->cbAlloc = cb; + pTracker->idxPreAllocated = idxPreAllocated; + EnterCriticalSection(&g_Sandbox.VirtualAllocLock); + pTracker->pNext = g_Sandbox.pVirtualAllocHead; + g_Sandbox.pVirtualAllocHead = pTracker; + LeaveCriticalSection(&g_Sandbox.VirtualAllocLock); + } + 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; + EnterCriticalSection(&g_Sandbox.VirtualAllocLock); + 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); + LeaveCriticalSection(&g_Sandbox.VirtualAllocLock); + 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; + } + LeaveCriticalSection(&g_Sandbox.VirtualAllocLock); + 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)); + } + LeaveCriticalSection(&g_Sandbox.VirtualAllocLock); + } + } + +#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_CACHE + +/** + * Gets our crypto provider context/instance, creating it if needed. + */ +static HCRYPTPROV kwSandboxGetCryptoProvider(ALG_ID idAlg) +{ + DWORD dwProvider; + HCRYPTPROV *phCryptProv; + HCRYPTPROV hCryptProv; + if ( idAlg == CALG_SHA_256 + || idAlg == CALG_SHA_512) + { + phCryptProv = &g_Sandbox.hCryptProvAes; + dwProvider = PROV_RSA_AES; + } + else + { + phCryptProv = &g_Sandbox.hCryptProvRsa; + dwProvider = PROV_RSA_FULL; + } + hCryptProv = *phCryptProv; + if (hCryptProv) + return hCryptProv; + + /* Create it. */ + if (CryptAcquireContextW(&hCryptProv, NULL, NULL, dwProvider, CRYPT_VERIFYCONTEXT)) + { + kHlpAssert(hCryptProv != 0); + kHlpAssert(hCryptProv != KUPTR_MAX); + *phCryptProv = hCryptProv; + return hCryptProv; + } + + kwErrPrintf("kwSandboxGetCryptoProvider: CryptAcquireContext(,,,%#x, CRYPT_VERIFYCONTEXT) failed! %u\n", + dwProvider, GetLastError()); + return (HCRYPTPROV)NULL; +} + +/** 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) + { + KU32 cbDigest; + const char *pszName; + switch (idAlg) + { + case CALG_MD5: + cbDigest = 128/8; + pszName = "MD5"; + g_cHashesMd5++; + break; + case CALG_SHA1: + cbDigest = 160/8; + pszName = "SHA1"; + g_cHashesSha1++; + break; + case CALG_SHA_256: + cbDigest = 256/8; + pszName = "SHA-256"; + g_cHashesSha256++; + break; + case CALG_SHA_512: + cbDigest = 512/8; + pszName = "SHA-512"; + g_cHashesSha512++; + break; + default: + cbDigest = 0; + pszName = NULL; + break; + } + + if (cbDigest) + { + if (hKey == 0) + { + if (dwFlags == 0) + { + PKWCRYPTHASH pHash = (PKWCRYPTHASH)kHlpAllocZ(sizeof(*pHash)); + if (pHash) + { + kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); + pHash->uMagic = KWCRYPTHASH_MAGIC; + pHash->cbHashed = 0; + pHash->fGoneBad = K_FALSE; + pHash->fFinal = K_FALSE; + pHash->hFallback = KUPTR_MAX; + pHash->idAlg = idAlg; + pHash->pszAlgName = pszName; + pHash->cbDigest = cbDigest; + + /* link it. */ + pHash->pNext = g_Sandbox.pHashHead; + g_Sandbox.pHashHead = pHash; + + *phHash = (KUPTR)pHash; + KWCRYPT_LOG(("CryptCreateHash(hProv=%p, idAlg=%s, 0, 0, *phHash=%p) -> %d [cached]\n", + hProv, pszName, *phHash, TRUE)); + return TRUE; + } + + kwErrPrintf("CryptCreateHash: out of memory!\n"); + } + else + kwErrPrintf("CryptCreateHash: dwFlags=%p is not supported with %s\n", hKey, pszName); + } + else + kwErrPrintf("CryptCreateHash: hKey=%p is not supported with %s\n", hKey, pszName); + } + 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; + PKWCRYPTHASH 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 == KWCRYPTHASH_MAGIC + && !pHash->fFinal + && !pHash->fGoneBad) + { + if (pHash->hFallback == KUPTR_MAX) + { + /* + * 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) + KWCRYPT_LOG(("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) + { + /* Initiate fallback mode (file that we don't normally cache, like .c/.cpp). */ + HCRYPTPROV hCryptProv = kwSandboxGetCryptoProvider(pHash->idAlg); + if (hCryptProv) + { + HCRYPTHASH hCryptHash = KUPTR_MAX; + if (CryptCreateHash(hCryptProv, pHash->idAlg, 0, 0, &hCryptHash)) + { + kHlpAssert(hCryptHash != KUPTR_MAX); + pHash->hFallback = hCryptHash; + fRc = CryptHashData(hCryptHash, pbData, cbData, dwFlags); + if (fRc) + pHash->cbHashed = cbData; + g_cHashesFallbacks++; + KWCRYPT_LOG(("CryptHashData(hHash=%p/fallback, pbData=%p, cbData=%#x, dwFlags=%#x) -> %d (%u) [fallback!]\n", + hHash, pbData, cbData, dwFlags, fRc, GetLastError())); + } + else + { + kwErrPrintf("kwSandbox_Advapi32_CryptHashData: Fallback CryptCreateHash(%u) failed: %u\n", + pHash->idAlg, GetLastError()); + fRc = FALSE; + } + return fRc; + } + } + pHash->fGoneBad = K_TRUE; + SetLastError(ERROR_INVALID_PARAMETER); + fRc = FALSE; + } + else + { + /* fallback. */ + fRc = CryptHashData(pHash->hFallback, pbData, cbData, dwFlags); + if (fRc) + pHash->cbHashed += cbData; + KWCRYPT_LOG(("CryptHashData(hHash=%p/fallback, pbData=%p, cbData=%#x, dwFlags=%#x) -> %d [fallback]\n", + hHash, pbData, cbData, dwFlags, fRc)); + } + } + /* + * Bad handle state. + */ + else + { + if (pHash->uMagic != KWCRYPTHASH_MAGIC) + kwErrPrintf("CryptHashData: Invalid cached hash handle!!\n"); + else + kwErrPrintf("CryptHashData: Hash is already finalized!!\n"); + SetLastError((DWORD)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; +} + + +/** Helper for simpe data hashing. */ +static BOOL kwSandboxCalcHash(ALG_ID idAlg, void const *pvData, KSIZE cbData, KU8 *pbDigest, KSIZE cbDigest) +{ + BOOL fRet = FALSE; + if (idAlg == CALG_MD5) + { + struct MD5Context Ctx; + MD5Init(&Ctx); + MD5Update(&Ctx, (unsigned char const *)pvData, (unsigned)cbData); + MD5Final(pbDigest, &Ctx); + fRet = TRUE; + } + else + { + HCRYPTPROV hCryptProv = kwSandboxGetCryptoProvider(idAlg); + if (hCryptProv) + { + HCRYPTHASH hCryptHash = KUPTR_MAX; + if (CryptCreateHash(hCryptProv, idAlg, 0, 0, &hCryptHash)) + { + if (CryptHashData(hCryptHash, (const BYTE *)pvData, (DWORD)cbData, 0)) + { + DWORD cbActual = (DWORD)cbDigest; + if (CryptGetHashParam(hCryptHash, HP_HASHVAL, pbDigest, &cbActual, 0)) + { + fRet = TRUE; + kHlpAssert(cbActual == cbDigest); + } + else + kwErrPrintf("CryptGetHashParam([%#x],HP_HASHVAL,%p,%#x,0) failed: %u\n", + idAlg, pbDigest, cbDigest, GetLastError()); + } + else + kwErrPrintf("CryptHashData([%#x],%p,%#x,0) failed: %u\n", idAlg, pvData, cbData, GetLastError()); + CryptDestroyHash(hCryptHash); + } + else + kwErrPrintf("CryptCreateHash(%#x) failed: %u\n", idAlg, GetLastError()); + } + } + return fRet; +} + + +/** AdvApi32 - CryptGetHashParam */ +static BOOL WINAPI kwSandbox_Advapi32_CryptGetHashParam(HCRYPTHASH hHash, DWORD dwParam, + BYTE *pbData, DWORD *pcbData, DWORD dwFlags) +{ + BOOL fRc; + PKWCRYPTHASH pHash = g_Sandbox.pHashHead; + kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); + while (pHash && (KUPTR)pHash != hHash) + pHash = pHash->pNext; + if (pHash) + { + if (pHash->uMagic == KWCRYPTHASH_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; + g_cHashes++; + if (pCachedFile) + { + if ( pCachedFile->cbCached == pHash->cbHashed + && !pHash->fGoneBad) + { + KBOOL *pfValid; + switch (pHash->idAlg) + { + case CALG_MD5: + pfValid = &pCachedFile->fValidMd5; + pvRet = pCachedFile->abMd5Digest; + break; + case CALG_SHA1: + pfValid = &pCachedFile->fValidSha1; + pvRet = pCachedFile->abSha1Digest; + break; + case CALG_SHA_256: + pfValid = &pCachedFile->fValidSha256; + pvRet = pCachedFile->abSha256Digest; + break; + case CALG_SHA_512: + pfValid = &pCachedFile->fValidSha512; + pvRet = pCachedFile->abSha512Digest; + break; + default: + kwErrPrintf("Invalid idAlg value: %#x\n", pHash->idAlg); + SetLastError(ERROR_INVALID_SERVER_STATE); + return FALSE; + } + + if (*pfValid) + { + KWCRYPT_LOG(("Already calculated hash for %p/%s! [hit]\n", pCachedFile, pCachedFile->szPath)); + g_cHashesCached++; + } + else + { + fRc = kwSandboxCalcHash(pHash->idAlg, pCachedFile->pbCached, pCachedFile->cbCached, + pvRet, pHash->cbDigest); + if (!fRc) + return FALSE; + *pfValid = K_TRUE; + KWCRYPT_LOG(("Calculated hash for %p/%s.\n", pCachedFile, pCachedFile->szPath)); + } + } + 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)); + g_cHashesPartial++; + pHash->pCachedFile = NULL; + pvRet = pHash->abDigest; + fRc = kwSandboxCalcHash(pHash->idAlg, pCachedFile->pbCached, pHash->cbHashed, + pvRet, pHash->cbDigest); + if (!fRc) + { + pHash->fGoneBad = K_TRUE; + return FALSE; + } + } + pHash->fFinal = K_TRUE; + cbRet = pHash->cbDigest; + break; + } + + pvRet = pHash->abDigest; + cbRet = pHash->cbDigest; + if (pHash->fFinal) + break; + if (pHash->hFallback != KUPTR_MAX) + { + DWORD cbActual = (DWORD)pHash->cbDigest; + if (CryptGetHashParam(pHash->hFallback, HP_HASHVAL, pHash->abDigest, &cbActual, 0)) + { + kHlpAssert(cbActual == pHash->cbDigest); + pHash->fFinal = K_TRUE; + break; + } + kwErrPrintf("CryptGetHashParam/HP_HASHVAL: Fallback CryptGetHashParam failed: %u!!\n", GetLastError()); + } + else + { + kwErrPrintf("CryptGetHashParam/HP_HASHVAL: pCachedFile is NULL!!\n"); + SetLastError(ERROR_INVALID_SERVER_STATE); + } + return FALSE; + } + + case HP_HASHSIZE: + uBuf.dw = pHash->cbDigest; + pvRet = &uBuf; + cbRet = sizeof(DWORD); + break; + + case HP_ALGID: + uBuf.dw = pHash->idAlg; + pvRet = &uBuf; + cbRet = sizeof(DWORD); + break; + + default: + kwErrPrintf("CryptGetHashParam: Unknown dwParam=%#x\n", dwParam); + SetLastError((DWORD)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 if (cbRet == 20) + 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%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], + pbData[16], pbData[17], pbData[18], pbData[19] )); + else if (cbRet >= 32) + 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%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%s [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], + pbData[16], pbData[17], pbData[18], pbData[19], + pbData[20], pbData[21], pbData[22], pbData[23], + pbData[24], pbData[25], pbData[26], pbData[27], + pbData[28], pbData[29], pbData[30], pbData[31], cbRet > 32 ? "..." : "")); + 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((DWORD)NTE_BAD_FLAGS); + } + } + else + { + kwErrPrintf("CryptGetHashParam: Invalid cached hash handle!!\n"); + SetLastError((DWORD)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; + PKWCRYPTHASH pPrev = NULL; + PKWCRYPTHASH pHash = g_Sandbox.pHashHead; + kHlpAssert(GetCurrentThreadId() == g_Sandbox.idMainThread); + while (pHash && (KUPTR)pHash != hHash) + { + pPrev = pHash; + pHash = pHash->pNext; + } + if (pHash) + { + if (pHash->uMagic == KWCRYPTHASH_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_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 = (DWORD)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++ = (KU8)bFiller; + return pvDst; +} + +#endif /* NDEBUG */ + + +/** @todo consider hooking NtQueryDirectoryFile as c1xx.dll/c1.dll in 2019 + * uses it directly to read the content of include directories, however + * they do it one file at the time. We already have the info in the + * cache (where we do bulk reads). There are a lot of calls for the + * SDK include directories, as one can imagine. */ + +/** + * 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("GetFileAttributesExA"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileAttributesExA }, + { TUPLE("GetFileAttributesExW"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileAttributesExW }, + { TUPLE("GetShortPathNameW"), NULL, (KUPTR)kwSandbox_Kernel32_GetShortPathNameW }, +#ifdef WITH_TEMP_MEMORY_FILES + { TUPLE("DeleteFileW"), NULL, (KUPTR)kwSandbox_Kernel32_DeleteFileW }, +#endif + +#ifdef WITH_CONSOLE_OUTPUT_BUFFERING + { TUPLE("WriteConsoleA"), NULL, (KUPTR)kwSandbox_Kernel32_WriteConsoleA }, + { TUPLE("WriteConsoleW"), NULL, (KUPTR)kwSandbox_Kernel32_WriteConsoleW }, +#endif + + { 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_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, K_FALSE /*fOnlyExe*/, K_TRUE /*fCrtSlotArray*/ }, + { 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 }, + + { TUPLE("GetCommandLineA"), NULL, (KUPTR)kwSandbox_Kernel32_GetCommandLineA }, + { TUPLE("GetCommandLineW"), NULL, (KUPTR)kwSandbox_Kernel32_GetCommandLineW }, + +#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("GetFileAttributesExA"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileAttributesExA }, + { TUPLE("GetFileAttributesExW"), NULL, (KUPTR)kwSandbox_Kernel32_GetFileAttributesExW }, + { 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("LoadLibraryExW"), NULL, (KUPTR)kwSandbox_Kernel32_Native_LoadLibraryExW }, +#ifndef NDEBUG + { TUPLE("GetProcAddress"), NULL, (KUPTR)kwSandbox_Kernel32_Native_GetProcAddress }, +#endif + +#ifdef WITH_CONSOLE_OUTPUT_BUFFERING + { TUPLE("WriteConsoleA"), NULL, (KUPTR)kwSandbox_Kernel32_WriteConsoleA }, + { TUPLE("WriteConsoleW"), NULL, (KUPTR)kwSandbox_Kernel32_WriteConsoleW }, +#endif + +#ifdef WITH_HASH_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 }, + { TUPLE("_wdupenv_s"), NULL, (KUPTR)kwSandbox_msvcrt__wdupenv_s, K_FALSE /*fOnlyExe*/, K_TRUE /*fCrtSlotArray*/ }, + +#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) +{ + DWORD cbIgn; + int volatile rc; /* volatile for debugging */ + int volatile rcPrev; + const char *pszMsg; + switch (dwCtrlType) + { + case CTRL_C_EVENT: + rc = 9; + pszMsg = "kWorker: Ctrl-C\r\n"; + break; + + case CTRL_BREAK_EVENT: + rc = 10; + pszMsg = "kWorker: Ctrl-Break\r\n"; + break; + + case CTRL_CLOSE_EVENT: + rc = 11; + pszMsg = "kWorker: console closed\r\n"; + break; + + case CTRL_LOGOFF_EVENT: + rc = 11; + pszMsg = "kWorker: logoff event\r\n"; + break; + + case CTRL_SHUTDOWN_EVENT: + rc = 11; + pszMsg = "kWorker: shutdown event\r\n"; + break; + + default: + fprintf(stderr, "kwSandboxCtrlHandler: %#x\n", dwCtrlType); + return TRUE; + } + + /* + * Terminate the process after 5 seconds. + * If we get here a second time we just terminate the process ourselves. + * + * Note! We do no try call exit() here as it turned out to deadlock a lot + * flusing file descriptors (stderr back when we first wrote to it). + */ + rcPrev = g_rcCtrlC; + g_rcCtrlC = rc; + WriteFile(GetStdHandle(STD_ERROR_HANDLE), pszMsg, (DWORD)strlen(pszMsg), &cbIgn, NULL); + if (rcPrev == 0) + { + int i; + for (i = 0; i < 10; i++) + { + CancelIoEx(g_hPipe, NULL); /* wake up idle main thread */ + Sleep(500); + } + } + TerminateProcess(GetCurrentProcess(), rc); + return TRUE; +} + + +#if 0 +/** + * Resets the KWMODULE::fVisited flag for _all_ known modules. + */ +static void kwSandboxResetModuleVisited(void) +{ + PKWMODULE pMod = g_pModuleHead; + while (pMod) + { + pMod->fVisited = K_FALSE; + pMod = pMod->pNextList; + } +} + + +/** + * 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) +{ + KWLDR_LOG(("kwSandboxResetModuleState: %d %d %s\n", pMod->fNative, pMod->fVisited, pMod->pszPath)); + if (!pMod->fNative) + { + pMod->u.Manual.enmState = KWMODSTATE_NEEDS_BITS; + if (!pMod->fVisited) /* Avoid loops. */ + { + KSIZE iImp; + pMod->fVisited = K_TRUE; + iImp = pMod->u.Manual.cImpMods; + while (iImp-- > 0) + kwSandboxResetModuleState(pMod->u.Manual.apImpMods[iImp]); + } + } + /* Hack: Re-init mspdbXXX.dll when we want to use a different mspdbsrv.exe instance. */ + else if (pMod->fReInitOnMsPdbSrvEndpointChange) + { + const char *pszValue = kwSandboxDoGetEnvA(&g_Sandbox, TUPLE("_MSPDBSRV_ENDPOINT_")); + if (pMod->fReInitOnMsPdbSrvEndpointChange == 1) + { + pMod->fReInitOnMsPdbSrvEndpointChange = 2; + pMod->pszMsPdbSrvEndpoint = pszValue ? kHlpStrDup(pszValue) : NULL; + KWLDR_LOG(("Not re-initing '%s': first time used (_MSPDBSRV_ENDPOINT_ is '%s')\n", + pMod->pszPath, pszValue ? pszValue : "<null>")); + } + else if ( (pszValue == NULL && pMod->pszMsPdbSrvEndpoint == NULL) + || (pszValue != NULL && pMod->pszMsPdbSrvEndpoint != NULL && kHlpStrComp(pszValue, pMod->pszMsPdbSrvEndpoint) == 0)) + KWLDR_LOG(("Not re-initing '%s': _MSPDBSRV_ENDPOINT_ unchanged ('%s')\n", + pMod->pszPath, pszValue ? pszValue : "<null>")); + else + { + KWLDR_LOG(("Re-initing '%s': _MSPDBSRV_ENDPOINT_ changed from '%s' to '%s'\n", pMod->pszPath, + pMod->pszMsPdbSrvEndpoint ? pMod->pszMsPdbSrvEndpoint : "<null>", pszValue ? pszValue : "<null>")); + kHlpFree(pMod->pszMsPdbSrvEndpoint); + if (pszValue != NULL) + pMod->pszMsPdbSrvEndpoint = kHlpStrDup(pszValue); + else + pMod->pszMsPdbSrvEndpoint = NULL; + pMod->fNeedReInit = K_TRUE; + } + } +} +#else +/** + * Used by kwSandboxExec to reset the state of the module tree. + */ +static void kwSandboxResetModuleState(void) +{ + PKWMODULE pMod = g_pModuleHead; + while (pMod) + { + if (!pMod->fNative) + pMod->u.Manual.enmState = K_MIN(pMod->u.Manual.enmReInitState, pMod->u.Manual.enmState); + /* Hack: Re-init mspdbXXX.dll when we want to use a different mspdbsrv.exe instance. */ + else if ( pMod->fReInitOnMsPdbSrvEndpointChange + && ( g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_CL + || g_Sandbox.pTool->u.Sandboxed.enmHint == KWTOOLHINT_VISUAL_CPP_LINK)) + { + const char *pszValue = kwSandboxDoGetEnvA(&g_Sandbox, TUPLE("_MSPDBSRV_ENDPOINT_")); + if (pMod->fReInitOnMsPdbSrvEndpointChange == 1) + { + pMod->fReInitOnMsPdbSrvEndpointChange = 2; + pMod->pszMsPdbSrvEndpoint = pszValue ? kHlpStrDup(pszValue) : NULL; + KWLDR_LOG(("Not re-initing '%s': first time used (_MSPDBSRV_ENDPOINT_ is '%s')\n", + pMod->pszPath, pszValue ? pszValue : "<null>")); + } + else if ( (pszValue == NULL && pMod->pszMsPdbSrvEndpoint == NULL) + || (pszValue != NULL && pMod->pszMsPdbSrvEndpoint != NULL && kHlpStrComp(pszValue, pMod->pszMsPdbSrvEndpoint) == 0)) + KWLDR_LOG(("Not re-initing '%s': _MSPDBSRV_ENDPOINT_ unchanged ('%s')\n", + pMod->pszPath, pszValue ? pszValue : "<null>")); + else + { + KWLDR_LOG(("Re-initing '%s': _MSPDBSRV_ENDPOINT_ changed from '%s' to '%s'\n", pMod->pszPath, + pMod->pszMsPdbSrvEndpoint ? pMod->pszMsPdbSrvEndpoint : "<null>", pszValue ? pszValue : "<null>")); + kHlpFree(pMod->pszMsPdbSrvEndpoint); + if (pszValue != NULL) + pMod->pszMsPdbSrvEndpoint = kHlpStrDup(pszValue); + else + pMod->pszMsPdbSrvEndpoint = NULL; + pMod->fNeedReInit = K_TRUE; + } + } + + pMod = pMod->pNextList; + } +} +#endif + +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); + + EnterCriticalSection(&g_Sandbox.HandlesLock); + + /* + * 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) + { + LeaveCriticalSection(&g_Sandbox.HandlesLock); + 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. + */ + kHlpAssertStmtReturn(pSandbox->papHandles[idxHandle] == NULL, LeaveCriticalSection(&g_Sandbox.HandlesLock), K_FALSE); + pSandbox->papHandles[idxHandle] = pHandle; + pSandbox->cActiveHandles++; + LeaveCriticalSection(&g_Sandbox.HandlesLock); + return K_TRUE; +} + + +/** + * Safely looks up a handle, does not get it and it must not be 'put'. + * + * @returns Pointer to the handle structure if found, otherwise NULL. + * @param hFile The handle to resolve. + */ +static PKWHANDLE kwSandboxHandleLookup(HANDLE hFile) +{ + KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hFile); + EnterCriticalSection(&g_Sandbox.HandlesLock); + if (idxHandle < g_Sandbox.cHandles) + { + PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; + LeaveCriticalSection(&g_Sandbox.HandlesLock); + return pHandle; + } + LeaveCriticalSection(&g_Sandbox.HandlesLock); + return NULL; +} + + +/** + * Safely gets a handle, must be "put" when done with it. + * + * @returns Pointer to the handle structure if found, otherwise NULL. + * @param hFile The handle to resolve. + */ +static PKWHANDLE kwSandboxHandleGet(HANDLE hFile) +{ + KUPTR const idxHandle = KW_HANDLE_TO_INDEX(hFile); + EnterCriticalSection(&g_Sandbox.HandlesLock); + if (idxHandle < g_Sandbox.cHandles) + { + PKWHANDLE pHandle = g_Sandbox.papHandles[idxHandle]; + if (pHandle) + { + kHlpAssertMsg(pHandle->tidOwner == KU32_MAX, ("hFile=%p tidOwner=%#x\n", hFile, pHandle->tidOwner)); + pHandle->tidOwner = GetCurrentThreadId(); + LeaveCriticalSection(&g_Sandbox.HandlesLock); + return pHandle; + } + } + LeaveCriticalSection(&g_Sandbox.HandlesLock); + return NULL; +} + + +/** + * Puts a handle returned by kwSandboxHandleGet. + * + * @param pHandle The handle to "put". + */ +K_INLINE void kwSandboxHandlePut(PKWHANDLE pHandle) +{ + kHlpAssertMsg(pHandle->tidOwner == GetCurrentThreadId(), + ("hFile tidOwner=%#x tidMe=%#x\n", pHandle->hHandle, pHandle->tidOwner, GetCurrentThreadId())); + pHandle->tidOwner = KU32_MAX; +} + + +/** + * 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_CACHE + PKWCRYPTHASH 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). */ + EnterCriticalSection(&pSandbox->HandlesLock); + 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; +#ifdef WITH_TEMP_MEMORY_FILES + 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; +#endif + default: + kHlpAssertFailed(); + } + if (--pHandle->cRefs == 0) + kHlpFree(pHandle); + if (--pSandbox->cActiveHandles == pSandbox->cFixedHandles) + break; + } + } + kHlpAssert(pSandbox->cActiveHandles == pSandbox->cFixedHandles); + } + LeaveCriticalSection(&pSandbox->HandlesLock); + + /* 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. */ + EnterCriticalSection(&g_Sandbox.VirtualAllocLock); + pTracker = g_Sandbox.pVirtualAllocHead; + g_Sandbox.pVirtualAllocHead = NULL; + LeaveCriticalSection(&g_Sandbox.VirtualAllocLock); + 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_CACHE + /* + * Hash handles. + */ + pHash = pSandbox->pHashHead; + pSandbox->pHashHead = NULL; + while (pHash) + { + PKWCRYPTHASH pNext = pHash->pNext; + KWCRYPT_LOG(("Freeing leaked hash instance %#p\n", pHash)); + if (pHash->hFallback != KUPTR_MAX) + CryptDestroyHash(pHash->hFallback); + 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; +#ifdef WITH_CONSOLE_OUTPUT_BUFFERING + pProcParams->StandardOutput = pSandbox->StdOut.hOutput; + pProcParams->StandardError = pSandbox->StdErr.hOutput; /* CL.EXE messes with this one. */ +#endif +} + + +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) + { + if (g_cVerbose > 2) + fprintf(stderr, "kWorker: info: Executing (sandboxed): %s\n", g_Sandbox.pszCmdLine); + + /* + * Do module initialization. + */ +#if 0 + //kwSandboxResetModuleVisited(); + //kwSandboxResetModuleState(pTool->u.Sandboxed.pExe); +#else + kwSandboxResetModuleState(); +#endif + 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); + /** @todo Flush sandboxed native CRTs too. */ + } + 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) + { + KMKBUILTINCTX Ctx = { papszPostCmdArgs[0], NULL }; + return kmk_builtin_kDepObj(cPostCmdArgs, (char **)papszPostCmdArgs, NULL, &Ctx); + } + + return kwErrPrintfRc(42 + 5, "Unknown post command: '%s'\n", pszCmd); +} + + +/** + * Helper for kSubmitHandleSpecialEnvVar that gets the current process group. + */ +static unsigned kwGetCurrentProcessorGroup(void) +{ + typedef BOOL (WINAPI *PFNGETTHREADGROUPAFFINITY)(HANDLE, GROUP_AFFINITY *); + HMODULE hmodKernel32 = GetModuleHandleW(L"KERNEL32.DLL"); + PFNGETTHREADGROUPAFFINITY pfnGetter = (PFNGETTHREADGROUPAFFINITY)GetProcAddress(hmodKernel32, "GetThreadGroupAffinity"); + if (pfnGetter) + { + GROUP_AFFINITY GroupAffinity; + memset(&GroupAffinity, 0, sizeof(GroupAffinity)); + if (pfnGetter(GetCurrentThread(), &GroupAffinity)) + return GroupAffinity.Group; + } + return 0; +} + + +/** + * Helper for kSubmitHandleSpecialEnvVar that gets the current process group. + */ +static KSIZE kwGetCurrentAuthenticationIdAsString(char *pszValue) +{ + KSIZE cchRet = 0; + HANDLE hToken; + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + { + DWORD cbRet; + TOKEN_STATISTICS TokenStats; + memset(&TokenStats, 0, sizeof(TokenStats)); + if (GetTokenInformation(hToken, TokenStatistics, &TokenStats, sizeof(TokenStats), &cbRet)) + cchRet = sprintf(pszValue, "%" KX64_PRI, + ((KU64)TokenStats.AuthenticationId.HighPart << 32) | TokenStats.AuthenticationId.LowPart); + else + kwErrPrintf("GetTokenInformation/TokenStatistics failed: %u\n", GetLastError()); + CloseHandle(hToken); + } + else + kwErrPrintf("OpenProcessToken failed: %u\n", GetLastError()); + return cchRet; +} + + +/** + * Look for and expand the special environment variable. + * + * We the special variable contains elements like "@@VAR_NAME@@" that kmk + * couldn't accuratly determine. Currently the following variables are + * implemented: + * - "@@PROCESSOR_GROUP@@" - The processor group number. + * - "@@AUTHENTICATION_ID@@" - The authentication ID from the process token. + * - "@@PID@@" - The kWorker process ID. + * - "@@@@" - Escaped "@@". + * - "@@DEBUG_COUNTER@@" - An ever increasing counter (starts at zero). + */ +static int kSubmitHandleSpecialEnvVar(KU32 cEnvVars, const char **papszEnvVars, const char *pszSpecialEnv, char **ppszToFree) +{ + KSIZE const cchSpecialEnv = kHlpStrLen(pszSpecialEnv); + KU32 i = cEnvVars; + while (i-- > 0) + if ( kHlpStrNComp(papszEnvVars[i], pszSpecialEnv, cchSpecialEnv) == 0 + && papszEnvVars[i][cchSpecialEnv] == '=') + { + /* We will expand stuff like @@NAME@@ */ + const char *pszValue = papszEnvVars[i]; + KSIZE offDst = 0; + char szTmp[1024]; + for (;;) + { + const char *pszAt = kHlpStrChr(pszValue, '@'); + while (pszAt && pszAt[1] != '@') + pszAt = kHlpStrChr(pszAt + 1, '@'); + if (pszAt) + { + KSIZE cchSrc = pszAt - pszValue; + if (offDst + cchSrc < sizeof(szTmp)) + { + char szSrc[64]; + + kHlpMemCopy(&szTmp[offDst], pszValue, cchSrc); + offDst += cchSrc; + pszValue = pszAt + 2; + + if (kHlpStrNComp(pszValue, "PROCESS_GROUP@@", 15) == 0) + { + pszValue += 15; + if (g_iProcessGroup == -1) + g_iProcessGroup = kwGetCurrentProcessorGroup(); + cchSrc = sprintf(szSrc, "%u", g_iProcessGroup); + } + else if (kHlpStrNComp(pszValue, "AUTHENTICATION_ID@@", 19) == 0) + { + pszValue += 19; + cchSrc = kwGetCurrentAuthenticationIdAsString(szSrc); + } + else if (kHlpStrNComp(pszValue, "PID@@", 5) == 0) + { + pszValue += 5; + cchSrc = sprintf(szSrc, "%d", getpid()); + } + else if (kHlpStrNComp(pszValue, "@@", 2) == 0) + { + pszValue += 2; + szSrc[0] = '@'; + szSrc[1] = '@'; + szSrc[2] = '\0'; + cchSrc = 2; + } + else if (kHlpStrNComp(pszValue, "DEBUG_COUNTER@@", 15) == 0) + { + static unsigned int s_iCounter = 0; + pszValue += 15; + cchSrc = sprintf(szSrc, "%u", s_iCounter++); + } + else + return kwErrPrintfRc(42 + 6, "Special environment variable contains unknown reference: '%s'!\n", + pszValue - 2); + if (offDst + cchSrc < sizeof(szTmp)) + { + kHlpMemCopy(&szTmp[offDst], szSrc, cchSrc); + offDst += cchSrc; + continue; + } + } + } + else + { + KSIZE cchSrc = kHlpStrLen(pszValue); + if (offDst + cchSrc < sizeof(szTmp)) + { + kHlpMemCopy(&szTmp[offDst], pszValue, cchSrc); + offDst += cchSrc; + break; + } + } + return kwErrPrintfRc(42 + 6, "Special environment variable value too long!\n"); + } + szTmp[offDst] = '\0'; + + /* Return a copy of it: */ + papszEnvVars[i] = *ppszToFree = kHlpDup(szTmp, offDst + 1); + if (papszEnvVars[i]) + { + SetEnvironmentVariableA(pszSpecialEnv, kHlpStrChr(papszEnvVars[i], '=') + 1); /* hack */ + return 0; + } + return kwErrPrintfRc(42 + 6, "Special environment variable: out of memory\n"); + } + + return kwErrPrintfRc(42 + 6, "Special environment variable not found: '%s'\n", pszSpecialEnv); +} + + +/** + * 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 pszSpecialEnv Name of special environment variable that + * requires selective expansion here. + * @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, const char *pszSpecialEnv, + KBOOL fNoPchCaching, KU32 cPostCmdArgs, const char **papszPostCmdArgs) +{ + int rcExit; + PKWTOOL pTool; + char *pszSpecialEnvFree = NULL; + + 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++; + + /* + * Expand pszSpecialEnv if present. + */ + if (pszSpecialEnv && *pszSpecialEnv) + { + rcExit = kSubmitHandleSpecialEnvVar(cEnvVars, papszEnvVars, pszSpecialEnv, &pszSpecialEnvFree); + if (!rcExit) + { /* likely */ } + else + return rcExit; + } + + /* + * Lookup the tool. + */ + g_Sandbox.pTool = NULL; /* Avoid confusion between the SetDllDirectoryW hacks. */ + 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; + if (pszSpecialEnvFree) + { + SetEnvironmentVariableA(pszSpecialEnv, NULL); /* hack */ + kHlpFree(pszSpecialEnvFree); + } + 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; + + /* Name of special enviornment variable requiring selective expansion. */ + if (cbMsg >= 1) + { + const char *pszSpecialEnv = pszMsg; + cbTmp = kHlpStrLen(pszMsg); + pszMsg += cbTmp + 1; + cbMsg -= K_MIN(cbMsg, cbTmp + 1); + + /* 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, pszSpecialEnv, + 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 special environment variable!\n"); + } + else + kwErrPrintf("Detected bogus message unpacking flags!\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; + while (g_rcCtrlC == 0) + { + 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; + } + } + 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; + while (g_rcCtrlC == 0) + { + 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; + } + } + 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]; + + 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)) ); + +#ifdef WITH_HASH_CACHE + off += sprintf(&szBuf[off], "%s %14s hashes calculated, %s cache hits (%u%%), %s fallbacks, %s partial\n", szPrf, + kwFmtU64(sz1, g_cHashes), + kwFmtU64(sz2, g_cHashesCached), + (unsigned)(g_cHashesCached * 100 / K_MAX(g_cHashes, 1)), + kwFmtU64(sz3, g_cHashesFallbacks), + kwFmtU64(sz4, g_cHashesPartial)); + off += sprintf(&szBuf[off], "%s %14s MD5: %s SHA-1: %s SHA-256: %s SHA-512: %s\n", szPrf, "", kwFmtU64(sz1, g_cHashesMd5), + kwFmtU64(sz2, g_cHashesSha1), kwFmtU64(sz3, g_cHashesSha256), kwFmtU64(sz4, g_cHashesSha512)); +#endif + + /* + * 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; + char **papszEnvVars; + const char *pszSpecialEnv = ""; + const char *pszSpecialEnvFull = NULL; + 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++; + } + + /* Optional directory change. */ + if ( i < argc + && ( strcmp(argv[i], "--set-special") == 0 + || strcmp(argv[i], "-s") == 0 ) ) + { + i++; + if (i >= argc) + return kwErrPrintfRc(2, "--set-special takes an argument!\n"); + pszSpecialEnvFull = argv[i++]; + putenv(pszSpecialEnvFull); + pszSpecialEnv = strdup(pszSpecialEnvFull); + *strchr(pszSpecialEnv, '=') = '\0'; + } + + /* 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"); + + /* + * Duplicate the environment. + */ + cEnvVars = 0; + while (environ[cEnvVars] != NULL) + cEnvVars++; + papszEnvVars = (char **)kHlpAllocZ(sizeof(papszEnvVars[0]) * (cEnvVars + 2)); + + /* + * Do the job. + */ + rcExit = 0; + for (j = 0; j < cRepeats; j++) + { + memcpy(papszEnvVars, environ, sizeof(papszEnvVars[0]) * cEnvVars); + rcExit = kSubmitHandleJobUnpacked(argv[i], pszCwd, + argc - i, &argv[i], fWatcomBrainDamange, + cEnvVars, papszEnvVars, pszSpecialEnv, 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; +} + + +/** + * Reads @a pszFile into memory and chops it up into an argument vector. + * + * @returns Pointer to the argument vector on success, NULL on failure. + * @param pszFile The file to load. + * @param pcArgs Where to return the number of arguments. + * @param ppszFileContent Where to return the allocation. + */ +static char **kwFullTestLoadArgvFile(const char *pszFile, int *pcArgs, char **ppszFileContent) +{ + char **papszArgs = NULL; + FILE *pFile = fopen(pszFile, "r"); + if (pFile) + { + long cbFile; + if ( fseek(pFile, 0, SEEK_END) == 0 + && (cbFile = ftell(pFile)) >= 0 + && fseek(pFile, 0, SEEK_SET) == 0) + { + char *pszFile = kHlpAllocZ(cbFile + 3); + if (pszFile) + { + size_t cbRead = fread(pszFile, 1, cbFile + 1, pFile); + if ( feof(pFile) + && !ferror(pFile)) + { + size_t off = 0; + int cArgs = 0; + int cAllocated = 0; + char ch; + + pszFile[cbRead] = '\0'; + pszFile[cbRead + 1] = '\0'; + pszFile[cbRead + 2] = '\0'; + + while ((ch = pszFile[off]) != '\0') + { + char *pszArg; + switch (ch) + { + case ' ': + case '\t': + case '\n': + case '\r': + off++; + continue; + + case '\\': + if (pszFile[off + 1] == '\n' || pszFile[off + 1] == '\r') + { + off += 2; + continue; + } + /* fall thru */ + default: + pszArg = &pszFile[off]; + do + ch = pszFile[++off]; + while (ch != '\0' && ch != ' ' && ch != '\t' && ch != '\n' && ch != '\r'); + pszFile[off++] = '\0'; + break; + + case '\'': + pszArg = &pszFile[++off]; + while ((ch = pszFile[off]) != '\0' && ch != '\'') + off++; + pszFile[off++] = '\0'; + break; + + case '\"': /** @todo escape sequences */ + pszArg = &pszFile[++off]; + while ((ch = pszFile[off]) != '\0' && ch != '"') + off++; + pszFile[off++] = '\0'; + break; + } + if (cArgs + 1 >= cAllocated) + { + void *pvNew; + cAllocated = cAllocated ? cAllocated * 2 : 16; + pvNew = kHlpRealloc(papszArgs, cAllocated * sizeof(papszArgs[0])); + if (pvNew) + papszArgs = (char **)pvNew; + else + { + kHlpFree(papszArgs); + papszArgs = NULL; + break; + } + } + papszArgs[cArgs] = pszArg; + papszArgs[++cArgs] = NULL; + } + *pcArgs = cArgs; + } + else + kwErrPrintf("Error reading '%s'!\n", pszFile); + } + else + kwErrPrintf("Error allocating %lu bytes!\n", cbFile + 2); + } + else + kwErrPrintf("Error seeking '%s'!\n", pszFile); + fclose(pFile); + } + else + kwErrPrintf("Error opening '%s'!\n", pszFile); + return papszArgs; +} + +/** + * Appends a string to an string vector (arguments or enviornment). + * + * @returns 0 on success, non-zero on failure (exit code). + * @param ppapszVector Pointer to the string pointer array. + * @param pcEntries Pointer to the array size. + * @param pszAppend The string to append. + */ +static int kwFullTestVectorAppend(const char ***ppapszVector, KU32 *pcEntries, char const *pszAppend) +{ + KU32 cEntries = *pcEntries; + if (!(cEntries & 15)) + { + void *pvNew = kHlpRealloc((void *)*ppapszVector, sizeof(char *) * (cEntries + 16 + 1)); + if (pvNew) + *ppapszVector = (const char **)pvNew; + else + return kwErrPrintfRc(2, "Out of memory!\n"); + } + (*ppapszVector)[cEntries] = pszAppend; + (*ppapszVector)[++cEntries] = NULL; + *pcEntries = cEntries; + return 0; +} + + +/** + * Parses arguments for --full-test. + * + * @returns 0 on success, non-zero on failure (exit code). + */ +static int kwFullTestRunParseArgs(PKWONETEST *ppHead, int *piState, int argc, char **argv, + const char *pszDefaultCwd, int cRecursions, const char *pszJobSrc) +{ + PKWONETEST pCur = *ppHead; + int i; + for (i = 0; i < argc; i++) + { + int rc = 0; + const char *pszArg = argv[i]; + if (*pszArg == 'k') + { + if (kHlpStrComp(pszArg, "kSubmit") == 0) + { + if (*piState != 0) + { + pCur = (PKWONETEST)kHlpAllocZ(sizeof(*pCur)); + if (!pCur) + return kwErrPrintfRc(2, "Out of memory!\n"); + pCur->fVirgin = K_TRUE; + pCur->pszCwd = pszDefaultCwd; + pCur->cRuns = 1; + pCur->pNext = *ppHead; + *ppHead = pCur; + *piState = 0; + } + else if (!pCur->fVirgin) + return kwErrPrintfRc(2, "Unexpected 'kSubmit' as argument #%u\n", i); + pCur->pszJobSrc = pszJobSrc; + pCur->iJobSrc = i; + continue; /* (to stay virgin) */ + } + + /* Ignore "kWorker 378172/62:" sequences that kmk/kSubmit spews out on failure. */ + if ( kHlpStrComp(pszArg, "kWorker") == 0 + && i + 1 < argc + && (unsigned)(argv[i + 1][0] - '0') <= 9) + { + i++; + continue; + } + } + + if ( *pszArg == '-' + && ( *piState == 0 + || pszArg[1] == '@')) + { + const char *pszValue = NULL; + char ch = *++pszArg; + pszArg++; + if (ch == '-') + { + ch = '\0'; + if (*pszArg == '\0') /* -- */ + *piState = 2; + /* Translate or handle long options: */ + else if (kHlpStrComp(pszArg, "putenv") == 0 || kHlpStrComp(pszArg, "set") == 0) + ch = 'E'; + else if (kHlpStrComp(pszArg, "special-env") == 0) + ch = 's'; + else if (kHlpStrComp(pszArg, "default-env") == 0) + { + unsigned i; + pCur->cEnvVars = 0; + for (i = 0; environ[i] && rc == 0; i++) + rc = kwFullTestVectorAppend(&pCur->papszEnvVars, &pCur->cEnvVars, kHlpStrDup(environ[i])); /* leaks; unchecked */ + } + else if (kHlpStrComp(pszArg, "chdir") == 0) + ch = 'C'; + else if (kHlpStrComp(pszArg, "post-cmd") == 0) + ch = 'P'; + else if (kHlpStrComp(pszArg, "response-file") == 0) + ch = '@'; + else if (kHlpStrComp(pszArg, "runs") == 0) + ch = 'R'; + else if (kHlpStrComp(pszArg, "watcom-brain-damage") == 0) + pCur->fWatcomBrainDamange = K_TRUE; + else if (kHlpStrComp(pszArg, "no-pch-caching") == 0) + pCur->fNoPchCaching = K_TRUE; + else if (kHlpStrComp(pszArg, "executable") == 0) + ch = 'e'; + else if (kHlpStrComp(pszArg, "breakpoint") == 0) + { + __debugbreak(); + continue; /* (to stay virgin) */ + } + else + return kwErrPrintfRc(2, "Unknown option: --%s\n", pszArg); + pszArg = ""; + } + + while (ch != '\0' && rc == 0) + { + /* Fetch value if needed: */ + switch (ch) + { + case '@': + case 'e': + case 'E': + case 's': + case 'C': + case 'R': + if (*pszArg == ':' || *pszArg == '=') + pszValue = &pszArg[1]; + else if (*pszArg) + pszValue = pszArg; + else if (i + 1 < argc) + pszValue = argv[++i]; + else + return kwErrPrintfRc(2, "Option -%c takes a value\n", ch); + pszArg = ""; + break; + } + + /* Handle the option: */ + switch (ch) + { + case 'E': + rc = kwFullTestVectorAppend(&pCur->papszEnvVars, &pCur->cEnvVars, pszValue); + break; + case 'C': + pCur->pszCwd = pszValue; + break; + case 's': + pCur->pszSpecialEnv = pszValue; + break; + case 'e': + pCur->pszExecutable = pszValue; + break; + case 'P': + *piState = 1; + if (*pszArg) + return kwErrPrintfRc(2, "Option -P cannot be followed by other options!\n"); + break; + case 'R': + pCur->cRuns = atoi(pszValue); + if ((int)pCur->cRuns < 0) + return kwErrPrintfRc(2, "Option -R takes a positive (or zero) integer as value: %s\n", pszValue); + break; + case '@': + if (cRecursions < 5) + { + char *pszLeaked = NULL; + int cArgs = 0; + char **papszArgsLeaked = kwFullTestLoadArgvFile(pszValue, &cArgs, &pszLeaked); + if (papszArgsLeaked) + { + rc = kwFullTestRunParseArgs(ppHead, piState, cArgs, papszArgsLeaked, pszDefaultCwd, + cRecursions + 1, pszValue); + pCur = *ppHead; + } + else + return 2; + } + else + return kwErrPrintfRc(2, "Too deep response file nesting!\n"); + break; + } + + /* next */ + ch = *pszArg++; + } + } + else if (*piState == 2) + rc = kwFullTestVectorAppend(&pCur->papszArgs, &pCur->cArgs, pszArg); + else if (*piState == 1) + { + if (pszArg[0] != '-' || pszArg[1] != '-' || pszArg[2] != '\0') + rc = kwFullTestVectorAppend(&pCur->papszPostCmdArgs, &pCur->cPostCmdArgs, pszArg); + else + *piState = 2; + } + else + return kwErrPrintfRc(2, "Unexpected argument: %s\n", pszArg); + if (rc) + return rc; + pCur->fVirgin = K_FALSE; + } + return 0; +} + + +/** + * Handles what comes after --full-test. + * + * @returns Exit code. + * @param argc Number of arguments after --full-test. + * @param argv Arguments after --full-test. + */ +static int kwFullTestRun(int argc, char **argv) +{ + char szDefaultCwd[MAX_PATH]; + const char *pszDefaultCwd = getcwd(szDefaultCwd, sizeof(szDefaultCwd)); + KWONETEST FirstTest; + PKWONETEST pHead = &FirstTest; + PKWONETEST pCur; + int iState = 0; + int rcExit; + + /* + * Parse arguments. + */ + kHlpMemSet(&FirstTest, 0, sizeof(FirstTest)); + FirstTest.pszJobSrc = "command-line"; + FirstTest.iJobSrc = 1; + FirstTest.fVirgin = K_TRUE; + FirstTest.pszCwd = pszDefaultCwd; + FirstTest.cRuns = 1; + + rcExit = kwFullTestRunParseArgs(&pHead, &iState, argc, argv, pszDefaultCwd, 0, "command-line"); + if (rcExit) + return rcExit; + + /* + * Do the job. LIFO ordering (see kSubmit). + */ + for (pCur = pHead; pCur; pCur = pCur->pNext) + { + if (!pCur->pszExecutable && pCur->papszArgs) + pCur->pszExecutable = pCur->papszArgs[0]; + if ( pCur->pszExecutable + && pCur->cArgs > 0 + && pCur->cEnvVars > 0) + { + size_t const cbEnvVarCopy = sizeof(pCur->papszEnvVars[0]) * (pCur->cEnvVars + 1); + char ** const papszEnvVarsCopy = (char **)kHlpDup(pCur->papszEnvVars, cbEnvVarCopy); + unsigned iRun; + + for (iRun = 0; iRun < pCur->cRuns; iRun++) + { + rcExit = kSubmitHandleJobUnpacked(pCur->pszExecutable, pCur->pszCwd, + pCur->cArgs, pCur->papszArgs, pCur->fWatcomBrainDamange, + pCur->cEnvVars, pCur->papszEnvVars, pCur->pszSpecialEnv, + pCur->fNoPchCaching, pCur->cPostCmdArgs, pCur->papszPostCmdArgs); + + KW_LOG(("rcExit=%d\n", rcExit)); + kwSandboxCleanupLate(&g_Sandbox); + + memcpy((void *)pCur->papszEnvVars, papszEnvVarsCopy, cbEnvVarCopy); + } + kHlpFree(papszEnvVarsCopy); + } + else + rcExit = kwErrPrintfRc(2, "Job is underspecified! %s%s%s (Job started with argument #%u, %s)\n", + pCur->pszExecutable ? "" : " No executable!", + pCur->cArgs < 1 ? " No arguments!" : "", + pCur->cEnvVars < 1 ? " No environment!" : "", + pCur->iJobSrc, pCur->pszJobSrc); + } + + 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; +} + + +/** + * Helper for main() argument handling that sets the processor group if + * possible. + */ +static void kwSetProcessorGroup(unsigned long uGroup) +{ + typedef BOOL (WINAPI *PFNSETTHREADGROUPAFFINITY)(HANDLE, const GROUP_AFFINITY*, GROUP_AFFINITY *); + HMODULE const hmodKernel32 = GetModuleHandleW(L"KERNEL32.DLL"); + PFNSETTHREADGROUPAFFINITY pfnSetThreadGroupAffinity; + + pfnSetThreadGroupAffinity = (PFNSETTHREADGROUPAFFINITY)GetProcAddress(hmodKernel32, "SetThreadGroupAffinity"); + if (pfnSetThreadGroupAffinity) + { + GROUP_AFFINITY OldAff = { 0, 0, 0, 0, 0 }; + GROUP_AFFINITY NewAff = { 0, (WORD)uGroup, 0, 0, 0 }; + NewAff.Mask = win_get_processor_group_active_mask((WORD)uGroup); + if (NewAff.Mask && (WORD)uGroup == uGroup) + { + if (!pfnSetThreadGroupAffinity(GetCurrentThread(), &NewAff, &OldAff)) + kwErrPrintf("Failed to set processor group to %lu (%p): %u\n", uGroup, NewAff.Mask, GetLastError()); + } + else if (GetLastError() == NO_ERROR) + kwErrPrintf("Cannot set processor group to %lu: No active processors in group!\n", uGroup); + else + kwErrPrintf("Cannot set processor group to %lu: GetLogicalProcessorInformationEx failed: %u\n", + uGroup, GetLastError()); + } + else + { + OSVERSIONINFOA VerInfo = {0}; + if (VerInfo.dwMajorVersion > 6 || (VerInfo.dwMajorVersion == 6 && VerInfo.dwMinorVersion >= 1)) + kwErrPrintf("Cannot set processor group to %lu: SetThreadGroupAffinity no found! (Windows version %lu.%lu)\n", + uGroup, VerInfo.dwMajorVersion, VerInfo.dwMinorVersion); + } +} + + +int main(int argc, char **argv) +{ +#if defined(KBUILD_OS_WINDOWS) && defined(KBUILD_ARCH_X86) + PVOID pvVecXcptHandler = AddVectoredExceptionHandler(0 /*called last*/, + kwSandboxVecXcptEmulateChained); +#endif + KSIZE cbMsgBuf = 0; + KU8 *pbMsgBuf = NULL; + int i; + HANDLE hPipe = INVALID_HANDLE_VALUE; + const char *pszTmp; + KFSLOOKUPERROR enmIgnored; + DWORD dwType; +#ifdef WITH_CONSOLE_OUTPUT_BUFFERING + HANDLE hCurProc = GetCurrentProcess(); + PPEB pPeb = kwSandboxGetProcessEnvironmentBlock(); + PMY_RTL_USER_PROCESS_PARAMETERS pProcessParams = (PMY_RTL_USER_PROCESS_PARAMETERS)pPeb->ProcessParameters; +#endif +#if defined(KBUILD_OS_WINDOWS) && defined(KBUILD_ARCH_X86) + K_NOREF(pvVecXcptHandler); +#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()); + InitializeCriticalSection(&g_Sandbox.HandlesLock); + InitializeCriticalSection(&g_Sandbox.VirtualAllocLock); + +#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.tidOwner = KU32_MAX; + 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.tidOwner = KU32_MAX; + 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], "--full-test") == 0) + return kwFullTestRun(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; + int 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; + default: + case 3: dwClass = NORMAL_PRIORITY_CLASS; dwPriority = THREAD_PRIORITY_NORMAL; break; + case 4: dwClass = HIGH_PRIORITY_CLASS; dwPriority = INT_MAX; break; + case 5: dwClass = REALTIME_PRIORITY_CLASS; dwPriority = INT_MAX; break; + } + SetPriorityClass(GetCurrentProcess(), dwClass); + if (dwPriority != INT_MAX) + 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], "--group") == 0) + { + i++; + if (i < argc) + { + char *pszEnd = NULL; + unsigned long uValue = strtoul(argv[i], &pszEnd, 16); + if ( *argv[i] + && pszEnd != NULL + && *pszEnd == '\0' + && uValue == (WORD)uValue) + kwSetProcessorGroup(uValue); + 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], "--verbose") == 0 + || strcmp(argv[i], "-v") == 0) + g_cVerbose++; + else if ( strcmp(argv[i], "--help") == 0 + || strcmp(argv[i], "-h") == 0 + || strcmp(argv[i], "-?") == 0) + { + printf("usage: kWorker [--volatile dir] [--priority <1-5>] [--group <processor-grp>\n" + "usage: kWorker <--help|-h>\n" + "usage: kWorker <--version|-V>\n" + "usage: kWorker [--volatile dir] --full-test kSubmit ...\n" + "usage: kWorker [--volatile dir] --test [<times> [--chdir <dir>] [--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 no --pipe argument, then assume its standard input. + * We need to carefully replace the CRT stdin with a handle to "nul". + */ + if (hPipe == INVALID_HANDLE_VALUE) + { + hPipe = GetStdHandle(STD_INPUT_HANDLE); + if (GetFileType(hPipe) == FILE_TYPE_PIPE) + { + HANDLE hDuplicate = INVALID_HANDLE_VALUE; + if (DuplicateHandle(GetCurrentProcess(), hPipe, GetCurrentProcess(), &hDuplicate, 0, FALSE, DUPLICATE_SAME_ACCESS)) + { + int fdNul = _wopen(L"nul", O_RDWR | O_BINARY); + if (fdNul >= 0) + { + if (_dup2(fdNul, 0) >= 0) + { + close(fdNul); + hPipe = hDuplicate; + } + else + return kwErrPrintfRc(2, "DuplicateHandle pipe failed: %u\n", GetLastError()); + } + else + return kwErrPrintfRc(2, "DuplicateHandle pipe failed: %u\n", GetLastError()); + } + else + return kwErrPrintfRc(2, "DuplicateHandle pipe failed: %u\n", GetLastError()); + } + else + return kwErrPrintfRc(2, "No --pipe <pipe-handle> argument and standard input is not a valid pipe handle (%#x, %u)\n", + GetFileType(hPipe), GetLastError()); + } + else if (GetFileType(hPipe) != FILE_TYPE_PIPE) + return kwErrPrintfRc(2, "The specified --pipe %p is not a pipe handle: type %#x (last err %u)!\n", + GetFileType(hPipe), GetLastError()); + g_hPipe = hPipe; + + /* + * 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 + && g_rcCtrlC == 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); + if (g_rcCtrlC == 0) + 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) + { + DWORD cbIgnored = 1; + KU8 b; + FlushFileBuffers(hPipe); + ReadFile(hPipe, &b, 1, &cbIgnored, 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 g_rcCtrlC != 0 ? g_rcCtrlC : 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 + */ + diff --git a/src/kWorker/kWorkerTlsXxxK.c b/src/kWorker/kWorkerTlsXxxK.c new file mode 100644 index 0000000..829a66e --- /dev/null +++ b/src/kWorker/kWorkerTlsXxxK.c @@ -0,0 +1,138 @@ +/* $Id: kWorkerTlsXxxK.c 3366 2020-06-09 23:53:39Z bird $ */ +/** @file + * kWorkerTlsXxxK - Loader TLS allocation hack DLL. + */ + +/* + * Copyright (c) 2017 knut st. osmundsen <bird-kBuild-spamx@anduin.net> + * + * 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 <http://www.gnu.org/licenses/> + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <windows.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef void KWLDRTLSCALLBACK(void *hDll, DWORD dwReason, void *pvContext, void *pvWorkerModule); +typedef KWLDRTLSCALLBACK *PKWLDRTLSCALLBACK; +typedef PKWLDRTLSCALLBACK KWLDRTLSALLOCATIONHOOK(void *hDll, ULONG idxTls, char *pabInitData, void **ppvWorkerModule); + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +__declspec(dllexport) void __stdcall DummyTlsCallback(void *hDll, DWORD dwReason, void *pvContext); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The TLS pointer array. The 2nd entry is NULL and serve to terminate the array. + * The first entry can be used by kWorker if it needs to. */ +__declspec(dllexport) PIMAGE_TLS_CALLBACK g_apfnTlsCallbacks[2] = { DummyTlsCallback, NULL }; + +/** + * The TLS index. + */ +__declspec(dllexport) ULONG g_idxTls = ~(ULONG)0; + +/** + * Callback context. + */ +__declspec(dllexport) void *g_pvWorkerModule = NULL; + +/** + * Regular callback method (returned by kwLdrTlsAllocationHook). + */ +__declspec(dllexport) PKWLDRTLSCALLBACK g_pfnWorkerCallback = NULL; + + + +/** + * Initialization data. + * kWorker will copy the init data of the target DLL here. + */ +static char g_abInitData[TLS_SIZE] = {0}; + +/** + * The TLS directory entry. Not possible to get more than one from the linker + * and probably also the loader doesn't want more than one anyway. + */ +#pragma section(".rdata$T", long, read) +__declspec(allocate(".rdata$T")) const IMAGE_TLS_DIRECTORY _tls_used = +{ + (ULONG_PTR)&g_abInitData, + (ULONG_PTR)&g_abInitData + sizeof(g_abInitData), + (ULONG_PTR)&g_idxTls, + (ULONG_PTR)&g_apfnTlsCallbacks, + 0, /* This SizeOfZeroFill bugger doesn't work on w10/amd64 from what I can tell! */ + IMAGE_SCN_ALIGN_32BYTES +}; + + +/** + * Just a dummy callback function in case the allocation hook gambit fails below + * (see KWLDRTLSCALLBACK). + */ +static void DummyWorkerCallback(void *hDll, DWORD dwReason, void *pvContext, void *pvWorkerModule) +{ + (void)hDll; (void)dwReason; (void)pvContext; (void)pvWorkerModule; +} + + +/* + * This is just a dummy TLS callback function. + * We'll be replacing g_apfnTlsCallbacks[0] from kWorker.c after loading it. + * + * Note! W10 doesn't seem to want to process the TLS directory if the DLL + * doesn't have any imports (to snap). + */ +__declspec(dllexport) void __stdcall DummyTlsCallback(void *hDll, DWORD dwReason, void *pvContext) +{ + if (g_pfnWorkerCallback) + g_pfnWorkerCallback(hDll, dwReason, pvContext, g_pvWorkerModule); + else + { + g_pfnWorkerCallback = DummyWorkerCallback; + if (dwReason == DLL_PROCESS_ATTACH) + { + HMODULE hModExe = GetModuleHandleW(NULL); + KWLDRTLSALLOCATIONHOOK *pfnHook = (KWLDRTLSALLOCATIONHOOK *)GetProcAddress(hModExe, "kwLdrTlsAllocationHook"); + if (pfnHook) + g_pfnWorkerCallback = pfnHook(hDll, g_idxTls, g_abInitData, &g_pvWorkerModule); + else + __debugbreak(); + } + } +} + + +/* + * Dummy DLL entry point to avoid dragging in unnecessary CRT stuff. kWorkerTls1K!_tls_index + */ +BOOL __stdcall DummyDllEntry(void *hDll, DWORD dwReason, void *pvContext) +{ + (void)hDll; (void)dwReason; (void)pvContext; + return TRUE; +} + |