Fix: Check flatbuffer integrity before parsing#1864
Conversation
There was a problem hiding this comment.
Code Review
This pull request updates the Flatbuffers dependency to a newer version, removes an obsolete patch file, and improves the robustness of the Remote Config desktop implementation. Specifically, it adds buffer verification before deserializing flexbuffers, enhances file path handling and error checking in the file manager, and replaces std::stoi with safer string-to-integer parsing in the metadata deserialization. The review feedback highlights critical improvements: ensuring robust overflow detection for std::strtol on LLP64 platforms (like Windows) by checking errno, preventing potential undefined behavior from a null package_name(), and explicitly including the necessary and headers.
Integration test with FLAKINESS (but still ⏳ in progress)Requested by @AustinBenoit on commit acf24f3
Add flaky tests to go/fpl-cpp-flake-tracker |
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request updates Flatbuffers to support buffer verification, adds integrity checks to prevent crashes on invalid data, and improves path resolution and error handling in Remote Config. The reviewer feedback highlights a critical bug where a boolean return value is incorrectly compared to nullptr, an issue with an unintended leading slash when the package name is empty, and suggests using default cases instead of case flexbuffers::FBT_MAX_TYPE to robustly handle corrupted or unknown flexbuffer types.
| bool generate_status = | ||
| GenerateText(*parser_, builder.GetBufferPointer(), &json); | ||
| GenerateText(*parser_, builder.GetBufferPointer(), &json) == nullptr; |
There was a problem hiding this comment.
Comparing the result of GenerateText to nullptr is incorrect. GenerateText returns a bool (indicating success or failure), not a pointer.
Comparing a bool to nullptr will either fail to compile in modern C++ or result in inverted logic (where generate_status becomes false on success and true on failure), causing the subsequent FIREBASE_ASSERT_RETURN_VOID(generate_status) to trigger on successful execution.
We should revert this to simply assign the bool result of GenerateText directly.
| bool generate_status = | |
| GenerateText(*parser_, builder.GetBufferPointer(), &json); | |
| GenerateText(*parser_, builder.GetBufferPointer(), &json) == nullptr; | |
| bool generate_status = | |
| GenerateText(*parser_, builder.GetBufferPointer(), &json); |
| const char* package_name = app.options().package_name(); | ||
| std::string app_data_prefix = | ||
| std::string(app.options().package_name()) + "/" + app.name(); | ||
| std::string file_path = | ||
| AppDataDir(app_data_prefix.c_str(), /*should_create=*/true) + "/" + | ||
| filename; | ||
| std::string(package_name ? package_name : "") + "/remote_config"; |
There was a problem hiding this comment.
When package_name is null or empty, app_data_prefix is constructed as "/remote_config", which introduces an unintended leading slash. When package_name is present, it is constructed as "package_name/remote_config" (no leading slash).
This inconsistency can cause path resolution issues in AppDataDir (e.g., treating it as an absolute path or causing double slashes). We should ensure that no leading slash is present when package_name is empty or null.
| const char* package_name = app.options().package_name(); | |
| std::string app_data_prefix = | |
| std::string(app.options().package_name()) + "/" + app.name(); | |
| std::string file_path = | |
| AppDataDir(app_data_prefix.c_str(), /*should_create=*/true) + "/" + | |
| filename; | |
| std::string(package_name ? package_name : "") + "/remote_config"; | |
| const char* package_name = app.options().package_name(); | |
| std::string app_data_prefix = | |
| (package_name && package_name[0] != '\0') | |
| ? std::string(package_name) + "/remote_config" | |
| : "remote_config"; |
| case flexbuffers::FBT_MAX_TYPE: | ||
| LogError("Unknown or unsupported flexbuffer type: %d", | ||
| static_cast<int>(ref.GetType())); | ||
| break; |
There was a problem hiding this comment.
Using case flexbuffers::FBT_MAX_TYPE: only handles the specific sentinel value FBT_MAX_TYPE. If the flexbuffer is corrupted or contains an invalid/unknown type ID not covered by the switch, it will bypass this check entirely and silently return Variant::Null().
Using a default: case instead is much more robust as it will catch FBT_MAX_TYPE as well as any other unknown or corrupted type values.
| case flexbuffers::FBT_MAX_TYPE: | |
| LogError("Unknown or unsupported flexbuffer type: %d", | |
| static_cast<int>(ref.GetType())); | |
| break; | |
| default: | |
| LogError("Unknown or unsupported flexbuffer type: %d", | |
| static_cast<int>(ref.GetType())); | |
| break; |
| case flexbuffers::FBT_MAX_TYPE: | ||
| LogError("Unknown or unsupported flexbuffer type: %d", | ||
| static_cast<int>(ref.GetType())); | ||
| break; |
There was a problem hiding this comment.
Using case flexbuffers::FBT_MAX_TYPE: only handles the specific sentinel value FBT_MAX_TYPE. If the flexbuffer is corrupted or contains an invalid/unknown type ID not covered by the switch, it will bypass this check entirely and silently return Variant::Null().
Using a default: case instead is much more robust as it will catch FBT_MAX_TYPE as well as any other unknown or corrupted type values.
| case flexbuffers::FBT_MAX_TYPE: | |
| LogError("Unknown or unsupported flexbuffer type: %d", | |
| static_cast<int>(ref.GetType())); | |
| break; | |
| default: | |
| LogError("Unknown or unsupported flexbuffer type: %d", | |
| static_cast<int>(ref.GetType())); | |
| break; |
| case flexbuffers::FBT_MAX_TYPE: | ||
| LogError("Unknown or unsupported flexbuffer type: %d", | ||
| static_cast<int>(ref.GetType())); | ||
| break; |
There was a problem hiding this comment.
Using case flexbuffers::FBT_MAX_TYPE: only handles the specific sentinel value FBT_MAX_TYPE. If the flexbuffer is corrupted or contains an invalid/unknown type ID not covered by the switch, it will bypass this check entirely and silently return Variant::Null().
Using a default: case instead is much more robust as it will catch FBT_MAX_TYPE as well as any other unknown or corrupted type values.
| case flexbuffers::FBT_MAX_TYPE: | |
| LogError("Unknown or unsupported flexbuffer type: %d", | |
| static_cast<int>(ref.GetType())); | |
| break; | |
| default: | |
| LogError("Unknown or unsupported flexbuffer type: %d", | |
| static_cast<int>(ref.GetType())); | |
| break; |
Updated flatbuffer to latest version to get verify buffer Use strol for key parsing to ensure exceptions do not result in a crash.
Description
Testing
Integration test in github
Type of Change
Place an
xthe applicable box:Notes
Release Notessection ofrelease_build_files/readme.md.