From 58f93ffc4a23aa7240808288acf8cf9f3022abea Mon Sep 17 00:00:00 2001 From: Fedor Indutny Date: Wed, 3 Apr 2013 16:58:10 +0400 Subject: [PATCH] crypto: use better memory BIO implementation --- node.gyp | 3 +- src/node_crypto.cc | 9 +- src/node_crypto_bio.cc | 312 +++++++++++++++++++++++++++++++++++++++++++++++++ src/node_crypto_bio.h | 93 +++++++++++++++ 4 files changed, 412 insertions(+), 5 deletions(-) create mode 100644 src/node_crypto_bio.cc create mode 100644 src/node_crypto_bio.h diff --git a/node.gyp b/node.gyp index fc7e215..efeff53 100644 --- a/node.gyp +++ b/node.gyp @@ -110,6 +110,7 @@ 'src/node_buffer.h', 'src/node_constants.h', 'src/node_crypto.h', + 'src/node_crypto_bio.h', 'src/node_extensions.h', 'src/node_file.h', 'src/node_http_parser.h', @@ -147,7 +148,7 @@ 'conditions': [ [ 'node_use_openssl=="true"', { 'defines': [ 'HAVE_OPENSSL=1' ], - 'sources': [ 'src/node_crypto.cc' ], + 'sources': [ 'src/node_crypto.cc', 'src/node_crypto_bio.cc' ], 'conditions': [ [ 'node_shared_openssl=="false"', { 'dependencies': [ './deps/openssl/openssl.gyp:openssl' ], diff --git a/src/node_crypto.cc b/src/node_crypto.cc index ec2dbe4..3c0e76d 100644 --- a/src/node_crypto.cc +++ b/src/node_crypto.cc @@ -20,6 +20,7 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. #include "node_crypto.h" +#include "node_crypto_bio.h" #include "node_crypto_groups.h" #include "v8.h" @@ -291,7 +292,7 @@ int SecureContext::NewSessionCallback(SSL* s, SSL_SESSION* sess) { // Takes a string or buffer and loads it into a BIO. // Caller responsible for BIO_free-ing the returned object. static BIO* LoadBIO (Handle v) { - BIO *bio = BIO_new(BIO_s_mem()); + BIO *bio = BIO_new(NodeBIO::GetMethod()); if (!bio) return NULL; HandleScope scope; @@ -544,7 +545,7 @@ Handle SecureContext::AddRootCerts(const Arguments& args) { root_cert_store = X509_STORE_new(); for (int i = 0; root_certs[i]; i++) { - BIO *bp = BIO_new(BIO_s_mem()); + BIO *bp = BIO_new(NodeBIO::GetMethod()); if (!BIO_write(bp, root_certs[i], strlen(root_certs[i]))) { BIO_free(bp); @@ -1201,8 +1202,8 @@ Handle Connection::New(const Arguments& args) { bool is_server = args[1]->BooleanValue(); p->ssl_ = SSL_new(sc->ctx_); - p->bio_read_ = BIO_new(BIO_s_mem()); - p->bio_write_ = BIO_new(BIO_s_mem()); + p->bio_read_ = BIO_new(NodeBIO::GetMethod()); + p->bio_write_ = BIO_new(NodeBIO::GetMethod()); SSL_set_app_data(p->ssl_, p); diff --git a/src/node_crypto_bio.cc b/src/node_crypto_bio.cc new file mode 100644 index 0000000..a314bf6 --- /dev/null +++ b/src/node_crypto_bio.cc @@ -0,0 +1,312 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "node_crypto_bio.h" +#include "openssl/bio.h" +#include + +namespace node { + +BIO_METHOD NodeBIO::method_ = { + BIO_TYPE_MEM, + "node.js SSL buffer", + NodeBIO::Write, + NodeBIO::Read, + NodeBIO::Puts, + NodeBIO::Gets, + NodeBIO::Ctrl, + NodeBIO::New, + NodeBIO::Free, + NULL +}; + + +int NodeBIO::New(BIO* bio) { + bio->ptr = new NodeBIO(); + + // XXX Why am I doing it?! + bio->shutdown = 1; + bio->init = 1; + bio->num = -1; + + return 1; +} + + +int NodeBIO::Free(BIO* bio) { + if (bio == NULL) return 0; + + if (bio->shutdown) { + if (bio->init && bio->ptr != NULL) { + delete FromBIO(bio); + bio->ptr = NULL; + } + } + + return 1; +} + + +int NodeBIO::Read(BIO* bio, char* out, int len) { + int bytes; + BIO_clear_retry_flags(bio); + + bytes = FromBIO(bio)->Read(out, len); + + if (bytes == 0) { + bytes = bio->num; + if (bytes != 0) { + BIO_set_retry_read(bio); + } + } + + return bytes; +} + + +int NodeBIO::Write(BIO* bio, const char* data, int len) { + BIO_clear_retry_flags(bio); + + FromBIO(bio)->Write(data, len); + + return len; +} + + +int NodeBIO::Puts(BIO* bio, const char* str) { + return Write(bio, str, strlen(str)); +} + + +int NodeBIO::Gets(BIO* bio, char* out, int size) { + NodeBIO* nbio = FromBIO(bio); + + if (nbio->Length() == 0) + return 0; + + int i = nbio->IndexOf('\n', size); + + // Include '\n' + if (i < size) i++; + + // Shift `i` a bit to NULL-terminate string later + if (size == i) i--; + + // Flush read data + nbio->Read(out, i); + + out[i] = 0; + + return i; +} + + +long NodeBIO::Ctrl(BIO* bio, int cmd, long num, void* ptr) { + NodeBIO* nbio; + long ret; + + nbio = FromBIO(bio); + ret = 1; + + switch (cmd) { + case BIO_CTRL_RESET: + nbio->Reset(); + break; + case BIO_CTRL_EOF: + ret = nbio->Length() == 0; + break; + case BIO_C_SET_BUF_MEM_EOF_RETURN: + bio->num = num; + break; + case BIO_CTRL_INFO: + ret = nbio->Length(); + if (ptr != NULL) + *reinterpret_cast(ptr) = NULL; + break; + case BIO_C_SET_BUF_MEM: + assert(0 && "Can't use SET_BUF_MEM_PTR with NodeBIO"); + abort(); + break; + case BIO_C_GET_BUF_MEM_PTR: + assert(0 && "Can't use GET_BUF_MEM_PTR with NodeBIO"); + ret = 0; + break; + case BIO_CTRL_GET_CLOSE: + ret = bio->shutdown; + break; + case BIO_CTRL_SET_CLOSE: + bio->shutdown = num; + break; + case BIO_CTRL_WPENDING: + ret = 0; + break; + case BIO_CTRL_PENDING: + ret = nbio->Length(); + break; + case BIO_CTRL_DUP: + case BIO_CTRL_FLUSH: + ret = 1; + break; + case BIO_CTRL_PUSH: + case BIO_CTRL_POP: + default: + ret = 0; + break; + } + return ret; +} + + +size_t NodeBIO::Read(char* out, size_t size) { + size_t bytes_read = 0; + size_t expected = Length() > size ? size : Length(); + + while (bytes_read < expected) { + assert(read_head_->read_pos_ <= read_head_->write_pos_); + size_t avail = read_head_->write_pos_ - read_head_->read_pos_; + if (avail > size) + avail = size; + + // Copy data + if (out != NULL) + memcpy(out, read_head_->data_ + read_head_->read_pos_, avail); + read_head_->read_pos_ += avail; + + // Move pointers + bytes_read += avail; + out += avail; + size -= avail; + + // Move to next buffer + if (read_head_->read_pos_ == read_head_->write_pos_) { + read_head_->read_pos_ = 0; + read_head_->write_pos_ = 0; + read_head_ = read_head_->next_; + } + } + assert(expected == bytes_read); + length_ -= bytes_read; + + return bytes_read; +} + + +size_t NodeBIO::IndexOf(char delim, size_t limit) { + size_t bytes_read = 0; + size_t max = Length() > limit ? limit : Length(); + Buffer* current = read_head_; + + while (bytes_read < max) { + assert(current->read_pos_ <= current->write_pos_); + size_t avail = current->write_pos_ - current->read_pos_; + if (avail > limit) + avail = limit; + + // Walk through data + char* tmp = current->data_ + current->read_pos_; + size_t off = 0; + while (off < avail && *tmp != delim) { + off++; + tmp++; + } + + // Move pointers + bytes_read += off; + limit -= off; + + // Found `delim` + if (off != avail) { + return bytes_read; + } + + // Move to next buffer + if (current->read_pos_ + avail == kBufferLength) { + current = current->next_; + } + } + assert(max == bytes_read); + + return max; +} + + +void NodeBIO::Write(const char* data, size_t len) { + while (len > 0) { + size_t to_write = len; + assert(write_head_->write_pos_ <= kBufferLength); + size_t avail = kBufferLength - write_head_->write_pos_; + + if (to_write > avail) + to_write = avail; + + // Copy data + memcpy(write_head_->data_ + write_head_->write_pos_, data, to_write); + write_head_->write_pos_ += to_write; + assert(write_head_->write_pos_ <= kBufferLength); + + // Move pointers + len -= to_write; + data += to_write; + length_ += to_write; + + // Still have some bytes left: + // 1. Go to next buffer + // 2. Allocate new if next is already full or is partially read + // (is read head) + if (write_head_->next_->write_pos_ == kBufferLength || + write_head_->next_->read_pos_ != 0) { + Buffer* next = new Buffer(); + next->next_ = write_head_->next_; + write_head_->next_ = next; + } + write_head_ = write_head_->next_; + } + assert(len == 0); +} + + +void NodeBIO::Reset() { + while (read_head_->read_pos_ != read_head_->write_pos_) { + assert(read_head_->write_pos_ > read_head_->read_pos_); + + length_ -= read_head_->write_pos_ - read_head_->read_pos_; + read_head_->write_pos_ = 0; + read_head_->read_pos_ = 0; + + read_head_ = read_head_->next_; + } + assert(length_ == 0); +} + + +NodeBIO::~NodeBIO() { + Buffer* current = head_.next_; + while (current != &head_) { + Buffer* next = current->next_; + delete current; + current = next; + } + + read_head_ = NULL; + write_head_ = NULL; +} + +} // namespace node diff --git a/src/node_crypto_bio.h b/src/node_crypto_bio.h new file mode 100644 index 0000000..95d173e --- /dev/null +++ b/src/node_crypto_bio.h @@ -0,0 +1,93 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +#include "openssl/bio.h" +#include + +namespace node { + +class NodeBIO { + public: + static inline BIO_METHOD* GetMethod() { + return &method_; + } + + static int New(BIO* bio); + static int Free(BIO* bio); + static int Read(BIO* bio, char* out, int len); + static int Write(BIO* bio, const char* data, int len); + static int Puts(BIO* bio, const char* str); + static int Gets(BIO* bio, char* out, int size); + static long Ctrl(BIO* bio, int cmd, long num, void* ptr); + + protected: + static const size_t kBufferLength = 16 * 1024; + + class Buffer { + public: + Buffer() : read_pos_(0), write_pos_(0), next_(NULL) { + } + + size_t read_pos_; + size_t write_pos_; + Buffer* next_; + char data_[kBufferLength]; + }; + + NodeBIO() : length_(0), read_head_(&head_), write_head_(&head_) { + // Loop head + head_.next_ = &head_; + } + + ~NodeBIO(); + + // Read `len` bytes maximum into `out`, return actual number of read bytes + size_t Read(char* out, size_t size); + + // Find first appearance of `delim` in buffer or `limit` if `delim` + // wasn't found. + size_t IndexOf(char delim, size_t limit); + + // Discard all available data + void Reset(); + + // Put `len` bytes from `data` into buffer + void Write(const char* data, size_t len); + + // Return size of buffer in bytes + size_t inline Length() { + return length_; + } + + static inline NodeBIO* FromBIO(BIO* bio) { + assert(bio->ptr != NULL); + return static_cast(bio->ptr); + } + + size_t length_; + Buffer head_; + Buffer* read_head_; + Buffer* write_head_; + + static BIO_METHOD method_; +}; + +} // namespace node -- 2.7.4