codegen: escape string default values to prevent code injection (#8964)

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.
This commit is contained in:
Kevin Zhao
2026-03-19 10:01:23 +08:00
committed by GitHub
parent 2e07f269b9
commit 8afb68f074
5 changed files with 54 additions and 18 deletions

View File

@@ -2779,15 +2779,17 @@ class CppGenerator : public BaseGenerator {
get_call += ">(" + offset_str + ");"; get_call += ">(" + offset_str + ");";
code_ += get_call; code_ += get_call;
} else if (IsString(type) && field.value.constant != "0") { } else if (IsString(type) && field.value.constant != "0") {
// TODO: Add logic to always convert the string to a valid C++ string std::string escaped;
// literal by handling string escapes. flatbuffers::EscapeString(field.value.constant.c_str(),
field.value.constant.length(), &escaped,
true, false);
code_ += " auto* ptr = {{FIELD_VALUE}};"; code_ += " auto* ptr = {{FIELD_VALUE}};";
code_ += " if (ptr) return ptr;"; code_ += " if (ptr) return ptr;";
code_ += " static const struct { uint32_t len; const char s[" + code_ += " static const struct { uint32_t len; const char s[" +
NumToString(field.value.constant.length() + 1) + NumToString(field.value.constant.length() + 1) +
"]; } bfbs_string = { " + "]; } bfbs_string = { " +
NumToString(field.value.constant.length()) + ", \"" + NumToString(field.value.constant.length()) + ", " +
field.value.constant + "\" };"; escaped + " };";
code_ += code_ +=
" return reinterpret_cast<const ::flatbuffers::String " " return reinterpret_cast<const ::flatbuffers::String "
" *>(&bfbs_string);"; " *>(&bfbs_string);";
@@ -3417,11 +3419,15 @@ class CppGenerator : public BaseGenerator {
code_.SetValue("CREATE_STRING", "CreateSharedString"); code_.SetValue("CREATE_STRING", "CreateSharedString");
} }
if (field->value.constant != "0") { if (field->value.constant != "0") {
std::string escaped;
flatbuffers::EscapeString(field->value.constant.c_str(),
field->value.constant.length(), &escaped,
true, false);
code_ += code_ +=
" auto {{FIELD_NAME}}__ = {{FIELD_NAME}} ? " " auto {{FIELD_NAME}}__ = {{FIELD_NAME}} ? "
"_fbb.{{CREATE_STRING}}({{FIELD_NAME}}) : " "_fbb.{{CREATE_STRING}}({{FIELD_NAME}}) : "
"_fbb.{{CREATE_STRING}}(\"" + "_fbb.{{CREATE_STRING}}(" +
field->value.constant + "\");"; escaped + ");";
} else { } else {
code_ += code_ +=
" auto {{FIELD_NAME}}__ = {{FIELD_NAME}} ? " " auto {{FIELD_NAME}}__ = {{FIELD_NAME}} ? "

View File

@@ -368,7 +368,17 @@ static std::string GenerateFBS(const Parser& parser,
if (field.value.type.base_type != BASE_TYPE_UTYPE) { if (field.value.type.base_type != BASE_TYPE_UTYPE) {
GenComment(field.doc_comment, &schema, nullptr, " "); GenComment(field.doc_comment, &schema, nullptr, " ");
schema += " " + field.name + ":" + GenType(field.value.type); 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<std::string> attributes; std::vector<std::string> attributes;
if (field.IsRequired()) attributes.push_back("required"); if (field.IsRequired()) attributes.push_back("required");
if (field.key) attributes.push_back("key"); if (field.key) attributes.push_back("key");

View File

@@ -1138,9 +1138,14 @@ class RustGenerator : public BaseGenerator {
// need one for Rust's Default trait so we use empty string. The usual // 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 // value of field.value.constant is `0`, which is non-sensical except
// maybe to c++ (nullptr == 0). // maybe to c++ (nullptr == 0).
// TODO: Escape strings? std::string defval;
const std::string defval = if (field.IsRequired()) {
field.IsRequired() ? "\"\"" : "\"" + field.value.constant + "\""; defval = "\"\"";
} else {
flatbuffers::EscapeString(field.value.constant.c_str(),
field.value.constant.length(), &defval,
true, false);
}
if (context == kObject) { if (context == kObject) {
return "alloc::string::ToString::to_string(" + defval + ")"; return "alloc::string::ToString::to_string(" + defval + ")";
} }

View File

@@ -859,7 +859,10 @@ class SwiftGenerator : public BaseGenerator {
break; break;
case BASE_TYPE_STRING: { 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("VALUETYPE", GenType(field.value.type));
code_.SetValue("CONSTANT", field.IsDefault() ? default_string : "nil"); code_.SetValue("CONSTANT", field.IsDefault() ? default_string : "nil");
code_ += GenReaderMainBody(is_required) + GenOffset() + code_ += GenReaderMainBody(is_required) + GenOffset() +
@@ -1649,15 +1652,23 @@ class SwiftGenerator : public BaseGenerator {
buffer_constructor.push_back(field_var + " = _t." + field_field); buffer_constructor.push_back(field_var + " = _t." + field_field);
if (field.IsRequired()) { if (field.IsRequired()) {
std::string default_value = std::string default_value;
field.IsDefault() ? SwiftConstant(field) : ""; if (field.IsDefault()) {
base_constructor.push_back(field_var + " = \"" + default_value + 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; break;
} }
if (field.IsDefault() && !field.IsRequired()) { if (field.IsDefault() && !field.IsRequired()) {
std::string value = field.IsDefault() ? SwiftConstant(field) : "nil"; const auto sc = SwiftConstant(field);
base_constructor.push_back(field_var + " = \"" + value + "\""); std::string value;
flatbuffers::EscapeString(sc.c_str(), sc.length(), &value,
true, false);
base_constructor.push_back(field_var + " = " + value);
} }
break; break;
} }

View File

@@ -529,7 +529,11 @@ class TsGenerator : public BaseGenerator {
if (value.constant == "0" || value.constant == "null") { if (value.constant == "0" || value.constant == "null") {
return "null"; return "null";
} else { } 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: case BASE_TYPE_UNION: