Skip to content

Commit

Permalink
Fix asio (+add tests).
Browse files Browse the repository at this point in the history
There appears to be an oversight in `async_result<>::initiate()`: it accepts
all its arguments by forwarding references, it may capture a value on a stack
of an asynchronous function, which would then go out of scope before the I/O
really gets initiated. For example,

    co_await stream.async_handshake(
        ssl::stream_base::client,
        corral::asio_awaitable)

(see the new `beast-https-client` test) triggers a stack-use-after-free,
as it captures a reference to `async_handshake()`s copy of `stream_base::client`.

Since asio appears to favor lightweight wrappers around any heavy objects
(like `asio::buffer`), it looks like we can circumvent this by taking all
arguments to initiation function by value.
  • Loading branch information
dprokoptsev committed Aug 7, 2024
1 parent d125962 commit 77647fa
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 14 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,27 @@ jobs:

include:
- variant: gcc-12
deps: g++-12
deps: g++-12 libssl-dev
cc: gcc-12
cxx: g++-12

- variant: gcc-13
deps: g++-13
deps: g++-13 libssl-dev
cc: gcc-13
cxx: g++-13

- variant: gcc-14
deps: g++-14
deps: g++-14 libssl-dev
cc: gcc-14
cxx: g++-14

- variant: clang-15
deps: clang-15 g++-13 libstdc++-14-dev- libgcc-14-dev-
deps: clang-15 g++-13 libstdc++-14-dev- libgcc-14-dev- libssl-dev
cc: clang-15
cxx: clang++-15

- variant: clang-16
deps: clang-16
deps: clang-16 libssl-dev
cc: clang-16
cxx: clang++-16

Expand Down
14 changes: 11 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ add_library(corral INTERFACE
target_include_directories(corral INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

option(CORRAL_TEST_RUNNER "Use this wrapper for running tests" "")
option(CORRAL_XBUILD "Do not pull any features unavailable while cross-compiling" "")

option(
CORRAL_CATCH2
Expand Down Expand Up @@ -89,14 +90,21 @@ if(NOT ("${CORRAL_BOOST}" STREQUAL ""))
FetchContent_MakeAvailable(Boost)
else()
set(Boost_INCLUDE_DIR "${CORRAL_BOOST}")
find_package(
Boost 1.77.0 REQUIRED
)
find_package(Boost 1.77.0 REQUIRED)
endif()

add_executable(corral_asio_test test/asio_test.cc test/helpers.h)
target_link_libraries(corral_asio_test PRIVATE corral Catch2::Catch2)
target_compile_definitions(corral_asio_test PRIVATE CATCH_CONFIG_MAIN)

if("${CORRAL_TEST_RUNNER}" STREQUAL "")
find_package(OpenSSL)
if(OpenSSL_FOUND)
target_compile_definitions(corral_asio_test PRIVATE CORRAL_HAVE_OPENSSL)
target_link_libraries(corral_asio_test PRIVATE OpenSSL::SSL OpenSSL::Crypto)
endif()
endif()

add_test(NAME corral_asio_test COMMAND ${CORRAL_TEST_RUNNER} corral_asio_test)
endif()

Expand Down
5 changes: 3 additions & 2 deletions corral/asio.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
//
// SPDX-License-Identifier: MIT

#pragma once

#include <chrono>

Expand Down Expand Up @@ -280,10 +281,10 @@ class async_result<::corral::detail::asio_awaitable_t<Executor, ThrowOnError>,
static auto initiate(
Init&& init,
::corral::detail::asio_awaitable_t<Executor, ThrowOnError>,
Args&&... args) {
Args... args) {
return ::corral::detail::AsioAwaitable<ThrowOnError, Init,
std::tuple<Args...>, Ret...>(
std::forward<Init>(init), std::forward<Args>(args)...);
std::forward<Init>(init), std::move(args)...);
}
};

Expand Down
190 changes: 186 additions & 4 deletions test/asio_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,21 @@
//
// SPDX-License-Identifier: MIT

#include "../corral/asio.h"

#include <chrono>

#include <boost/asio.hpp>
#include <boost/beast.hpp>

#ifdef CORRAL_HAVE_OPENSSL
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>

#include <boost/asio/ssl.hpp>
#include <boost/beast/ssl.hpp>
#endif

#include "../corral/asio.h"
#include "../corral/corral.h"
#include "config.h"
#include "helpers.h"
Expand All @@ -40,16 +49,20 @@ namespace {
using Clock = std::chrono::high_resolution_clock;
using namespace std::chrono_literals;

namespace asio = boost::asio;
using tcp = asio::ip::tcp;


CORRAL_TEST_CASE("asio-smoke", "[asio]") {
boost::asio::deadline_timer t(io);
asio::deadline_timer t(io);
t.expires_from_now(boost::posix_time::millisec(100));
auto from = Clock::now();
co_await t.async_wait(corral::asio_awaitable);
CATCH_CHECK(Clock::now() - from >= 90ms);
}

CORRAL_TEST_CASE("asio-anyof", "[asio]") {
boost::asio::deadline_timer t1(io), t2(io);
asio::deadline_timer t1(io), t2(io);
t1.expires_from_now(boost::posix_time::millisec(100));
t2.expires_from_now(boost::posix_time::millisec(500));
auto from = Clock::now();
Expand All @@ -65,4 +78,173 @@ CORRAL_TEST_CASE("asio-anyof", "[asio]") {
CATCH_CHECK(!s2);
}

CORRAL_TEST_CASE("asio-socket-smoke", "[asio]") {
tcp::acceptor acceptor(io, tcp::endpoint(tcp::v4(), 0));
co_await corral::allOf(
[&]() -> corral::Task<> {
tcp::socket sock(io);
co_await acceptor.async_accept(sock, corral::asio_awaitable);
co_await asio::async_write(sock, asio::buffer("hello, world"),
corral::asio_awaitable);
},
[&]() -> corral::Task<> {
tcp::socket sock(io);
co_await sock.async_connect(acceptor.local_endpoint(),
corral::asio_awaitable);
char buf[12];
size_t n = co_await asio::async_read(sock, asio::buffer(buf),
corral::asio_awaitable);
CATCH_REQUIRE(std::string(buf, n) == "hello, world");
});
}


namespace beast = boost::beast;
namespace http = beast::http;

CORRAL_TEST_CASE("beast-http", "[asio]") {
tcp::acceptor acceptor(io, tcp::endpoint(tcp::v4(), 0));
co_await corral::allOf(
[&]() -> corral::Task<> {
tcp::socket sock(io);
co_await acceptor.async_accept(sock, corral::asio_awaitable);

beast::flat_buffer buffer;
http::request<http::empty_body> req;
co_await http::async_read(sock, buffer, req,
corral::asio_awaitable);

http::response<http::string_body> res;
res.result(http::status::ok);
res.set(http::field::content_type, "text/plain");
res.keep_alive(false);
res.body() = "hello, world";
res.prepare_payload();
co_await http::async_write(sock, res, corral::asio_awaitable);
},

[&]() -> corral::Task<> {
tcp::socket sock(io);
co_await sock.async_connect(acceptor.local_endpoint(),
corral::asio_awaitable);

http::request<http::empty_body> req(http::verb::get, "/", 11);
req.set(http::field::host, "example.org");
req.prepare_payload();
co_await http::async_write(sock, req, corral::asio_awaitable);

beast::flat_buffer buffer;
http::response<http::dynamic_body> res;
co_await http::async_read(sock, buffer, res,
corral::asio_awaitable);

CATCH_CHECK(res.result() == http::status::ok);
std::string body = beast::buffers_to_string(res.body().data());
CATCH_CHECK(body == "hello, world");
});
}

#ifdef CORRAL_HAVE_OPENSSL
namespace ssl = asio::ssl;

std::pair<std::string /*cert*/, std::string /*privateKey*/>
generateSelfSignedCert() {
EVP_PKEY* pkey = EVP_PKEY_new();
RSA* rsa = RSA_generate_key(2048, RSA_F4, nullptr, nullptr);
EVP_PKEY_assign_RSA(pkey, rsa);

X509* x509 = X509_new();
ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), 31536000L);

X509_set_pubkey(x509, pkey);

X509_NAME* name = X509_get_subject_name(x509);
X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*) "US",
-1, -1, 0);
X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC,
(unsigned char*) "Boost.Beast", -1, -1, 0);
X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC,
(unsigned char*) "localhost", -1, -1, 0);

