seccomp-tools 0.1.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.
- checksums.yaml +7 -0
- data/README.md +151 -0
- data/bin/seccomp-tools +5 -0
- data/ext/ptrace/extconf.rb +5 -0
- data/ext/ptrace/ptrace.c +76 -0
- data/lib/seccomp-tools.rb +8 -0
- data/lib/seccomp-tools/bpf.rb +71 -0
- data/lib/seccomp-tools/cli/base.rb +67 -0
- data/lib/seccomp-tools/cli/cli.rb +72 -0
- data/lib/seccomp-tools/cli/disasm.rb +41 -0
- data/lib/seccomp-tools/cli/dump.rb +66 -0
- data/lib/seccomp-tools/const.rb +112 -0
- data/lib/seccomp-tools/consts/amd64.rb +335 -0
- data/lib/seccomp-tools/consts/i386.rb +382 -0
- data/lib/seccomp-tools/context.rb +31 -0
- data/lib/seccomp-tools/disasm.rb +37 -0
- data/lib/seccomp-tools/dumper.rb +128 -0
- data/lib/seccomp-tools/instruction/alu.rb +42 -0
- data/lib/seccomp-tools/instruction/base.rb +30 -0
- data/lib/seccomp-tools/instruction/instruction.rb +8 -0
- data/lib/seccomp-tools/instruction/jmp.rb +76 -0
- data/lib/seccomp-tools/instruction/ld.rb +69 -0
- data/lib/seccomp-tools/instruction/ldx.rb +14 -0
- data/lib/seccomp-tools/instruction/misc.rb +24 -0
- data/lib/seccomp-tools/instruction/ret.rb +19 -0
- data/lib/seccomp-tools/instruction/st.rb +20 -0
- data/lib/seccomp-tools/instruction/stx.rb +14 -0
- data/lib/seccomp-tools/syscall.rb +67 -0
- data/lib/seccomp-tools/util.rb +54 -0
- data/lib/seccomp-tools/version.rb +4 -0
- metadata +173 -0
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'seccomp-tools/ptrace'
|
2
|
+
require 'seccomp-tools/syscall'
|
3
|
+
|
4
|
+
module SeccompTools
|
5
|
+
# Dump seccomp-bpf using ptrace of binary.
|
6
|
+
# Currently only support x86_64.
|
7
|
+
module Dumper
|
8
|
+
module_function
|
9
|
+
|
10
|
+
# Main bpf dump function.
|
11
|
+
# Yield seccomp bpf whenever find a +prctl(SET_SECCOMP)+ call.
|
12
|
+
#
|
13
|
+
# @param [Array<String>] args
|
14
|
+
# The arguments for target execution file.
|
15
|
+
# @param [Integer] limit
|
16
|
+
# By default, +dump+ will only dump the first +SET_SECCOMP+ call.
|
17
|
+
# Set +limit+ to the number of calling +prctl(SET_SECCOMP)+ then the child process will be killed when number of
|
18
|
+
# calling +prctl+ reaches +limit+.
|
19
|
+
#
|
20
|
+
# Negative number for unlimited.
|
21
|
+
# @yieldparam [String] bpf
|
22
|
+
# Seccomp bpf in raw bytes.
|
23
|
+
# @yieldparam [Symbol] arch
|
24
|
+
# Architecture of the target process.
|
25
|
+
# @return [Array<Object>, Array<String>]
|
26
|
+
# Return the block returned. If block is not given, array of raw bytes will be returned.
|
27
|
+
# @example
|
28
|
+
# dump('ls', '-l', '-a')
|
29
|
+
# #=> []
|
30
|
+
# dump('spec/binary/twctf-2016-diary') { |c| c[0, 10] }
|
31
|
+
# #=> [" \x00\x00\x00\x00\x00\x00\x00\x15\x00"]
|
32
|
+
# @todo
|
33
|
+
# Detect execution file architecture to know which syscall number should be traced.
|
34
|
+
# @todo
|
35
|
+
# +timeout+ option.
|
36
|
+
def dump(*args, limit: 1, &block)
|
37
|
+
pid = fork { handle_child(*args) }
|
38
|
+
Handler.new(pid).handle(limit, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Do the tracer things.
|
42
|
+
class Handler
|
43
|
+
def initialize(pid)
|
44
|
+
Process.waitpid(pid)
|
45
|
+
opt = Ptrace::O_TRACESYSGOOD | Ptrace::O_TRACECLONE | Ptrace::O_TRACEFORK | Ptrace::O_TRACEVFORK
|
46
|
+
Ptrace.setoptions(pid, 0, opt)
|
47
|
+
Ptrace.syscall(pid, 0, 0)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Tracer.
|
51
|
+
#
|
52
|
+
# @param [Integer] limit
|
53
|
+
# Child will be killed when number of calling +prctl(SET_SECCOMP)+ reaches +limit+.
|
54
|
+
# @yieldparam [String] bpf
|
55
|
+
# Seccomp bpf in raw bytes.
|
56
|
+
# @return [Array<Object>, Array<String>]
|
57
|
+
# Return the block returned. If block is not given, array of raw bytes will be returned.
|
58
|
+
def handle(limit, &block)
|
59
|
+
collect = []
|
60
|
+
syscalls = {} # record last syscall
|
61
|
+
loop while wait_syscall do |child|
|
62
|
+
if syscalls[child].nil? # invoke syscall
|
63
|
+
syscalls[child] = syscall(child)
|
64
|
+
next true
|
65
|
+
end
|
66
|
+
# syscall finished
|
67
|
+
sys = syscalls[child]
|
68
|
+
syscalls[child] = nil
|
69
|
+
if sys.set_seccomp? && syscall(child).ret.zero? # consider successful call only
|
70
|
+
bpf = sys.dump_bpf
|
71
|
+
collect << (block.nil? ? bpf : yield(bpf, sys.arch))
|
72
|
+
limit -= 1
|
73
|
+
end
|
74
|
+
!limit.zero?
|
75
|
+
end
|
76
|
+
syscalls.keys.each { |cpid| Process.kill('KILL', cpid) if alive?(cpid) }
|
77
|
+
collect
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
# @yieldparam [Integer] pid
|
83
|
+
# @return [Boolean]
|
84
|
+
# +true+ for continue,
|
85
|
+
# +false+ for break.
|
86
|
+
def wait_syscall
|
87
|
+
child, status = Process.wait2
|
88
|
+
cont = true
|
89
|
+
# TODO: Test if clone / vfork works
|
90
|
+
if [Ptrace::EVENT_CLONE, Ptrace::EVENT_FORK, Ptrace::EVENT_VFORK].include?(status >> 16)
|
91
|
+
# New child launched!
|
92
|
+
# newpid = SeccompTools::Ptrace.geteventmsg(child)
|
93
|
+
elsif status.stopped? && status.stopsig & 0x80 != 0
|
94
|
+
cont = yield(child)
|
95
|
+
end
|
96
|
+
Ptrace.syscall(child, 0, 0) unless status.exited?
|
97
|
+
return cont
|
98
|
+
rescue Errno::ECHILD
|
99
|
+
return false
|
100
|
+
end
|
101
|
+
|
102
|
+
# @return [SeccompTools::Syscall]
|
103
|
+
def syscall(pid)
|
104
|
+
SeccompTools::Syscall.new(pid)
|
105
|
+
end
|
106
|
+
|
107
|
+
def alive?(pid)
|
108
|
+
Process.getpgid(pid)
|
109
|
+
true
|
110
|
+
rescue Errno::ESRCH
|
111
|
+
false
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class << self
|
116
|
+
private
|
117
|
+
|
118
|
+
def handle_child(*args)
|
119
|
+
Ptrace.traceme_and_stop
|
120
|
+
exec(*args)
|
121
|
+
rescue # exec fail
|
122
|
+
# TODO: use logger
|
123
|
+
$stderr.puts("Failed to execute #{args.join(' ')}")
|
124
|
+
exit(1)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'seccomp-tools/instruction/base'
|
2
|
+
|
3
|
+
module SeccompTools
|
4
|
+
module Instruction
|
5
|
+
# Instruction alu.
|
6
|
+
class ALU < Base
|
7
|
+
# Decompile instruction.
|
8
|
+
def decompile
|
9
|
+
return 'A = -A' if op == :neg
|
10
|
+
"A #{op_sym}= #{src}"
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def op
|
16
|
+
o = OP.invert[code & 0xf0]
|
17
|
+
invalid('unknown op') if o.nil?
|
18
|
+
o
|
19
|
+
end
|
20
|
+
|
21
|
+
def op_sym
|
22
|
+
case op
|
23
|
+
when :add then :+
|
24
|
+
when :sub then :-
|
25
|
+
when :mul then :*
|
26
|
+
when :div then :/
|
27
|
+
when :or then :|
|
28
|
+
when :and then :&
|
29
|
+
when :lsh then :<<
|
30
|
+
when :rsh then :>>
|
31
|
+
# when :neg then :- # should not invoke this method
|
32
|
+
# when :mod then :% # unsupported
|
33
|
+
when :xor then :^
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def src
|
38
|
+
SRC.invert[code & 0x8] == :k ? k : 'X'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'seccomp-tools/const'
|
2
|
+
|
3
|
+
module SeccompTools
|
4
|
+
# For instructions' class.
|
5
|
+
module Instruction
|
6
|
+
# Base class for different instruction.
|
7
|
+
class Base
|
8
|
+
include SeccompTools::Const::BPF
|
9
|
+
|
10
|
+
# @param [SeccompTools::BPF] bpf
|
11
|
+
# An instruction.
|
12
|
+
def initialize(bpf)
|
13
|
+
@bpf = bpf
|
14
|
+
end
|
15
|
+
|
16
|
+
# @raise [ArgumentError]
|
17
|
+
def invalid(msg = 'unknown')
|
18
|
+
raise ArgumentError, "Line #{line} is invalid: #{msg}"
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
%i(code jt jf k arch line contexts).each do |sym|
|
24
|
+
define_method(sym) do
|
25
|
+
@bpf.send(sym)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'seccomp-tools/instruction/alu'
|
2
|
+
require 'seccomp-tools/instruction/jmp'
|
3
|
+
require 'seccomp-tools/instruction/ld'
|
4
|
+
require 'seccomp-tools/instruction/ldx'
|
5
|
+
require 'seccomp-tools/instruction/misc'
|
6
|
+
require 'seccomp-tools/instruction/ret'
|
7
|
+
require 'seccomp-tools/instruction/st'
|
8
|
+
require 'seccomp-tools/instruction/stx'
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'seccomp-tools/const'
|
2
|
+
require 'seccomp-tools/instruction/base'
|
3
|
+
|
4
|
+
module SeccompTools
|
5
|
+
module Instruction
|
6
|
+
# Instruction jmp.
|
7
|
+
class JMP < Base
|
8
|
+
# Decompile instruction.
|
9
|
+
def decompile
|
10
|
+
return goto(k) if jop == :none
|
11
|
+
# if jt == 0 && jf == 0 => no-op # should not happen
|
12
|
+
# jt == 0 => if(!) goto jf
|
13
|
+
# jf == 0 => if() goto jt;
|
14
|
+
# otherwise => if () goto jt; else goto jf;
|
15
|
+
return '/* no-op */' if jt.zero? && jf.zero?
|
16
|
+
return goto(jt) if jt == jf
|
17
|
+
return if_str + goto(jt) + ' else ' + goto(jf) unless jt.zero? || jf.zero?
|
18
|
+
return if_str + goto(jt) if jf.zero?
|
19
|
+
if_str(true) + goto(jf)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Array<(Integer, Context)>]
|
23
|
+
def branch(context)
|
24
|
+
return [[at(k), context]] if jop == :none
|
25
|
+
return [[at(jt), context]] if jt == jf
|
26
|
+
[[at(jt), context], [at(jf), context]]
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def jop
|
32
|
+
case Const::BPF::JMP.invert[code & 0x70]
|
33
|
+
when :ja then :none
|
34
|
+
when :jgt then :>
|
35
|
+
when :jge then :>=
|
36
|
+
when :jeq then :==
|
37
|
+
when :jset then :&
|
38
|
+
else invalid('unknown jmp type')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def src_str
|
43
|
+
return 'X' if SRC.invert[code & 8] == :x
|
44
|
+
# if A in all contexts are same
|
45
|
+
a = contexts.map(&:a).uniq
|
46
|
+
return k.to_s if a.size != 1
|
47
|
+
a = a[0]
|
48
|
+
return k.to_s unless a.instance_of?(Array) && a.first == :data
|
49
|
+
case a.last
|
50
|
+
when 0 then Util.colorize((Const::Syscall.const_get(arch.upcase.to_sym).invert[k] || k).to_s, t: :syscall)
|
51
|
+
when 4 then Util.colorize(Const::Audit::ARCH.invert[k] || k.to_s(16), t: :arch)
|
52
|
+
else '0x' + k.to_s(16)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def goto(off)
|
57
|
+
format('goto %04d', at(off))
|
58
|
+
end
|
59
|
+
|
60
|
+
def at(off)
|
61
|
+
line + off + 1
|
62
|
+
end
|
63
|
+
|
64
|
+
def if_str(neg = false)
|
65
|
+
return "if (A #{jop} #{src_str}) " unless neg
|
66
|
+
return "if (!(A & #{src_str})) " if jop == :&
|
67
|
+
op = case jop
|
68
|
+
when :>= then :<
|
69
|
+
when :> then :<=
|
70
|
+
when :== then :!=
|
71
|
+
end
|
72
|
+
"if (A #{op} #{src_str}) "
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'seccomp-tools/instruction/base'
|
2
|
+
|
3
|
+
module SeccompTools
|
4
|
+
module Instruction
|
5
|
+
# Instruction ld.
|
6
|
+
class LD < Base
|
7
|
+
# Decompile instruction.
|
8
|
+
def decompile
|
9
|
+
ret = reg + ' = '
|
10
|
+
type = load_val
|
11
|
+
return ret + type[:val].to_s if type[:rel] == :immi
|
12
|
+
return ret + "mem[#{type[:val]}]" if type[:rel] == :mem
|
13
|
+
ret + seccomp_data_str
|
14
|
+
end
|
15
|
+
|
16
|
+
# Accumulator register.
|
17
|
+
# @return ['A']
|
18
|
+
def reg
|
19
|
+
'A'
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Array<(Integer, Context)>]
|
23
|
+
def branch(context)
|
24
|
+
nctx = context.dup
|
25
|
+
type = load_val
|
26
|
+
nctx[reg] = case type[:rel]
|
27
|
+
when :immi then type[:val]
|
28
|
+
when :mem then context.mem[type[:val]]
|
29
|
+
when :data then [:data, type[:val]]
|
30
|
+
end
|
31
|
+
[[line + 1, nctx]]
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def mode
|
37
|
+
@mode ||= MODE.invert[code & 0xe0]
|
38
|
+
# Seccomp doesn't support this mode
|
39
|
+
invalid if @mode.nil? || @mode == :ind
|
40
|
+
@mode
|
41
|
+
end
|
42
|
+
|
43
|
+
def load_val
|
44
|
+
return { rel: :immi, val: k } if mode == :imm
|
45
|
+
return { rel: :immi, val: SIZEOF_SECCOMP_DATA } if mode == :len
|
46
|
+
return { rel: :mem, val: k } if mode == :mem
|
47
|
+
{ rel: :data, val: k }
|
48
|
+
end
|
49
|
+
|
50
|
+
# struct seccomp_data {
|
51
|
+
# int nr;
|
52
|
+
# __u32 arch;
|
53
|
+
# __u64 instruction_pointer;
|
54
|
+
# __u64 args[6];
|
55
|
+
# };
|
56
|
+
def seccomp_data_str
|
57
|
+
case k
|
58
|
+
when 0 then 'sys_number'
|
59
|
+
when 4 then 'arch'
|
60
|
+
when 8 then 'instruction_pointer'
|
61
|
+
else
|
62
|
+
idx = Array.new(12) { |i| i * 4 + 16 }.index(k)
|
63
|
+
return 'INVALID' if idx.nil?
|
64
|
+
idx.even? ? "args[#{idx / 2}]" : "args[#{idx / 2}] >> 32"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'seccomp-tools/instruction/base'
|
2
|
+
|
3
|
+
module SeccompTools
|
4
|
+
module Instruction
|
5
|
+
# Instruction misc.
|
6
|
+
class MISC < Base
|
7
|
+
# Decompile instruction.
|
8
|
+
def decompile
|
9
|
+
case op
|
10
|
+
when :txa then 'A = X'
|
11
|
+
when :tax then 'X = A'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def op
|
18
|
+
o = MISCOP.invert[code & 0xf8]
|
19
|
+
invalid('MISC operation only supports txa/tax') if o.nil?
|
20
|
+
o
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'seccomp-tools/instruction/base'
|
2
|
+
|
3
|
+
module SeccompTools
|
4
|
+
module Instruction
|
5
|
+
# Instruction ret.
|
6
|
+
class RET < Base
|
7
|
+
# Decompile instruction.
|
8
|
+
def decompile
|
9
|
+
return 'return A' if code & 0x18 == SRC[:a]
|
10
|
+
"return #{ACTION.invert[k & 0x7fff0000]}"
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [[]]
|
14
|
+
def branch(*)
|
15
|
+
[]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'seccomp-tools/instruction/ld'
|
2
|
+
|
3
|
+
module SeccompTools
|
4
|
+
module Instruction
|
5
|
+
# Instruction st.
|
6
|
+
class ST < LD
|
7
|
+
# Decompile instruction.
|
8
|
+
def decompile
|
9
|
+
"mem[#{k}] = #{reg}"
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [Array<(Integer, Context)>]
|
13
|
+
def branch(context)
|
14
|
+
ctx = context.dup
|
15
|
+
ctx.mem[k] = ctx[reg]
|
16
|
+
[[line + 1, ctx]]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|