seccomp-tools 1.4.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +105 -17
  3. data/ext/ptrace/ptrace.c +56 -4
  4. data/lib/seccomp-tools/asm/asm.rb +8 -6
  5. data/lib/seccomp-tools/asm/compiler.rb +130 -224
  6. data/lib/seccomp-tools/asm/sasm.tab.rb +780 -0
  7. data/lib/seccomp-tools/asm/sasm.y +175 -0
  8. data/lib/seccomp-tools/asm/scalar.rb +129 -0
  9. data/lib/seccomp-tools/asm/scanner.rb +163 -0
  10. data/lib/seccomp-tools/asm/statement.rb +32 -0
  11. data/lib/seccomp-tools/asm/token.rb +29 -0
  12. data/lib/seccomp-tools/bpf.rb +31 -7
  13. data/lib/seccomp-tools/cli/asm.rb +3 -3
  14. data/lib/seccomp-tools/cli/base.rb +4 -4
  15. data/lib/seccomp-tools/cli/disasm.rb +27 -3
  16. data/lib/seccomp-tools/cli/dump.rb +4 -2
  17. data/lib/seccomp-tools/cli/emu.rb +1 -4
  18. data/lib/seccomp-tools/const.rb +37 -3
  19. data/lib/seccomp-tools/consts/sys_nr/aarch64.rb +284 -0
  20. data/lib/seccomp-tools/consts/sys_nr/amd64.rb +5 -1
  21. data/lib/seccomp-tools/consts/sys_nr/i386.rb +14 -14
  22. data/lib/seccomp-tools/consts/sys_nr/s390x.rb +365 -0
  23. data/lib/seccomp-tools/disasm/context.rb +2 -2
  24. data/lib/seccomp-tools/disasm/disasm.rb +16 -9
  25. data/lib/seccomp-tools/dumper.rb +12 -3
  26. data/lib/seccomp-tools/emulator.rb +5 -9
  27. data/lib/seccomp-tools/error.rb +31 -0
  28. data/lib/seccomp-tools/instruction/alu.rb +1 -1
  29. data/lib/seccomp-tools/instruction/base.rb +1 -1
  30. data/lib/seccomp-tools/instruction/jmp.rb +28 -10
  31. data/lib/seccomp-tools/instruction/ld.rb +5 -3
  32. data/lib/seccomp-tools/syscall.rb +23 -13
  33. data/lib/seccomp-tools/templates/asm.s390x.asm +26 -0
  34. data/lib/seccomp-tools/util.rb +3 -1
  35. data/lib/seccomp-tools/version.rb +1 -1
  36. metadata +38 -9
  37. data/lib/seccomp-tools/asm/tokenizer.rb +0 -169
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'seccomp-tools/asm/tokenizer'
3
+ require 'seccomp-tools/asm/sasm.tab'
4
+ require 'seccomp-tools/asm/scalar'
5
+ require 'seccomp-tools/asm/scanner'
4
6
  require 'seccomp-tools/bpf'
5
- require 'seccomp-tools/const'
7
+ require 'seccomp-tools/error'
6
8
 
7
9
  module SeccompTools
8
10
  module Asm
@@ -12,74 +14,99 @@ module SeccompTools
12
14
  class Compiler
13
15
  # Instantiate a {Compiler} object.
14
16
  #
17
+ # @param [String] source
18
+ # Input string.
19
+ # @param [String?] filename
20
+ # Only used in error messages.
15
21
  # @param [Symbol] arch
16
22
  # Architecture.
17
- def initialize(arch)
23
+ def initialize(source, filename, arch)
24
+ @scanner = Scanner.new(source, arch, filename: filename)
18
25
  @arch = arch
19
- @insts = []
20
- @labels = {}
21
- @insts_linenum = {}
22
- @input = []
23
- end
24
-
25
- # Before compile assembly codes, process each lines.
26
- #
27
- # With this we can support label in seccomp rules.
28
- # @param [String] line
29
- # One line of seccomp rule.
30
- # @return [void]
31
- def process(line)
32
- @input << line.strip
33
- line = remove_comment(line)
34
- @token = Tokenizer.new(line)
35
- return if line.empty?
36
-
37
- begin
38
- res = case line
39
- when /\?/ then cmp
40
- when /^#{Tokenizer::LABEL_REGEXP}:/ then define_label
41
- when /^return/ then ret
42
- when /^A\s*=\s*-A/ then alu_neg
43
- when /^(A|X)\s*=[^=]/ then assign
44
- when /^mem\[\d+\]\s*=\s*(A|X)/ then store
45
- when /^A\s*.{1,2}=/ then alu
46
- when /^(goto|jmp|jump)/ then jmp_abs
47
- end
48
- rescue ArgumentError => e
49
- invalid(@input.size - 1, e.message)
50
- end
51
- invalid(@input.size - 1) if res.nil?
52
- if res.first == :label
53
- @labels[res.last] = @insts.size
54
- else
55
- @insts << res
56
- @insts_linenum[@insts.size - 1] = @input.size - 1
57
- end
26
+ @symbols = {}
58
27
  end
