Coverage Report

Created: 2026-01-10 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/protocols/tftp/decode.c
Line
Count
Source
1
/*
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/**
18
 * $Id: 693a9fdc174b57014a92543c92a75603771d3fe3 $
19
 * @file src/protocols/tftp/decode.c
20
 * @brief Functions to decode TFTP packets.
21
 * @author Jorge Pereira <jpereira@freeradius.org>
22
 *
23
 * @copyright 2021 The FreeRADIUS server project.
24
 * @copyright 2021 Network RADIUS SAS (legal@networkradius.com)
25
 */
26
RCSID("$Id: 693a9fdc174b57014a92543c92a75603771d3fe3 $")
27
28
#include <freeradius-devel/util/udp.h>
29
30
#include <freeradius-devel/io/test_point.h>
31
32
#include "tftp.h"
33
#include "attrs.h"
34
35
/*
36
 *  https://tools.ietf.org/html/rfc1350
37
 *
38
 *  Order of Headers
39
 *
40
 *                                                 2 bytes
41
 *   ----------------------------------------------------------
42
 *  |  Local Medium  |  Internet  |  Datagram  |  TFTP Opcode  |
43
 *   ----------------------------------------------------------
44
 *
45
 *  TFTP Formats
46
 *
47
 *  Type   Op #     Format without header
48
 *
49
 *         2 bytes    string   1 byte     string   1 byte
50
 *         -----------------------------------------------
51
 *  RRQ/  | 01/02 |  Filename  |   0  |    Mode    |   0  |
52
 *  WRQ    -----------------------------------------------
53
 *          2 bytes    2 bytes       n bytes
54
 *         ---------------------------------
55
 *  DATA  | 03    |   Block #  |    Data    |
56
 *         ---------------------------------
57
 *          2 bytes    2 bytes
58
 *         -------------------
59
 *  ACK   | 04    |   Block #  |
60
 *         --------------------
61
 *         2 bytes  2 bytes        string    1 byte
62
 *         ----------------------------------------
63
 *  ERROR | 05    |  ErrorCode |   ErrMsg   |   0  |
64
 *         ----------------------------------------
65
 *
66
 *  Initial Connection Protocol for reading a file
67
 *
68
 *  1. Host  A  sends  a  "RRQ"  to  host  B  with  source= A's TID,
69
 *     destination= 69.
70
 *
71
 *  2. Host B sends a "DATA" (with block number= 1) to host  A  with
72
 *     source= B's TID, destination= A's TID.
73
 */
