Skip to content

Commit 5dec0de

Browse files
authored
fuzz: don't panic when the proxy closes the conn (#986)
Currently, the inbound HTTP fuzz tests are failing. This is because they reuse the test-support code for making an HTTP request to a proxy stack and running the futures necessary to drive that request in the background. This code currently unwraps both the `JoinHandle`s of the spawned tasks (which would be `Err` if the task panicked) _and_ the returned `Result` from those `JoinHandle`s (which is an `Err` if the `Service::call` future returned an error, or if the client returned an error). If the future completes with an error, then the proxy simply tears down the connection. In the integration tests, we currently `expect` _both_ of these `Results` --- since the inputs are valid, we want to assert the proxy doesn't return an error incorrectly. However, the fuzz tests can and will generate malformed HTTP requests, and in this case, the proxy will reject those requests by returning an error and closing the connection. This is *not* incorrect behavior. Instead, we want to ensure that the proxy doesn't panic in the face of potentially malformed requests. This branch changes the test-support HTTP code to return the `Result` of serving a request, and unwrap it in the integration tests rather than in the support code. The fuzz logic is updated to simply log errors returned here, since returning an error is expected behavior when we receive invalid inputs. If we wanted to be _really_ fancy, a potential follow-up would be to extend the fuzz logic to determine whether or not a fuzz spec should result in an error, and assert that errors are only returned for invalid requests...but, doing this without using any of the code that's being exercised in the fuzz test (e.g. all of `hyper`'s request parsing etc) would be a _lot_ of work... Depends on #985 Signed-off-by: Eliza Weisman <eliza@buoyant.io>
1 parent 826655d commit 5dec0de

File tree

4 files changed

+30
-19
lines changed

4 files changed

+30
-19
lines changed

linkerd/app/inbound/src/http/mod.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ pub mod fuzz_logic {
323323
.body(Body::default())
324324
{
325325
let rsp = http_util::http_request(&mut client, req).await;
326+
tracing::info!(?rsp);
326327
let _body = http_util::body_to_string(rsp.into_body()).await;
327328
}
328329
}
@@ -331,7 +332,13 @@ pub mod fuzz_logic {
331332
}
332333

333334
drop(client);
334-
bg.await;
335+
// It's okay if the background task returns an error, as this would
336+
// indcate that the proxy closed the connection --- which it will do on
337+
// invalid inputs. We want to ensure that the proxy doesn't crash in the
338+
// face of these inputs, and the background task will panic in this
339+
// case.
340+
let res = bg.await;
341+
tracing::info!(?res, "background tasks completed")
335342
}
336343

