summaryrefslogtreecommitdiffstats
path: root/security/sandbox/chromium/sandbox/win/src/policy_engine_opcodes.h
blob: 8e8816df1cb1c1d0b17ebc303c33355ec6c62096 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
// 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_ = options; }

  // Returns the parameter of the function the opcode concerns.
  uint16_t GetParameter() const { return parameter_; }

 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_;
  uint32_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_