seccomp-tools 1.1.0 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +112 -30
  3. data/bin/seccomp-tools +1 -0
  4. data/ext/ptrace/extconf.rb +2 -0
  5. data/ext/ptrace/ptrace.c +107 -5
  6. data/lib/seccomp-tools.rb +5 -0
  7. data/lib/seccomp-tools/asm/asm.rb +5 -2
  8. data/lib/seccomp-tools/asm/compiler.rb +96 -18
  9. data/lib/seccomp-tools/asm/tokenizer.rb +25 -8
  10. data/lib/seccomp-tools/bpf.rb +7 -4
  11. data/lib/seccomp-tools/cli/asm.rb +16 -6
  12. data/lib/seccomp-tools/cli/base.rb +10 -4
  13. data/lib/seccomp-tools/cli/cli.rb +9 -6
  14. data/lib/seccomp-tools/cli/disasm.rb +6 -2
  15. data/lib/seccomp-tools/cli/dump.rb +37 -6
  16. data/lib/seccomp-tools/cli/emu.rb +41 -22
  17. data/lib/seccomp-tools/const.rb +47 -16
  18. data/lib/seccomp-tools/consts/sys_arg.rb +432 -0
  19. data/lib/seccomp-tools/consts/sys_nr/aarch64.rb +284 -0
  20. data/lib/seccomp-tools/consts/{amd64.rb → sys_nr/amd64.rb} +6 -1
  21. data/lib/seccomp-tools/consts/{i386.rb → sys_nr/i386.rb} +18 -15
  22. data/lib/seccomp-tools/disasm/context.rb +125 -34
  23. data/lib/seccomp-tools/disasm/disasm.rb +5 -2
  24. data/lib/seccomp-tools/dumper.rb +75 -8
  25. data/lib/seccomp-tools/emulator.rb +19 -8
  26. data/lib/seccomp-tools/instruction/alu.rb +7 -2
  27. data/lib/seccomp-tools/instruction/base.rb +5 -3
  28. data/lib/seccomp-tools/instruction/instruction.rb +2 -0
  29. data/lib/seccomp-tools/instruction/jmp.rb +28 -14
  30. data/lib/seccomp-tools/instruction/ld.rb +28 -12
  31. data/lib/seccomp-tools/instruction/ldx.rb +2 -0
  32. data/lib/seccomp-tools/instruction/misc.rb +2 -0
  33. data/lib/seccomp-tools/instruction/ret.rb +14 -2
  34. data/lib/seccomp-tools/instruction/st.rb +4 -2
  35. data/lib/seccomp-tools/instruction/stx.rb +2 -0
  36. data/lib/seccomp-tools/logger.rb +40 -0
  37. data/lib/seccomp-tools/syscall.rb +24 -13
  38. data/lib/seccomp-tools/templates/asm.amd64.asm +26 -0
  39. data/lib/seccomp-tools/templates/asm.c +17 -0
  40. data/lib/seccomp-tools/templates/asm.i386.asm +33 -0
  41. data/lib/seccomp-tools/util.rb +24 -3
  42. data/lib/seccomp-tools/version.rb +3 -1
  43. metadata +51 -44
@@ -1,15 +1,24 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'seccomp-tools/asm/tokenizer'
2
4
  require 'seccomp-tools/bpf'
3
5
  require 'seccomp-tools/const'
4
6
 
5
7
  module SeccompTools
6
8
  module Asm
9
+ # @private
10
+ #
7
11
  # Compile seccomp rules.
8
12
  class Compiler
13
+ # Instantiate a {Compiler} object.
14
+ #
15
+ # @param [Symbol] arch
16
+ # Architecture.
9
17
  def initialize(arch)
10
18
  @arch = arch
11
19
  @insts = []
12
20
  @labels = {}
21
+ @insts_linenum = {}
13
22
  @input = []
14
23
  end
15
24
 
@@ -24,13 +33,17 @@ module SeccompTools
24
33
  line = remove_comment(line)
25
34
  @token = Tokenizer.new(line)
26
35
  return if line.empty?
36
+
27
37
  begin
28
38
  res = case line
29
39
  when /\?/ then cmp
30
40
  when /^#{Tokenizer::LABEL_REGEXP}:/ then define_label
31
41
  when /^return/ then ret
42
+ when /^A\s*=\s*-A/ then alu_neg
32
43
  when /^(A|X)\s*=[^=]/ then assign
33
- when /^A\s*.=/ then alu
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
34
47
  end
35
48
  rescue ArgumentError => e
36
49
  invalid(@input.size - 1, e.message)
@@ -40,11 +53,16 @@ module SeccompTools
40
53
  @labels[res.last] = @insts.size
