Skip to content
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

HCM: guard Envoy from removal of critical response headers. #15658

Closed
wants to merge 37 commits into from

Conversation

mathetake
Copy link
Member

@mathetake mathetake commented Mar 25, 2021

Signed-off-by: Takeshi Yoneda takeshi@tetrate.io

Commit Message: HCM: guard Envoy from removal of critical response headers.
Additional Description: Previously, Envoy hasn't been guarded from the removal of critical response headers like :status. This happens when there's misbehaving filter chan, and especially this protection is important when users run their own extensions through Wasm. Resolves #13756 and ref: #15487.
For the request path, Envoy is already guarded by HeaderUtility::checkRequiredHeaders and its error is gracefully handled in the onPoolReady.

Risk Level: high (change in the core feature's critical code path)
Testing: unittest && integration test

The following is the current crash path with uncaught exception:

and we've never reached the assertion for this here:

ASSERT(headers.Status() != nullptr);

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
@mathetake mathetake changed the title HCM: guard Envoy from removal of cirical response headers. HCM: guard Envoy from removal of critical response headers. Mar 25, 2021
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
@mathetake mathetake marked this pull request as ready for review March 25, 2021 07:14
@mathetake
Copy link
Member Author

cc @asraa

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Copy link
Contributor

@asraa asraa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My 2 cents! Thank you so much for doing this!

source/common/http/header_utility.cc Outdated Show resolved Hide resolved
source/common/http/filter_manager.cc Outdated Show resolved Hide resolved
source/common/http/filter_manager.cc Outdated Show resolved Hide resolved
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Comment on lines 909 to 910
ASSERT(response_encoder_->encode100ContinueHeaders(continue_headers).ok());
chargeStats(continue_headers);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chargeStats assumes that the required headers are present, so we need to change the order here and elsewhere.

@mathetake
Copy link
Member Author

still crashes when it is head request 😕

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
@mathetake
Copy link
Member Author

OK, now all the crashes disappeared. Will work on the unit / integration tests.

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
filter_manager_callbacks_.encodeHeaders(headers, modified_end_stream);
state_.non_100_response_headers_encoded_ = true;
Copy link
Member Author

@mathetake mathetake Mar 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change is necessary since there's a check of non_100_response_headers_encoded_ != false in the local reply and it makes sense to keep that assertion.

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
@mathetake mathetake requested a review from asraa March 28, 2021 07:17
@asraa
Copy link
Contributor

asraa commented Mar 31, 2021

I thikn you could probably add an ASSERT(checkRequiredResponseHeaders()) after Alyssa's code to reset.

@mathetake
Copy link
Member Author

mathetake commented Apr 2, 2021

Our test added in this PR has passed for QUIC now, but

  • OverflowingResponseCode
  • MissingStatus

protocol tests have started failing for not only QUIC but also htt1,2. Will take a look.

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
@mathetake
Copy link
Member Author

mathetake commented Apr 2, 2021

OK I hope I fixed the problem. Our approach here would change the behavior slightly different from the one in #15648. That is the upstream client will never receive the RESET even if there's invalid status headers coming, and instead just send the 502 with 'protocol error'-ish message.

@mathetake
Copy link
Member Author

mathetake commented Apr 2, 2021

I hope the latest commit efdc243 explains well :)

@mathetake
Copy link
Member Author

@asraa @alyssawilk PTAL! :)

Copy link
Contributor

@asraa asraa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code changes by themselves look OK to me. I'm just not 100% sure if they should all be handled with reset like QUIC was.
@alyssawilk

Copy link
Contributor

@alyssawilk alyssawilk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tackling this! It'll be great to have Envoy even more robust.

