summaryrefslogtreecommitdiffstats
path: root/mfbt/Tainting.h
blob: b6cd9cd8524644c659ef8532ccf3bf363a639b05 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * Creates a Tainted<> wrapper to enforce data validation before use.
 */

#ifndef mozilla_Tainting_h
#define mozilla_Tainting_h

#include <utility>
#include "mozilla/MacroArgs.h"

namespace mozilla {

template <typename T>
class Tainted;

namespace ipc {
template <typename>
struct IPDLParamTraits;
}

/*
 * The Tainted<> class allows data to be wrapped and considered 'tainted'; which
 * requires explicit validation of the data before it can be used for
 * comparisons or in arithmetic.
 *
 * Tainted<> objects are intended to be passed down callstacks (still in
 * Tainted<> form) to whatever location is appropriate to validate (or complete
 * validation) of the data before finally unwrapping it.
 *
 * Tainting data ensures that validation actually occurs and is not forgotten,
 * increase consideration of validation so it can be as strict as possible, and
 * makes it clear from a code point of view where and what validation is
 * performed.
 */

// ====================================================================
// ====================================================================
/*
 * Simple Tainted<foo> class
 *
 * Class should not support any de-reference or comparison operator and instead
 * force all access to the member variable through the MOZ_VALIDATE macros.
 *
 * While the Coerce() function is publicly accessible on the class, it should
 * only be used by the MOZ_VALIDATE macros, and static analysis will prevent
 * it being used elsewhere.
 */

template <typename T>
class Tainted {
 private:
  T mValue;

 public:
  explicit Tainted() = default;

  template <typename U>
  explicit Tainted(U&& aValue) : mValue(std::forward<U>(aValue)) {}

  T& Coerce() { return this->mValue; }
  const T& Coerce() const { return this->mValue; }

  friend struct mozilla::ipc::IPDLParamTraits<Tainted<T>>;
};

// ====================================================================
// ====================================================================
/*
 * This section contains non-user-facing C++ gobblygook to support
 * variable-argument macros.
 */
#define MOZ_TAINT_GLUE(a, b) a b

// We use the same variable name in the nested scope, shadowing the outer
// scope - this allows the user to write the same variable name in the
// macro's condition without using a magic name like 'value'.
//
// We explicitly do not mark it MOZ_MAYBE_UNUSED because the condition
// should always make use of tainted_value, not doing so should cause an
// unused variable warning. That would only happen when we are bypssing
// validation.
//
// The separate bool variable is required to allow condition to be a lambda
// expression; lambdas cannot be placed directly inside ASSERTs.
#define MOZ_VALIDATE_AND_GET_HELPER3(tainted_value, condition, \
                                     assertionstring)          \
  [&]() {                                                      \
    auto& tmp = tainted_value.Coerce();                        \
    auto& tainted_value = tmp;                                 \
    bool test = (condition);                                   \
    MOZ_RELEASE_ASSERT(test, assertionstring);                 \
    return tmp;                                                \
  }()

