// 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: // low-level policy是基于opcodes概念实现的 // opcode是一种包含了充分信息的结构用来与单一输入参数进行比较 // 下面给出了几个比较的范例: // // - 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? // 看起来opcode可以裁决某个输入参数的值是否是理想值 // // 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.
// 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. // 仅仅表示应该怎样处理,但并未规范如何处理。对于特殊情景,可以不采纳输出的结果。 enumEvalResult { // Comparison opcode values: // 参考头注释的说明,group是以多个comparison+一个action组成 // 这三个result是comparison的结果 EVAL_TRUE, // Opcode condition evaluated true. EVAL_FALSE, // Opcode condition evaluated false. EVAL_ERROR, // Opcode condition generated an error while evaluating. // Action opcode values: // 这些都是action的值,表示裁决的行动 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. enumOpcodeID { // 前6个都是具体的比较方式 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. // 后1个应该是标志action所用 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. constuint32_t kPolNone = 0;
// Convert EVAL_TRUE into EVAL_FALSE and vice-versa. This allows to express // negated conditions such as if ( a && !b). constuint32_t kPolNegateEval = 1;
// Zero the MatchContext context structure. This happens after the opcode // is evaluated. constuint32_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)). constuint32_t kPolUseOREval = 4;
一个专门为连续字符串匹配定制的专属结构体:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// 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. structMatchContext { size_t position; uint32_t options;
// 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. // policy opcode是基于此运营的,除了第一个参数都存储在此对象中进行裁决。 // OpcodeFactory工厂类负责创建该类型对象。 // // 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. // 该类仅仅是抽象体,不对外暴露(client/service)。 // 操作码的三个组成 // - opcode的id // - 一个index索引表示哪个是输入参数 // - 一个参数数组 // // 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. classPolicyOpcode { friendclassOpcodeFactory;
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. // 这个函数就是裁决的执行,根据opcode是comparison还是action,返回两类结果 // // 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. // 可以看到参数数组是个ParameterSet结构,这个结构在policy_engine_params.h中定义 // 一会儿展开看看。count表示参数个数,match是一个辅助结构,用于支持连续字符串匹配用的 EvalResult Evaluate(const ParameterSet* parameters, size_t count, MatchContext* match);
// 一目了然的三个set/get方法 // Retrieves a stored argument by index. Valid index values are // from 0 to < kArgumentCount. template <typename T> voidGetArgument(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> voidSetArgument(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 an 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. constwchar_t* GetRelativeString(size_t index)const{ ptrdiff_t str_delta = 0; GetArgument(index, &str_delta); // 字符串的GetArgument返回的是一个offset constchar* delta = reinterpret_cast<constchar*>(this) + str_delta; returnreinterpret_cast<constwchar_t*>(delta); }
// OpcodeID相关 // 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. boolIsAction()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_tGetOptions()const{ return options_; }
// Sets the stored options such as kPolNegateEval. voidSetOptions(uint32_t options){ options_ = base::checked_cast<uint16_t>(options); }
private: staticconstsize_t kArgumentCount = 4; // The number of supported argument.
structOpcodeArgument { UINT_PTR mem; };
// Better define placement new in the class instead of relying on the // global definition which seems to be fubared. void* operatornew(size_t, void* location){ return location; }
// Helper function to evaluate the opcode. The parameters have the same // meaning that in Evaluate(). // 这个应该是真正的裁决处理helper函数 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]; // 其实就是4个指针,供参数get/set用 };
// This function is the one and only entry for evaluating any opcode. It is // in charge of applying any relevant opcode options and calling EvaluateInner // were the actual dispatch-by-id is made. It would seem at first glance that // the dispatch should be done by virtual function (vtable) calls but you have // to remember that the opcodes are made in the broker process and copied as // raw memory to the target process.
// Apply the general options regardless of the particular type of opcode. // 如果本PolicyOpcode的标记为kPolNone,直接返回结果,什么也不做 if (kPolNone == options_) { return result; }
// 如果本PolicyOpcode的标记了kPolNegateEval位,那么就要对结果取反(ERROR不管) if (options_ & kPolNegateEval) { if (EVAL_TRUE == result) { result = EVAL_FALSE; } elseif (EVAL_FALSE == result) { result = EVAL_TRUE; } elseif (EVAL_ERROR != result) { result = EVAL_ERROR; } } if (match) { // 如果标记了kPolClearContext位,那么要对辅助结构MatchContext进行清理工作 if (options_ & kPolClearContext) match->Clear(); // 如果标记了kPolUseOREval,那么就对辅助结构MatchContext打上标记 if (options_ & kPolUseOREval) match->options = kPolUseOREval; //默认是用AND来裁决,该标记表示用OR } return result; }
#define OPCODE_EVAL(op, x, y, z) \ case op: \ return OpcodeEval<op>(x, y, z)
int start_position = 0; int match_len = 0; unsignedint match_opts = 0; // PolicyOpcode中的arguments_[1,2,3]成员分别是比较的长度、起始位置和比较时的选项 opcode->GetArgument(1, &match_len); opcode->GetArgument(2, &start_position); opcode->GetArgument(3, &match_opts);
// PolicyOpcode中的arguments_[0]是字符串的offset,通过GetRelativeString才能转成真实地址 constwchar_t* match_str = opcode->GetRelativeString(0); // Advance the source string to the last successfully evaluated position // according to the match context. // 如果是首次匹配字符串,context->position应该是0,如果是连续匹配source字符串,那就表示下一个串的位置 source_str = &source_str[context->position]; // 每个串都有终结符,计算出本次待比较的长度 int source_len = static_cast<int>(g_nt.wcslen(source_str));
if (0 == source_len) { // If we reached the end of the source string there is nothing we can // match against. return EVAL_FALSE; } if (match_len > source_len) { // There can't be a positive match when the target string is bigger than // the source string return EVAL_FALSE; }
// We have three cases, depending on the value of start_pos: // Case 1. We skip N characters and compare once. // Case 2: We skip to the end and compare once. // Case 3: We match the first substring (if we find any). // 具体的子串匹配,基操勿6 if (start_position >= 0) { if (kSeekToEnd == start_position) { start_position = source_len - match_len; } elseif (match_opts & EXACT_LENGTH) { // A sub-case of case 3 is when the EXACT_LENGTH flag is on // the match needs to be not just substring but full match. if ((match_len + start_position) != source_len) { return EVAL_FALSE; } }
// Advance start_pos characters. Warning! this does not consider // utf16 encodings (surrogate pairs) or other Unicode 'features'. source_str += start_position;
// Since we skipped, lets reevaluate just the lengths again. if ((match_len + start_position) > source_len) { return EVAL_FALSE; }
// 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(). constint kSeekForward = -1; // Perform a match with the end of the string. It only does a single comparison. constint kSeekToEnd = 0xfffff;
// A PolicyBuffer is a variable size structure that contains all the opcodes // that are to be created or evaluated in sequence. structPolicyBuffer { size_t opcode_count; PolicyOpcode opcodes[1]; // 又见flexible };
// 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. classOpcodeFactory { 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维护了一堆组织好的PolicyOpcode,所以需要一个buffer来存储 // 构造器先对buffer的顶和底进行初始化,memory_top_和memory_bottom_ // 根据头注释,这个buffer应该是双向增长的,共用中间的free空间 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. // 传PolicyBuffer进来,与上面不同的地方在于,memory_top_一开始跳过了前4个opcode_count字节 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. // 看看free还有多大 size_tmemory_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, constvoid* 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, constwchar_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_tAllocRelative(void* start, constwchar_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_;
////////////////////////////////////////////////////////////////////////////// // Opcode OpAction: // Does not require input parameter. // Argument 0 contains the actual action to return.
// 剩下的这些也都和OpcodeEval<xxx>一一对上,就不多说了 ////////////////////////////////////////////////////////////////////////////// // Opcode OpNumberMatch: // Requires a uint32_t or void* in selected_param // Argument 0 is the stored number to match. // Argument 1 is the C++ type of the 0th argument.
////////////////////////////////////////////////////////////////////////////// // Opcode OpNumberMatchRange // Requires a uint32_t in selected_param. // Argument 0 is the stored lower bound to match. // Argument 1 is the stored upper bound to match.
PolicyOpcode* OpcodeFactory::MakeOpNumberMatchRange(int16_t selected_param, uint32_t lower_bound, uint32_t upper_bound, uint32_t options){ if (lower_bound > upper_bound) { returnnullptr; } PolicyOpcode* opcode = MakeBase(OP_NUMBER_MATCH_RANGE, options, selected_param); if (!opcode) returnnullptr; opcode->SetArgument(0, lower_bound); opcode->SetArgument(1, upper_bound); return opcode; } ////////////////////////////////////////////////////////////////////////////// // Opcode OpNumberAndMatch: // Requires a uint32_t in selected_param. // Argument 0 is the stored number to match.
PolicyOpcode* OpcodeFactory::MakeOpNumberAndMatch(int16_t selected_param, uint32_t match, uint32_t options){ PolicyOpcode* opcode = MakeBase(OP_NUMBER_AND_MATCH, options, selected_param); if (!opcode) returnnullptr; opcode->SetArgument(0, match); return opcode; } ////////////////////////////////////////////////////////////////////////////// // Opcode OpWStringMatch: // Requires a wchar_t* in selected_param. // Argument 0 is the byte displacement of the stored string. // Argument 1 is the length in chars of the stored string. // Argument 2 is the offset to apply on the input string. It has special values. // as noted in the header file. // Argument 3 is the string matching options.
PolicyOpcode* OpcodeFactory::MakeOpWStringMatch(int16_t selected_param, constwchar_t* match_str, int start_position, StringMatchOptions match_opts, uint32_t options){ if (!match_str) returnnullptr; if ('\0' == match_str[0]) returnnullptr;
// Models the set of interesting parameters of an intercepted system call // normally you don't create objects of this class directly, instead you // use the POLPARAMS_XXX macros. // For example, if an intercepted function has the following signature: // // NTSTATUS NtOpenFileFunction (PHANDLE FileHandle, // ACCESS_MASK DesiredAccess, // POBJECT_ATTRIBUTES ObjectAttributes, // PIO_STATUS_BLOCK IoStatusBlock, // ULONG ShareAccess, // ULONG OpenOptions); // // You could say that the following parameters are of interest to policy: // // POLPARAMS_BEGIN(open_params) // POLPARAM(DESIRED_ACCESS) // POLPARAM(OBJECT_NAME) // POLPARAM(SECURITY_DESCRIPTOR) // POLPARAM(IO_STATUS) // POLPARAM(OPEN_OPTIONS) // POLPARAMS_END; // // and the actual code will use this for defining the parameters: // // CountedParameterSet<open_params> p; // p[open_params::DESIRED_ACCESS] = ParamPickerMake(DesiredAccess); // p[open_params::OBJECT_NAME] = // ParamPickerMake(ObjectAttributes->ObjectName); // p[open_params::SECURITY_DESCRIPTOR] = // ParamPickerMake(ObjectAttributes->SecurityDescriptor); // p[open_params::IO_STATUS] = ParamPickerMake(IoStatusBlock); // p[open_params::OPEN_OPTIONS] = ParamPickerMake(OpenOptions); // // These will create an stack-allocated array of ParameterSet objects which // have each 1) the address of the parameter 2) a numeric id that encodes the // original C++ type. This allows the policy to treat any set of supported // argument types uniformily and with some type safety. // // TODO(cpu): support not fully implemented yet for unicode string and will // probably add other types as well. classParameterSet { public: ParameterSet() : real_type_(INVALID_TYPE), address_(nullptr) {}
// real_type的3种类型对应的重载Get方法,real_type_来判定 // Retrieve the stored parameter. If the type does not match ulong fail. boolGet(uint32_t* destination)const{ if (real_type_ != UINT32_TYPE) { returnfalse; } *destination = Void2TypePointerCopy<uint32_t>();//返回的实际上是address_成员的地址 returntrue; }
// Retrieve the stored parameter. If the type does not match void* fail. boolGet(constvoid** destination)const{ if (real_type_ != VOIDPTR_TYPE) { returnfalse; } *destination = Void2TypePointerCopy<void*>(); returntrue; }
// Retrieve the stored parameter. If the type does not match wchar_t* fail. boolGet(constwchar_t** destination)const{ if (real_type_ != WCHAR_TYPE) { returnfalse; } *destination = Void2TypePointerCopy<constwchar_t*>(); returntrue; }
// False if the parameter is not properly initialized. boolIsValid()const{ return real_type_ != INVALID_TYPE; }
protected: // The constructor can only be called by derived types, which should // safely provide the real_type and the address of the argument. // 所以他才是关键,真正有用的构造函数,以address赋给address_,实际上参数的buffer // 可以看出由外部维护,ParameterSet只是架在上面的操纵者罢了 // protected权限又表示外部使用该对象时,必须得间接用派生对象 ParameterSet(ArgType real_type, constvoid* address) : real_type_(real_type), address_(address) {}
private: // This template provides the same functionality as bits_cast but // it works with pointer while the former works only with references. template <typename T> T Void2TypePointerCopy()const{ return *(reinterpret_cast<const T*>(address_));//类型转换模板 }
// To safely infer the type, we use a set of template specializations // in ParameterSetEx with a template function ParamPickerMake to do the // parameter type deduction.
// Base template class. Not implemented so using unsupported types should // fail to compile. // 使用ParameterSetEx类模板 template <typename T> classParameterSetEx : public ParameterSet { public: ParameterSetEx(constvoid* address); };
// This template defines the actual list of policy parameters for a given // interception. // Warning: This template stores the address to the actual variables, in // other words, the values are not copied. template <typename T> structCountedParameterSet { CountedParameterSet() : count(T::PolParamLast) {}
// This header contains the core policy evaluator. In its simplest form // it evaluates a stream of opcodes assuming that they are laid out in // memory as opcode groups. // // An opcode group has N comparison opcodes plus 1 action opcode. For // example here we have 3 opcode groups (A, B,C): // 一个opcode组由N个comparison opcodes加上一个action opcode组成 // 这里是个3组的示范: // // [comparison 1] <-- group A start // [comparison 2] // [comparison 3] // [action A ] // [comparison 1] <-- group B start // [action B ] // [comparison 1] <-- group C start // [comparison 2] // [action C ] // // The opcode evaluator proceeds from the top, evaluating each opcode in // sequence. An opcode group is evaluated until the first comparison that // returns false. At that point the rest of the group is skipped and evaluation // resumes with the first comparison of the next group. When all the comparisons // in a group have evaluated to true and the action is reached. The group is // considered a matching group. // 裁决从top开始,逐个opcode进行evaluate。 // 一组opcode设定的条件只有全部满足时,才继续下一组的匹配,否则就不必继续审判了 // // In the 'ShortEval' mode evaluation stops when it reaches the end or the first // matching group. The action opcode from this group is the resulting policy // action. // // In the 'RankedEval' mode evaluation stops only when it reaches the end of the // the opcode stream. In the process all matching groups are saved and at the // end the 'best' group is selected (what makes the best is TBD) and the action // from this group is the resulting policy action. // // As explained above, the policy evaluation of a group is a logical AND of // the evaluation of each opcode. However an opcode can request kPolUseOREval // which makes the evaluation to use logical OR. Given that each opcode can // request its evaluation result to be negated with kPolNegateEval you can // achieve the negation of the total group evaluation. This means that if you // need to express: // 对一组opcode的裁决,默认每个comparsion都是AND操作,可以通过kPolUseOREval来置成 // OR操作 // if (!(c1 && c2 && c3)) // You can do it by: // if ((!c1) || (!c2) || (!c3)) //
// Possible outcomes of policy evaluation.
一些常量、枚举:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// Possible outcomes of policy evaluation. enumPolicyResult { NO_POLICY_MATCH, POLICY_MATCH, POLICY_ERROR };
// Policy evaluation flags // TODO(cpu): implement the options kStopOnErrors & kRankedEval. // // Stop evaluating as soon as an error is encountered. constuint32_t kStopOnErrors = 1; // Ignore all non fatal opcode evaluation errors. constuint32_t kIgnoreErrors = 2; // Short-circuit evaluation: Only evaluate until opcode group that // evaluated to true has been found. constuint32_t kShortEval = 4; // Discussed briefly at the policy design meeting. It will evaluate // all rules and then return the 'best' rule that evaluated true. constuint32_t kRankedEval = 8;
// This class evaluates a policy-opcode stream given the memory where the // opcodes are and an input 'parameter set'. // // This class is designed to be callable from interception points // as low as the NtXXXX service level (it is not currently safe, but // it is designed to be made safe). // // Its usage in an interception is: // // POLPARAMS_BEGIN(eval_params) // POLPARAM(param1) // POLPARAM(param2) // POLPARAM(param3) // POLPARAM(param4) // POLPARAM(param5) // POLPARAMS_END; // // PolicyProcessor pol_evaluator(policy_memory); // PolicyResult pr = pol_evaluator.Evaluate(ShortEval, eval_params, // _countof(eval_params)); // if (NO_POLICY_MATCH == pr) { // EvalResult policy_action = pol_evaluator.GetAction(); // // apply policy here... // } // // Where the POLPARAM() arguments are derived from the intercepted function // arguments, and represent all the 'interesting' policy inputs, and // policy_memory is a memory buffer containing the opcode stream that is the // relevant policy for this intercept. classPolicyProcessor { public: // policy_buffer contains opcodes made with OpcodeFactory. They are usually // created in the broker process and evaluated in the target process.
// This constructor is just a variant of the previous constructor. // 该构造器把PolicyBuffer传入,存储PolicyOpcode的容器与操纵者关联 explicitPolicyProcessor(PolicyBuffer* policy) : policy_(policy) { SetInternalState(0, EVAL_FALSE); }
// Evaluates a policy-opcode stream. See the comments at the top of this // class for more info. Returns POLICY_MATCH if a rule set was found that // matches an active policy. // 这个就是某个函数所有参数的Evaluate,传递了ParameterSet和count进去,其内部理应对每个参数进行 // PolicyOpcode::Evaluate PolicyResult Evaluate(uint32_t options, ParameterSet* parameters, size_t parameter_count);
// If the result of Evaluate() was POLICY_MATCH, calling this function returns // the recommended policy action. EvalResult GetAction()const;
// Loop over all the opcodes Evaluating in sequence. Since we only support // short circuit evaluation, we stop as soon as we find an 'action' opcode // and the current evaluation is true. // // Skipping opcodes can happen when we are in AND mode (!kPolUseOREval) and // have got EVAL_FALSE or when we are in OR mode (kPolUseOREval) and got // EVAL_TRUE. Skipping will stop at the next action opcode or at the opcode // after the action depending on kPolUseOREval.
for (size_t ix = 0; ix != count; ++ix) { PolicyOpcode& opcode = policy_->opcodes[ix]; // Skipping block. if (skip_group) { if (SkipOpcode(opcode, &context, &skip_group)) continue; } // Evaluation block. // 这里使用具体的PolicyOpcode::Evaluate来对parameters中某个参数进行匹配 EvalResult result = opcode.Evaluate(parameters, param_count, &context); switch (result) { case EVAL_FALSE: evaluation = false; // 如果某一个参数匹配失败了,那么看看是否设置了OR型匹配,如果没设置,就跳过本组 // 因为本组已经失败了,说明传递进来的参数不匹配本组 if (kPolUseOREval != context.options) skip_group = true; break; case EVAL_ERROR: if (kStopOnErrors & options) return POLICY_ERROR; break; case EVAL_TRUE: evaluation = true; // 如果某个匹配成功了,且设置了OR型匹配,那么本组就不必继续了,因为已经成功了 // 这说明本组也不是想要找的参数匹配组 if (kPolUseOREval == context.options) skip_group = true; break; default: // We have evaluated an action. // action opcode返回类型是action而非comparison,这说明本组匹配成功 // 这时要对第ix组进行结果的设置,然后返回 SetInternalState(ix, result); return POLICY_MATCH; } }
if (evaluation) { // Reaching the end of the policy with a positive evaluation is probably // an error: we did not find a final action opcode? return POLICY_ERROR; } return NO_POLICY_MATCH; }
SkipOpcode非常简单,闭着眼睛都知道怎么处理的,当然是根据定界的action来移动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// Decides if an opcode can be skipped (not evaluated) or not. The function // takes as inputs the opcode and the current evaluation context and returns // true if the opcode should be skipped or not and also can set keep_skipping // to false to signal that the current instruction should be skipped but not // the next after the current one. boolSkipOpcode(const PolicyOpcode& opcode, MatchContext* context, bool* keep_skipping){ if (opcode.IsAction()) { uint32_t options = context->options; context->Clear(); *keep_skipping = false; return (kPolUseOREval != options); } *keep_skipping = true; returntrue; }
// Test that we can store and retrieve a void pointer: // 先确认ParameterSet本身没毛病 constvoid* result1 = 0; uint32_t result2 = 0; EXPECT_TRUE(pset1.Get(&result1)); EXPECT_TRUE(pv1 == result1); EXPECT_FALSE(pset1.Get(&result2)); EXPECT_TRUE(pset2.Get(&result1)); EXPECT_TRUE(pv2 == result1); EXPECT_FALSE(pset2.Get(&result2));
// Test that we can store and retrieve a uint32_t: // 确保ParameterSet对uint32_t类型的处理是正确的(它是兼容uint32_t和指针两种处理的) uint32_t number = 12747; ParameterSet pset3 = ParamPickerMake(number); EXPECT_FALSE(pset3.Get(&result1)); // Get方法有多个重载,这里因为TYPE对不上理应返会false EXPECT_TRUE(pset3.Get(&result2)); // 这个则应该返回true EXPECT_EQ(number, result2);
// Test that we can store and retrieve a string: // 测试字符串的ParameterSet有没有毛病 constwchar_t* txt = L"S231L"; ParameterSet pset4 = ParamPickerMake(txt); constwchar_t* result3 = nullptr; EXPECT_TRUE(pset4.Get(&result3)); EXPECT_EQ(0, wcscmp(txt, result3)); }
// Nulls not allowed on the params. // 参数是null的情况,会返回ERROR(此时不会返回裁决值 true或false) EXPECT_EQ(EVAL_ERROR, op2->Evaluate(nullptr, 0, nullptr)); EXPECT_EQ(EVAL_ERROR, op2->Evaluate(nullptr, 1, nullptr));
// True and False opcodes do not 'require' a number of parameters EXPECT_EQ(EVAL_TRUE, op2->Evaluate(&ppb1, 0, nullptr)); EXPECT_EQ(EVAL_TRUE, op2->Evaluate(&ppb1, 1, nullptr));
// Test Inverting the logic. Note that inversion is done outside // any particular opcode evaluation so no need to repeat for all // opcodes. // 设置一个有着kPolNegateEval标记位得永远返回false的opcode PolicyOpcode* op3 = opcode_maker.MakeOpAlwaysFalse(kPolNegateEval); ASSERT_NE(nullptr, op3); // 由于kPolNegateEval,所以永远返回True EXPECT_EQ(EVAL_TRUE, op3->Evaluate(&ppb1, 1, nullptr)); PolicyOpcode* op4 = opcode_maker.MakeOpAlwaysTrue(kPolNegateEval); ASSERT_NE(nullptr, op4); // 这个就永远返回false EXPECT_EQ(EVAL_FALSE, op4->Evaluate(&ppb1, 1, nullptr));
// Test that we clear the match context PolicyOpcode* op5 = opcode_maker.MakeOpAlwaysTrue(kPolClearContext); ASSERT_NE(nullptr, op5); MatchContext context; context.position = 1; context.options = kPolUseOREval; // 对字符串连续匹配所用的辅助结构context的测试,实际上这里是用不到的 // 只是单纯的测试options的值是否会正确的设置给context EXPECT_EQ(EVAL_TRUE, op5->Evaluate(&ppb1, 1, &context)); EXPECT_EQ(0u, context.position); MatchContext context2; EXPECT_EQ(context2.options, context.options); }
TEST(PolicyEngineTest, OpcodeMakerCase1) { // Testing that the opcode maker does not overrun the // supplied buffer. It should only be able to make 'count' opcodes. void* dummy = nullptr; ParameterSet ppb1 = ParamPickerMake(dummy);
// 放置count个false comparsion opcode到buffer中 for (size_t ix = 0; ix != count; ++ix) { PolicyOpcode* op = opcode_maker.MakeOpAlwaysFalse(kPolNone); ASSERT_NE(nullptr, op); EXPECT_EQ(EVAL_FALSE, op->Evaluate(&ppb1, 1, nullptr)); } // There should be no room more another opcode: PolicyOpcode* op1 = opcode_maker.MakeOpAlwaysFalse(kPolNone); ASSERT_EQ(nullptr, op1); }
TEST(PolicyEngineTest, OpcodeMakerCase2) { SetupNtdllImports(); // Testing that the opcode maker does not overrun the // supplied buffer. It should only be able to make 'count' opcodes. // The difference with the previous test is that this opcodes allocate // the string 'txt2' inside the same buffer. constwchar_t* txt1 = L"1234"; constwchar_t txt2[] = L"123";
// Test that it does not overrun the buffer. // 放置count个字符串比较opcode到buffer for (size_t ix = 0; ix != count; ++ix) { PolicyOpcode* op = opcode_maker.MakeOpWStringMatch( 0, txt2, 0, CASE_SENSITIVE, kPolClearContext); ASSERT_NE(nullptr, op); EXPECT_EQ(EVAL_TRUE, op->Evaluate(&ppb1, 1, &mc1)); }
// There should be no room more another opcode: PolicyOpcode* op1 = opcode_maker.MakeOpWStringMatch(0, txt2, 0, CASE_SENSITIVE, kPolNone); ASSERT_EQ(nullptr, op1); }
// Construct two policy rules that say: // // #1 // If the path is c:\\documents and settings\\* AND // If the creation mode is 'open existing' AND // If the security descriptor is null THEN // Ask the broker. // // #2 // If the security descriptor is null AND // If the path ends with *.txt AND // If the creation mode is not 'create new' THEN // return Access Denied. // 构造了两个rule: // 第一个规则的判断条件: // path得是c:\\documents and settings\\*且创建模式是open existing且安全描述符是null // 满足则action为ASK_BROKER // 第二个规则的判断条件: // 安全描述符是null且path以*.txt结尾且创建模式不是`create new` // 满足则action为Access Denied // // 这几个参数就是要进行裁决的ParameterSet[] enumFileCreateArgs { FileNameArg, CreationDispositionArg, FlagsAndAttributesArg, SecurityAttributes };
// Test should match the first rule set. // 看起来eval_params就是ParameterSet数组,这一组参数理应匹配到第一套规则 // 设置kShortEval模式,匹配到了一个就会返回 pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); // 结果应该是匹配到 EXPECT_EQ(POLICY_MATCH, pr);、 // 结果应该是ASK_BROKER EXPECT_EQ(ASK_BROKER, pol_ev.GetAction());
// Test should still match the first rule set. // 再次审判,结果应该还是一样的 pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); EXPECT_EQ(POLICY_MATCH, pr); EXPECT_EQ(ASK_BROKER, pol_ev.GetAction());
// Changing creation_mode such that evaluation should not match any rule. // 修改一下参数的值,此时应该匹配不到规则 creation_mode = CREATE_NEW; pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); EXPECT_EQ(NO_POLICY_MATCH, pr);
// Changing creation_mode such that evaluation should match rule #2. // 再次修改一个参数值,此时应该匹配到第二套规则 creation_mode = OPEN_ALWAYS; pr = pol_ev.Evaluate(kShortEval, eval_params, _countof(eval_params)); EXPECT_EQ(POLICY_MATCH, pr); EXPECT_EQ(FAKE_ACCESS_DENIED, pol_ev.GetAction());
// Warning: The following macros store the address to the actual variables, in // other words, the values are not copied. #define POLPARAMS_BEGIN(type) class type { public: enum Args { #define POLPARAM(arg) arg, #define POLPARAMS_END(type) PolParamLast }; }; \ typedef sandbox::ParameterSet type##Array [type::PolParamLast];