include/envoy/http/codec.h Show resolved Hide resolved
source/common/http/conn_manager_impl.cc Outdated Show resolved Hide resolved
source/common/http/conn_manager_impl.cc Outdated Show resolved Hide resolved
source/common/http/conn_manager_impl.cc Show resolved Hide resolved
// filter_manager_callbacks_.streamEnded() returns True here when the codec failed to encode
// headers due to the lack of required response headers, and filter_manager_callbacks already sent
// the local reply and ended the stream by itself. So in that case this function must be no-op.
if (end_stream && !filter_manager_callbacks_.streamEnded()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where are we now calling maybeEndEncode after the stream already ended?
I'm not sure but my concern is we may have a problem we should be solving before we reach this point.

Copy link
Member Author

@mathetake mathetake Apr 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is when the local reply is sent (due to the missing requires response headers) in the ConnectionManagerImpl::ActiveStream::encodeHeaders as filter_manager_callbacks.encodeHeaders, and the filter manager then reaches maybeEndEncode.

filter_manager_callbacks_.encodeHeaders(headers, modified_end_stream);
state_.non_100_response_headers_encoded_ = true;
maybeEndEncode(modified_end_stream);

But yeah I think this made the code a little bit more complex so maybe we should just return the status from filter_manager_callbacks and send the LocalReply just before maybeEndEncode in FilterManager..

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT?

Copy link
Contributor

@alyssawilk alyssawilk Apr 8, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think I'd rather have the HCM encodeHeaders return a status, so the filter manager knows something has gone awry.
I think it's useful to retain checks in maybeEndEncode happening once to catch other weird corner case bugs.
cc @snowp for a second opinion.

test/common/http/conn_manager_impl_test.cc Outdated Show resolved Hide resolved
encoder_.encodeHeaders(*headers_copy, end_stream);
const auto status = encoder_.encodeHeaders(*headers_copy, end_stream);
// simulate the connection manager's behavior to handle encoding failures.
if (!status.ok()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a problem.
Is this added to fix the integration tests we had that Envoy successfully handed upstream responses with no status? I think it's now testing the upstream works, not that Envoy works, and we need some way to force the encoder to encode this for tests, even if it looks invalid.

I think this is why coverage is now failing, because we're no longer testing handling bad responses from upstream.

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
@mathetake
Copy link
Member Author

I'm waiting for the reply to #15658 (comment) since it might affect the other parts

@mathetake
Copy link
Member Author

will work on the fix of test after the comment https://github.com/envoyproxy/envoy/pull/15658/files#r608174940 has reached consensus since it also affects the other parts

@github-actions
Copy link

github-actions bot commented May 8, 2021

This pull request has been automatically marked as stale because it has not had activity in the last 30 days. It will be closed in 7 days if no further activity occurs. Please feel free to give a status update now, ping for review, or re-open when it's ready. Thank you for your contributions!

@github-actions github-actions bot added the stale stalebot believes this issue/PR has not been touched recently label May 8, 2021
@github-actions
Copy link

This pull request has been automatically closed because it has not had activity in the last 37 days. Please feel free to give a status update now, ping for review, or re-open when it's ready. Thank you for your contributions!

@github-actions github-actions bot closed this May 16, 2021
alyssawilk pushed a commit that referenced this pull request Jun 2, 2021
… chain. (#16745)

This is the rework of the previous PR (#15658).

Additional Description: Previously, Envoy hasn't been guarded from the removal of critical response headers like :status. This happens when there's misbehaving filter chan, and especially this protection is important when users run their own extensions through Wasm. Resolves #13756 and ref: #15487.
Testing: new integration tests
Risk: low

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
@mathetake mathetake deleted the status-header branch June 29, 2021 04:18
leyao-daily pushed a commit to leyao-daily/envoy that referenced this pull request Sep 30, 2021
… chain. (envoyproxy#16745)

This is the rework of the previous PR (envoyproxy#15658).

Additional Description: Previously, Envoy hasn't been guarded from the removal of critical response headers like :status. This happens when there's misbehaving filter chan, and especially this protection is important when users run their own extensions through Wasm. Resolves envoyproxy#13756 and ref: envoyproxy#15487.
Testing: new integration tests
Risk: low

Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
stale stalebot believes this issue/PR has not been touched recently
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Gracefully handle removal of critical headers by filters
3 participants