41
54
  else
42
55
  @insts << res
56
+ @insts_linenum[@insts.size - 1] = @input.size - 1
43
57
  end
44
- res
45
58
  end
46
59
 
60
+ # Compiles the processed instructions.
61
+ #
47
62
  # @return [Array<SeccompTools::BPF>]
63
+ # Returns the compiled {BPF} array.
64
+ # @raise [ArgumentError]
65
+ # Raises the error found during compilation.
48
66
  def compile!
49
67
  @insts.map.with_index do |inst, idx|
50
68
  @line = idx
@@ -53,17 +71,22 @@ module SeccompTools
53
71
  when :alu then compile_alu(inst[1], inst[2])
54
72
  when :ret then compile_ret(inst[1])
55
73
  when :cmp then compile_cmp(inst[1], inst[2], inst[3], inst[4])
74
+ when :jmp_abs then compile_jmp_abs(inst[1])
56
75
  end
57
76
  end
58
77
  rescue ArgumentError => e
59
- invalid(@line, e.message)
78
+ invalid(@insts_linenum[@line], e.message)
60
79
  end
61
80
 
62
81
  private
63
82
 
83
+ # Emits a raw BPF object.
84
+ #
85
+ # @return [BPF]
86
+ # Returns the emitted {BPF} object.
64
87
  def emit(*args, k: 0, jt: 0, jf: 0)
65
88
  code = 0
66
- # bad idea, while keys are not duplicated so this is ok.
89
+ # bad idea, but keys are not duplicated so this is ok.
67
90
  args.each do |a|
68
91
  code |= Const::BPF::COMMAND.fetch(a, 0)
69
92
  code |= Const::BPF::JMP.fetch(a, 0)
@@ -76,27 +99,37 @@ module SeccompTools
76
99
  end
77
100
 
78
101
  # A = X / X = A
79
- # <A|X> = mem[i]
102
+ # mem[i] = <A|X>
80
103
  # <A|X> = 123|sys_const
81
- # A = args[i]|sys_number|arch
104
+ # A = len
105
+ # <A|X> = mem[i]
106
+ # A = args_h[i]|args[i]|sys_number|arch
82
107
  # A = data[4 * i]
83
108
  def compile_assign(dst, src)
84
109
  # misc txa / tax
85
- return emit(:misc, :txa) if dst == :a && src == :x
86
- return emit(:misc, :tax) if dst == :x && src == :a
110
+ return compile_assign_misc(dst, src) if (dst == :a && src == :x) || (dst == :x && src == :a)
111
+ # case of st / stx
112
+ return emit(src == :x ? :stx : :st, k: dst.last) if dst.is_a?(Array) && dst.first == :mem
113
+
87
114
  src = evaluate(src)
88
- # TODO: handle store case.
89
115
  ld = dst == :x ? :ldx : :ld
90
116
  # <A|X> = <immi>
91
117
  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
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
94
120
  # check if num is multiple of 4
95
- raise ArgumentError, 'Index of data[] must be multiplication of 4' if src.last % 4 != 0
121
+ raise ArgumentError, 'Index of data[] must be a multiple of 4' if src.last % 4 != 0
122
+
96
123
  emit(ld, :abs, k: src.last)
97
124
  end
98
125
 
126
+ def compile_assign_misc(dst, _src)
127
+ emit(:misc, dst == :a ? :txa : :tax)
128
+ end
129
+
99
130
  def compile_alu(op, val)
131
+ return emit(:alu, :neg) if op == :neg
132
+
100
133
  val = evaluate(val)
101
134
  src = val == :x ? :x : :k
102
135
  val = 0 if val == :x
@@ -104,9 +137,19 @@ module SeccompTools
104
137
  end
105
138
 
106
139
  def compile_ret(val)
107
- emit(:ret, k: val)
140
+ if val == :a
141
+ src = :a
142
+ val = 0
143
+ end
144
+ emit(:ret, src, k: val)
108
145
  end
109
146
 
147
+ def compile_jmp_abs(target)
148
+ targ = label_offset(target)
149
+ emit(:jmp, :ja, k: targ)
150
+ end
151
+
152
+ # Compiles comparison.
110
153
  def compile_cmp(op, val, jt, jf)
111
154
  jt = label_offset(jt)
112
155
  jf = label_offset(jf)
@@ -120,26 +163,48 @@ module SeccompTools
120
163
  return label if label.is_a?(Integer)
121
164
  return 0 if label == 'next'
122
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
167
+
123
168
  @labels[label] - @line - 1
124
169
  end
125
170
 
126
171
  def evaluate(val)
