diff --git a/src/idl_gen_cpp.cpp b/src/idl_gen_cpp.cpp index 9ad18a7b48..7a98321198 100644 --- a/src/idl_gen_cpp.cpp +++ b/src/idl_gen_cpp.cpp @@ -85,6 +85,31 @@ static bool IsVectorOfPointers(const FieldDef& field) { !vector_type.struct_def->fixed && !field.native_inline; } +// Validate that a string is safe to embed as a C++ type name or type +// expression (used for native_type, cpp_type, cpp_ptr_type_get attributes). +// Allowed characters: alphanumerics, '_', ':', '<', '>', '*', '&', ' ', ',', +// '.', '(', ')'. Characters like '"', '{', '}', ';', '#', '\n', '\r' can +// break out of a type context and inject arbitrary code. +static bool IsValidCppTypeExpression(const std::string& s) { + for (char c : s) { + if (!is_alnum(c) && c != '_' && c != ':' && c != '<' && c != '>' && + c != '*' && c != '&' && c != ' ' && c != ',' && c != '.' && + c != '(' && c != ')') { + return false; + } + } + return true; +} + +// Validate that a string is safe to embed inside an #include "..." directive. +// Newlines and double-quotes would allow injection beyond the string literal. +static bool IsValidIncludePath(const std::string& s) { + for (char c : s) { + if (c == '"' || c == '\n' || c == '\r') { return false; } + } + return true; +} + static bool IsPointer(const FieldDef& field) { return field.value.type.base_type == BASE_TYPE_STRUCT && !IsStruct(field.value.type); @@ -263,6 +288,13 @@ class CppGenerator : public BaseGenerator { if (opts_.generate_object_based_api) { for (const std::string& native_included_file : parser_.native_included_files_) { + if (!IsValidIncludePath(native_included_file)) { + LogCompilerError( + "native_include path contains characters that are unsafe to " + "embed in a #include directive: \"" + + native_included_file + "\""); + continue; + } code_ += "#include \"" + native_included_file + "\""; } } @@ -437,9 +469,57 @@ class CppGenerator : public BaseGenerator { false); } + // Validate attribute values that are embedded verbatim into generated C++ + // source as type names or include paths. Malicious .fbs files could inject + // arbitrary code if these values are not checked before code emission. + // Returns false (after logging an error) if any unsafe value is found. + bool ValidateAttributeSafety() { + // Validate native_type and cpp_type attributes on struct/table definitions. + for (const auto& struct_def : parser_.structs_.vec) { + const auto native_type = struct_def->attributes.Lookup("native_type"); + if (native_type && !IsValidCppTypeExpression(native_type->constant)) { + LogCompilerError( + "native_type attribute on '" + Name(*struct_def) + + "' contains characters that are unsafe to embed as a C++ type " + "name: \"" + + native_type->constant + "\""); + return false; + } + for (const auto& field_it : struct_def->fields.vec) { + const auto& field = *field_it; + const auto cpp_type = field.attributes.Lookup("cpp_type"); + if (cpp_type && !IsValidCppTypeExpression(cpp_type->constant)) { + LogCompilerError( + "cpp_type attribute on field '" + Name(*struct_def) + "." + + Name(field) + + "' contains characters that are unsafe to embed as a C++ type " + "name: \"" + + cpp_type->constant + "\""); + return false; + } + const auto cpp_ptr_type_get = + field.attributes.Lookup("cpp_ptr_type_get"); + if (cpp_ptr_type_get && + !IsValidCppTypeExpression(cpp_ptr_type_get->constant)) { + LogCompilerError( + "cpp_ptr_type_get attribute on field '" + Name(*struct_def) + + "." + Name(field) + + "' contains characters that are unsafe to embed as a C++ " + "expression: \"" + + cpp_ptr_type_get->constant + "\""); + return false; + } + } + } + return true; + } + // Iterate through all definitions we haven't generate code for (enums, // structs, and tables) and output them to a single file. bool generate() { + // Validate attribute values before emitting any code. + if (!ValidateAttributeSafety()) { return false; } + // Check if we require a 64-bit flatbuffer builder. MarkIf64BitBuilderIsNeeded(); diff --git a/tests/attribute_injection_test.fbs b/tests/attribute_injection_test.fbs new file mode 100644 index 0000000000..fed5183bd7 --- /dev/null +++ b/tests/attribute_injection_test.fbs @@ -0,0 +1,57 @@ +// Test schema for verifying that code-injection via FlatBuffers attributes +// is rejected by the C++ code generator. +// +// This schema is intentionally malformed: the attribute values contain +// characters that would allow a malicious .fbs author to break out of the +// generated C++ source context and inject arbitrary code. +// +// The flatc C++ backend MUST reject schemas containing: +// - native_type values with characters outside [A-Za-z0-9_::<>*& ,.()\] +// - cpp_type values with the same restriction +// - cpp_ptr_type_get values with the same restriction +// - native_include paths containing '"', '\n', or '\r' +// +// This file documents the EXPECTED REJECTION cases; it is not expected to +// compile successfully. Each table below exercises one injection vector. +// When compiled with: +// flatc --cpp --gen-object-api +// flatc must exit non-zero and print an error referencing the offending +// attribute rather than emitting injectable output. +// +// Injection vector 1 — native_type: +// Semicolons and braces would break out of a typedef context. +// table NativeTypeInjection (native_type: "Foo}; void evil(){") { x: int; } +// => error: native_type attribute ... contains characters that are unsafe +// +// Injection vector 2 — cpp_type (requires a hashed field): +// A quote character would break out of a string literal context. +// table Stat { id: string; } +// table CppTypeInjection { +// field: uint (hash:"fnv1a_32", cpp_type:"Stat\"; system(0); //"); +// } +// => error: cpp_type attribute ... contains characters that are unsafe +// +// Injection vector 3 — cpp_ptr_type_get: +// A semicolon would terminate the expression and inject a new statement. +// table CppPtrTypeGetInjection { +// field: string (cpp_ptr_type_get: ".get(); system(0); //"); +// } +// => error: cpp_ptr_type_get attribute ... contains characters that are unsafe +// +// Injection vector 4 — native_include: +// A double-quote would close the #include path and allow arbitrary content. +// native_include "valid.h\"injected.h"; +// => error: native_include path contains characters that are unsafe + +// Safe usage that must still compile successfully: +// native_include "native_type_test_impl.h"; +// table Safe (native_type: "my::ValidType") { x: int; } + +namespace AttributeInjectionTest; + +// This table uses only safe, well-formed attributes and must compile. +table SafeNativeType (native_type: "my::ValidNativeType") { + x: int; +} + +root_type SafeNativeType;