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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bd34a83058ba2aee13d7ddc08a6547fc70eac825
4
- data.tar.gz: 0e07f97dfc60b2a21a26a584eadaae86055c3578
3
+ metadata.gz: 52d71ec73ddfa5dab7d22d982b27b2d4c3610080
4
+ data.tar.gz: '08439390a2e4a08d8619eba08b7b66a2814372b1'
5
5
  SHA512:
6
- metadata.gz: 1415f2f48d7a626f3390f0a6705871da51448c92a086c6321bd201e7d5666be1f1691b561d0fbbf91265419905b44052fa1a89d7f59140ff922cba15c737549e
7
- data.tar.gz: c5496c8708fb0f88e8dca0b967bcedd5221bc66ccdc0fd2f93f45d3b76ed8e8446677f5694b1c34d2ce2b4bfa18a12fb56e433187c8ece9d2dc7f9f719e21c5b
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
@@ -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
- io = StringIO.new(raw)
31
- @code = io.read(2).unpack('S').first
32
- @jt = io.read(1).ord
33
- @jf = io.read(1).ord
34
- @k = io.read(4).unpack('L').first
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 'dump' command.
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(IO.binread(option[:ifile]), arch: option[:arch]) }
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 = IO.binread(option[:ifile])
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
@@ -28,7 +28,7 @@ module SeccompTools
28
28
  # Run emulation!
29
29
  # @return [Hash{Symbol, Integer => Integer}]
30
30
  def run
31
- @values = { pc: 0 }
31
+ @values = { pc: 0, a: 0, x: 0 }
32
32
  loop do
33
33
  break if @values[:ret] # break when returned
34
34
  yield(@values) if block_given?
@@ -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
- case op
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
- src == :x ? 'X' : src.to_s
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
@@ -1,4 +1,4 @@
1
1
  module SeccompTools
2
2
  # Gem version.
3
- VERSION = '1.0.0'.freeze
3
+ VERSION = '1.1.0'.freeze
4
4
  end
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.0.0
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-06-10 00:00:00.000000000 Z
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.6.10
191
+ rubygems_version: 2.5.2
174
192
  signing_key:
175
193
  specification_version: 4
176
194
  summary: seccomp-tools