http2_inspect.h
http2_module.cc
http2_module.h
+ http2_push_promise_frame.cc
+ http2_push_promise_frame.h
http2_request_line.cc
http2_request_line.h
http2_settings_frame.cc
EVENT_TRAILERS_NOT_END = 20,
EVENT_PADDING_ON_INVALID_FRAME = 21,
EVENT_PADDING_ON_EMPTY_FRAME = 22,
+ EVENT_C2S_PUSH = 23,
+ EVENT_INVALID_PUSH_FRAME = 24,
+ EVENT_BAD_PUSH_SEQUENCE = 25,
+ EVENT_BAD_SETTINGS_VALUE = 26,
+ EVENT_PUSH_WHEN_PROHIBITED = 27,
+ EVENT_INVALID_PROMISED_STREAM = 28,
+ EVENT_INVALID_STREAM_ID = 29,
+ EVENT_INVALID_FLAG = 30,
EVENT__MAX_VALUE
};
INF_HUFFMAN_INCOMPLETE_CODE_PADDING = 9,
INF_MISSING_CONTINUATION = 10,
INF_UNEXPECTED_CONTINUATION = 11,
- INF_UNUSED_1 = 12,
+ INF_C2S_PUSH = 12,
INF_INVALID_PSEUDO_HEADER = 13,
INF_PSEUDO_HEADER_AFTER_REGULAR_HEADER = 14,
INF_PSEUDO_HEADER_URI_FORM_MISMATCH = 15,
INF_INVALID_STARTLINE = 25,
INF_INVALID_HEADER = 26,
INF_PADDING_LEN = 27,
- INF_UNUSED_2 = 28,
+ INF_PUSH_FRAME_TOO_SHORT = 28,
INF_PSEUDO_HEADER_IN_TRAILERS = 29,
INF_TRAILERS_NOT_END = 30,
INF_PADDING_ON_INVALID_FRAME = 31,
INF_PADDING_ON_EMPTY_FRAME = 32,
+ INF_BAD_PUSH_SEQUENCE = 33,
+ INF_BAD_SETTINGS_PUSH_VALUE = 34,
+ INF_PUSH_WHEN_PROHIBITED = 35,
+ INF_INVALID_PROMISED_STREAM = 36,
+ INF_INVALID_STREAM_ID = 37,
+ INF_INVALID_FLAG = 38,
INF__MAX_VALUE
};
#include "http2_enum.h"
#include "http2_frame.h"
#include "http2_module.h"
+#include "http2_push_promise_frame.h"
#include "http2_start_line.h"
#include "http2_stream.h"
return get_stream(current_stream[source_id]);
}
+// processing stream is the current stream except for push promise frames with properly formatted
+// promised stream IDs
+class Http2Stream* Http2FlowData::get_processing_stream(const HttpCommon::SourceId source_id)
+{
+ if (processing_stream_id[source_id] == NO_STREAM_ID)
+ {
+ if (frame_type[source_id] == FT_PUSH_PROMISE)
+ processing_stream_id[source_id] = Http2PushPromiseFrame::get_promised_stream_id(
+ events[source_id], infractions[source_id], frame_data[source_id],
+ frame_data_size[source_id]);
+ if (processing_stream_id[source_id] == NO_STREAM_ID)
+ processing_stream_id[source_id] = current_stream[source_id];
+ }
+ return get_stream(processing_stream_id[source_id]);
+}
+
uint32_t Http2FlowData::get_current_stream_id(const HttpCommon::SourceId source_id)
{
return current_stream[source_id];
friend class Http2HeadersFrameTrailer;
friend class Http2Hpack;
friend class Http2Inspect;
+ friend class Http2PushPromiseFrame;
friend class Http2RequestLine;
friend class Http2SettingsFrame;
friend class Http2StartLine;
};
class Http2Stream* get_current_stream(const HttpCommon::SourceId source_id);
uint32_t get_current_stream_id(const HttpCommon::SourceId source_id);
+ class Http2Stream* get_processing_stream(const HttpCommon::SourceId source_id);
Http2HpackDecoder* get_hpack_decoder(const HttpCommon::SourceId source_id)
{ return &hpack_decoder[source_id]; }
- Http2ConnectionSettings* get_connection_settings(const HttpCommon::SourceId source_id)
+ Http2ConnectionSettings* get_my_connection_settings(const HttpCommon::SourceId source_id)
{ return &connection_settings[source_id]; }
+ Http2ConnectionSettings* get_recipient_connection_settings(const HttpCommon::SourceId source_id)
+ { return &connection_settings[1 - source_id]; }
bool is_mid_frame(const HttpCommon::SourceId source_id = HttpCommon::SRC_SERVER)
{ return (continuation_expected[source_id] || reading_frame[source_id]); }
Http2Infractions* const infractions[2] = { new Http2Infractions, new Http2Infractions };
Http2EventGen* const events[2] = { new Http2EventGen, new Http2EventGen };
- // Stream ID of the frame currently being read in and processed
+ // Stream ID of the frame currently being processed was sent on (i.e. the stream in the frame
+ // header). This is set in scan().
uint32_t current_stream[2] = { Http2Enums::NO_STREAM_ID, Http2Enums::NO_STREAM_ID };
+ // Stream ID of the stream responsible for processing the current frame. This will be the same
+ // as current_stream except when processing a push_promise frame. This is set in eval().
+ uint32_t processing_stream_id[2] = { Http2Enums::NO_STREAM_ID, Http2Enums::NO_STREAM_ID };
// At any given time there may be different streams going in each direction. But only one of
// them is the stream that http_inspect is actually processing at the moment.
uint32_t stream_in_hi = Http2Enums::NO_STREAM_ID;
#include "http2_flow_data.h"
#include "http2_headers_frame_header.h"
#include "http2_headers_frame_trailer.h"
+#include "http2_push_promise_frame.h"
#include "http2_settings_frame.h"
#include "http2_stream.h"
#include "service_inspectors/http_inspect/http_field.h"
case FT_DATA:
return new Http2DataFrame(header, header_len, data, data_len, session_data, source_id,
stream);
+ case FT_PUSH_PROMISE:
+ return new Http2PushPromiseFrame(header, header_len, data, data_len, session_data,
+ source_id, stream);
default:
return new Http2Frame(header, header_len, data, data_len, session_data, source_id,
stream);
bool Http2HeadersFrameTrailer::valid_sequence(Http2Enums::StreamState state)
{
- return (state == Http2Enums::STREAM_EXPECT_BODY) || (state == Http2Enums::STREAM_BODY);
+ if ((state == STREAM_EXPECT_BODY) || (state == STREAM_BODY))
+ return true;
+ if (state == STREAM_COMPLETE)
+ {
+ *session_data->infractions[source_id] += INF_FRAME_SEQUENCE;
+ session_data->events[source_id]->create_event(EVENT_FRAME_SEQUENCE);
+ }
+ return false;
}
void Http2HeadersFrameTrailer::analyze_http1()
bool HpackIndexTable::hpack_table_size_update(uint32_t new_size)
{
encoder_set_max_size = true;
- if (new_size <= session_data->get_connection_settings((HttpCommon::SourceId)(1 - source_id))->
+ if (new_size <= session_data->get_recipient_connection_settings(source_id)->
get_param(HEADER_TABLE_SIZE))
{
dynamic_table.update_size(new_size);
return;
}
- Http2Stream* stream = session_data->get_current_stream(source_id);
+ Http2Stream* stream = session_data->get_processing_stream(source_id);
+ assert(session_data->processing_stream_id[source_id] != NO_STREAM_ID);
+ assert(stream);
session_data->stream_in_hi = stream->get_stream_id();
stream->eval_frame(session_data->frame_header[source_id],
const SourceId source_id = p->is_from_client() ? SRC_CLIENT : SRC_SERVER;
- Http2Stream* stream = session_data->get_current_stream(source_id);
+ Http2Stream* stream = session_data->get_processing_stream(source_id);
stream->clear_frame();
session_data->stream_in_hi = NO_STREAM_ID;
+ session_data->processing_stream_id[source_id] = NO_STREAM_ID;
}
#ifdef REG_TEST
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// http2_push_promise_frame.cc author Katura Harvey <katharve@cisco.com>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "http2_push_promise_frame.h"
+
+#include "http2_flow_data.h"
+#include "http2_stream.h"
+#include "http2_utils.h"
+
+using namespace HttpCommon;
+using namespace Http2Enums;
+
+Http2PushPromiseFrame::Http2PushPromiseFrame(const uint8_t* header_buffer,
+ const uint32_t header_len, const uint8_t* data_buffer, const uint32_t data_len,
+ Http2FlowData* session_data_, HttpCommon::SourceId source_id_, Http2Stream* stream_) :
+ Http2Frame(header_buffer, header_len, data_buffer, data_len, session_data_, source_id_, stream_)
+{
+ // If this was a short frame, it's being processed by the stream that sent it. We've already
+ // alerted
+ if (data_len < PROMISED_ID_LENGTH)
+ return;
+
+ // Server-initiated streams must be even
+ if (source_id == SRC_SERVER and stream->get_stream_id() % 2 != 0)
+ {
+ *session_data->infractions[source_id] += INF_INVALID_STREAM_ID;
+ session_data->events[source_id]->create_event(EVENT_INVALID_STREAM_ID);
+ }
+
+ if (session_data->get_recipient_connection_settings(source_id)->get_param(ENABLE_PUSH) == 0)
+ {
+ session_data->events[source_id]->create_event(EVENT_PUSH_WHEN_PROHIBITED);
+ *session_data->infractions[source_id] += INF_PUSH_WHEN_PROHIBITED;
+ }
+
+ // Push_promise frames only define the padded and end_headers flags
+ if (get_flags() & ~PADDED & ~END_HEADERS)
+ {
+ session_data->events[source_id]->create_event(EVENT_INVALID_FLAG);
+ *session_data->infractions[source_id] += INF_INVALID_FLAG;
+ }
+}
+
+bool Http2PushPromiseFrame::valid_sequence(Http2Enums::StreamState)
+{
+ if (data.length() < PROMISED_ID_LENGTH)
+ return false;
+
+ if (source_id == SRC_CLIENT)
+ {
+ *session_data->infractions[source_id] += INF_C2S_PUSH;
+ session_data->events[source_id]->create_event(EVENT_C2S_PUSH);
+ return false;
+ }
+
+ // Promised stream must not be already in use
+ if (stream->get_state(SRC_CLIENT) != STREAM_EXPECT_HEADERS or
+ stream->get_state(SRC_SERVER) != STREAM_EXPECT_HEADERS)
+ {
+ *session_data->infractions[source_id] += INF_INVALID_PROMISED_STREAM;
+ session_data->events[source_id]->create_event(EVENT_INVALID_PROMISED_STREAM);
+ return false;
+ }
+
+ // Alert but continue processing if invalid sequence on stream push_promise was sent on
+ Http2Stream* const stream_sent_on = session_data->get_current_stream(source_id);
+ if (stream_sent_on->get_state(SRC_CLIENT) == STREAM_EXPECT_HEADERS or
+ stream_sent_on->get_state(SRC_SERVER) >= STREAM_COMPLETE)
+ {
+ *session_data->infractions[source_id] += INF_BAD_PUSH_SEQUENCE;
+ session_data->events[source_id]->create_event(EVENT_BAD_PUSH_SEQUENCE);
+ }
+ return true;
+}
+
+void Http2PushPromiseFrame::update_stream_state()
+{
+ switch (stream->get_state(source_id))
+ {
+ case STREAM_EXPECT_HEADERS:
+ stream->set_state(SRC_CLIENT, STREAM_COMPLETE);
+ break;
+ default:
+ //only STREAM_EXPECT_HEADERS is valid so should never get here
+ assert(false);
+ stream->set_state(source_id, STREAM_ERROR);
+ }
+}
+
+uint32_t Http2PushPromiseFrame::get_promised_stream_id(Http2EventGen* const events,
+ Http2Infractions* const infractions, const uint8_t* data_buffer, uint32_t data_len)
+{
+ if (data_len < PROMISED_ID_LENGTH)
+ {
+ events->create_event(EVENT_INVALID_PUSH_FRAME);
+ *infractions += INF_PUSH_FRAME_TOO_SHORT;
+ return NO_STREAM_ID;
+ }
+
+ // the first four bytes of the push_promise frame are the pushed stream ID
+ return get_stream_id_from_buffer(data_buffer);
+}
+
+#ifdef REG_TEST
+void Http2PushPromiseFrame::print_frame(FILE* output)
+{
+ fprintf(output, "Push_Promise frame\n");
+ Http2Frame::print_frame(output);
+}
+#endif
--- /dev/null
+//--------------------------------------------------------------------------
+// Copyright (C) 2020-2020 Cisco and/or its affiliates. All rights reserved.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License Version 2 as published
+// by the Free Software Foundation. You may not use, modify or distribute
+// this program under any other version of the GNU General Public License.
+//
+// This program is distributed in the hope that it will be useful, but
+// WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License along
+// with this program; if not, write to the Free Software Foundation, Inc.,
+// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+//--------------------------------------------------------------------------
+// http2_push_promise_frame.h author Katura Harvey <katharve@cisco.com>
+
+#ifndef HTTP2_PUSH_PROMISE_FRAME_H
+#define HTTP2_PUSH_PROMISE_FRAME_H
+
+#include "service_inspectors/http_inspect/http_common.h"
+#include "utils/event_gen.h"
+#include "utils/infractions.h"
+
+#include "http2_enum.h"
+#include "http2_frame.h"
+
+class Field;
+class Http2Frame;
+class Http2Stream;
+class HttpFlowData;
+
+using Http2Infractions = Infractions<Http2Enums::INF__MAX_VALUE, Http2Enums::INF__NONE>;
+using Http2EventGen = EventGen<Http2Enums::EVENT__MAX_VALUE, Http2Enums::EVENT__NONE,
+ Http2Enums::HTTP2_GID>;
+
+class Http2PushPromiseFrame : public Http2Frame
+{
+public:
+ bool valid_sequence(Http2Enums::StreamState state) override;
+ void update_stream_state() override;
+ static uint32_t get_promised_stream_id(Http2EventGen* const events,
+ Http2Infractions* const infractions, const uint8_t* data_buffer, uint32_t data_len);
+
+ friend Http2Frame* Http2Frame::new_frame(const uint8_t*, const uint32_t, const uint8_t*,
+ const uint32_t, Http2FlowData*, HttpCommon::SourceId, Http2Stream* stream);
+
+#ifdef REG_TEST
+ void print_frame(FILE* output) override;
+#endif
+
+private:
+ Http2PushPromiseFrame(const uint8_t* header_buffer, const uint32_t header_len,
+ const uint8_t* data_buffer, const uint32_t data_len, Http2FlowData* ssn_data,
+ HttpCommon::SourceId src_id, Http2Stream* stream);
+ static const int32_t PROMISED_ID_LENGTH = 4;
+};
+#endif
continue;
}
- handle_update(parameter_id, parameter_value);
- session_data->connection_settings[source_id].set_param(parameter_id, parameter_value);
+ if (handle_update(parameter_id, parameter_value))
+ session_data->connection_settings[source_id].set_param(parameter_id, parameter_value);
}
}
return !(bad_frame);
}
-void Http2SettingsFrame::handle_update(uint16_t id, uint32_t value)
+bool Http2SettingsFrame::handle_update(uint16_t id, uint32_t value)
{
switch (id)
{
session_data->get_hpack_decoder((HttpCommon::SourceId) (1 - source_id))->
get_decode_table()->settings_table_size_update(value);
break;
+ case ENABLE_PUSH:
+ // Only values of 0 or 1 are allowed
+ if (!(value == 0 or value == 1))
+ {
+ session_data->events[source_id]->create_event(EVENT_BAD_SETTINGS_VALUE);
+ *session_data->infractions[source_id] += INF_BAD_SETTINGS_PUSH_VALUE;
+ return false;
+ }
+ break;
default:
break;
}
+ return true;
}
#ifdef REG_TEST
void parse_settings_frame();
bool sanity_check();
- void handle_update(uint16_t id, uint32_t value);
+ bool handle_update(uint16_t id, uint32_t value);
bool bad_frame = false;
static const uint8_t SfAck = 0x01;
if (type == FT_CONTINUATION and !session_data->continuation_expected[source_id])
{
- // FIXIT-E CONTINUATION frames can also follow PUSH_PROMISE frames, which
- // are not currently supported
*session_data->infractions[source_id] += INF_UNEXPECTED_CONTINUATION;
session_data->events[source_id]->create_event(
EVENT_UNEXPECTED_CONTINUATION);
switch (type)
{
case FT_HEADERS:
+ case FT_PUSH_PROMISE:
if (!(frame_flags & END_HEADERS))
{
session_data->continuation_expected[source_id] = true;
scan_frame_header[source_id]);
const uint32_t old_stream = session_data->current_stream[source_id];
session_data->current_stream[source_id] =
- get_stream_id(session_data->scan_frame_header[source_id]);
+ get_stream_id_from_header(session_data->scan_frame_header[source_id]);
if (session_data->continuation_expected[source_id] && type != FT_CONTINUATION)
{
{ EVENT_TRAILERS_NOT_END, "HTTP/2 trailers without END_STREAM bit" },
{ EVENT_PADDING_ON_INVALID_FRAME, "padding flag set on invalid HTTP/2 frame type" },
{ EVENT_PADDING_ON_EMPTY_FRAME, "padding flag set on HTTP/2 frame with zero length" },
+ { EVENT_C2S_PUSH, "HTTP/2 push promise frame in c2s direction" },
+ { EVENT_INVALID_PUSH_FRAME, "invalid HTTP/2 push promise frame" },
+ { EVENT_BAD_PUSH_SEQUENCE, "HTTP/2 push promise frame sent at invalid time" },
+ { EVENT_BAD_SETTINGS_VALUE, "invalid parameter value sent in HTTP/2 settings frame" },
+ { EVENT_PUSH_WHEN_PROHIBITED, "HTTP/2 push promise frame sent when prohibited by receiver" },
+ { EVENT_INVALID_PROMISED_STREAM, "HTTP/2 push promise frame with invalid promised stream id" },
+ { EVENT_INVALID_STREAM_ID, "HTTP/2 stream initiated with invalid stream id" },
+ { EVENT_INVALID_FLAG, "invalid flag set on HTTP/2 frame" },
{ 0, nullptr }
};
return NO_HEADER;
}
-uint8_t get_stream_id(const uint8_t* frame_header_buffer)
+uint32_t get_stream_id_from_header(const uint8_t* frame_header_buffer)
{
const uint8_t stream_id_index = 5;
assert(frame_header_buffer != nullptr);
- return ((frame_header_buffer[stream_id_index] & 0x7f) << 24) +
- (frame_header_buffer[stream_id_index + 1] << 16) +
- (frame_header_buffer[stream_id_index + 2] << 8) +
- frame_header_buffer[stream_id_index + 3];
+ return get_stream_id_from_buffer(frame_header_buffer + stream_id_index);
+
}
+uint32_t get_stream_id_from_buffer(const uint8_t* buffer)
+{
+ return ((buffer[0] & 0x7f) << 24) + (buffer[1] << 16) + (buffer[2] << 8) + buffer[3];
+}
uint32_t get_frame_length(const uint8_t* frame_header_buffer);
uint8_t get_frame_type(const uint8_t* frame_header_buffer);
uint8_t get_frame_flags(const uint8_t* frame_header_buffer);
-uint8_t get_stream_id(const uint8_t* frame_header_buffer);
+uint32_t get_stream_id_from_header(const uint8_t* frame_header_buffer);
+uint32_t get_stream_id_from_buffer(const uint8_t* buffer);
#endif