summaryrefslogtreecommitdiffstats
path: root/mfbt/Tainting.h
diff options
context:
space:
mode:
Diffstat (limited to 'mfbt/Tainting.h')
-rw-r--r--mfbt/Tainting.h348
1 files changed, 348 insertions, 0 deletions
diff --git a/mfbt/Tainting.h b/mfbt/Tainting.h
new file mode 100644
index 0000000000..2df6176f89
--- /dev/null
+++ b/mfbt/Tainting.h
@@ -0,0 +1,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 obscure, non-user-facing C++ 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 */