From c4cb758eccb56f0d0c8bf26197156dfb5f6cb21c Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Tue, 12 Aug 2025 15:41:15 -0400 Subject: [PATCH 1/3] mpgen: Work around c++20 / capnproto 0.8 incompatibility The kj/string.h header installed by capnproto 0.8 doesn't work well when compiling with -std=c++20 or later because the reverse StringPtr/char* comparison function it provides is broken in c++20: inline bool operator==(const char* a, const StringPtr& b) { return b == a; } Before C++20 this would implicitly convert `a` to a StringPtr and call the StringPtr::operator== method. But starting with C++20 it actually calls itself recursively and either loops forever or crashes. This problem was fixed upstream by https://github.com/capnproto/capnproto/pull/1170 in Cap'n Proto 0.9.0. Avoid the problem here for older versions by just not using the operator. A CI job testing older versions is added in the next commit to avoid similar breakage in the future. --- src/mp/gen.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mp/gen.cpp b/src/mp/gen.cpp index 21a4d931..18400710 100644 --- a/src/mp/gen.cpp +++ b/src/mp/gen.cpp @@ -146,7 +146,7 @@ static void Generate(kj::StringPtr src_prefix, const std::vector>& import_dirs) { std::string output_path; - if (src_prefix == ".") { + if (src_prefix == kj::StringPtr{"."}) { output_path = src_file; } else if (!src_file.startsWith(src_prefix) || src_file.size() <= src_prefix.size() || src_file[src_prefix.size()] != '/') { @@ -156,7 +156,7 @@ static void Generate(kj::StringPtr src_prefix, } std::string include_path; - if (include_prefix == ".") { + if (include_prefix == kj::StringPtr{"."}) { include_path = src_file; } else if (!src_file.startsWith(include_prefix) || src_file.size() <= include_prefix.size() || src_file[include_prefix.size()] != '/') { @@ -425,8 +425,8 @@ static void Generate(kj::StringPtr src_prefix, const std::string method_prefix = Format() << message_namespace << "::" << method_interface.getShortDisplayName() << "::" << Cap(method_name); - const bool is_construct = method_name == "construct"; - const bool is_destroy = method_name == "destroy"; + const bool is_construct = method_name == kj::StringPtr{"construct"}; + const bool is_destroy = method_name == kj::StringPtr{"destroy"}; struct Field { @@ -465,7 +465,7 @@ static void Generate(kj::StringPtr src_prefix, field.result_is_set = true; } - if (!param && field_name == "result") { + if (!param && field_name == kj::StringPtr{"result"}) { field.retval = true; has_result = true; } From 8ceeaa6ae401c96de14e6fa67bb723f42da92c32 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Tue, 12 Aug 2025 16:00:38 -0400 Subject: [PATCH 2/3] ci: Add olddeps job to test old dependencies versions The CI job currently just tests old Cap'n Proto versions, but it might be nice to extend in the future to test old compilers & build tools too. Support for versions of Cap'n Proto before 0.7.0 was dropped in https://github.com/bitcoin-core/libmultiprocess/pull/88 in order to avoid compiler warnings and simplify code. Before that, versions back to 0.5 were supported and are basically still compatible since the Cap'n Proto API hasn't changed and libmultiprocess does not rely on newer features. --- .github/workflows/ci.yml | 2 +- ci/README.md | 1 + ci/configs/olddeps.bash | 5 +++++ ci/patches/spaceship.patch | 29 +++++++++++++++++++++++++++++ shell.nix | 29 ++++++++++++++++++++++++++++- 5 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 ci/configs/olddeps.bash create mode 100644 ci/patches/spaceship.patch diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e751c5f..db1e538f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - config: [default, llvm, gnu32, sanitize] + config: [default, llvm, gnu32, sanitize, olddeps] name: build • ${{ matrix.config }} diff --git a/ci/README.md b/ci/README.md index 85eb467c..2ddaa9e1 100644 --- a/ci/README.md +++ b/ci/README.md @@ -20,6 +20,7 @@ CI_CONFIG=ci/configs/default.bash ci/scripts/run.sh CI_CONFIG=ci/configs/llvm.bash ci/scripts/run.sh CI_CONFIG=ci/configs/gnu32.bash ci/scripts/run.sh CI_CONFIG=ci/configs/sanitize.bash ci/scripts/run.sh +CI_CONFIG=ci/configs/olddeps.bash ci/scripts/run.sh ``` By default CI jobs will reuse their build directories. `CI_CLEAN=1` can be specified to delete them before running instead. diff --git a/ci/configs/olddeps.bash b/ci/configs/olddeps.bash new file mode 100644 index 00000000..4684bd58 --- /dev/null +++ b/ci/configs/olddeps.bash @@ -0,0 +1,5 @@ +CI_DESC="CI job using old Cap'n Proto version" +CI_DIR=build-olddeps +export CXXFLAGS="-Werror -Wall -Wextra -Wpedantic -Wno-unused-parameter -Wno-error=array-bounds" +NIX_ARGS=(--argstr capnprotoVersion "0.7.1") +BUILD_ARGS=(-k) diff --git a/ci/patches/spaceship.patch b/ci/patches/spaceship.patch new file mode 100644 index 00000000..08a885dd --- /dev/null +++ b/ci/patches/spaceship.patch @@ -0,0 +1,29 @@ +commit e3da7da967b94f373c29a198ce45f30fb9f0e517 +Author: Ed Catmur +Date: Tue Jan 31 16:27:04 2023 +0000 + + Remove operator!= synthesized by spaceship + + An operator!= suppresses the reversed equality comparison candidate generation. + + This is visible in clang 16 (rc0 just released) and in gcc trunk (so probably gcc 13). + +diff --git a/c++/src/kj/string.h b/c++/src/kj/string.h +index 193442aa..17835892 100644 +--- a/c++/src/kj/string.h ++++ b/c++/src/kj/string.h +@@ -122,10 +122,14 @@ public: + inline constexpr const char* end() const { return content.end() - 1; } + + inline constexpr bool operator==(decltype(nullptr)) const { return content.size() <= 1; } ++#if !__cpp_impl_three_way_comparison + inline constexpr bool operator!=(decltype(nullptr)) const { return content.size() > 1; } ++#endif + + inline bool operator==(const StringPtr& other) const; ++#if !__cpp_impl_three_way_comparison + inline bool operator!=(const StringPtr& other) const { return !(*this == other); } ++#endif + inline bool operator< (const StringPtr& other) const; + inline bool operator> (const StringPtr& other) const { return other < *this; } + inline bool operator<=(const StringPtr& other) const { return !(other < *this); } diff --git a/shell.nix b/shell.nix index eacfdc2a..0b7dce89 100644 --- a/shell.nix +++ b/shell.nix @@ -2,12 +2,39 @@ , crossPkgs ? import {} , enableLibcxx ? false # Whether to use libc++ toolchain and libraries instead of libstdc++ , minimal ? false # Whether to create minimal shell without extra tools (faster when cross compiling) +, capnprotoVersion ? null }: let lib = pkgs.lib; llvm = crossPkgs.llvmPackages_20; - capnproto = crossPkgs.capnproto.override (lib.optionalAttrs enableLibcxx { clangStdenv = llvm.libcxxStdenv; }); + capnprotoHashes = { + "0.7.0" = "sha256-Y/7dUOQPDHjniuKNRw3j8dG1NI9f/aRWpf8V0WzV9k8="; + "0.7.1" = "sha256-3cBpVmpvCXyqPUXDp12vCFCk32ZXWpkdOliNH37UwWE="; + "0.8.0" = "sha256-rfiqN83begjJ9eYjtr21/tk1GJBjmeVfa3C3dZBJ93w="; + "0.8.1" = "sha256-OZqNVYdyszro5rIe+w6YN00g6y8U/1b8dKYc214q/2o="; + "0.9.0" = "sha256-yhbDcWUe6jp5PbIXzn5EoKabXiWN8lnS08hyfxUgEQ0="; + "0.9.2" = "sha256-BspWOPZcP5nCTvmsDE62Zutox+aY5pw42d6hpH3v4cM="; + "0.10.0" = "sha256-++F4l54OMTDnJ+FO3kV/Y/VLobKVRk461dopanuU3IQ="; + "0.10.4" = "sha256-45sxnVyyYIw9i3sbFZ1naBMoUzkpP21WarzR5crg4X8="; + "1.0.0" = "sha256-NLTFJdeOzqhk4ATvkc17Sh6g/junzqYBBEoXYGH/czo="; + "1.0.2" = "sha256-LVdkqVBTeh8JZ1McdVNtRcnFVwEJRNjt0JV2l7RkuO8="; + "1.1.0" = "sha256-gxkko7LFyJNlxpTS+CWOd/p9x/778/kNIXfpDGiKM2A="; + "1.2.0" = "sha256-aDcn4bLZGq8915/NPPQsN5Jv8FRWd8cAspkG3078psc="; + }; + capnprotoBase = if capnprotoVersion == null then crossPkgs.capnproto else crossPkgs.capnproto.overrideAttrs (old: { + version = capnprotoVersion; + src = crossPkgs.fetchFromGitHub { + owner = "capnproto"; + repo = "capnproto"; + rev = "v${capnprotoVersion}"; + hash = lib.attrByPath [capnprotoVersion] "" capnprotoHashes; + }; + patches = lib.optionals (lib.versionAtLeast capnprotoVersion "0.9.0" && lib.versionOlder capnprotoVersion "0.10.4") [ ./ci/patches/spaceship.patch ]; + } // (lib.optionalAttrs (lib.versionOlder capnprotoVersion "0.10") { + env = { }; # Drop -std=c++20 flag forced by nixpkgs + })); + capnproto = capnprotoBase.override (lib.optionalAttrs enableLibcxx { clangStdenv = llvm.libcxxStdenv; }); clang = if enableLibcxx then llvm.libcxxClang else llvm.clang; clang-tools = llvm.clang-tools.override { inherit enableLibcxx; }; in crossPkgs.mkShell { From dc3ba2204606d8b5b681a6798407e68b3567e7a9 Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Wed, 13 Aug 2025 06:11:37 -0400 Subject: [PATCH 3/3] cmake, doc: Add check for CVE-2022-46149 Also document minimum Cap'n Proto version in doc/install.md --- CMakeLists.txt | 23 ++++++++++++++++++++++- doc/install.md | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 284eba24..ef694412 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,8 +12,29 @@ endif() include("cmake/compat_find.cmake") -find_package(CapnProto 0.7.0 REQUIRED) find_package(Threads REQUIRED) +find_package(CapnProto 0.7 REQUIRED) + +# Check for list-of-pointers memory access bug from Nov 2022 +# https://nvd.nist.gov/vuln/detail/CVE-2022-46149 +# https://github.com/advisories/GHSA-qqff-4vw4-f6hx +# https://github.com/capnproto/capnproto/security/advisories/GHSA-qqff-4vw4-f6hx +# https://github.com/capnproto/capnproto/blob/master/security-advisories/2022-11-30-0-pointer-list-bounds.md +# https://capnproto.org/news/2022-11-30-CVE-2022-46149-security-advisory.html +# https://dwrensha.github.io/capnproto-rust/2022/11/30/out_of_bounds_memory_access_bug.html +if(CapnProto_VERSION STREQUAL "0.7.0" + OR CapnProto_VERSION STREQUAL "0.8.0" + OR CapnProto_VERSION STREQUAL "0.9.0" + OR CapnProto_VERSION STREQUAL "0.9.1" + OR CapnProto_VERSION STREQUAL "0.10.0" + OR CapnProto_VERSION STREQUAL "0.10.1" + OR CapnProto_VERSION STREQUAL "0.10.2") + message(FATAL_ERROR + "Cap'n Proto ${CapnProto_VERSION} is affected by CVE-2022-46149.\n" + "Please install an updated package.\n" + "Details: https://github.com/advisories/GHSA-qqff-4vw4-f6hx + ") +endif() set(MPGEN_EXECUTABLE "" CACHE FILEPATH "If specified, should be full path to an external mpgen binary to use rather than the one built internally.") diff --git a/doc/install.md b/doc/install.md index 3fe7c7b2..ac2f7f6a 100644 --- a/doc/install.md +++ b/doc/install.md @@ -1,6 +1,6 @@ # libmultiprocess Installation -Installation currently requires Cap'n Proto: +Installation currently requires Cap'n Proto 0.7 or higher: ```sh apt install libcapnp-dev capnproto