seccomp-tools 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|