seccomp-tools 1.4.0 → 1.6.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.
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