Skip to content

[fuzz] split http filter logic into a fuzzing class#13016

Merged
htuch merged 2 commits intoenvoyproxy:masterfrom
asraa:refactor-http-test
Sep 11, 2020
Merged

[fuzz] split http filter logic into a fuzzing class#13016
htuch merged 2 commits intoenvoyproxy:masterfrom
asraa:refactor-http-test

Conversation

@asraa
Copy link
Contributor

@asraa asraa commented Sep 8, 2020

Signed-off-by: Asra Ali asraa@google.com

Commit Message: This splits out the "filter manager" logic that the HTTP filter fuzzer uses into a lightweight HttpFilterFuzzer class. The logic implemented control the flow for calling decode(Headers/Data/Trailers) and encode(Headers/Data/Trailers). Users of this class now create a filter, set callbacks, and then call HttpFilterFuzzer::runData(filter, fuzzed_data). See example (https://github.com/GoogleCloudPlatform/esp-v2/blob/e86330b106484eb3734dc207a3ab9419f9dcb903/src/envoy/http/path_matcher/filter_fuzz_test.cc#L59). It could be done before, but UberFilterFuzzer held much more state than necessary to apply to a dedicated fuzzer.

The UberFilterFuzzer extends this and holds the logic to generically create and set up filters. It uses this HttpFilterFuzzer library for the headers, data, trailer logic calls. Now all state needed for all filters is held in UberFilterFuzzer.

Risk Level: Low
Testing: filter_fuzz_test still works, this is a no-op

Follow-up PRs:

  • [docs/test] Add test/extensions/filters/http/common/fuzz/README.md for devs and build out the corpus for the generic fuzzer
  • [test] Apply the library to ext_authz for a dedicated fuzzer.

Signed-off-by: Asra Ali <asraa@google.com>
@asraa asraa changed the title [http] split http filter logic into a fuzzing class [fuzz] split http filter logic into a fuzzing class Sep 8, 2020
@asraa
Copy link
Contributor Author

asraa commented Sep 8, 2020

\cc @nareddyt This is my attempt at cleaning up the design for fuzzing http filters... We have a base HttpFilterFuzzer library that can truly be applied to any filter without extra mocks or any set-up needed. It just holds onto the filter decoding/encoding logic.
The UberFilterFuzzer is basically a user of this, and handles automatic generic filter creation.

If you import this it would mean changing UberFilterFuzzer to HttpFilterFuzzer. It should increase the speed of the fuzzer drastically. WDYT?

I could also implement these outside of a class, but I wouldn't be able to hold onto the headermaps for access logging. Open to suggestions.

@nareddyt
Copy link
Contributor

nareddyt commented Sep 8, 2020

This is great, thanks @asraa!

I was brainstorming a way of simplifying this a few months ago, but I was trying to use composition instead of inheritance and had some issues with holding onto the header maps. Your solution works really well and it seems clean to me.

I'll be sure to switch over ESPv2 (maybe in the next month) and try collect some metrics on the performance improvement. I'm curious how much improvement we'll see from removing mocks, though we might still have to mock the StreamInfo.

htuch
htuch previously approved these changes Sep 9, 2020
Copy link
Member

@htuch htuch 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! The comment applies to existing code.

return status;
}

template <>
Copy link
Member

Choose a reason for hiding this comment

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

Why are these all templated?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The logic of handling filter return status and end_stream values in runData is the common for encoders/decoder, so we meant to share that across both encoder/decoder despite different methods.

There were some alternatives considered here #11209, one of which was adding a lambda in for the correct header/data/trailer funcs (encodeHeaders, decodeHeaders), and having the runData use those lambda HeaderFunc, DataFunc, TrailerFunc depending on if it's encoder or decoder.

If I were to de-template this, it's not too much additional work. The lambdas (Header/Data/Trailer)Funcs are the template send(Header/Data/Trailer) specializations. I guess it also depends on what API is exposed to the fuzzer:
runData(FilterType, data) vs runEncode(encoder, data) and runDecode(decoder, data)
where runEncode(encoder, data) calls runData with the appropriate lambas. I was happy with both.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

quick ping?

Copy link
Contributor

Choose a reason for hiding this comment

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

FYI if we don't template these functions, then you have to duplicate all the code in runData, right?

void HttpFilterFuzzer::runData(FilterType* filter, const test::fuzz::HttpData& data) {
bool end_stream = false;
enabled_ = true;
if (data.body_case() == test::fuzz::HttpData::BODY_NOT_SET && !data.has_trailers()) {
end_stream = true;
}
const auto& headersStatus = sendHeaders(filter, data, end_stream);
ENVOY_LOG_MISC(debug, "Finished with FilterHeadersStatus: {}", headersStatus);
if ((headersStatus != Http::FilterHeadersStatus::Continue &&
headersStatus != Http::FilterHeadersStatus::StopIteration) ||
!enabled_) {
return;
}
const std::vector<std::string> data_chunks = Fuzz::parseHttpData(data);
for (size_t i = 0; i < data_chunks.size(); i++) {
if (!data.has_trailers() && i == data_chunks.size() - 1) {
end_stream = true;
}
Buffer::OwnedImpl buffer(data_chunks[i]);
const auto& dataStatus = sendData(filter, buffer, end_stream);
ENVOY_LOG_MISC(debug, "Finished with FilterDataStatus: {}", dataStatus);
if (dataStatus != Http::FilterDataStatus::Continue || !enabled_) {
return;
}
}
if (data.has_trailers() && enabled_) {
sendTrailers(filter, data);
}
}

Copy link
Contributor Author

@asraa asraa Sep 11, 2020

Choose a reason for hiding this comment

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

I don't think so. runData's signature could be
runData(FilterType* filter, const test::fuzz::HttpData& data, HeaderFunc header_func, DataFunc data_func, TrailerFunc trailer_func)
and runEncode(encoder, data) would call runData with the right funcs (the specializations of sendHeaders/sendData/sendTrailers)

Edit: maybe because of encoder/decoder typing there'd need to be type downcasting... I'll just give it a shot

@htuch htuch self-assigned this Sep 9, 2020
Signed-off-by: Asra Ali <asraa@google.com>
@htuch htuch merged commit 429d5e8 into envoyproxy:master Sep 11, 2020
lhluo pushed a commit to lhluo/envoy that referenced this pull request Sep 11, 2020
…code

* upstream/master:
  lint: add more linters for using absl:: over std:: (envoyproxy#13043)
  udpa: filesystem list collection support for inline entries. (envoyproxy#13028)
  filter: http: jwt: implement matching for HTTP CONNECT (envoyproxy#13064)
  [fuzz] split http filter logic into a fuzzing class (envoyproxy#13016)
  xds: allow empty delta update (envoyproxy#12699)
  CacheFilter: parses the allowed_vary_headers from the cache config. (envoyproxy#12928)
  router: extend HTTP CONNECT route matching criteria (envoyproxy#13056)
  docs: clarify use of Extended CONNECT for h/2 (envoyproxy#13051)
  build: shellcheck tools/ (envoyproxy#13007)
  [fuzz] Refactored Health Checker Impl Tests (envoyproxy#13017)

Signed-off-by: Lihao Luo <lihao@squareup.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants