seccomp-tools 1.4.0 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +105 -17
- data/ext/ptrace/ptrace.c +56 -4
- 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 +3 -3
- data/lib/seccomp-tools/cli/base.rb +4 -4
- data/lib/seccomp-tools/cli/disasm.rb +27 -3
- data/lib/seccomp-tools/cli/dump.rb +4 -2
- data/lib/seccomp-tools/cli/emu.rb +1 -4
- data/lib/seccomp-tools/const.rb +37 -3
- data/lib/seccomp-tools/consts/sys_nr/aarch64.rb +284 -0
- data/lib/seccomp-tools/consts/sys_nr/amd64.rb +5 -1
- data/lib/seccomp-tools/consts/sys_nr/i386.rb +14 -14
- 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 +16 -9
- data/lib/seccomp-tools/dumper.rb +12 -3
- data/lib/seccomp-tools/emulator.rb +5 -9
- data/lib/seccomp-tools/error.rb +31 -0
- data/lib/seccomp-tools/instruction/alu.rb +1 -1
- data/lib/seccomp-tools/instruction/base.rb +1 -1
- data/lib/seccomp-tools/instruction/jmp.rb +28 -10
- data/lib/seccomp-tools/instruction/ld.rb +5 -3
- data/lib/seccomp-tools/syscall.rb +23 -13
- data/lib/seccomp-tools/templates/asm.s390x.asm +26 -0
- data/lib/seccomp-tools/util.rb +3 -1
- data/lib/seccomp-tools/version.rb +1 -1
- metadata +38 -9
- 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).
|
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+.
|
@@ -10,7 +10,7 @@ module SeccompTools
|
|
10
10
|
# Summary of this command.
|
11
11
|
SUMMARY = 'Seccomp bpf assembler.'
|
12
12
|
# Usage of this command.
|
13
|
-
USAGE =
|
13
|
+
USAGE = "asm - #{SUMMARY}\n\nUsage: seccomp-tools asm IN_FILE [options]"
|
14
14
|
|
15
15
|
def initialize(*)
|
16
16
|
super
|
@@ -44,10 +44,10 @@ 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
|
-
when :inspect then res.inspect
|
50
|
+
when :inspect then "#{res.inspect}\n"
|
51
51
|
when :raw then res
|
52
52
|
when :c_array, :carray then "unsigned char bpf[] = {#{res.bytes.join(',')}};\n"
|
53
53
|
when :c_source then SeccompTools::Util.template('asm.c').sub('<TO_BE_REPLACED>', res.bytes.join(','))
|
@@ -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] == '-' ?
|
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
|