Skip to content

Commit b234fcc

Browse files
authored
contrib/golang: address race condition in Go HTTP filter (#44429)
1 parent de48687 commit b234fcc

5 files changed

Lines changed: 99 additions & 1 deletion

File tree

contrib/golang/filters/http/source/go/pkg/http/shim.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,9 @@ func getRequest(r *C.httpRequest) *httpRequest {
157157
func getState(s *C.processState) *processState {
158158
r := s.req
159159
req := getRequest(r)
160+
if req == nil {
161+
return nil
162+
}
160163
if s.is_encoding == 0 {
161164
return &req.decodingState.processState
162165
}
@@ -237,6 +240,10 @@ func envoyGoFilterOnHttpHeader(s *C.processState, endStream, headerNum, headerBy
237240
//export envoyGoFilterOnHttpData
238241
func envoyGoFilterOnHttpData(s *C.processState, endStream, buffer, length uint64) uint64 {
239242
state := getState(s)
243+
if state == nil {
244+
// safe to do as the C++ side hasDestroyed() check prevents acting on the returned value
245+
return uint64(api.Continue)
246+
}
240247

241248
req := state.request
242249
if req.pInfo.paniced {
@@ -389,6 +396,9 @@ func envoyGoFilterOnHttpDestroy(r *C.httpRequest, reason uint64) {
389396
//export envoyGoRequestSemaDec
390397
func envoyGoRequestSemaDec(r *C.httpRequest) {
391398
req := getRequest(r)
399+
if req == nil {
400+
return
401+
}
392402
defer req.recoverPanic()
393403
req.resumeWaitCallback()
394404
}

contrib/golang/filters/http/source/golang_filter.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,12 +404,19 @@ void Filter::continueStatusInternal(ProcessorState& state, GolangStatus status)
404404

405405
case FilterState::ProcessingTrailer:
406406
state.continueDoData();
407+
if (hasDestroyed()) {
408+
return;
409+
}
407410
state.continueProcessing();
408411
break;
409412

410413
default:
411414
ASSERT(0, "unexpected state");
412415
}
416+
417+
if (hasDestroyed()) {
418+
return;
419+
}
413420
}
414421

415422
ENVOY_LOG(debug,
@@ -426,6 +433,9 @@ void Filter::continueStatusInternal(ProcessorState& state, GolangStatus status)
426433
auto done = doDataGo(state, state.getBufferData(), state.getEndStream());
427434
if (done) {
428435
state.continueDoData();
436+
if (hasDestroyed()) {
437+
return;
438+
}
429439
} else {
430440
// do not process trailers when data is not finished
431441
return;

contrib/golang/filters/http/source/golang_filter.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,8 @@ class Filter : public Http::StreamFilter,
348348
void deferredDeleteRequest(HttpRequestInternal* req);
349349

350350
private:
351+
friend class TestFilter;
352+
351353
bool hasDestroyed() {
352354
Thread::LockGuard lock(mutex_);
353355
return has_destroyed_;

contrib/golang/filters/http/test/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ envoy_cc_test(
3333
],
3434
rbe_pool = "6gig",
3535
deps = [
36+
"//contrib/golang/common/dso/test:dso_mocks",
3637
"//contrib/golang/filters/http/source:golang_filter_lib",
38+
"//source/common/network:address_lib",
3739
"//source/common/stream_info:stream_info_lib",
3840
"//test/mocks/api:api_mocks",
3941
"//test/mocks/http:http_mocks",

contrib/golang/filters/http/test/golang_filter_test.cc

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "source/common/buffer/buffer_impl.h"
77
#include "source/common/http/message_impl.h"
8+
#include "source/common/network/address_impl.h"
89
#include "source/common/stream_info/stream_info_impl.h"
910

1011
#include "test/common/stats/stat_test_utility.h"
@@ -21,25 +22,32 @@
2122
#include "test/test_common/utility.h"
2223

2324
#include "absl/strings/str_format.h"
25+
#include "contrib/golang/common/dso/test/mocks.h"
2426
#include "contrib/golang/filters/http/source/golang_filter.h"
2527
#include "gmock/gmock.h"
2628

2729
using testing::_;
2830
using testing::AtLeast;
2931
using testing::InSequence;
3032
using testing::Invoke;
33+
using testing::Return;
34+
using testing::SaveArg;
3135

3236
namespace Envoy {
3337
namespace Extensions {
3438
namespace HttpFilters {
3539
namespace Golang {
36-
namespace {
3740

3841
class TestFilter : public Filter {
3942
public:
43+
using Filter::continueStatusInternal;
4044
using Filter::Filter;
45+
DecodingProcessorState& testDecodingState() { return decoding_state_; }
46+
HttpRequestInternal* testReq() { return req_; }
4147
};
4248

49+
namespace {
50+
4351
class GolangHttpFilterTest : public testing::Test {
4452
public:
4553
GolangHttpFilterTest() {
@@ -191,6 +199,72 @@ TEST_F(GolangHttpFilterTest, InvalidConfigForRouteConfigFilter) {
191199
"golang filter failed to parse plugin config");
192200
}
193201

202+
// Regression test for https://github.com/envoyproxy/envoy/issues/44320.
203+
TEST_F(GolangHttpFilterTest, BufferedDataAfterDestroyDuringContinue) {
204+
auto dso_lib = std::make_shared<NiceMock<Dso::MockHttpFilterDsoImpl>>();
205+
ON_CALL(*dso_lib, envoyGoFilterNewHttpPluginConfig(_)).WillByDefault(Return(1));
206+
ON_CALL(*dso_lib, envoyGoFilterOnHttpHeader(_, _, _, _))
207+
.WillByDefault(Return(static_cast<uint64_t>(GolangStatus::Running)));
208+
209+
bool destroyed = false;
210+
bool data_called_after_destroy = false;
211+
ON_CALL(*dso_lib, envoyGoFilterOnHttpData(_, _, _, _))
212+
.WillByDefault(Invoke(
213+
[&destroyed, &data_called_after_destroy](processState*, GoUint64, GoUint64, GoUint64) {
214+
if (destroyed) {
215+
data_called_after_destroy = true;
216+
}
217+
return static_cast<uint64_t>(GolangStatus::Continue);
218+
}));
219+
ON_CALL(*dso_lib, envoyGoFilterOnHttpDestroy(_, _))
220+
.WillByDefault(Invoke([&destroyed](httpRequest*, int) { destroyed = true; }));
221+
222+
const auto yaml = R"EOF(
223+
library_id: test
224+
library_path: test
225+
plugin_name: test
226+
)EOF";
227+
envoy::extensions::filters::http::golang::v3alpha::Config proto_config;
228+
TestUtility::loadFromYaml(yaml, proto_config);
229+
NiceMock<Server::Configuration::MockFactoryContext> mock_context;
230+
auto config = std::make_shared<FilterConfig>(proto_config, dso_lib, "", mock_context);
231+
config->newGoPluginConfig();
232+
233+
Network::Address::InstanceConstSharedPtr addr(
234+
(*Network::Address::PipeInstance::create("/test/test.sock")).release());
235+
NiceMock<Http::MockStreamDecoderFilterCallbacks> mock_callbacks;
236+
NiceMock<Http::MockStreamEncoderFilterCallbacks> mock_enc_callbacks;
237+
NiceMock<Envoy::Network::MockConnection> mock_connection;
238+
ON_CALL(mock_callbacks, connection())
239+
.WillByDefault(Return(OptRef<const Network::Connection>{mock_connection}));
240+
mock_connection.stream_info_.downstream_connection_info_provider_->setRemoteAddress(addr);
241+
mock_connection.stream_info_.downstream_connection_info_provider_->setLocalAddress(addr);
242+
EXPECT_CALL(mock_callbacks.dispatcher_, isThreadSafe()).WillRepeatedly(Return(true));
243+
244+
auto filter = std::make_shared<TestFilter>(config, dso_lib, 0);
245+
filter->setDecoderFilterCallbacks(mock_callbacks);
246+
filter->setEncoderFilterCallbacks(mock_enc_callbacks);
247+
248+
Http::TestRequestHeaderMapImpl request_headers{{":path", "/"}};
249+
EXPECT_EQ(Http::FilterHeadersStatus::StopIteration,
250+
filter->decodeHeaders(request_headers, false));
251+
252+
Buffer::OwnedImpl body("request body");
253+
EXPECT_EQ(Http::FilterDataStatus::StopIterationNoBuffer, filter->decodeData(body, false));
254+
255+
EXPECT_CALL(mock_callbacks, continueDecoding()).WillOnce(Invoke([&filter]() {
256+
filter->onDestroy();
257+
}));
258+
259+
filter->continueStatusInternal(filter->testDecodingState(), GolangStatus::Continue);
260+
261+
EXPECT_FALSE(data_called_after_destroy)
262+
<< "envoyGoFilterOnHttpData must not be called after onDestroy";
263+
264+
ASSERT_NE(nullptr, filter->testReq());
265+
delete filter->testReq();
266+
}
267+
194268
} // namespace
195269
} // namespace Golang
196270
} // namespace HttpFilters

0 commit comments

Comments
 (0)