seccomp-tools 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +84 -17
  3. data/bin/seccomp-tools +1 -0
  4. data/ext/ptrace/ptrace.c +8 -1
  5. data/lib/seccomp-tools.rb +2 -0
  6. data/lib/seccomp-tools/asm/asm.rb +4 -1
  7. data/lib/seccomp-tools/asm/compiler.rb +61 -10
  8. data/lib/seccomp-tools/asm/tokenizer.rb +15 -3
  9. data/lib/seccomp-tools/bpf.rb +2 -0
  10. data/lib/seccomp-tools/cli/asm.rb +14 -4
  11. data/lib/seccomp-tools/cli/base.rb +5 -0
  12. data/lib/seccomp-tools/cli/cli.rb +6 -3
  13. data/lib/seccomp-tools/cli/disasm.rb +5 -1
  14. data/lib/seccomp-tools/cli/dump.rb +4 -1
  15. data/lib/seccomp-tools/cli/emu.rb +15 -2
  16. data/lib/seccomp-tools/const.rb +25 -19
  17. data/lib/seccomp-tools/consts/sys_arg.rb +432 -0
  18. data/lib/seccomp-tools/consts/{amd64.rb → sys_nr/amd64.rb} +4 -2
  19. data/lib/seccomp-tools/consts/{i386.rb → sys_nr/i386.rb} +5 -2
  20. data/lib/seccomp-tools/disasm/context.rb +125 -34
  21. data/lib/seccomp-tools/disasm/disasm.rb +4 -2
  22. data/lib/seccomp-tools/dumper.rb +4 -0
  23. data/lib/seccomp-tools/emulator.rb +10 -0
  24. data/lib/seccomp-tools/instruction/alu.rb +6 -1
  25. data/lib/seccomp-tools/instruction/base.rb +4 -2
  26. data/lib/seccomp-tools/instruction/instruction.rb +2 -0
  27. data/lib/seccomp-tools/instruction/jmp.rb +12 -2
  28. data/lib/seccomp-tools/instruction/ld.rb +27 -11
  29. data/lib/seccomp-tools/instruction/ldx.rb +2 -0
  30. data/lib/seccomp-tools/instruction/misc.rb +2 -0
  31. data/lib/seccomp-tools/instruction/ret.rb +3 -0
  32. data/lib/seccomp-tools/instruction/st.rb +3 -1
  33. data/lib/seccomp-tools/instruction/stx.rb +2 -0
  34. data/lib/seccomp-tools/syscall.rb +5 -1
  35. data/lib/seccomp-tools/templates/asm.amd64.asm +26 -0
  36. data/lib/seccomp-tools/templates/asm.c +17 -0
  37. data/lib/seccomp-tools/templates/asm.i386.asm +33 -0
  38. data/lib/seccomp-tools/util.rb +16 -1
  39. data/lib/seccomp-tools/version.rb +3 -1
  40. metadata +18 -11
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  {
2
4
  read: 0,
3
5
  write: 1,
@@ -16,8 +18,8 @@
16
18
  rt_sigprocmask: 14,
17
19
  rt_sigreturn: 15,
18
20
  ioctl: 16,
19
- pread: 17,
20
- pwrite: 18,
21
+ pread64: 17,
22
+ pwrite64: 18,
21
23
  readv: 19,
22
24
  writev: 20,
23
25
  access: 21,
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  {
4
+ setup: 0,
2
5
  exit: 1,
3
6
  fork: 2,
4
7
  read: 3,
@@ -178,8 +181,8 @@
178
181
  rt_sigtimedwait: 177,
179
182
  rt_sigqueueinfo: 178,
180
183
  rt_sigsuspend: 179,
181
- pread: 180,
182
- pwrite: 181,
184
+ pread64: 180,
185
+ pwrite64: 181,
183
186
  chown: 182,
184
187
  getcwd: 183,
185
188
  capget: 184,
@@ -1,79 +1,170 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module SeccompTools
2
4
  module Disasm
5
+ # @private
6
+ #
3
7
  # Context for disassembler to analyze.
4
8
  #
5
- # This context only care if +reg/mem+ can be one of +data[*]+.
9
+ # This class maintains:
10
+ # * if +reg/mem+ can be one of +data[*]+
11
+ # * if +data[0]+ (i.e. sys_number) is a known value
6
12
  class Context
7
- # @return [Hash{Integer, Symbol => Integer?}] Records reg and mem values.
13
+ # @private
14
+ #
15
+ # Records the type and value.
16
+ class Value
17
+ attr_reader :val # @return [Integer]
18
+
19
+ # @param [:imm, :data, :mem] rel
20
+ # @param [Integer?] val
21
+ def initialize(rel: :imm, val: nil)
22
+ @rel = rel
23
+ @val = val
24
+ end
25
+
26
+ # @return [Boolean]
27
+ def data?
28
+ @rel == :data
29
+ end
30
+
31
+ # @return [Boolean]
32
+ def imm?
33
+ @rel == :imm && @val.is_a?(Integer)
34
+ end
35
+
36
+ # Defines hash function.
37
+ # @return [Integer]
38
+ def hash
39
+ @rel.hash ^ @val.hash
40
+ end
41
+
42
+ # Defines +eql?+.
43
+ #
44
+ # @param [Context::Value] other
45
+ # @return [Boolean]
46
+ def eql?(other)
47
+ @val == other.val && @rel == other.instance_variable_get(:@rel)
48
+ end
49
+ end
50
+
51
+ # @return [{Integer, Symbol => Context::Value}] Records reg and mem values.
8
52
  attr_reader :values
53
+ # @return [Array<Integer?>] Records the known value of data.
54
+ attr_reader :known_data
9
55
 
10
56
  # Instantiate a {Context} object.
11
- # @param [Integer?] a
12
- # Value to be set to +A+ register.
13
- # @param [Integer?] x
14
- # Value to be set to +X+ register.
15
- # @param [Hash{Integer => Integer?}] mem
16
- # Value to be set to +mem+.
17
- def initialize(a: nil, x: nil, mem: {})
18
- @values = mem
19
- 16.times { |i| @values[i] ||= nil } # make @values always has all keys
20
- @values[:a] = a
21
- @values[:x] = x
57
+ # @param [{Integer, Symbol => Context::Value?}] values
58
+ # Value to be set to +reg/mem+.
59
+ # @param [Array<Integer?>] known_data
60
+ # Records which index of data is known.
61
+ # It's used for tracking if the syscall number is known, which can be used to display argument names of the
62
+ # syscall.
63
+ def initialize(values: {}, known_data: [])
64
+ @values = values
65
+ 16.times { |i| @values[i] ||= Value.new(rel: :mem, val: i) } # make @values always has all keys
66
+ @values[:a] ||= Value.new
67
+ @values[:x] ||= Value.new
68
+ @known_data = known_data
22
69
  end
23
70
 
24
- # Implement a deep dup.
71
+ # Is used for the ld/ldx instructions.
72
+ #
73
+ # @param [#downcase, :a, :x] reg
74
+ # Register to be set
75
+ # @return [void]
76
+ def load(reg, rel: nil, val: nil)
77
+ reg = reg.downcase.to_sym
78
+ values[reg] = if rel == :mem
79
+ values[val]
80
+ else
81
+ Value.new(rel: rel, val: val)
82
+ end
83
+ end
84
+
85
+ # Is used for the st/stx instructions.
86
+ #
87
+ # @param [Integer] idx
88
+ # Index of +mem+ array.
89
+ # @param [#downcase, :a, :x] reg
90
+ # Register.
91
+ #
92
+ # @return [void]
93
+ def store(idx, reg)
94
+ raise RangeError, "Expect 0 <= idx < 16, got #{idx}." unless idx.between?(0, 15)
95
+
96
+ values[idx] = values[reg.downcase.to_sym]
97
+ end
98
+
99
+ # Hints context that current value of register A equals to +val+.
100
+ #
101
+ # @param [Integer, :x] val
102
+ # An immediate value or the symbol x.
103
+ # @return [self]
104
+ # Returns the object itself.
105
+ def eql!(val)
106
+ tap do
107
+ # only cares if A is fetched from data
108
+ next unless a.data?
109
+ next known_data[a.val] = val if val.is_a?(Integer)
110
+ # A == X, we can handle these cases:
111
+ # * X is an immi
112
+ # * X is a known data
113
+ next unless x.data? || x.imm?
114
+ next known_data[a.val] = x.val if x.imm?
115
+
116
+ known_data[a.val] = known_data[x.val]
117
+ end
118
+ end
119
+
120
+ # Implements a deep dup.
25
121
  # @return [Context]
26
122
  def dup
27
- Context.new(a: a, x: x, mem: values.dup)
123
+ Context.new(values: values.dup, known_data: known_data.dup)
28
124
  end
29
125
 
30
126
  # Register A.
31
- # @return [Integer?]
127
+ # @return [Context::Value]
32
128
  def a
33
129
  values[:a]
34
130
  end
35
131
 
36
132
  # Register X.
37
- # @return [Integer?]
133
+ # @return [Context::Value]
38
134
  def x
39
135
  values[:x]
40
136
  end
41
137
 
42
138
  # For conveniently get instance variable.
43
139
  # @param [String, Symbol, Integer] key
44
- # @return [Integer?]
140
+ # @return [Context::Value]
45
141
  def [](key)
46
142
  return values[key] if key.is_a?(Integer) # mem
143
+
47
144
  values[key.downcase.to_sym]
48
145
  end
49
146
 
50
- # For conveniently set instance variable.
51
- # @param [#downcase, Integer] key
52
- # Can be +'A', 'a', :a, 'X', 'x', :x+ or an integer.
53
- # @param [Integer?] val
147
+ # For conveniently set an instance variable.
148
+ # @param [#downcase, :a, :x] reg
149
+ # Can be +'A', 'a', :a, 'X', 'x', :x+.
150
+ # @param [Value] val
54
151
  # Value to set.
55
152
  # @return [void]
56
- def []=(key, val)
57
- if key.is_a?(Integer)
58
- raise RangeError, "Expect 0 <= key < 16, got #{key}." unless key.between?(0, 15)
59
- raise RangeError, "Expect 0 <= val < 64, got #{val}." unless val.nil? || val.between?(0, 63)
60
- values[key] = val
61
- else
62
- values[key.downcase.to_sym] = val
63
- end
153
+ def []=(reg, val)
154
+ values[reg.downcase.to_sym] = val
64
155
  end
65
156
 
66
- # For +Set+ to compare two {Context} object.
157
+ # For +Set+ to compare two {Context} objects.
67
158
  # @param [Context] other
68
159
  # @return [Boolean]
69
160
  def eql?(other)
70
- values.eql?(other.values)
161
+ values.eql?(other.values) && known_data.eql?(other.known_data)
71
162
  end
72
163
 
73
- # For +Set+ to get hash key.
164
+ # For +Set+ to get the hash value.
74
165
  # @return [Integer]
75
166
  def hash
76
- values.hash
167
+ values.hash ^ known_data.hash
77
168
  end
78
169
  end
79
170
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  require 'seccomp-tools/bpf'
@@ -28,10 +30,10 @@ module SeccompTools
28
30
  code.contexts = ctxs
29
31
  code.disasm
30
32
  end.join("\n")
31
- <<EOS + dis + "\n"
33
+ <<-EOS + dis + "\n"
32
34
  line CODE JT JF K
33
35
  =================================
34
- EOS
36
+ EOS
35
37
  end
36
38
 
37
39
  # Convert raw bpf string to array of {BPF}.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'seccomp-tools/ptrace'
2
4
  require 'seccomp-tools/syscall'
3
5
 
@@ -54,6 +56,8 @@ module SeccompTools
54
56
  # Child will be killed when number of calling +prctl(SET_SECCOMP)+ reaches +limit+.
55
57
  # @yieldparam [String] bpf
56
58
  # Seccomp bpf in raw bytes.
59
+ # @yieldparam [Symbol] arch
60
+ # Architecture, either :i386 or :amd64.
57
61
  # @return [Array<Object>, Array<String>]
58
62
  # Return the block returned. If block is not given, array of raw bytes will be returned.
59
63
  def handle(limit, &block)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'seccomp-tools/const'
2
4
 
3
5
  module SeccompTools
@@ -31,6 +33,7 @@ module SeccompTools
31
33
  @values = { pc: 0, a: 0, x: 0 }
32
34
  loop do
33
35
  break if @values[:ret] # break when returned
36
+
34
37
  yield(@values) if block_given?
35
38
  inst = @instructions[pc]
36
39
  op, *args = inst.symbolize
@@ -79,6 +82,7 @@ module SeccompTools
79
82
 
80
83
  def st(reg, index)
81
84
  raise IndexError, "Expect 0 <= index < 16, got: #{index}" unless index.between?(0, 15)
85
+
82
86
  set(:mem, index, get(reg))
83
87
  end
84
88
 
@@ -86,6 +90,7 @@ module SeccompTools
86
90
  set(:pc, get(:pc) + k + 1)
87
91
  end
88
92
 
93
+ # Emulates cmp instruction.
89
94
  def cmp(op, src, jt, jf)
90
95
  src = get(:x) if src == :x
91
96
  a = get(:a)
@@ -115,10 +120,12 @@ module SeccompTools
115
120
  if arg.size == 1
116
121
  arg = arg.first
117
122
  raise ArgumentError, "Invalid #{arg}" unless %i[a x pc ret].include?(arg)
123
+
118
124
  @values[arg] = val & 0xffffffff
119
125
  else
120
126
  raise ArgumentError, arg.to_s unless arg.first == :mem
121
127
  raise IndexError, "Invalid index: #{arg[1]}" unless arg[1].between?(0, 15)
128
+
122
129
  @values[arg[1]] = val & 0xffffffff
123
130
  end
124
131
  end
@@ -127,15 +134,18 @@ module SeccompTools
127
134
  if arg.size == 1
128
135
  arg = arg.first
129
136
  raise ArgumentError, "Invalid #{arg}" unless %i[a x pc ret].include?(arg)
137
+
130
138
  undefined(arg.upcase) if @values[arg].nil?
131
139
  return @values[arg]
132
140
  end
133
141
  return @values[arg[1]] if arg.first == :mem
142
+
134
143
  data_of(arg[1])
135
144
  end
136
145
 
137
146
  def data_of(index)
138
147
  raise IndexError, "Invalid index: #{index}" unless (index & 3).zero? && index.between?(0, 63)
148
+
139
149
  index /= 4
140
150
  case index
141
151
  when 0 then @sys_nr || undefined('sys_number')
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'seccomp-tools/instruction/base'
2
4
 
3
5
  module SeccompTools
@@ -21,6 +23,7 @@ module SeccompTools
21
23
  # Decompile instruction.
22
24
  def decompile
23
25
  return 'A = -A' if op == :neg
26
+
24
27
  "A #{op_sym}= #{src_str}"
25
28
  end
26
29
 
@@ -28,6 +31,7 @@ module SeccompTools
28
31
  # @return [[:alu, Symbol, (:x, Integer, nil)]]
29
32
  def symbolize
30
33
  return [:alu, :neg, nil] if op == :neg
34
+
31
35
  [:alu, op_sym, src]
32
36
  end
33
37
 
@@ -37,7 +41,7 @@ module SeccompTools
37
41
  # @return [Array<(Integer, Context)>]
38
42
  def branch(context)
39
43
  ctx = context.dup
40
- ctx[:a] = nil
44
+ ctx[:a] = Disasm::Context::Value.new
41
45
  [[line + 1, ctx]]
42
46
  end
43
47
 
@@ -55,6 +59,7 @@ module SeccompTools
55
59
 
56
60
  def src_str
57
61
  return 'X' if src == :x
62
+
58
63
  case op
59
64
  when :lsh, :rsh then src.to_s
60
65
  else '0x' + src.to_s(16)
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'seccomp-tools/const'
2
4
 
3
5
  module SeccompTools
4
6
  # For instructions' class.
5
7
  module Instruction
6
- # Base class for different instruction.
8
+ # Base class of instructions.
7
9
  class Base
8
10
  include SeccompTools::Const::BPF
9
11
 
@@ -22,7 +24,7 @@ module SeccompTools
22
24
  raise ArgumentError, "Line #{line} is invalid: #{msg}"
23
25
  end
24
26
 
25
- # Return the possible branches after executing this instruction.
27
+ # Returns the possible branches after executing this instruction.
26
28
  # @param [Context] _context
27
29
  # Current context.
28
30
  # @return [Array<(Integer, Context)>]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'seccomp-tools/instruction/alu'
2
4
  require 'seccomp-tools/instruction/jmp'
3
5
  require 'seccomp-tools/instruction/ld'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'seccomp-tools/const'
2
4
  require 'seccomp-tools/instruction/base'
3
5
 
@@ -15,6 +17,7 @@ module SeccompTools
15
17
  return '/* no-op */' if jt.zero? && jf.zero?
16
18
  return goto(jt) if jt == jf
17
19
  return if_str(true) + goto(jf) if jt.zero?
20
+
18
21
  if_str + goto(jt) + (jf.zero? ? '' : ' else ' + goto(jf))
19
22
  end
20
23
 
@@ -22,6 +25,7 @@ module SeccompTools
22
25
  # @return [[:cmp, Symbol, (:x, Integer), Integer, Integer], [:jmp, Integer]]
23
26
  def symbolize
24
27
  return [:jmp, k] if jop == :none
28
+
25
29
  [:cmp, jop, src, jt, jf]
26
30
  end
27
31
 
@@ -32,6 +36,8 @@ module SeccompTools
32
36
  def branch(context)
33
37
  return [[at(k), context]] if jop == :none
34
38
  return [[at(jt), context]] if jt == jf
39
+ return [[at(jt), context.dup.eql!(src)], [at(jf), context]] if jop == :==
40
+
35
41
  [[at(jt), context], [at(jf), context]]
36
42
  end
37
43
 
@@ -50,13 +56,16 @@ module SeccompTools
50
56
 
51
57
  def src_str
52
58
  return 'X' if src == :x
59
+
53
60
  # if A in all contexts are same
54
61
  a = contexts.map(&:a).uniq
55
62
  return k.to_s if a.size != 1
63
+
56
64
  a = a[0]
57
- return k.to_s if a.nil?
65
+ return k.to_s unless a.data?
66
+
58
67
  hex = '0x' + k.to_s(16)
59
- case a
68
+ case a.val
60
69
  when 0 then Util.colorize(Const::Syscall.const_get(arch.upcase.to_sym).invert[k] || hex, t: :syscall)
61
70
  when 4 then Util.colorize(Const::Audit::ARCH.invert[k] || hex, t: :arch)
62
71
  else hex
@@ -78,6 +87,7 @@ module SeccompTools
78
87
  def if_str(neg = false)
79
88
  return "if (A #{jop} #{src_str}) " unless neg
80
89
  return "if (!(A & #{src_str})) " if jop == :&
90
+
81
91
  op = case jop
82
92
  when :>= then :<
83
93
  when :> then :<=