59
28
 
60
29
  # Compiles the processed instructions.
61
30
  #
62
31
  # @return [Array<SeccompTools::BPF>]
63
32
  # Returns the compiled {BPF} array.
64
- # @raise [ArgumentError]
33
+ # @raise [SeccompTools::Error]
65
34
  # Raises the error found during compilation.
66
35
  def compile!
67
- @insts.map.with_index do |inst, idx|
36
+ @scanner.validate!
37
+ statements = SeccompAsmParser.new(@scanner).parse
38
+ fixup_symbols(statements)
39
+ resolve_symbols(statements)
40
+
41
+ statements.map.with_index do |s, idx|
68
42
  @line = idx
69
- case inst.first
70
- when :assign then compile_assign(inst[1], inst[2])
71
- when :alu then compile_alu(inst[1], inst[2])
72
- when :ret then compile_ret(inst[1])
73
- when :cmp then compile_cmp(inst[1], inst[2], inst[3], inst[4])
74
- when :jmp_abs then compile_jmp_abs(inst[1])
43
+ case s.type
44
+ when :alu then emit_alu(*s.data)
45
+ when :assign then emit_assign(*s.data)
46
+ when :if then emit_cmp(*s.data)
47
+ when :ret then emit_ret(*s.data)
75
48
  end
76
49
  end
77
- rescue ArgumentError => e
78
- invalid(@insts_linenum[@line], e.message)
79
50
  end
80
51
 
81
52
  private
82
53
 
54
+ def fixup_symbols(statements)
55
+ statements.each_with_index do |statement, idx|
56
+ statement.symbols.uniq(&:str).each do |s|
57
+ if @symbols[s.str]
58
+ msg = @scanner.format_error(s, "duplicate label '#{s.str}'")
59
+ msg += @scanner.format_error(@symbols[s.str][0], 'previously defined here')
60
+ raise SeccompTools::DuplicateLabelError, msg
61
+ end
62
+
63
+ @symbols[s.str] = [s, idx]
64
+ end
65
+ end
66
+ end
67
+
68
+ def resolve_symbols(statements)
69
+ statements.each_with_index do |statement, idx|
70
+ next if statement.type != :if
71
+
72
+ jt = resolve_symbol(idx, statement.data[1])
73
+ jf = resolve_symbol(idx, statement.data[2])
74
+ statement.data[1] = jt
75
+ statement.data[2] = jf
76
+ end
77
+ end
78
+
79
+ # The farthest distance of a relative jump in BPF.
80
+ JUMP_DISTANCE_MAX = 255
81
+
82
+ # @param [Integer] index
83
+ # @param [SeccompTools::Asm::Token, :next] sym
84
+ def resolve_symbol(index, sym)
85
+ return 0 if sym.is_a?(Symbol) && sym == :next
86
+
87
+ str = sym.str
88
+ return 0 if str == 'next'
89
+
90
+ if @symbols[str].nil?
91
+ # special case - goto <n> can be considered as $+1+<n>
92
+ return str.to_i if str == str.to_i.to_s && str.to_i <= JUMP_DISTANCE_MAX
93
+
94
+ raise SeccompTools::UndefinedLabelError,
95
+ @scanner.format_error(sym, "Cannot find label '#{str}'")
96
+ end
97
+
98
+ (@symbols[str][1] - index - 1).tap do |dis|
99
+ if dis.negative?
100
+ raise SeccompTools::BackwardJumpError,
101
+ @scanner.format_error(sym, "Does not support backward jumping to '#{str}'")
102
+ end
103
+ if dis > JUMP_DISTANCE_MAX
104
+ raise SeccompTools::LongJumpError,
105
+ @scanner.format_error(sym, "Does not support jumping farther than #{JUMP_DISTANCE_MAX}, got: #{dis}")
106
+ end
107
+ end
108
+ end
109
+
83
110
  # Emits a raw BPF object.
84
111
  #
85
112
  # @return [BPF]
@@ -98,201 +125,80 @@ module SeccompTools
98
125
  BPF.new({ code: code, k: k, jt: jt, jf: jf }, @arch, @line)
99
126
  end
100
127
 
128
+ # A = -A
101
129
  # A = X / X = A
102
130
  # mem[i] = <A|X>
103
- # <A|X> = 123|sys_const
104
131
  # A = len