337344
fn hello_fuzz_server(

linkerd/app/inbound/src/http/tests.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ async fn unmeshed_http1_hello_world() {
8787
assert_eq!(body, "Hello world!");
8888

8989
drop(client);
90-
bg.await;
90+
bg.await.expect("background task failed");
9191
}
9292

9393
#[tokio::test(flavor = "current_thread")]
@@ -136,7 +136,7 @@ async fn downgrade_origin_form() {
136136
assert_eq!(body, "Hello world!");
137137

138138
drop(client);
139-
bg.await;
139+
bg.await.expect("background task failed");
140140
}
141141

142142
#[tokio::test(flavor = "current_thread")]
@@ -184,7 +184,7 @@ async fn downgrade_absolute_form() {
184184
assert_eq!(body, "Hello world!");
185185

186186
drop(client);
187-
bg.await;
187+
bg.await.expect("background task failed");
188188
}
189189

190190
#[tracing::instrument]

linkerd/app/outbound/src/http/tests.rs

+11-5
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ async fn meshed_hello_world() {
278278
assert_eq!(body, "Hello world!");
279279

280280
drop(client);
281-
bg.await;
281+
bg.await.expect("background task failed");
282282
}
283283

284284
#[tokio::test(flavor = "current_thread")]
@@ -338,7 +338,7 @@ async fn stacks_idle_out() {
338338
assert_eq!(body, "Hello world!");
339339

340340
drop(client);
341-
bg.await;
341+
bg.await.expect("background task failed");
342342

343343
assert_eq!(handle.tracked_services(), 1);
344344
// wait for long enough to ensure that it _definitely_ idles out...
@@ -438,8 +438,14 @@ async fn active_stacks_dont_idle_out() {
438438
tokio::time::sleep(idle_timeout * 2).await;
439439
assert_eq!(handle.tracked_services(), 0);
440440

441-
client_bg.await.unwrap();
442-
proxy_bg.await.unwrap();
441+
client_bg
442+
.await
443+
.unwrap()
444+
.expect("client background task failed");
445+
proxy_bg
446+
.await
447+
.unwrap()
448+
.expect("proxy background task failed");
443449
}
444450

445451
async fn unmeshed_hello_world(
@@ -470,7 +476,7 @@ async fn unmeshed_hello_world(
470476
assert_eq!(body, "Hello world!");
471477

472478
drop(client);
473-
bg.await;
479+
bg.await.expect("background task failed");
474480
}
475481

476482
#[tracing::instrument]

linkerd/app/test/src/http_util.rs

+8-10
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ impl Default for Server {
3636
}
3737
}
3838

39-
pub async fn run_proxy<S>(mut server: S) -> (io::DuplexStream, JoinHandle<()>)
39+
pub async fn run_proxy<S>(mut server: S) -> (io::DuplexStream, JoinHandle<Result<(), Error>>)
4040
where
4141
S: tower::Service<io::DuplexStream> + Send + Sync + 'static,
4242
S::Error: Into<Error>,
@@ -56,7 +56,7 @@ where
5656
drop(server);
5757
tracing::debug!("dropped server");
5858
tracing::info!(?res, "proxy serve task complete");
59-
res.expect("proxy failed");
59+
res.map(|_| ())
6060
}
6161
.instrument(tracing::info_span!("proxy"));
6262
(client_io, tokio::spawn(proxy))
@@ -65,15 +65,15 @@ where
6565
pub async fn connect_client(
6666
client_settings: &mut ClientBuilder,
6767
io: io::DuplexStream,
68-
) -> (SendRequest<Body>, JoinHandle<()>) {
68+
) -> (SendRequest<Body>, JoinHandle<Result<(), Error>>) {
6969
let (client, conn) = client_settings
7070
.handshake(io)
7171
.await
7272
.expect("Client must connect");
7373
let client_bg = conn
7474
.map(|res| {
7575
tracing::info!(?res, "Client background complete");
76-
res.expect("client bg task failed");
76+
res.map_err(Into::into)
7777
})
7878
.instrument(tracing::info_span!("client_bg"));
7979
(client, tokio::spawn(client_bg))
@@ -82,7 +82,7 @@ pub async fn connect_client(
8282
pub async fn connect_and_accept<S>(
8383
client_settings: &mut ClientBuilder,
8484
server: S,
85-
) -> (SendRequest<Body>, impl Future<Output = ()>)
85+
) -> (SendRequest<Body>, impl Future<Output = Result<(), Error>>)
8686
where
8787
S: tower::Service<io::DuplexStream> + Send + Sync + 'static,
8888
S::Error: Into<Error>,
@@ -93,11 +93,9 @@ where
9393
let (client_io, proxy) = run_proxy(server).await;
9494
let (client, client_bg) = connect_client(client_settings, client_io).await;
9595
let bg = async move {
96-
let res = tokio::try_join! {
97-
proxy,
98-
client_bg,
99-
};
100-
res.unwrap();
96+
proxy.await.expect("proxy background task panicked")?;
97+
client_bg.await.expect("client background task panicked")?;
98+
Ok(())
10199
};
102100
(client, bg)
103101
}

0 commit comments

Comments
 (0)