5656 // Large buffer
5757 k_large_buf_max_size = 1 << 14 , // 16K
5858 k_large_buf_max_size_mask = k_large_buf_max_size - 1 ,
59+
60+ // Sanity checks
61+ k_mysql_payload_length_max = 1 << 13 , // 8K
5962};
6063
6164struct {
@@ -67,43 +70,50 @@ struct {
6770
6871SCRATCH_MEM_SIZED (mysql_large_buffers , k_large_buf_max_size );
6972
73+ // This function is used to store the MySQL header if it comes in split packets
74+ // from double send.
75+ // Given the fact that we need to store this for the duration of the full request
76+ // (split in potentially multiple packets), we will **not** process or preserve
77+ // any actual payloads that are exactly 4 bytes long — they are intentionally
78+ // dropped in favor of state storage.
7079static __always_inline int mysql_store_state_data (const connection_info_t * conn_info ,
7180 const unsigned char * data ,
7281 size_t data_len ) {
7382 if (data_len != k_mysql_hdr_without_command_size ) {
7483 return 0 ;
7584 }
7685
77- struct mysql_state_data * state_data = bpf_map_lookup_elem (& mysql_state , conn_info );
78- if (state_data == NULL ) {
79- // State data not found, treat this data as a header.
80- struct mysql_state_data new_state_data = {};
81- bpf_probe_read (& new_state_data , k_mysql_hdr_without_command_size , (const void * )data );
82- bpf_map_update_elem (& mysql_state , conn_info , & new_state_data , BPF_ANY );
83- return -1 ;
84- }
86+ struct mysql_state_data new_state_data = {};
87+ bpf_probe_read (& new_state_data , k_mysql_hdr_without_command_size , (const void * )data );
88+ bpf_map_update_elem (& mysql_state , conn_info , & new_state_data , BPF_ANY );
8589
86- // This is a payload.
87- return 0 ;
90+ return -1 ;
8891}
8992
9093static __always_inline int mysql_parse_fixup_header (const connection_info_t * conn_info ,
9194 struct mysql_hdr * hdr ,
9295 const unsigned char * data ,
9396 size_t data_len ) {
97+ // Try to parse and validate the header first.
98+ bpf_probe_read (hdr , k_mysql_hdr_size , (const void * )data );
99+ if (mysql_payload_length (hdr -> payload_length ) ==
100+ (data_len - k_mysql_hdr_without_command_size )) {
101+ // Header is valid and we have the full data, we can proceed.
102+ hdr -> hdr_arrived = false;
103+ return 0 ;
104+ }
105+
106+ // Prepend the header from state data.
94107 struct mysql_state_data * state_data = bpf_map_lookup_elem (& mysql_state , conn_info );
95108 if (state_data != NULL ) {
96109 __builtin_memcpy (hdr , state_data , k_mysql_hdr_without_command_size );
97110 bpf_probe_read (& hdr -> command_id , k_mysql_hdr_command_id_size , (const void * )data );
98111 hdr -> hdr_arrived = true;
99- } else {
100- if (data_len < k_mysql_hdr_size ) {
101- bpf_dbg_printk ("mysql_parse_fixup_header: data_len is too short: %d" , data_len );
102- return -1 ;
103- }
104- bpf_probe_read (hdr , k_mysql_hdr_size , (const void * )data );
112+ return 0 ;
105113 }
106- return 0 ;
114+
115+ bpf_dbg_printk ("mysql_parse_fixup_header: failed to parse mysql header" );
116+ return -1 ;
107117}
108118
109119// This is an alternative version of mysql_parse_fixup_header that fills the buffer
@@ -140,43 +150,44 @@ static __always_inline int mysql_read_fixup_buffer(const connection_info_t *conn
140150 return * buf_len ;
141151}
142152
143- static __always_inline void mysql_send_large_buffer (tcp_req_t * req ,
144- pid_connection_info_t * pid_conn ,
145- const void * u_buf ,
146- u32 bytes_len ,
147- u8 direction ) {
153+ // Emit a large buffer event for MySQL protocol.
154+ // The return value is used to control the flow for this specific protocol.
155+ // -1: wait additional data; 0: continue, regardless of errors.
156+ static __always_inline int mysql_send_large_buffer (tcp_req_t * req ,
157+ pid_connection_info_t * pid_conn ,
158+ const void * u_buf ,
159+ u32 bytes_len ,
160+ u8 direction ,
161+ enum large_buf_action action ) {
148162 if (mysql_store_state_data (& pid_conn -> conn , u_buf , bytes_len ) < 0 ) {
149163 bpf_dbg_printk ("mysql_send_large_buffer: 4 bytes packet, storing state data" );
150- return ;
151- }
152-
153- if (bytes_len < (k_mysql_hdr_size + 1 )) {
154- bpf_dbg_printk ("mysql_send_large_buffer: bytes_len is too short: %d" , bytes_len );
155- return ;
164+ return -1 ;
156165 }
157166
158167 tcp_large_buffer_t * large_buf = (tcp_large_buffer_t * )mysql_large_buffers_mem ();
159168 if (!large_buf ) {
160169 bpf_dbg_printk ("mysql_send_large_buffer: failed to reserve space for MySQL large buffer" );
161- return ;
170+ return 0 ;
162171 }
163172
164173 large_buf -> type = EVENT_TCP_LARGE_BUFFER ;
165174 large_buf -> direction = direction ;
175+ large_buf -> action = action ;
166176 __builtin_memcpy ((void * )& large_buf -> tp , (void * )& req -> tp , sizeof (tp_info_t ));
167177
168178 int written =
169179 mysql_read_fixup_buffer (& pid_conn -> conn , large_buf -> buf , & large_buf -> len , u_buf , bytes_len );
170180 if (written < 0 ) {
171181 bpf_dbg_printk ("mysql_send_large_buffer: failed to read buffer, not sending large buffer" );
172- return ;
182+ return 0 ;
173183 }
174184
175185 req -> has_large_buffers = true;
176186 bpf_ringbuf_output (& events ,
177187 large_buf ,
178188 (sizeof (tcp_large_buffer_t ) + written ) & k_large_buf_max_size_mask ,
179189 get_flags ());
190+ return 0 ;
180191}
181192
182193static __always_inline u32 data_offset (struct mysql_hdr * hdr ) {
@@ -188,32 +199,6 @@ static __always_inline u32 mysql_command_offset(struct mysql_hdr *hdr) {
188199 return data_offset (hdr ) - k_mysql_hdr_command_id_size ;
189200}
190201
191- // k_tail_protocol_mysql
192- SEC ("kprobe/mysql" )
193- int obi_protocol_mysql (void * ctx ) {
194- call_protocol_args_t * args = protocol_args ();
195- if (!args ) {
196- return 0 ;
197- }
198-
199- bpf_dbg_printk ("=== tcp_mysql_event len=%d pid=%d ===" ,
200- args -> bytes_len ,
201- pid_from_pid_tgid (bpf_get_current_pid_tgid ()));
202-
203- if (mysql_store_state_data (
204- & args -> pid_conn .conn , (const unsigned char * )args -> u_buf , args -> bytes_len ) < 0 ) {
205- bpf_dbg_printk ("mysql: 4 bytes packet, storing state data" );
206- return 0 ;
207- }
208-
209- // Tail call back into generic TCP handler.
210- // Once the header is fixed up, we can use the generic TCP handling code
211- // in order to reuse all the common logic.
212- bpf_tail_call (ctx , & jump_table , k_tail_protocol_tcp );
213-
214- return 0 ;
215- }
216-
217202static __always_inline u8 is_mysql (connection_info_t * conn_info ,
218203 const unsigned char * data ,
219204 u32 data_len ,
@@ -224,19 +209,20 @@ static __always_inline u8 is_mysql(connection_info_t *conn_info,
224209 return 0 ;
225210 }
226211
227- if (data_len < (k_mysql_hdr_size + 1 )) {
228- bpf_dbg_printk ("is_mysql: data_len is too short: %d" , data_len );
229- return 0 ;
230- }
231-
232212 struct mysql_hdr hdr = {};
233213 if (mysql_parse_fixup_header (conn_info , & hdr , data , data_len ) != 0 ) {
234214 bpf_dbg_printk ("is_mysql: failed to parse mysql header" );
235215 return 0 ;
236216 }
217+ const u32 payload_len = mysql_payload_length (hdr .payload_length );
218+
219+ if (payload_len > k_mysql_payload_length_max ) {
220+ bpf_dbg_printk ("is_mysql: payload length is too large: %d" , payload_len );
221+ return 0 ;
222+ }
237223
238224 bpf_dbg_printk ("is_mysql: payload_length=%d sequence_id=%d command_id=%d" ,
239- mysql_payload_length ( hdr . payload_length ) ,
225+ payload_len ,
240226 hdr .sequence_id ,
241227 hdr .command_id );
242228
@@ -274,8 +260,9 @@ static __always_inline u8 is_mysql(connection_info_t *conn_info,
274260 // NOTE: Trying to classify the connection based on this command
275261 // would be unreliable, as the check is too shallow.
276262 * packet_type = PACKET_TYPE_REQUEST ;
263+ break ;
277264 }
278- break ;
265+ return 0 ;
279266 default :
280267 if (* protocol_type == k_protocol_type_mysql ) {
281268 // Check sequence ID and make sure we are processing a response.
0 commit comments