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.
- checksums.yaml +5 -5
- data/README.md +84 -17
- data/bin/seccomp-tools +1 -0
- data/ext/ptrace/ptrace.c +8 -1
- data/lib/seccomp-tools.rb +2 -0
- data/lib/seccomp-tools/asm/asm.rb +4 -1
- data/lib/seccomp-tools/asm/compiler.rb +61 -10
- data/lib/seccomp-tools/asm/tokenizer.rb +15 -3
- data/lib/seccomp-tools/bpf.rb +2 -0
- data/lib/seccomp-tools/cli/asm.rb +14 -4
- data/lib/seccomp-tools/cli/base.rb +5 -0
- data/lib/seccomp-tools/cli/cli.rb +6 -3
- data/lib/seccomp-tools/cli/disasm.rb +5 -1
- data/lib/seccomp-tools/cli/dump.rb +4 -1
- data/lib/seccomp-tools/cli/emu.rb +15 -2
- data/lib/seccomp-tools/const.rb +25 -19
- data/lib/seccomp-tools/consts/sys_arg.rb +432 -0
- data/lib/seccomp-tools/consts/{amd64.rb → sys_nr/amd64.rb} +4 -2
- data/lib/seccomp-tools/consts/{i386.rb → sys_nr/i386.rb} +5 -2
- data/lib/seccomp-tools/disasm/context.rb +125 -34
- data/lib/seccomp-tools/disasm/disasm.rb +4 -2
- data/lib/seccomp-tools/dumper.rb +4 -0
- data/lib/seccomp-tools/emulator.rb +10 -0
- data/lib/seccomp-tools/instruction/alu.rb +6 -1
- data/lib/seccomp-tools/instruction/base.rb +4 -2
- data/lib/seccomp-tools/instruction/instruction.rb +2 -0
- data/lib/seccomp-tools/instruction/jmp.rb +12 -2
- data/lib/seccomp-tools/instruction/ld.rb +27 -11
- data/lib/seccomp-tools/instruction/ldx.rb +2 -0
- data/lib/seccomp-tools/instruction/misc.rb +2 -0
- data/lib/seccomp-tools/instruction/ret.rb +3 -0
- data/lib/seccomp-tools/instruction/st.rb +3 -1
- data/lib/seccomp-tools/instruction/stx.rb +2 -0
- data/lib/seccomp-tools/syscall.rb +5 -1
- data/lib/seccomp-tools/templates/asm.amd64.asm +26 -0
- data/lib/seccomp-tools/templates/asm.c +17 -0
- data/lib/seccomp-tools/templates/asm.i386.asm +33 -0
- data/lib/seccomp-tools/util.rb +16 -1
- data/lib/seccomp-tools/version.rb +3 -1
- metadata +18 -11
@@ -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
|
-
|
182
|
-
|
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
|
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
|
-
# @
|
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?]
|
12
|
-
# Value to be set to +
|
13
|
-
# @param [Integer
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
def initialize(
|
18
|
-
@values =
|
19
|
-
16.times { |i| @values[i] ||=
|
20
|
-
@values[:a]
|
21
|
-
@values[: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
|
-
#
|
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(
|
123
|
+
Context.new(values: values.dup, known_data: known_data.dup)
|
28
124
|
end
|
29
125
|
|
30
126
|
# Register A.
|
31
|
-
# @return [
|
127
|
+
# @return [Context::Value]
|
32
128
|
def a
|
33
129
|
values[:a]
|
34
130
|
end
|
35
131
|
|
36
132
|
# Register X.
|
37
|
-
# @return [
|
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 [
|
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,
|
52
|
-
# Can be +'A', 'a', :a, 'X', 'x', :x
|
53
|
-
# @param [
|
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 []=(
|
57
|
-
|
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}
|
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
|
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
|
-
|
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}.
|
data/lib/seccomp-tools/dumper.rb
CHANGED
@@ -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] =
|
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
|
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
|
-
#
|
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/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
|
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 :<=
|