X509_set_issuer_name(x509, name);

X509_sign(x509, pkey, EVP_sha1());

BIO* bio = BIO_new(BIO_s_mem());
PEM_write_bio_X509(bio, x509);
BUF_MEM* bptr;
BIO_get_mem_ptr(bio, &bptr);

std::string cert(bptr->data, bptr->length);
BIO_free_all(bio);

bio = BIO_new(BIO_s_mem());
PEM_write_bio_PrivateKey(bio, pkey, nullptr, nullptr, 0, nullptr, nullptr);
BIO_get_mem_ptr(bio, &bptr);

std::string key(bptr->data, bptr->length);
BIO_free_all(bio);

X509_free(x509);
EVP_PKEY_free(pkey);

return {cert, key};
}

CORRAL_TEST_CASE("beast-https", "[asio]") {
ssl::context sslCtx(ssl::context::tlsv12);
auto [cert, key] = generateSelfSignedCert();
sslCtx.use_certificate_chain(asio::buffer(cert));
sslCtx.use_private_key(asio::buffer(key), ssl::context::file_format::pem);

tcp::acceptor acceptor(io, tcp::endpoint(tcp::v4(), 0));
co_await corral::allOf(
[&]() -> corral::Task<> {
tcp::socket sock(io);
co_await acceptor.async_accept(sock, corral::asio_awaitable);
beast::ssl_stream<beast::tcp_stream> stream(std::move(sock),
sslCtx);
co_await stream.async_handshake(ssl::stream_base::server,
corral::asio_awaitable);

beast::flat_buffer buffer;
http::request<http::empty_body> req;
co_await http::async_read(stream, buffer, req,
corral::asio_awaitable);

http::response<http::string_body> res;
res.result(http::status::ok);
res.set(http::field::content_type, "text/plain");
res.keep_alive(false);
res.body() = "hello, world";
res.prepare_payload();
co_await http::async_write(stream, res, corral::asio_awaitable);
},

[&]() -> corral::Task<> {
beast::ssl_stream<beast::tcp_stream> stream(io, sslCtx);
co_await beast::get_lowest_layer(stream).async_connect(
acceptor.local_endpoint(), corral::asio_awaitable);
co_await stream.async_handshake(ssl::stream_base::client,
corral::asio_awaitable);

http::request<http::empty_body> req(http::verb::get, "/", 11);
req.set(http::field::host, "example.org");
req.prepare_payload();
co_await http::async_write(stream, req, corral::asio_awaitable);

beast::flat_buffer buffer;
http::response<http::dynamic_body> res;
co_await http::async_read(stream, buffer, res,
corral::asio_awaitable);

CATCH_CHECK(res.result() == http::status::ok);
std::string body = beast::buffers_to_string(res.body().data());
CATCH_CHECK(body == "hello, world");
});
}
#endif

} // namespace

0 comments on commit 77647fa

Please sign in to comment.