-
Notifications
You must be signed in to change notification settings - Fork 5.3k
http: fix allocation bug introduced in #4211. #4245
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2c9cc74
28d232d
7c7c122
36af343
8e12de5
cd531fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,9 +14,25 @@ | |
| namespace Envoy { | ||
| namespace Http { | ||
|
|
||
| namespace { | ||
| constexpr size_t MinDynamicCapacity{32}; | ||
| // This includes the NULL (StringUtil::itoa technically only needs 21). | ||
| constexpr size_t MaxIntegerLength{32}; | ||
|
|
||
| void validateCapacity(size_t new_capacity) { | ||
| // If the resizing will cause buffer overflow due to hitting uint32_t::max, an OOM is likely | ||
| // imminent. Fast-fail rather than allow a buffer overflow attack (issue #1421) | ||
| RELEASE_ASSERT(new_capacity <= std::numeric_limits<uint32_t>::max(), ""); | ||
| ASSERT(new_capacity >= MinDynamicCapacity); | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| HeaderString::HeaderString() : type_(Type::Inline) { | ||
| buffer_.dynamic_ = inline_buffer_; | ||
| clear(); | ||
| static_assert(sizeof(inline_buffer_) >= MaxIntegerLength, ""); | ||
| static_assert(MinDynamicCapacity >= MaxIntegerLength, ""); | ||
| } | ||
|
|
||
| HeaderString::HeaderString(const LowerCaseString& ref_value) : type_(Type::Reference) { | ||
|
|
@@ -70,7 +86,9 @@ void HeaderString::append(const char* data, uint32_t size) { | |
| // Rather than be too clever and optimize this uncommon case, we dynamically | ||
| // allocate and copy. | ||
| type_ = Type::Dynamic; | ||
| dynamic_capacity_ = (string_length_ + size) * 2; | ||
| dynamic_capacity_ = | ||
| std::max(MinDynamicCapacity, static_cast<size_t>((string_length_ + size) * 2)); | ||
| validateCapacity(dynamic_capacity_); | ||
| char* buf = static_cast<char*>(malloc(dynamic_capacity_)); | ||
| RELEASE_ASSERT(buf != nullptr, ""); | ||
| memcpy(buf, buffer_.ref_, string_length_); | ||
|
|
@@ -90,19 +108,18 @@ void HeaderString::append(const char* data, uint32_t size) { | |
| case Type::Dynamic: { | ||
| // We can get here either because we didn't fit in inline or we are already dynamic. | ||
| if (type_ == Type::Inline) { | ||
| const uint64_t new_capacity = (static_cast<uint64_t>(string_length_) + size) * 2; | ||
| // If the resizing will cause buffer overflow due to hitting uint32_t::max, an OOM is likely | ||
| // imminent. Fast-fail rather than allow a buffer overflow attack (issue #1421) | ||
| RELEASE_ASSERT(new_capacity <= std::numeric_limits<uint32_t>::max(), ""); | ||
| const size_t new_capacity = (string_length_ + size) * 2; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wow, thanks for doing the validation - sorry I missed this in #4211 Unfortunately I don't think this size_t does what we want it to do - I think we need to static_cast string_length to size_t to not do this as a unit32_t. Also on 122 we're definitely setting to a unit32_t before checking (which is totally a preexisting bug but as long as you're making our code safer I get to suggest we make it safer-er =P). Honestly I can't do this stuff by eye either, so I cheated and wrote some death test to sanity check things and validate our validation :-) |
||
| validateCapacity(new_capacity); | ||
| buffer_.dynamic_ = static_cast<char*>(malloc(new_capacity)); | ||
| memcpy(buffer_.dynamic_, inline_buffer_, string_length_); | ||
| RELEASE_ASSERT(buffer_.dynamic_ != nullptr, ""); | ||
| memcpy(buffer_.dynamic_, inline_buffer_, string_length_); | ||
| dynamic_capacity_ = new_capacity; | ||
| type_ = Type::Dynamic; | ||
| } else { | ||
| if (size + 1 + string_length_ > dynamic_capacity_) { | ||
| // Need to reallocate. | ||
| dynamic_capacity_ = (string_length_ + size) * 2; | ||
| validateCapacity(dynamic_capacity_); | ||
| buffer_.dynamic_ = static_cast<char*>(realloc(buffer_.dynamic_, dynamic_capacity_)); | ||
| RELEASE_ASSERT(buffer_.dynamic_ != nullptr, ""); | ||
| } | ||
|
|
@@ -153,14 +170,18 @@ void HeaderString::setCopy(const char* data, uint32_t size) { | |
| // We can get here either because we didn't fit in inline or we are already dynamic. | ||
| if (type_ == Type::Inline) { | ||
| dynamic_capacity_ = size * 2; | ||
| validateCapacity(dynamic_capacity_); | ||
| buffer_.dynamic_ = static_cast<char*>(malloc(dynamic_capacity_)); | ||
| RELEASE_ASSERT(buffer_.dynamic_ != nullptr, ""); | ||
| type_ = Type::Dynamic; | ||
| } else { | ||
| if (size + 1 > dynamic_capacity_) { | ||
| // Need to reallocate. Use free/malloc to avoid the copy since we are about to overwrite. | ||
| dynamic_capacity_ = size * 2; | ||
| validateCapacity(dynamic_capacity_); | ||
| free(buffer_.dynamic_); | ||
| buffer_.dynamic_ = static_cast<char*>(malloc(dynamic_capacity_)); | ||
| RELEASE_ASSERT(buffer_.dynamic_ != nullptr, ""); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -182,8 +203,14 @@ void HeaderString::setInteger(uint64_t value) { | |
| } | ||
|
|
||
| case Type::Inline: | ||
| // buffer_.dynamic_ should always point at inline_buffer_ for Type::Inline. | ||
| ASSERT(buffer_.dynamic_ == inline_buffer_); | ||
| case Type::Dynamic: { | ||
| // Whether dynamic or inline the buffer is guaranteed to be large enough. | ||
| ASSERT(type_ == Type::Inline || dynamic_capacity_ >= MaxIntegerLength); | ||
| // It's safe to use buffer.dynamic_, since buffer.ref_ is union aliased. | ||
| // This better not change without verifying assumptions across this file. | ||
| static_assert(offsetof(Buffer, dynamic_) == offsetof(Buffer, ref_), ""); | ||
| string_length_ = StringUtil::itoa(buffer_.dynamic_, 32, value); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { set_reference { } } actions { } actions { get_and_mutate { append: "" } } actions { } actions { get_and_mutate { set_integer: 0 } } actions { } actions { } actions { } actions { } actions { } actions { } actions { } actions { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You use
size_there, but below the same calculation below usesstatic_cast<uint64_t>. They should be the same, but it might be good to be consistent here, probably standardizing onsize_t.