seccomp-tools 1.5.0 → 1.6.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 +4 -4
- data/README.md +98 -18
- data/lib/seccomp-tools/asm/asm.rb +8 -6
- data/lib/seccomp-tools/asm/compiler.rb +130 -224
- data/lib/seccomp-tools/asm/sasm.tab.rb +780 -0
- data/lib/seccomp-tools/asm/sasm.y +175 -0
- data/lib/seccomp-tools/asm/scalar.rb +129 -0
- data/lib/seccomp-tools/asm/scanner.rb +163 -0
- data/lib/seccomp-tools/asm/statement.rb +32 -0
- data/lib/seccomp-tools/asm/token.rb +29 -0
- data/lib/seccomp-tools/bpf.rb +31 -7
- data/lib/seccomp-tools/cli/asm.rb +1 -1
- data/lib/seccomp-tools/cli/base.rb +4 -4
- data/lib/seccomp-tools/cli/disasm.rb +26 -2
- data/lib/seccomp-tools/const.rb +25 -4
- data/lib/seccomp-tools/consts/sys_nr/amd64.rb +1 -0
- data/lib/seccomp-tools/consts/sys_nr/s390x.rb +365 -0
- data/lib/seccomp-tools/disasm/context.rb +2 -2
- data/lib/seccomp-tools/disasm/disasm.rb +15 -9
- data/lib/seccomp-tools/dumper.rb +1 -2
- data/lib/seccomp-tools/emulator.rb +5 -10
- data/lib/seccomp-tools/error.rb +31 -0
- data/lib/seccomp-tools/instruction/base.rb +1 -1
- data/lib/seccomp-tools/instruction/jmp.rb +14 -1
- data/lib/seccomp-tools/instruction/ld.rb +3 -1
- data/lib/seccomp-tools/syscall.rb +13 -8
- data/lib/seccomp-tools/templates/asm.s390x.asm +26 -0
- data/lib/seccomp-tools/util.rb +2 -1
- data/lib/seccomp-tools/version.rb +1 -1
- metadata +17 -15
- data/lib/seccomp-tools/asm/tokenizer.rb +0 -169
@@ -0,0 +1,175 @@
|
|
1
|
+
class SeccompTools::Asm::SeccompAsmParser
|
2
|
+
options no_result_var
|
3
|
+
rule
|
4
|
+
prog: normalized_statement terminator { [val[0]] }
|
5
|
+
| prog normalized_statement terminator { val[0] << val[1] }
|
6
|
+
normalized_statement: symbols newlines statement { Statement.new(*val[2], val[0]) }
|
7
|
+
| statement { Statement.new(*val[0], []) }
|
8
|
+
symbols: symbol { val }
|
9
|
+
| symbols newlines symbol { val[0] << val[2] }
|
10
|
+
symbol: SYMBOL {
|
11
|
+
t = val[0]
|
12
|
+
raise_error("'next' is a reserved label") if t == 'next'
|
13
|
+
last_token
|
14
|
+
}
|
15
|
+
statement: arithmetic { [:alu, val[0]] }
|
16
|
+
| assignment { [:assign, val[0]] }
|
17
|
+
| conditional { [:if, val[0]] }
|
18
|
+
| goto_expr { [:if, [nil, val[0], val[0]]] }
|
19
|
+
| return_stat { [:ret, val[0]] }
|
20
|
+
arithmetic: A alu_op_eq newlines x_constexpr { [val[1], val[3]] }
|
21
|
+
assignment: a ASSIGN a_rval { [val[0], val[2]] }
|
22
|
+
| x ASSIGN x_rval { [val[0], val[2]] }
|
23
|
+
| memory ASSIGN ax { [val[0], val[2]] }
|
24
|
+
# X = ?
|
25
|
+
x_rval: constexpr
|
26
|
+
| memory
|
27
|
+
| a
|
28
|
+
# A = ?
|
29
|
+
a_rval: x_constexpr
|
30
|
+
| argument { Scalar::Data.new(val[0]) }
|
31
|
+
| memory
|
32
|
+
| LEN { Scalar::Len.instance }
|
33
|
+
# A = -A is a special case, it's in an assignment form but belongs to ALU BPF
|
34
|
+
| ALU_OP A {
|
35
|
+
raise_error('do you mean A = -A?', -1) if val[0] != '-'
|
36
|
+
:neg
|
37
|
+
}
|
38
|
+
conditional: IF comparison newlines goto_expr newlines else_block {
|
39
|
+
op, rval, parity = val[1]
|
40
|
+
jt, jf = val[3], val[5]
|
41
|
+
jt, jf = jf, jt if parity.odd?
|
42
|
+
[[op, rval], jt, jf]
|
43
|
+
}
|
44
|
+
| A compare x_constexpr goto_symbol goto_symbol { [[val[1], val[2]], val[3], val[4]] }
|
45
|
+
else_block: ELSE newlines goto_expr { val[2] }
|
46
|
+
| { :next }
|
47
|
+
comparison: LPAREN newlines comparison_ newlines RPAREN { val[2] }
|
48
|
+
comparison_: a newlines compare newlines x_constexpr { [val[2], val[4], 0] }
|
49
|
+
| BANG LPAREN comparison_ RPAREN { val[2][2] += 1; val[2] }
|
50
|
+
compare: COMPARE
|
51
|
+
| AND
|
52
|
+
goto_expr: GOTO goto_symbol { val[1] }
|
53
|
+
goto_symbol: GOTO_SYMBOL { last_token }
|
54
|
+
return_stat: RETURN ret_val { val[1] }
|
55
|
+
ret_val: a
|
56
|
+
| ACTION { Scalar::ConstVal.new(Const::BPF::ACTION[val[0].to_sym]) }
|
57
|
+
| ACTION LPAREN constexpr RPAREN {
|
58
|
+
Scalar::ConstVal.new(Const::BPF::ACTION[val[0].to_sym] |
|
59
|
+
(val[2].to_i & Const::BPF::SECCOMP_RET_DATA))
|
60
|
+
}
|
61
|
+
| constexpr
|
62
|
+
memory: MEM LBRACK constexpr RBRACK {
|
63
|
+
idx = val[2].to_i
|
64
|
+
raise_error(format("index of mem[] must between 0 and 15, got %d", idx), -1) unless idx.between?(0, 15)
|
65
|
+
Scalar::Mem.new(idx)
|
66
|
+
}
|
67
|
+
x_constexpr: x
|
68
|
+
| constexpr
|
69
|
+
argument: argument_long
|
70
|
+
| argument_long alu_op INT {
|
71
|
+
if val[1] != '>>' || val[2].to_i != 32
|
72
|
+
off = val[1] == '>>' ? 0 : -1
|
73
|
+
raise_error("operator after an argument can only be '>> 32'", off)
|
74
|
+
end
|
75
|
+
val[0] + 4
|
76
|
+
}
|
77
|
+
| SYS_NUMBER { 0 }
|
78
|
+
| ARCH { 4 }
|
79
|
+
| DATA LBRACK constexpr RBRACK {
|
80
|
+
idx = val[2].to_i
|
81
|
+
if idx % 4 != 0 || idx >= 64
|
82
|
+
raise_error(format('index of data[] must be a multiple of 4 and less than 64, got %d', idx), -1)
|
83
|
+
end
|
84
|
+
idx
|
85
|
+
}
|
86
|
+
# 8-byte long arguments
|
87
|
+
argument_long: args LBRACK constexpr RBRACK {
|
88
|
+
idx = val[2].to_i
|
89
|
+
s = val[0]
|
90
|
+
raise_error(format('index of %s[] must between 0 and 5, got %d', s, idx), -1) unless idx.between?(0, 5)
|
91
|
+
16 + idx * 8 + (s.downcase.end_with?('h') ? 4 : 0)
|
92
|
+
}
|
93
|
+
| INSTRUCTION_POINTER { 8 }
|
94
|
+
args: ARGS
|
95
|
+
| ARGS_H
|
96
|
+
alu_op_eq: alu_op ASSIGN { val[0] + val[1] }
|
97
|
+
alu_op: ALU_OP
|
98
|
+
| AND
|
99
|
+
constexpr: number { Scalar::ConstVal.new(val[0] & 0xffffffff) }
|
100
|
+
| LPAREN constexpr RPAREN { val[1] }
|
101
|
+
ax: a
|
102
|
+
| x
|
103
|
+
a: A { Scalar::A.instance }
|
104
|
+
x: X { Scalar::X.instance }
|
105
|
+
number: INT { val[0].to_i }
|
106
|
+
| HEX_INT { val[0].to_i(16) }
|
107
|
+
| ARCH_VAL { Const::Audit::ARCH[val[0]] }
|
108
|
+
| SYSCALL {
|
109
|
+
s = val[0]
|
110
|
+
return @scanner.syscalls[s.to_sym] unless s.include?('.')
|
111
|
+
|
112
|
+
arch, sys = s.split('.')
|
113
|
+
Const::Syscall.const_get(arch.upcase)[sys.to_sym].tap do |v|
|
114
|
+
raise_error("syscall '#{sys}' doesn't exist on #{arch}") if v.nil?
|
115
|
+
end
|
116
|
+
}
|
117
|
+
terminator: newlines
|
118
|
+
| false
|
119
|
+
newlines: newlines NEWLINE
|
120
|
+
|
|
121
|
+
ASSIGN: '='
|
122
|
+
LPAREN: '('
|
123
|
+
RPAREN: ')'
|
124
|
+
LBRACK: '['
|
125
|
+
RBRACK: ']'
|
126
|
+
AND: '&'
|
127
|
+
BANG: '!'
|
128
|
+
end
|
129
|
+
|
130
|
+
---- header
|
131
|
+
require 'seccomp-tools/asm/scalar'
|
132
|
+
require 'seccomp-tools/asm/scanner'
|
133
|
+
require 'seccomp-tools/asm/statement'
|
134
|
+
|
135
|
+
---- inner
|
136
|
+
def initialize(scanner)
|
137
|
+
@scanner = scanner
|
138
|
+
super()
|
139
|
+
end
|
140
|
+
|
141
|
+
# @return [Array<Statement>]
|
142
|
+
def parse
|
143
|
+
@tokens = @scanner.scan.dup
|
144
|
+
return [] if @tokens.empty?
|
145
|
+
|
146
|
+
@cur_idx = 0
|
147
|
+
do_parse
|
148
|
+
end
|
149
|
+
|
150
|
+
def next_token
|
151
|
+
token = @tokens[@cur_idx]
|
152
|
+
return [false, '$'] if token.nil?
|
153
|
+
|
154
|
+
@cur_idx += 1
|
155
|
+
[token.sym, token.str]
|
156
|
+
end
|
157
|
+
|
158
|
+
def on_error(t, val, vstack)
|
159
|
+
raise_error("unexpected string #{last_token.str.inspect}")
|
160
|
+
end
|
161
|
+
|
162
|
+
# @param [String] msg
|
163
|
+
# @param [Integer] offset
|
164
|
+
# @private
|
165
|
+
def raise_error(msg, offset = 0)
|
166
|
+
raise SeccompTools::ParseError, @scanner.format_error(last_token(offset), msg)
|
167
|
+
end
|
168
|
+
|
169
|
+
# @param [Integer] off
|
170
|
+
# 0 for the last parsed token, -n for the n-th previous parsed token, n for the future n-th token.
|
171
|
+
def last_token(off = 0)
|
172
|
+
@tokens[@cur_idx - 1 + off]
|
173
|
+
end
|
174
|
+
|
175
|
+
---- footer
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
|
5
|
+
module SeccompTools
|
6
|
+
module Asm
|
7
|
+
# Collection of scalars.
|
8
|
+
#
|
9
|
+
# Internally used by sasm.y.
|
10
|
+
# @private
|
11
|
+
module Scalar
|
12
|
+
# To be used to denote a register (A / X), an argument (data[]), or a memory data (mem[]).
|
13
|
+
class Base
|
14
|
+
attr_reader :val
|
15
|
+
|
16
|
+
def a?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
|
20
|
+
def x?
|
21
|
+
false
|
22
|
+
end
|
23
|
+
|
24
|
+
def len?
|
25
|
+
false
|
26
|
+
end
|
27
|
+
|
28
|
+
def data?
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
def mem?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
|
36
|
+
def const?
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Register A.
|
42
|
+
class A < Base
|
43
|
+
include Singleton
|
44
|
+
|
45
|
+
def a?
|
46
|
+
true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Register X.
|
51
|
+
class X < Base
|
52
|
+
include Singleton
|
53
|
+
|
54
|
+
def x?
|
55
|
+
true
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Len.
|
60
|
+
class Len < Base
|
61
|
+
include Singleton
|
62
|
+
|
63
|
+
def len?
|
64
|
+
true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# A constant value.
|
69
|
+
class ConstVal < Base
|
70
|
+
# @param [Integer] val
|
71
|
+
def initialize(val)
|
72
|
+
@val = val
|
73
|
+
super()
|
74
|
+
end
|
75
|
+
|
76
|
+
# @param [ConstVal, Integer] other
|
77
|
+
def ==(other)
|
78
|
+
to_i == other.to_i
|
79
|
+
end
|
80
|
+
|
81
|
+
def const?
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
alias to_i val
|
86
|
+
end
|
87
|
+
|
88
|
+
# data[]
|
89
|
+
class Data < Base
|
90
|
+
# Instantiates a {Data} object.
|
91
|
+
#
|
92
|
+
# @param [Integer] index
|
93
|
+
def initialize(index)
|
94
|
+
@val = index
|
95
|
+
super()
|
96
|
+
end
|
97
|
+
|
98
|
+
# @param [Data] other
|
99
|
+
def ==(other)
|
100
|
+
other.is_a?(Data) && val == other.val
|
101
|
+
end
|
102
|
+
|
103
|
+
def data?
|
104
|
+
true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# mem[]
|
109
|
+
class Mem < Base
|
110
|
+
# Instantiates a {Mem} object.
|
111
|
+
#
|
112
|
+
# @param [Integer] index
|
113
|
+
def initialize(index)
|
114
|
+
@val = index
|
115
|
+
super()
|
116
|
+
end
|
117
|
+
|
118
|
+
# @param [Data] other
|
119
|
+
def ==(other)
|
120
|
+
other.is_a?(Mem) && val == other.val
|
121
|
+
end
|
122
|
+
|
123
|
+
def mem?
|
124
|
+
true
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'seccomp-tools/asm/token'
|
4
|
+
require 'seccomp-tools/const'
|
5
|
+
require 'seccomp-tools/error'
|
6
|
+
require 'seccomp-tools/syscall'
|
7
|
+
|
8
|
+
module SeccompTools
|
9
|
+
module Asm
|
10
|
+
# Converts text to array of tokens.
|
11
|
+
#
|
12
|
+
# Maintains columns and rows to have informative error messages.
|
13
|
+
#
|
14
|
+
# Internally used by {SeccompAsmParser}.
|
15
|
+
class Scanner
|
16
|
+
attr_reader :syscalls
|
17
|
+
|
18
|
+
# Keywords with special meanings in our assembly. Keywords are all case-insensitive.
|
19
|
+
KEYWORDS = %w[a x if else return mem args args_h data len sys_number arch instruction_pointer].freeze
|
20
|
+
# Regexp for matching keywords.
|
21
|
+
KEYWORD_MATCHER = /\A\b(#{KEYWORDS.join('|')})\b/i.freeze
|
22
|
+
# Action strings can be used in a return statement. Actions must be in upper case.
|
23
|
+
# See {SeccompTools::Const::BPF::ACTION}.
|
24
|
+
ACTIONS = Const::BPF::ACTION.keys.map(&:to_s)
|
25
|
+
# Regexp for matching actions.
|
26
|
+
ACTION_MATCHER = /\A\b(#{ACTIONS.join('|')})\b/.freeze
|
27
|
+
# Special constants for checking the current architecture. See {SeccompTools::Const::Audit::ARCH}. These constants
|
28
|
+
# are case-insensitive.
|
29
|
+
AUDIT_ARCHES = Const::Audit::ARCH.keys
|
30
|
+
# Regexp for matching arch values.
|
31
|
+
AUDIT_ARCH_MATCHER = /\A\b(#{AUDIT_ARCHES.join('|')})\b/i.freeze
|
32
|
+
# Comparisons.
|
33
|
+
COMPARE = %w[== != >= <= > <].freeze
|
34
|
+
# Regexp for matching comparisons.
|
35
|
+
COMPARE_MATCHER = /\A(#{COMPARE.join('|')})/.freeze
|
36
|
+
# All valid arithmetic operators.
|
37
|
+
ALU_OP = %w[+ - * / | ^ << >>].freeze
|
38
|
+
# Regexp for matching ALU operators.
|
39
|
+
ALU_OP_MATCHER = /\A(#{ALU_OP.map { |o| ::Regexp.escape(o) }.join('|')})/.freeze
|
40
|
+
# Supported architectures
|
41
|
+
ARCHES = SeccompTools::Syscall::ABI.keys.map(&:to_s)
|
42
|
+
|
43
|
+
# @param [String] str
|
44
|
+
# @param [Symbol] arch
|
45
|
+
# @param [String?] filename
|
46
|
+
# @example
|
47
|
+
# Scanner.new('return ALLOW', :amd64)
|
48
|
+
def initialize(str, arch, filename: nil)
|
49
|
+
@filename = filename || '<inline>'
|
50
|
+
@str = str
|
51
|
+
@syscalls =
|
52
|
+
begin; Const::Syscall.const_get(arch.to_s.upcase); rescue NameError; []; end
|
53
|
+
@syscall_all = ARCHES.each_with_object({}) do |ar, memo|
|
54
|
+
memo.merge!(Const::Syscall.const_get(ar.to_s.upcase))
|
55
|
+
end.keys
|
56
|
+
end
|
57
|
+
|
58
|
+
# Scans the whole string and raises errors when there are unrecognized tokens.
|
59
|
+
# @return [self]
|
60
|
+
# @raise [UnrecognizedTokenError]
|
61
|
+
def validate!
|
62
|
+
errors = validate
|
63
|
+
return self if errors.empty?
|
64
|
+
|
65
|
+
raise UnrecognizedTokenError, errors.map { |e| format_error(e, "unknown token #{e.str.inspect}") }.join("\n")
|
66
|
+
end
|
67
|
+
|
68
|
+
# Same as {#validate!} but returns the array of errors instead of raising an exception.
|
69
|
+
#
|
70
|
+
# @return [Array<Token>]
|
71
|
+
def validate
|
72
|
+
scan.select { |t| t.sym == :unknown }
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [Array<Token>]
|
76
|
+
def scan
|
77
|
+
return @tokens if defined?(@tokens)
|
78
|
+
|
79
|
+
@tokens = []
|
80
|
+
row = 0
|
81
|
+
col = 0
|
82
|
+
str = @str
|
83
|
+
add_token = ->(sym, s, c = col) { @tokens.push(Token.new(sym, s, row, c)) }
|
84
|
+
# define a helper because it's commonly used - add a token with matched string, bump col with string size
|
85
|
+
bump_vars = lambda {
|
86
|
+
col += ::Regexp.last_match(0).size
|
87
|
+
str = ::Regexp.last_match.post_match
|
88
|
+
}
|
89
|
+
add_token_def = lambda do |sym|
|
90
|
+
add_token.call(sym, ::Regexp.last_match(0))
|
91
|
+
bump_vars.call
|
92
|
+
end
|
93
|
+
syscalls = @syscalls.keys.map { |s| ::Regexp.escape(s) }.join('|')
|
94
|
+
syscall_matcher = ::Regexp.compile("\\A\\b(#{syscalls})\\b")
|
95
|
+
syscall_all_matcher = ::Regexp.compile("\\A(#{ARCHES.join('|')})\\.(#{@syscall_all.join('|')})\\b")
|
96
|
+
until str.empty?
|
97
|
+
case str
|
98
|
+
when /\A\n+/
|
99
|
+
# Don't push newline as the first token
|
100
|
+
add_token.call(:NEWLINE, ::Regexp.last_match(0)) unless @tokens.empty?
|
101
|
+
row += ::Regexp.last_match(0).size
|
102
|
+
col = 0
|
103
|
+
str = ::Regexp.last_match.post_match
|
104
|
+
when /\A\s+/, /\A#.*/ then bump_vars.call
|
105
|
+
when /\A(\w+):/
|
106
|
+
add_token.call(:SYMBOL, ::Regexp.last_match(1))
|
107
|
+
bump_vars.call
|
108
|
+
when /\A(goto|jmp|jump)\s+(\w+)\b/i
|
109
|
+
add_token.call(:GOTO, ::Regexp.last_match(1), col + ::Regexp.last_match.begin(1))
|
110
|
+
add_token.call(:GOTO_SYMBOL, ::Regexp.last_match(2), col + ::Regexp.last_match.begin(2))
|
111
|
+
bump_vars.call
|
112
|
+
when KEYWORD_MATCHER then add_token_def.call(::Regexp.last_match(0).upcase.to_sym)
|
113
|
+
when ACTION_MATCHER then add_token_def.call(:ACTION)
|
114
|
+
when AUDIT_ARCH_MATCHER then add_token_def.call(:ARCH_VAL)
|
115
|
+
when syscall_matcher, syscall_all_matcher then add_token_def.call(:SYSCALL)
|
116
|
+
when /\A-?0x[0-9a-f]+\b/ then add_token_def.call(:HEX_INT)
|
117
|
+
when /\A-?[0-9]+\b/ then add_token_def.call(:INT)
|
118
|
+
when ALU_OP_MATCHER then add_token_def.call(:ALU_OP)
|
119
|
+
when COMPARE_MATCHER then add_token_def.call(:COMPARE)
|
120
|
+
# '&' is in both compare and ALU op category, handle it here
|
121
|
+
when /\A(\(|\)|=|\[|\]|&|!)/ then add_token_def.call(::Regexp.last_match(0))
|
122
|
+
when /\A\?\s*(?<jt>\w+)\s*:\s*(?<jf>\w+)/
|
123
|
+
%i[jt jf].each do |s|
|
124
|
+
add_token.call(:GOTO_SYMBOL, ::Regexp.last_match(s), col + ::Regexp.last_match.begin(s))
|
125
|
+
end
|
126
|
+
bump_vars.call
|
127
|
+
when /\A([^\s]+)(\s?)/
|
128
|
+
# unrecognized token - match until \s
|
129
|
+
last = ::Regexp.last_match(1)
|
130
|
+
add_token.call(:unknown, last)
|
131
|
+
col += last.size
|
132
|
+
str = ::Regexp.last_match(2) + ::Regexp.last_match.post_match
|
133
|
+
end
|
134
|
+
end
|
135
|
+
@tokens
|
136
|
+
end
|
137
|
+
|
138
|
+
# Let tab on terminal be 4 spaces wide.
|
139
|
+
TAB_WIDTH = 4
|
140
|
+
|
141
|
+
# @param [Token] tok
|
142
|
+
# @param [String] msg
|
143
|
+
# @return [String]
|
144
|
+
def format_error(tok, msg)
|
145
|
+
@lines = @str.lines unless defined?(@lines)
|
146
|
+
line = @lines[tok.line]
|
147
|
+
line = line[0..-2] if line.end_with?("\n")
|
148
|
+
line = line.gsub("\t", ' ' * TAB_WIDTH)
|
149
|
+
<<-EOS
|
150
|
+
#{@filename}:#{tok.line + 1}:#{tok.col + 1} #{msg}
|
151
|
+
#{line}
|
152
|
+
#{' ' * calculate_spaces(@lines[tok.line][0...tok.col]) + '^' * tok.str.size}
|
153
|
+
EOS
|
154
|
+
end
|
155
|
+
|
156
|
+
private
|
157
|
+
|
158
|
+
def calculate_spaces(str)
|
159
|
+
str.size + str.count("\t") * (TAB_WIDTH - 1)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SeccompTools
|
4
|
+
module Asm
|
5
|
+
# A statement after parsing the assembly.
|
6
|
+
# Each statement will be converted to a BPF.
|
7
|
+
#
|
8
|
+
# Internally used by sasm.y.
|
9
|
+
# @private
|
10
|
+
class Statement
|
11
|
+
attr_reader :type, :data, :symbols
|
12
|
+
|
13
|
+
# Instantiates a {Statement} object.
|
14
|
+
#
|
15
|
+
# @param [:alu, :assign, :if, :ret] type
|
16
|
+
# @param [Integer, Array<Integer, String>] data
|
17
|
+
# The data for describing this statement. Type of +data+ is variant according to the value of +type+.
|
18
|
+
# @param [Array<String>] symbols
|
19
|
+
# Symbols that refer to this statement.
|
20
|
+
def initialize(type, data, symbols)
|
21
|
+
@type = type
|
22
|
+
@data = data
|
23
|
+
@symbols = symbols
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [Statement] other
|
27
|
+
def ==(other)
|
28
|
+
[type, data, symbols] == [other.type, other.data, other.symbols]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SeccompTools
|
4
|
+
module Asm
|
5
|
+
# Records information of a token.
|
6
|
+
class Token
|
7
|
+
attr_reader :sym, :str, :line, :col
|
8
|
+
|
9
|
+
# Instantiates a {Token} object.
|
10
|
+
# @param [Symbol] sym
|
11
|
+
# @param [String] str
|
12
|
+
# @param [Integer] line
|
13
|
+
# @param [Integer] col
|
14
|
+
def initialize(sym, str, line, col)
|
15
|
+
@sym = sym
|
16
|
+
@str = str
|
17
|
+
@line = line
|
18
|
+
@col = col
|
19
|
+
end
|
20
|
+
|
21
|
+
# To compare with another {Token} object.
|
22
|
+
# @param [Token] other
|
23
|
+
# @return [Boolean]
|
24
|
+
def ==(other)
|
25
|
+
[other.sym, other.str, other.line, other.col] == [sym, str, line, col]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/seccomp-tools/bpf.rb
CHANGED
@@ -26,7 +26,7 @@ module SeccompTools
|
|
26
26
|
|
27
27
|
# Instantiate a {BPF} object.
|
28
28
|
# @param [String] raw
|
29
|
-
# One +struct sock_filter+ in bytes, should
|
29
|
+
# One +struct sock_filter+ in bytes, should be 8 bytes long.
|
30
30
|
# @param [Symbol] arch
|
31
31
|
# Architecture, for showing constant names in decompile.
|
32
32
|
# @param [Integer] line
|
@@ -34,10 +34,11 @@ module SeccompTools
|
|
34
34
|
def initialize(raw, arch, line)
|
35
35
|
if raw.is_a?(String)
|
36
36
|
io = ::StringIO.new(raw)
|
37
|
-
|
37
|
+
endian = Const::Endian::ENDIAN[arch]
|
38
|
+
@code = io.read(2).unpack1("S#{endian}")
|
38
39
|
@jt = io.read(1).ord
|
39
40
|
@jf = io.read(1).ord
|
40
|
-
@k = io.read(4).unpack1(
|
41
|
+
@k = io.read(4).unpack1("L#{endian}")
|
41
42
|
else
|
42
43
|
@code = raw[:code]
|
43
44
|
@jt = raw[:jt]
|
@@ -47,20 +48,43 @@ module SeccompTools
|
|
47
48
|
@arch = arch
|
48
49
|
@line = line
|
49
50
|
@contexts = Set.new
|
51
|
+
@disasm_setting = {
|
52
|
+
code: true,
|
53
|
+
arg_infer: true
|
54
|
+
}
|
50
55
|
end
|
51
56
|
|
52
57
|
# Pretty display the disassemble result.
|
58
|
+
# @param [{Symbol => Boolean}] options
|
59
|
+
# Set display settings.
|
53
60
|
# @return [String]
|
54
|
-
def disasm
|
55
|
-
|
56
|
-
|
61
|
+
def disasm(**options)
|
62
|
+
@disasm_setting.merge!(options)
|
63
|
+
if show_code?
|
64
|
+
format(' %04d: 0x%02x 0x%02x 0x%02x 0x%08x %s',
|
65
|
+
line, code, jt, jf, k, decompile)
|
66
|
+
else
|
67
|
+
format('%04d: %s',
|
68
|
+
line, decompile)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Whether needs to dump code, jt, jf, k.
|
73
|
+
def show_code?
|
74
|
+
@disasm_setting[:code]
|
75
|
+
end
|
76
|
+
|
77
|
+
# Whether needs to infer the syscall argument names.
|
78
|
+
def show_arg_infer?
|
79
|
+
@disasm_setting[:arg_infer]
|
57
80
|
end
|
58
81
|
|
59
82
|
# Convert to raw bytes.
|
60
83
|
# @return [String]
|
61
84
|
# Raw bpf bytes.
|
62
85
|
def asm
|
63
|
-
|
86
|
+
endian = Const::Endian::ENDIAN[arch]
|
87
|
+
[code, jt, jf, k].pack("S#{endian}CCL#{endian}")
|
64
88
|
end
|
65
89
|
|
66
90
|
# Command according to +code+.
|
@@ -44,7 +44,7 @@ module SeccompTools
|
|
44
44
|
option[:ifile] = argv.shift
|
45
45
|
return CLI.show(parser.help) if option[:ifile].nil?
|
46
46
|
|
47
|
-
res = SeccompTools::Asm.asm(input, arch: option[:arch])
|
47
|
+
res = SeccompTools::Asm.asm(input, filename: option[:ifile], arch: option[:arch])
|
48
48
|
output do
|
49
49
|
case option[:format]
|
50
50
|
when :inspect then "#{res.inspect}\n"
|
@@ -8,7 +8,7 @@ module SeccompTools
|
|
8
8
|
module CLI
|
9
9
|
# Base class for handlers.
|
10
10
|
class Base
|
11
|
-
# @return [
|
11
|
+
# @return [{Symbol => Object}] Options.
|
12
12
|
attr_reader :option
|
13
13
|
# @return [Array<String>] Arguments array.
|
14
14
|
attr_reader :argv
|
@@ -25,7 +25,7 @@ module SeccompTools
|
|
25
25
|
|
26
26
|
# Handle show help message.
|
27
27
|
# @return [Boolean]
|
28
|
-
# For decestors to check
|
28
|
+
# For decestors to check whether need to continue.
|
29
29
|
def handle
|
30
30
|
return CLI.show(parser.help) if argv.empty? || %w[-h --help].any? { |h| argv.include?(h) }
|
31
31
|
|
@@ -39,7 +39,7 @@ module SeccompTools
|
|
39
39
|
# @return [String]
|
40
40
|
# String read from file.
|
41
41
|
def input
|
42
|
-
option[:ifile] == '-' ? $stdin.read.force_encoding('ascii-8bit') :
|
42
|
+
option[:ifile] == '-' ? $stdin.read.force_encoding('ascii-8bit') : File.binread(option[:ifile])
|
43
43
|
end
|
44
44
|
|
45
45
|
# Write data to stdout or file(s).
|
@@ -55,7 +55,7 @@ module SeccompTools
|
|
55
55
|
# Write to file, we should disable colorize
|
56
56
|
enabled = Util.colorize_enabled?
|
57
57
|
Util.disable_color! if enabled
|
58
|
-
|
58
|
+
File.binwrite(file_of(option[:ofile], @serial), yield)
|
59
59
|
Util.enable_color! if enabled
|
60
60
|
@serial += 1
|
61
61
|
end
|
@@ -12,6 +12,12 @@ module SeccompTools
|
|
12
12
|
# Usage of this command.
|
13
13
|
USAGE = "disasm - #{SUMMARY}\n\nUsage: seccomp-tools disasm BPF_FILE [options]"
|
14
14
|
|
15
|
+
def initialize(*)
|
16
|
+
super
|
17
|
+
option[:bpf] = true
|
18
|
+
option[:arg_infer] = true
|
19
|
+
end
|
20
|
+
|
15
21
|
# Define option parser.
|
16
22
|
# @return [OptionParser]
|
17
23
|
def parser
|
@@ -20,8 +26,23 @@ module SeccompTools
|
|
20
26
|
opt.on('-o', '--output FILE', 'Output result into FILE instead of stdout.') do |o|
|
21
27
|
option[:ofile] = o
|
22
28
|
end
|
23
|
-
|
24
29
|
option_arch(opt)
|
30
|
+
opt.on('--[no-]bpf', 'Display BPF bytes (code, jt, etc.).',
|
31
|
+
'Default: true') do |f|
|
32
|
+
option[:bpf] = f
|
33
|
+
end
|
34
|
+
opt.on('--[no-]arg-infer', 'Display syscall arguments with parameter names when possible.',
|
35
|
+
'Default: true') do |f|
|
36
|
+
option[:arg_infer] = f
|
37
|
+
end
|
38
|
+
opt.on('--asm-able', 'Output with this flag is a valid input of "seccomp-tools asm".',
|
39
|
+
'By default, "seccomp-tools disasm" is in a human-readable format that easy for analysis.',
|
40
|
+
'Passing this flag can have the output be simplified to a valid input for "seccomp-tools asm".',
|
41
|
+
'This flag implies "--no-bpf --no-arg-infer".',
|
42
|
+
'Default: false') do |_f|
|
43
|
+
option[:bpf] = false
|
44
|
+
option[:arg_infer] = false
|
45
|
+
end
|
25
46
|
end
|
26
47
|
end
|
27
48
|
|
@@ -33,7 +54,10 @@ module SeccompTools
|
|
33
54
|
option[:ifile] = argv.shift
|
34
55
|
return CLI.show(parser.help) if option[:ifile].nil?
|
35
56
|
|
36
|
-
output
|
57
|
+
output do
|
58
|
+
SeccompTools::Disasm.disasm(input, arch: option[:arch], display_bpf: option[:bpf],
|
59
|
+
arg_infer: option[:arg_infer])
|
60
|
+
end
|
37
61
|
end
|
38
62
|
end
|
39
63
|
end
|