-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
headers: fixing fast fail of size-validation #4269
Changes from 6 commits
176472e
f668193
f39ec4c
8745ebd
580e760
d3ffa9d
eefc16f
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 |
---|---|---|
|
@@ -19,10 +19,15 @@ 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) { | ||
uint64_t newCapacity(uint32_t existing_capacity, uint32_t size_to_append) { | ||
return (static_cast<uint64_t>(existing_capacity) + size_to_append) * 2; | ||
} | ||
|
||
void validateCapacity(uint64_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(), ""); | ||
RELEASE_ASSERT(new_capacity <= std::numeric_limits<uint32_t>::max(), | ||
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. std::numeric_limits::max() returns type T; should that part be cast to size_t before comparison? |
||
"Trying to allocate overly large headers."); | ||
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. Should there be a TODO here for a more graceful failure handling? Or is dying the only appropriate action at this point? 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. Dying before overflow is appropriate but I agree I'd like something before that. I've had this discussion with Matt before. For some context on how headers get added it's I think if each header input has at least some scoped / sane limits, having a relesae assert for bugs is Ok-but-not-great. What I'd prefer is that we have a soft-fail option a lot earlier - a configured max header limit where instead of appending headers we do the logical equivalent of an internal GFE_BUG macro to note the error for both logging and monitoring, but not kill Envoy except on configuration of a build-in bugs-should-be-fatal flag for the benefit of folks on the fail-fast fail-hard side of the fence. This is on my list of things to do to secure Envoy before widespread use for my project, but not high enough up I've filed an issue tracker for it. |
||
ASSERT(new_capacity >= MinDynamicCapacity); | ||
} | ||
|
||
|
@@ -86,9 +91,13 @@ 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_ = | ||
std::max(MinDynamicCapacity, static_cast<size_t>((string_length_ + size) * 2)); | ||
validateCapacity(dynamic_capacity_); | ||
const uint64_t new_capacity = newCapacity(string_length_, size); | ||
if (new_capacity > MinDynamicCapacity) { | ||
validateCapacity(new_capacity); | ||
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. For some reason, my comment was lost here. I was wondering if we could move |
||
dynamic_capacity_ = new_capacity; | ||
} else { | ||
dynamic_capacity_ = MinDynamicCapacity; | ||
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. Yep, much cleaner now, thanks. |
||
} | ||
char* buf = static_cast<char*>(malloc(dynamic_capacity_)); | ||
RELEASE_ASSERT(buf != nullptr, ""); | ||
memcpy(buf, buffer_.ref_, string_length_); | ||
|
@@ -97,7 +106,8 @@ void HeaderString::append(const char* data, uint32_t size) { | |
} | ||
|
||
case Type::Inline: { | ||
if (size + 1 + string_length_ <= sizeof(inline_buffer_)) { | ||
const uint64_t new_capacity = static_cast<uint64_t>(size) + 1 + string_length_; | ||
if (new_capacity <= sizeof(inline_buffer_)) { | ||
// Already inline and the new value fits in inline storage. | ||
break; | ||
} | ||
|
@@ -108,7 +118,7 @@ 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 size_t new_capacity = (string_length_ + size) * 2; | ||
const uint64_t new_capacity = newCapacity(string_length_, size); | ||
validateCapacity(new_capacity); | ||
buffer_.dynamic_ = static_cast<char*>(malloc(new_capacity)); | ||
RELEASE_ASSERT(buffer_.dynamic_ != nullptr, ""); | ||
|
@@ -117,9 +127,11 @@ void HeaderString::append(const char* data, uint32_t size) { | |
type_ = Type::Dynamic; | ||
} else { | ||
if (size + 1 + string_length_ > dynamic_capacity_) { | ||
const uint64_t new_capacity = newCapacity(string_length_, size); | ||
validateCapacity(new_capacity); | ||
|
||
// Need to reallocate. | ||
dynamic_capacity_ = (string_length_ + size) * 2; | ||
validateCapacity(dynamic_capacity_); | ||
dynamic_capacity_ = new_capacity; | ||
buffer_.dynamic_ = static_cast<char*>(realloc(buffer_.dynamic_, dynamic_capacity_)); | ||
RELEASE_ASSERT(buffer_.dynamic_ != nullptr, ""); | ||
} | ||
|
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.
Nit: you could make
existing_capacity
auint64_t
to avoid the cast, but this doesn't really matter.