Mirror of BoringSSL (grpc依赖) https://boringssl.googlesource.com/boringssl
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

718 lines
21 KiB

/* Copyright (c) 2018, Google Inc.
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#include "handshake_util.h"
#include <assert.h>
#if defined(HANDSHAKER_SUPPORTED)
#include <errno.h>
#include <fcntl.h>
#include <spawn.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
#include <functional>
#include <map>
#include <vector>
#include "async_bio.h"
#include "packeted_bio.h"
#include "test_config.h"
#include "test_state.h"
#include <openssl/bytestring.h>
#include <openssl/ssl.h>
using namespace bssl;
bool RetryAsync(SSL *ssl, int ret) {
const TestConfig *config = GetTestConfig(ssl);
TestState *test_state = GetTestState(ssl);
if (ret >= 0) {
return false;
}
int ssl_err = SSL_get_error(ssl, ret);
if (ssl_err == SSL_ERROR_WANT_RENEGOTIATE && config->renegotiate_explicit) {
test_state->explicit_renegotiates++;
return SSL_renegotiate(ssl);
}
if (test_state->quic_transport && ssl_err == SSL_ERROR_WANT_READ) {
return test_state->quic_transport->ReadHandshake();
}
if (!config->async) {
// Only asynchronous tests should trigger other retries.
return false;
}
if (test_state->packeted_bio != nullptr &&
PacketedBioAdvanceClock(test_state->packeted_bio)) {
// The DTLS retransmit logic silently ignores write failures. So the test
// may progress, allow writes through synchronously.
AsyncBioEnforceWriteQuota(test_state->async_bio, false);
int timeout_ret = DTLSv1_handle_timeout(ssl);
AsyncBioEnforceWriteQuota(test_state->async_bio, true);
if (timeout_ret < 0) {
fprintf(stderr, "Error retransmitting.\n");
return false;
}
return true;
}
// See if we needed to read or write more. If so, allow one byte through on
// the appropriate end to maximally stress the state machine.
switch (ssl_err) {
case SSL_ERROR_WANT_READ:
AsyncBioAllowRead(test_state->async_bio, 1);
return true;
case SSL_ERROR_WANT_WRITE:
AsyncBioAllowWrite(test_state->async_bio, 1);
return true;
case SSL_ERROR_WANT_X509_LOOKUP:
test_state->cert_ready = true;
return true;
case SSL_ERROR_PENDING_SESSION:
test_state->session = std::move(test_state->pending_session);
return true;
case SSL_ERROR_PENDING_CERTIFICATE:
test_state->early_callback_ready = true;
return true;
case SSL_ERROR_WANT_PRIVATE_KEY_OPERATION:
test_state->private_key_retries++;
return true;
case SSL_ERROR_WANT_CERTIFICATE_VERIFY:
test_state->custom_verify_ready = true;
return true;
default:
return false;
}
}
int CheckIdempotentError(const char *name, SSL *ssl,
std::function<int()> func) {
int ret = func();
int ssl_err = SSL_get_error(ssl, ret);
uint32_t err = ERR_peek_error();
if (ssl_err == SSL_ERROR_SSL || ssl_err == SSL_ERROR_ZERO_RETURN) {
int ret2 = func();
int ssl_err2 = SSL_get_error(ssl, ret2);
uint32_t err2 = ERR_peek_error();
if (ret != ret2 || ssl_err != ssl_err2 || err != err2) {
fprintf(stderr, "Repeating %s did not replay the error.\n", name);
char buf[256];
ERR_error_string_n(err, buf, sizeof(buf));
fprintf(stderr, "Wanted: %d %d %s\n", ret, ssl_err, buf);
ERR_error_string_n(err2, buf, sizeof(buf));
fprintf(stderr, "Got: %d %d %s\n", ret2, ssl_err2, buf);
// runner treats exit code 90 as always failing. Otherwise, it may
// accidentally consider the result an expected protocol failure.
exit(90);
}
}
return ret;
}
#if defined(HANDSHAKER_SUPPORTED)
// MoveBIOs moves the |BIO|s of |src| to |dst|. It is used for handoff.
static void MoveBIOs(SSL *dest, SSL *src) {
BIO *rbio = SSL_get_rbio(src);
BIO_up_ref(rbio);
SSL_set0_rbio(dest, rbio);
BIO *wbio = SSL_get_wbio(src);
BIO_up_ref(wbio);
SSL_set0_wbio(dest, wbio);
SSL_set0_rbio(src, nullptr);
SSL_set0_wbio(src, nullptr);
}
static bool HandoffReady(SSL *ssl, int ret) {
return ret < 0 && SSL_get_error(ssl, ret) == SSL_ERROR_HANDOFF;
}
static ssize_t read_eintr(int fd, void *out, size_t len) {
ssize_t ret;
do {
ret = read(fd, out, len);
} while (ret < 0 && errno == EINTR);
return ret;
}
static ssize_t write_eintr(int fd, const void *in, size_t len) {
ssize_t ret;
do {
ret = write(fd, in, len);
} while (ret < 0 && errno == EINTR);
return ret;
}
static ssize_t waitpid_eintr(pid_t pid, int *wstatus, int options) {
pid_t ret;
do {
ret = waitpid(pid, wstatus, options);
} while (ret < 0 && errno == EINTR);
return ret;
}
// Proxy relays data between |socket|, which is connected to the client, and the
// handshaker, which is connected to the numerically specified file descriptors,
// until the handshaker returns control.
static bool Proxy(BIO *socket, bool async, int control, int rfd, int wfd) {
for (;;) {
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(wfd, &rfds);
FD_SET(control, &rfds);
int fd_max = wfd > control ? wfd : control;
if (select(fd_max + 1, &rfds, nullptr, nullptr, nullptr) == -1) {
perror("select");
return false;
}
char buf[64];
ssize_t bytes;
if (FD_ISSET(wfd, &rfds) &&
(bytes = read_eintr(wfd, buf, sizeof(buf))) > 0) {
char *b = buf;
while (bytes) {
int written = BIO_write(socket, b, bytes);
if (!written) {
fprintf(stderr, "BIO_write wrote nothing\n");
return false;
}
if (written < 0) {
if (async) {
AsyncBioAllowWrite(socket, 1);
continue;
}
fprintf(stderr, "BIO_write failed\n");
return false;
}
b += written;
bytes -= written;
}
// Flush all pending data from the handshaker to the client before
// considering control messages.
continue;
}
if (!FD_ISSET(control, &rfds)) {
continue;
}
char msg;
if (read_eintr(control, &msg, 1) != 1) {
perror("read");
return false;
}
switch (msg) {
case kControlMsgDone:
return true;
case kControlMsgError:
return false;
case kControlMsgWantRead:
break;
default:
fprintf(stderr, "Unknown control message from handshaker: %c\n", msg);
return false;
}
auto proxy_data = [&](uint8_t *out, size_t len) -> bool {
if (async) {
AsyncBioAllowRead(socket, len);
}
while (len > 0) {
int bytes_read = BIO_read(socket, out, len);
if (bytes_read < 1) {
fprintf(stderr, "BIO_read failed\n");
return false;
}
ssize_t bytes_written = write_eintr(rfd, out, bytes_read);
if (bytes_written == -1) {
perror("write");
return false;
}
if (bytes_written != bytes_read) {
Fix the easy -Wformat-signedness errors. GCC has a warning that complains about even more type mismatches in printf. Some of these are a bit messy and will be fixed in separate CLs. This covers the easy ones. The .*s stuff is unfortunate, but printf has no size_t-clean string printer. ALPN protocol lengths are bound by uint8_t, so it doesn't really matter. The IPv6 printing one is obnoxious and arguably a false positive. It's really a C language flaw: all types smaller than int get converted to int when you do arithmetic. So something like this first doesn't overflow the shift because it computes over int, but then the result overall is stored as an int. uint8_t a, b; (a << 8) | b On the one hand, this fixes a "missing" cast to uint16_t before the shift. At the same time, the incorrect final type means passing it to %x, which expects unsigned int. The compiler has forgotten this value actually fits in uint16_t and flags a warning. Mitigate this by storing in a uint16_t first. The story doesn't quite end here. Arguments passed to variadic functions go through integer promotion[0], so the argument is still passed to snprintf as an int! But then va_arg allows for a signedness mismatch[1], provided the value is representable in both types. The combination means that %x, though actually paired with unsigned, also accept uint8_t and uint16_t, because those are guaranteed to promote to an int that meets [1]. GCC recognizes [1] applies here. (There's also PRI16x, but that's a bit tedious to use and, in glibc, is defined as plain "x" anyway.) [0] https://en.cppreference.com/w/c/language/conversion#Default_argument_promotions [1] https://en.cppreference.com/w/c/variadic/va_arg Bug: 450 Change-Id: Ic1d41356755a18ab922956dd2e07b560470341f4 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/50765 Reviewed-by: Adam Langley <agl@google.com> Commit-Queue: Adam Langley <agl@google.com>
3 years ago
fprintf(stderr, "short write (%zd of %d bytes)\n", bytes_written,
bytes_read);
return false;
}
len -= bytes_read;
out += bytes_read;
}
return true;
};
// Process one SSL record at a time. That way, we don't send the handshaker
// anything it doesn't want to process, e.g. early data.
uint8_t header[SSL3_RT_HEADER_LENGTH];
if (!proxy_data(header, sizeof(header))) {
return false;
}
if (header[1] != 3) {
fprintf(stderr, "bad header\n");
return false;
}
size_t remaining = (header[3] << 8) + header[4];
while (remaining > 0) {
uint8_t readbuf[64];
size_t len = remaining > sizeof(readbuf) ? sizeof(readbuf) : remaining;
if (!proxy_data(readbuf, len)) {
return false;
}
remaining -= len;
}
// The handshaker blocks on the control channel, so we have to signal
// it that the data have been written.
msg = kControlMsgWriteCompleted;
if (write_eintr(control, &msg, 1) != 1) {
perror("write");
return false;
}
}
}
class ScopedFD {
public:
ScopedFD() : fd_(-1) {}
explicit ScopedFD(int fd) : fd_(fd) {}
~ScopedFD() { Reset(); }
ScopedFD(ScopedFD &&other) { *this = std::move(other); }
ScopedFD &operator=(ScopedFD &&other) {
Reset(other.fd_);
other.fd_ = -1;
return *this;
}
int fd() const { return fd_; }
void Reset(int fd = -1) {
if (fd_ >= 0) {
close(fd_);
}
fd_ = fd;
}
private:
int fd_;
};
class ScopedProcess {
public:
ScopedProcess() : pid_(-1) {}
~ScopedProcess() { Reset(); }
ScopedProcess(ScopedProcess &&other) { *this = std::move(other); }
ScopedProcess &operator=(ScopedProcess &&other) {
Reset(other.pid_);
other.pid_ = -1;
return *this;
}
pid_t pid() const { return pid_; }
void Reset(pid_t pid = -1) {
if (pid_ >= 0) {
kill(pid_, SIGTERM);
int unused;
Wait(&unused);
}
pid_ = pid;
}
bool Wait(int *out_status) {
if (pid_ < 0) {
return false;
}
if (waitpid_eintr(pid_, out_status, 0) != pid_) {
return false;
}
pid_ = -1;
return true;
}
private:
pid_t pid_;
};
class FileActionsDestroyer {
public:
explicit FileActionsDestroyer(posix_spawn_file_actions_t *actions)
: actions_(actions) {}
~FileActionsDestroyer() { posix_spawn_file_actions_destroy(actions_); }
FileActionsDestroyer(const FileActionsDestroyer &) = delete;
FileActionsDestroyer &operator=(const FileActionsDestroyer &) = delete;
private:
posix_spawn_file_actions_t *actions_;
};
// StartHandshaker starts the handshaker process and, on success, returns a
// handle to the process in |*out|. It sets |*out_control| to a control pipe to
// the process. |map_fds| maps from desired fd number in the child process to
// the source fd in the calling process. |close_fds| is the list of additional
// fds to close, which may overlap with |map_fds|. Other than stdin, stdout, and
// stderr, the status of fds not listed in either set is undefined.
static bool StartHandshaker(ScopedProcess *out, ScopedFD *out_control,
const TestConfig *config, bool is_resume,
std::map<int, int> map_fds,
std::vector<int> close_fds) {
if (config->handshaker_path.empty()) {
fprintf(stderr, "no -handshaker-path specified\n");
return false;
}
struct stat dummy;
if (stat(config->handshaker_path.c_str(), &dummy) == -1) {
perror(config->handshaker_path.c_str());
return false;
}
std::vector<const char *> args;
args.push_back(config->handshaker_path.c_str());
static const char kResumeFlag[] = "-handshaker-resume";
if (is_resume) {
args.push_back(kResumeFlag);
}
// config->argv omits argv[0].
for (int j = 0; j < config->argc; ++j) {
args.push_back(config->argv[j]);
}
args.push_back(nullptr);
// A datagram socket guarantees that writes are all-or-nothing.
int control[2];
if (socketpair(AF_LOCAL, SOCK_DGRAM, 0, control) != 0) {
perror("socketpair");
return false;
}
ScopedFD scoped_control0(control[0]), scoped_control1(control[1]);
close_fds.push_back(control[0]);
map_fds[kFdControl] = control[1];
posix_spawn_file_actions_t actions;
if (posix_spawn_file_actions_init(&actions) != 0) {
return false;
}
FileActionsDestroyer actions_destroyer(&actions);
for (int fd : close_fds) {
if (posix_spawn_file_actions_addclose(&actions, fd) != 0) {
return false;
}
}
if (!map_fds.empty()) {
int max_fd = STDERR_FILENO;
for (const auto &pair : map_fds) {
max_fd = std::max(max_fd, pair.first);
max_fd = std::max(max_fd, pair.second);
}
// |map_fds| may contain cycles, so make a copy of all the source fds.
// |posix_spawn| can only use |dup2|, not |dup|, so we assume |max_fd| is
// the last fd we care about inheriting. |temp_fds| maps from fd number in
// the parent process to a temporary fd number in the child process.
std::map<int, int> temp_fds;
int next_fd = max_fd + 1;
for (const auto &pair : map_fds) {
if (temp_fds.count(pair.second)) {
continue;
}
temp_fds[pair.second] = next_fd;
if (posix_spawn_file_actions_adddup2(&actions, pair.second, next_fd) !=
0 ||
posix_spawn_file_actions_addclose(&actions, pair.second) != 0) {
return false;
}
next_fd++;
}
for (const auto &pair : map_fds) {
if (posix_spawn_file_actions_adddup2(&actions, temp_fds[pair.second],
pair.first) != 0) {
return false;
}
}
// Clean up temporary fds.
for (int fd = max_fd + 1; fd < next_fd; fd++) {
if (posix_spawn_file_actions_addclose(&actions, fd) != 0) {
return false;
}
}
}
fflush(stdout);
fflush(stderr);
// MSan doesn't know that |posix_spawn| initializes its output, so initialize
// it to -1.
pid_t pid = -1;
if (posix_spawn(&pid, args[0], &actions, nullptr,
const_cast<char *const *>(args.data()), environ) != 0) {
return false;
}
out->Reset(pid);
*out_control = std::move(scoped_control0);
return true;
}
// RunHandshaker forks and execs the handshaker binary, handing off |input|,
// and, after proxying some amount of handshake traffic, handing back |out|.
static bool RunHandshaker(BIO *bio, const TestConfig *config, bool is_resume,
Span<const uint8_t> input,
std::vector<uint8_t> *out) {
int rfd[2], wfd[2];
// We use pipes, rather than some other mechanism, for their buffers. During
// the handshake, this process acts as a dumb proxy until receiving the
// handback signal, which arrives asynchronously. The race condition means
// that this process could incorrectly proxy post-handshake data from the
// client to the handshaker.
//
// To avoid this, this process never proxies data to the handshaker that the
// handshaker has not explicitly requested as a result of hitting
// |SSL_ERROR_WANT_READ|. Pipes allow the data to sit in a buffer while the
// two processes synchronize over the |control| channel.
if (pipe(rfd) != 0) {
perror("pipe");
return false;
}
ScopedFD rfd0_closer(rfd[0]), rfd1_closer(rfd[1]);
if (pipe(wfd) != 0) {
perror("pipe");
return false;
}
ScopedFD wfd0_closer(wfd[0]), wfd1_closer(wfd[1]);
ScopedProcess handshaker;
ScopedFD control;
if (!StartHandshaker(
&handshaker, &control, config, is_resume,
{{kFdProxyToHandshaker, rfd[0]}, {kFdHandshakerToProxy, wfd[1]}},
{rfd[1], wfd[0]})) {
return false;
}
rfd0_closer.Reset();
wfd1_closer.Reset();
if (write_eintr(control.fd(), input.data(), input.size()) == -1) {
perror("write");
return false;
}
bool ok = Proxy(bio, config->async, control.fd(), rfd[1], wfd[0]);
int wstatus;
if (!handshaker.Wait(&wstatus)) {
perror("waitpid");
return false;
}
if (ok && wstatus) {
fprintf(stderr, "handshaker exited irregularly\n");
return false;
}
if (!ok) {
return false; // This is a "good", i.e. expected, error.
}
constexpr size_t kBufSize = 1024 * 1024;
std::vector<uint8_t> buf(kBufSize);
ssize_t len = read_eintr(control.fd(), buf.data(), buf.size());
if (len == -1) {
perror("read");
return false;
}
buf.resize(len);
*out = std::move(buf);
return true;
}
static bool RequestHandshakeHint(const TestConfig *config, bool is_resume,
Span<const uint8_t> input, bool *out_has_hints,
std::vector<uint8_t> *out_hints) {
ScopedProcess handshaker;
ScopedFD control;
if (!StartHandshaker(&handshaker, &control, config, is_resume, {}, {})) {
return false;
}
if (write_eintr(control.fd(), input.data(), input.size()) == -1) {
perror("write");
return false;
}
char msg;
if (read_eintr(control.fd(), &msg, 1) != 1) {
perror("read");
return false;
}
switch (msg) {
case kControlMsgDone: {
constexpr size_t kBufSize = 1024 * 1024;
out_hints->resize(kBufSize);
ssize_t len =
read_eintr(control.fd(), out_hints->data(), out_hints->size());
if (len == -1) {
perror("read");
return false;
}
out_hints->resize(len);
*out_has_hints = true;
break;
}
case kControlMsgError:
*out_has_hints = false;
break;
default:
fprintf(stderr, "Unknown control message from handshaker: %c\n", msg);
return false;
}
int wstatus;
if (!handshaker.Wait(&wstatus)) {
perror("waitpid");
return false;
}
if (wstatus) {
fprintf(stderr, "handshaker exited irregularly\n");
return false;
}
return true;
}
// PrepareHandoff accepts the |ClientHello| from |ssl| and serializes state to
// be passed to the handshaker. The serialized state includes both the SSL
// handoff, as well test-related state.
static bool PrepareHandoff(SSL *ssl, SettingsWriter *writer,
std::vector<uint8_t> *out_handoff) {
SSL_set_handoff_mode(ssl, 1);
const TestConfig *config = GetTestConfig(ssl);
int ret = -1;
do {
ret = CheckIdempotentError(
"SSL_do_handshake", ssl,
[&]() -> int { return SSL_do_handshake(ssl); });
} while (!HandoffReady(ssl, ret) &&
config->async &&
RetryAsync(ssl, ret));
if (!HandoffReady(ssl, ret)) {
fprintf(stderr, "Handshake failed while waiting for handoff.\n");
return false;
}
ScopedCBB cbb;
SSL_CLIENT_HELLO hello;
if (!CBB_init(cbb.get(), 512) ||
!SSL_serialize_handoff(ssl, cbb.get(), &hello) ||
!writer->WriteHandoff({CBB_data(cbb.get()), CBB_len(cbb.get())}) ||
!SerializeContextState(SSL_get_SSL_CTX(ssl), cbb.get()) ||
!GetTestState(ssl)->Serialize(cbb.get())) {
fprintf(stderr, "Handoff serialisation failed.\n");
return false;
}
out_handoff->assign(CBB_data(cbb.get()),
CBB_data(cbb.get()) + CBB_len(cbb.get()));
return true;
}
// DoSplitHandshake delegates the SSL handshake to a separate process, called
// the handshaker. This process proxies I/O between the handshaker and the
// client, using the |BIO| from |ssl|. After a successful handshake, |ssl| is
// replaced with a new |SSL| object, in a way that is intended to be invisible
// to the caller.
bool DoSplitHandshake(UniquePtr<SSL> *ssl, SettingsWriter *writer,
bool is_resume) {
assert(SSL_get_rbio(ssl->get()) == SSL_get_wbio(ssl->get()));
std::vector<uint8_t> handshaker_input;
const TestConfig *config = GetTestConfig(ssl->get());
// out is the response from the handshaker, which includes a serialized
// handback message, but also serialized updates to the |TestState|.
std::vector<uint8_t> out;
if (!PrepareHandoff(ssl->get(), writer, &handshaker_input) ||
!RunHandshaker(SSL_get_rbio(ssl->get()), config, is_resume,
handshaker_input, &out)) {
fprintf(stderr, "Handoff failed.\n");
return false;
}
SSL_CTX *ctx = SSL_get_SSL_CTX(ssl->get());
UniquePtr<SSL> ssl_handback = config->NewSSL(ctx, nullptr, nullptr);
if (!ssl_handback) {
return false;
}
CBS output, handback;
CBS_init(&output, out.data(), out.size());
if (!CBS_get_u24_length_prefixed(&output, &handback) ||
!DeserializeContextState(&output, ctx) ||
!SetTestState(ssl_handback.get(), TestState::Deserialize(&output, ctx)) ||
!GetTestState(ssl_handback.get()) || !writer->WriteHandback(handback) ||
!SSL_apply_handback(ssl_handback.get(), handback)) {
fprintf(stderr, "Handback failed.\n");
return false;
}
MoveBIOs(ssl_handback.get(), ssl->get());
GetTestState(ssl_handback.get())->async_bio =
GetTestState(ssl->get())->async_bio;
GetTestState(ssl->get())->async_bio = nullptr;
*ssl = std::move(ssl_handback);
return true;
}
bool GetHandshakeHint(SSL *ssl, SettingsWriter *writer, bool is_resume,
const SSL_CLIENT_HELLO *client_hello) {
ScopedCBB input;
CBB child;
if (!CBB_init(input.get(), client_hello->client_hello_len + 256) ||
!CBB_add_u24_length_prefixed(input.get(), &child) ||
!CBB_add_bytes(&child, client_hello->client_hello,
client_hello->client_hello_len) ||
!CBB_add_u24_length_prefixed(input.get(), &child) ||
!SSL_serialize_capabilities(ssl, &child) || //
!CBB_flush(input.get())) {
return false;
}
bool has_hints;
std::vector<uint8_t> hints;
if (!RequestHandshakeHint(
GetTestConfig(ssl), is_resume,
MakeConstSpan(CBB_data(input.get()), CBB_len(input.get())),
&has_hints, &hints)) {
return false;
}
if (has_hints &&
(!writer->WriteHints(hints) ||
!SSL_set_handshake_hints(ssl, hints.data(), hints.size()))) {
return false;
}
return true;
}
#endif // defined(HANDSHAKER_SUPPORTED)