summaryrefslogtreecommitdiff
path: root/chromium/sandbox/linux/seccomp-bpf/verifier.cc
blob: 60c0eab20d613ef0fe165098dc66e2945d9e93fc (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
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
// Copyright (c) 2012 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.

#include <string.h>

#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h"
#include "sandbox/linux/seccomp-bpf/syscall_iterator.h"
#include "sandbox/linux/seccomp-bpf/verifier.h"


namespace {

using playground2::ErrorCode;
using playground2::Sandbox;
using playground2::Verifier;
using playground2::arch_seccomp_data;

struct State {
  State(const std::vector<struct sock_filter>& p,
        const struct arch_seccomp_data& d) :
    program(p),
    data(d),
    ip(0),
    accumulator(0),
    acc_is_valid(false) {
  }
  const std::vector<struct sock_filter>& program;
  const struct arch_seccomp_data&        data;
  unsigned int                           ip;
  uint32_t                               accumulator;
  bool                                   acc_is_valid;

 private:
  DISALLOW_IMPLICIT_CONSTRUCTORS(State);
};

uint32_t EvaluateErrorCode(Sandbox *sandbox, const ErrorCode& code,
                           const struct arch_seccomp_data& data) {
  if (code.error_type() == ErrorCode::ET_SIMPLE ||
      code.error_type() == ErrorCode::ET_TRAP) {
    return code.err();
  } else if (code.error_type() == ErrorCode::ET_COND) {
    if (code.width() == ErrorCode::TP_32BIT &&
        (data.args[code.argno()] >> 32) &&
        (data.args[code.argno()] & 0xFFFFFFFF80000000ull) !=
        0xFFFFFFFF80000000ull) {
      return sandbox->Unexpected64bitArgument().err();
    }
    switch (code.op()) {
    case ErrorCode::OP_EQUAL:
      return EvaluateErrorCode(sandbox,
                               (code.width() == ErrorCode::TP_32BIT
                                ? uint32_t(data.args[code.argno()])
                                : data.args[code.argno()]) == code.value()
                               ? *code.passed()
                               : *code.failed(),
                               data);
    case ErrorCode::OP_HAS_ALL_BITS:
      return EvaluateErrorCode(sandbox,
                               ((code.width() == ErrorCode::TP_32BIT
                                 ? uint32_t(data.args[code.argno()])
                                 : data.args[code.argno()]) & code.value())
                               == code.value()
                               ? *code.passed()
                               : *code.failed(),
                               data);
    case ErrorCode::OP_HAS_ANY_BITS:
      return EvaluateErrorCode(sandbox,
                               (code.width() == ErrorCode::TP_32BIT
                                ? uint32_t(data.args[code.argno()])
                                : data.args[code.argno()]) & code.value()
                               ? *code.passed()
                               : *code.failed(),
                               data);
    default:
      return SECCOMP_RET_INVALID;
    }
  } else {
    return SECCOMP_RET_INVALID;
  }
}

bool VerifyErrorCode(Sandbox *sandbox,
                     const std::vector<struct sock_filter>& program,
                     struct arch_seccomp_data *data,
                     const ErrorCode& root_code,
                     const ErrorCode& code,
                     const char **err) {
  if (code.error_type() == ErrorCode::ET_SIMPLE ||
      code.error_type() == ErrorCode::ET_TRAP) {
    uint32_t computed_ret = Verifier::EvaluateBPF(program, *data, err);
    if (*err) {
      return false;
    } else if (computed_ret != EvaluateErrorCode(sandbox, root_code, *data)) {
      // For efficiency's sake, we'd much rather compare "computed_ret"
      // against "code.err()". This works most of the time, but it doesn't
      // always work for nested conditional expressions. The test values
      // that we generate on the fly to probe expressions can trigger
      // code flow decisions in multiple nodes of the decision tree, and the
      // only way to compute the correct error code in that situation is by
      // calling EvaluateErrorCode().
      *err = "Exit code from BPF program doesn't match";
      return false;
    }
  } else if (code.error_type() == ErrorCode::ET_COND) {
    if (code.argno() < 0 || code.argno() >= 6) {
      *err = "Invalid argument number in error code";
      return false;
    }
    switch (code.op()) {
    case ErrorCode::OP_EQUAL:
      // Verify that we can check a 32bit value (or the LSB of a 64bit value)
      // for equality.
      data->args[code.argno()] = code.value();
      if (!VerifyErrorCode(sandbox, program, data, root_code,
                           *code.passed(), err)) {
        return false;
      }

      // Change the value to no longer match and verify that this is detected
      // as an inequality.
      data->args[code.argno()] = code.value() ^ 0x55AA55AA;
      if (!VerifyErrorCode(sandbox, program, data, root_code,
                           *code.failed(), err)) {
        return false;
      }

      // BPF programs can only ever operate on 32bit values. So, we have
      // generated additional BPF instructions that inspect the MSB. Verify
      // that they behave as intended.
      if (code.width() == ErrorCode::TP_32BIT) {
        if (code.value() >> 32) {
          SANDBOX_DIE("Invalid comparison of a 32bit system call argument "
                      "against a 64bit constant; this test is always false.");
        }

        // If the system call argument was intended to be a 32bit parameter,
        // verify that it is a fatal error if a 64bit value is ever passed
        // here.
        data->args[code.argno()] = 0x100000000ull;
        if (!VerifyErrorCode(sandbox, program, data, root_code,
                             sandbox->Unexpected64bitArgument(),
                             err)) {
          return false;
        }
      } else {
        // If the system call argument was intended to be a 64bit parameter,
        // verify that we can handle (in-)equality for the MSB. This is
        // essentially the same test that we did earlier for the LSB.
        // We only need to verify the behavior of the inequality test. We
        // know that the equality test already passed, as unlike the kernel
        // the Verifier does operate on 64bit quantities.
        data->args[code.argno()] = code.value() ^ 0x55AA55AA00000000ull;
        if (!VerifyErrorCode(sandbox, program, data, root_code,
                             *code.failed(), err)) {
          return false;
        }
      }
      break;
    case ErrorCode::OP_HAS_ALL_BITS:
    case ErrorCode::OP_HAS_ANY_BITS:
      // A comprehensive test of bit values is difficult and potentially rather
      // time-expensive. We avoid doing so at run-time and instead rely on the
      // unittest for full testing. The test that we have here covers just the
      // common cases. We test against the bitmask itself, all zeros and all
      // ones.
      {
        // Testing "any" bits against a zero mask is always false. So, there
        // are some cases, where we expect tests to take the "failed()" branch
        // even though this is a test that normally should take "passed()".
        const ErrorCode& passed =
          (!code.value() && code.op() == ErrorCode::OP_HAS_ANY_BITS) ||

          // On a 32bit system, it is impossible to pass a 64bit value as a
          // system call argument. So, some additional tests always evaluate
          // as false.
          ((code.value() & ~uint64_t(uintptr_t(-1))) &&
           code.op() == ErrorCode::OP_HAS_ALL_BITS) ||
          (code.value() && !(code.value() & uintptr_t(-1)) &&
           code.op() == ErrorCode::OP_HAS_ANY_BITS)

          ? *code.failed() : *code.passed();

        // Similary, testing for "all" bits in a zero mask is always true. So,
        // some cases pass despite them normally failing.
        const ErrorCode& failed =
          !code.value() && code.op() == ErrorCode::OP_HAS_ALL_BITS
          ? *code.passed() : *code.failed();

        data->args[code.argno()] = code.value() & uintptr_t(-1);
        if (!VerifyErrorCode(sandbox, program, data, root_code, passed, err)) {
          return false;
        }
        data->args[code.argno()] = uintptr_t(-1);
        if (!VerifyErrorCode(sandbox, program, data, root_code, passed, err)) {
          return false;
        }
        data->args[code.argno()] = 0;
        if (!VerifyErrorCode(sandbox, program, data, root_code, failed, err)) {
          return false;
        }
      }
      break;
    default: // TODO(markus): Need to add support for OP_GREATER
      *err = "Unsupported operation in conditional error code";
      return false;
    }
  } else {
    *err = "Attempting to return invalid error code from BPF program";
    return false;
  }
  return true;
}

void Ld(State *state, const struct sock_filter& insn, const char **err) {
  if (BPF_SIZE(insn.code) != BPF_W ||
      BPF_MODE(insn.code) != BPF_ABS) {
    *err = "Invalid BPF_LD instruction";
    return;
  }
  if (insn.k < sizeof(struct arch_seccomp_data) && (insn.k & 3) == 0) {
    // We only allow loading of properly aligned 32bit quantities.
    memcpy(&state->accumulator,
           reinterpret_cast<const char *>(&state->data) + insn.k,
           4);
  } else {
    *err = "Invalid operand in BPF_LD instruction";
    return;
  }
  state->acc_is_valid = true;
  return;
}

void Jmp(State *state, const struct sock_filter& insn, const char **err) {
  if (BPF_OP(insn.code) == BPF_JA) {
    if (state->ip + insn.k + 1 >= state->program.size() ||
        state->ip + insn.k + 1 <= state->ip) {
    compilation_failure:
      *err = "Invalid BPF_JMP instruction";
      return;
    }
    state->ip += insn.k;
  } else {
    if (BPF_SRC(insn.code) != BPF_K ||
        !state->acc_is_valid ||
        state->ip + insn.jt + 1 >= state->program.size() ||
        state->ip + insn.jf + 1 >= state->program.size()) {
      goto compilation_failure;
    }
    switch (BPF_OP(insn.code)) {
    case BPF_JEQ:
      if (state->accumulator == insn.k) {
        state->ip += insn.jt;
      } else {
        state->ip += insn.jf;
      }
      break;
    case BPF_JGT:
      if (state->accumulator > insn.k) {
        state->ip += insn.jt;
      } else {
        state->ip += insn.jf;
      }
      break;
    case BPF_JGE:
      if (state->accumulator >= insn.k) {
        state->ip += insn.jt;
      } else {
        state->ip += insn.jf;
      }
      break;
    case BPF_JSET:
      if (state->accumulator & insn.k) {
        state->ip += insn.jt;
      } else {
        state->ip += insn.jf;
      }
      break;
    default:
      goto compilation_failure;
    }
  }
}

uint32_t Ret(State *, const struct sock_filter& insn, const char **err) {
  if (BPF_SRC(insn.code) != BPF_K) {
    *err = "Invalid BPF_RET instruction";
    return 0;
  }
  return insn.k;
}

void Alu(State *state, const struct sock_filter& insn, const char **err) {
  if (BPF_OP(insn.code) == BPF_NEG) {
    state->accumulator = -state->accumulator;
    return;
  } else {
    if (BPF_SRC(insn.code) != BPF_K) {
      *err = "Unexpected source operand in arithmetic operation";
      return;
    }
    switch (BPF_OP(insn.code)) {
    case BPF_ADD:
      state->accumulator += insn.k;
      break;
    case BPF_SUB:
      state->accumulator -= insn.k;
      break;
    case BPF_MUL:
      state->accumulator *= insn.k;
      break;
    case BPF_DIV:
      if (!insn.k) {
        *err = "Illegal division by zero";
        break;
      }
      state->accumulator /= insn.k;
      break;
    case BPF_MOD:
      if (!insn.k) {
        *err = "Illegal division by zero";
        break;
      }
      state->accumulator %= insn.k;
      break;
    case BPF_OR:
      state->accumulator |= insn.k;
      break;
    case BPF_XOR:
      state->accumulator ^= insn.k;
      break;
    case BPF_AND:
      state->accumulator &= insn.k;
      break;
    case BPF_LSH:
      if (insn.k > 32) {
        *err = "Illegal shift operation";
        break;
      }
      state->accumulator <<= insn.k;
      break;
    case BPF_RSH:
      if (insn.k > 32) {
        *err = "Illegal shift operation";
        break;
      }
      state->accumulator >>= insn.k;
      break;
    default:
      *err = "Invalid operator in arithmetic operation";
      break;
    }
  }
}

}  // namespace

namespace playground2 {

bool Verifier::VerifyBPF(Sandbox *sandbox,
                         const std::vector<struct sock_filter>& program,
                         const Sandbox::Evaluators& evaluators,
                         const char **err) {
  *err = NULL;
  if (evaluators.size() != 1) {
    *err = "Not implemented";
    return false;
  }
  Sandbox::EvaluateSyscall evaluate_syscall = evaluators.begin()->first;
  void *aux                                 = evaluators.begin()->second;
  for (SyscallIterator iter(false); !iter.Done(); ) {
    uint32_t sysnum = iter.Next();
    // We ideally want to iterate over the full system call range and values
    // just above and just below this range. This gives us the full result set
    // of the "evaluators".
    // On Intel systems, this can fail in a surprising way, as a cleared bit 30
    // indicates either i386 or x86-64; and a set bit 30 indicates x32. And
    // unless we pay attention to setting this bit correctly, an early check in
    // our BPF program will make us fail with a misleading error code.
    struct arch_seccomp_data data = { static_cast<int>(sysnum),
                                      static_cast<uint32_t>(SECCOMP_ARCH) };
#if defined(__i386__) || defined(__x86_64__)
#if defined(__x86_64__) && defined(__ILP32__)
    if (!(sysnum & 0x40000000u)) {
      continue;
    }
#else
    if (sysnum & 0x40000000u) {
      continue;
    }
#endif
#endif
    ErrorCode code = evaluate_syscall(sandbox, sysnum, aux);
    if (!VerifyErrorCode(sandbox, program, &data, code, code, err)) {
      return false;
    }
  }
  return true;
}

uint32_t Verifier::EvaluateBPF(const std::vector<struct sock_filter>& program,
                               const struct arch_seccomp_data& data,
                               const char **err) {
  *err = NULL;
  if (program.size() < 1 || program.size() >= SECCOMP_MAX_PROGRAM_SIZE) {
    *err = "Invalid program length";
    return 0;
  }
  for (State state(program, data); !*err; ++state.ip) {
    if (state.ip >= program.size()) {
      *err = "Invalid instruction pointer in BPF program";
      break;
    }
    const struct sock_filter& insn = program[state.ip];
    switch (BPF_CLASS(insn.code)) {
    case BPF_LD:
      Ld(&state, insn, err);
      break;
    case BPF_JMP:
      Jmp(&state, insn, err);
      break;
    case BPF_RET: {
      uint32_t r = Ret(&state, insn, err);
      switch (r & SECCOMP_RET_ACTION) {
      case SECCOMP_RET_TRAP:
      case SECCOMP_RET_ERRNO:
      case SECCOMP_RET_ALLOW:
        break;
      case SECCOMP_RET_KILL:     // We don't ever generate this
      case SECCOMP_RET_TRACE:    // We don't ever generate this
      case SECCOMP_RET_INVALID:  // Should never show up in BPF program
      default:
        *err = "Unexpected return code found in BPF program";
        return 0;
      }
      return r; }
    case BPF_ALU:
      Alu(&state, insn, err);
      break;
    default:
      *err = "Unexpected instruction in BPF program";
      break;
    }
  }
  return 0;
}

}  // namespace