Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package capnproto for openSUSE:Factory checked in at 2026-07-10 17:40:54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/capnproto (Old) and /work/SRC/openSUSE:Factory/.capnproto.new.1991 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "capnproto" Fri Jul 10 17:40:54 2026 rev:21 rq:1364810 version:1.5.0 Changes: -------- --- /work/SRC/openSUSE:Factory/capnproto/capnproto.changes 2026-04-21 12:47:07.928177078 +0200 +++ /work/SRC/openSUSE:Factory/.capnproto.new.1991/capnproto.changes 2026-07-10 17:43:35.913901584 +0200 @@ -1,0 +2,32 @@ +Fri Jul 10 04:57:04 UTC 2026 - Luigi Baldoni <[email protected]> + + * kj: tolerate ERROR_INVALID_FUNCTION from + GetFileInformationByHandleEx + * Fix bare includes in ez-rpc.h + * Revert rpc.h include to relative form in ez-rpc.h + * Add repo_name = ssl for boringssl Bzlmod compatibility + * Replace c++/LICENSE.txt symlink with real file + * Support Bazel 9 in V1 + * Address compiler errors when building against OpenSSL 4.0 + * Maintain compatibility with older OpenSSL versions + * Scope testing to project + * KJ HTTP: validate request URL and status text before + serializing + * KJ HTTP: reject whitespace between header name and colon + * KJ HTTP: reject obsolete line folding + * Fix infinite recursion in KJ_CONTEXT when evaluating + parameters throws + * fix offset overflow in schema loader struct validation + * fix off-by-one write to membersByDiscriminant in schema + validator + * Limit nesting depth when lexing textual capnp schemas + * Clamp rpc Exception type enum. + * fix segment size overflow in message readers + * Document security issues fixed in Cap'n Proto v1.5. + * Fix KJ_CONTEXT throwing test with -fno-exceptions. + * Fix new warning about whitespace when defining a literal + suffix operator. + * Disable new complier warning on mac since it is blocking the + tests. + +------------------------------------------------------------------- Old: ---- capnproto-c++-1.4.0.tar.gz New: ---- capnproto-c++-1.5.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ capnproto.spec ++++++ --- /var/tmp/diff_new_pack.xesECO/_old 2026-07-10 17:43:38.137977770 +0200 +++ /var/tmp/diff_new_pack.xesECO/_new 2026-07-10 17:43:38.137977770 +0200 @@ -22,9 +22,9 @@ %bcond_with clang %endif -%define _libver 1_4_0 +%define _libver 1_5_0 Name: capnproto -Version: 1.4.0 +Version: 1.5.0 Release: 0 Summary: A Data Serialization Format License: MIT ++++++ capnproto-c++-1.4.0.tar.gz -> capnproto-c++-1.5.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/CMakeLists.txt new/capnproto-c++-1.5.0/CMakeLists.txt --- old/capnproto-c++-1.4.0/CMakeLists.txt 2026-03-12 14:59:55.000000000 +0100 +++ new/capnproto-c++-1.5.0/CMakeLists.txt 2026-07-10 00:38:56.000000000 +0200 @@ -2,11 +2,15 @@ include_guard(GLOBAL) project("Cap'n Proto" CXX) -set(VERSION 1.4.0) +set(VERSION 1.5.0) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -include(CTest) +# PROJECT_IS_TOP_LEVEL was added in CMake 3.21 +if (NOT DEFINED PROJECT_IS_TOP_LEVEL OR PROJECT_IS_TOP_LEVEL) + include(CTest) +endif() + include(CheckIncludeFileCXX) include(GNUInstallDirs) if(MSVC) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/configure new/capnproto-c++-1.5.0/configure --- old/capnproto-c++-1.4.0/configure 2026-03-12 14:59:57.000000000 +0100 +++ new/capnproto-c++-1.5.0/configure 2026-07-10 00:38:58.000000000 +0200 @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.72 for Capn Proto 1.4.0. +# Generated by GNU Autoconf 2.72 for Capn Proto 1.5.0. # # Report bugs to <[email protected]>. # @@ -614,8 +614,8 @@ # Identity of this package. PACKAGE_NAME='Capn Proto' PACKAGE_TARNAME='capnproto-c++' -PACKAGE_VERSION='1.4.0' -PACKAGE_STRING='Capn Proto 1.4.0' +PACKAGE_VERSION='1.5.0' +PACKAGE_STRING='Capn Proto 1.5.0' PACKAGE_BUGREPORT='[email protected]' PACKAGE_URL='' @@ -1391,7 +1391,7 @@ # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -'configure' configures Capn Proto 1.4.0 to adapt to many kinds of systems. +'configure' configures Capn Proto 1.5.0 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1462,7 +1462,7 @@ if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of Capn Proto 1.4.0:";; + short | recursive ) echo "Configuration of Capn Proto 1.5.0:";; esac cat <<\_ACEOF @@ -1593,7 +1593,7 @@ test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -Capn Proto configure 1.4.0 +Capn Proto configure 1.5.0 generated by GNU Autoconf 2.72 Copyright (C) 2023 Free Software Foundation, Inc. @@ -2218,7 +2218,7 @@ This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by Capn Proto $as_me 1.4.0, which was +It was created by Capn Proto $as_me 1.5.0, which was generated by GNU Autoconf 2.72. Invocation command line was $ $0$ac_configure_args_raw @@ -3925,7 +3925,7 @@ # Define the identity of the package. PACKAGE='capnproto-c++' - VERSION='1.4.0' + VERSION='1.5.0' printf "%s\n" "#define PACKAGE \"$PACKAGE\"" >>confdefs.h @@ -21136,7 +21136,7 @@ # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by Capn Proto $as_me 1.4.0, which was +This file was extended by Capn Proto $as_me 1.5.0, which was generated by GNU Autoconf 2.72. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -21204,7 +21204,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config='$ac_cs_config_escaped' ac_cs_version="\\ -Capn Proto config.status 1.4.0 +Capn Proto config.status 1.5.0 configured by $0, generated by GNU Autoconf 2.72, with options \\"\$ac_cs_config\\" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/configure.ac new/capnproto-c++-1.5.0/configure.ac --- old/capnproto-c++-1.4.0/configure.ac 2026-03-12 14:59:55.000000000 +0100 +++ new/capnproto-c++-1.5.0/configure.ac 2026-07-10 00:38:56.000000000 +0200 @@ -1,6 +1,6 @@ ## Process this file with autoconf to produce configure. -AC_INIT([Capn Proto],[1.4.0],[[email protected]],[capnproto-c++]) +AC_INIT([Capn Proto],[1.5.0],[[email protected]],[capnproto-c++]) AC_CONFIG_SRCDIR([src/capnp/layout.c++]) AC_CONFIG_AUX_DIR([build-aux]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/c++.capnp.h new/capnproto-c++-1.5.0/src/capnp/c++.capnp.h --- old/capnproto-c++-1.4.0/src/capnp/c++.capnp.h 2026-03-12 14:59:55.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/c++.capnp.h 2026-07-10 00:38:56.000000000 +0200 @@ -8,7 +8,7 @@ #ifndef CAPNP_VERSION #error "CAPNP_VERSION is not defined, is capnp/generated-header-support.h missing?" -#elif CAPNP_VERSION != 1004000 +#elif CAPNP_VERSION != 1005000 #error "Version mismatch between generated code and library headers. You must use the same version of the Cap'n Proto compiler and library." #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/common.h new/capnproto-c++-1.5.0/src/capnp/common.h --- old/capnproto-c++-1.4.0/src/capnp/common.h 2026-03-12 14:59:55.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/common.h 2026-07-10 00:38:56.000000000 +0200 @@ -47,7 +47,7 @@ namespace capnp { #define CAPNP_VERSION_MAJOR 1 -#define CAPNP_VERSION_MINOR 4 +#define CAPNP_VERSION_MINOR 5 #define CAPNP_VERSION_MICRO 0 #define CAPNP_VERSION \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/compat/json.capnp.h new/capnproto-c++-1.5.0/src/capnp/compat/json.capnp.h --- old/capnproto-c++-1.4.0/src/capnp/compat/json.capnp.h 2026-03-12 14:59:55.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/compat/json.capnp.h 2026-07-10 00:38:56.000000000 +0200 @@ -11,7 +11,7 @@ #ifndef CAPNP_VERSION #error "CAPNP_VERSION is not defined, is capnp/generated-header-support.h missing?" -#elif CAPNP_VERSION != 1004000 +#elif CAPNP_VERSION != 1005000 #error "Version mismatch between generated code and library headers. You must use the same version of the Cap'n Proto compiler and library." #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/compiler/grammar.capnp.h new/capnproto-c++-1.5.0/src/capnp/compiler/grammar.capnp.h --- old/capnproto-c++-1.4.0/src/capnp/compiler/grammar.capnp.h 2026-03-12 14:59:55.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/compiler/grammar.capnp.h 2026-07-10 00:38:56.000000000 +0200 @@ -8,7 +8,7 @@ #ifndef CAPNP_VERSION #error "CAPNP_VERSION is not defined, is capnp/generated-header-support.h missing?" -#elif CAPNP_VERSION != 1004000 +#elif CAPNP_VERSION != 1005000 #error "Version mismatch between generated code and library headers. You must use the same version of the Cap'n Proto compiler and library." #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/compiler/lexer-test.c++ new/capnproto-c++-1.5.0/src/capnp/compiler/lexer-test.c++ --- old/capnproto-c++-1.4.0/src/capnp/compiler/lexer-test.c++ 2023-11-21 17:03:29.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/compiler/lexer-test.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -368,6 +368,57 @@ doLex<LexedTokens>("\xef\xbb\xbf""foo bar\xef\xbb\xbf""baz").cStr()); } +class TestCountingErrorReporter: public ErrorReporter { +public: + uint errorCount = 0; + void addError(uint32_t startByte, uint32_t endByte, kj::StringPtr message) override { + ++errorCount; + } + bool hadErrors() override { + return errorCount > 0; + } +}; + +kj::String nested(char open, char close, uint count) { + // Produces a string of `count` `open` chars followed by `count` `close` chars. + auto result = kj::heapString(count * 2); + for (uint i = 0; i < count; i++) { + result[i] = open; + result[count + i] = close; + } + return result; +} + +TEST(Lexer, DeeplyNestedInputDoesNotOverflowStack) { + // Regression test for a stack-overflow vulnerability: the lexer is a recursive-descent parser, + // so deeply-nested brackets/braces could overflow the stack and crash the process. The lexer now + // enforces a maximum nesting depth, rejecting over-nested input rather than crashing. + + auto tryLexTokens = [](kj::StringPtr text) { + MallocMessageBuilder message; + auto file = message.initRoot<LexedTokens>(); + TestCountingErrorReporter errorReporter; + return lex(text, file, errorReporter); + }; + auto tryLexStatements = [](kj::StringPtr text) { + MallocMessageBuilder message; + auto file = message.initRoot<LexedStatements>(); + TestCountingErrorReporter errorReporter; + return lex(text, file, errorReporter); + }; + + // A reasonable amount of nesting is accepted. + EXPECT_TRUE(tryLexTokens(nested('(', ')', 30))); + EXPECT_TRUE(tryLexTokens(nested('[', ']', 30))); + EXPECT_TRUE(tryLexStatements(nested('{', '}', 30))); + + // Extremely deep nesting is rejected (rather than crashing with a stack overflow). We use a + // large count that would definitely overflow the stack absent the depth limit. + EXPECT_FALSE(tryLexTokens(nested('(', ')', 100000))); // parenthesized lists + EXPECT_FALSE(tryLexTokens(nested('[', ']', 100000))); // bracketed lists + EXPECT_FALSE(tryLexStatements(nested('{', '}', 100000))); // curly-brace blocks +} + } // namespace } // namespace compiler } // namespace capnp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/compiler/lexer.c++ new/capnproto-c++-1.5.0/src/capnp/compiler/lexer.c++ --- old/capnproto-c++-1.4.0/src/capnp/compiler/lexer.c++ 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/compiler/lexer.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -74,6 +74,49 @@ namespace { +constexpr uint MAX_NESTING_DEPTH = 64; +// Maximum allowed nesting depth of parentheses/brackets/braces in a schema file. The lexer is a +// recursive-descent parser, so each level of nesting consumes stack space; without a limit, a +// maliciously-crafted input consisting of deeply-nested brackets could overflow the stack and +// crash the process. This limit is far larger than any legitimate schema requires. +// +// Note that this single limit protects both the lexer and the later parsing stages: the lexer +// builds nested token/statement trees whose depth is bounded here, and the parser (parser.c++) +// merely recurses over those already-bounded trees. + +template <typename SubParser> +class NestingGuard { + // Parser combinator which wraps another parser, incrementing a shared depth counter for the + // duration of the sub-parser and failing (without recursing) if the depth would exceed + // MAX_NESTING_DEPTH. Placed at each point where the grammar can recurse, this bounds the total + // recursion depth and thus the stack usage. +public: + explicit constexpr NestingGuard(uint& depth, SubParser&& subParser) + : depth(depth), subParser(kj::mv(subParser)) {} + + template <typename Input> + auto operator()(Input& input) const + -> decltype(kj::instance<const SubParser&>()(input)) { + if (depth >= MAX_NESTING_DEPTH) { + // Too deeply nested; refuse to recurse further. This causes a parse failure which will be + // reported as a generic parse error at this location. + return nullptr; + } + ++depth; + KJ_DEFER(--depth); + return subParser(input); + } + +private: + uint& depth; + SubParser subParser; +}; + +template <typename SubParser> +constexpr NestingGuard<SubParser> nestingGuard(uint& depth, SubParser&& subParser) { + return NestingGuard<SubParser>(depth, kj::fwd<SubParser>(subParser)); +} + typedef p::Span<uint32_t> Location; Token::Builder initTok(Orphan<Token>& t, const Location& loc) { @@ -173,7 +216,7 @@ } })); - auto& token = arena.copy(p::oneOf( + auto& rawToken = arena.copy(p::oneOf( p::transformWithLocation(p::identifier, [this](Location loc, kj::String name) -> Orphan<Token> { auto t = orphanage.newOrphan<Token>(); @@ -250,6 +293,11 @@ "Non-UTF-8 input detected. Cap'n Proto schema files must be UTF-8 text."); return nullptr; }), [](kj::Maybe<Orphan<Token>> param) { return param; }))); + + // Wrap `token` in a nesting guard so that recursion through parenthesized/bracketed lists (which + // go token -> commaDelimitedList -> tokenSequence -> token) is depth-limited. + auto& token = arena.copy(nestingGuard(nestingDepth, p::ref<ParserInput>(rawToken))); + parsers.tokenSequence = arena.copy(p::sequence( commentsAndWhitespace, p::many(p::sequence(token, commentsAndWhitespace)))); @@ -288,7 +336,7 @@ }) )); - auto& statement = arena.copy(p::transformWithLocation(p::sequence(tokenSequence, statementEnd), + auto& rawStatement = arena.copy(p::transformWithLocation(p::sequence(tokenSequence, statementEnd), [](Location loc, kj::Array<Orphan<Token>>&& tokens, Orphan<Statement>&& statement) { auto builder = statement.get(); auto tokensBuilder = builder.initTokens(tokens.size()); @@ -300,6 +348,11 @@ return kj::mv(statement); })); + // Wrap `statement` in a nesting guard so that recursion through curly-brace blocks (which go + // statement -> statementEnd -> statementSequence -> statement) is depth-limited. This shares the + // same depth counter as the token guard above, so the limit applies to the total nesting depth. + auto& statement = arena.copy(nestingGuard(nestingDepth, p::ref<ParserInput>(rawStatement))); + parsers.statementSequence = arena.copy(sequence( commentsAndWhitespace, many(sequence(statement, commentsAndWhitespace)))); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/compiler/lexer.capnp.h new/capnproto-c++-1.5.0/src/capnp/compiler/lexer.capnp.h --- old/capnproto-c++-1.4.0/src/capnp/compiler/lexer.capnp.h 2026-03-12 14:59:55.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/compiler/lexer.capnp.h 2026-07-10 00:38:56.000000000 +0200 @@ -8,7 +8,7 @@ #ifndef CAPNP_VERSION #error "CAPNP_VERSION is not defined, is capnp/generated-header-support.h missing?" -#elif CAPNP_VERSION != 1004000 +#elif CAPNP_VERSION != 1005000 #error "Version mismatch between generated code and library headers. You must use the same version of the Cap'n Proto compiler and library." #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/compiler/lexer.h new/capnproto-c++-1.5.0/src/capnp/compiler/lexer.h --- old/capnproto-c++-1.4.0/src/capnp/compiler/lexer.h 2023-11-21 17:03:29.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/compiler/lexer.h 2026-07-10 00:33:53.000000000 +0200 @@ -91,6 +91,11 @@ Orphanage orphanage; kj::Arena arena; Parsers parsers; + + uint nestingDepth = 0; + // Tracks the current recursion depth of the recursive-descent parser, so that maliciously-nested + // input (e.g. deeply-nested parentheses, brackets, or curly-brace blocks) can be rejected before + // it overflows the stack. See NestingGuard in lexer.c++. }; } // namespace compiler diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/ez-rpc.h new/capnproto-c++-1.5.0/src/capnp/ez-rpc.h --- old/capnproto-c++-1.4.0/src/capnp/ez-rpc.h 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/ez-rpc.h 2026-07-10 00:33:53.000000000 +0200 @@ -22,7 +22,7 @@ #pragma once #include "rpc.h" -#include "message.h" +#include <capnp/message.h> CAPNP_BEGIN_HEADER diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/persistent.capnp.h new/capnproto-c++-1.5.0/src/capnp/persistent.capnp.h --- old/capnproto-c++-1.4.0/src/capnp/persistent.capnp.h 2026-03-12 14:59:55.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/persistent.capnp.h 2026-07-10 00:38:56.000000000 +0200 @@ -11,7 +11,7 @@ #ifndef CAPNP_VERSION #error "CAPNP_VERSION is not defined, is capnp/generated-header-support.h missing?" -#elif CAPNP_VERSION != 1004000 +#elif CAPNP_VERSION != 1005000 #error "Version mismatch between generated code and library headers. You must use the same version of the Cap'n Proto compiler and library." #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/rpc-test.c++ new/capnproto-c++-1.5.0/src/capnp/rpc-test.c++ --- old/capnproto-c++-1.4.0/src/capnp/rpc-test.c++ 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/rpc-test.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -1311,6 +1311,42 @@ EXPECT_TRUE(conn->receiveIncomingMessage().wait(context.waitScope) == nullptr); } +KJ_TEST("abort with invalid exception type") { + // Regression test: a peer sends an abort message with an out-of-range Exception::type. + // Cap'n Proto enums are raw UInt16 on the wire, so any value 0-65535 is possible. + // The server must handle this gracefully (clamp to FAILED) rather than crashing + // due to an out-of-bounds array access in KJ_STRINGIFY(Exception::Type). + + TestContext context; + + MallocMessageBuilder refMessage(128); + auto hostId = refMessage.initRoot<test::TestSturdyRefHostId>(); + hostId.setHost("server"); + + auto conn = KJ_ASSERT_NONNULL(context.clientNetwork.connect(hostId)); + + { + // Send an abort with an out-of-range exception type (0xFFFF). + auto msg = conn->newOutgoingMessage(128); + auto abort = msg->getBody().initAs<rpc::Message>().initAbort(); + abort.setReason("malformed type test"); + abort.setType(static_cast<rpc::Exception::Type>(0xFFFF)); + msg->send(); + } + + // The server should handle the malformed abort without crashing. It disconnects and + // sends its own abort back (with the clamped exception type). + auto reply = KJ_ASSERT_NONNULL(conn->receiveIncomingMessage().wait(context.waitScope)); + KJ_EXPECT(reply->getBody().getAs<rpc::Message>().which() == rpc::Message::ABORT); + + // Verify the abort response has a valid (clamped) exception type. + auto responseException = reply->getBody().getAs<rpc::Message>().getAbort(); + KJ_EXPECT(responseException.getType() == rpc::Exception::Type::FAILED); + + // Connection should then close. + KJ_EXPECT(conn->receiveIncomingMessage().wait(context.waitScope) == nullptr); +} + KJ_TEST("handles exceptions thrown during disconnect") { // This is similar to the earlier "abort" test, but throws an exception on // connection shutdown, to exercise the RpcConnectionState error handler. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/rpc-twoparty.capnp.h new/capnproto-c++-1.5.0/src/capnp/rpc-twoparty.capnp.h --- old/capnproto-c++-1.4.0/src/capnp/rpc-twoparty.capnp.h 2026-03-12 14:59:55.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/rpc-twoparty.capnp.h 2026-07-10 00:38:56.000000000 +0200 @@ -11,7 +11,7 @@ #ifndef CAPNP_VERSION #error "CAPNP_VERSION is not defined, is capnp/generated-header-support.h missing?" -#elif CAPNP_VERSION != 1004000 +#elif CAPNP_VERSION != 1005000 #error "Version mismatch between generated code and library headers. You must use the same version of the Cap'n Proto compiler and library." #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/rpc.c++ new/capnproto-c++-1.5.0/src/capnp/rpc.c++ --- old/capnproto-c++-1.4.0/src/capnp/rpc.c++ 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/rpc.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -120,8 +120,18 @@ } }(); - kj::Exception result(static_cast<kj::Exception::Type>(exception.getType()), - "(remote)", 0, kj::mv(reason)); + // Cap'n Proto enums are encoded as raw UInt16 on the wire, so a peer can send any value 0-65535. + // We must clamp to valid kj::Exception::Type values to avoid undefined behavior (e.g. out-of- + // bounds array access when stringifying the exception type). + kj::Exception::Type type = kj::Exception::Type::FAILED; + switch (exception.getType()) { + case rpc::Exception::Type::FAILED: type = kj::Exception::Type::FAILED; break; + case rpc::Exception::Type::OVERLOADED: type = kj::Exception::Type::OVERLOADED; break; + case rpc::Exception::Type::DISCONNECTED: type = kj::Exception::Type::DISCONNECTED; break; + case rpc::Exception::Type::UNIMPLEMENTED: type = kj::Exception::Type::UNIMPLEMENTED; break; + } + + kj::Exception result(type, "(remote)", 0, kj::mv(reason)); if (exception.hasTrace()) { result.setRemoteTrace(kj::str(exception.getTrace())); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/rpc.capnp.h new/capnproto-c++-1.5.0/src/capnp/rpc.capnp.h --- old/capnproto-c++-1.4.0/src/capnp/rpc.capnp.h 2026-03-12 14:59:55.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/rpc.capnp.h 2026-07-10 00:38:56.000000000 +0200 @@ -8,7 +8,7 @@ #ifndef CAPNP_VERSION #error "CAPNP_VERSION is not defined, is capnp/generated-header-support.h missing?" -#elif CAPNP_VERSION != 1004000 +#elif CAPNP_VERSION != 1005000 #error "Version mismatch between generated code and library headers. You must use the same version of the Cap'n Proto compiler and library." #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/schema-loader-test.c++ new/capnproto-c++-1.5.0/src/capnp/schema-loader-test.c++ --- old/capnproto-c++-1.4.0/src/capnp/schema-loader-test.c++ 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/schema-loader-test.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -233,6 +233,31 @@ loadUnderAlternateTypeId<test::TestAllTypes>(loader, typeId<test::TestListDefaults>())); } +TEST(SchemaLoader, OutOfBoundsFieldOffset) { + // A field's offset is a UInt32 taken straight from the schema node. The validator must reject + // offsets that don't fit within the struct's declared data section, because the layout code + // (StructBuilder::setDataField) trusts the offset and does no bounds check of its own. An offset + // near 2^32 used to slip through, because the bounds check evaluated (offset + 1) * bits in + // 32-bit arithmetic, which wraps around to a small value. + SchemaLoader loader; + + MallocMessageBuilder builder; + builder.setRoot(Schema::from<test::TestAllTypes>().getProto()); + auto node = builder.getRoot<schema::Node>(); + + bool found = false; + for (auto field: node.getStruct().getFields()) { + if (field.isSlot() && field.getSlot().getType().isUint64()) { + field.getSlot().setOffset(0xffffffffu); + found = true; + break; + } + } + ASSERT_TRUE(found); + + EXPECT_NONFATAL_FAILURE(loader.load(node)); +} + TEST(SchemaLoader, Enumerate) { SchemaLoader loader; loader.loadCompiledTypeAndDependencies<TestAllTypes>(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/schema-loader.c++ new/capnproto-c++-1.5.0/src/capnp/schema-loader.c++ --- old/capnproto-c++-1.4.0/src/capnp/schema-loader.c++ 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/schema-loader.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -327,8 +327,9 @@ VALIDATE_SCHEMA(structNode.getDiscriminantCount() <= fields.size(), "struct can't have more union fields than total fields"); - VALIDATE_SCHEMA((structNode.getDiscriminantOffset() + 1) * 16 <= dataSizeInBits, - "union discriminant is out-of-bounds"); + VALIDATE_SCHEMA( + (static_cast<uint64_t>(structNode.getDiscriminantOffset()) + 1) * 16 <= dataSizeInBits, + "union discriminant is out-of-bounds"); } membersByDiscriminant = loader.arena.allocateArray<uint16_t>(fields.size()); @@ -361,7 +362,7 @@ membersByDiscriminant[discriminantPos++] = index; } else { - VALIDATE_SCHEMA(nonDiscriminantPos <= fields.size(), + VALIDATE_SCHEMA(nonDiscriminantPos < fields.size(), "discriminantCount did not match fields"); membersByDiscriminant[nonDiscriminantPos++] = index; } @@ -373,10 +374,11 @@ uint fieldBits = 0; bool fieldIsPointer = false; validate(slot.getType(), slot.getDefaultValue(), &fieldBits, &fieldIsPointer); - VALIDATE_SCHEMA(fieldBits * (slot.getOffset() + 1) <= dataSizeInBits && - fieldIsPointer * (slot.getOffset() + 1) <= pointerCount, + uint64_t offset = slot.getOffset(); + VALIDATE_SCHEMA(fieldBits * (offset + 1) <= dataSizeInBits && + fieldIsPointer * (offset + 1) <= pointerCount, "field offset out-of-bounds", - slot.getOffset(), dataSizeInBits, pointerCount); + offset, dataSizeInBits, pointerCount); break; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/schema.capnp.h new/capnproto-c++-1.5.0/src/capnp/schema.capnp.h --- old/capnproto-c++-1.4.0/src/capnp/schema.capnp.h 2026-03-12 14:59:55.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/schema.capnp.h 2026-07-10 00:38:56.000000000 +0200 @@ -8,7 +8,7 @@ #ifndef CAPNP_VERSION #error "CAPNP_VERSION is not defined, is capnp/generated-header-support.h missing?" -#elif CAPNP_VERSION != 1004000 +#elif CAPNP_VERSION != 1005000 #error "Version mismatch between generated code and library headers. You must use the same version of the Cap'n Proto compiler and library." #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/serialize-async.c++ new/capnproto-c++-1.5.0/src/capnp/serialize-async.c++ --- old/capnproto-c++-1.4.0/src/capnp/serialize-async.c++ 2026-03-12 14:11:58.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/serialize-async.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -140,7 +140,7 @@ kj::Promise<void> AsyncMessageReader::readSegments(kj::AsyncInputStream& inputStream, kj::ArrayPtr<word> scratchSpace) { - size_t totalWords = segment0Size(); + uint64_t totalWords = segment0Size(); if (segmentCount() > 1) { for (uint i = 0; i < segmentCount() - 1; i++) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/serialize.c++ new/capnproto-c++-1.5.0/src/capnp/serialize.c++ --- old/capnproto-c++-1.4.0/src/capnp/serialize.c++ 2026-03-12 14:11:58.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/serialize.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -55,7 +55,7 @@ { uint segmentSize = table[1].get(); - KJ_REQUIRE(array.size() >= offset + segmentSize, + KJ_REQUIRE(array.size() - offset >= segmentSize, "Message ends prematurely in first segment.") { return; } @@ -70,7 +70,7 @@ for (uint i = 1; i < segmentCount; i++) { uint segmentSize = table[i + 1].get(); - KJ_REQUIRE(array.size() >= offset + segmentSize, "Message ends prematurely.") { + KJ_REQUIRE(array.size() - offset >= segmentSize, "Message ends prematurely.") { moreSegments = nullptr; return; } @@ -188,7 +188,7 @@ uint segmentCount = firstWord[0].get() + 1; uint segment0Size = firstWord[1].get(); - size_t totalWords = segment0Size; + uint64_t totalWords = segment0Size; // Reject messages with too many segments for security reasons. // Use firstWord[0].get() here instead of segmentCount to catch overflow. The actual limit diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/capnp/stream.capnp.h new/capnproto-c++-1.5.0/src/capnp/stream.capnp.h --- old/capnproto-c++-1.4.0/src/capnp/stream.capnp.h 2026-03-12 14:59:55.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/capnp/stream.capnp.h 2026-07-10 00:38:56.000000000 +0200 @@ -8,7 +8,7 @@ #ifndef CAPNP_VERSION #error "CAPNP_VERSION is not defined, is capnp/generated-header-support.h missing?" -#elif CAPNP_VERSION != 1004000 +#elif CAPNP_VERSION != 1005000 #error "Version mismatch between generated code and library headers. You must use the same version of the Cap'n Proto compiler and library." #endif diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/kj/compat/http-test.c++ new/capnproto-c++-1.5.0/src/kj/compat/http-test.c++ --- old/capnproto-c++-1.4.0/src/kj/compat/http-test.c++ 2026-03-12 14:39:15.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/kj/compat/http-test.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -308,6 +308,104 @@ } } +KJ_TEST("HttpHeaders reject whitespace before colon") { + // RFC 9112 section 5.1 requires rejecting a header with whitespace between the field name and the + // colon. Historically KJ silently stripped it, which -- paired with a peer that treats the space + // as part of the name -- could enable HTTP desync / request smuggling. + auto table = HttpHeaderTable::Builder().build(); + HttpHeaders headers(*table); + + // Space before the colon. + { + auto input = kj::heapString( + "POST /some/path HTTP/1.1\r\n" + "Host: example.com\r\n" + "Content-Length : 0\r\n" + "\r\n"); + + auto protocolError = headers.tryParseRequest(input).get<HttpHeaders::ProtocolError>(); + + KJ_EXPECT(protocolError.statusCode == 400, protocolError.statusCode); + KJ_EXPECT(protocolError.description == "The headers sent by your client are not valid.", + protocolError.description); + } + + // Tab before the colon. + { + auto input = kj::heapString( + "POST /some/path HTTP/1.1\r\n" + "Host: example.com\r\n" + "Content-Length\t: 0\r\n" + "\r\n"); + + auto protocolError = headers.tryParseRequest(input).get<HttpHeaders::ProtocolError>(); + + KJ_EXPECT(protocolError.statusCode == 400, protocolError.statusCode); + } + + // Whitespace *after* the colon (i.e. before the value) is still allowed and stripped. + { + auto input = kj::heapString( + "POST /some/path HTTP/1.1\r\n" + "Host: example.com\r\n" + "Content-Length: 123\r\n" + "\r\n"); + + auto result = headers.tryParseRequest(input).get<HttpHeaders::Request>(); + KJ_EXPECT(result.method == HttpMethod::POST); + KJ_EXPECT(KJ_ASSERT_NONNULL(headers.get(HttpHeaderId::CONTENT_LENGTH)) == "123"); + } +} + +KJ_TEST("HttpHeaders reject obsolete line folding") { + // RFC 9112 section 7.1.4 deprecates line folding and allows rejecting it with 400 (Bad Request). + // Folding has historically been a source of HTTP desync when peers disagree about whether a + // folded line is a continuation or a new header, so KJ rejects it. + auto table = HttpHeaderTable::Builder().build(); + + // Folded value with a leading space on the continuation line. + { + HttpHeaders headers(*table); + auto input = kj::heapString( + "POST /some/path HTTP/1.1\r\n" + "Host: example.com\r\n" + "Some-Header: a really long\r\n" + " header value\r\n" + "\r\n"); + + auto protocolError = headers.tryParseRequest(input).get<HttpHeaders::ProtocolError>(); + KJ_EXPECT(protocolError.statusCode == 400, protocolError.statusCode); + } + + // Folded value with a leading tab on the continuation line. + { + HttpHeaders headers(*table); + auto input = kj::heapString( + "POST /some/path HTTP/1.1\r\n" + "Host: example.com\r\n" + "Some-Header: a really long\r\n" + "\theader value\r\n" + "\r\n"); + + auto protocolError = headers.tryParseRequest(input).get<HttpHeaders::ProtocolError>(); + KJ_EXPECT(protocolError.statusCode == 400, protocolError.statusCode); + } + + // Folding used to smuggle what looks like a separate header. + { + HttpHeaders headers(*table); + auto input = kj::heapString( + "HTTP/1.1 200 OK\r\n" + "Host: example.com\r\n" + "Some-Header: value\r\n" + " Smuggled-Header: value\r\n" + "\r\n"); + + auto protocolError = headers.tryParseResponse(input).get<HttpHeaders::ProtocolError>(); + KJ_EXPECT(protocolError.statusCode == 502, protocolError.statusCode); + } +} + KJ_TEST("HttpHeaders require valid HttpHeaderTable") { const auto ERROR_MESSAGE = "HttpHeaders object was constructed from HttpHeaderTable " @@ -373,6 +471,52 @@ KJ_EXPECT_THROW_MESSAGE("invalid header value", headers.add("Valid-Name", "in\nvalid")); } +KJ_TEST("HttpHeaders serialization validation") { + // The serialization functions must reject request URLs and status texts containing characters + // that would allow HTTP desync / request smuggling (e.g. when http-over-capnp forwards untrusted + // metadata to a plain-HTTP connection). See GHSL-2026-146. + auto table = HttpHeaderTable::Builder().build(); + HttpHeaders headers(*table); + + // Valid values serialize fine. + KJ_EXPECT(headers.serializeRequest(HttpMethod::GET, "/some/path?query=1") == + "GET /some/path?query=1 HTTP/1.1\r\n\r\n"); + KJ_EXPECT(headers.serializeResponse(200, "OK") == + "HTTP/1.1 200 OK\r\n\r\n"); + + // A CRLF in the URL could inject headers or an entire second request. + KJ_EXPECT_THROW_MESSAGE("invalid request URL", + headers.serializeRequest(HttpMethod::GET, "/foo\r\nX-Injected: 1")); + + // A bare LF is equally dangerous. + KJ_EXPECT_THROW_MESSAGE("invalid request URL", + headers.serializeRequest(HttpMethod::GET, "/foo\nbar")); + + // A space in the request-target would introduce an extra token into the request line. + KJ_EXPECT_THROW_MESSAGE("invalid request URL", + headers.serializeRequest(HttpMethod::GET, "/foo bar")); + + // A NUL byte terminates the C string and could truncate the request line. + KJ_EXPECT_THROW_MESSAGE("invalid request URL", + headers.serializeRequest(HttpMethod::GET, kj::StringPtr("/foo\0bar", 8))); + + // CONNECT authority is validated too. + KJ_EXPECT_THROW_MESSAGE("invalid request URL", + headers.serializeConnectRequest("example.com:443\r\nX-Injected: 1")); + + // A CRLF in the status text could inject headers into the response. + KJ_EXPECT_THROW_MESSAGE("invalid status text", + headers.serializeResponse(200, "OK\r\nX-Injected: 1")); + + // A bare LF is equally dangerous. + KJ_EXPECT_THROW_MESSAGE("invalid status text", + headers.serializeResponse(200, "OK\nfoo")); + + // Status text may legitimately contain spaces. + KJ_EXPECT(headers.serializeResponse(418, "I'm a teapot") == + "HTTP/1.1 418 I'm a teapot\r\n\r\n"); +} + KJ_TEST("HttpHeaders Set-Cookie handling") { HttpHeaderTable::Builder builder; auto hCookie = builder.add("Cookie"); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/kj/compat/http.c++ new/capnproto-c++-1.5.0/src/kj/compat/http.c++ --- old/capnproto-c++-1.4.0/src/kj/compat/http.c++ 2026-03-12 14:11:58.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/kj/compat/http.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -499,6 +499,40 @@ kj::encodeCEscape(value)); } +static bool isValidRequestUrl(kj::StringPtr url) { + // The request-target (URL) appears in the request line as `METHOD SP request-target SP version`. + // It must not contain whitespace (which would introduce extra tokens into the request line) nor + // any control characters (in particular CR or LF, which would allow injecting additional headers + // or entire requests). We reject any byte <= 0x20 (this covers space, tab, CR, LF, and NUL) as + // well as 0x7f (DEL). Bytes >= 0x80 are permitted since some callers pass pre-encoded or + // non-ASCII targets; these cannot cause desync. + for (char c: url) { + if (static_cast<byte>(c) <= 0x20 || static_cast<byte>(c) == 0x7f) { + return false; + } + } + return true; +} + +static void requireValidRequestUrl(kj::StringPtr url) { + KJ_REQUIRE(isValidRequestUrl(url), "invalid request URL", kj::encodeCEscape(url)); +} + +static bool isValidStatusText(kj::StringPtr text) { + // The status text (reason-phrase) appears at the end of the response status line. It must not + // contain CR, LF, or NUL, which would allow injecting additional headers or corrupt the framing. + for (char c: text) { + if (c == '\0' || c == '\r' || c == '\n') { + return false; + } + } + return true; +} + +static void requireValidStatusText(kj::StringPtr text) { + KJ_REQUIRE(isValidStatusText(text), "invalid status text", kj::encodeCEscape(text)); +} + static const char* BUILTIN_HEADER_NAMES[] = { // Indexed by header ID, which includes connection headers, so we include those names too. #define HEADER_NAME(id, name) name, @@ -824,7 +858,10 @@ } } -static kj::StringPtr consumeLine(char*& ptr) { +static kj::StringPtr consumeLine(char*& ptr, bool* sawFolding = nullptr) { + // If `sawFolding` is non-null, it is set to true if an obsolete "line folding" continuation was + // encountered. Callers that parse header fields use this to reject the message (see + // parseHeaders()). char* start = skipSpace(ptr); char* p = start; @@ -843,6 +880,7 @@ // a space was treated as a continuation of the previous line. The behavior should be // the same as if the \r\n were replaced with spaces, so let's do that here to prevent // confusion later. + if (sawFolding != nullptr) *sawFolding = true; *end = ' '; p[-1] = ' '; break; @@ -861,6 +899,7 @@ // a space was treated as a continuation of the previous line. The behavior should be // the same as if the \n were replaced with spaces, so let's do that here to prevent // confusion later. + if (sawFolding != nullptr) *sawFolding = true; *end = ' '; break; } @@ -886,7 +925,11 @@ while (HTTP_HEADER_NAME_CHARS.contains(*p)) ++p; char* end = p; - p = skipSpace(p); + // Note: We intentionally do NOT skip whitespace between the header name and the colon. RFC 9112 + // section 5.1 requires that no whitespace appear there, and that a message with such whitespace + // be rejected with 400 (Bad Request). Historically some HTTP implementations treated the + // trailing whitespace as part of the header name, which -- if a message passed through both such + // an implementation and a lenient one -- could lead to HTTP desync / request smuggling. if (end == start || *p != ':') return nullptr; ++p; @@ -1030,7 +1073,15 @@ bool HttpHeaders::parseHeaders(char* ptr, char* end) { while (*ptr != '\0') { KJ_IF_MAYBE(name, consumeHeaderName(ptr)) { - kj::StringPtr line = consumeLine(ptr); + bool sawFolding = false; + kj::StringPtr line = consumeLine(ptr, &sawFolding); + if (sawFolding) { + // Obsolete line folding (a continuation line beginning with whitespace). RFC 9112 section + // 7.1.4 says a server MUST either reject such a message with 400 (Bad Request) or replace + // the folding with spaces. Folding is never used legitimately and has historically been a + // source of HTTP desync, so we reject. + return false; + } addNoCheck(*name, line); } else { return false; @@ -1045,18 +1096,22 @@ kj::String HttpHeaders::serializeRequest( HttpMethod method, kj::StringPtr url, kj::ArrayPtr<const kj::StringPtr> connectionHeaders) const { + requireValidRequestUrl(url); return serialize(kj::toCharSequence(method), url, kj::StringPtr("HTTP/1.1"), connectionHeaders); } kj::String HttpHeaders::serializeConnectRequest( kj::StringPtr authority, kj::ArrayPtr<const kj::StringPtr> connectionHeaders) const { + requireValidRequestUrl(authority); return serialize("CONNECT"_kj, authority, kj::StringPtr("HTTP/1.1"), connectionHeaders); } kj::String HttpHeaders::serializeResponse( uint statusCode, kj::StringPtr statusText, kj::ArrayPtr<const kj::StringPtr> connectionHeaders) const { + requireValidStatusText(statusText); + auto statusCodeStr = kj::toCharSequence(statusCode); return serialize(kj::StringPtr("HTTP/1.1"), statusCodeStr, statusText, connectionHeaders); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/kj/compat/tls.c++ new/capnproto-c++-1.5.0/src/kj/compat/tls.c++ --- old/capnproto-c++-1.4.0/src/kj/compat/tls.c++ 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/kj/compat/tls.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -1125,13 +1125,14 @@ KJ_FAIL_REQUIRE("client did not provide a certificate") { return nullptr; } } - X509_NAME* subj = X509_get_subject_name(reinterpret_cast<X509*>(cert)); + const X509_NAME* subj = X509_get_subject_name(reinterpret_cast<X509*>(cert)); - int index = X509_NAME_get_index_by_NID(subj, NID_commonName, -1); + // Cast away const for compatibility with older OpenSSL versions + int index = X509_NAME_get_index_by_NID(const_cast<X509_NAME*>(subj), NID_commonName, -1); KJ_ASSERT(index != -1, "certificate has no common name?"); - X509_NAME_ENTRY* entry = X509_NAME_get_entry(subj, index); + const X509_NAME_ENTRY* entry = X509_NAME_get_entry(subj, index); KJ_ASSERT(entry != nullptr); - ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry); + const ASN1_STRING* data = X509_NAME_ENTRY_get_data(entry); KJ_ASSERT(data != nullptr); unsigned char* out = nullptr; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/kj/debug-test.c++ new/capnproto-c++-1.5.0/src/kj/debug-test.c++ --- old/capnproto-c++-1.4.0/src/kj/debug-test.c++ 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/kj/debug-test.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -46,6 +46,13 @@ class MockException {}; +struct ThrowOnStringify {}; +kj::String KJ_STRINGIFY(ThrowOnStringify) { + // Simulates a parameter whose stringification throws an exception (e.g. reading a corrupt + // Cap'n Proto field). Used to test that KJ_CONTEXT handles this gracefully. + KJ_FAIL_ASSERT("stringification intentionally failed") { return nullptr; } +} + class MockExceptionCallback: public ExceptionCallback { public: ~MockExceptionCallback() {} @@ -463,6 +470,25 @@ )); } +KJ_TEST("KJ_CONTEXT parameter that throws during evaluation is dropped") { + // Regression test: previously, if evaluating a KJ_CONTEXT parameter threw an exception (e.g. + // stringifying a corrupt value), that exception would re-enter the same Context callback, which + // would re-evaluate the parameter, throwing again, ... leading to infinite recursion and a + // stack overflow. Now, if evaluating the context throws, the context is simply dropped and the + // original exception propagates normally. + auto exception = kj::runCatchingExceptions([&]() { + KJ_CONTEXT("bad context", ThrowOnStringify()); + KJ_FAIL_ASSERT("the real error") { break; } + }); + + auto& e = KJ_ASSERT_NONNULL(exception); + auto text = kj::str(e); + // The real error still propagates... + KJ_EXPECT(strstr(text.cStr(), "the real error") != nullptr, text); + // ...but the context that failed to evaluate was dropped. + KJ_EXPECT(strstr(text.cStr(), "bad context") == nullptr, text); +} + KJ_TEST("magic assert stringification") { { auto exception = KJ_ASSERT_NONNULL(kj::runCatchingExceptions([&]() { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/kj/debug.c++ new/capnproto-c++-1.5.0/src/kj/debug.c++ --- old/capnproto-c++-1.4.0/src/kj/debug.c++ 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/kj/debug.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -441,32 +441,63 @@ Debug::Context::Context(): logged(false) {} Debug::Context::~Context() noexcept(false) {} -Debug::Context::Value Debug::Context::ensureInitialized() { +Maybe<Debug::Context::Value> Debug::Context::ensureInitialized() { + if (evaluationFailed || evaluating) { + // Either a previous evaluation attempt threw, or we are being called re-entrantly from within + // evaluate() itself (because evaluating the context parameters threw an exception, which + // re-entered the exception callback chain -- see the comments in debug.h). In either case we + // must drop the context to avoid infinite recursion / stack overflow. + evaluationFailed = true; + return nullptr; + } + + if (value == nullptr) { + // Evaluating the context parameters could itself throw (e.g. if a parameter's stringification + // throws). We must catch such exceptions here; otherwise the thrown exception would re-enter + // this same Context callback, calling ensureInitialized() again, leading to infinite recursion + // and eventually a stack overflow. If evaluation throws, `value` won't be set and we'll drop + // the context below. + evaluating = true; + KJ_DEFER(evaluating = false); + auto e = kj::runCatchingExceptions([&]() { + value = evaluate(); + }); + if (e != nullptr) { + // In -fno-exceptions mode, a recoverable exception may have been thrown but we still + // proceeded to assing `value`. Discard it here to match the behavior if exceptions were + // enabled. + value = nullptr; + } + } + KJ_IF_MAYBE(v, value) { return Value(v->file, v->line, heapString(v->description)); } else { - Value result = evaluate(); - value = Value(result.file, result.line, heapString(result.description)); - return result; + // Our attempt to call `evaluate()` must have thrown an exception. Drop the context. + evaluationFailed = true; + return nullptr; } } void Debug::Context::onRecoverableException(Exception&& exception) { - Value v = ensureInitialized(); - exception.wrapContext(v.file, v.line, mv(v.description)); + KJ_IF_MAYBE(v, ensureInitialized()) { + exception.wrapContext(v->file, v->line, mv(v->description)); + } next.onRecoverableException(kj::mv(exception)); } void Debug::Context::onFatalException(Exception&& exception) { - Value v = ensureInitialized(); - exception.wrapContext(v.file, v.line, mv(v.description)); + KJ_IF_MAYBE(v, ensureInitialized()) { + exception.wrapContext(v->file, v->line, mv(v->description)); + } next.onFatalException(kj::mv(exception)); } void Debug::Context::logMessage(LogSeverity severity, const char* file, int line, int contextDepth, String&& text) { if (!logged) { - Value v = ensureInitialized(); - next.logMessage(LogSeverity::INFO, trimSourceFilename(v.file).cStr(), v.line, 0, - str("context: ", mv(v.description), '\n')); + KJ_IF_MAYBE(v, ensureInitialized()) { + next.logMessage(LogSeverity::INFO, trimSourceFilename(v->file).cStr(), v->line, 0, + str("context: ", mv(v->description), '\n')); + } logged = true; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/kj/debug.h new/capnproto-c++-1.5.0/src/kj/debug.h --- old/capnproto-c++-1.4.0/src/kj/debug.h 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/kj/debug.h 2026-07-10 00:33:53.000000000 +0200 @@ -488,9 +488,20 @@ private: bool logged; + bool evaluationFailed = false; + // Set true if an attempt to evaluate() the context threw an exception. Once this happens we + // permanently drop the context. This prevents re-evaluation, which could throw again, leading + // to infinite recursion: evaluate() throwing an exception re-enters the exception callback + // chain, which includes this very Context, so onFatalException()/onRecoverableException() would + // be invoked recursively. + bool evaluating = false; + // Set true while evaluate() is running, so that we can detect the re-entrant case described + // above and bail out. Maybe<Value> value; - Value ensureInitialized(); + Maybe<Value> ensureInitialized(); + // Evaluates the context (if not already evaluated) and returns its value. Returns kj::none if + // evaluating the context threw an exception, in which case the context should be dropped. }; template <typename Func> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/kj/exception-test.c++ new/capnproto-c++-1.5.0/src/kj/exception-test.c++ --- old/capnproto-c++-1.4.0/src/kj/exception-test.c++ 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/kj/exception-test.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -294,6 +294,16 @@ {8, 7, 6, 5, 6, 7, 8, 7, 8}); } +KJ_TEST("KJ_STRINGIFY(Exception::Type) handles out-of-range values") { + KJ_EXPECT(kj::str(kj::Exception::Type::FAILED) == "failed"); + KJ_EXPECT(kj::str(kj::Exception::Type::OVERLOADED) == "overloaded"); + KJ_EXPECT(kj::str(kj::Exception::Type::DISCONNECTED) == "disconnected"); + KJ_EXPECT(kj::str(kj::Exception::Type::UNIMPLEMENTED) == "unimplemented"); + + KJ_EXPECT(kj::str(static_cast<kj::Exception::Type>(99)) == "failed"); + KJ_EXPECT(kj::str(static_cast<kj::Exception::Type>(0xffff)) == "failed"); +} + } // namespace } // namespace _ (private) } // namespace kj diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/kj/exception.c++ new/capnproto-c++-1.5.0/src/kj/exception.c++ --- old/capnproto-c++-1.4.0/src/kj/exception.c++ 2026-03-12 14:11:58.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/kj/exception.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -758,7 +758,11 @@ "unimplemented" }; - return TYPE_STRINGS[static_cast<uint>(type)]; + uint i = static_cast<uint>(type); + if (i >= kj::size(TYPE_STRINGS)) { + i = 0; + } + return TYPE_STRINGS[i]; } String KJ_STRINGIFY(const Exception& e) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/kj/filesystem-disk-win32.c++ new/capnproto-c++-1.5.0/src/kj/filesystem-disk-win32.c++ --- old/capnproto-c++-1.4.0/src/kj/filesystem-disk-win32.c++ 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/kj/filesystem-disk-win32.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -380,6 +380,11 @@ case ERROR_CALL_NOT_IMPLEMENTED: // Probably WINE. break; + case ERROR_INVALID_FUNCTION: + // Also WINE: NtQueryInformationFile returns STATUS_NOT_IMPLEMENTED for + // unsupported FileInfoClass values, which RtlNtStatusToDosError maps to + // ERROR_INVALID_FUNCTION (not ERROR_CALL_NOT_IMPLEMENTED). + break; case ERROR_INVALID_PARAMETER: // Probably VeraCrypt. See https://github.com/capnproto/capnproto/issues/2176 break; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/kj/filesystem-test.c++ new/capnproto-c++-1.5.0/src/kj/filesystem-test.c++ --- old/capnproto-c++-1.4.0/src/kj/filesystem-test.c++ 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/kj/filesystem-test.c++ 2026-07-10 00:33:53.000000000 +0200 @@ -155,7 +155,7 @@ KJ_EXPECT_THROW_MESSAGE("root path has no parent", Path(nullptr).parent()); } -constexpr kj::ArrayPtr<const wchar_t> operator "" _a(const wchar_t* str, size_t n) { +constexpr kj::ArrayPtr<const wchar_t> operator ""_a(const wchar_t* str, size_t n) { return { str, n }; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/capnproto-c++-1.4.0/src/kj/string.h new/capnproto-c++-1.5.0/src/kj/string.h --- old/capnproto-c++-1.4.0/src/kj/string.h 2025-12-16 23:16:27.000000000 +0100 +++ new/capnproto-c++-1.5.0/src/kj/string.h 2026-07-10 00:33:53.000000000 +0200 @@ -37,7 +37,7 @@ class StringTree; // string-tree.h } -constexpr kj::StringPtr operator "" _kj(const char* str, size_t n); +constexpr kj::StringPtr operator ""_kj(const char* str, size_t n); // You can append _kj to a string literal to make its type be StringPtr. There are a few cases // where you must do this for correctness: // - When you want to declare a constexpr StringPtr. Without _kj, this is a compile error. @@ -53,7 +53,7 @@ // string literal vs. one with _kj (assuming the compiler is able to optimize away strlen() on a // string literal). -constexpr kj::LiteralStringConst operator "" _kjc(const char* str, size_t n); +constexpr kj::LiteralStringConst operator ""_kjc(const char* str, size_t n); namespace kj { @@ -176,7 +176,7 @@ private: inline explicit constexpr StringPtr(ArrayPtr<const char> content): content(content) {} - friend constexpr StringPtr (::operator "" _kj)(const char* str, size_t n); + friend constexpr StringPtr (::operator ""_kj)(const char* str, size_t n); friend class LiteralStringConst; ArrayPtr<const char> content; @@ -222,7 +222,7 @@ private: inline explicit constexpr LiteralStringConst(ArrayPtr<const char> content): StringPtr(content) {} - friend constexpr LiteralStringConst (::operator "" _kjc)(const char* str, size_t n); + friend constexpr LiteralStringConst (::operator ""_kjc)(const char* str, size_t n); }; // ======================================================================================= @@ -924,11 +924,11 @@ } // namespace kj -constexpr kj::StringPtr operator "" _kj(const char* str, size_t n) { +constexpr kj::StringPtr operator ""_kj(const char* str, size_t n) { return kj::StringPtr(kj::ArrayPtr<const char>(str, n + 1)); }; -constexpr kj::LiteralStringConst operator "" _kjc(const char* str, size_t n) { +constexpr kj::LiteralStringConst operator ""_kjc(const char* str, size_t n) { return kj::LiteralStringConst(kj::ArrayPtr<const char>(str, n + 1)); };
