From ab86ae0284469a9a941f5b196f9cc99a90305e3a Mon Sep 17 00:00:00 2001 From: _ov4 Date: Thu, 15 Oct 2026 08:20:37 +0800 Subject: fix(codegen): prevent NUM_TO_STR buffer overflow Size REAL buffers for the full fixed-point double range and bound writes with snprintf. Register MinGW CRT aliases for embedded execution. Co-Authored-By: Claude Fable 5.1 --- AGENTS/PITFALLS.md | 4 ++-- include/cps/StringConversionHandler.h | 2 +- lib/CodeGen/FunctionGen.cc | 2 +- lib/CodeGen/StringConversionHandler.cc | 11 +++++++---- tools/driver/EmbeddedRunner.cc | 13 ++++++++++--- 5 files changed, 21 insertions(+), 11 deletions(-) diff --git a/AGENTS/PITFALLS.md b/AGENTS/PITFALLS.md index 47910ce..0fe61fe 100644 --- a/AGENTS/PITFALLS.md +++ b/AGENTS/PITFALLS.md @@ -30,8 +30,8 @@ test programs. must be a variable"; annotate `BYVAL` explicitly. - **`/` always yields REAL** (double FDiv), even for two INTEGERs; integer division is DIV/MOD only. CHR returns a STRING (2-byte heap buffer), not CHAR. STR_TO_NUM always - returns REAL. NUM_TO_STR formats INTEGER with sprintf `"%lld"` (the full signed - i64 range) and REAL with `"%f"`. + returns REAL. NUM_TO_STR formats INTEGER with bounded snprintf `"%lld"` (the full signed + i64 range) and REAL with `"%f"`. REAL buffers cover the full fixed-point double range. - **`getExprTypeInfo` returns nullptr** for expressions it can't classify (null-check it at any new call site) and internally defaults to INTEGER for arithmetic binaries and unknown-function calls; OUTPUT reports an error for a nullptr result. diff --git a/include/cps/StringConversionHandler.h b/include/cps/StringConversionHandler.h index 9637620..c4879aa 100644 --- a/include/cps/StringConversionHandler.h +++ b/include/cps/StringConversionHandler.h @@ -10,7 +10,7 @@ class StringConversionHandler { llvm::IRBuilder<> &Builder; llvm::Module &Module; - llvm::FunctionCallee SprintfFunc; + llvm::FunctionCallee SnprintfFunc; llvm::FunctionCallee StrtollFunc; llvm::FunctionCallee StrtodFunc; llvm::FunctionCallee MallocFunc; diff --git a/lib/CodeGen/FunctionGen.cc b/lib/CodeGen/FunctionGen.cc index 978970a..ca8fa45 100644 --- a/lib/CodeGen/FunctionGen.cc +++ b/lib/CodeGen/FunctionGen.cc @@ -11,7 +11,7 @@ using namespace cps; static bool isReservedRuntimeName(const std::string &Name) { static const char *Reserved[] = { "main", "printf", "scanf", "malloc", "free", "strlen", "memcpy", - "strcpy", "strcat", "toupper", "tolower", "sprintf", "strtol", "strtoll", + "strcpy", "strcat", "toupper", "tolower", "sprintf", "snprintf", "strtol", "strtoll", "strtod", "memset", "exit", "fopen", "fclose", "fgetc", "ungetc", "fputs", "fputc", "realloc", "strcmp", "__cxa_allocate_exception", "__cxa_throw", diff --git a/lib/CodeGen/StringConversionHandler.cc b/lib/CodeGen/StringConversionHandler.cc index 6f7461c..2263ef1 100644 --- a/lib/CodeGen/StringConversionHandler.cc +++ b/lib/CodeGen/StringConversionHandler.cc @@ -1,4 +1,5 @@ #include "cps/StringConversionHandler.h" +#include using namespace llvm; using namespace cps; @@ -9,8 +10,8 @@ StringConversionHandler::StringConversionHandler(LLVMContext &Ctx, IRBuilder<> & } void StringConversionHandler::setupExternalFunctions() { - FunctionType *SprintfType = FunctionType::get(Type::getInt32Ty(Context), {PointerType::getUnqual(Context), PointerType::getUnqual(Context)}, true); - SprintfFunc = Module.getOrInsertFunction("sprintf", SprintfType); + FunctionType *SnprintfType = FunctionType::get(Type::getInt32Ty(Context), {PointerType::getUnqual(Context), Type::getInt64Ty(Context), PointerType::getUnqual(Context)}, true); + SnprintfFunc = Module.getOrInsertFunction("snprintf", SnprintfType); FunctionType *StrtollType = FunctionType::get(Type::getInt64Ty(Context), {PointerType::getUnqual(Context), PointerType::getUnqual(Context), Type::getInt32Ty(Context)}, false); StrtollFunc = Module.getOrInsertFunction("strtoll", StrtollType); @@ -23,7 +24,9 @@ void StringConversionHandler::setupExternalFunctions() { } Value *StringConversionHandler::emitNumToStr(Value *Num, bool IsReal) { - Value *AllocSize = ConstantInt::get(Type::getInt64Ty(Context), 64); + // Allow an extra integer digit, sign, decimal point, six fractional digits and NUL. + Value *AllocSize = ConstantInt::get(Type::getInt64Ty(Context), + IsReal ? std::numeric_limits::max_exponent10 + 10 : 64); Value *Buffer = Builder.CreateCall(MallocFunc, AllocSize, "num_str_buf"); Value *FormatStr; @@ -33,7 +36,7 @@ Value *StringConversionHandler::emitNumToStr(Value *Num, bool IsReal) { FormatStr = Builder.CreateGlobalString("%lld"); } - Builder.CreateCall(SprintfFunc, {Buffer, FormatStr, Num}); + Builder.CreateCall(SnprintfFunc, {Buffer, AllocSize, FormatStr, Num}); return Buffer; } diff --git a/tools/driver/EmbeddedRunner.cc b/tools/driver/EmbeddedRunner.cc index a654d5a..cad1f84 100644 --- a/tools/driver/EmbeddedRunner.cc +++ b/tools/driver/EmbeddedRunner.cc @@ -8,11 +8,13 @@ #include "llvm/TargetParser/Host.h" #include "llvm/TargetParser/Triple.h" #include "llvm/Transforms/Utils/Cloning.h" +#include +#include #include #include #include -// Static C++ runtime symbols are not available through dlsym. Register their +// Static runtime symbols are not available through dlsym. Register their // actual addresses so the JIT and its unwind tables use the host's runtime. extern "C" int #if defined(__SEH__) @@ -24,8 +26,13 @@ __gxx_personality_v0 #endif (int, _Unwind_Action, _Unwind_Exception_Class, _Unwind_Exception *, _Unwind_Context *); -static void registerExceptionSymbols() { +static void registerRuntimeSymbols() { using llvm::sys::DynamicLibrary; +#ifdef __MINGW32__ + // Use the same CRT aliases as native IR linking, including its stdio formatting. + DynamicLibrary::AddSymbol("snprintf", reinterpret_cast(&__ms_snprintf)); + DynamicLibrary::AddSymbol("strtoll", reinterpret_cast(&std::strtoll)); +#endif DynamicLibrary::AddSymbol("__cxa_allocate_exception", reinterpret_cast(&__cxxabiv1::__cxa_allocate_exception)); DynamicLibrary::AddSymbol("__cxa_throw", reinterpret_cast(&__cxxabiv1::__cxa_throw)); DynamicLibrary::AddSymbol("__cxa_begin_catch", reinterpret_cast(&__cxxabiv1::__cxa_begin_catch)); @@ -52,7 +59,7 @@ int runEmbedded(const cps::CodeGen &CG) { llvm::errs() << "error: cannot load process symbols: " << Error << "\n"; return 1; } - registerExceptionSymbols(); + registerRuntimeSymbols(); // Keep CodeGen's module and context alive while the engine owns its clone. auto Module = llvm::CloneModule(CG.getModule()); Module->setTargetTriple(llvm::Triple(llvm::sys::getProcessTriple())); -- cgit v1.3.1-4-g156e