Coverage Report

Created: 2026-11-12 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wireshark/epan/dissectors/packet-ieee80211-radiotap-iter.c
Line
Count
Source
1
/*
2
 * Radiotap parser
3
 *
4
 * Copyright 2007   Andy Green <andy@warmcat.com>
5
 * Copyright 2009   Johannes Berg <johannes@sipsolutions.net>
6
 *
7
 * SPDX-License-Identifier: (ISC OR GPL-2.0-only)
8
 */
9
10
#include "config.h"
11
12
#include <errno.h>
13
14
#include <epan/packet.h>
15
16
#include <wsutil/pint.h>
17
#include <wsutil/array.h>
18
19
#define le16_to_cpu   GINT16_FROM_LE
20
#define le32_to_cpu   GINT32_FROM_LE
21
83
#define get_unaligned_le16  pletohu16
22
771
#define get_unaligned_le32  pletohu32
23
24
#include "packet-ieee80211-radiotap-iter.h"
25
26
/* function prototypes and related defs are in radiotap_iter.h */
27
28
static const struct radiotap_align_size rtap_namespace_sizes[] = {
29
  /* [IEEE80211_RADIOTAP_TSFT] = 0 */     { 8, 8 },
30
  /* [IEEE80211_RADIOTAP_FLAGS] = 1 */      { 1, 1 },
31
  /* [IEEE80211_RADIOTAP_RATE] = 2 */     { 1, 1 },
32
  /* [IEEE80211_RADIOTAP_CHANNEL] = 3 */      { 2, 4 },
33
  /* [IEEE80211_RADIOTAP_FHSS] = 4 */     { 2, 2 },
34
  /* [IEEE80211_RADIOTAP_DBM_ANTSIGNAL] = 5 */    { 1, 1 },
35
  /* [IEEE80211_RADIOTAP_DBM_ANTNOISE] = 6 */   { 1, 1 },
36
  /* [IEEE80211_RADIOTAP_LOCK_QUALITY] = 7 */   { 2, 2 },
37
  /* [IEEE80211_RADIOTAP_TX_ATTENUATION] = 8 */   { 2, 2 },
38
  /* [IEEE80211_RADIOTAP_DB_TX_ATTENUATION] = 9 */  { 2, 2 },
39
  /* [IEEE80211_RADIOTAP_DBM_TX_POWER] = 10 */    { 1, 1 },
40
  /* [IEEE80211_RADIOTAP_ANTENNA] = 11 */     { 1, 1 },
41
  /* [IEEE80211_RADIOTAP_DB_ANTSIGNAL] = 12 */    { 1, 1 },
42
  /* [IEEE80211_RADIOTAP_DB_ANTNOISE] = 13 */   { 1, 1 },
43
  /* [IEEE80211_RADIOTAP_RX_FLAGS] = 14 */    { 2, 2 },
44
  /* [IEEE80211_RADIOTAP_TX_FLAGS] = 15 */    { 2, 2 },
45
  /* [IEEE80211_RADIOTAP_RTS_RETRIES] = 16 */   { 1, 1 },
46
  /* [IEEE80211_RADIOTAP_DATA_RETRIES] = 17 */    { 1, 1 },
47
  /* [IEEE80211_RADIOTAP_XCHANNEL] = 18 */    { 0, 0 }, /* Unofficial, used by FreeBSD */
48
  /* [IEEE80211_RADIOTAP_MCS] = 19 */     { 1, 3 },
49
  /* [IEEE80211_RADIOTAP_AMPDU_STATUS] = 20 */    { 4, 8 },
50
  /* [IEEE80211_RADIOTAP_VHT] = 21 */     { 2, 12 },
51
  /* [IEEE80211_RADIOTAP_TIMESTAMP] = 22 */   { 8, 12 },
52
  /* [IEEE80211_RADIOTAP_HE] = 23 */      { 2, 12 },
53
  /* [IEEE80211_RADIOTAP_HE_MU] = 24 */     { 2, 12 },
54
  /* [IEEE80211_RADIOTAP_HE_MU_USER = 25 notdef */  { 0, 0 },
55
  /* [IEEE80211_RADIOTAP_0_LENGTH_PSDU = 26 */    { 1, 1 },
56
  /* [IEEE80211_RADIOTAP_L_SIG = 27 */      { 2, 4 },
57
  /* [IEEE80211_RADIOTAP_TLV = 28 */                      { 4, 10 },
58
  /*
59
   * add more here as they are defined in
60
   * include/net/ieee80211_radiotap.h
61
   */
62
};
63
64
static const struct ieee80211_radiotap_namespace radiotap_ns = {
65
  rtap_namespace_sizes,
66
  (int)array_length(rtap_namespace_sizes),
67
  0,
68
  0
69
};
70
71
/*
72
 * Sanity check.
73
 */
