| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ |
| #define SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "sandbox/win/src/policy_engine_params.h" |
| |
| // The low-level policy is implemented using the concept of policy 'opcodes'. |
| // An opcode is a structure that contains enough information to perform one |
| // comparison against one single input parameter. For example, an opcode can |
| // encode just one of the following comparison: |
| // |
| // - Is input parameter 3 not equal to nullptr? |
| // - Does input parameter 2 start with L"c:\\"? |
| // - Is input parameter 5, bit 3 is equal 1? |
| // |
| // Each opcode is in fact equivalent to a function invocation where all |
| // the parameters are known by the opcode except one. So say you have a |
| // function of this form: |
| // bool fn(a, b, c, d) with 4 arguments |
| // |
| // Then an opcode is: |
| // op(fn, b, c, d) |
| // Which stores the function to call and its 3 last arguments |
| // |
| // Then and opcode evaluation is: |
| // op.eval(a) ------------------------> fn(a,b,c,d) |
| // internally calls |
| // |
| // The idea is that complex policy rules can be split into streams of |
| // opcodes which are evaluated in sequence. The evaluation is done in |
| // groups of opcodes that have N comparison opcodes plus 1 action opcode: |
| // |
| // [comparison 1][comparison 2]...[comparison N][action][comparison 1]... |
| // ----- evaluation order-----------> |
| // |
| // Each opcode group encodes one high-level policy rule. The rule applies |
| // only if all the conditions on the group evaluate to true. The action |
| // opcode contains the policy outcome for that particular rule. |
| // |
| // Note that this header contains the main building blocks of low-level policy |
| // but not the low level policy class. |
| namespace sandbox { |
| |
| // These are the possible policy outcomes. Note that some of them might |
| // not apply and can be removed. Also note that The following values only |
| // specify what to do, not how to do it and it is acceptable given specific |
| // cases to ignore the policy outcome. |
| enum EvalResult { |
| // Comparison opcode values: |
| EVAL_TRUE, // Opcode condition evaluated true. |
| EVAL_FALSE, // Opcode condition evaluated false. |
| EVAL_ERROR, // Opcode condition generated an error while evaluating. |
| // Action opcode values: |
| ASK_BROKER, // The target must generate an IPC to the broker. On the broker |
| // side, this means grant access to the resource. |
| DENY_ACCESS, // No access granted to the resource. |
| GIVE_READONLY, // Give readonly access to the resource. |
| GIVE_ALLACCESS, // Give full access to the resource. |
| GIVE_CACHED, // IPC is not required. Target can return a cached handle. |
| GIVE_FIRST, // TODO(cpu) |
| SIGNAL_ALARM, // Unusual activity. Generate an alarm. |
| FAKE_SUCCESS, // Do not call original function. Just return 'success'. |
| FAKE_ACCESS_DENIED, // Do not call original function. Just return 'denied' |
| // and do not do IPC. |
| TERMINATE_PROCESS, // Destroy target process. Do IPC as well. |
| }; |
| |
| // The following are the implemented opcodes. |
| enum OpcodeID { |
| OP_ALWAYS_FALSE, // Evaluates to false (EVAL_FALSE). |
| OP_ALWAYS_TRUE, // Evaluates to true (EVAL_TRUE). |
| OP_NUMBER_MATCH, // Match a 32-bit integer as n == a. |
| OP_NUMBER_MATCH_RANGE, // Match a 32-bit integer as a <= n <= b. |
| OP_NUMBER_AND_MATCH, // Match using bitwise AND; as in: n & a != 0. |
| OP_WSTRING_MATCH, // Match a string for equality. |
| OP_ACTION // Evaluates to an action opcode. |
| }; |
| |
| // Options that apply to every opcode. They are specified when creating |
| // each opcode using OpcodeFactory::MakeOpXXXXX() family of functions |
| // Do nothing special. |
| const uint32_t kPolNone = 0; |
| |
| // Convert EVAL_TRUE into EVAL_FALSE and vice-versa. This allows to express |
| // negated conditions such as if ( a && !b). |
| const uint32_t kPolNegateEval = 1; |
| |
| // Zero the MatchContext context structure. This happens after the opcode |
| // is evaluated. |
| const uint32_t kPolClearContext = 2; |
| |
| // Use OR when evaluating this set of opcodes. The policy evaluator by default |
| // uses AND when evaluating. Very helpful when |
| // used with kPolNegateEval. For example if you have a condition best expressed |
| // as if(! (a && b && c)), the use of this flags allows it to be expressed as |
| // if ((!a) || (!b) || (!c)). |
| const uint32_t kPolUseOREval = 4; |
| |
| // Keeps the evaluation state between opcode evaluations. This is used |
| // for string matching where the next opcode needs to continue matching |
| // from the last character position from the current opcode. The match |
| // context is preserved across opcode evaluation unless an opcode specifies |
| // as an option kPolClearContext. |
| struct MatchContext { |
| size_t position; |
| uint32_t options; |
| |
| MatchContext() { Clear(); } |
| |
| void Clear() { |
| position = 0; |
| options = 0; |
| } |
| }; |
| |
| // Models a policy opcode; that is a condition evaluation were all the |
| // arguments but one are stored in objects of this class. Use OpcodeFactory |
| // to create objects of this type. |
| // This class is just an implementation artifact and not exposed to the |
| // API clients or visible in the intercepted service. Internally, an |
| // opcode is just: |
| // - An integer that identifies the actual opcode. |
| // - An index to indicate which one is the input argument |
| // - An array of arguments. |
| // While an OO hierarchy of objects would have been a natural choice, the fact |
| // that 1) this code can execute before the CRT is loaded, presents serious |
| // problems in terms of guarantees about the actual state of the vtables and |
| // 2) because the opcode objects are generated in the broker process, we need to |
| // use plain objects. To preserve some minimal type safety templates are used |
| // when possible. |
| class PolicyOpcode { |
| friend class OpcodeFactory; |
| |
| public: |
| // Evaluates the opcode. For a typical comparison opcode the return value |
| // is EVAL_TRUE or EVAL_FALSE. If there was an error in the evaluation the |
| // the return is EVAL_ERROR. If the opcode is an action opcode then the |
| // return can take other values such as ASK_BROKER. |
| // parameters: An array of all input parameters. This argument is normally |
| // created by the macros POLPARAMS_BEGIN() POLPARAMS_END. |
| // count: The number of parameters passed as first argument. |
| // match: The match context that is persisted across the opcode evaluation |
| // sequence. |
| EvalResult Evaluate(const ParameterSet* parameters, |
| size_t count, |
| MatchContext* match); |
| |
| // Retrieves a stored argument by index. Valid index values are |
| // from 0 to < kArgumentCount. |
| template <typename T> |
| void GetArgument(size_t index, T* argument) const { |
| static_assert(sizeof(T) <= sizeof(arguments_[0]), "invalid size"); |
| *argument = *reinterpret_cast<const T*>(&arguments_[index].mem); |
| } |
| |
| // Sets a stored argument by index. Valid index values are |
| // from 0 to < kArgumentCount. |
| template <typename T> |
| void SetArgument(size_t index, const T& argument) { |
| static_assert(sizeof(T) <= sizeof(arguments_[0]), "invalid size"); |
| *reinterpret_cast<T*>(&arguments_[index].mem) = argument; |
| } |
| |
| // Retrieves the actual address of a string argument. When using |
| // GetArgument() to retrieve an index that contains a string, the returned |
| // value is just an offset to the actual string. |
| // index: the stored string index. Valid values are from 0 |
| // to < kArgumentCount. |
| const wchar_t* GetRelativeString(size_t index) const { |
| ptrdiff_t str_delta = 0; |
| GetArgument(index, &str_delta); |
| const char* delta = reinterpret_cast<const char*>(this) + str_delta; |
| return reinterpret_cast<const wchar_t*>(delta); |
| } |
| |
| // Returns true if this opcode is an action opcode without actually |
| // evaluating it. Used to do a quick scan forward to the next opcode group. |
| bool IsAction() const { return (OP_ACTION == opcode_id_); } |
| |
| // Returns the opcode type. |
| OpcodeID GetID() const { return opcode_id_; } |
| |
| // Returns the stored options such as kPolNegateEval and others. |
| uint32_t GetOptions() const { return options_; } |
| |
| // Sets the stored options such as kPolNegateEval. |
| void SetOptions(uint32_t options) { |
| options_ = base::checked_cast<uint16_t>(options); |
| } |
| |
| private: |
| static const size_t kArgumentCount = 4; // The number of supported argument. |
| |
| struct OpcodeArgument { |
| UINT_PTR mem; |
| }; |
| |
| // Better define placement new in the class instead of relying on the |
| // global definition which seems to be fubared. |
| void* operator new(size_t, void* location) { return location; } |
| |
| // Helper function to evaluate the opcode. The parameters have the same |
| // meaning that in Evaluate(). |
| EvalResult EvaluateHelper(const ParameterSet* parameters, |
| MatchContext* match); |
| OpcodeID opcode_id_; |
| int16_t parameter_; |
| // TODO(cpu): Making |options_| a uint32_t would avoid casting, but causes |
| // test failures. Somewhere code is relying on the size of this struct. |
| // http://crbug.com/420296 |
| uint16_t options_; |
| OpcodeArgument arguments_[PolicyOpcode::kArgumentCount]; |
| }; |
| |
| enum StringMatchOptions { |
| CASE_SENSITIVE = 0, // Pay or Not attention to the case as defined by |
| CASE_INSENSITIVE = 1, // RtlCompareUnicodeString windows API. |
| EXACT_LENGTH = 2 // Don't do substring match. Do full string match. |
| }; |
| |
| // Opcodes that do string comparisons take a parameter that is the starting |
| // position to perform the comparison so we can do substring matching. There |
| // are two special values: |
| // |
| // Start from the current position and compare strings advancing forward until |
| // a match is found if any. Similar to CRT strstr(). |
| const int kSeekForward = -1; |
| // Perform a match with the end of the string. It only does a single comparison. |
| const int kSeekToEnd = 0xfffff; |
| |
| // A PolicyBuffer is a variable size structure that contains all the opcodes |
| // that are to be created or evaluated in sequence. |
| struct PolicyBuffer { |
| size_t opcode_count; |
| PolicyOpcode opcodes[1]; |
| }; |
| |
| // Helper class to create any opcode sequence. This class is normally invoked |
| // only by the high level policy module or when you need to handcraft a special |
| // policy. |
| // The factory works by creating the opcodes using a chunk of memory given |
| // in the constructor. The opcodes themselves are allocated from the beginning |
| // (top) of the memory, while any string that an opcode needs is allocated from |
| // the end (bottom) of the memory. |
| // |
| // In essence: |
| // |
| // low address ---> [opcode 1] |
| // [opcode 2] |
| // [opcode 3] |
| // | | <--- memory_top_ |
| // | free | |
| // | | |
| // | | <--- memory_bottom_ |
| // [string 1] |
| // high address --> [string 2] |
| // |
| // Note that this class does not keep track of the number of opcodes made and |
| // it is designed to be a building block for low-level policy. |
| // |
| // Note that any of the MakeOpXXXXX member functions below can return nullptr on |
| // failure. When that happens opcode sequence creation must be aborted. |
| class OpcodeFactory { |
| public: |
| // memory: base pointer to a chunk of memory where the opcodes are created. |
| // memory_size: the size in bytes of the memory chunk. |
| OpcodeFactory(char* memory, size_t memory_size) : memory_top_(memory) { |
| memory_bottom_ = &memory_top_[memory_size]; |
| } |
| |
| // policy: contains the raw memory where the opcodes are created. |
| // memory_size: contains the actual size of the policy argument. |
| OpcodeFactory(PolicyBuffer* policy, size_t memory_size) { |
| memory_top_ = reinterpret_cast<char*>(&policy->opcodes[0]); |
| memory_bottom_ = &memory_top_[memory_size]; |
| } |
| |
| // Returns the available memory to make opcodes. |
| size_t memory_size() const { |
| DCHECK_GE(memory_bottom_, memory_top_); |
| return memory_bottom_ - memory_top_; |
| } |
| |
| // Creates an OpAlwaysFalse opcode. |
| PolicyOpcode* MakeOpAlwaysFalse(uint32_t options); |
| |
| // Creates an OpAlwaysFalse opcode. |
| PolicyOpcode* MakeOpAlwaysTrue(uint32_t options); |
| |
| // Creates an OpAction opcode. |
| // action: The action to return when Evaluate() is called. |
| PolicyOpcode* MakeOpAction(EvalResult action, uint32_t options); |
| |
| // Creates an OpNumberMatch opcode. |
| // selected_param: index of the input argument. It must be a uint32_t or the |
| // evaluation result will generate a EVAL_ERROR. |
| // match: the number to compare against the selected_param. |
| PolicyOpcode* MakeOpNumberMatch(int16_t selected_param, |
| uint32_t match, |
| uint32_t options); |
| |
| // Creates an OpNumberMatch opcode (void pointers are cast to numbers). |
| // selected_param: index of the input argument. It must be an void* or the |
| // evaluation result will generate a EVAL_ERROR. |
| // match: the pointer numeric value to compare against selected_param. |
| PolicyOpcode* MakeOpVoidPtrMatch(int16_t selected_param, |
| const void* match, |
| uint32_t options); |
| |
| // Creates an OpNumberMatchRange opcode using the memory passed in the ctor. |
| // selected_param: index of the input argument. It must be a uint32_t or the |
| // evaluation result will generate a EVAL_ERROR. |
| // lower_bound, upper_bound: the range to compare against selected_param. |
| PolicyOpcode* MakeOpNumberMatchRange(int16_t selected_param, |
| uint32_t lower_bound, |
| uint32_t upper_bound, |
| uint32_t options); |
| |
| // Creates an OpWStringMatch opcode using the raw memory passed in the ctor. |
| // selected_param: index of the input argument. It must be a wide string |
| // pointer or the evaluation result will generate a EVAL_ERROR. |
| // match_str: string to compare against selected_param. |
| // start_position: when its value is from 0 to < 0x7fff it indicates an |
| // offset from the selected_param string where to perform the comparison. If |
| // the value is SeekForward then a substring search is performed. If the |
| // value is SeekToEnd the comparison is performed against the last part of |
| // the selected_param string. |
| // Note that the range in the position (0 to 0x7fff) is dictated by the |
| // current implementation. |
| // match_opts: Indicates additional matching flags. Currently CaseInsensitive |
| // is supported. |
| PolicyOpcode* MakeOpWStringMatch(int16_t selected_param, |
| const wchar_t* match_str, |
| int start_position, |
| StringMatchOptions match_opts, |
| uint32_t options); |
| |
| // Creates an OpNumberAndMatch opcode using the raw memory passed in the ctor. |
| // selected_param: index of the input argument. It must be uint32_t or the |
| // evaluation result will generate a EVAL_ERROR. |
| // match: the value to bitwise AND against selected_param. |
| PolicyOpcode* MakeOpNumberAndMatch(int16_t selected_param, |
| uint32_t match, |
| uint32_t options); |
| |
| private: |
| // Constructs the common part of every opcode. selected_param is the index |
| // of the input param to use when evaluating the opcode. Pass -1 in |
| // selected_param to indicate that no input parameter is required. |
| PolicyOpcode* MakeBase(OpcodeID opcode_id, |
| uint32_t options, |
| int16_t selected_param); |
| |
| // Allocates (and copies) a string (of size length) inside the buffer and |
| // returns the displacement with respect to start. |
| ptrdiff_t AllocRelative(void* start, const wchar_t* str, size_t length); |
| |
| // Points to the lowest currently available address of the memory |
| // used to make the opcodes. This pointer increments as opcodes are made. |
| char* memory_top_; |
| |
| // Points to the highest currently available address of the memory |
| // used to make the opcodes. This pointer decrements as opcode strings are |
| // allocated. |
| char* memory_bottom_; |
| |
| DISALLOW_COPY_AND_ASSIGN(OpcodeFactory); |
| }; |
| |
| } // namespace sandbox |
| |
| #endif // SANDBOX_WIN_SRC_POLICY_ENGINE_OPCODES_H_ |