Skip to content

Commit

Permalink
Merge eed1861 into 089f627
Browse files Browse the repository at this point in the history
  • Loading branch information
jpsamaroo committed Oct 28, 2020
2 parents 089f627 + eed1861 commit 25a7b3e
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 3 deletions.
67 changes: 67 additions & 0 deletions test_framework/test_verifier.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import os
import tempfile
import struct
from subprocess import Popen, PIPE
from nose.plugins.skip import Skip, SkipTest
import ubpf.assembler
import testdata
VM = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "vm", "test")

def check_datafile(filename):
"""
Given assembly source code and an expected result, run the eBPF program and
verify that the result matches.
"""
data = testdata.read(filename)
if 'asm' not in data and 'raw' not in data:
raise SkipTest("no asm or raw section in datafile")
if 'result' not in data and 'verifier error' not in data:
raise SkipTest("no result or verifier error section in datafile")
if 'error' in data or 'error pattern' in data:
raise SkipTest("non-verifier error section in datafile")
if not os.path.exists(VM):
raise SkipTest("VM not found")

if 'raw' in data:
code = b''.join(struct.pack("=Q", x) for x in data['raw'])
else:
code = ubpf.assembler.assemble(data['asm'])

memfile = None

cmd = [VM]
if 'mem' in data:
memfile = tempfile.NamedTemporaryFile()
memfile.write(data['mem'])
memfile.flush()
cmd.extend(['-m', memfile.name])

cmd.extend(['-V', '-'])

vm = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)

stdout, stderr = vm.communicate(code)
stdout = stdout.decode("utf-8")
stderr = stderr.decode("utf-8")
stderr = stderr.strip()

if memfile:
memfile.close()

if 'verifier error' in data:
if data['verifier error'] + '\nFailed verification' != stderr:
raise AssertionError("Expected error %r, got %r" % (data['verifier error'] + '\nFailed verification', stderr))
if vm.returncode == 0:
raise AssertionError("Expected VM to exit with an error code")
elif 'error' in data or 'error-pattern' in data:
if vm.returncode == 0:
raise AssertionError("Expected VM to exit with an error code")
else:
if vm.returncode != 0:
raise AssertionError("VM exited with status %d, stderr=%r" % (vm.returncode, stderr))

def test_datafiles():
# Nose test generator
# Creates a testcase for each datafile
for filename in testdata.list_files():
yield check_datafile, filename
3 changes: 3 additions & 0 deletions tests/early-exit.data
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ mov r0, 4
exit
-- result
0x3
-- verifier error
Dead instruction at offset 2
Dead instruction at offset 3
2 changes: 2 additions & 0 deletions tests/mul-loop.data
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,5 @@ jne r1, 0x0, -3
exit
-- result
0x75db9c97
-- verifier error
Loop detected at offset 8
2 changes: 2 additions & 0 deletions tests/prime.data
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,5 @@ jne r4, 0x0, -10
exit
-- result
0x1
-- verifier error
Loop detected at offset 14
2 changes: 2 additions & 0 deletions tests/tcp-sack/match.data
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
-- mem @ pkt-sack.hex
-- result
0x1
-- verifier error
Loop detected at offset 40
2 changes: 2 additions & 0 deletions tests/tcp-sack/nomatch.data
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
-- mem @ pkt-nosack.hex
-- result
0x0
-- verifier error
Loop detected at offset 40
5 changes: 4 additions & 1 deletion vm/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ all: libubpf.a test

ubpf_jit_x86_64.o: ubpf_jit_x86_64.c ubpf_jit_x86_64.h

libubpf.a: ubpf_vm.o ubpf_jit_x86_64.o ubpf_loader.o
libubpf.a: ubpf_vm.o ubpf_jit_x86_64.o ubpf_loader.o ubpf_verifier.o
ar rc $@ $^

ubpf_verifier.o: ubpf_verifier.c
$(CC) -Wall -Werror -Iinc -O2 -g -c -o ubpf_verifier.o ubpf_verifier.c

test: test.o libubpf.a

install:
Expand Down
2 changes: 2 additions & 0 deletions vm/inc/ubpf.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,6 @@ uint64_t ubpf_exec(const struct ubpf_vm *vm, void *mem, size_t mem_len);

ubpf_jit_fn ubpf_compile(struct ubpf_vm *vm, char **errmsg);

int ubpf_verify(struct ubpf_vm *vm);

#endif
16 changes: 14 additions & 2 deletions vm/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ static void register_functions(struct ubpf_vm *vm);

static void usage(const char *name)
{
fprintf(stderr, "usage: %s [-h] [-j|--jit] [-m|--mem PATH] BINARY\n", name);
fprintf(stderr, "usage: %s [-h] [-j|--jit] [-V|--verify] [-m|--mem PATH] BINARY\n", name);
fprintf(stderr, "\nExecutes the eBPF code in BINARY and prints the result to stdout.\n");
fprintf(stderr, "If --mem is given then the specified file will be read and a pointer\nto its data passed in r1.\n");
fprintf(stderr, "If --jit is given then the JIT compiler will be used.\n");
fprintf(stderr, "If --verify is given then the program must pass verification before loading.\n");
fprintf(stderr, "\nOther options:\n");
fprintf(stderr, " -r, --register-offset NUM: Change the mapping from eBPF to x86 registers\n");
}
Expand All @@ -49,14 +50,16 @@ int main(int argc, char **argv)
{ .name = "mem", .val = 'm', .has_arg=1 },
{ .name = "jit", .val = 'j' },
{ .name = "register-offset", .val = 'r', .has_arg=1 },
{ .name = "verify", .val = 'V' },
{ }
};

const char *mem_filename = NULL;
bool jit = false;
bool verify = false;

int opt;
while ((opt = getopt_long(argc, argv, "hm:jr:", longopts, NULL)) != -1) {
while ((opt = getopt_long(argc, argv, "hm:jr:V", longopts, NULL)) != -1) {
switch (opt) {
case 'm':
mem_filename = optarg;
Expand All @@ -67,6 +70,9 @@ int main(int argc, char **argv)
case 'r':
ubpf_set_register_offset(atoi(optarg));
break;
case 'V':
verify = true;
break;
case 'h':
usage(argv[0]);
return 0;
Expand Down Expand Up @@ -128,6 +134,12 @@ int main(int argc, char **argv)
return 1;
}

if (verify && ubpf_verify(vm)) {
fprintf(stderr, "Failed verification\n");
ubpf_destroy(vm);
return 1;
}

uint64_t ret;

if (jit) {
Expand Down
135 changes: 135 additions & 0 deletions vm/ubpf_verifier.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright 2020 Julian P. Samaroo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
#include <sys/mman.h>
#include <errno.h>
#include <assert.h>
#include "ubpf_int.h"
#include "ebpf.h"

typedef int (*WALKER)(struct ubpf_vm *vm, struct ebpf_inst inst, void *data, int inst_off, char *visited);

enum ubpf_walk_action
{
UBPF_WALK_CONTINUE,
UBPF_WALK_STOP,
UBPF_WALK_INVALID,
};

int isjmp(struct ebpf_inst inst)
{
if (((inst.opcode & EBPF_CLS_MASK) == EBPF_CLS_JMP) && (inst.opcode != EBPF_OP_CALL)) {
return true;
}
return false;
}

int
ubpf_walk_paths(struct ubpf_vm *vm, WALKER walk_fn, void *data, int inst_off, char *visited)
{
struct ebpf_inst inst = vm->insts[inst_off];
int cmd = walk_fn(vm, inst, data, inst_off, visited);
visited[inst_off] = 1;
if (cmd != UBPF_WALK_CONTINUE)
return cmd;
if (inst.opcode == EBPF_OP_EXIT) {
return UBPF_WALK_CONTINUE;
} else if (isjmp(inst)) {
int next_pc = inst_off+1+inst.offset;
if (next_pc == inst_off) {
fprintf(stderr, "Jump to self at offset %d\n", inst_off);
return UBPF_WALK_INVALID;
} else if ((next_pc < 0) || (next_pc > vm->num_insts-1)) {
fprintf(stderr, "Jump out-of-bounds at offset %d to %d\n", inst_off, next_pc);
return UBPF_WALK_INVALID;
}
if (visited[next_pc] == 0) {
cmd = ubpf_walk_paths(vm, walk_fn, data, next_pc, visited);
if (cmd == UBPF_WALK_STOP || cmd == UBPF_WALK_INVALID)
return cmd;
}
}
if (inst_off == vm->num_insts-1) {
return UBPF_WALK_CONTINUE;
} else {
return ubpf_walk_paths(vm, walk_fn, data, inst_off+1, visited);
}
}

int
ubpf_walk_start(struct ubpf_vm *vm, WALKER walk_fn, void *data)
{
char visited[vm->num_insts];
memset((void *)visited, 0, vm->num_insts);
return ubpf_walk_paths(vm, walk_fn, data, 0, visited);
}

int
_walker_no_dead_insts(struct ubpf_vm *vm, struct ebpf_inst inst, void *data, int inst_off, char *visited)
{
return UBPF_WALK_CONTINUE;
}

int
ubpf_verify_no_dead_insts(struct ubpf_vm *vm)
{
char visited[vm->num_insts];
memset((void *)visited, 0, vm->num_insts);
int ret = ubpf_walk_paths(vm, _walker_no_dead_insts, NULL, 0, visited);
if (ret)
return ret;
int any_dead = 0;
for (int i = 0; i < vm->num_insts; i++) {
if (visited[i] == 0) {
any_dead = 1;
fprintf(stderr, "Dead instruction at offset %d\n", i);
}
}
return any_dead;
}

int
_walker_no_loops(struct ubpf_vm *vm, struct ebpf_inst inst, void *data, int inst_off, char *visited)
{
if (isjmp(inst) && (inst_off+1+inst.offset < inst_off) && visited[inst_off+1+inst.offset]) {
fprintf(stderr, "Loop detected at offset %d\n", inst_off);
return UBPF_WALK_STOP;
}
return UBPF_WALK_CONTINUE;
}

int
ubpf_verify_no_loops(struct ubpf_vm *vm)
{
return ubpf_walk_start(vm, _walker_no_loops, NULL);
}

int
ubpf_verify(struct ubpf_vm *vm)
{
if (ubpf_verify_no_loops(vm))
return 1;
if (ubpf_verify_no_dead_insts(vm))
return 1;
return 0;
}

0 comments on commit 25a7b3e

Please sign in to comment.