74
#define ITERATOR_VALID(iterator, size) \
75
3.08k
    (((iterator)->_arg + (size) - (unsigned char *)((iterator)->_rtheader)) <= \
76
3.08k
        (ptrdiff_t)(iterator)->_max_length)
77
78
/**
79
 * ieee80211_radiotap_iterator_init - radiotap parser iterator initialization
80
 * @iterator: radiotap_iterator to initialize
81
 * @radiotap_header: radiotap header to parse
82
 * @max_length: total length we can parse into (eg, whole packet length)
83
 *
84
 * Returns: 0 or a negative error code if there is a problem.
85
 *
86
 * This function initializes an opaque iterator struct which can then
87
 * be passed to ieee80211_radiotap_iterator_next() to visit every radiotap
88
 * argument which is present in the header.  It knows about extended
89
 * present headers and handles them.
90
 *
91
 * How to use:
92
 * call __ieee80211_radiotap_iterator_init() to init a semi-opaque iterator
93
 * struct ieee80211_radiotap_iterator (no need to init the struct beforehand)
94
 * checking for a good 0 return code.  Then loop calling
95
 * __ieee80211_radiotap_iterator_next()... it returns either 0,
96
 * -ENOENT if there are no more args to parse, or -EINVAL if there is a problem.
97
 * The iterator's @this_arg member points to the start of the argument
98
 * associated with the current argument index that is present, which can be
99
 * found in the iterator's @this_arg_index member.  This arg index corresponds
100
 * to the IEEE80211_RADIOTAP_... defines.
101
 *
102
 * Radiotap header length:
103
 * You can find the CPU-endian total radiotap header length in
104
 * iterator->max_length after executing ieee80211_radiotap_iterator_init()
105
 * successfully.
106
 *
107
 * Alignment Gotcha:
108
 * You must take care when dereferencing iterator.this_arg
109
 * for multibyte types... the pointer is not aligned.  Use
110
 * get_unaligned((type *)iterator.this_arg) to dereference
111
 * iterator.this_arg for type "type" safely on all arches.
112
 *
113
 * Example code:
114
 * See https://www.kernel.org/doc/Documentation/networking/radiotap-headers.txt
115
 */
116
117
int ieee80211_radiotap_iterator_init(
118
  struct ieee80211_radiotap_iterator *iterator,
119
  struct ieee80211_radiotap_header *radiotap_header,
120
  int max_length, const struct ieee80211_radiotap_vendor_namespaces *vns)