127
- return val if val.is_a?(Integer) || val == :x
172
+ return val if val.is_a?(Integer) || val == :x || val == :a
173
+
128
174
  # keywords
129
175
  val = case val
130
176
  when 'sys_number' then [:data, 0]
131
177
  when 'arch' then [:data, 4]
178
+ when 'len' then [:len, 0]
132
179
  else val
133
180
  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>]
181
+ return eval_constants(val) if val.is_a?(String)
182
+
183
+ # remains are [:mem/:data/:args/:args_h, <num>]
136
184
  # first convert args to data
137
185
  val = [:data, val.last * 8 + 16] if val.first == :args
186
+ val = [:data, val.last * 8 + 20] if val.first == :args_h
138
187
  val
139
188
  end
140
189
 
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
+
141
196
  attr_reader :token
142
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
+
143
208
  # A <comparison> <sys_str|X|Integer> ? <label|Integer> : <label|Integer>
144
209
  def cmp
145
210
  token.fetch!('A')
@@ -179,6 +244,7 @@ module SeccompTools
179
244
  # A = mem[i]
180
245
  # A = args[i]
181
246
  # A = sys_number|arch
247
+ # A = len
182
248
  def assign
183
249
  dst = token.fetch!(:ax)
184
250
  token.fetch!('=')
@@ -186,10 +252,17 @@ module SeccompTools
186
252
  token.fetch(:sys_num_x) ||
187
253
  token.fetch(:ary) ||
188
254
  token.fetch('sys_number') ||
189
- token.fetch('arch')
255
+ token.fetch('arch') ||
256
+ token.fetch('len') ||
257
+ raise(ArgumentError, "Invalid source: #{token.cur.inspect}")
190
258
  [:assign, dst, src]
191
259
  end
192
260
 
261
+ # returns same format as assign
262
+ def store
263
+ [:assign, token.fetch!(:ary), token.fetch!('=') && token.fetch!(:ax)]
264
+ end
265
+
193
266
  def define_label
194
267
  name = token.fetch!(:goto)
195
268
  token.fetch(':')
@@ -205,6 +278,11 @@ module SeccompTools
205
278
  [:alu, op, src]
206
279
  end
207
280
 
281
+ # A = -A
282
+ def alu_neg
283
+ %i[alu neg]
284
+ end
285
+
208
286
  def remove_comment(line)
209
287
  line = line.to_s.dup
210
288
  line.slice!(/#.*\Z/m)
@@ -213,7 +291,7 @@ module SeccompTools
213
291
 
214
292
  def invalid(line, extra_msg = nil)
215
293
  message = "Invalid instruction at line #{line + 1}: #{@input[line].inspect}"
216
- message += "\n" + 'Error: ' + extra_msg if extra_msg
294
+ message += "\nError: #{extra_msg}" if extra_msg
217
295
  raise ArgumentError, message
218
296
  end
219
297
  end
@@ -1,13 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'seccomp-tools/const'
2
4
  require 'seccomp-tools/instruction/alu'
3
5
 
4
6
  module SeccompTools
5
7
  module Asm
6
- # Fetch tokens from string.
7
- # This class is for internel usage, used by {Compiler}.
8
+ # Fetch tokens from a string.
9
+ #
10
+ # Internal used by {Compiler}.
11
+ # @private
8
12
  class Tokenizer
9
13
  # a valid label
10
- LABEL_REGEXP = /[a-z_][a-z0-9_]+/
14
+ LABEL_REGEXP = /[A-Za-z_]\w*/.freeze
11
15
  attr_accessor :cur
12
16
 
13
17
  # @param [String] str
@@ -40,7 +44,7 @@ module SeccompTools
40
44
  res = case type
41
45
  when String then fetch_str(type) || raise_expected("token #{type.inspect}")
42
46
  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'")
47
+ when :sys_num_x then fetch_sys_num_arch_x || raise_expected("a syscall number or 'X'")
44
48
  when :goto then fetch_number || fetch_label || raise_expected('a number or label name')
45
49
  when :ret then fetch_return || raise(ArgumentError, <<-EOS)
46
50
  Invalid return type: #{cur.inspect}.
@@ -64,6 +68,8 @@ Invalid return type: #{cur.inspect}.
64
68
 
65
69
  def fetch_str(str)
66
70
  return nil unless cur.start_with?(str)
71
+ return nil if str =~ /\A[A-Za-z0-9_]+\Z/ && cur[str.size] =~ /[A-Za-z0-9_]/
72
+
67
73
  @last_match_size = str.size
68
74
  str
69
75
  end
@@ -71,18 +77,21 @@ Invalid return type: #{cur.inspect}.
71
77
  def fetch_ax
72
78
  return :a if fetch_str('A')
73
79
  return :x if fetch_str('X')
80
+
74
81
  nil
75
82
  end
76
83
 
77
- def fetch_sys_num_x
84
+ def fetch_sys_num_arch_x
78
85
  return :x if fetch_str('X')
79
- fetch_number || fetch_syscall
86
+
87
+ fetch_number || fetch_syscall || fetch_arch
80
88
  end
81
89
 
82
90
  # Currently only supports 10-based decimal numbers.
83
91
  def fetch_number
84
92
  res = fetch_regexp(/^0x[0-9a-f]+/) || fetch_regexp(/^[0-9]+/)
85
93
  return nil if res.nil?
94
+
86
95
  Integer(res)
87
96
  end
88
97
 
@@ -92,9 +101,14 @@ Invalid return type: #{cur.inspect}.
92
101
  fetch_strs(sys.keys.map(&:to_s).sort_by(&:size).reverse)
93
102
  end
94
103
 
104
+ def fetch_arch
105
+ fetch_strs(Const::Audit::ARCH.keys)
106
+ end
107
+
95
108
  def fetch_regexp(regexp)
96
109
  idx = cur =~ regexp
97
110
  return nil if idx.nil? || idx != 0
111
+
98
112
  match = cur.match(regexp)[0]
99
113
  @last_match_size = match.size
100
114
  match
@@ -110,6 +124,7 @@ Invalid return type: #{cur.inspect}.
110
124
  regexp = /(#{Const::BPF::ACTION.keys.join('|')})(\([0-9]{1,5}\))?/
111
125
  action = fetch_regexp(regexp)
112
126
  return fetch_str('A') && :a if action.nil?
127
+
113
128
  # check if action contains '('the next bytes are (<num>)
114
129
  ret_val = 0
115
130
  if action.include?('(')
@@ -120,10 +135,11 @@ Invalid return type: #{cur.inspect}.
120
135
  end
121
136
 
122
137
  def fetch_ary
123
- support_name = %w[data mem args]
138
+ support_name = %w[data mem args args_h]
124
139
  regexp = /(#{support_name.join('|')})\[[0-9]{1,2}\]/
125
140
  match = fetch_regexp(regexp)
126
141
  return nil if match.nil?
142
+
127
143
  res, val = match.split('[')
128
144
  val = val.to_i
129
145
  [res.to_sym, val]
@@ -133,6 +149,7 @@ Invalid return type: #{cur.inspect}.
133
149
  ops = %w[+ - * / | & ^ << >>]
134
150
  op = fetch_strs(ops)
135
151
  return nil if op.nil?
152
+
136
153
  Instruction::ALU::OP_SYM.invert[op.to_sym]
137
154
  end
138
155
 
@@ -144,7 +161,7 @@ Invalid return type: #{cur.inspect}.
144
161
 
145
162
  def raise_expected(msg)
146
163
  raise ArgumentError, <<-EOS
147
- Expected #{msg}, while #{cur.split[0].inspect} occured.
164
+ Expected #{msg} but found #{cur.split[0].inspect}.
148
165
  EOS
149
166
  end
150
167
  end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
4
+ require 'stringio'
2
5
 
3
6
  require 'seccomp-tools/const'
4
7
  require 'seccomp-tools/instruction/instruction'
@@ -30,11 +33,11 @@ module SeccompTools
30
33
  # Line number of this filter.
31
34
  def initialize(raw, arch, line)
32
35
  if raw.is_a?(String)
33
- io = StringIO.new(raw)
34
- @code = io.read(2).unpack('S').first
36
+ io = ::StringIO.new(raw)
37
+ @code = io.read(2).unpack1('S')
35
38
  @jt = io.read(1).ord
36
39
  @jf = io.read(1).ord
37
- @k = io.read(4).unpack('L').first
40
+ @k = io.read(4).unpack1('L')
38
41
  else
39
42
  @code = raw[:code]
40
43
  @jt = raw[:jt]
@@ -77,7 +80,7 @@ module SeccompTools
77
80
  # @param [Context] context
78
81
  # Current context.
79
82
  # @yieldparam [Integer] pc
80
- # Program conter after this instruction.
83
+ # Program counter after this instruction.
81
84
  # @yieldparam [Context] ctx
82
85
  # Context after this instruction.
83
86
  # @return [void]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'seccomp-tools/cli/base'
2
4
  require 'seccomp-tools/asm/asm'
3
5
 
@@ -6,9 +8,9 @@ module SeccompTools
6
8
  # Handle 'asm' command.
7
9
  class Asm < Base
8
10
  # Summary of this command.
9
- SUMMARY = 'Seccomp bpf assembler.'.freeze
11
+ SUMMARY = 'Seccomp bpf assembler.'
10
12
  # Usage of this command.
11
- USAGE = ('asm - ' + SUMMARY + "\n\n" + 'Usage: seccomp-tools asm IN_FILE [options]').freeze
13
+ USAGE = "asm - #{SUMMARY}\n\nUsage: seccomp-tools asm IN_FILE [options]"
12
14
 
13
15
  def initialize(*)
14
16
  super
@@ -24,8 +26,8 @@ module SeccompTools
24
26
  option[:ofile] = o
25
27
  end
26
28
 
27
- opt.on('-f', '--format FORMAT', %i[inspect raw carray],
28
- 'Output format. FORMAT can only be one of <inspect|raw|carray>.',
29
+ opt.on('-f', '--format FORMAT', %i[inspect raw c_array carray c_source assembly],
30
+ 'Output format. FORMAT can only be one of <inspect|raw|c_array|c_source|assembly>.',
29
31
  'Default: inspect') do |f|
30
32
  option[:format] = f
31
33
  end
@@ -38,14 +40,22 @@ module SeccompTools
38
40
  # @return [void]
39
41
  def handle
40
42
  return unless super
43
+
41
44
  option[:ifile] = argv.shift
42
45
  return CLI.show(parser.help) if option[:ifile].nil?
46
+
43
47
  res = SeccompTools::Asm.asm(input, arch: option[:arch])
44
48
  output do
45
49
  case option[:format]
46
- when :inspect then res.inspect + "\n"
50
+ when :inspect then "#{res.inspect}\n"
47
51
  when :raw then res
48
- when :carray then "unsigned char bpf[] = {#{res.bytes.join(',')}};\n"
52
+ when :c_array, :carray then "unsigned char bpf[] = {#{res.bytes.join(',')}};\n"
53
+ when :c_source then SeccompTools::Util.template('asm.c').sub('<TO_BE_REPLACED>', res.bytes.join(','))
54
+ when :assembly
55
+ SeccompTools::Util.template("asm.#{option[:arch]}.asm").sub(
56
+ '<TO_BE_REPLACED>',
57
+ res.bytes.map { |b| format('\\\%03o', b) }.join
58
+ )
49
59
  end
50
60
  end
51
61
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'optparse'
2
4
 
3
5
  require 'seccomp-tools/util'
@@ -23,10 +25,12 @@ module SeccompTools
23
25
 
24
26
  # Handle show help message.
25
27
  # @return [Boolean]
26
- # For decestors to check if needs to conitnue.
28
+ # For decestors to check if need to continue.
27
29
  def handle
28
30
  return CLI.show(parser.help) if argv.empty? || %w[-h --help].any? { |h| argv.include?(h) }
31
+
29
32
  parser.parse!(argv)
33
+ option[:arch] ||= Util.system_arch
30
34
  true
31
35
  end
32
36
 
@@ -35,7 +39,7 @@ module SeccompTools
35
39
  # @return [String]
36
40
  # String read from file.
37
41
  def input
38
- option[:ifile] == '-' ? STDIN.read.force_encoding('ascii-8bit') : IO.binread(option[:ifile])
42
+ option[:ifile] == '-' ? $stdin.read.force_encoding('ascii-8bit') : IO.binread(option[:ifile])
39
43
  end
40
44
 
41
45
  # Write data to stdout or file(s).
@@ -45,6 +49,7 @@ module SeccompTools
45
49
  def output
46
50
  # if file name not present, just output to stdout.
47
51
  return $stdout.write(yield) if option[:ofile].nil?
52
+
48
53
  # times of calling output
49
54
  @serial ||= 0
50
55
  # Write to file, we should disable colorize
@@ -77,7 +82,7 @@ module SeccompTools
77
82
  File.join(File.dirname(file), base + suffix) + ext
78
83
  end
79
84
 
80
- # For decestors easy to define usage message.
85
+ # For descendants to define usage message easily.
81
86
  # @return [String]
82
87
  # Usage information.
83
88
  def usage
@@ -87,7 +92,8 @@ module SeccompTools
87
92
  def option_arch(opt)
88
93
  supported = Util.supported_archs
89
94
  opt.on('-a', '--arch ARCH', supported, 'Specify architecture.',
90
- "Supported architectures are <#{supported.join('|')}>.") do |a|
95
+ "Supported architectures are <#{supported.join('|')}>.",
96
+ "Default: #{Util.system_arch}") do |a|
91
97
  option[:arch] = a
92
98
  end
93
99
  end