From f4fa5f5ba7bcb717209cc464d2566602b389603b Mon Sep 17 00:00:00 2001 From: ASDAlexander77 Date: Tue, 6 Oct 2026 01:30:05 +0100 Subject: [PATCH] String copies are bounded: memcpy_s on Windows, __memcpy_chk on Linux and Android lib.ts copied string bytes with memcpy, trusting each count. Every copy now goes through boundedCopy(dest, destSize, src, count), which ends the process rather than write past the destination: - Windows: the CRT's memcpy_s (its invalid parameter handler fails fast, 0xC0000409) - Linux and Android: glibc's and Bionic's fortified __memcpy_chk ("buffer overflow detected"), through __builtin___memcpy_chk, which the compiler turns back into a plain memcpy where it can prove the copy fits boundedCopy is a C++ wrapper (src/wrappers/memory.cpp, chosen by #ifdef), not TypeScript in lib.win32.ts / lib.linux.ts. The compiler now emits memcpy_s / __memcpy_chk for string copies of its own. Under -mm=rc, lib.win32.ts made such a copy before a TypeScript declaration of memcpy_s further down was lowered, and the module failed with "redefinition of symbol named 'memcpy_s'". memory.cpp is added to build_core.bat, build.sh and build_android.bat. regex.cpp's two copies out of std::string use memcpy_s on Windows too. Co-Authored-By: Claude Opus 5.5 --- scripts/build.sh | 5 +++-- scripts/build_android.bat | 4 ++-- scripts/build_core.bat | 6 ++++-- src/lib.ts | 14 +++++++------- src/native/lib.native.d.ts | 3 +++ src/wrappers/memory.cpp | 22 ++++++++++++++++++++++ src/wrappers/regex.cpp | 8 ++++++++ 7 files changed, 49 insertions(+), 13 deletions(-) create mode 100644 src/wrappers/memory.cpp diff --git a/scripts/build.sh b/scripts/build.sh index d653f32..4e73892 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -70,12 +70,13 @@ $TOOL $DBG_GCC $CPP_FLAGS -c $SRC/src/wrappers/datetime.cpp -o $OUTPUT/lib/$BUIL $TOOL $DBG_GCC $CPP_FLAGS -c $SRC/src/wrappers/regex.cpp -o $OUTPUT/lib/$BUILD/$MM/regex.o $TOOL $DBG_GCC $CPP_FLAGS -c $SRC/src/wrappers/thread.cpp -o $OUTPUT/lib/$BUILD/$MM/thread.o $TOOL $DBG_GCC $CPP_FLAGS -c $SRC/src/wrappers/http_linux.cpp -o $OUTPUT/lib/$BUILD/$MM/http_linux.o +$TOOL $DBG_GCC $CPP_FLAGS -c $SRC/src/wrappers/memory.cpp -o $OUTPUT/lib/$BUILD/$MM/memory.o $BIN_PATH/$TOOL_NAME $DBG_OPTS $MM_OPT --emit=obj --export=none --nowarn --no-default-lib $SRC/src/lib.linux.ts $PIC -o $OUTPUT/lib/$BUILD/$MM/lib.linux.o # Build Lib $BIN_PATH/$TOOL_NAME $DBG_OPTS $MM_OPT --emit=obj --export=none --nowarn --no-default-lib $SRC/src/lib.ts $PIC -o $OUTPUT/lib/$BUILD/$MM/lib.o -$ARC rcs $OUTPUT/lib/$BUILD/$MM/libTypeScriptDefaultLibCore.a $OUTPUT/lib/$BUILD/$MM/lib.o $OUTPUT/lib/$BUILD/$MM/lib.linux.o $OUTPUT/lib/$BUILD/$MM/io.o $OUTPUT/lib/$BUILD/$MM/datetime.o $OUTPUT/lib/$BUILD/$MM/regex.o $OUTPUT/lib/$BUILD/$MM/thread.o $OUTPUT/lib/$BUILD/$MM/http_linux.o +$ARC rcs $OUTPUT/lib/$BUILD/$MM/libTypeScriptDefaultLibCore.a $OUTPUT/lib/$BUILD/$MM/lib.o $OUTPUT/lib/$BUILD/$MM/lib.linux.o $OUTPUT/lib/$BUILD/$MM/io.o $OUTPUT/lib/$BUILD/$MM/datetime.o $OUTPUT/lib/$BUILD/$MM/regex.o $OUTPUT/lib/$BUILD/$MM/thread.o $OUTPUT/lib/$BUILD/$MM/http_linux.o $OUTPUT/lib/$BUILD/$MM/memory.o # A static archive cannot record its own dependencies on ELF (there is no ld.bfd equivalent of # the `#pragma comment(lib, ...)` http.cpp uses on Windows), so programs linking @@ -93,7 +94,7 @@ EOF # undefined on purpose: they must bind to the one collector already in the process # (libTypeScriptRuntime.so under the JIT, libgc.a in an exe); linking libgc.a in here would # give the library a second, separate heap. -g++ -shared $DBG_GCC $OUTPUT/lib/$BUILD/$MM/lib.o $OUTPUT/lib/$BUILD/$MM/lib.linux.o $OUTPUT/lib/$BUILD/$MM/io.o $OUTPUT/lib/$BUILD/$MM/datetime.o $OUTPUT/lib/$BUILD/$MM/regex.o $OUTPUT/lib/$BUILD/$MM/thread.o $OUTPUT/lib/$BUILD/$MM/http_linux.o -lcurl -lm -o $OUTPUT/dll/$BUILD/$MM/libTypeScriptDefaultLib.so +g++ -shared $DBG_GCC $OUTPUT/lib/$BUILD/$MM/lib.o $OUTPUT/lib/$BUILD/$MM/lib.linux.o $OUTPUT/lib/$BUILD/$MM/io.o $OUTPUT/lib/$BUILD/$MM/datetime.o $OUTPUT/lib/$BUILD/$MM/regex.o $OUTPUT/lib/$BUILD/$MM/thread.o $OUTPUT/lib/$BUILD/$MM/http_linux.o $OUTPUT/lib/$BUILD/$MM/memory.o -lcurl -lm -o $OUTPUT/dll/$BUILD/$MM/libTypeScriptDefaultLib.so # Copy # Stage into a single shared defaultlib tree with per-build subfolders under dll/ and lib/, diff --git a/scripts/build_android.bat b/scripts/build_android.bat index 04b14c8..8daed45 100644 --- a/scripts/build_android.bat +++ b/scripts/build_android.bat @@ -102,14 +102,14 @@ if exist "%OBJ%" rd /s /q "%OBJ%" if exist "%LIB_OUT%" rd /s /q "%LIB_OUT%" md "%OBJ%" "%LIB_OUT%" -for %%w in (io datetime regex thread http_stub) do ( +for %%w in (io datetime regex thread http_stub memory) do ( %CXX% -c "%ROOT%\src\wrappers\%%w.cpp" -o "%OBJ%\%%w.o" || exit /b 1 ) %TSC% "%ROOT%\src\lib.linux.ts" -o "%OBJ%\lib.linux.o" || exit /b 1 %TSC% "%ROOT%\src\lib.ts" -o "%OBJ%\lib.o" || exit /b 1 -set OBJS="%OBJ%\lib.o" "%OBJ%\lib.linux.o" "%OBJ%\io.o" "%OBJ%\datetime.o" "%OBJ%\regex.o" "%OBJ%\thread.o" "%OBJ%\http_stub.o" +set OBJS="%OBJ%\lib.o" "%OBJ%\lib.linux.o" "%OBJ%\io.o" "%OBJ%\datetime.o" "%OBJ%\regex.o" "%OBJ%\thread.o" "%OBJ%\http_stub.o" "%OBJ%\memory.o" %AR% rcs "%LIB_OUT%\libTypeScriptDefaultLib.a" %OBJS% || exit /b 1 rd /s /q "%OBJ%" diff --git a/scripts/build_core.bat b/scripts/build_core.bat index d546d4d..9a8069b 100644 --- a/scripts/build_core.bat +++ b/scripts/build_core.bat @@ -205,6 +205,7 @@ rem Build native wrappers for C++ code %TSLANG_CC% %DBG_CL% /EHsc /Wall /c /Fo%OUTPUT%\%LIB_OUT%\ %SRC%\src\wrappers\regex.cpp %TSLANG_CC% %DBG_CL% /EHsc /Wall /c /Fo%OUTPUT%\%LIB_OUT%\ %SRC%\src\wrappers\thread.cpp %TSLANG_CC% %DBG_CL% /EHsc /Wall /c /Fo%OUTPUT%\%LIB_OUT%\ %SRC%\src\wrappers\http.cpp +%TSLANG_CC% %DBG_CL% /EHsc /Wall /c /Fo%OUTPUT%\%LIB_OUT%\ %SRC%\src\wrappers\memory.cpp rem Build OS-specific Lib echo Build OS-specific Lib @@ -212,7 +213,7 @@ echo Build OS-specific Lib rem Build DLL echo Build DLL -%TOOL_PATH%\%TOOL_NAME%.exe %TRIPLE_OPT% %DBG% %MM_OPT% --emit=dll --gc-lib-path=%GC_SHARED_LIB_PATH% %TSLANG_RUNTIME_OPT% --embed-declarations=false --nowarn --no-default-lib %SRC%\src\lib.ts --obj=%OUTPUT%\%LIB_OUT%\lib.win32.obj --obj=%OUTPUT%\%LIB_OUT%\io.obj --obj=%OUTPUT%\%LIB_OUT%\datetime.obj --obj=%OUTPUT%\%LIB_OUT%\regex.obj --obj=%OUTPUT%\%LIB_OUT%\thread.obj --obj=%OUTPUT%\%LIB_OUT%\http.obj -o %OUTPUT%\%DLL_OUT%\TypeScriptDefaultLib.dll +%TOOL_PATH%\%TOOL_NAME%.exe %TRIPLE_OPT% %DBG% %MM_OPT% --emit=dll --gc-lib-path=%GC_SHARED_LIB_PATH% %TSLANG_RUNTIME_OPT% --embed-declarations=false --nowarn --no-default-lib %SRC%\src\lib.ts --obj=%OUTPUT%\%LIB_OUT%\lib.win32.obj --obj=%OUTPUT%\%LIB_OUT%\io.obj --obj=%OUTPUT%\%LIB_OUT%\datetime.obj --obj=%OUTPUT%\%LIB_OUT%\regex.obj --obj=%OUTPUT%\%LIB_OUT%\thread.obj --obj=%OUTPUT%\%LIB_OUT%\http.obj --obj=%OUTPUT%\%LIB_OUT%\memory.obj -o %OUTPUT%\%DLL_OUT%\TypeScriptDefaultLib.dll rem Build Lib echo Build Lib @@ -220,7 +221,7 @@ echo Build Lib rem %TOOL_PATH%\%TOOL_NAME%.exe %TRIPLE_OPT% %DBG% %MM_OPT% --emit=llvm --export=none %SRC%\src\lib.ts -o %OUTPUT%\%LIB_OUT%\lib.ll rem %TOOL_PATH%\%TOOL_NAME%.exe %TRIPLE_OPT% %DBG% %MM_OPT% --emit=mlir --export=none %SRC%\src\lib.ts 2> %OUTPUT%\%LIB_OUT%\lib.mlir -%TSLANG_AR% /out:%OUTPUT%\%LIB_OUT%\TypeScriptDefaultLib.lib %OUTPUT%\%LIB_OUT%\lib.obj %OUTPUT%\%LIB_OUT%\lib.win32.obj %OUTPUT%\%LIB_OUT%\io.obj %OUTPUT%\%LIB_OUT%\datetime.obj %OUTPUT%\%LIB_OUT%\regex.obj %OUTPUT%\%LIB_OUT%\thread.obj %OUTPUT%\%LIB_OUT%\http.obj +%TSLANG_AR% /out:%OUTPUT%\%LIB_OUT%\TypeScriptDefaultLib.lib %OUTPUT%\%LIB_OUT%\lib.obj %OUTPUT%\%LIB_OUT%\lib.win32.obj %OUTPUT%\%LIB_OUT%\io.obj %OUTPUT%\%LIB_OUT%\datetime.obj %OUTPUT%\%LIB_OUT%\regex.obj %OUTPUT%\%LIB_OUT%\thread.obj %OUTPUT%\%LIB_OUT%\http.obj %OUTPUT%\%LIB_OUT%\memory.obj del %OUTPUT%\%LIB_OUT%\lib.obj del %OUTPUT%\%LIB_OUT%\lib.win32.obj @@ -229,6 +230,7 @@ del %OUTPUT%\%LIB_OUT%\datetime.obj del %OUTPUT%\%LIB_OUT%\regex.obj del %OUTPUT%\%LIB_OUT%\thread.obj del %OUTPUT%\%LIB_OUT%\http.obj +del %OUTPUT%\%LIB_OUT%\memory.obj rem Stage into a single shared defaultlib tree with per-build subfolders under rem dll\ and lib\. Only the current build's subfolders are refreshed so the diff --git a/src/lib.ts b/src/lib.ts index 5c62175..af7e9eb 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -576,7 +576,7 @@ export class RegExp // get formatted replacement const len = r.length; let result = "".clone().resize(len); - memcpy(Ref(result[0]), r, len); + boundedCopy(Ref(result[0]), len, r, len); regexp_free_string(r); regexp_free(cmatch); @@ -592,7 +592,7 @@ export class RegExp const r = regexp_replace(this.source, this.flags, s, replacement); const len = r.length; let result = "".clone().resize(len); - memcpy(Ref(result[0]), r, len); + boundedCopy(Ref(result[0]), len, r, len); regexp_free_string(r); return result; } @@ -655,7 +655,7 @@ namespace __String { const newString = this.clone().resize(count); let index = this.length; for (const item of other) { - memcpy(Ref(newString[index]), Ref(item[0]), sizeof() * item.length); + boundedCopy(Ref(newString[index]), sizeof() * (count - index), Ref(item[0]), sizeof() * item.length); index += item.length; } @@ -670,7 +670,7 @@ namespace __String { const newString = "".clone().resize(count); let index = 0; for (const item of parts) { - memcpy(Ref(newString[index]), Ref(item[0]), sizeof() * item.length); + boundedCopy(Ref(newString[index]), sizeof() * (count - index), Ref(item[0]), sizeof() * item.length); index += item.length; } @@ -881,7 +881,7 @@ namespace __String { // the clone holds the first copy already: count - 1 more fill the string, and one more // wrote past its end for (let i = 1; i < count; i++) { - memcpy(Ref(newString[index]), this, byteSize); + boundedCopy(Ref(newString[index]), sizeof() * (newSize - index), this, byteSize); index += this.length; } @@ -980,7 +980,7 @@ namespace __String { const count = indexEnd - indexStart; const newString = "".clone().resize(count); - memcpy(Ref(newString[0]), Ref(this[indexStart]), sizeof() * count); + boundedCopy(Ref(newString[0]), sizeof() * count, Ref(this[indexStart]), sizeof() * count); return newString; } @@ -1075,7 +1075,7 @@ namespace __String { const count = indexEnd - indexStart; const newString = "".clone().resize(count); - memcpy(Ref(newString[0]), Ref(this[indexStart]), sizeof() * count); + boundedCopy(Ref(newString[0]), sizeof() * count, Ref(this[indexStart]), sizeof() * count); return newString; } diff --git a/src/native/lib.native.d.ts b/src/native/lib.native.d.ts index 1f73db5..4ee225c 100644 --- a/src/native/lib.native.d.ts +++ b/src/native/lib.native.d.ts @@ -85,6 +85,9 @@ declare function trunc(x: number): number; // memory declare function memcpy(dest: Opaque, src: Opaque, bytes: index): Opaque; declare function memmove(dest: Opaque, src: Opaque, bytes: index): Opaque; +// wrappers/memory.cpp: memcpy_s on Windows, __memcpy_chk elsewhere - ends the process rather +// than copy more than `destSize` bytes +declare function boundedCopy(dest: Opaque, destSize: index, src: Opaque, count: index): void; declare function memcmp(dest: Opaque, src: Opaque, bytes: index): int; // gc weak references diff --git a/src/wrappers/memory.cpp b/src/wrappers/memory.cpp new file mode 100644 index 0000000..db0eb3e --- /dev/null +++ b/src/wrappers/memory.cpp @@ -0,0 +1,22 @@ +#include +#include + +#include "tslang_export.h" + +// A copy of `count` bytes into `dest`, which has `destSize` bytes left, that ends the process +// rather than write past them. Windows has the CRT's memcpy_s: its invalid parameter handler ends +// the process. glibc and Bionic have no memcpy_s, but have the fortified copy _FORTIFY_SOURCE +// uses, which aborts with "buffer overflow detected"; the builtin calls it, or a plain memcpy +// where the compiler can prove the copy fits. +// +// In C++, not in lib.win32.ts / lib.linux.ts: the compiler emits memcpy_s / __memcpy_chk for +// copies of its own, and a TypeScript declaration of the same function in a module that makes such +// a copy before the declaration is lowered is a second definition of the symbol. +extern "C" TSLANG_EXPORT void boundedCopy(void *dest, size_t destSize, const void *src, size_t count) +{ +#ifdef _WIN32 + memcpy_s(dest, destSize, src, count); +#else + __builtin___memcpy_chk(dest, src, count, destSize); +#endif +} diff --git a/src/wrappers/regex.cpp b/src/wrappers/regex.cpp index a7dd922..3328da0 100644 --- a/src/wrappers/regex.cpp +++ b/src/wrappers/regex.cpp @@ -53,7 +53,11 @@ extern "C" TSLANG_EXPORT char* regexp_match_results_format(std::cmatch *cm, cons std::allocator alloc; char* buffer = alloc.allocate(len + 1); +#ifdef _WIN32 + memcpy_s(buffer, len + 1, result.c_str(), len); +#else std::memcpy(buffer, result.c_str(), len); +#endif buffer[len] = 0; @@ -114,7 +118,11 @@ extern "C" TSLANG_EXPORT char* regexp_replace(const char *expr, const char *flag std::allocator alloc; char* buffer = alloc.allocate(len + 1); +#ifdef _WIN32 + memcpy_s(buffer, len + 1, result.c_str(), len); +#else std::memcpy(buffer, result.c_str(), len); +#endif buffer[len] = 0;