121
31
{
122
  /* XXX - in Wireshark, we've already checked for this */
123
31
  if (max_length < (int)sizeof(struct ieee80211_radiotap_header))
124
0
    return -EINVAL;
125
126
  /* Linux only supports version 0 radiotap format */
127
  /* XXX - this is Wireshark, not Linux, and we should report an expert info */
128
31
  if (radiotap_header->it_version)
129
0
    return -EINVAL;
130
131
  /* sanity check for allowed length and radiotap length field */
132
  /* XXX - in Wireshark, this compares the length against itself. */
133
31
  if (max_length < get_unaligned_le16(&radiotap_header->it_len))
134
0
    return -EINVAL;
135
136
31
  iterator->_rtheader = radiotap_header;
137
31
  iterator->_max_length = get_unaligned_le16(&radiotap_header->it_len);
138
31
  iterator->_arg_index = 0;
139
31
  iterator->_bitmap_shifter = get_unaligned_le32(&radiotap_header->it_present);
140
31
  iterator->_arg = (uint8_t *)radiotap_header + sizeof(*radiotap_header);
141
31
  iterator->_reset_on_ext = 0;
142
31
  iterator->_next_ns_data = NULL;
143
31
  iterator->_next_bitmap = &radiotap_header->it_present;
144
31
  iterator->_next_bitmap++;
145
31
  iterator->_vns = vns;
146
31
  iterator->current_namespace = &radiotap_ns;
147
31
  iterator->is_radiotap_ns = 1;
148
31
  iterator->tlv_mode = 0;
149
31
#ifdef RADIOTAP_SUPPORT_OVERRIDES
150
31
  iterator->n_overrides = 0;
151
31
  iterator->overrides = NULL;
152
31
#endif
153
154
  /* find payload start allowing for extended bitmap(s) */
155
31
  if (iterator->_bitmap_shifter & (1U << IEEE80211_RADIOTAP_EXT)) {
156
    /* XXX - we should report an expert info here */
157
27
    if (!ITERATOR_VALID(iterator, sizeof(uint32_t)))
158
0
      return -EINVAL;
159
278
    while (get_unaligned_le32(iterator->_arg) &
160
278
          (1U << IEEE80211_RADIOTAP_EXT)) {
161
253
      iterator->_arg += sizeof(uint32_t);
162
163
      /*
164
       * check for insanity where the present bitmaps
165
       * keep claiming to extend up to or even beyond the
166
       * stated radiotap header length
167
       */
168
      /* XXX - we should report an expert info here */
169
253
      if (!ITERATOR_VALID(iterator, sizeof(uint32_t)))
170
0
        return -EINVAL;
171
172
      /* XXX - we should report an expert info here */
173
253
      if ((get_unaligned_le32(iterator->_arg) &
174
253
          (1U << IEEE80211_RADIOTAP_TLVS)) &&
175
2
          (get_unaligned_le32(iterator->_arg) &
176
2
          (1U << IEEE80211_RADIOTAP_EXT)))
177
2
        return -EINVAL;
178
253
    }
179
180
25
    iterator->_arg += sizeof(uint32_t);
181
182
    /*
183
     * no need to check again for blowing past stated radiotap
184
     * header length, because ieee80211_radiotap_iterator_next
185
     * checks it before it is dereferenced
186
     */
187
25
  }
188
189
29
  iterator->this_arg = iterator->_arg;
190
191
  /* we are all initialized happily */
192
193
29
  return 0;
194
31
}
195
196
static void find_ns(struct ieee80211_radiotap_iterator *iterator,
197
        uint32_t oui, uint8_t subns)
198
3
{
199
3
  int i;
200
201
3
  iterator->current_namespace = NULL;
202
203
3
  if (!iterator->_vns)
204
3
    return;
205
206
0
  for (i = 0; i < iterator->_vns->n_ns; i++) {
207
0
    if (iterator->_vns->ns[i].oui != oui)
208
0
      continue;
209
0
    if (iterator->_vns->ns[i].subns != subns)
210
0
      continue;
211
212
0
    iterator->current_namespace = &iterator->_vns->ns[i];
213
0
    break;
214
0
  }
215
0
}
216
217
#ifdef RADIOTAP_SUPPORT_OVERRIDES
218
static int find_override(struct ieee80211_radiotap_iterator *iterator,
219
       int *align, int *size)