105
- # <A|X> = mem[i]
106
- # A = args_h[i]|args[i]|sys_number|arch
107
- # A = data[4 * i]
108
- def compile_assign(dst, src)
132
+ # <A|X> = <immi|mem[i]|data[i]>
133
+ def emit_assign(dst, src)
134
+ return emit(:alu, :neg) if src.is_a?(Symbol) && src == :neg
109
135
  # misc txa / tax
110
- return compile_assign_misc(dst, src) if (dst == :a && src == :x) || (dst == :x && src == :a)
136
+ return emit(:misc, dst.a? ? :txa : :tax) if (dst.a? && src.x?) || (dst.x? && src.a?)
111
137
  # case of st / stx
112
- return emit(src == :x ? :stx : :st, k: dst.last) if dst.is_a?(Array) && dst.first == :mem
113
-
114
- src = evaluate(src)
115
- ld = dst == :x ? :ldx : :ld
116
- # <A|X> = <immi>
117
- return emit(ld, :imm, k: src) if src.is_a?(Integer)
118
- # now src must be in form [:len/:mem/:data, num]
119
- return emit(ld, src.first, k: src.last) if src.first == :mem || src.first == :len
120
- # check if num is multiple of 4
121
- raise ArgumentError, 'Index of data[] must be a multiple of 4' if src.last % 4 != 0
138
+ return emit(src.x? ? :stx : :st, k: dst.val) if dst.mem?
122
139
 
123
- emit(ld, :abs, k: src.last)
140
+ emit_ld(dst, src)
124
141
  end
125
142
 
126
- def compile_assign_misc(dst, _src)
127
- emit(:misc, dst == :a ? :txa : :tax)
143
+ def emit_ld(dst, src)
144
+ ld = dst.x? ? :ldx : :ld
145
+ return emit(ld, :len, k: 0) if src.len?
146
+ return emit(ld, :imm, k: src.to_i) if src.const?
147
+ return emit(ld, :mem, k: src.val) if src.mem?
148
+
149
+ emit(ld, :abs, k: src.val) if src.data?
128
150
  end
129
151
 
130
- def compile_alu(op, val)
131
- return emit(:alu, :neg) if op == :neg
152
+ def emit_alu(op, val)
153
+ src, k = val.x? ? [:x, 0] : [:k, val.to_i]
154
+ emit(:alu, convert_alu_op(op), src, k: k)
155
+ end
132
156
 
133
- val = evaluate(val)
134
- src = val == :x ? :x : :k
135
- val = 0 if val == :x
136
- emit(:alu, op, src, k: val)
157
+ def convert_alu_op(op)
158
+ {
159
+ '+' => :add,
160
+ '-' => :sub,
161
+ '*' => :mul,
162
+ '/' => :div,
163
+ '|' => :or,
164
+ '&' => :and,
165
+ '<<' => :lsh,
166
+ '>>' => :rsh,
167
+ '^' => :xor
168
+ }[op[0..-2]]
137
169
  end
138
170
 
139
- def compile_ret(val)
140
- if val == :a
171
+ def emit_ret(val)
172
+ if val.a?
141
173
  src = :a
142
174
  val = 0
143
175
  end
144
- emit(:ret, src, k: val)
145
- end
146
-
147
- def compile_jmp_abs(target)
148
- targ = label_offset(target)
149
- emit(:jmp, :ja, k: targ)
150
- end
151
-
152
- # Compiles comparison.
153
- def compile_cmp(op, val, jt, jf)
154
- jt = label_offset(jt)
155
- jf = label_offset(jf)
156
- val = evaluate(val)
157
- src = val == :x ? :x : :k
158
- val = 0 if val == :x
159
- emit(:jmp, op, src, jt: jt, jf: jf, k: val)
176
+ emit(:ret, src, k: val.to_i)
160
177
  end
161
178
 
162
- def label_offset(label)
163
- return label if label.is_a?(Integer)
164
- return 0 if label == 'next'
165
- raise ArgumentError, "Undefined label #{label.inspect}" if @labels[label].nil?
166
- raise ArgumentError, "Does not support backward jumping to #{label.inspect}" if @labels[label] < @line
179
+ def emit_cmp(cmp, jt, jf)
180
+ jop, jt, jf = convert_jmp_op(cmp, jt, jf)
181
+ return emit(:jmp, jop, 0, jt: 0, jf: 0, k: jt) if jop == :ja || jt == jf
167
182
 
168
- @labels[label] - @line - 1
183
+ val = cmp[1]
184
+ src = val.x? ? :x : :k
185
+ k = val.x? ? 0 : val.to_i
186
+ emit(:jmp, jop, src, jt: jt, jf: jf, k: k)
169
187
  end
170
188
 
171
- def evaluate(val)
172
- return val if val.is_a?(Integer) || val == :x || val == :a
173
-
174
- # keywords
175
- val = case val
176
- when 'sys_number' then [:data, 0]
177
- when 'arch' then [:data, 4]
178
- when 'len' then [:len, 0]
179
- else val
180
- end
181
- return eval_constants(val) if val.is_a?(String)
182
-
183
- # remains are [:mem/:data/:args/:args_h, <num>]
184
- # first convert args to data
185
- val = [:data, val.last * 8 + 16] if val.first == :args
186
- val = [:data, val.last * 8 + 20] if val.first == :args_h
187
- val
188
- end
189
+ # == != >= <= > < &
190
+ def convert_jmp_op(cmp, jt, jf)
191
+ return [:ja, jt, jf] if cmp.nil?
189
192
 
190
- def eval_constants(str)
191
- Const::Syscall.const_get(@arch.upcase.to_sym)[str.to_sym] ||
192
- Const::Audit::ARCH[str] ||
193
- raise(ArgumentError, "Invalid constant: #{str.inspect}")
194
- end
195
-
196
- attr_reader :token
197
-
198
- # <goto|jmp|jump> <label|Integer>
199
- def jmp_abs
200
- token.fetch('goto') ||
201
- token.fetch('jmp') ||
202
- token.fetch('jump') ||
203
- raise(ArgumentError, 'Invalid jump alias: ' + token.cur.inspect)
204
- target = token.fetch!(:goto)
205
- [:jmp_abs, target]
206
- end
207
-
208
- # A <comparison> <sys_str|X|Integer> ? <label|Integer> : <label|Integer>
209
- def cmp
210
- token.fetch!('A')
211
- op = token.fetch!(:comparison)
212
- dst = token.fetch!(:sys_num_x)
213
- token.fetch!('?')
214
- jt = token.fetch!(:goto)
215
- token.fetch!(':')
216
- jf = token.fetch!(:goto)
217
- convert = {
218
- :< => :>=,
219
- :<= => :>,
220
- :!= => :==
221
- }
222
- if convert[op]
223
- op = convert[op]
224
- jt, jf = jf, jt
193
+ case cmp[0]
194
+ when '==' then [:jeq, jt, jf]
195
+ when '!=' then [:jeq, jf, jt]
196
+ when '>=' then [:jge, jt, jf]
197
+ when '<=' then [:jgt, jf, jt]
198
+ when '>' then [:jgt, jt, jf]
199
+ when '<' then [:jge, jf, jt]
200
+ when '&' then [:jset, jt, jf]
225
201
  end
226
- op = {
227
- :>= => :jge,
228
- :> => :jgt,
229
- :== => :jeq
230
- }[op]
231
- [:cmp, op, dst, jt, jf]
232
- end
233
-
234
- def ret
235
- token.fetch!('return')
236
- [:ret, token.fetch!(:ret)]
237
- end
238
-
239
- # possible types after '=':
240
- # A = X
241
- # X = A
242
- # A = 123
243
- # A = data[i]
244
- # A = mem[i]
245
- # A = args[i]
246
- # A = sys_number|arch
247
- # A = len
248
- def assign
249
- dst = token.fetch!(:ax)
250
- token.fetch!('=')
251
- src = token.fetch(:ax) ||
252
- token.fetch(:sys_num_x) ||
253
- token.fetch(:ary) ||
254
- token.fetch('sys_number') ||
255
- token.fetch('arch') ||
256
- token.fetch('len') ||
257
- raise(ArgumentError, 'Invalid source: ' + token.cur.inspect)
258
- [:assign, dst, src]
259
- end
260
-
261
- # returns same format as assign
262
- def store
263
- [:assign, token.fetch!(:ary), token.fetch!('=') && token.fetch!(:ax)]
264
- end
265
-
266
- def define_label
267
- name = token.fetch!(:goto)
268
- token.fetch(':')
269
- [:label, name]
270
- end
271
-
272
- # A op= sys_num_x
273
- def alu
274
- token.fetch!('A')
275
- op = token.fetch!(:alu_op)
276
- token.fetch!('=')
277
- src = token.fetch!(:sys_num_x)
278
- [:alu, op, src]
279
- end
280
-
281
- # A = -A
282
- def alu_neg
283
- %i[alu neg]
284
- end
285
-
286
- def remove_comment(line)
287
- line = line.to_s.dup
288
- line.slice!(/#.*\Z/m)
289
- line.strip
290
- end
291
-
292
- def invalid(line, extra_msg = nil)
293
- message = "Invalid instruction at line #{line + 1}: #{@input[line].inspect}"
294
- message += "\n" + 'Error: ' + extra_msg if extra_msg
295
- raise ArgumentError, message
296
202
  end
297
203
  end
298
204
  end