74
int fr_tftp_decode(TALLOC_CTX *ctx, fr_pair_list_t *out, uint8_t const *data, size_t data_len)
75
334
{
76
334
  uint8_t const   *q, *p, *end;
77
334
  uint16_t  opcode;
78
334
  fr_pair_t *vp = NULL;
79
80
334
  if (data_len == 0) return -1;
81
82
334
  if (data_len < FR_TFTP_HDR_LEN) {
83
4
    fr_strerror_printf("TFTP packet is too small. (%zu < %d)", data_len, FR_TFTP_HDR_LEN);
84
118
  error:
85
118
    return -1;
86
4
  }
87
88
330
  p = data;
89
330
  end = (data + data_len);
90
91
  /* Opcode */
92
330
  opcode = fr_nbo_to_uint16(p);
93
330
  vp = fr_pair_afrom_da(ctx, attr_tftp_opcode);
94
330
  if (!vp) goto error;
95
96
330
  vp->vp_uint16 = opcode;
97
330
  fr_pair_append(out, vp);
98
330
  p += 2;
99
100
330
  switch (opcode) {
101
38
  case FR_OPCODE_VALUE_READ_REQUEST:
102
253
  case FR_OPCODE_VALUE_WRITE_REQUEST:
103
    /*
104
     *   2 bytes     string    1 byte     string   1 byte   string    1 byte   string   1 byte
105
     *  +------------------------------------------------------------------------------------+
106
     *  | Opcode |  Filename  |   0  |    Mode    |   0  |  blksize  |  0  |  #blksize |  0  |
107
     *  +------------------------------------------------------------------------------------+
108
     *  Figure 5-1: RRQ/WRQ packet
109
     */
110
111
    /* first of all, here we should have always a '\0' */
112
253
    if (*(end - 1) != '\0') goto error_malformed;
113
114
    /* first character should be alpha numeric */
115
245
    if (!isalnum(p[0])) {
116
1
      fr_strerror_printf("Invalid Filename");
117
1
      goto error;
118
1
    }
119
120
    /* <filename> */
121
244
    q = memchr(p, '\0', (end - p));
122
244
    if (!(q && q[0] == '\0')) {
123
77
    error_malformed:
124
77
      fr_strerror_printf("Packet contains malformed attribute");
125
77
      return -1;
126
0
    }
127
128
244
    vp = fr_pair_afrom_da(ctx, attr_tftp_filename);
129
244
    if (!vp) goto error;
130
131
244
    fr_pair_value_bstrndup(vp, (char const *)p, (q - p), true);
132
244
    fr_pair_append(out, vp);
133
244
    p += (q - p) + 1 /* \0 */;
134
135
    /* <mode> */
136
244
    q = memchr(p, '\0', (end - p));
137
244
    if (!(q && q[0] == '\0')) goto error_malformed;
138
139
225
    vp = fr_pair_afrom_da(ctx, attr_tftp_mode);
140
225
    if (!vp) goto error;
141
142
    /* (netascii || ascii || octet) + \0 */
143
225
    if ((q - p) == 5 && !memcmp(p, "octet", 5)) {
144
6
      p += 5;
145
6
      vp->vp_uint8 = FR_MODE_VALUE_OCTET;
146
219
    } else if ((q - p) == 5 && !memcmp(p, "ascii", 5)) {
147
113
      p += 5;
148
113
      vp->vp_uint8 = FR_MODE_VALUE_ASCII;
149
113
    } else if ((q - p) == 8 && !memcmp(p, "netascii", 8)) {
150
1
      p += 8;
151
1
      vp->vp_uint8 = FR_MODE_VALUE_ASCII;
152
105
    } else {
153
105
      fr_strerror_printf("Invalid Mode");
154
105
      goto error;
155
105
    }
156
157
120
    fr_pair_append(out, vp);
158
120
    p += 1 /* \0 */;
159
160
120
    if (p >= end) goto done;
161
162
    /*
163
     *  Once here, the next 'blksize' is optional.
164
     *  At least: | blksize | \0 | #blksize | \0 |
165
     */
166
117
    if ((end - p) < 10) goto error_malformed;
167
168
113
    if (!memcmp(p, "blksize\0", 8)) {
169
89
      char *p_end = NULL;
170
89
      long blksize;
171
172
89
      p += 8;
173
174
89
      if (p >= end || (end - p) < 1 || (end - p) > 6) goto error_malformed;
175
176
46
      vp = fr_pair_afrom_da(ctx, attr_tftp_block_size);
177
46
      if (!vp) goto error;
178
179
46
      blksize = strtol((const char *)p, &p_end, 10);
180
181
46
      if (p == (const uint8_t *)p_end || blksize > FR_TFTP_BLOCK_MAX_SIZE) {
182
3
        fr_strerror_printf("Invalid Block-Size %ld value", blksize);
183
3
        goto error;
184
3
      }
185
186
43
      vp->vp_uint16 = (uint16_t)blksize;
187
43
      fr_pair_append(out, vp);
188
43
    }
189
190
67
    break;
191
192
67
  case FR_OPCODE_VALUE_ACKNOWLEDGEMENT:
193
41
  case FR_OPCODE_VALUE_DATA:
194
    /**
195
     *  2 bytes     2 bytes
196
     *  ---------------------
197
     *  | Opcode |   Block #  |
198
     *  ---------------------
199
     *  Figure 5-3: ACK packet
200
     */
201
202
41
    vp = fr_pair_afrom_da(ctx, attr_tftp_block);
203
41
    if (!vp) goto error;
204
205
41
    vp->vp_uint16 = fr_nbo_to_uint16(p);
206
207
41
    fr_pair_append(out, vp);
208
209
    /*
210
     *  From that point...
211
     *
212
     *  2 bytes     2 bytes      n bytes
213
     *  ----------------------------------
214
     *  | Opcode |   Block #  |   Data     |
215
     *  ----------------------------------
216
     *  Figure 5-2: DATA packet
217
     */
218
41
    if (opcode != FR_OPCODE_VALUE_DATA) goto done;
219
220
40
    if ((p + 2) >= end) goto error_malformed;
221
222
39
    p += 2;
223
224
39
    vp = fr_pair_afrom_da(ctx, attr_tftp_data);
225
39
    if (!vp) goto error;
226
227
39
    fr_pair_value_memdup(vp, p, (end - p), true);
228
39
    fr_pair_append(out, vp);
229
230
39
    break;
231
232
31
  case FR_OPCODE_VALUE_ERROR:
233
    /**
234
     *  2 bytes     2 bytes      string    1 byte
235
     *  -----------------------------------------
236
     *  | Opcode |  ErrorCode |   ErrMsg   |   0  |
237
     *  -----------------------------------------
238
     *
239
     *  Figure 5-4: ERROR packet
240
     */
241
242
31
    if ((p + 2) >= end) goto error_malformed;
243
244
30
    vp = fr_pair_afrom_da(ctx, attr_tftp_error_code);
245
30
    if (!vp) goto error;
246
247
30
    vp->vp_uint16 = fr_nbo_to_uint16(p);
248
249
30
    fr_pair_append(out, vp);
250
251
30
    p  += 2; /* <ErrorCode> */
252
30
    q   = memchr(p, '\0', (end - p));
253
30
    if (!q || q[0] != '\0') goto error_malformed;
254
255
29
    vp = fr_pair_afrom_da(ctx, attr_tftp_error_message);
256
29
    if (!vp) goto error;
257
258
29
    fr_pair_value_bstrndup(vp, (char const *)p, (q - p), true);
259
29
    fr_pair_append(out, vp);
260
261
29
    break;
262
263
5
  default:
264
5
    fr_strerror_printf("Invalid TFTP opcode %#04x", opcode);
265
5
    goto error;
266
330
  }
267
268
139
done:
269
139
  return data_len;
270
330
}
271
272
/**
273
 *  Used as the decoder ctx.
274
 */