220
2.35k
{
221
2.35k
  int i;
222
223
2.35k
  if (!iterator->overrides)
224
0
    return 0;
225
226
4.65k
  for (i = 0; i < iterator->n_overrides; i++) {
227
2.35k
    if (iterator->_arg_index == iterator->overrides[i].field) {
228
50
      *align = iterator->overrides[i].align;
229
50
      *size = iterator->overrides[i].size;
230
50
      if (!*align) /* erroneous override */
231
0
        return 0;
232
50
      return 1;
233
50
    }
234
2.35k
  }
235
236
2.30k
  return 0;
237
2.35k
}
238
#endif
239
240
241
/**
242
 * ieee80211_radiotap_iterator_next - return next radiotap parser iterator arg
243
 * @iterator: radiotap_iterator to move to next arg (if any)
244
 *
245
 * Returns: 0 if there is an argument to handle,
246
 * -ENOENT if there are no more args or -EINVAL
247
 * if there is something else wrong.
248
 *
249
 * This function provides the next radiotap arg index (IEEE80211_RADIOTAP_*)
250
 * in @this_arg_index and sets @this_arg to point to the
251
 * payload for the field.  It takes care of alignment handling and extended
252
 * present fields.  @this_arg can be changed by the caller (eg,
253
 * incremented to move inside a compound argument like
254
 * IEEE80211_RADIOTAP_CHANNEL).  The args pointed to are in
255
 * little-endian format whatever the endianess of your CPU.
256
 *
257
 * Alignment Gotcha:
258
 * You must take care when dereferencing iterator.this_arg
259
 * for multibyte types... the pointer is not aligned.  Use
260
 * get_unaligned((type *)iterator.this_arg) to dereference
261
 * iterator.this_arg for type "type" safely on all arches.
262
 */
263
264
int ieee80211_radiotap_iterator_next(
265
  struct ieee80211_radiotap_iterator *iterator)
