Handle malformed HEADERS

This commit is contained in:
Carl Lerche
2017-08-25 13:00:42 -07:00
parent 9d45255c75
commit 14f35f1be6
12 changed files with 155 additions and 54 deletions

View File

@@ -186,14 +186,16 @@ impl Headers {
-> Result<(), Error> -> Result<(), Error>
{ {
let mut reg = false; let mut reg = false;
let mut err = false; let mut malformed = false;
macro_rules! set_pseudo { macro_rules! set_pseudo {
($field:ident, $val:expr) => {{ ($field:ident, $val:expr) => {{
if reg { if reg {
err = true; trace!("load_hpack; header malformed -- pseudo not at head of block");
malformed = true;
} else if self.pseudo.$field.is_some() { } else if self.pseudo.$field.is_some() {
err = true; trace!("load_hpack; header malformed -- repeated pseudo");
malformed = true;
} else { } else {
self.pseudo.$field = Some($val); self.pseudo.$field = Some($val);
} }
@@ -212,8 +214,19 @@ impl Headers {
match header { match header {
Field { name, value } => { Field { name, value } => {
reg = true; // Connection level header fields are not supported and must
self.fields.append(name, value); // result in a protocol error.
if name == header::CONNECTION {
trace!("load_hpack; connection level header");
malformed = true;
} else if name == header::TE && value != "trailers" {
trace!("load_hpack; TE header not set to trailers; val={:?}", value);
malformed = true;
} else {
reg = true;
self.fields.append(name, value);
}
} }
Authority(v) => set_pseudo!(authority, v), Authority(v) => set_pseudo!(authority, v),
Method(v) => set_pseudo!(method, v), Method(v) => set_pseudo!(method, v),
@@ -228,9 +241,9 @@ impl Headers {
return Err(e.into()); return Err(e.into());
} }
if err { if malformed {
trace!("repeated pseudo"); trace!("malformed message");
return Err(hpack::DecoderError::RepeatedPseudo.into()); return Err(Error::MalformedMessage.into());
} }
Ok(()) Ok(())

View File

@@ -171,6 +171,9 @@ pub enum Error {
/// identifier other than zero. /// identifier other than zero.
InvalidStreamId, InvalidStreamId,
/// A request or response is malformed.
MalformedMessage,
/// An invalid stream dependency ID was provided /// An invalid stream dependency ID was provided
/// ///
/// This is returend if a HEADERS or PRIORITY frame is received with an /// This is returend if a HEADERS or PRIORITY frame is received with an

View File

@@ -35,7 +35,6 @@ pub enum DecoderError {
IntegerUnderflow, IntegerUnderflow,
IntegerOverflow, IntegerOverflow,
StringUnderflow, StringUnderflow,
RepeatedPseudo,
UnexpectedEndOfStream, UnexpectedEndOfStream,
} }

View File

@@ -53,9 +53,9 @@ impl<T, B> futures::Stream for Codec<T, B>
where T: AsyncRead, where T: AsyncRead,
{ {
type Item = Frame; type Item = Frame;
type Error = ConnectionError; type Error = ProtoError;
fn poll(&mut self) -> Poll<Option<Frame>, ConnectionError> { fn poll(&mut self) -> Poll<Option<Frame>, Self::Error> {
self.inner.poll() self.inner.poll()
} }
} }

View File

@@ -123,6 +123,7 @@ impl<T, P, B> Connection<T, P, B>
fn poll2(&mut self) -> Poll<(), ConnectionError> { fn poll2(&mut self) -> Poll<(), ConnectionError> {
use frame::Frame::*; use frame::Frame::*;
use proto::ProtoError::*;
loop { loop {
// First, ensure that the `Connection` is able to receive a frame // First, ensure that the `Connection` is able to receive a frame
@@ -130,13 +131,29 @@ impl<T, P, B> Connection<T, P, B>
trace!("polling codec"); trace!("polling codec");
let frame = match try!(self.codec.poll()) { let frame = match self.codec.poll() {
Async::Ready(frame) => frame, // Receive a frame
Async::NotReady => { Ok(Async::Ready(frame)) => frame,
// Socket not ready, try to flush any pending data
Ok(Async::NotReady) => {
// Flush any pending writes // Flush any pending writes
let _ = try!(self.poll_complete()); let _ = try!(self.poll_complete());
return Ok(Async::NotReady); return Ok(Async::NotReady);
} }
// Connection level error, set GO_AWAY and close connection
Err(Connection(reason)) => {
return Err(ConnectionError::Proto(reason));
}
// Stream level error, reset the stream
Err(Stream { id, reason }) => {
trace!("stream level error; id={:?}; reason={:?}", id, reason);
self.streams.send_reset::<P>(id, reason);
continue;
}
// I/O error, nothing more can be done
Err(Io(err)) => {
return Err(err.into());
}
}; };
debug!("recv; frame={:?}", frame); debug!("recv; frame={:?}", frame);

View File

@@ -36,6 +36,8 @@ struct Partial {
#[derive(Debug)] #[derive(Debug)]
enum Continuable { enum Continuable {
Headers(frame::Headers), Headers(frame::Headers),
// Decode the Continuation frame but ignore it...
// Ignore(StreamId),
// PushPromise(frame::PushPromise), // PushPromise(frame::PushPromise),
} }
@@ -52,14 +54,16 @@ impl<T> FramedRead<T> {
// TODO: Is this needed? // TODO: Is this needed?
} }
fn decode_frame(&mut self, mut bytes: BytesMut) -> Result<Option<Frame>, ConnectionError> { fn decode_frame(&mut self, mut bytes: BytesMut) -> Result<Option<Frame>, ProtoError> {
use self::ProtoError::*;
trace!("decoding frame from {}B", bytes.len()); trace!("decoding frame from {}B", bytes.len());
// Parse the head // Parse the head
let head = frame::Head::parse(&bytes); let head = frame::Head::parse(&bytes);
if self.partial.is_some() && head.kind() != Kind::Continuation { if self.partial.is_some() && head.kind() != Kind::Continuation {
return Err(ProtocolError.into()); return Err(Connection(ProtocolError));
} }
let kind = head.kind(); let kind = head.kind();
@@ -68,17 +72,26 @@ impl<T> FramedRead<T> {
let frame = match kind { let frame = match kind {
Kind::Settings => { Kind::Settings => {
frame::Settings::load(head, &bytes[frame::HEADER_LEN..])?.into() let res = frame::Settings::load(head, &bytes[frame::HEADER_LEN..]);
res.map_err(|_| Connection(ProtocolError))?.into()
} }
Kind::Ping => { Kind::Ping => {
frame::Ping::load(head, &bytes[frame::HEADER_LEN..])?.into() let res = frame::Ping::load(head, &bytes[frame::HEADER_LEN..]);
res.map_err(|_| Connection(ProtocolError))?.into()
} }
Kind::WindowUpdate => { Kind::WindowUpdate => {
frame::WindowUpdate::load(head, &bytes[frame::HEADER_LEN..])?.into() let res = frame::WindowUpdate::load(head, &bytes[frame::HEADER_LEN..]);
res.map_err(|_| Connection(ProtocolError))?.into()
} }
Kind::Data => { Kind::Data => {
let _ = bytes.split_to(frame::HEADER_LEN); let _ = bytes.split_to(frame::HEADER_LEN);
frame::Data::load(head, bytes.freeze())?.into() let res = frame::Data::load(head, bytes.freeze());
// TODO: Should this always be connection level? Probably not...
res.map_err(|_| Connection(ProtocolError))?.into()
} }
Kind::Headers => { Kind::Headers => {
// Drop the frame header // Drop the frame header
@@ -86,11 +99,24 @@ impl<T> FramedRead<T> {
let _ = bytes.split_to(frame::HEADER_LEN); let _ = bytes.split_to(frame::HEADER_LEN);
// Parse the header frame w/o parsing the payload // Parse the header frame w/o parsing the payload
let (mut headers, payload) = frame::Headers::load(head, bytes)?; let (mut headers, payload) = match frame::Headers::load(head, bytes) {
Ok(res) => res,
Err(_) => unimplemented!(),
};
if headers.is_end_headers() { if headers.is_end_headers() {
// Load the HPACK encoded headers & return the frame // Load the HPACK encoded headers & return the frame
headers.load_hpack(payload, &mut self.hpack)?; match headers.load_hpack(payload, &mut self.hpack) {
Ok(_) => {}
Err(frame::Error::MalformedMessage) => {
return Err(Stream {
id: head.stream_id(),
reason: ProtocolError,
});
}
Err(_) => return Err(Connection(ProtocolError)),
}
headers.into() headers.into()
} else { } else {
// Defer loading the frame // Defer loading the frame
@@ -103,16 +129,20 @@ impl<T> FramedRead<T> {
} }
} }
Kind::Reset => { Kind::Reset => {
frame::Reset::load(head, &bytes[frame::HEADER_LEN..])?.into() let res = frame::Reset::load(head, &bytes[frame::HEADER_LEN..]);
res.map_err(|_| Connection(ProtocolError))?.into()
} }
Kind::GoAway => { Kind::GoAway => {
frame::GoAway::load(&bytes[frame::HEADER_LEN..])?.into() let res = frame::GoAway::load(&bytes[frame::HEADER_LEN..]);
res.map_err(|_| Connection(ProtocolError))?.into()
} }
Kind::PushPromise => { Kind::PushPromise => {
frame::PushPromise::load(head, &bytes[frame::HEADER_LEN..])?.into() let res = frame::PushPromise::load(head, &bytes[frame::HEADER_LEN..]);
res.map_err(|_| Connection(ProtocolError))?.into()
} }
Kind::Priority => { Kind::Priority => {
frame::Priority::load(head, &bytes[frame::HEADER_LEN..])?.into() let res = frame::Priority::load(head, &bytes[frame::HEADER_LEN..]);
res.map_err(|_| Connection(ProtocolError))?.into()
} }
Kind::Continuation => { Kind::Continuation => {
// TODO: Un-hack this // TODO: Un-hack this
@@ -120,7 +150,7 @@ impl<T> FramedRead<T> {
let mut partial = match self.partial.take() { let mut partial = match self.partial.take() {
Some(partial) => partial, Some(partial) => partial,
None => return Err(ProtocolError.into()), None => return Err(Connection(ProtocolError)),
}; };
// Extend the buf // Extend the buf
@@ -135,10 +165,20 @@ impl<T> FramedRead<T> {
Continuable::Headers(mut frame) => { Continuable::Headers(mut frame) => {
// The stream identifiers must match // The stream identifiers must match
if frame.stream_id() != head.stream_id() { if frame.stream_id() != head.stream_id() {
return Err(ProtocolError.into()); return Err(Connection(ProtocolError));
}
match frame.load_hpack(partial.buf, &mut self.hpack) {
Ok(_) => {}
Err(frame::Error::MalformedMessage) => {
return Err(Stream {
id: head.stream_id(),
reason: ProtocolError,
});
}
Err(_) => return Err(Connection(ProtocolError)),
} }
frame.load_hpack(partial.buf, &mut self.hpack)?;
frame.into() frame.into()
} }
} }
@@ -165,9 +205,9 @@ impl<T> futures::Stream for FramedRead<T>
where T: AsyncRead, where T: AsyncRead,
{ {
type Item = Frame; type Item = Frame;
type Error = ConnectionError; type Error = ProtoError;
fn poll(&mut self) -> Poll<Option<Frame>, ConnectionError> { fn poll(&mut self) -> Poll<Option<Frame>, Self::Error> {
loop { loop {
trace!("poll"); trace!("poll");
let bytes = match try_ready!(self.inner.poll()) { let bytes = match try_ready!(self.inner.poll()) {

View File

@@ -26,6 +26,8 @@ use bytes::{Buf, IntoBuf};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio_io::codec::length_delimited; use tokio_io::codec::length_delimited;
use std::io;
/// Either a Client or a Server /// Either a Client or a Server
pub trait Peer { pub trait Peer {
/// Message type sent into the transport /// Message type sent into the transport
@@ -50,6 +52,17 @@ pub type PingPayload = [u8; 8];
pub type WindowSize = u32; pub type WindowSize = u32;
/// Errors that are received
#[derive(Debug)]
pub enum ProtoError {
Connection(Reason),
Stream {
id: StreamId,
reason: Reason,
},
Io(io::Error),
}
// Constants // Constants
pub const DEFAULT_INITIAL_WINDOW_SIZE: WindowSize = 65_535; pub const DEFAULT_INITIAL_WINDOW_SIZE: WindowSize = 65_535;
pub const MAX_WINDOW_SIZE: WindowSize = (1 << 31) - 1; pub const MAX_WINDOW_SIZE: WindowSize = (1 << 31) - 1;
@@ -88,3 +101,11 @@ pub(crate) fn from_framed_write<T, P, B>(framed_write: FramedWrite<T, Prioritize
Connection::new(codec) Connection::new(codec)
} }
// ===== impl ProtoError =====
impl From<io::Error> for ProtoError {
fn from(src: io::Error) -> Self {
ProtoError::Io(src)
}
}

View File

@@ -1,4 +1,3 @@
use ConnectionError;
use frame::Ping; use frame::Ping;
use proto::*; use proto::*;

View File

@@ -1,4 +1,4 @@
use {frame, ConnectionError}; use frame;
use proto::*; use proto::*;
use futures::Sink; use futures::Sink;

View File

@@ -1,5 +1,4 @@
use {frame, ConnectionError}; use {frame, ConnectionError};
use error::User::InactiveStreamId;
use proto::*; use proto::*;
use super::*; use super::*;
@@ -101,27 +100,10 @@ impl<B> Send<B> where B: Buf {
Ok(()) Ok(())
} }
/// This is called by the user to send a reset and should not be called
/// by internal state transitions. Use `reset_stream` for that.
pub fn send_reset(&mut self, pub fn send_reset(&mut self,
reason: Reason, reason: Reason,
stream: &mut store::Ptr<B>, stream: &mut store::Ptr<B>,
task: &mut Option<Task>) task: &mut Option<Task>)
-> Result<(), ConnectionError>
{
if stream.state.is_closed() {
debug!("send_reset; invalid stream ID");
return Err(InactiveStreamId.into())
}
self.reset_stream(reason, stream, task);
Ok(())
}
fn reset_stream(&mut self,
reason: Reason,
stream: &mut store::Ptr<B>,
task: &mut Option<Task>)
{ {
if stream.state.is_reset() { if stream.state.is_reset() {
// Don't double reset // Don't double reset
@@ -240,7 +222,7 @@ impl<B> Send<B> where B: Buf {
{ {
if let Err(e) = self.prioritize.recv_stream_window_update(sz, stream) { if let Err(e) = self.prioritize.recv_stream_window_update(sz, stream) {
debug!("recv_stream_window_update !!; err={:?}", e); debug!("recv_stream_window_update !!; err={:?}", e);
self.reset_stream(FlowControlError.into(), stream, task); self.send_reset(FlowControlError.into(), stream, task);
} }
Ok(()) Ok(())

View File

@@ -312,6 +312,33 @@ impl<B> Streams<B>
key: key, key: key,
}) })
} }
pub fn send_reset<P: Peer>(&mut self, id: StreamId, reason: Reason) {
let mut me = self.inner.lock().unwrap();
let me = &mut *me;
let key = match me.store.find_entry(id) {
Entry::Occupied(e) => e.key(),
Entry::Vacant(e) => {
match me.actions.recv.open::<P>(id) {
Ok(Some(stream_id)) => {
let stream = Stream::new(
stream_id, 0, 0);
e.insert(stream)
}
_ => return,
}
}
};
let stream = me.store.resolve(key);
me.actions.transition::<P, _, _>(stream, move |actions, stream| {
actions.send.send_reset(reason, stream, &mut actions.task)
})
}
} }
// ===== impl StreamRef ===== // ===== impl StreamRef =====
@@ -367,7 +394,7 @@ impl<B> StreamRef<B>
me.actions.recv.take_request(&mut stream) me.actions.recv.take_request(&mut stream)
} }
pub fn send_reset<P: Peer>(&mut self, reason: Reason) -> Result<(), ConnectionError> { pub fn send_reset<P: Peer>(&mut self, reason: Reason) {
let mut me = self.inner.lock().unwrap(); let mut me = self.inner.lock().unwrap();
let me = &mut *me; let me = &mut *me;

View File

@@ -191,7 +191,7 @@ impl<B: IntoBuf> Stream<B> {
self.inner.send_trailers::<Peer>(trailers) self.inner.send_trailers::<Peer>(trailers)
} }
pub fn send_reset(mut self, reason: Reason) -> Result<(), ConnectionError> { pub fn send_reset(mut self, reason: Reason) {
self.inner.send_reset::<Peer>(reason) self.inner.send_reset::<Peer>(reason)
} }
} }