Repository navigation
Bug: COPY ... TO segfaults in Binder::bindCopyToClause with the prebuilt liblbug on Ubuntu 26.04 / GCC 15 #1381
Description
Ladybug version
v0.21.2 (Rust crate lbug = "=0.21.2", default prebuilt liblbug-static-linux-x86_64-compat)
What operating system are you using?
Ubuntu 26.04 (GCC 15.2.0, glibc 2.43, OpenSSL 3.5.5). This is the image currently behind GitHub Actions' ubuntu-latest. Ubuntu 24.04 (GCC 13.3.0) is not affected.
What happened?
Any COPY (...) TO '...' segfaults while binding the statement when the Rust crate links the prebuilt static liblbug on Ubuntu 26.04. The same program on Ubuntu 24.04 writes the file and exits normally. Expected: the parquet file is written, as on 24.04.
Backtrace (gdb, from our application; the top frames are the same in the minimal reproduction below):
#0 std::__format::_Sink<char>::_M_write(std::basic_string_view<char>) ()
#1 std::__format::_Sink_iter<char> std::__format::__formatter_str<char>::format<...>(std::basic_string_view<char>, std::basic_format_context<...>&) ()
#2 decltype(auto) std::basic_format_arg<...>::_M_visit<std::__format::_Formatting_scanner<...>::_M_format_arg(...)>(...) ()
#3 lbug::binder::Binder::bindCopyToClause(lbug::parser::Statement const&) ()
#4 lbug::binder::Binder::bind(lbug::parser::Statement const&) ()
#5 lbug::main::ClientContext::prepareNoLock(...) ()
...
#10 lbug::main::Connection::query(std::basic_string_view<char>) ()
#11 lbug_rs::connection_query(lbug::main::Connection&, std::basic_string_view<char>) ()
#12 lbug_rs$cxxbridge1$connection_query ()
#13 <lbug::connection::Connection>::query ()
Likely cause (our analysis, not confirmed): frames #0–#2 resolve to addresses next to the locally compiled cxx bridge (lbug_rs$cxxbridge1$...), not next to the prebuilt core (lbug::binder::...). The prebuilt archive appears to be compiled with an older GCC, while the Rust crate's cxx bridge is compiled on the user's machine with GCC 15. Both instantiate libstdc++'s std::format templates as weak/COMDAT symbols, so the linker keeps one copy: the GCC 15 one from the bridge. The prebuilt's bindCopyToClause, compiled against the older <format> layout, then calls into it. std::format's ABI was not stable across these GCC versions, which would explain the crash.
Supporting evidence: building from source on the same Ubuntu 26.04 machine (LBUG_BUILD_FROM_SOURCE=1), so that everything is compiled by GCC 15, fixes it. Our full test suite, which hits this path, passes.
Possible fixes upstream, if the analysis holds: build the Linux prebuilts with the newest GCC you support, or compile them with -fvisibility=hidden / -fvisibility-inlines-hidden (or a version script) so the archive's std::format instantiations stay internal and don't merge with the consumer's.
Are there known steps to reproduce?
Cargo.toml:
[package]
name = "lbug-copy-to-repro"
version = "0.1.0"
edition = "2021"
[dependencies]
lbug = "=0.21.2"src/main.rs:
use lbug::{Connection, Database, SystemConfig};
fn main() {
let dir = std::env::temp_dir().join("lbug-copy-to-repro");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let db = Database::new(dir.join("db"), SystemConfig::default()).unwrap();
let conn = Connection::new(&db).unwrap();
conn.query("CREATE NODE TABLE T(id INT64 PRIMARY KEY, name STRING)").unwrap();
conn.query("CREATE (:T {id: 1, name: 'a'})").unwrap();
let out = dir.join("out.parquet");
let q = format!("COPY (MATCH (t:T) RETURN t.id, t.name) TO '{}'", out.display());
println!("running: {q}");
conn.query(&q).unwrap();
println!("ok: wrote {}", out.display());
}Run it in a clean container:
docker run --rm -v "$PWD":/src:ro ubuntu:26.04 bash -c '
apt-get update -qq && apt-get install -y -qq curl ca-certificates build-essential pkg-config libssl-dev >/dev/null
curl -sSf https://sh.rustup.rs | sh -s -- -y -q --profile minimal; . $HOME/.cargo/env
cp -r /src /work && cd /work && cargo build -q && ./target/debug/lbug-copy-to-repro; echo "exit code: $?"'Result:
| Image | GCC | Result |
|---|---|---|
ubuntu:26.04 |
15.2.0 | Segmentation fault (core dumped), exit code 139 |
ubuntu:24.04 |
13.3.0 | ok: wrote /tmp/lbug-copy-to-repro/out.parquet, exit code 0 |
ubuntu:26.04 with LBUG_BUILD_FROM_SOURCE=1 |
15.2.0 | passes (checked with our application's test suite) |
We currently work around it by setting LBUG_BUILD_FROM_SOURCE=1 on Linux in CI and release builds.
Activity
sunsided commentedon Oct 24, 2026
Follow-up: I've opened #1383 with a fix verified against this repro.
One correction to the suggestions above:
-fvisibility=hiddendoesn't help here. Visibility only affects the dynamic symbol table, and the static linker still deduplicates COMDAT groups across objects regardless of visibility. Building the prebuilts with a newer GCC would only reverse the mismatch for consumers on older distributions.#1383 instead post-processes the bundled static archive behind a new, default-off option
LBUG_LOCALIZE_STATIC_SYMBOLS, which the Linux prebuilt workflow enables:--force-group-allocation,lbugglobal local,STB_GNU_UNIQUEdefinitions.I built v0.21.2 with the patch in manylinux_2_28 / gcc-toolset-13. The resulting archive passes the minimal repro on Ubuntu 26.04 and 24.04, and our 608-test downstream suite on 26.04.