Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions src/idl_gen_cpp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 + "\"";
}
}
Expand Down Expand Up @@ -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();

Expand Down
57 changes: 57 additions & 0 deletions tests/attribute_injection_test.fbs
Original file line number Diff line number Diff line change
@@ -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 <file>
// 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<int*>") { 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;