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

Avoid send empty body to ext_proc server if decodeData() never called #28672

Merged
merged 4 commits into from
Aug 3, 2023

Conversation

yanjunxiang-google
Copy link
Contributor

@yanjunxiang-google yanjunxiang-google commented Jul 27, 2023

If decodeData() is not called, currently ext_proc filter is still possible to send an empty body to ext_proc server with

sendBufferedData(state, ProcessorState::CallbackState::BufferedBodyCallback, true);

and

sendBodyChunk(state, data, new_state, end_stream);

After body mutation from ext_proc server is received by ext_proc filter, it will modify the decoding buffer, which leads to an ASSERT() in filter manager
common/http/filter_manager.cc:426] assert failure: parent_.state_.latest_data_decoding_filter_ == this

The condition to trigger this ASSERT is ext_proc filter is configured to send body, but client only sends headers and trailers.

Commit Message:
Additional Description:
Risk Level:
Testing:
Docs Changes:
Release Notes:
Platform Specific Features:
[Optional Runtime guard:]
[Optional Fixes #Issue]
[Optional Fixes commit #PR or SHA]
[Optional Deprecated:]
[Optional API Considerations:]

Signed-off-by: Yanjun Xiang <yanjunxiang@google.com>
@repokitteh-read-only
Copy link

CC @envoyproxy/api-shepherds: Your approval is needed for changes made to (api/envoy/|docs/root/api-docs/).
envoyproxy/api-shepherds assignee is @mattklein123
CC @envoyproxy/api-watchers: FYI only for changes made to (api/envoy/|docs/root/api-docs/).

🐱

Caused by: #28672 was opened by yanjunxiang-google.

see: more, trace.

@yanjunxiang-google yanjunxiang-google marked this pull request as draft July 27, 2023 23:45
@yanjunxiang-google
Copy link
Contributor Author

The solution is to add a bufferedData() check before sending body to ext_proc server. bufferedData() will only become valid pointer if decodeData() is every called.

If no data received, and decodeData() is never called, it will be nullptr.

Signed-off-by: Yanjun Xiang <yanjunxiang@google.com>
Signed-off-by: Yanjun Xiang <yanjunxiang@google.com>
@yanjunxiang-google
Copy link
Contributor Author

/retest

@yanjunxiang-google yanjunxiang-google marked this pull request as ready for review July 28, 2023 13:40
@yanjunxiang-google
Copy link
Contributor Author

/assign @tyxia @yanavlasov

…_body_add

Signed-off-by: Yanjun Xiang <yanjunxiang@google.com>
@mattklein123 mattklein123 assigned yanavlasov and tyxia and unassigned mattklein123 Jul 28, 2023
@yanjunxiang-google
Copy link
Contributor Author

/assign @stevenzzzz

@yanjunxiang-google yanjunxiang-google changed the title Avoid send empty body to ext_proc server if decodeData() not called Avoid send empty body to ext_proc server if decodeData() never called Jul 29, 2023
@@ -463,7 +463,7 @@ FilterTrailersStatus Filter::onTrailers(ProcessorState& state, Http::HeaderMap&
return FilterTrailersStatus::StopIteration;
}

if (!body_delivered && state.bodyMode() == ProcessingMode::BUFFERED) {
if (!body_delivered && state.bufferedData() && state.bodyMode() == ProcessingMode::BUFFERED) {
Copy link
Contributor Author

@yanjunxiang-google yanjunxiang-google Jul 29, 2023

Choose a reason for hiding this comment

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

The other utility function hasBufferedData() can not be used here since it not only check bufferedData() not nullptr, also checks the data->length >0. Using hasBufferedData() here will break the decodeData() with an empty chunk case.

Copy link
Member

@tyxia tyxia Aug 1, 2023

Choose a reason for hiding this comment

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

QQ: I am wondering when do we need to decodeData() with an empty chunk case? What are the use cases for this?

Copy link
Contributor

Choose a reason for hiding this comment

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

bufferedData()!=nullptr

@yanjunxiang-google
Copy link
Contributor Author

yanjunxiang-google commented Jul 29, 2023

This is the crash log and crash traceback decode:

test_ext_proc copy.odt

@yanjunxiang-google
Copy link
Contributor Author

The root cause of the issue is this:

If ext_proc filter is configured to send body, and if only headers and trailers are received, no body is received by the ext_proc filter, it will send an empty boy to the ext_proc server.
The ext_proc server will mutate the body, and send back a body response. The body response will modify the decoding buffer, which trigger the ASSERT there:

ASSERT(parent_.state_.latest_data_decoding_filter_ == this);

So, filter_manager carefully maintain state_.latest_data_decoding_filter_ during decodeData():

recordLatestDataFilter(entry, state_.latest_data_decoding_filter_, decoder_filters_);

In this case, decodeData() is never called due to client is not sending data, and data is constructed by ext_proc filter itself. That leads to latest_data_decoding_filter_ is an empty pointer not pointing the ext_proc filter

Thus the ASSERT failed, and crashed there.

Overall, the ext_proc filter should not send data based on the ext_proc filter configuration. It should only send data if the data presents. Concept wise, this issue is similar to sending trailer when trailer is not present case : #28592

@yanjunxiang-google
Copy link
Contributor Author

/assign @mattklein123

@yanjunxiang-google
Copy link
Contributor Author

Kind Ping

Copy link
Member

@tyxia tyxia left a comment

Choose a reason for hiding this comment

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

Nice work!

@@ -463,7 +463,7 @@ FilterTrailersStatus Filter::onTrailers(ProcessorState& state, Http::HeaderMap&
return FilterTrailersStatus::StopIteration;
}

if (!body_delivered && state.bodyMode() == ProcessingMode::BUFFERED) {
if (!body_delivered && state.bufferedData() && state.bodyMode() == ProcessingMode::BUFFERED) {
Copy link
Member

@tyxia tyxia Aug 1, 2023

Choose a reason for hiding this comment

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

QQ: I am wondering when do we need to decodeData() with an empty chunk case? What are the use cases for this?

Copy link
Member

@tyxia tyxia left a comment

Choose a reason for hiding this comment

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

LGTM, Thanks!

Copy link
Contributor

@stevenzzzz stevenzzzz left a comment

Choose a reason for hiding this comment

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

The "skipping body" approach in this PR feels like a behavior change that we can avoid by calling de/encodeData(dataMutations, false); on receiving the data mutation response from ext_server.

But no strong feelings.

@@ -463,7 +463,7 @@ FilterTrailersStatus Filter::onTrailers(ProcessorState& state, Http::HeaderMap&
return FilterTrailersStatus::StopIteration;
}

if (!body_delivered && state.bodyMode() == ProcessingMode::BUFFERED) {
if (!body_delivered && state.bufferedData() && state.bodyMode() == ProcessingMode::BUFFERED) {
Copy link
Contributor

Choose a reason for hiding this comment

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

bufferedData()!=nullptr

@yanjunxiang-google
Copy link
Contributor Author

yanjunxiang-google commented Aug 1, 2023

The "skipping body" approach in this PR feels like a behavior change that we can avoid by calling de/encodeData(dataMutations, false); on receiving the data mutation response from ext_server.

But no strong feelings.

Thanks for the comments!

The ext_proc behavior is now changed into "if configured as such, deliver body/trailer to the ext_proc server if they are present".

@yanjunxiang-google
Copy link
Contributor Author

@envoyproxy/api-shepherds PTAL

@yanavlasov
Copy link
Contributor

API change is comment only.

@yanavlasov yanavlasov merged commit 5e4f350 into envoyproxy:main Aug 3, 2023
115 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants