Commit 7b71f5e5 authored by Ilya Maykov's avatar Ilya Maykov Committed by Facebook GitHub Bot

fix self-assignment for OpenSSLHash::Digest, throw if context allocation...

fix self-assignment for OpenSSLHash::Digest, throw if context allocation fails, implement move support

Summary:
The copy assignment operator was not checking for self-assignment, which means the code was wrong when an object was being copied into itself.
Also, allocation failure in the constructor would be silently ignored and result in a segfault crash later. Throw std::runtime_error if OpenSSL context allocation fails.
Also, move constructor and move assignment operator were not implemented - they are now.

Reviewed By: yfeldblum

Differential Revision: D30879545

fbshipit-source-id: 8b06f6fe97912a03ec5480a3b7c69aebf3a7f2ca
parent b258ee31
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
#pragma once #pragma once
#include <stdexcept>
#include <folly/Range.h> #include <folly/Range.h>
#include <folly/io/IOBuf.h> #include <folly/io/IOBuf.h>
#include <folly/portability/OpenSSL.h> #include <folly/portability/OpenSSL.h>
...@@ -30,35 +31,49 @@ class OpenSSLHash { ...@@ -30,35 +31,49 @@ class OpenSSLHash {
public: public:
class Digest { class Digest {
public: public:
Digest() : ctx_(EVP_MD_CTX_new()) {} Digest() { check_context_notnull(); }
Digest(const Digest& other) { Digest(const Digest& that) {
ctx_ = EvpMdCtxUniquePtr(EVP_MD_CTX_new()); check_context_notnull();
if (other.md_ != nullptr) { copy_impl(that);
hash_init(other.md_); }
check_libssl_result(
1, EVP_MD_CTX_copy_ex(ctx_.get(), other.ctx_.get())); Digest(Digest&& that) noexcept(false) {
check_context_notnull();
move_impl(std::move(that));
} }
Digest& operator=(const Digest& that) {
if (this != &that) {
copy_impl(that);
}
return *this;
} }
Digest& operator=(const Digest& other) { Digest& operator=(Digest&& that) noexcept(false) {
this->~Digest(); if (this != &that) {
return *new (this) Digest(other); move_impl(std::move(that));
that.hash_reset();
}
return *this;
} }
void hash_init(const EVP_MD* md) { void hash_init(const EVP_MD* md) {
md_ = md; md_ = md;
check_libssl_result(1, EVP_DigestInit_ex(ctx_.get(), md, nullptr)); check_libssl_result(1, EVP_DigestInit_ex(ctx_.get(), md, nullptr));
} }
void hash_update(ByteRange data) { void hash_update(ByteRange data) {
check_libssl_result( check_libssl_result(
1, EVP_DigestUpdate(ctx_.get(), data.data(), data.size())); 1, EVP_DigestUpdate(ctx_.get(), data.data(), data.size()));
} }
void hash_update(const IOBuf& data) { void hash_update(const IOBuf& data) {
for (auto r : data) { for (auto r : data) {
hash_update(r); hash_update(r);
} }
} }
void hash_final(MutableByteRange out) { void hash_final(MutableByteRange out) {
const auto size = EVP_MD_size(md_); const auto size = EVP_MD_size(md_);
check_out_size(size_t(size), out); check_out_size(size_t(size), out);
...@@ -69,8 +84,34 @@ class OpenSSLHash { ...@@ -69,8 +84,34 @@ class OpenSSLHash {
} }
private: private:
const EVP_MD* md_ = nullptr; const EVP_MD* md_{nullptr};
EvpMdCtxUniquePtr ctx_{nullptr}; EvpMdCtxUniquePtr ctx_{EVP_MD_CTX_new()};
void hash_reset() {
md_ = nullptr;
check_libssl_result(1, EVP_MD_CTX_reset(ctx_.get()));
}
void check_context_notnull() {
if (nullptr == ctx_) {
throw_exception<std::runtime_error>(
"EVP_MD_CTX_new() returned nullptr");
}
}
void copy_impl(const Digest& that) {
if (that.md_ != nullptr) {
hash_init(that.md_);
check_libssl_result(1, EVP_MD_CTX_copy_ex(ctx_.get(), that.ctx_.get()));
} else {
this->hash_reset();
}
}
void move_impl(Digest&& that) noexcept {
std::swap(this->md_, that.md_);
std::swap(this->ctx_, that.ctx_);
}
}; };
static void hash(MutableByteRange out, const EVP_MD* md, ByteRange data) { static void hash(MutableByteRange out, const EVP_MD* md, ByteRange data) {
......
...@@ -45,36 +45,131 @@ TEST_F(OpenSSLHashTest, sha256) { ...@@ -45,36 +45,131 @@ TEST_F(OpenSSLHashTest, sha256) {
} }
TEST_F(OpenSSLHashTest, sha256_hashcopy) { TEST_F(OpenSSLHashTest, sha256_hashcopy) {
std::array<uint8_t, 32> expected, actual1, actual2;
constexpr StringPiece data{"foobar"};
OpenSSLHash::hash(range(expected), EVP_sha256(), data);
OpenSSLHash::Digest digest;
digest.hash_init(EVP_sha256());
digest.hash_update(ByteRange(data));
OpenSSLHash::Digest copy1(digest); // copy constructor
OpenSSLHash::Digest copy2 = digest; // copy assignment operator
copy1.hash_final(range(actual1));
copy2.hash_final(range(actual2));
EXPECT_EQ(expected, actual1);
EXPECT_EQ(expected, actual2);
}
TEST_F(OpenSSLHashTest, sha256_hashcopy_self) {
std::array<uint8_t, 32> expected, actual; std::array<uint8_t, 32> expected, actual;
constexpr StringPiece data{"foobar"};
OpenSSLHash::hash(range(expected), EVP_sha256(), data);
OpenSSLHash::Digest digest; OpenSSLHash::Digest digest;
digest.hash_init(EVP_sha256()); digest.hash_init(EVP_sha256());
digest.hash_update(ByteRange(StringPiece("foobar"))); digest.hash_update(ByteRange(data));
OpenSSLHash::Digest copy(digest); OpenSSLHash::Digest* ptr = &digest;
digest = *ptr; // test copy of an object to itself
digest.hash_final(range(expected)); digest.hash_final(range(actual));
copy.hash_final(range(actual));
EXPECT_EQ(expected, actual); EXPECT_EQ(expected, actual);
} }
TEST_F(OpenSSLHashTest, sha256_hashcopy_intermediate) { TEST_F(OpenSSLHashTest, sha256_hashmove) {
std::array<uint8_t, 32> expected, actual1, actual2;
constexpr StringPiece data{"foobar"};
OpenSSLHash::hash(range(expected), EVP_sha256(), data);
OpenSSLHash::Digest digest;
digest.hash_init(EVP_sha256());
digest.hash_update(ByteRange(data));
OpenSSLHash::Digest copy1(std::move(digest)); // move constructor
copy1.hash_final(range(actual1));
EXPECT_EQ(expected, actual1);
digest = OpenSSLHash::Digest{}; // should be safe to reassign to moved object
digest.hash_init(EVP_sha256());
digest.hash_update(ByteRange(data));
OpenSSLHash::Digest copy2 = std::move(digest); // move assignment operator
copy2.hash_final(range(actual2));
EXPECT_EQ(expected, actual2);
}
TEST_F(OpenSSLHashTest, sha256_hashmove_self) {
std::array<uint8_t, 32> expected, actual; std::array<uint8_t, 32> expected, actual;
constexpr StringPiece data{"foobar"};
OpenSSLHash::hash(range(expected), EVP_sha256(), data);
OpenSSLHash::Digest digest; OpenSSLHash::Digest digest;
digest.hash_init(EVP_sha256()); digest.hash_init(EVP_sha256());
digest.hash_update(ByteRange(StringPiece("foo"))); digest.hash_update(ByteRange(data));
OpenSSLHash::Digest* ptr = &digest;
digest = std::move(*ptr); // test move of an object to itself
digest.hash_final(range(actual));
EXPECT_EQ(expected, actual);
}
TEST_F(OpenSSLHashTest, sha256_hashcopy_intermediate) {
std::array<uint8_t, 32> expected, actual1, actual2;
constexpr StringPiece data1("foo");
constexpr StringPiece data2("bar");
OpenSSLHash::Digest copy(digest); OpenSSLHash::Digest digest;
digest.hash_init(EVP_sha256());
digest.hash_update(ByteRange(data1));
digest.hash_update(ByteRange(StringPiece("bar"))); OpenSSLHash::Digest copy1(digest); // copy constructor
copy.hash_update(ByteRange(StringPiece("bar"))); OpenSSLHash::Digest copy2 = digest; // copy assignment operator
digest.hash_update(ByteRange(data2));
digest.hash_final(range(expected)); digest.hash_final(range(expected));
copy.hash_final(range(actual));
EXPECT_EQ(expected, actual); copy1.hash_update(ByteRange(data2));
copy1.hash_final(range(actual1));
EXPECT_EQ(expected, actual1);
copy2.hash_update(ByteRange(data2));
copy2.hash_final(range(actual2));
EXPECT_EQ(expected, actual2);
}
TEST_F(OpenSSLHashTest, sha256_hashmove_intermediate) {
std::array<uint8_t, 32> expected, actual1, actual2;
constexpr StringPiece fulldata("foobar");
constexpr StringPiece data1("foo");
constexpr StringPiece data2("bar");
OpenSSLHash::hash(range(expected), EVP_sha256(), fulldata);
OpenSSLHash::Digest digest;
digest.hash_init(EVP_sha256());
digest.hash_update(ByteRange(data1));
OpenSSLHash::Digest copy1(std::move(digest)); // move constructor
copy1.hash_update(ByteRange(data2));
copy1.hash_final(range(actual1));
EXPECT_EQ(expected, actual1);
digest.hash_init(EVP_sha256()); // should be safe to re-init moved object
digest.hash_update(ByteRange(data1));
OpenSSLHash::Digest copy2 = std::move(digest); // move assignment operator
copy2.hash_update(ByteRange(data2));
copy2.hash_final(range(actual2));
EXPECT_EQ(expected, actual2);
// Make sure it's safe to re-init moved object after move operator=()
digest.hash_init(EVP_sha256());
} }
TEST_F(OpenSSLHashTest, hmac_sha256) { TEST_F(OpenSSLHashTest, hmac_sha256) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment