Skip to content

[C++] Parsing crashes with segmentation fault when clang is used, since protobuf >= 29 #21447

Open
@koxu1996

Description

@koxu1996

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

  1. 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;
}
  1. 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;
}
  1. 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.

  1. Compile binary with nix develop.

  2. 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 👌.

  1. 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.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions