seccomp-tools 1.2.0 → 1.3.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 (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 :<=