266
2.37k
{
267
2.37k
  if (iterator->tlv_mode) {
268
7
    struct ieee80211_radiotap_tlv *tlv;
269
7
    uint32_t size;
270
271
14
#define TLV_LEN_ALIGN(x) ((x + 3) & ~3)
272
7
    size = sizeof(*tlv) + TLV_LEN_ALIGN(iterator->this_arg_size);
273
274
    /*
275
     * We know that without the alignment padding it was valid, so
276
     * ignore arbitrary padding and return that we finished if no
277
     * further TLV could fit.
278
     */
279
7
    if (!ITERATOR_VALID(iterator, size))
280
0
      return -ENOENT;
281
282
    /* move to next entry */
283
7
    iterator->_arg += sizeof(*tlv) + TLV_LEN_ALIGN(iterator->this_arg_size);
284
285
9
return_tlv:
286
    /* and check again if we reached the end */
287
9
    if (!ITERATOR_VALID(iterator, 1))
288
0
      return -ENOENT;
289
290
    /* if it's not the end but a new TLV won't fit - error out */
291
9
    if (!ITERATOR_VALID(iterator, sizeof(*tlv)))
292
0
      return -EINVAL;
293
294
9
    tlv = (struct ieee80211_radiotap_tlv *)iterator->_arg;
295
296
9
    iterator->this_arg_index = get_unaligned_le16(&tlv->type);
297
9
    iterator->this_arg_size = get_unaligned_le16(&tlv->datalen);
298
9
    iterator->this_arg = tlv->data;
299
9
    iterator->is_radiotap_ns =
300
9
      iterator->this_arg_index != IEEE80211_RADIOTAP_VENDOR_NAMESPACE;
301
302
9
    if (!ITERATOR_VALID(iterator, sizeof(*tlv) + iterator->this_arg_size))
303
2
      return -EINVAL;
304
7
    return 0;
305
9
  }
306
307
7.32k
  while (1) {
308
7.32k
    int hit = 0;
309
7.32k
    int pad, align, size, subns;
310
7.32k
    uint32_t oui;
311
312
    /* if no more EXT bits, that's it */
313
7.32k
    if ((iterator->_arg_index % 32) == IEEE80211_RADIOTAP_EXT &&
314
217
        !(iterator->_bitmap_shifter & 1))
315
10
      return -ENOENT;
316
317
7.31k
    if (!(iterator->_bitmap_shifter & 1))
318
4.54k
      goto next_entry; /* arg not present */
319
320
    /* get alignment/size of data */
321
2.77k
    switch (iterator->_arg_index % 32) {
322
2
    case IEEE80211_RADIOTAP_TLVS:
323
2
      align = 4;
324
2
      size = 0;
325
2
      break;
326
211
    case IEEE80211_RADIOTAP_RADIOTAP_NAMESPACE:
327
418
    case IEEE80211_RADIOTAP_EXT:
328
418
      align = 1;
329
418
      size = 0;
330
418
      break;
331
3
    case IEEE80211_RADIOTAP_VENDOR_NAMESPACE:
332
3
      align = 2;
333
3
      size = 6;
334
3
      break;
335
2.35k
    default:
336
2.35k
#ifdef RADIOTAP_SUPPORT_OVERRIDES
337
2.35k
      if (find_override(iterator, &align, &size)) {
338
        /* all set */
339
50
      } else
340
2.30k
#endif
341
2.30k
      if (!iterator->current_namespace ||
342
2.30k
          iterator->_arg_index >= iterator->current_namespace->n_bits) {
343
1
        if (iterator->current_namespace == &radiotap_ns)
344
1
          return -ENOENT;
345
0
        align = 0;
346
2.29k
      } else {
347
2.29k
        align = iterator->current_namespace->align_size[iterator->_arg_index].align;
348
2.29k
        size = iterator->current_namespace->align_size[iterator->_arg_index].size;
349
2.29k
      }
350
2.34k
      if (!align) {
351
        /* skip all subsequent data */
352
4
        int skip_size = IEEE80211_RADIOTAP_RADIOTAP_NAMESPACE - (iterator->_arg_index % 32);
353
        /* XXX - we should report an expert info here */
354
4
        if (!iterator->_next_ns_data)
355
4
          return -EINVAL;
356
0
        iterator->_arg = iterator->_next_ns_data;
357
        /* give up on this namespace */
358
0
        iterator->current_namespace = NULL;
359
0
        iterator->_next_ns_data = NULL;
360
        // Remove 1 because jump to next_entry will also shift bitmap by 1
361
0
        iterator->_bitmap_shifter >>= skip_size - 1;
362
0
        iterator->_arg_index += skip_size - 1;
363
        /* XXX - we should report an expert info here */
364
0
        if (!ITERATOR_VALID(iterator, 0))
365
0
          return -EINVAL;
366
0
        goto next_entry;
367
0
      }
368
2.34k
      break;
369
2.77k
    }
370
371
    /*
372
     * arg is present, account for alignment padding
373
     *
374
     * Note that these alignments are relative to the start
375
     * of the radiotap header.  There is no guarantee
376
     * that the radiotap header itself is aligned on any
377
     * kind of boundary.
378
     *
379
     * The above is why get_unaligned() is used to dereference
380
     * multibyte elements from the radiotap area.
381
     */
382
383
2.76k
    pad = (int)((iterator->_arg - (unsigned char *)iterator->_rtheader) & (align - 1));
384
385
2.76k
    if (pad)
386
437
      iterator->_arg += align - pad;
387
388
2.76k
    if (iterator->_arg_index % 32 == IEEE80211_RADIOTAP_VENDOR_NAMESPACE) {
389
3
      int vnslen;
390
391
      /* XXX - we should report an expert info here */
392
3
      if (!ITERATOR_VALID(iterator, size))
393
0
        return -EINVAL;
394
395
3
      oui = (*iterator->_arg << 16) |
396
3
        (*(iterator->_arg + 1) << 8) |
397
3
        *(iterator->_arg + 2);
398
3
      subns = *(iterator->_arg + 3);
399
400
3
      find_ns(iterator, oui, subns);
401
402
3
      vnslen = get_unaligned_le16(iterator->_arg + 4);
403
3
      iterator->_next_ns_data = iterator->_arg + size + vnslen;
404
3
      if (!iterator->current_namespace)
405
3
        size += vnslen;
406
2.76k
    } else if (iterator->_arg_index % 32 == IEEE80211_RADIOTAP_TLVS) {
407
2
      iterator->tlv_mode = 1;
408
2
      goto return_tlv;
409
2
    }
410
411
    /*
412
     * this is what we will return to user, but we need to
413
     * move on first so next call has something fresh to test
414
     */
415
2.76k
    iterator->this_arg_index = iterator->_arg_index;
416
2.76k
    iterator->this_arg = iterator->_arg;
417
2.76k
    iterator->this_arg_size = size;
418
419
    /* internally move on the size of this arg */
420
2.76k
    iterator->_arg += size;
421
422
    /*
423
     * check for insanity where we are given a bitmap that
424
     * claims to have more arg content than the length of the
425
     * radiotap section.  We will normally end up equalling this
426
     * max_length on the last arg, never exceeding it.
427
     */
428
    /* XXX - we should report an expert info here */
429
2.76k
    if (!ITERATOR_VALID(iterator, 0))
430
10
      return -EINVAL;
431
432
    /* these special ones are valid in each bitmap word */
433
2.75k
    switch (iterator->_arg_index % 32) {
434
0
    case IEEE80211_RADIOTAP_VENDOR_NAMESPACE:
435
0
      iterator->_reset_on_ext = 1;
436
437
0
      iterator->is_radiotap_ns = 0;
438
      /*
439
       * If parser didn't register this vendor
440
       * namespace with us, allow it to show it
441
       * as 'raw. Do do that, set argument index
442
       * to vendor namespace.
443
       */
444
0
      iterator->this_arg_index =
445
0
        IEEE80211_RADIOTAP_VENDOR_NAMESPACE;
446
0
      if (!iterator->current_namespace)
447
0
        hit = 1;
448
0
      goto next_entry;
449
211
    case IEEE80211_RADIOTAP_RADIOTAP_NAMESPACE:
450
211
      iterator->_reset_on_ext = 1;
451
211
      iterator->current_namespace = &radiotap_ns;
452
211
      iterator->is_radiotap_ns = 1;
453
211
      goto next_entry;
454
207
    case IEEE80211_RADIOTAP_EXT:
455
      /*
456
       * bit 31 was set, there is more
457
       * -- move to next u32 bitmap
458
       */
459
207
      iterator->_bitmap_shifter =
460
207
        get_unaligned_le32(iterator->_next_bitmap);
461
207
      iterator->_next_bitmap++;
462
207
      if (iterator->_reset_on_ext)
463
206
        iterator->_arg_index = 0;
464
1
      else
465
1
        iterator->_arg_index++;
466
207
      iterator->_reset_on_ext = 0;
467
207
      break;
468
2.33k
    default:
469
      /* we've got a hit! */
470
2.33k
      hit = 1;
471
7.09k
 next_entry:
472
7.09k
      iterator->_bitmap_shifter >>= 1;
473
7.09k
      iterator->_arg_index++;
474
2.75k
    }
475
476
    /* if we found a valid arg earlier, return it now */
477
7.29k
    if (hit)
478
2.33k
      return 0;
479
7.29k
  }
480
2.36k
}
481
482
/*
483
 * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
484
 *
485
 * Local variables:
486
 * c-basic-offset: 8
487
 * tab-width: 8
488
 * indent-tabs-mode: t
489
 * End:
490
 *
491
 * vi: set shiftwidth=8 tabstop=8 noexpandtab:
492
 * :indentSize=8:tabSize=8:noTabs=false:
493
 */