Browse Source

src: unify implementations of Utf8Value etc.

Unify the common code of `Utf8Value`, `TwoByteValue`, `BufferValue`
and `StringBytes::InlineDecoder` into one class. Always make the
result zero-terminated for the first three.

This fixes two problems in passing:

* When the conversion of the input value to String fails,
  make the buffer zero-terminated anyway. Previously, this would
  have resulted in possibly reading uninitialized data in multiple
  places in the code. An instance of that problem can be reproduced
  by running e.g.
  `valgrind node -e 'net.isIP({ toString() { throw Error() } })'`.
* Previously, `BufferValue` copied one byte too much from the source,
  possibly resulting in an out-of-bounds memory access.
  This can be reproduced by running e.g.
  `valgrind node -e \
    'fs.openSync(Buffer.from("node".repeat(8192)), "r")'`.

Further minor changes:
* This lifts the `out()` method of `StringBytes::InlineDecoder`
  to the common class so that it can be used when using the
  overloaded `operator*` does not seem appropiate.
* Hopefully clearer variable names.
* Add checks to make sure the length of the data does not exceed
  the allocated storage size, including the possible null terminator.

PR-URL: https://github.com/nodejs/node/pull/6357
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
process-exit-stdio-flushing
Anna Henningsen 9 years ago
parent
commit
44a40325da
No known key found for this signature in database GPG Key ID: D8B9F5AEAE84E4CF
  1. 44
      src/string_bytes.h
  2. 75
      src/util.cc
  3. 123
      src/util.h

44
src/string_bytes.h

@ -7,22 +7,14 @@
#include "node.h" #include "node.h"
#include "env.h" #include "env.h"
#include "env-inl.h" #include "env-inl.h"
#include "util.h"
namespace node { namespace node {
class StringBytes { class StringBytes {
public: public:
class InlineDecoder { class InlineDecoder : public MaybeStackBuffer<char> {
public: public:
InlineDecoder() : out_(nullptr) {
}
~InlineDecoder() {
if (out_ != out_st_)
delete[] out_;
out_ = nullptr;
}
inline bool Decode(Environment* env, inline bool Decode(Environment* env,
v8::Local<v8::String> string, v8::Local<v8::String> string,
v8::Local<v8::Value> encoding, v8::Local<v8::Value> encoding,
@ -33,28 +25,22 @@ class StringBytes {
return false; return false;
} }
size_t buflen = StringBytes::StorageSize(env->isolate(), string, enc); const size_t storage = StringBytes::StorageSize(env->isolate(),
if (buflen > sizeof(out_st_)) string,
out_ = new char[buflen]; enc);
else AllocateSufficientStorage(storage);
out_ = out_st_; const size_t length = StringBytes::Write(env->isolate(),
size_ = StringBytes::Write(env->isolate(), out(),
out_, storage,
buflen, string,
string, enc);
enc);
// No zero terminator is included when using this method.
SetLength(length);
return true; return true;
} }
inline const char* out() const { return out_; } inline size_t size() const { return length(); }
inline size_t size() const { return size_; }
private:
static const int kStorageSize = 1024;
char out_st_[kStorageSize];
char* out_;
size_t size_;
}; };
// Does the string match the encoding? Quick but non-exhaustive. // Does the string match the encoding? Quick but non-exhaustive.

75
src/util.cc

@ -10,76 +10,69 @@ using v8::Local;
using v8::String; using v8::String;
using v8::Value; using v8::Value;
static int MakeUtf8String(Isolate* isolate, template <typename T>
Local<Value> value, static void MakeUtf8String(Isolate* isolate,
char** dst, Local<Value> value,
const size_t size) { T* target) {
Local<String> string = value->ToString(isolate); Local<String> string = value->ToString(isolate);
if (string.IsEmpty()) if (string.IsEmpty())
return 0; return;
size_t len = StringBytes::StorageSize(isolate, string, UTF8) + 1;
if (len > size) { const size_t storage = StringBytes::StorageSize(isolate, string, UTF8) + 1;
*dst = static_cast<char*>(malloc(len)); target->AllocateSufficientStorage(storage);
CHECK_NE(*dst, nullptr);
}
const int flags = const int flags =
String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8; String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8;
const int length = string->WriteUtf8(*dst, len, 0, flags); const int length = string->WriteUtf8(target->out(), storage, 0, flags);
(*dst)[length] = '\0'; target->SetLengthAndZeroTerminate(length);
return length;
} }
Utf8Value::Utf8Value(Isolate* isolate, Local<Value> value) Utf8Value::Utf8Value(Isolate* isolate, Local<Value> value) {
: length_(0), str_(str_st_) {
if (value.IsEmpty()) if (value.IsEmpty())
return; return;
length_ = MakeUtf8String(isolate, value, &str_, sizeof(str_st_));
MakeUtf8String(isolate, value, this);
} }
TwoByteValue::TwoByteValue(Isolate* isolate, Local<Value> value) TwoByteValue::TwoByteValue(Isolate* isolate, Local<Value> value) {
: length_(0), str_(str_st_) { if (value.IsEmpty()) {
if (value.IsEmpty())
return; return;
}
Local<String> string = value->ToString(isolate); Local<String> string = value->ToString(isolate);
if (string.IsEmpty()) if (string.IsEmpty())
return; return;
// Allocate enough space to include the null terminator // Allocate enough space to include the null terminator
size_t len = const size_t storage = string->Length() + 1;
StringBytes::StorageSize(isolate, string, UCS2) + AllocateSufficientStorage(storage);
sizeof(uint16_t);
if (len > sizeof(str_st_)) {
str_ = static_cast<uint16_t*>(malloc(len));
CHECK_NE(str_, nullptr);
}
const int flags = const int flags =
String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8; String::NO_NULL_TERMINATION | String::REPLACE_INVALID_UTF8;
length_ = string->Write(str_, 0, len, flags); const int length = string->Write(out(), 0, storage, flags);
str_[length_] = '\0'; SetLengthAndZeroTerminate(length);
} }
BufferValue::BufferValue(Isolate* isolate, Local<Value> value) BufferValue::BufferValue(Isolate* isolate, Local<Value> value) {
: str_(str_st_), fail_(true) {
// Slightly different take on Utf8Value. If value is a String, // Slightly different take on Utf8Value. If value is a String,
// it will return a Utf8 encoded string. If value is a Buffer, // it will return a Utf8 encoded string. If value is a Buffer,
// it will copy the data out of the Buffer as is. // it will copy the data out of the Buffer as is.
if (value.IsEmpty()) if (value.IsEmpty()) {
// Dereferencing this object will return nullptr.
Invalidate();
return; return;
}
if (value->IsString()) { if (value->IsString()) {
MakeUtf8String(isolate, value, &str_, sizeof(str_st_)); MakeUtf8String(isolate, value, this);
fail_ = false;
} else if (Buffer::HasInstance(value)) { } else if (Buffer::HasInstance(value)) {
size_t len = Buffer::Length(value) + 1; const size_t len = Buffer::Length(value);
if (len > sizeof(str_st_)) { // Leave place for the terminating '\0' byte.
str_ = static_cast<char*>(malloc(len)); AllocateSufficientStorage(len + 1);
CHECK_NE(str_, nullptr); memcpy(out(), Buffer::Data(value), len);
} SetLengthAndZeroTerminate(len);
memcpy(str_, Buffer::Data(value), len); } else {
str_[len - 1] = '\0'; Invalidate();
fail_ = false;
} }
} }

123
src/util.h

@ -178,77 +178,102 @@ inline TypeName* Unwrap(v8::Local<v8::Object> object);
inline void SwapBytes(uint16_t* dst, const uint16_t* src, size_t buflen); inline void SwapBytes(uint16_t* dst, const uint16_t* src, size_t buflen);
class Utf8Value { // Allocates an array of member type T. For up to kStackStorageSize items,
// the stack is used, otherwise malloc().
template <typename T, size_t kStackStorageSize = 1024>
class MaybeStackBuffer {
public: public:
explicit Utf8Value(v8::Isolate* isolate, v8::Local<v8::Value> value); const T* out() const {
return buf_;
}
~Utf8Value() { T* out() {
if (str_ != str_st_) return buf_;
free(str_);
} }
char* operator*() { // operator* for compatibility with `v8::String::(Utf8)Value`
return str_; T* operator*() {
}; return buf_;
}
const char* operator*() const { const T* operator*() const {
return str_; return buf_;
}; }
size_t length() const { size_t length() const {
return length_; return length_;
}; }
private:
size_t length_;
char* str_;
char str_st_[1024];
};
class TwoByteValue { // Call to make sure enough space for `storage` entries is available.
public: // There can only be 1 call to AllocateSufficientStorage or Invalidate
explicit TwoByteValue(v8::Isolate* isolate, v8::Local<v8::Value> value); // per instance.
void AllocateSufficientStorage(size_t storage) {
if (storage <= kStackStorageSize) {
buf_ = buf_st_;
} else {
// Guard against overflow.
CHECK_LE(storage, sizeof(T) * storage);
buf_ = static_cast<T*>(malloc(sizeof(T) * storage));
CHECK_NE(buf_, nullptr);
}
// Remember how much was allocated to check against that in SetLength().
length_ = storage;
}
~TwoByteValue() { void SetLength(size_t length) {
if (str_ != str_st_) // length_ stores how much memory was allocated.
free(str_); CHECK_LE(length, length_);
length_ = length;
} }
uint16_t* operator*() { void SetLengthAndZeroTerminate(size_t length) {
return str_; // length_ stores how much memory was allocated.
}; CHECK_LE(length + 1, length_);
SetLength(length);
const uint16_t* operator*() const { // T() is 0 for integer types, nullptr for pointers, etc.
return str_; buf_[length] = T();
}; }
size_t length() const { // Make derefencing this object return nullptr.
return length_; // Calling this is mutually exclusive with calling
}; // AllocateSufficientStorage.
void Invalidate() {
CHECK_EQ(buf_, buf_st_);
length_ = 0;
buf_ = nullptr;
}
MaybeStackBuffer() : length_(0), buf_(buf_st_) {
// Default to a zero-length, null-terminated buffer.
buf_[0] = T();
}
~MaybeStackBuffer() {
if (buf_ != buf_st_)
free(buf_);
}
private: private:
size_t length_; size_t length_;
uint16_t* str_; T* buf_;
uint16_t str_st_[1024]; T buf_st_[kStackStorageSize];
}; };
class BufferValue { class Utf8Value : public MaybeStackBuffer<char> {
public: public:
explicit BufferValue(v8::Isolate* isolate, v8::Local<v8::Value> value); explicit Utf8Value(v8::Isolate* isolate, v8::Local<v8::Value> value);
};
~BufferValue() {
if (str_ != str_st_)
free(str_);
}
const char* operator*() const { class TwoByteValue : public MaybeStackBuffer<uint16_t> {
return fail_ ? nullptr : str_; public:
}; explicit TwoByteValue(v8::Isolate* isolate, v8::Local<v8::Value> value);
};
private: class BufferValue : public MaybeStackBuffer<char> {
char* str_; public:
char str_st_[1024]; explicit BufferValue(v8::Isolate* isolate, v8::Local<v8::Value> value);
bool fail_;
}; };
} // namespace node } // namespace node

Loading…
Cancel
Save