seccomp-tools 1.1.0 → 1.5.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 (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