#define MOZ_VALIDATE_AND_GET_HELPER2(tainted_value, condition)        \
  MOZ_VALIDATE_AND_GET_HELPER3(tainted_value, condition,              \
                               "MOZ_VALIDATE_AND_GET(" #tainted_value \
                               ", " #condition ") has failed")

// ====================================================================
// ====================================================================
/*
 * Macros to validate and un-taint a value.
 *
 * All macros accept the tainted variable as the first argument, and a
 * condition as the second argument. If the condition is satisfied,
 * then the value is considered valid.
 *
 * This file contains documentation and examples for the functions;
 * more usage examples are present in mfbt/tests/gtest/TestTainting.cpp
 */

/*
 * MOZ_VALIDATE_AND_GET is the bread-and-butter validation function.
 * It confirms the value abides by the condition specified and then
 * returns the untainted value.
 *
 * If the condition is not satisified, we RELEASE_ASSERT.
 *
 * Examples:
 *
 *   int bar;
 *   Tainted<int> foo;
 *   int comparisonVariable = 20;
 *
 *   bar = MOZ_VALIDATE_AND_GET(foo, foo < 20);
 *   bar = MOZ_VALIDATE_AND_GET(foo, foo < comparisonVariable);
 *
 * Note that while the comparison of foo < 20 works inside the macro,
 * doing so outside the macro (such as with `if (foo < 20)` will
 * (intentionally) fail during compilation. We do this to ensure that
 * all validation logic is self-contained inside the macro.
 *
 *
 * The macro also supports supplying a custom string to the
 * MOZ_RELEASE_ASSERT. This is strongly encouraged because it
 * provides the author the opportunity to explain by way of an
 * english comment what is happening.
 *
 * Good things to include in the comment:
 *  - What the validation is doing or what it means
 *  - The impact that could occur if validation was bypassed.
 *    e.g. 'This value is used to allocate memory, so sane values
 *          should be enforced.''
 *  - How validation could change in the future to be more or less
 *    restrictive.
 *
 * Example:
 *
 *   bar = MOZ_VALIDATE_AND_GET(
 *    foo, foo < 20,
 *    "foo must be less than 20 because higher values represent decibel"
 *    "levels greater than a a jet engine inside your ear.");
 *
 *
 * The condition can also be a lambda function if you need to
 * define temporary variables or perform more complex validation.
 *
 * Square brackets represent the capture group - local variables
 * can be specified here to capture them and use them inside the
 * lambda. Prefacing the variable with '&' means the variable is
 * captured by-reference. It is typically better to capture
 * variables by reference rather than making them parameters.
 *
 * When using this technique:
 *  - the tainted value must be present and should be captured
 *    by reference. (You could make it a parameter if you wish, but
 *    it's more typing.)
 *  - the entire lambda function must be enclosed in parens
 *    (if you omit this, you might get errors of the form:
 *     'use of undeclared identifier 'MOZ_VALIDATE_AND_GET_HELPER4')
 *
 * Example:
 *
 *   bar = MOZ_VALIDATE_AND_GET(foo, ([&foo, &comparisonVariable]() {
 *           bool intermediateResult = externalFunction(foo);
 *           if (intermediateResult || comparisonVariable < 4) {
 *             return true;
 *           }
 *           return false;
 *         }()));
 *
 *
 * You can also define a lambda external to the macro if you prefer
 * this over a static function.
 *
 * This is possible, and supported, but requires a different syntax.
 * Instead of specifying the tainted value in the capture group [&foo],
 * it must be provided as an argument of the unwrapped type.
 * (The argument name can be anything you choose of course.)
 *
 * Example:
 *
 *   auto lambda1 = [](int foo) {
 *     bool intermediateResult = externalFunction(foo);
 *     if (intermediateResult) {
 *       return true;
 *     }
 *     return false;
 *   };
 *   bar = MOZ_VALIDATE_AND_GET(foo, lambda1(foo));
 *
 *
 * Arguments:
 *   tainted_value - the name of the Tainted<> variable
 *   condition - a comparison involving the tainted value
 *   assertionstring [optional] - A string to include in the RELEASE_ASSERT
 */
#define MOZ_VALIDATE_AND_GET(...)                                            \
  MOZ_TAINT_GLUE(MOZ_PASTE_PREFIX_AND_ARG_COUNT(MOZ_VALIDATE_AND_GET_HELPER, \
                                                __VA_ARGS__),                \
                 (__VA_ARGS__))

/*
 * MOZ_IS_VALID is the other most common use, it allows one to test
 * validity without asserting, for use in a if/else statement.
 *
 * It supports the same lambda behavior, but does not support a
 * comment explaining the validation.
 *
 * Example:
 *
 *   if (MOZ_IS_VALID(foo, foo < 20)) {
 *      ...
 *   }
 *
 *
 * Arguments:
 *   tainted_value - the name of the Tainted<> variable
 *   condition - a comparison involving the tainted value
 */
#define MOZ_IS_VALID(tainted_value, condition) \
  [&]() {                                      \
    auto& tmp = tainted_value.Coerce();        \
    auto& tainted_value = tmp;                 \
    return (condition);                        \
  }()

/*
 * MOZ_VALIDATE_OR is a shortcut that tests validity and if invalid,
 * return an alternate value.
 *
 * Note that the following will not work:
 *   MOZ_RELEASE_ASSERT(MOZ_VALIDATE_OR(foo, foo < 20, 100) == EXPECTED_VALUE);
 *   MOZ_ASSERT(MOZ_VALIDATE_OR(foo, foo < 20, 100) == EXPECTED_VALUE);
 * This is because internally, many MOZ_VALIDATE macros use lambda
 * expressions (for variable shadowing purposes) and lambas cannot be
 * expressions in (potentially) unevaluated operands.
 *
 * Example:
 *
 *   bar = MOZ_VALIDATE_OR(foo, foo < 20, 100);
 *
 *
 * Arguments:
 *   tainted_value - the name of the Tainted<> variable
 *   condition - a comparison involving the tainted value
 *   alternate_value - the value to use if the condition is false
 */
#define MOZ_VALIDATE_OR(tainted_value, condition, alternate_value) \
  (MOZ_IS_VALID(tainted_value, condition) ? tainted_value.Coerce() \
                                          : alternate_value)

/*
 * MOZ_FIND_AND_VALIDATE is for testing validity of a tainted value by comparing
 * it against a list of known safe values. Returns a pointer to the matched
 * safe value or nullptr if none was found.
 *
 * Note that for the comparison the macro will loop over the list and that the
 * current element being tested against is provided as list_item.
 *
 * Example:
 *
 * Tainted<int> aId;
 * NSTArray<Person> list;
 * const Person* foo = MOZ_FIND_AND_VALIDATE(aId, list_item.id == aId, list);
 *
 * // Typically you would do nothing if invalid data is passed:
 * if (MOZ_UNLIKELY(!foo)) {
 *     return;
 * }
 *
 * // Or alternately you can crash on invalid data
 * MOZ_RELEASE_ASSERT(foo != nullptr, "Invalid person id sent from content
 * process.");
 *
 * Arguments:
 *  tainted_value - the name of the Tainted<> variable
 *  condition - a condition involving the tainted value and list_item
 *  validation_list - a list of known safe values to compare against
 */
#define MOZ_FIND_AND_VALIDATE(tainted_value, condition, validation_list) \
  [&]() {                                                                \
    auto& tmp = tainted_value.Coerce();                                  \
    auto& tainted_value = tmp;                                           \
    const auto macro_find_it =                                           \
        std::find_if(validation_list.cbegin(), validation_list.cend(),   \
                     [&](const auto& list_item) { return condition; });  \
    return macro_find_it != validation_list.cend() ? &*macro_find_it     \
                                                   : nullptr;            \
  }()

/*
 * MOZ_NO_VALIDATE allows unsafe removal of the Taint wrapper.
 * A justification string is required to explain why this is acceptable.
 *
 * Example:
 *
 *  bar = MOZ_NO_VALIDATE(
 *    foo,
 *    "Value is used to match against a dictionary key in the parent."
 *    "If there's no key present, there won't be a match."
 *    "There is no risk of grabbing a cross-origin value from the dictionary,"
 *    "because the IPC actor is instatiated per-content-process and the "
 *    "dictionary is not shared between actors.");
 *
 *
 * Arguments:
 *   tainted_value - the name of the Tainted<> variable
 *   justification - a human-understandable string explaining why it is
 *                   permissible to omit validation
 */
#define MOZ_NO_VALIDATE(tainted_value, justification)      \
  [&tainted_value] {                                       \
    static_assert(sizeof(justification) > 3,               \
                  "Must provide a justification string."); \
    return tainted_value.Coerce();                         \
  }()

/*
 TODO:

  - Figure out if there are helpers that would be useful for Strings and
 Principals
  - Write static analysis to enforce invariants:
    - No use of .Coerce() except in the header file.
    - No constant passed to the condition of MOZ_VALIDATE_AND_GET
 */

}  // namespace mozilla

#endif /* mozilla_Tainting_h */