Open
Description
Problem Description
After updating protobuf to v29, my clang-compiled app started crashing on parsing - TcParser::FastMtS1
call:
Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
(gdb) bt
#0 0x0000000000000000 in ?? ()
#1 0x00007ffff7e43c17 in google::protobuf::internal::TcParser::FastMtS1(google::protobuf::MessageLite*, char const*, google::protobuf::internal::ParseContext*, google::protobuf::internal::TcFieldData, google::protobuf::internal::TcParseTableBase const*, unsigned long) () from /nix/store/s0521fkq7z9lycdgal6y7g726vz5d6zk-protobuf-29.4/lib/libprotobuf.so.29.4.0
#2 0x00007ffff7ece3aa in bool google::protobuf::internal::MergeFromImpl<false>(google::protobuf::io::ZeroCopyInputStream*, google::protobuf::MessageLite*, google::protobuf::internal::TcParseTableBase const*, google::protobuf::MessageLite::ParseFlags) ()
from /nix/store/s0521fkq7z9lycdgal6y7g726vz5d6zk-protobuf-29.4/lib/libprotobuf.so.29.4.0
#3 0x00007ffff7ecd877 in google::protobuf::MessageLite::ParseFromIstream(std::basic_istream<char, std::char_traits<char> >*) () from /nix/store/s0521fkq7z9lycdgal6y7g726vz5d6zk-protobuf-29.4/lib/libprotobuf.so.29.4.0
#4 0x00005555555578c9 in main ()
Reproducing with Nix
- Create
message.proto
:
syntax = "proto3";
message Settings {
bool email_notifications = 1;
string theme = 2;
}
message User {
string username = 1;
string email = 2;
Settings preferences = 3;
}
- Create
main.cc
:
#include <iostream>
#include <fstream>
#include "message.pb.h"
int main() {
GOOGLE_PROTOBUF_VERIFY_VERSION;
std::cout << "🔧 Creating a user message..." << std::endl;
// Create the settings
Settings prefs;
prefs.set_email_notifications(true);
prefs.set_theme("dark");
// Create the user
User user;
user.set_username("johndoe");
user.set_email("[email protected]");
*user.mutable_preferences() = prefs;
std::cout << "📦 Serializing to 'input.bin'..." << std::endl;
std::ofstream output("input.bin", std::ios::binary);
if (!output) {
std::cerr << "❌ Failed to open 'input.bin' for writing." << std::endl;
return 1;
}
if (!user.SerializeToOstream(&output)) {
std::cerr << "❌ Failed to write protobuf message to file." << std::endl;
return 1;
}
output.close();
std::cout << "✅ Serialization complete.\n" << std::endl;
std::cout << "📂 Reading from 'input.bin'..." << std::endl;
std::ifstream input("input.bin", std::ios::binary);
if (!input) {
std::cerr << "❌ Failed to open 'input.bin' for reading." << std::endl;
return 1;
}
User read_user;
if (!read_user.ParseFromIstream(&input)) {
std::cerr << "❌ Failed to parse protobuf message from file." << std::endl;
return 1;
}
std::cout << "✅ Successfully read and parsed the message:\n";
std::cout << read_user.DebugString() << std::endl;
google::protobuf::ShutdownProtobufLibrary();
return 0;
}
- Create
flake.nix
:
{
description = "Example of protobuf parsing crash.";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils, ... }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
protobuf = pkgs.protobuf_29; # Clang-compiled binary worked with version 28!
stdenv = pkgs.stdenv;
abseil = pkgs.abseil-cpp;
in
{
packages.default = stdenv.mkDerivation {
name = "proto-cpp-app";
src = ./.;
buildInputs = [ protobuf pkgs.clang pkgs.gcc pkgs.abseil-cpp ];
buildPhase = ''
# Generate C++ files from proto definitions.
mkdir -p generated
${protobuf}/bin/protoc --cpp_out=./generated message.proto
# Compile binary with Clang.
clang++ -std=c++17 -Igenerated -I${protobuf}/include \
main.cc generated/message.pb.cc \
-L${abseil}/lib -labsl_log_internal_check_op -labsl_log_internal_message \
-L${protobuf}/lib -lprotobuf -o proto-app-clang
# Compile binary with GCC.
g++ -std=c++17 -Igenerated -I${protobuf}/include \
main.cc generated/message.pb.cc \
-L${abseil}/lib -labsl_log_internal_check_op -labsl_log_internal_message \
-L${protobuf}/lib -lprotobuf -o proto-app-gnu
'';
installPhase = ''
mkdir -p $out/bin
cp proto-app-* $out/bin/
'';
};
devShells.default = pkgs.mkShell {
name = "proto-dev-shell";
buildInputs = [ protobuf pkgs.clang pkgs.gcc pkgs.abseil-cpp ];
shellHook = ''
echo "🚀 Protobuf dev shell with Clang/GCC/Abseil is ready"
'';
};
});
}
NOTE: Here is my flake.lock.
-
Compile binary with
nix develop
. -
Try running GCC-compiled version:
$ ./result/bin/proto-app-gnu
🔧 Creating a user message...
📦 Serializing to 'input.bin'...
✅ Serialization complete.
📂 Reading from 'input.bin'...
✅ Successfully read and parsed the message:
username: "johndoe"
email: "[email protected]"
preferences {
email_notifications: true
theme: "dark"
}
Works as expected 👌.
- Try running clang-version:
$ ./result/bin/proto-app-clang
🔧 Creating a user message...
📦 Serializing to 'input.bin'...
✅ Serialization complete.
📂 Reading from 'input.bin'...
Segmentation fault (core dumped)
It crashed 😑.
What Is Wrong?
There is nothing wrong with Clang per se, but mixing compilers is dangerous 💣. I discovered that my protobuf library was compiled with GCC:
$ ldd ./result/bin/proto-app-clang | grep libprotobuf
libprotobuf.so.29.4.0 => /nix/store/s0521fkq7z9lycdgal6y7g726vz5d6zk-protobuf-29.4/lib/libprotobuf.so.29.4.0 (0x00007f2f1a9d1000)
$ readelf -p .comment /nix/store/s0521fkq7z9lycdgal6y7g726vz5d6zk-protobuf-29.4/lib/libprotobuf.so.29.4.0
String dump of section '.comment':
[ 0] GCC: (GNU) 14.2.1 20250322
Why it worked in previous protobuf version (v28)? I have no idea, but I am sharing it so someone else can understand what is happening - like in #20812.