Skip to content

Commit

Permalink
usm: http2: Handle frame header remainder in the first packet (#24371)
Browse files Browse the repository at this point in the history
* usm: http2: Handle frame header remainder in the first packet

Handling an edge case, where we have a remainder in the first frame, we consume it, and then
we assued (wrongfully) that we have a full valid frame. But we can have a part of a frame
thus we need to create a new frame remainder for header remainder

* Fixed test

* Fixed CR and add TLS version

* Fixed documentation
  • Loading branch information
guyarb committed Apr 4, 2024
1 parent 9deee18 commit 3afaaa5
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 4 deletions.
15 changes: 15 additions & 0 deletions pkg/network/ebpf/c/protocols/http2/decoding-tls.h
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,9 @@ static __always_inline bool tls_get_first_frame(tls_dispatcher_arguments_t *info
if (info->data_off == info->data_end) {
return false;
}
if (info->data_off + HTTP2_FRAME_HEADER_SIZE > info->data_end) {
return false;
}
reset_frame(current_frame);
read_into_user_buffer_http2_frame_header((char *)current_frame, info->buffer_ptr + info->data_off);
if (format_http2_frame_header(current_frame)) {
Expand Down Expand Up @@ -634,6 +637,18 @@ int uprobe__http2_tls_handle_first_frame(struct pt_regs *ctx) {
bpf_map_delete_elem(&http2_remainder, &dispatcher_args_copy.tup);
}
if (!has_valid_first_frame) {
// Handling the case where we have a frame header remainder, and we couldn't read the frame header.
if (dispatcher_args_copy.data_off < dispatcher_args_copy.data_end && dispatcher_args_copy.data_off + HTTP2_FRAME_HEADER_SIZE > dispatcher_args_copy.data_end) {
frame_header_remainder_t new_frame_state = { 0 };
new_frame_state.remainder = HTTP2_FRAME_HEADER_SIZE - (dispatcher_args_copy.data_end - dispatcher_args_copy.data_off);
bpf_memset(new_frame_state.buf, 0, HTTP2_FRAME_HEADER_SIZE);
#pragma unroll(HTTP2_FRAME_HEADER_SIZE)
for (__u32 iteration = 0; iteration < HTTP2_FRAME_HEADER_SIZE && new_frame_state.remainder + iteration < HTTP2_FRAME_HEADER_SIZE; ++iteration) {
bpf_probe_read_user(new_frame_state.buf + iteration, 1, dispatcher_args_copy.buffer_ptr + dispatcher_args_copy.data_off + iteration);
}
new_frame_state.header_length = HTTP2_FRAME_HEADER_SIZE - new_frame_state.remainder;
bpf_map_update_elem(&http2_remainder, &dispatcher_args_copy.tup, &new_frame_state, BPF_ANY);
}
return 0;
}

Expand Down
15 changes: 15 additions & 0 deletions pkg/network/ebpf/c/protocols/http2/decoding.h
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,9 @@ static __always_inline bool get_first_frame(struct __sk_buff *skb, skb_info_t *s
if (skb_info->data_off == skb_info->data_end) {
return false;
}
if (skb_info->data_off + HTTP2_FRAME_HEADER_SIZE > skb_info->data_end) {
return false;
}
reset_frame(current_frame);
bpf_skb_load_bytes(skb, skb_info->data_off, (char *)current_frame, HTTP2_FRAME_HEADER_SIZE);
if (format_http2_frame_header(current_frame)) {
Expand Down Expand Up @@ -539,6 +542,18 @@ int socket__http2_handle_first_frame(struct __sk_buff *skb) {
}

if (!has_valid_first_frame) {
// Handling the case where we have a frame header remainder, and we couldn't read the frame header.
if (dispatcher_args_copy.skb_info.data_off < dispatcher_args_copy.skb_info.data_end && dispatcher_args_copy.skb_info.data_off + HTTP2_FRAME_HEADER_SIZE > dispatcher_args_copy.skb_info.data_end) {
frame_header_remainder_t new_frame_state = { 0 };
new_frame_state.remainder = HTTP2_FRAME_HEADER_SIZE - (dispatcher_args_copy.skb_info.data_end - dispatcher_args_copy.skb_info.data_off);
bpf_memset(new_frame_state.buf, 0, HTTP2_FRAME_HEADER_SIZE);
#pragma unroll(HTTP2_FRAME_HEADER_SIZE)
for (__u32 iteration = 0; iteration < HTTP2_FRAME_HEADER_SIZE && new_frame_state.remainder + iteration < HTTP2_FRAME_HEADER_SIZE; ++iteration) {
bpf_skb_load_bytes(skb, dispatcher_args_copy.skb_info.data_off + iteration, new_frame_state.buf + iteration, 1);
}
new_frame_state.header_length = HTTP2_FRAME_HEADER_SIZE - new_frame_state.remainder;
bpf_map_update_elem(&http2_remainder, &dispatcher_args_copy.tup, &new_frame_state, BPF_ANY);
}
return 0;
}

Expand Down
40 changes: 36 additions & 4 deletions pkg/network/usm/usm_http2_monitor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,38 @@ func (s *usmHTTP2Suite) TestRawTraffic() {
},
expectedEndpoints: nil,
},
{
name: "remainder + header remainder",
// Testing the scenario where we have both a remainder (a frame's payload split over 2 packets) and in the
// second packet, we have the remainder and a partial frame header of a new request. We're testing that we
// can capture the 2 requests in this scenario.
messageBuilder: func() [][]byte {
data := []byte("testcontent")
request1 := newFramer().
writeHeaders(t, 1, usmhttp2.HeadersFrameOptions{Headers: generateTestHeaderFields(headersGenerationOptions{overrideContentLength: len(data)})}).
writeData(t, 1, true, data).bytes()
request2 := newFramer().
writeHeaders(t, 3, usmhttp2.HeadersFrameOptions{Headers: headersWithGivenEndpoint("/bbb")}).
writeData(t, 3, true, emptyBody).bytes()
firstPacket := request1[:len(request1)-6]
secondPacket := append(request1[len(request1)-6:], request2[:5]...)
return [][]byte{
firstPacket,
secondPacket,
request2[5:],
}
},
expectedEndpoints: map[usmhttp.Key]int{
{
Path: usmhttp.Path{Content: usmhttp.Interner.GetString(http2DefaultTestPath)},
Method: usmhttp.MethodPost,
}: 1,
{
Path: usmhttp.Path{Content: usmhttp.Interner.GetString("/bbb")},
Method: usmhttp.MethodPost,
}: 1,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -1432,10 +1464,11 @@ func (s *usmHTTP2Suite) TestRemainderTable() {
name: "validate clean with remainder and header zero",
// The purpose of this test is to validate that we cannot handle reassembled tcp segments.
messageBuilder: func() [][]byte {
data := []byte("test12345")
a := newFramer().
writeHeaders(t, 1, usmhttp2.HeadersFrameOptions{Headers: testHeaders()}).bytes()
b := newFramer().writeData(t, 1, true, []byte("test12345")).bytes()
message := append(a, b[11:]...)
writeHeaders(t, 1, usmhttp2.HeadersFrameOptions{Headers: generateTestHeaderFields(headersGenerationOptions{overrideContentLength: len(data)})}).bytes()
b := newFramer().writeData(t, 1, true, data).bytes()
message := append(a, b[:11]...)
return [][]byte{
// we split it in 11 bytes in order to split the payload itself.
message,
Expand All @@ -1462,7 +1495,6 @@ func (s *usmHTTP2Suite) TestRemainderTable() {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {

usmMonitor := setupUSMTLSMonitor(t, cfg)
if s.isTLS {
utils.WaitForProgramsToBeTraced(t, "go-tls", proxyProcess.Process.Pid)
Expand Down

0 comments on commit 3afaaa5

Please sign in to comment.