275
typedef struct {
276
  int   nothing;
277
} fr_tftp_ctx_t;
278
279
/*
280
 *  Test points for protocol decode
281
 */
282
static ssize_t fr_tftp_decode_proto(TALLOC_CTX *ctx, fr_pair_list_t *out,
283
            uint8_t const *data, size_t data_len, UNUSED void *proto_ctx)
284
334
{
285
334
  return fr_tftp_decode(ctx, out, data, data_len);
286
334
}
287
288
static int _decode_test_ctx(UNUSED fr_tftp_ctx_t *proto_ctx)
289
334
{
290
334
  fr_tftp_global_free();
291
292
334
  return 0;
293
334
}
294
295
static int decode_test_ctx(void **out, TALLOC_CTX *ctx, UNUSED fr_dict_t const *dict,
296
         UNUSED fr_dict_attr_t const *root_da)
297
5.75k
{
298
5.75k
  fr_tftp_ctx_t *test_ctx;
299
300
5.75k
  if (fr_tftp_global_init() < 0) return -1;
301
302
5.75k
  test_ctx = talloc_zero(ctx, fr_tftp_ctx_t);
303
5.75k
  if (!test_ctx) return -1;
304
305
5.75k
  talloc_set_destructor(test_ctx, _decode_test_ctx);
306
307
5.75k
  *out = test_ctx;
308
309
5.75k
  return 0;
310
5.75k
}
311
312
extern fr_test_point_proto_decode_t tftp_tp_decode_proto;
313
fr_test_point_proto_decode_t tftp_tp_decode_proto = {
314
  .test_ctx = decode_test_ctx,
315
  .func   = fr_tftp_decode_proto
316
};