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