Line data Source code
1 : #include "fd_pcapng_private.h"
2 : #include "../fd_util.h"
3 : #include "../sanitize/fd_msan.h"
4 : #include <errno.h>
5 : #include <stdio.h>
6 :
7 : FD_FN_CONST ulong
8 6 : fd_pcapng_iter_align( void ) {
9 6 : return alignof(fd_pcapng_iter_t);
10 6 : }
11 :
12 : FD_FN_CONST ulong
13 6 : fd_pcapng_iter_footprint( void ) {
14 6 : return sizeof(fd_pcapng_iter_t);
15 6 : }
16 :
17 : static char const *
18 : fd_pcapng_iter_strerror( int error,
19 0 : FILE * file ) {
20 0 : static FD_TL char err_cstr_buf[ 1024UL ];
21 0 : char * err_cstr = fd_cstr_init( err_cstr_buf );
22 0 : if( error==EPROTO ) {
23 0 : return fd_cstr_printf( err_cstr, sizeof(err_cstr_buf), NULL, "parse error at %#lx", (ulong)ftell(file) );
24 0 : } else if( error==-1 && !feof( file ) ) {
25 0 : return "end of section";
26 0 : } else {
27 0 : return fd_cstr_printf( err_cstr, sizeof(err_cstr_buf), NULL, "%i-%s", error, fd_io_strerror( error ) );
28 0 : }
29 0 : }
30 :
31 : static int
32 : fd_pcapng_read_block( FILE * stream,
33 : fd_pcapng_iter_t * iter,
34 33 : fd_pcapng_block_hdr_t * _hdr ) {
35 :
36 : /* Remember offset of block */
37 33 : long pos = ftell( stream );
38 33 : if( FD_UNLIKELY( pos<0L ) )
39 0 : return ferror( stream );
40 33 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)pos, 4U ) ) ) {
41 0 : FD_LOG_DEBUG(( "pcapng: misaligned stream at %#lx", (ulong)pos ));
42 0 : return EPROTO;
43 0 : }
44 :
45 : /* Read header */
46 33 : fd_pcapng_block_hdr_t hdr;
47 33 : if( FD_UNLIKELY( 1UL!=fread( &hdr, sizeof(fd_pcapng_block_hdr_t), 1, stream ) ) ) {
48 6 : if( FD_LIKELY( feof( stream ) ) ) return -1; /* eof */
49 0 : else return ferror( stream );
50 6 : }
51 :
52 : /* Coherence check length field */
53 27 : if( FD_UNLIKELY( (hdr.block_sz < 12U) /* header and footer are mandatory */
54 27 : | (hdr.block_sz >32768U) /* way too large */
55 27 : | (!fd_ulong_is_aligned( hdr.block_sz, 4U )) ) ) {
56 0 : FD_LOG_DEBUG(( "pcapng: block with invalid size %#x at %#lx", hdr.block_sz, (ulong)pos ));
57 0 : return EPROTO;
58 0 : }
59 :
60 27 : if( FD_UNLIKELY( hdr.block_sz>FD_PCAPNG_BLOCK_SZ ) ) {
61 0 : FD_LOG_DEBUG(( "pcapng: block too large for buffer (%#x)", hdr.block_sz ));
62 0 : return EPROTO;
63 0 : }
64 :
65 27 : memcpy( iter->block_buf, &hdr, sizeof(fd_pcapng_block_hdr_t) );
66 27 : ulong remaining = hdr.block_sz - sizeof(fd_pcapng_block_hdr_t);
67 :
68 : /* Read rest of block */
69 27 : if( FD_UNLIKELY( 1UL!=fread( iter->block_buf + sizeof(fd_pcapng_block_hdr_t), remaining, 1, stream ) ) ) {
70 0 : if( FD_LIKELY( feof( stream ) ) ) return EPROTO; /* truncated block */
71 0 : else return ferror( stream );
72 0 : }
73 :
74 27 : iter->block_buf_sz = hdr.block_sz;
75 27 : iter->block_buf_pos = sizeof(fd_pcapng_block_hdr_t);
76 :
77 : /* Verify footer */
78 27 : uint * footer = (uint *)( iter->block_buf + hdr.block_sz - sizeof(uint) );
79 27 : uint block_sz = *footer;
80 :
81 : /* Check that header and footer match */
82 27 : if( FD_UNLIKELY( hdr.block_sz != block_sz ) ) {
83 0 : FD_LOG_DEBUG(( "pcapng: block size in header and footer don't match at %#lx", (ulong)pos ));
84 0 : return EPROTO;
85 0 : }
86 :
87 27 : *_hdr = hdr;
88 :
89 27 : return 0; /* success */
90 27 : }
91 :
92 : static int
93 : fd_pcapng_read_option( fd_pcapng_iter_t * iter,
94 45 : fd_pcapng_option_t * opt ) {
95 :
96 45 : if( FD_UNLIKELY( iter->block_buf_pos + 4UL > iter->block_buf_sz ) ) {
97 3 : opt->type = 0;
98 3 : opt->sz = 0;
99 3 : opt->value = NULL;
100 3 : return 0;
101 3 : }
102 :
103 42 : struct __attribute__((packed)) {
104 42 : ushort type;
105 42 : ushort sz;
106 42 : } opt_hdr;
107 42 : memcpy( &opt_hdr, iter->block_buf + iter->block_buf_pos, 4UL );
108 42 : iter->block_buf_pos += 4UL;
109 42 : if( FD_UNLIKELY( opt_hdr.sz > (iter->block_buf_sz - iter->block_buf_pos) ) ) {
110 0 : iter->error = EPROTO;
111 0 : FD_LOG_WARNING(( "option size out of bounds" ));
112 0 : return EPROTO;
113 0 : }
114 :
115 42 : uint read_sz = fd_uint_min( opt_hdr.sz, opt->sz );
116 42 : opt->type = opt_hdr.type;
117 42 : opt->sz = (ushort)read_sz;
118 :
119 42 : if( read_sz ) {
120 21 : if( FD_UNLIKELY( iter->block_buf_pos + read_sz > iter->block_buf_sz ) ) {
121 0 : iter->error = EPROTO;
122 0 : FD_LOG_WARNING(( "out of bounds option" ));
123 0 : return EPROTO;
124 0 : }
125 21 : memcpy( opt->value, iter->block_buf + iter->block_buf_pos, read_sz );
126 21 : fd_msan_unpoison( opt->value, read_sz );
127 21 : }
128 :
129 42 : iter->block_buf_pos += fd_uint_align_up( opt_hdr.sz, 4U );
130 42 : if( FD_UNLIKELY( iter->block_buf_pos > iter->block_buf_sz ) )
131 0 : return EPROTO;
132 :
133 42 : return 0; /* success */
134 42 : }
135 :
136 : fd_pcapng_iter_t *
137 : fd_pcapng_iter_new( void * mem,
138 6 : void * _file ) {
139 :
140 6 : if( FD_UNLIKELY( !mem ) ) {
141 0 : FD_LOG_WARNING(( "NULL mem" ));
142 0 : return NULL;
143 0 : }
144 6 : if( FD_UNLIKELY( !fd_ulong_is_aligned( (ulong)mem, alignof(fd_pcapng_iter_t) ) ) ) {
145 0 : FD_LOG_WARNING(( "unaligned mem" ));
146 0 : return NULL;
147 0 : }
148 6 : if( FD_UNLIKELY( !_file ) ) {
149 0 : FD_LOG_WARNING(( "NULL file" ));
150 0 : return NULL;
151 0 : }
152 :
153 6 : FILE * file = (FILE *)_file;
154 :
155 6 : memset( mem, 0, sizeof(fd_pcapng_iter_t) );
156 6 : fd_pcapng_iter_t * iter = (fd_pcapng_iter_t *)mem;
157 6 : iter->stream = (FILE *)file;
158 6 : iter->empty = 1;
159 :
160 : /* File starts with a Section Header Block */
161 :
162 6 : fd_pcapng_block_hdr_t shb_hdr;
163 6 : int err = fd_pcapng_read_block( file, iter, &shb_hdr );
164 6 : if( FD_UNLIKELY( err ) ) {
165 0 : FD_LOG_WARNING(( "pcapng: SHB read failed (%s)", fd_pcapng_iter_strerror( err, file ) ));
166 0 : return NULL;
167 0 : }
168 6 : if( FD_UNLIKELY( shb_hdr.block_type!=FD_PCAPNG_BLOCK_TYPE_SHB
169 6 : || shb_hdr.block_sz < sizeof(fd_pcapng_shb_t) ) ) {
170 0 : FD_LOG_WARNING(( "pcapng: not a valid Section Header Block" ));
171 0 : return NULL;
172 0 : }
173 :
174 :
175 6 : fd_pcapng_shb_t shb = FD_LOAD( fd_pcapng_shb_t, iter->block_buf );
176 6 : if( FD_UNLIKELY( (shb.version_major!=1) | (shb.version_minor!=0) ) ) {
177 0 : FD_LOG_WARNING(( "pcapng: unsupported file format version %u.%u",
178 0 : shb.version_major, shb.version_minor ));
179 0 : return NULL;
180 0 : }
181 :
182 6 : return iter;
183 6 : }
184 :
185 : void *
186 3 : fd_pcapng_iter_delete( fd_pcapng_iter_t * iter ) {
187 3 : void * mem = (void *)iter;
188 3 : memset( mem, 0, sizeof(fd_pcapng_iter_t) );
189 3 : return mem;
190 3 : }
191 :
192 : static fd_pcapng_frame_t *
193 21 : fd_pcapng_iter_next1( fd_pcapng_iter_t * iter ) {
194 21 : fd_pcapng_frame_t * pkt = &iter->pkt;
195 :
196 : /* Clear fields */
197 21 : pkt->ts = 0L;
198 21 : pkt->type = 0U;
199 21 : pkt->data_sz = 0U;
200 21 : pkt->orig_sz = 0U;
201 21 : pkt->if_idx = 0U;
202 21 : pkt->idb = NULL;
203 :
204 21 : FILE * stream = iter->stream;
205 :
206 : /* Attempt a number of times to find a frame of known type.
207 : Abort if there are too many unknown frames. */
208 27 : for( uint attempt=0U; attempt<256U; attempt++ ) {
209 :
210 27 : fd_pcapng_block_hdr_t hdr;
211 27 : if( FD_UNLIKELY( 0!=(iter->error = fd_pcapng_read_block( stream, iter, &hdr )) ) ) {
212 6 : if( FD_UNLIKELY( iter->error != -1 ) )
213 0 : FD_LOG_WARNING(( "pcapng: read failed (%s)", fd_pcapng_iter_strerror( iter->error, stream ) ));
214 6 : return NULL;
215 6 : }
216 :
217 21 : switch( hdr.block_type ) {
218 0 : case FD_PCAPNG_BLOCK_TYPE_SHB: {
219 0 : iter->error = -1; /* eof */
220 : /* FIXME CONSIDER SILENTLY CONTINUING? */
221 0 : return NULL;
222 0 : }
223 6 : case FD_PCAPNG_BLOCK_TYPE_IDB: {
224 : /* Read IDB */
225 6 : if( FD_UNLIKELY( hdr.block_sz<sizeof(fd_pcapng_idb_t) ) ) {
226 0 : iter->error = EPROTO;
227 0 : FD_LOG_WARNING(( "pcapng: invalid IDB block size (%#x)", hdr.block_sz ));
228 0 : return NULL;
229 0 : }
230 6 : fd_pcapng_idb_t idb = FD_LOAD( fd_pcapng_idb_t, iter->block_buf );
231 6 : iter->block_buf_pos = sizeof(fd_pcapng_idb_t);
232 :
233 : /* Add interface to list */
234 6 : if( FD_UNLIKELY( iter->iface_cnt>=FD_PCAPNG_IFACE_CNT ) ) {
235 0 : iter->error = EPROTO;
236 0 : FD_LOG_WARNING(( "pcapng: too many interfaces (max %d)", FD_PCAPNG_IFACE_CNT ));
237 0 : return NULL;
238 0 : }
239 :
240 6 : fd_pcapng_idb_desc_t * iface = &iter->iface[ iter->iface_cnt++ ];
241 6 : memset( iface, 0, sizeof(fd_pcapng_idb_desc_t) );
242 6 : iface->link_type = idb.link_type;
243 :
244 : /* Read options */
245 27 : for( uint j=0; j<FD_PCAPNG_MAX_OPT_CNT; j++ ) {
246 27 : uchar opt_buf[ 128UL ] __attribute__((aligned(32UL)));
247 27 : fd_pcapng_option_t opt = { .sz=sizeof(opt_buf), .value=&opt_buf };
248 27 : if( FD_UNLIKELY( 0!=(iter->error = fd_pcapng_read_option( iter, &opt )) ) ) {
249 0 : FD_LOG_WARNING(( "pcapng: read failed (%s)", fd_pcapng_iter_strerror( iter->error, stream ) ));
250 0 : return NULL;
251 0 : }
252 27 : if( !opt.type ) break;
253 21 : switch( opt.type ) {
254 0 : case FD_PCAPNG_OPT_COMMENT:
255 0 : FD_LOG_HEXDUMP_DEBUG(( "IDB comment", opt_buf, opt.sz ));
256 0 : break;
257 6 : case FD_PCAPNG_IDB_OPT_NAME:
258 6 : fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( iface->opts.name ), (char const *)opt_buf, fd_ulong_min( sizeof(iface->opts.name)-1, opt.sz ) ) );
259 6 : iface->opts.name[ sizeof(iface->opts.name)-1 ] = '\0';
260 6 : break;
261 3 : case FD_PCAPNG_IDB_OPT_HARDWARE:
262 3 : fd_cstr_fini( fd_cstr_append_text( fd_cstr_init( iface->opts.hardware ), (char const *)opt_buf, fd_ulong_min( sizeof(iface->opts.hardware)-1, opt.sz ) ) );
263 3 : iface->opts.hardware[ sizeof(iface->opts.hardware)-1 ] = '\0';
264 3 : break;
265 3 : case FD_PCAPNG_IDB_OPT_IPV4_ADDR:
266 3 : if( FD_UNLIKELY( opt.sz!=4U ) )
267 0 : continue;
268 3 : memcpy( iface->opts.ip4_addr, opt_buf, 4UL );
269 3 : break;
270 3 : case FD_PCAPNG_IDB_OPT_MAC_ADDR:
271 3 : if( FD_UNLIKELY( opt.sz!=6U ) )
272 0 : continue;
273 3 : memcpy( iface->opts.mac_addr, opt_buf, 6UL );
274 3 : break;
275 6 : case FD_PCAPNG_IDB_OPT_TSRESOL:
276 6 : if( FD_UNLIKELY( opt.sz!=1U ) )
277 0 : continue;
278 6 : iface->opts.tsresol = opt_buf[ 0 ];
279 6 : break;
280 0 : default:
281 0 : FD_LOG_DEBUG(( "Ignoring unknown IDB option type %#x", opt.type ));
282 0 : break;
283 21 : }
284 21 : }
285 :
286 6 : break;
287 6 : }
288 6 : case FD_PCAPNG_BLOCK_TYPE_SPB: {
289 : /* Read SPB */
290 0 : if( FD_UNLIKELY( hdr.block_sz<sizeof(fd_pcapng_spb_t) ) ) {
291 0 : iter->error = EPROTO;
292 0 : FD_LOG_WARNING(( "pcapng: invalid SPB block size (%#x)", hdr.block_sz ));
293 0 : return NULL;
294 0 : }
295 :
296 0 : uint hdr_sz = sizeof(fd_pcapng_spb_t);
297 0 : uint data_sz = hdr.block_sz - hdr_sz;
298 :
299 0 : fd_pcapng_spb_t spb = FD_LOAD( fd_pcapng_spb_t, iter->block_buf );
300 0 : iter->block_buf_pos = hdr_sz;
301 :
302 0 : if( FD_UNLIKELY( spb.orig_len > (iter->block_buf_sz - iter->block_buf_pos) ) ) {
303 0 : iter->error = EPROTO;
304 0 : FD_LOG_WARNING(( "pcapng: invalid SPB block size (%#x)", hdr.block_sz ));
305 0 : return NULL;
306 0 : }
307 0 : pkt->data = iter->block_buf + iter->block_buf_pos;
308 :
309 0 : pkt->type = FD_PCAPNG_FRAME_SIMPLE;
310 0 : pkt->data_sz = (ushort)data_sz;
311 0 : pkt->orig_sz = (ushort)spb.orig_len;
312 0 : return pkt;
313 0 : }
314 12 : case FD_PCAPNG_BLOCK_TYPE_EPB: {
315 : /* Read EPB */
316 12 : if( FD_UNLIKELY( hdr.block_sz<sizeof(fd_pcapng_epb_t) ) ) {
317 0 : iter->error = EPROTO;
318 0 : FD_LOG_WARNING(( "pcapng: invalid EPB block size (%#x)", hdr.block_sz ));
319 0 : return NULL;
320 0 : }
321 :
322 12 : fd_pcapng_epb_t epb = FD_LOAD( fd_pcapng_epb_t, iter->block_buf );
323 12 : iter->block_buf_pos = sizeof(fd_pcapng_epb_t);
324 :
325 12 : if( FD_UNLIKELY( epb.cap_len > (iter->block_buf_sz - iter->block_buf_pos) ) ) {
326 0 : iter->error = EPROTO;
327 0 : FD_LOG_WARNING(( "pcapng: invalid EPB block size (%#x)", hdr.block_sz ));
328 0 : return NULL;
329 0 : }
330 12 : pkt->data = iter->block_buf + iter->block_buf_pos;
331 12 : iter->block_buf_pos += fd_uint_align_up( epb.cap_len, 4U );
332 :
333 : /* Read options */
334 15 : for( uint j=0; j<FD_PCAPNG_MAX_OPT_CNT; j++ ) {
335 15 : uchar opt_buf[ 128UL ] __attribute__((aligned(32UL)));
336 15 : fd_pcapng_option_t opt = { .sz=sizeof(opt_buf), .value=&opt_buf };
337 15 : if( FD_UNLIKELY( 0!=(iter->error = fd_pcapng_read_option( iter, &opt )) ) ) {
338 0 : FD_LOG_WARNING(( "pcapng: read failed (%s)", fd_pcapng_iter_strerror( iter->error, stream ) ));
339 0 : return NULL;
340 0 : }
341 15 : if( !opt.type ) break;
342 3 : switch( opt.type ) {
343 0 : case FD_PCAPNG_OPT_COMMENT:
344 0 : FD_LOG_HEXDUMP_DEBUG(( "Packet comment", opt_buf, opt.sz ));
345 0 : break;
346 3 : default:
347 3 : FD_LOG_DEBUG(( "Ignoring unknown EPB option type %#x", opt.type ));
348 3 : break;
349 3 : }
350 3 : }
351 :
352 12 : if( FD_LIKELY( epb.if_idx < iter->iface_cnt ) ) {
353 12 : ulong raw = ( ((ulong)epb.ts_hi << 32UL) | (ulong)epb.ts_lo );
354 : /* FIXME support more timestamp resolutions */
355 12 : if( iter->iface[ epb.if_idx ].opts.tsresol == FD_PCAPNG_TSRESOL_NS ) {
356 12 : pkt->ts = (long)raw;
357 12 : }
358 12 : }
359 :
360 12 : pkt->type = FD_PCAPNG_FRAME_ENHANCED;
361 12 : pkt->data_sz = (ushort)epb.cap_len;
362 12 : pkt->orig_sz = (ushort)epb.orig_len;
363 12 : pkt->if_idx = epb.if_idx;
364 12 : pkt->idb = (epb.if_idx<iter->iface_cnt) ? &iter->iface[ epb.if_idx ] : NULL;
365 12 : return pkt;
366 12 : }
367 3 : case FD_PCAPNG_BLOCK_TYPE_DSB: {
368 : /* Read DSB */
369 3 : if( FD_UNLIKELY( hdr.block_sz<sizeof(fd_pcapng_dsb_t) ) ) {
370 0 : iter->error = EPROTO;
371 0 : FD_LOG_WARNING(( "pcapng: invalid DSB block size (%#x)", hdr.block_sz ));
372 0 : return NULL;
373 0 : }
374 :
375 3 : fd_pcapng_dsb_t dsb = FD_LOAD( fd_pcapng_dsb_t, iter->block_buf );
376 3 : iter->block_buf_pos = sizeof(fd_pcapng_dsb_t);
377 :
378 3 : if( FD_UNLIKELY( dsb.secret_sz > (iter->block_buf_sz - iter->block_buf_pos) ) ) {
379 0 : iter->error = EPROTO;
380 0 : FD_LOG_WARNING(( "pcapng: invalid DSB block size (%#x)", hdr.block_sz ));
381 0 : return NULL;
382 0 : }
383 3 : pkt->data = iter->block_buf + sizeof(fd_pcapng_dsb_t);
384 3 : iter->block_buf_pos += fd_uint_align_up( dsb.secret_sz, 4U );
385 :
386 : /* Read options */
387 3 : for( uint j=0; j<FD_PCAPNG_MAX_OPT_CNT; j++ ) {
388 3 : uchar opt_buf[ 128UL ] __attribute__((aligned(32UL)));
389 3 : fd_pcapng_option_t opt = { .sz=sizeof(opt_buf), .value=&opt_buf };
390 3 : if( FD_UNLIKELY( 0!=(iter->error = fd_pcapng_read_option( iter, &opt )) ) ) {
391 0 : FD_LOG_WARNING(( "pcapng: read failed (%s)", fd_pcapng_iter_strerror( iter->error, stream ) ));
392 0 : return NULL;
393 0 : }
394 3 : if( !opt.type ) break;
395 0 : switch( opt.type ) {
396 0 : case FD_PCAPNG_OPT_COMMENT:
397 0 : FD_LOG_HEXDUMP_DEBUG(( "Decryption secrets comment", opt_buf, opt.sz ));
398 0 : break;
399 0 : default:
400 0 : FD_LOG_DEBUG(( "Ignoring unknown DSB option type %#x", opt.type ));
401 0 : break;
402 0 : }
403 0 : }
404 :
405 3 : if( dsb.secret_type!=FD_PCAPNG_SECRET_TYPE_TLS ) {
406 0 : FD_LOG_DEBUG(( "Ignoring secret (type %#x)", dsb.secret_type ));
407 0 : break;
408 0 : }
409 :
410 3 : pkt->type = FD_PCAPNG_FRAME_TLSKEYS;
411 3 : pkt->data_sz = dsb.secret_sz;
412 3 : return pkt;
413 3 : }
414 0 : default:
415 0 : FD_LOG_DEBUG(( "pcapng: skipping unknown block (type=%#x)", hdr.block_type ));
416 0 : break;
417 21 : }
418 21 : }
419 :
420 : /* Found no blocks that are interesting to user */
421 0 : iter->error = EPROTO;
422 0 : FD_LOG_WARNING(( "pcapng: aborting, too many non-packet frames" ));
423 0 : return NULL;
424 21 : }
425 :
426 : fd_pcapng_frame_t *
427 21 : fd_pcapng_iter_next( fd_pcapng_iter_t * iter ) {
428 21 : fd_pcapng_frame_t * frame = fd_pcapng_iter_next1( iter );
429 21 : iter->empty = !frame;
430 21 : return frame;
431 21 : }
432 :
433 : fd_pcapng_frame_t *
434 0 : fd_pcapng_iter_ele( fd_pcapng_iter_t * iter ) {
435 0 : if( FD_UNLIKELY( iter->empty ) ) return NULL;
436 0 : return &iter->pkt;
437 0 : }
438 :
439 : FD_FN_PURE int
440 9 : fd_pcapng_iter_err( fd_pcapng_iter_t const * iter ) {
441 9 : return iter->error;
442 9 : }
|