1
#include "source/common/tracing/tracing_validation.h"
2

            
3
#include <algorithm>
4
#include <cstddef>
5
#include <vector>
6

            
7
#include "absl/container/flat_hash_set.h"
8
#include "absl/strings/ascii.h"
9
#include "absl/strings/match.h"
10
#include "absl/strings/str_split.h"
11
#include "absl/strings/string_view.h"
12

            
13
namespace Envoy {
14
namespace Tracing {
15

            
16
namespace {
17

            
18
// W3C Trace Context constants
19
constexpr size_t kTraceParentExpectedSize = 55;
20
constexpr size_t kVersionHexSize = 2;
21
constexpr size_t kTraceIdHexSize = 32;
22
constexpr size_t kParentIdHexSize = 16;
23
constexpr size_t kTraceFlagsHexSize = 2;
24

            
25
// W3C Baggage constants
26
constexpr size_t kMaxBaggageSize = 8192;
27
constexpr size_t kMaxBaggageMembers = 64;
28

            
29
56
bool isValidLowercaseHex(absl::string_view input) {
30
713
  return std::all_of(input.begin(), input.end(), [](unsigned char c) {
31
713
    return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
32
713
  });
33
56
}
34

            
35
19
bool isAllZeros(absl::string_view input) {
36
81
  return std::all_of(input.begin(), input.end(), [](char c) { return c == '0'; });
37
19
}
38

            
39
// Tracestate validation helpers
40
532
bool isValidTraceStateKeyChar(char c) {
41
532
  return absl::ascii_islower(c) || absl::ascii_isdigit(c) || c == '_' || c == '-' || c == '*' ||
42
532
         c == '/';
43
532
}
44

            
45
69
bool isValidTraceStateKey(absl::string_view key) {
46
69
  if (key.empty() || key.size() > 256) {
47
1
    return false;
48
1
  }
49

            
50
68
  auto at_pos = key.find('@');
51
68
  if (at_pos == absl::string_view::npos) {
52
    // simple-key = lcalpha 0*255( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
53
52
    if (!absl::ascii_islower(key[0])) {
54
      // first char must be lowercase letter
55
2
      return false;
56
2
    }
57
50
    return std::all_of(key.begin(), key.end(), isValidTraceStateKeyChar);
58
52
  } else {
59
    // multi-tenant-key = tenant-id "@" system-id
60

            
61
    // tenant-id = ( lcalpha / DIGIT ) 0*240( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
62
16
    absl::string_view tenant_id = key.substr(0, at_pos);
63
16
    if (tenant_id.empty() || tenant_id.size() > 241) {
64
2
      return false;
65
2
    }
66
14
    if (!absl::ascii_islower(tenant_id[0]) && !absl::ascii_isdigit(tenant_id[0])) {
67
      // first char of tenant-id must be lowercase letter or digit
68
2
      return false;
69
2
    }
70
12
    if (!std::all_of(tenant_id.begin(), tenant_id.end(), isValidTraceStateKeyChar)) {
71
      return false;
72
    }
73

            
74
    // system-id = lcalpha 0*13( lcalpha / DIGIT / "_" / "-"/ "*" / "/" )
75
12
    absl::string_view system_id = key.substr(at_pos + 1);
76
12
    if (system_id.empty() || system_id.size() > 14) {
77
2
      return false;
78
2
    }
79
10
    if (!absl::ascii_islower(system_id[0])) {
80
      // first char of system-id must be lowercase letter
81
3
      return false;
82
3
    }
83
7
    if (!std::all_of(system_id.begin(), system_id.end(), isValidTraceStateKeyChar)) {
84
      return false;
85
    }
86
7
    return true;
87
7
  }
88
68
}
89

            
90
// https://www.w3.org/TR/trace-context/#value
91
// value    = 0*255(chr) nblk-chr
92
// nblk-chr = %x21-2B / %x2D-3C / %x3E-7E
93
// chr      = %x20 / nblk-chr
94
224
inline bool isTraceStateValueNblkChr(char c) {
95
224
  return (c >= 0x21 && c <= 0x2b) || (c >= 0x2d && c <= 0x3c) || (c >= 0x3e && c <= 0x7e);
96
224
}
97
172
inline bool isTraceStateValueChr(char c) { return c == 0x20 || isTraceStateValueNblkChr(c); }
98

            
99
57
bool isValidTraceStateValue(absl::string_view value) {
100
57
  if (value.size() > 256) {
101
1
    return false;
102
1
  }
103
56
  if (value.empty()) {
104
3
    return true;
105
3
  }
106
  // last char must be nblk-chr
107
53
  unsigned char last = value.back();
108
53
  if (!isTraceStateValueNblkChr(last)) {
109
    return false;
110
  }
111
  // interior chars may include space (0x20)
112
53
  return std::all_of(value.begin(), value.end(),
113
172
                     [](unsigned char c) { return isTraceStateValueChr(c); });
114
53
}
115

            
116
// Baggage validation helpers
117
339
bool isTokenChar(char c) {
118
339
  if (c <= 0x20 || c > 0x7e) {
119
1
    return false;
120
1
  }
121
338
  static constexpr absl::string_view kDelimiters = "\"(),/:;<=>?@[\\]{}";
122
338
  return !absl::StrContains(kDelimiters, c);
123
339
}
124

            
125
168
bool isBaggageOctet(char c) {
126
168
  return c == 0x21 || (c >= 0x23 && c <= 0x2b) || (c >= 0x2d && c <= 0x3a) ||
127
168
         (c >= 0x3c && c <= 0x5b) || (c >= 0x5d && c <= 0x7e);
128
168
}
129

            
130
102
bool isValidBaggageKey(absl::string_view key) {
131
102
  absl::string_view trimmed = absl::StripAsciiWhitespace(key);
132
102
  if (trimmed.empty()) {
133
2
    return false;
134
2
  }
135
100
  return std::all_of(trimmed.begin(), trimmed.end(), isTokenChar);
136
102
}
137

            
138
93
bool isValidBaggageValue(absl::string_view value) {
139
93
  absl::string_view trimmed = absl::StripAsciiWhitespace(value);
140
93
  return std::all_of(trimmed.begin(), trimmed.end(), isBaggageOctet);
141
93
}
142

            
143
} // namespace
144

            
145
28
bool isValidTraceParent(absl::string_view trace_parent) {
146
28
  if (trace_parent.size() < kTraceParentExpectedSize) {
147
7
    return false;
148
7
  }
149

            
150
21
  std::vector<absl::string_view> components = absl::StrSplit(trace_parent, '-');
151
21
  if (components.size() < 4) {
152
1
    return false;
153
1
  }
154

            
155
20
  absl::string_view version = components[0];
156
20
  absl::string_view trace_id = components[1];
157
20
  absl::string_view parent_id = components[2];
158
20
  absl::string_view flags = components[3];
159

            
160
20
  if (version.size() != kVersionHexSize || trace_id.size() != kTraceIdHexSize ||
161
20
      parent_id.size() != kParentIdHexSize || flags.size() != kTraceFlagsHexSize) {
162
4
    return false;
163
4
  }
164

            
165
16
  if (!isValidLowercaseHex(version) || !isValidLowercaseHex(trace_id) ||
166
16
      !isValidLowercaseHex(parent_id) || !isValidLowercaseHex(flags)) {
167
5
    return false;
168
5
  }
169

            
170
11
  if (version == "ff") {
171
1
    return false;
172
1
  }
173

            
174
10
  if (isAllZeros(trace_id) || isAllZeros(parent_id)) {
175
2
    return false;
176
2
  }
177

            
178
8
  return true;
179
10
}
180

            
181
37
bool isValidTraceState(absl::string_view trace_state) {
182
37
  if (trace_state.empty()) {
183
1
    return true;
184
1
  }
185

            
186
36
  std::vector<absl::string_view> members = absl::StrSplit(trace_state, ',');
187
36
  if (members.size() > 32) {
188
1
    return false;
189
1
  }
190

            
191
35
  absl::flat_hash_set<absl::string_view> keys;
192
72
  for (absl::string_view member : members) {
193
72
    absl::string_view trimmed_member = absl::StripAsciiWhitespace(member);
194
72
    if (trimmed_member.empty()) {
195
1
      continue;
196
1
    }
197
71
    std::vector<absl::string_view> kv = absl::StrSplit(trimmed_member, absl::MaxSplits('=', 1));
198
71
    if (kv.size() != 2) {
199
2
      return false;
200
2
    }
201
69
    absl::string_view key = kv[0];
202
69
    if (!isValidTraceStateKey(key) || !isValidTraceStateValue(kv[1])) {
203
14
      return false;
204
14
    }
205
55
    if (!keys.insert(key).second) {
206
2
      return false; // Duplicate key
207
2
    }
208
55
  }
209

            
210
17
  return true;
211
35
}
212

            
213
32
bool isValidBaggage(absl::string_view baggage) {
214
32
  if (baggage.empty()) {
215
1
    return true;
216
1
  }
217
31
  if (baggage.size() > kMaxBaggageSize) {
218
1
    return false;
219
1
  }
220

            
221
30
  std::vector<absl::string_view> members = absl::StrSplit(baggage, ',');
222
30
  if (members.size() > kMaxBaggageMembers) {
223
1
    return false;
224
1
  }
225

            
226
97
  for (absl::string_view member : members) {
227
97
    absl::string_view trimmed_member = absl::StripAsciiWhitespace(member);
228
97
    if (trimmed_member.empty()) {
229
2
      return false; // Baggage doesn't allow empty members
230
2
    }
231
95
    std::vector<absl::string_view> parts = absl::StrSplit(trimmed_member, absl::MaxSplits(';', 1));
232
95
    std::vector<absl::string_view> kv = absl::StrSplit(parts[0], absl::MaxSplits('=', 1));
233
95
    if (kv.size() != 2) {
234
4
      return false;
235
4
    }
236
91
    if (!isValidBaggageKey(kv[0]) || !isValidBaggageValue(kv[1])) {
237
5
      return false;
238
5
    }
239
    // Optional properties
240
86
    if (parts.size() == 2) {
241
9
      std::vector<absl::string_view> props = absl::StrSplit(parts[1], ';');
242
11
      for (absl::string_view prop : props) {
243
11
        std::vector<absl::string_view> pkv = absl::StrSplit(prop, absl::MaxSplits('=', 1));
244
11
        if (!isValidBaggageKey(pkv[0])) {
245
3
          return false;
246
3
        }
247
8
        if (pkv.size() == 2 && !isValidBaggageValue(pkv[1])) {
248
2
          return false;
249
2
        }
250
8
      }
251
9
    }
252
86
  }
253

            
254
13
  return true;
255
29
}
256

            
257
} // namespace Tracing
258
} // namespace Envoy