From cb9398a4196ebfcfb6c20aa714ee08a0e2994fb9 Mon Sep 17 00:00:00 2001 From: Kevin Zhao Date: Sat, 7 Mar 2026 12:19:31 +0000 Subject: [PATCH] codegen: escape string default values to prevent code injection String default values parsed from .fbs schemas are un-escaped by the IDL parser (e.g., \x22 becomes a raw " byte), but code generators embed these raw values directly into generated source code string literals. This allows specially crafted .fbs files to break out of string literals and inject arbitrary code into generated C++, Rust, TypeScript, and Swift source. Fix by adding EscapeCodeGenString() helper that re-escapes string content before embedding, and applying it to all 7 affected injection points across 5 code generators (C++, Rust, TypeScript, Swift, FBS). Resolves the TODO comments in idl_gen_cpp.cpp and idl_gen_rust.cpp. --- src/idl_gen_cpp.cpp | 18 ++++++++++++------ src/idl_gen_fbs.cpp | 12 +++++++++++- src/idl_gen_rust.cpp | 11 ++++++++--- src/idl_gen_swift.cpp | 25 ++++++++++++++++++------- src/idl_gen_ts.cpp | 6 +++++- 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/src/idl_gen_cpp.cpp b/src/idl_gen_cpp.cpp index 51bb820490..49db81e690 100644 --- a/src/idl_gen_cpp.cpp +++ b/src/idl_gen_cpp.cpp @@ -2779,15 +2779,17 @@ class CppGenerator : public BaseGenerator { get_call += ">(" + offset_str + ");"; code_ += get_call; } else if (IsString(type) && field.value.constant != "0") { - // TODO: Add logic to always convert the string to a valid C++ string - // literal by handling string escapes. + std::string escaped; + flatbuffers::EscapeString(field.value.constant.c_str(), + field.value.constant.length(), &escaped, + true, false); code_ += " auto* ptr = {{FIELD_VALUE}};"; code_ += " if (ptr) return ptr;"; code_ += " static const struct { uint32_t len; const char s[" + NumToString(field.value.constant.length() + 1) + "]; } bfbs_string = { " + - NumToString(field.value.constant.length()) + ", \"" + - field.value.constant + "\" };"; + NumToString(field.value.constant.length()) + ", " + + escaped + " };"; code_ += " return reinterpret_cast(&bfbs_string);"; @@ -3417,11 +3419,15 @@ class CppGenerator : public BaseGenerator { code_.SetValue("CREATE_STRING", "CreateSharedString"); } if (field->value.constant != "0") { + std::string escaped; + flatbuffers::EscapeString(field->value.constant.c_str(), + field->value.constant.length(), &escaped, + true, false); code_ += " auto {{FIELD_NAME}}__ = {{FIELD_NAME}} ? " "_fbb.{{CREATE_STRING}}({{FIELD_NAME}}) : " - "_fbb.{{CREATE_STRING}}(\"" + - field->value.constant + "\");"; + "_fbb.{{CREATE_STRING}}(" + + escaped + ");"; } else { code_ += " auto {{FIELD_NAME}}__ = {{FIELD_NAME}} ? " diff --git a/src/idl_gen_fbs.cpp b/src/idl_gen_fbs.cpp index da4289587b..0acd7e49a4 100644 --- a/src/idl_gen_fbs.cpp +++ b/src/idl_gen_fbs.cpp @@ -368,7 +368,17 @@ static std::string GenerateFBS(const Parser& parser, if (field.value.type.base_type != BASE_TYPE_UTYPE) { GenComment(field.doc_comment, &schema, nullptr, " "); schema += " " + field.name + ":" + GenType(field.value.type); - if (field.value.constant != "0") schema += " = " + field.value.constant; + if (field.value.constant != "0") { + if (IsString(field.value.type)) { + std::string escaped; + flatbuffers::EscapeString(field.value.constant.c_str(), + field.value.constant.length(), &escaped, + true, false); + schema += " = " + escaped; + } else { + schema += " = " + field.value.constant; + } + } std::vector attributes; if (field.IsRequired()) attributes.push_back("required"); if (field.key) attributes.push_back("key"); diff --git a/src/idl_gen_rust.cpp b/src/idl_gen_rust.cpp index 3430a3486c..d62bbffa4d 100644 --- a/src/idl_gen_rust.cpp +++ b/src/idl_gen_rust.cpp @@ -1138,9 +1138,14 @@ class RustGenerator : public BaseGenerator { // need one for Rust's Default trait so we use empty string. The usual // value of field.value.constant is `0`, which is non-sensical except // maybe to c++ (nullptr == 0). - // TODO: Escape strings? - const std::string defval = - field.IsRequired() ? "\"\"" : "\"" + field.value.constant + "\""; + std::string defval; + if (field.IsRequired()) { + defval = "\"\""; + } else { + flatbuffers::EscapeString(field.value.constant.c_str(), + field.value.constant.length(), &defval, + true, false); + } if (context == kObject) { return "alloc::string::ToString::to_string(" + defval + ")"; } diff --git a/src/idl_gen_swift.cpp b/src/idl_gen_swift.cpp index f926aa5d4f..f534ca254f 100644 --- a/src/idl_gen_swift.cpp +++ b/src/idl_gen_swift.cpp @@ -859,7 +859,10 @@ class SwiftGenerator : public BaseGenerator { break; case BASE_TYPE_STRING: { - const auto default_string = "\"" + SwiftConstant(field) + "\""; + const auto sc = SwiftConstant(field); + std::string default_string; + flatbuffers::EscapeString(sc.c_str(), sc.length(), &default_string, + true, false); code_.SetValue("VALUETYPE", GenType(field.value.type)); code_.SetValue("CONSTANT", field.IsDefault() ? default_string : "nil"); code_ += GenReaderMainBody(is_required) + GenOffset() + @@ -1649,15 +1652,23 @@ class SwiftGenerator : public BaseGenerator { buffer_constructor.push_back(field_var + " = _t." + field_field); if (field.IsRequired()) { - std::string default_value = - field.IsDefault() ? SwiftConstant(field) : ""; - base_constructor.push_back(field_var + " = \"" + default_value + - "\""); + std::string default_value; + if (field.IsDefault()) { + const auto sc = SwiftConstant(field); + flatbuffers::EscapeString(sc.c_str(), sc.length(), &default_value, + true, false); + } else { + default_value = "\"\""; + } + base_constructor.push_back(field_var + " = " + default_value); break; } if (field.IsDefault() && !field.IsRequired()) { - std::string value = field.IsDefault() ? SwiftConstant(field) : "nil"; - base_constructor.push_back(field_var + " = \"" + value + "\""); + const auto sc = SwiftConstant(field); + std::string value; + flatbuffers::EscapeString(sc.c_str(), sc.length(), &value, + true, false); + base_constructor.push_back(field_var + " = " + value); } break; } diff --git a/src/idl_gen_ts.cpp b/src/idl_gen_ts.cpp index 9bd00d0e38..8d02b8eb45 100644 --- a/src/idl_gen_ts.cpp +++ b/src/idl_gen_ts.cpp @@ -529,7 +529,11 @@ class TsGenerator : public BaseGenerator { if (value.constant == "0" || value.constant == "null") { return "null"; } else { - return "\"" + value.constant + "\""; + std::string escaped; + flatbuffers::EscapeString(value.constant.c_str(), + value.constant.length(), &escaped, + true, false); + return escaped; } } case BASE_TYPE_UNION: