seccomp-tools 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +62 -1
- data/lib/seccomp-tools/asm/asm.rb +34 -0
- data/lib/seccomp-tools/asm/compiler.rb +221 -0
- data/lib/seccomp-tools/asm/tokenizer.rb +152 -0
- data/lib/seccomp-tools/bpf.rb +21 -5
- data/lib/seccomp-tools/cli/asm.rb +54 -0
- data/lib/seccomp-tools/cli/base.rb +8 -0
- data/lib/seccomp-tools/cli/cli.rb +2 -0
- data/lib/seccomp-tools/cli/disasm.rb +2 -2
- data/lib/seccomp-tools/cli/emu.rb +1 -1
- data/lib/seccomp-tools/emulator.rb +1 -1
- data/lib/seccomp-tools/instruction/alu.rb +20 -14
- data/lib/seccomp-tools/version.rb +1 -1
- metadata +21 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 52d71ec73ddfa5dab7d22d982b27b2d4c3610080
|
4
|
+
data.tar.gz: '08439390a2e4a08d8619eba08b7b66a2814372b1'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c69c741edae030e7a775dcad768fa983c627cda1d849947c079d45148453a018cbba249abd18faced059b4c4cdc793e746d707e178b2e845804e7bb9a4c70437
|
7
|
+
data.tar.gz: fc8cc5926cba54d561822b3a75b57c8721b1113ef16117f455c8a91f651dbad16ced7f6e09209d2adcd7a8183f4a1f8d327aa490652fe5f8ee7a3be187fe361a
|
data/README.md
CHANGED
@@ -16,6 +16,7 @@ Some features might be CTF-specific, but still useful for analysis of seccomp in
|
|
16
16
|
* Disasm - Convert bpf to human readable format.
|
17
17
|
- Simple decompile.
|
18
18
|
- Show syscall names.
|
19
|
+
* Asm - Write seccomp rules is so easy!
|
19
20
|
* Emu - Emulate seccomp rules.
|
20
21
|
* (TODO) Solve constraints for executing syscalls (e.g. `execve/open/read/write`).
|
21
22
|
* Support multi-architectures.
|
@@ -39,6 +40,7 @@ $ seccomp-tools --help
|
|
39
40
|
#
|
40
41
|
# dump Automatically dump seccomp bpf from execution file.
|
41
42
|
# disasm Disassemble seccomp bpf.
|
43
|
+
# asm Seccomp bpf assembler.
|
42
44
|
# emu Emulate seccomp rules.
|
43
45
|
#
|
44
46
|
# See 'seccomp-tools --help <command>' to read about a specific subcommand.
|
@@ -111,7 +113,7 @@ $ seccomp-tools dump spec/binary/twctf-2016-diary -f raw | xxd
|
|
111
113
|
|
112
114
|
### disasm
|
113
115
|
|
114
|
-
Disassemble the seccomp bpf.
|
116
|
+
Disassemble the seccomp from raw bpf.
|
115
117
|
```bash
|
116
118
|
$ xxd spec/data/twctf-2016-diary.bpf | head -n 3
|
117
119
|
# 00000000: 2000 0000 0000 0000 1500 0001 0200 0000 ...............
|
@@ -142,6 +144,65 @@ $ seccomp-tools disasm spec/data/twctf-2016-diary.bpf
|
|
142
144
|
|
143
145
|
```
|
144
146
|
|
147
|
+
### asm
|
148
|
+
|
149
|
+
Assemble the seccomp rules into raw bytes.
|
150
|
+
Very useful when want to write custom seccomp rules.
|
151
|
+
|
152
|
+
Supports labels for jumping and use syscall names directly. See example below.
|
153
|
+
```bash
|
154
|
+
$ seccomp-tools asm
|
155
|
+
# asm - Seccomp bpf assembler.
|
156
|
+
#
|
157
|
+
# Usage: seccomp-tools asm IN_FILE [options]
|
158
|
+
# -o, --output FILE Output result into FILE instead of stdout.
|
159
|
+
# -f, --format FORMAT Output format. FORMAT can only be one of <inspect|raw|carray>.
|
160
|
+
# Default: inspect
|
161
|
+
# -a, --arch ARCH Specify architecture.
|
162
|
+
# Supported architectures are <amd64|i386>.
|
163
|
+
|
164
|
+
# Input file for asm
|
165
|
+
$ cat spec/data/libseccomp.asm
|
166
|
+
# # check if arch is X86_64
|
167
|
+
# A = arch
|
168
|
+
# A == 0xc000003e ? next : dead
|
169
|
+
# A = sys_number
|
170
|
+
# A >= 0x40000000 ? dead : next
|
171
|
+
# A == write ? ok : next
|
172
|
+
# A == close ? ok : next
|
173
|
+
# A == dup ? ok : next
|
174
|
+
# A == exit ? ok : next
|
175
|
+
# return ERRNO(5)
|
176
|
+
# ok:
|
177
|
+
# return ALLOW
|
178
|
+
# dead:
|
179
|
+
# return KILL
|
180
|
+
|
181
|
+
$ seccomp-tools asm spec/data/libseccomp.asm
|
182
|
+
# " \x00\x00\x00\x04\x00\x00\x00\x15\x00\x00\b>\x00\x00\xC0 \x00\x00\x00\x00\x00\x00\x005\x00\x06\x00\x00\x00\x00@\x15\x00\x04\x00\x01\x00\x00\x00\x15\x00\x03\x00\x03\x00\x00\x00\x15\x00\x02\x00 \x00\x00\x00\x15\x00\x01\x00<\x00\x00\x00\x06\x00\x00\x00\x05\x00\x05\x00\x06\x00\x00\x00\x00\x00\xFF\x7F\x06\x00\x00\x00\x00\x00\x00\x00"
|
183
|
+
|
184
|
+
$ seccomp-tools asm spec/data/libseccomp.asm -f carray
|
185
|
+
# unsigned char bpf[] = {32,0,0,0,4,0,0,0,21,0,0,8,62,0,0,192,32,0,0,0,0,0,0,0,53,0,6,0,0,0,0,64,21,0,4,0,1,0,0,0,21,0,3,0,3,0,0,0,21,0,2,0,32,0,0,0,21,0,1,0,60,0,0,0,6,0,0,0,5,0,5,0,6,0,0,0,0,0,255,127,6,0,0,0,0,0,0,0};
|
186
|
+
|
187
|
+
|
188
|
+
# let's asm then disasm!
|
189
|
+
$ seccomp-tools asm spec/data/libseccomp.asm -f raw | seccomp-tools disasm -
|
190
|
+
# line CODE JT JF K
|
191
|
+
# =================================
|
192
|
+
# 0000: 0x20 0x00 0x00 0x00000004 A = arch
|
193
|
+
# 0001: 0x15 0x00 0x08 0xc000003e if (A != ARCH_X86_64) goto 0010
|
194
|
+
# 0002: 0x20 0x00 0x00 0x00000000 A = sys_number
|
195
|
+
# 0003: 0x35 0x06 0x00 0x40000000 if (A >= 0x40000000) goto 0010
|
196
|
+
# 0004: 0x15 0x04 0x00 0x00000001 if (A == write) goto 0009
|
197
|
+
# 0005: 0x15 0x03 0x00 0x00000003 if (A == close) goto 0009
|
198
|
+
# 0006: 0x15 0x02 0x00 0x00000020 if (A == dup) goto 0009
|
199
|
+
# 0007: 0x15 0x01 0x00 0x0000003c if (A == exit) goto 0009
|
200
|
+
# 0008: 0x06 0x00 0x00 0x00050005 return ERRNO
|
201
|
+
# 0009: 0x06 0x00 0x00 0x7fff0000 return ALLOW
|
202
|
+
# 0010: 0x06 0x00 0x00 0x00000000 return KILL
|
203
|
+
|
204
|
+
```
|
205
|
+
|
145
206
|
### Emu
|
146
207
|
|
147
208
|
Emulate seccomp given `sys_nr`, `arg0`, `arg1`, etc.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'seccomp-tools/asm/compiler'
|
2
|
+
require 'seccomp-tools/util'
|
3
|
+
|
4
|
+
module SeccompTools
|
5
|
+
# Assembler of seccomp bpf.
|
6
|
+
module Asm
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# Assembler of seccomp bpf.
|
10
|
+
# @param [String] str
|
11
|
+
# @return [String]
|
12
|
+
# Raw bpf bytes.
|
13
|
+
# @example
|
14
|
+
# asm(<<EOS)
|
15
|
+
# # lines start with '#' are comments
|
16
|
+
# A = sys_number # here's a comment, too
|
17
|
+
# A >= 0x40000000 ? dead : next # 'next' is a keyword, denote the next instruction
|
18
|
+
# A == read ? ok : next # custom defined label 'dead' and 'ok'
|
19
|
+
# A == 1 ? ok : next # SYS_write = 1 in amd64
|
20
|
+
# return ERRNO(1)
|
21
|
+
# dead:
|
22
|
+
# return KILL
|
23
|
+
# ok:
|
24
|
+
# return ALLOW
|
25
|
+
# EOS
|
26
|
+
# #=> <raw binary bytes>
|
27
|
+
def asm(str, arch: nil)
|
28
|
+
arch = Util.system_arch if arch.nil? # TODO: show warning
|
29
|
+
compiler = Compiler.new(arch)
|
30
|
+
str.lines.each { |l| compiler.process(l) }
|
31
|
+
compiler.compile!.map(&:asm).join
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,221 @@
|
|
1
|
+
require 'seccomp-tools/asm/tokenizer'
|
2
|
+
require 'seccomp-tools/bpf'
|
3
|
+
require 'seccomp-tools/const'
|
4
|
+
|
5
|
+
module SeccompTools
|
6
|
+
module Asm
|
7
|
+
# Compile seccomp rules.
|
8
|
+
class Compiler
|
9
|
+
def initialize(arch)
|
10
|
+
@arch = arch
|
11
|
+
@insts = []
|
12
|
+
@labels = {}
|
13
|
+
@input = []
|
14
|
+
end
|
15
|
+
|
16
|
+
# Before compile assembly codes, process each lines.
|
17
|
+
#
|
18
|
+
# With this we can support label in seccomp rules.
|
19
|
+
# @param [String] line
|
20
|
+
# One line of seccomp rule.
|
21
|
+
# @return [void]
|
22
|
+
def process(line)
|
23
|
+
@input << line.strip
|
24
|
+
line = remove_comment(line)
|
25
|
+
@token = Tokenizer.new(line)
|
26
|
+
return if line.empty?
|
27
|
+
begin
|
28
|
+
res = case line
|
29
|
+
when /\?/ then cmp
|
30
|
+
when /^#{Tokenizer::LABEL_REGEXP}:/ then define_label
|
31
|
+
when /^return/ then ret
|
32
|
+
when /^(A|X)\s*=[^=]/ then assign
|
33
|
+
when /^A\s*.=/ then alu
|
34
|
+
end
|
35
|
+
rescue ArgumentError => e
|
36
|
+
invalid(@input.size - 1, e.message)
|
37
|
+
end
|
38
|
+
invalid(@input.size - 1) if res.nil?
|
39
|
+
if res.first == :label
|
40
|
+
@labels[res.last] = @insts.size
|
41
|
+
else
|
42
|
+
@insts << res
|
43
|
+
end
|
44
|
+
res
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Array<SeccompTools::BPF>]
|
48
|
+
def compile!
|
49
|
+
@insts.map.with_index do |inst, idx|
|
50
|
+
@line = idx
|
51
|
+
case inst.first
|
52
|
+
when :assign then compile_assign(inst[1], inst[2])
|
53
|
+
when :alu then compile_alu(inst[1], inst[2])
|
54
|
+
when :ret then compile_ret(inst[1])
|
55
|
+
when :cmp then compile_cmp(inst[1], inst[2], inst[3], inst[4])
|
56
|
+
end
|
57
|
+
end
|
58
|
+
rescue ArgumentError => e
|
59
|
+
invalid(@line, e.message)
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def emit(*args, k: 0, jt: 0, jf: 0)
|
65
|
+
code = 0
|
66
|
+
# bad idea, while keys are not duplicated so this is ok.
|
67
|
+
args.each do |a|
|
68
|
+
code |= Const::BPF::COMMAND.fetch(a, 0)
|
69
|
+
code |= Const::BPF::JMP.fetch(a, 0)
|
70
|
+
code |= Const::BPF::SRC.fetch(a, 0)
|
71
|
+
code |= Const::BPF::MODE.fetch(a, 0)
|
72
|
+
code |= Const::BPF::OP.fetch(a, 0)
|
73
|
+
code |= Const::BPF::MISCOP.fetch(a, 0)
|
74
|
+
end
|
75
|
+
BPF.new({ code: code, k: k, jt: jt, jf: jf }, @arch, @line)
|
76
|
+
end
|
77
|
+
|
78
|
+
# A = X / X = A
|
79
|
+
# <A|X> = mem[i]
|
80
|
+
# <A|X> = 123|sys_const
|
81
|
+
# A = args[i]|sys_number|arch
|
82
|
+
# A = data[4 * i]
|
83
|
+
def compile_assign(dst, src)
|
84
|
+
# misc txa / tax
|
85
|
+
return emit(:misc, :txa) if dst == :a && src == :x
|
86
|
+
return emit(:misc, :tax) if dst == :x && src == :a
|
87
|
+
src = evaluate(src)
|
88
|
+
# TODO: handle store case.
|
89
|
+
ld = dst == :x ? :ldx : :ld
|
90
|
+
# <A|X> = <immi>
|
91
|
+
return emit(ld, :imm, k: src) if src.is_a?(Integer)
|
92
|
+
# now src must be in form [:mem/:data, num]
|
93
|
+
return emit(ld, :mem, k: src.last) if src.first == :mem
|
94
|
+
# check if num is multiple of 4
|
95
|
+
raise ArgumentError, 'Index of data[] must be multiplication of 4' if src.last % 4 != 0
|
96
|
+
emit(ld, :abs, k: src.last)
|
97
|
+
end
|
98
|
+
|
99
|
+
def compile_alu(op, val)
|
100
|
+
val = evaluate(val)
|
101
|
+
src = val == :x ? :x : :k
|
102
|
+
val = 0 if val == :x
|
103
|
+
emit(:alu, op, src, k: val)
|
104
|
+
end
|
105
|
+
|
106
|
+
def compile_ret(val)
|
107
|
+
emit(:ret, k: val)
|
108
|
+
end
|
109
|
+
|
110
|
+
def compile_cmp(op, val, jt, jf)
|
111
|
+
jt = label_offset(jt)
|
112
|
+
jf = label_offset(jf)
|
113
|
+
val = evaluate(val)
|
114
|
+
src = val == :x ? :x : :k
|
115
|
+
val = 0 if val == :x
|
116
|
+
emit(:jmp, op, src, jt: jt, jf: jf, k: val)
|
117
|
+
end
|
118
|
+
|
119
|
+
def label_offset(label)
|
120
|
+
return label if label.is_a?(Integer)
|
121
|
+
return 0 if label == 'next'
|
122
|
+
raise ArgumentError, "Undefined label #{label.inspect}" if @labels[label].nil?
|
123
|
+
@labels[label] - @line - 1
|
124
|
+
end
|
125
|
+
|
126
|
+
def evaluate(val)
|
127
|
+
return val if val.is_a?(Integer) || val == :x
|
128
|
+
# keywords
|
129
|
+
val = case val
|
130
|
+
when 'sys_number' then [:data, 0]
|
131
|
+
when 'arch' then [:data, 4]
|
132
|
+
else val
|
133
|
+
end
|
134
|
+
return Const::Syscall.const_get(@arch.upcase.to_sym)[val.to_sym] if val.is_a?(String)
|
135
|
+
# remains are [:mem/:data/:args, <num>]
|
136
|
+
# first convert args to data
|
137
|
+
val = [:data, val.last * 8 + 16] if val.first == :args
|
138
|
+
val
|
139
|
+
end
|
140
|
+
|
141
|
+
attr_reader :token
|
142
|
+
|
143
|
+
# A <comparison> <sys_str|X|Integer> ? <label|Integer> : <label|Integer>
|
144
|
+
def cmp
|
145
|
+
token.fetch!('A')
|
146
|
+
op = token.fetch!(:comparison)
|
147
|
+
dst = token.fetch!(:sys_num_x)
|
148
|
+
token.fetch!('?')
|
149
|
+
jt = token.fetch!(:goto)
|
150
|
+
token.fetch!(':')
|
151
|
+
jf = token.fetch!(:goto)
|
152
|
+
convert = {
|
153
|
+
:< => :>=,
|
154
|
+
:<= => :>,
|
155
|
+
:!= => :==
|
156
|
+
}
|
157
|
+
if convert[op]
|
158
|
+
op = convert[op]
|
159
|
+
jt, jf = jf, jt
|
160
|
+
end
|
161
|
+
op = {
|
162
|
+
:>= => :jge,
|
163
|
+
:> => :jgt,
|
164
|
+
:== => :jeq
|
165
|
+
}[op]
|
166
|
+
[:cmp, op, dst, jt, jf]
|
167
|
+
end
|
168
|
+
|
169
|
+
def ret
|
170
|
+
token.fetch!('return')
|
171
|
+
[:ret, token.fetch!(:ret)]
|
172
|
+
end
|
173
|
+
|
174
|
+
# possible types after '=':
|
175
|
+
# A = X
|
176
|
+
# X = A
|
177
|
+
# A = 123
|
178
|
+
# A = data[i]
|
179
|
+
# A = mem[i]
|
180
|
+
# A = args[i]
|
181
|
+
# A = sys_number|arch
|
182
|
+
def assign
|
183
|
+
dst = token.fetch!(:ax)
|
184
|
+
token.fetch!('=')
|
185
|
+
src = token.fetch(:ax) ||
|
186
|
+
token.fetch(:sys_num_x) ||
|
187
|
+
token.fetch(:ary) ||
|
188
|
+
token.fetch('sys_number') ||
|
189
|
+
token.fetch('arch')
|
190
|
+
[:assign, dst, src]
|
191
|
+
end
|
192
|
+
|
193
|
+
def define_label
|
194
|
+
name = token.fetch!(:goto)
|
195
|
+
token.fetch(':')
|
196
|
+
[:label, name]
|
197
|
+
end
|
198
|
+
|
199
|
+
# A op= sys_num_x
|
200
|
+
def alu
|
201
|
+
token.fetch!('A')
|
202
|
+
op = token.fetch!(:alu_op)
|
203
|
+
token.fetch!('=')
|
204
|
+
src = token.fetch!(:sys_num_x)
|
205
|
+
[:alu, op, src]
|
206
|
+
end
|
207
|
+
|
208
|
+
def remove_comment(line)
|
209
|
+
line = line.to_s.dup
|
210
|
+
line.slice!(/#.*\Z/m)
|
211
|
+
line.strip
|
212
|
+
end
|
213
|
+
|
214
|
+
def invalid(line, extra_msg = nil)
|
215
|
+
message = "Invalid instruction at line #{line + 1}: #{@input[line].inspect}"
|
216
|
+
message += "\n" + 'Error: ' + extra_msg if extra_msg
|
217
|
+
raise ArgumentError, message
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'seccomp-tools/const'
|
2
|
+
require 'seccomp-tools/instruction/alu'
|
3
|
+
|
4
|
+
module SeccompTools
|
5
|
+
module Asm
|
6
|
+
# Fetch tokens from string.
|
7
|
+
# This class is for internel usage, used by {Compiler}.
|
8
|
+
class Tokenizer
|
9
|
+
# a valid label
|
10
|
+
LABEL_REGEXP = /[a-z_][a-z0-9_]+/
|
11
|
+
attr_accessor :cur
|
12
|
+
|
13
|
+
# @param [String] str
|
14
|
+
# @example
|
15
|
+
# Tokenizer.new('return ALLOW')
|
16
|
+
def initialize(str)
|
17
|
+
@str = str
|
18
|
+
@cur = @str.dup
|
19
|
+
end
|
20
|
+
|
21
|
+
# Fetch a token without raising errors.
|
22
|
+
def fetch(type)
|
23
|
+
fetch!(type)
|
24
|
+
rescue ArgumentError
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# Fetch a token. When expected token is not found,
|
29
|
+
# error with proper message would be raised.
|
30
|
+
#
|
31
|
+
# @param [String, Symbol] type
|
32
|
+
# @example
|
33
|
+
# tokenizer = Tokenizer.new('return ALLOW')
|
34
|
+
# tokenfizer.fetch!('return')
|
35
|
+
# #=> "return"
|
36
|
+
# tokenizer.fetch!(:ret)
|
37
|
+
# #=> 2147418112
|
38
|
+
def fetch!(type)
|
39
|
+
@last_match_size = 0
|
40
|
+
res = case type
|
41
|
+
when String then fetch_str(type) || raise_expected("token #{type.inspect}")
|
42
|
+
when :comparison then fetch_strs(COMPARISON).to_sym || raise_expected('a comparison operator')
|
43
|
+
when :sys_num_x then fetch_sys_num_x || raise_expected("a syscall number or 'X'")
|
44
|
+
when :goto then fetch_number || fetch_label || raise_expected('a number or label name')
|
45
|
+
when :ret then fetch_return || raise(ArgumentError, <<-EOS)
|
46
|
+
Invalid return type: #{cur.inspect}.
|
47
|
+
EOS
|
48
|
+
when :ax then fetch_ax || raise_expected("'A' or 'X'")
|
49
|
+
when :ary then fetch_ary || raise_expected('data[<num>], mem[<num>], or args[<num>]')
|
50
|
+
when :alu_op then fetch_alu || raise_expected('an ALU operator')
|
51
|
+
else raise ArgumentError, "Unsupported type: #{type.inspect}"
|
52
|
+
end
|
53
|
+
slice!
|
54
|
+
res
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
COMPARISON = %w[== != <= >= < >].freeze
|
60
|
+
|
61
|
+
def fetch_strs(strs)
|
62
|
+
strs.find(&method(:fetch_str))
|
63
|
+
end
|
64
|
+
|
65
|
+
def fetch_str(str)
|
66
|
+
return nil unless cur.start_with?(str)
|
67
|
+
@last_match_size = str.size
|
68
|
+
str
|
69
|
+
end
|
70
|
+
|
71
|
+
def fetch_ax
|
72
|
+
return :a if fetch_str('A')
|
73
|
+
return :x if fetch_str('X')
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def fetch_sys_num_x
|
78
|
+
return :x if fetch_str('X')
|
79
|
+
fetch_number || fetch_syscall
|
80
|
+
end
|
81
|
+
|
82
|
+
# Currently only supports 10-based decimal numbers.
|
83
|
+
def fetch_number
|
84
|
+
res = fetch_regexp(/^0x[0-9a-f]+/) || fetch_regexp(/^[0-9]+/)
|
85
|
+
return nil if res.nil?
|
86
|
+
Integer(res)
|
87
|
+
end
|
88
|
+
|
89
|
+
def fetch_syscall
|
90
|
+
sys = Const::Syscall::AMD64
|
91
|
+
sys = sys.merge(Const::Syscall::I386)
|
92
|
+
fetch_strs(sys.keys.map(&:to_s).sort_by(&:size).reverse)
|
93
|
+
end
|
94
|
+
|
95
|
+
def fetch_regexp(regexp)
|
96
|
+
idx = cur =~ regexp
|
97
|
+
return nil if idx.nil? || idx != 0
|
98
|
+
match = cur.match(regexp)[0]
|
99
|
+
@last_match_size = match.size
|
100
|
+
match
|
101
|
+
end
|
102
|
+
|
103
|
+
def fetch_label
|
104
|
+
fetch_regexp(LABEL_REGEXP)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Convert <type>(num) into return value according to {Const::ACTION}
|
108
|
+
# @return [Integer, :a]
|
109
|
+
def fetch_return
|
110
|
+
regexp = /(#{Const::BPF::ACTION.keys.join('|')})(\([0-9]{1,5}\))?/
|
111
|
+
action = fetch_regexp(regexp)
|
112
|
+
return fetch_str('A') && :a if action.nil?
|
113
|
+
# check if action contains '('the next bytes are (<num>)
|
114
|
+
ret_val = 0
|
115
|
+
if action.include?('(')
|
116
|
+
action, val = action.split('(')
|
117
|
+
ret_val = val.to_i
|
118
|
+
end
|
119
|
+
Const::BPF::ACTION[action.to_sym] | ret_val
|
120
|
+
end
|
121
|
+
|
122
|
+
def fetch_ary
|
123
|
+
support_name = %w[data mem args]
|
124
|
+
regexp = /(#{support_name.join('|')})\[[0-9]{1,2}\]/
|
125
|
+
match = fetch_regexp(regexp)
|
126
|
+
return nil if match.nil?
|
127
|
+
res, val = match.split('[')
|
128
|
+
val = val.to_i
|
129
|
+
[res.to_sym, val]
|
130
|
+
end
|
131
|
+
|
132
|
+
def fetch_alu
|
133
|
+
ops = %w[+ - * / | & ^ << >>]
|
134
|
+
op = fetch_strs(ops)
|
135
|
+
return nil if op.nil?
|
136
|
+
Instruction::ALU::OP_SYM.invert[op.to_sym]
|
137
|
+
end
|
138
|
+
|
139
|
+
def slice!
|
140
|
+
ret = cur.slice!(0, @last_match_size)
|
141
|
+
cur.strip!
|
142
|
+
ret
|
143
|
+
end
|
144
|
+
|
145
|
+
def raise_expected(msg)
|
146
|
+
raise ArgumentError, <<-EOS
|
147
|
+
Expected #{msg}, while #{cur.split[0].inspect} occured.
|
148
|
+
EOS
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
data/lib/seccomp-tools/bpf.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
1
3
|
require 'seccomp-tools/const'
|
2
4
|
require 'seccomp-tools/instruction/instruction'
|
3
5
|
|
@@ -27,11 +29,18 @@ module SeccompTools
|
|
27
29
|
# @param [Integer] line
|
28
30
|
# Line number of this filter.
|
29
31
|
def initialize(raw, arch, line)
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
if raw.is_a?(String)
|
33
|
+
io = StringIO.new(raw)
|
34
|
+
@code = io.read(2).unpack('S').first
|
35
|
+
@jt = io.read(1).ord
|
36
|
+
@jf = io.read(1).ord
|
37
|
+
@k = io.read(4).unpack('L').first
|
38
|
+
else
|
39
|
+
@code = raw[:code]
|
40
|
+
@jt = raw[:jt]
|
41
|
+
@jf = raw[:jf]
|
42
|
+
@k = raw[:k]
|
43
|
+
end
|
35
44
|
@arch = arch
|
36
45
|
@line = line
|
37
46
|
@contexts = Set.new
|
@@ -44,6 +53,13 @@ module SeccompTools
|
|
44
53
|
line, code, jt, jf, k, decompile)
|
45
54
|
end
|
46
55
|
|
56
|
+
# Convert to raw bytes.
|
57
|
+
# @return [String]
|
58
|
+
# Raw bpf bytes.
|
59
|
+
def asm
|
60
|
+
[code].pack('S*') + [jt, jf].pack('C*') + [k].pack('L')
|
61
|
+
end
|
62
|
+
|
47
63
|
# Command according to +code+.
|
48
64
|
# @return [Symbol]
|
49
65
|
# See {Const::BPF::COMMAND} for list of commands.
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'seccomp-tools/cli/base'
|
2
|
+
require 'seccomp-tools/asm/asm'
|
3
|
+
|
4
|
+
module SeccompTools
|
5
|
+
module CLI
|
6
|
+
# Handle 'asm' command.
|
7
|
+
class Asm < Base
|
8
|
+
# Summary of this command.
|
9
|
+
SUMMARY = 'Seccomp bpf assembler.'.freeze
|
10
|
+
# Usage of this command.
|
11
|
+
USAGE = ('asm - ' + SUMMARY + "\n\n" + 'Usage: seccomp-tools asm IN_FILE [options]').freeze
|
12
|
+
|
13
|
+
def initialize(*)
|
14
|
+
super
|
15
|
+
option[:format] = :inspect
|
16
|
+
end
|
17
|
+
|
18
|
+
# Define option parser.
|
19
|
+
# @return [OptionParser]
|
20
|
+
def parser
|
21
|
+
@parser ||= OptionParser.new do |opt|
|
22
|
+
opt.banner = usage
|
23
|
+
opt.on('-o', '--output FILE', 'Output result into FILE instead of stdout.') do |o|
|
24
|
+
option[:ofile] = o
|
25
|
+
end
|
26
|
+
|
27
|
+
opt.on('-f', '--format FORMAT', %i[inspect raw carray],
|
28
|
+
'Output format. FORMAT can only be one of <inspect|raw|carray>.',
|
29
|
+
'Default: inspect') do |f|
|
30
|
+
option[:format] = f
|
31
|
+
end
|
32
|
+
|
33
|
+
option_arch(opt)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Handle options.
|
38
|
+
# @return [void]
|
39
|
+
def handle
|
40
|
+
return unless super
|
41
|
+
option[:ifile] = argv.shift
|
42
|
+
return CLI.show(parser.help) if option[:ifile].nil?
|
43
|
+
res = SeccompTools::Asm.asm(input, arch: option[:arch])
|
44
|
+
output do
|
45
|
+
case option[:format]
|
46
|
+
when :inspect then res.inspect + "\n"
|
47
|
+
when :raw then res
|
48
|
+
when :carray then "unsigned char bpf[] = {#{res.bytes.join(',')}};\n"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -30,6 +30,14 @@ module SeccompTools
|
|
30
30
|
true
|
31
31
|
end
|
32
32
|
|
33
|
+
# If +option[:ifile]+ is '-', read from stdin,
|
34
|
+
# otherwise, read from file.
|
35
|
+
# @return [String]
|
36
|
+
# String read from file.
|
37
|
+
def input
|
38
|
+
option[:ifile] == '-' ? STDIN.read.force_encoding('ascii-8bit') : IO.binread(option[:ifile])
|
39
|
+
end
|
40
|
+
|
33
41
|
# Write data to stdout or file(s).
|
34
42
|
# @yieldreturn [String]
|
35
43
|
# The data to be written.
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'seccomp-tools/cli/asm'
|
1
2
|
require 'seccomp-tools/cli/disasm'
|
2
3
|
require 'seccomp-tools/cli/dump'
|
3
4
|
require 'seccomp-tools/cli/emu'
|
@@ -10,6 +11,7 @@ module SeccompTools
|
|
10
11
|
COMMANDS = {
|
11
12
|
'dump' => SeccompTools::CLI::Dump,
|
12
13
|
'disasm' => SeccompTools::CLI::Disasm,
|
14
|
+
'asm' => SeccompTools::CLI::Asm,
|
13
15
|
'emu' => SeccompTools::CLI::Emu
|
14
16
|
}.freeze
|
15
17
|
|
@@ -3,7 +3,7 @@ require 'seccomp-tools/disasm/disasm'
|
|
3
3
|
|
4
4
|
module SeccompTools
|
5
5
|
module CLI
|
6
|
-
# Handle '
|
6
|
+
# Handle 'disasm' command.
|
7
7
|
class Disasm < Base
|
8
8
|
# Summary of this command.
|
9
9
|
SUMMARY = 'Disassemble seccomp bpf.'.freeze
|
@@ -29,7 +29,7 @@ module SeccompTools
|
|
29
29
|
return unless super
|
30
30
|
option[:ifile] = argv.shift
|
31
31
|
return CLI.show(parser.help) if option[:ifile].nil?
|
32
|
-
output { SeccompTools::Disasm.disasm(
|
32
|
+
output { SeccompTools::Disasm.disasm(input, arch: option[:arch]) }
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -42,7 +42,7 @@ module SeccompTools
|
|
42
42
|
return unless super
|
43
43
|
option[:ifile] = argv.shift
|
44
44
|
return CLI.show(parser.help) if option[:ifile].nil?
|
45
|
-
raw =
|
45
|
+
raw = input
|
46
46
|
insts = SeccompTools::Disasm.to_bpf(raw, option[:arch]).map(&:inst)
|
47
47
|
disasm = SeccompTools::Disasm.disasm(raw, arch: option[:arch])
|
48
48
|
sys, *args = argv
|
@@ -4,6 +4,20 @@ module SeccompTools
|
|
4
4
|
module Instruction
|
5
5
|
# Instruction alu.
|
6
6
|
class ALU < Base
|
7
|
+
# Mapping from name to operator.
|
8
|
+
OP_SYM = {
|
9
|
+
add: :+,
|
10
|
+
sub: :-,
|
11
|
+
mul: :*,
|
12
|
+
div: :/,
|
13
|
+
or: :|,
|
14
|
+
and: :&,
|
15
|
+
lsh: :<<,
|
16
|
+
rsh: :>>,
|
17
|
+
# neg: :-, # should not be invoked
|
18
|
+
# mod: :%, # unsupported
|
19
|
+
xor: :^
|
20
|
+
}.freeze
|
7
21
|
# Decompile instruction.
|
8
22
|
def decompile
|
9
23
|
return 'A = -A' if op == :neg
|
@@ -36,23 +50,15 @@ module SeccompTools
|
|
36
50
|
end
|
37
51
|
|
38
52
|
def op_sym
|
39
|
-
|
40
|
-
when :add then :+
|
41
|
-
when :sub then :-
|
42
|
-
when :mul then :*
|
43
|
-
when :div then :/
|
44
|
-
when :or then :|
|
45
|
-
when :and then :&
|
46
|
-
when :lsh then :<<
|
47
|
-
when :rsh then :>>
|
48
|
-
# when :neg then :- # should not invoke this method
|
49
|
-
# when :mod then :% # unsupported
|
50
|
-
when :xor then :^
|
51
|
-
end
|
53
|
+
OP_SYM[op]
|
52
54
|
end
|
53
55
|
|
54
56
|
def src_str
|
55
|
-
|
57
|
+
return 'X' if src == :x
|
58
|
+
case op
|
59
|
+
when :lsh, :rsh then src.to_s
|
60
|
+
else '0x' + src.to_s(16)
|
61
|
+
end
|
56
62
|
end
|
57
63
|
|
58
64
|
def src
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: seccomp-tools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- david942j
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: codeclimate-test-reporter
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0.6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pry
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.10'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rake
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -124,7 +138,11 @@ files:
|
|
124
138
|
- ext/ptrace/extconf.rb
|
125
139
|
- ext/ptrace/ptrace.c
|
126
140
|
- lib/seccomp-tools.rb
|
141
|
+
- lib/seccomp-tools/asm/asm.rb
|
142
|
+
- lib/seccomp-tools/asm/compiler.rb
|
143
|
+
- lib/seccomp-tools/asm/tokenizer.rb
|
127
144
|
- lib/seccomp-tools/bpf.rb
|
145
|
+
- lib/seccomp-tools/cli/asm.rb
|
128
146
|
- lib/seccomp-tools/cli/base.rb
|
129
147
|
- lib/seccomp-tools/cli/cli.rb
|
130
148
|
- lib/seccomp-tools/cli/disasm.rb
|
@@ -170,7 +188,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
170
188
|
version: '0'
|
171
189
|
requirements: []
|
172
190
|
rubyforge_project:
|
173
|
-
rubygems_version: 2.
|
191
|
+
rubygems_version: 2.5.2
|
174
192
|
signing_key:
|
175
193
|
specification_version: 4
|
176
194
|
summary: seccomp-tools
|