https://github.com/mozilla/gecko-dev
Raw File
Tip revision: 6e13466a0a47fb97c50f8aff7a5b2ee7eca74ac2 authored by Debadree Chatterjee on 20 August 2024, 07:13:16 UTC
Bug 1899500 - Implement explicit resource management in Baseline compiler. r=arai
Tip revision: 6e13466
Tainting.h
/* -*- 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 */
back to top