seccomp-tools 1.5.0 → 1.6.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa755fd2e7ed97279b094dfa28bd668aef4ee638005f0b38405066502fc8a30d
4
- data.tar.gz: 56b0d51796d97a89f67262c36716833d078ce19ea6b58fbb83c2f8bee88b8685
3
+ metadata.gz: dc7a6ded95dadf7826ae07868793839e0ffa417ed936ae8461ff5bb6b59ff135
4
+ data.tar.gz: 82a0f811819137309ea22d68fe21fec5d48aad7b96f142b981c57478146a47af
5
5
  SHA512:
6
- metadata.gz: 027f57a717cb63fadeefd4a60213115a27ed51bb1eb3e6b58251690ed7c1d42335086ff4448b480296b77d395ba2e8718fa662e227394abfddaf80b19b769b74
7
- data.tar.gz: f6aca254b4a43d6c60d5df7e6694b976de1e56fbb6d6585bd46fd17c0849820d76c81f25bbc872dc785576e05eeaaab5d0276a3f065b730988973c5073ace761
6
+ metadata.gz: 4d630e759687a7641d8cdeb4012a433a4fedc67c2f3e322c4cfaf041428e1199a2c46dd5db50c36a00a79a5b203f648f90345ac4ad6654b0cba93344c6737158
7
+ data.tar.gz: 23a441eb5cf55d3eb18a91a20e4b23a242e7b9b381dab81457ab3b087d9c53847e2d47be161c11305f213e629a23403b806e1a8e31e89adbd90443d1f79ef301
data/README.md CHANGED
@@ -1,26 +1,26 @@
1
1
  [![Build Status](https://github.com/david942j/seccomp-tools/workflows/build/badge.svg)](https://github.com/david942j/seccomp-tools/actions)
2
- [![Dependabot Status](https://api.dependabot.com/badges/status?host=github&repo=david942j/seccomp-tools)](https://dependabot.com)
3
2
  [![Code Climate](https://codeclimate.com/github/david942j/seccomp-tools/badges/gpa.svg)](https://codeclimate.com/github/david942j/seccomp-tools)
4
3
  [![Issue Count](https://codeclimate.com/github/david942j/seccomp-tools/badges/issue_count.svg)](https://codeclimate.com/github/david942j/seccomp-tools)
5
4
  [![Test Coverage](https://codeclimate.com/github/david942j/seccomp-tools/badges/coverage.svg)](https://codeclimate.com/github/david942j/seccomp-tools/coverage)
6
5
  [![Inline docs](https://inch-ci.org/github/david942j/seccomp-tools.svg?branch=master)](https://inch-ci.org/github/david942j/seccomp-tools)
6
+ [![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg)](https://www.rubydoc.info/github/david942j/seccomp-tools/)
7
7
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](http://choosealicense.com/licenses/mit/)
8
8
 
9
9
  # Seccomp Tools
10
10
  Provide powerful tools for seccomp analysis.
11
11
 
12
- This project is targeted to (but not limited to) analyze seccomp sandbox in CTF pwn challenges.
13
- Some features might be CTF-specific, but still useful for analyzing seccomp in real-case.
12
+ This project targets to (but is not limited to) analyze seccomp sandbox in CTF pwn challenges.
13
+ Some features might be CTF-specific, but also useful for analyzing seccomp of real cases.
14
14
 
15
15
  ## Features
16
- * Dump - Automatically dumps seccomp-bpf from execution file(s).
17
- * Disasm - Converts bpf to human readable format.
18
- - Simple decompile.
19
- - Display syscall names and arguments when possible.
16
+ * Dump - Automatically dumps seccomp BPF from execution file(s).
17
+ * Disasm - Converts seccomp BPF to a human readable format.
18
+ - With simple decompilation.
19
+ - With syscall names and arguments whenever possible.
20
20
  - Colorful!
21
- * Asm - Write seccomp rules is so easy!
21
+ * Asm - Makes writing seccomp rules similar to writing codes.
22
22
  * Emu - Emulates seccomp rules.
23
- * Supports multi-architectures.
23
+ * Supports multi-architecture.
24
24
 
25
25
  ## Installation
26
26
 
@@ -76,8 +76,8 @@ $ seccomp-tools dump --help
76
76
 
77
77
  ### dump
78
78
 
79
- Dumps the seccomp bpf from an execution file.
80
- This work is done by the `ptrace` syscall.
79
+ Dumps the seccomp BPF from an execution file.
80
+ This work is done by utilizing the `ptrace` syscall.
81
81
 
82
82
  NOTICE: beware of the execution file will be executed.
83
83
  ```bash
@@ -124,7 +124,7 @@ $ seccomp-tools dump spec/binary/twctf-2016-diary -f raw | xxd
124
124
 
125
125
  ### disasm
126
126
 
127
- Disassembles the seccomp from raw bpf.
127
+ Disassembles the seccomp from raw BPF.
128
128
  ```bash
129
129
  $ xxd spec/data/twctf-2016-diary.bpf | head -n 3
130
130
  # 00000000: 2000 0000 0000 0000 1500 0001 0200 0000 ...............
@@ -170,7 +170,7 @@ $ seccomp-tools asm
170
170
  # -f, --format FORMAT Output format. FORMAT can only be one of <inspect|raw|c_array|c_source|assembly>.
171
171
  # Default: inspect
172
172
  # -a, --arch ARCH Specify architecture.
173
- # Supported architectures are <aarch64|amd64|i386>.
173
+ # Supported architectures are <aarch64|amd64|i386|s390x>.
174
174
  # Default: amd64
175
175
 
176
176
  # Input file for asm
@@ -259,6 +259,83 @@ $ seccomp-tools asm spec/data/libseccomp.asm -f raw | seccomp-tools disasm -
259
259
 
260
260
  ```
261
261
 
262
+ Since v1.6.0 [not released yet], `asm` has switched to using a yacc-based syntax parser, hence supports more flexible and intuitive syntax!
263
+
264
+ ```bash
265
+ $ cat spec/data/example.asm
266
+ # # An example of supported assembly syntax
267
+ # if (A == X)
268
+ # goto next # 'next' is a reserved label, means the next statement ("A = args[0]" in this example)
269
+ # else
270
+ # goto err_label # custom defined label
271
+ # A = args[0]
272
+ # if (
273
+ # A # put a comment here is also valid
274
+ # == 0x123
275
+ # ) goto disallow
276
+ # if (! (A & 0x1337)) # support bang in if-conditions
277
+ # goto 0 # equivalent to 'goto next'
278
+ # else goto 2 # goto $ + 2, 'mem[0] = A' in this example
279
+ # A = sys_number
280
+ # A = instruction_pointer >> 32
281
+ # mem[0] = A
282
+ # A = data[4] # equivalent to 'A = arch'
283
+ # err_label: return ERRNO(1337)
284
+ # disallow:
285
+ # return KILL
286
+
287
+ $ seccomp-tools asm spec/data/example.asm -f raw | seccomp-tools disasm -
288
+ # line CODE JT JF K
289
+ # =================================
290
+ # 0000: 0x1d 0x00 0x07 0x00000000 if (A != X) goto 0008
291
+ # 0001: 0x20 0x00 0x00 0x00000010 A = args[0]
292
+ # 0002: 0x15 0x06 0x00 0x00000123 if (A == 0x123) goto 0009
293
+ # 0003: 0x45 0x02 0x00 0x00001337 if (A & 0x1337) goto 0006
294
+ # 0004: 0x20 0x00 0x00 0x00000000 A = sys_number
295
+ # 0005: 0x20 0x00 0x00 0x0000000c A = instruction_pointer >> 32
296
+ # 0006: 0x02 0x00 0x00 0x00000000 mem[0] = A
297
+ # 0007: 0x20 0x00 0x00 0x00000004 A = arch
298
+ # 0008: 0x06 0x00 0x00 0x00050539 return ERRNO(1337)
299
+ # 0009: 0x06 0x00 0x00 0x00000000 return KILL
300
+
301
+ ```
302
+
303
+ The output of `seccomp-tools disasm <file> --asm-able` is a valid input of `asm`:
304
+ ```bash
305
+ $ seccomp-tools disasm spec/data/x32.bpf --asm-able
306
+ # 0000: A = arch
307
+ # 0001: if (A != ARCH_X86_64) goto 0011
308
+ # 0002: A = sys_number
309
+ # 0003: if (A < 0x40000000) goto 0011
310
+ # 0004: if (A == x32_read) goto 0011
311
+ # 0005: if (A == x32_write) goto 0011
312
+ # 0006: if (A == x32_iopl) goto 0011
313
+ # 0007: if (A != x32_mmap) goto 0011
314
+ # 0008: A = args[0]
315
+ # 0009: if (A == 0x0) goto 0011
316
+ # 0010: return ERRNO(5)
317
+ # 0011: return ALLOW
318
+
319
+
320
+ # disasm then asm then disasm!
321
+ $ seccomp-tools disasm spec/data/x32.bpf --asm-able | seccomp-tools asm - -f raw | seccomp-tools disasm -
322
+ # line CODE JT JF K
323
+ # =================================
324
+ # 0000: 0x20 0x00 0x00 0x00000004 A = arch
325
+ # 0001: 0x15 0x00 0x09 0xc000003e if (A != ARCH_X86_64) goto 0011
326
+ # 0002: 0x20 0x00 0x00 0x00000000 A = sys_number
327
+ # 0003: 0x35 0x00 0x07 0x40000000 if (A < 0x40000000) goto 0011
328
+ # 0004: 0x15 0x06 0x00 0x40000000 if (A == x32_read) goto 0011
329
+ # 0005: 0x15 0x05 0x00 0x40000001 if (A == x32_write) goto 0011
330
+ # 0006: 0x15 0x04 0x00 0x400000ac if (A == x32_iopl) goto 0011
331
+ # 0007: 0x15 0x00 0x03 0x40000009 if (A != x32_mmap) goto 0011
332
+ # 0008: 0x20 0x00 0x00 0x00000010 A = addr # x32_mmap(addr, len, prot, flags, fd, pgoff)
333
+ # 0009: 0x15 0x01 0x00 0x00000000 if (A == 0x0) goto 0011
334
+ # 0010: 0x06 0x00 0x00 0x00050005 return ERRNO(5)
335
+ # 0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
336
+
337
+ ```
338
+
262
339
  ### Emu
263
340
 
264
341
  Emulates seccomp given `sys_nr`, `arg0`, `arg1`, etc.
@@ -268,7 +345,7 @@ $ seccomp-tools emu --help
268
345
  #
269
346
  # Usage: seccomp-tools emu [options] BPF_FILE [sys_nr [arg0 [arg1 ... arg5]]]
270
347
  # -a, --arch ARCH Specify architecture.
271
- # Supported architectures are <aarch64|amd64|i386>.
348
+ # Supported architectures are <aarch64|amd64|i386|s390x>.
272
349
  # Default: amd64
273
350
  # -q, --[no-]quiet Run quietly, only show emulation result.
274
351
 
@@ -301,12 +378,15 @@ $ seccomp-tools emu spec/data/libseccomp.bpf write 0x3
301
378
 
302
379
  ![emu](https://github.com/david942j/seccomp-tools/blob/master/examples/emu-amigo.png?raw=true)
303
380
 
304
- ## Architecture Supported
381
+ ## Supported Architectures
305
382
 
306
383
  - [x] x86_64
307
384
  - [x] x32
308
385
  - [x] x86
309
- - [x] arm64 (Thanks to @saagarjha!)
386
+ - [x] arm64 (@saagarjha)
387
+ - [x] s390x (@iii-i)
388
+
389
+ Pull Requests of adding more architectures support are welcome!
310
390
 
311
391
  ## Development
312
392
 
@@ -327,6 +407,6 @@ I recommend to use [rbenv](https://github.com/rbenv/rbenv) for your Ruby environ
327
407
 
328
408
  ## I Need You
329
409
 
330
- Any suggestion or feature request is welcome!
331
- Feel free to file an issue or send a pull request.
410
+ Any suggestions or feature requests are welcome!
411
+ Feel free to file issues or send pull requests.
332
412
  And, if you like this work, I'll be happy to be [starred](https://github.com/david942j/seccomp-tools/stargazers) :grimacing:
@@ -10,16 +10,18 @@ module SeccompTools
10
10
 
11
11
  # Assembler of seccomp bpf.
12
12
  # @param [String] str
13
- # @param [:amd64, :i386] arch
13
+ # @param [String] filename
14
+ # Only used for error messages.
15
+ # @param [Symbol?] arch
14
16
  # @return [String]
15
- # Raw bpf bytes.
17
+ # Raw BPF bytes.
16
18
  # @example
17
19
  # SeccompTools::Asm.asm(<<-EOS)
18
20
  # # lines start with '#' are comments
19
21
  # A = sys_number # here's a comment, too
20
22
  # A >= 0x40000000 ? dead : next # 'next' is a keyword, denote the next instruction
21
23
  # A == read ? ok : next # custom defined label 'dead' and 'ok'
22
- # A == 1 ? ok : next # SYS_write = 1 in amd64
24
+ # A == 1 ? ok : next # SYS_write = 1 on amd64
23
25
  # return ERRNO(1)
24
26
  # dead:
25
27
  # return KILL
@@ -27,10 +29,10 @@ module SeccompTools
27
29
  # return ALLOW
28
30
  # EOS
29
31
  # #=> <raw binary bytes>
30
- def asm(str, arch: nil)
32
+ def asm(str, filename: '-', arch: nil)
33
+ filename = nil if filename == '-'
31
34
  arch = Util.system_arch if arch.nil?
32
- compiler = Compiler.new(arch)
33
- str.lines.each { |l| compiler.process(l) }
35
+ compiler = Compiler.new(str, filename, arch)
34
36
  compiler.compile!.map(&:asm).join
35
37
  end
36
38
  end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'seccomp-tools/asm/tokenizer'
3
+ require 'seccomp-tools/asm/sasm.tab'
4
+ require 'seccomp-tools/asm/scalar'
5
+ require 'seccomp-tools/asm/scanner'
4
6
  require 'seccomp-tools/bpf'
5
- require 'seccomp-tools/const'
7
+ require 'seccomp-tools/error'
6
8
 
7
9
  module SeccompTools
8
10
  module Asm
@@ -12,74 +14,92 @@ module SeccompTools
12
14
  class Compiler
13
15
  # Instantiate a {Compiler} object.
14
16
  #
17
+ # @param [String] source
18
+ # Input string.
19
+ # @param [String?] filename
20
+ # Only used in error messages.
15
21
  # @param [Symbol] arch
16
22
  # Architecture.
17
- def initialize(arch)
23
+ def initialize(source, filename, arch)
24
+ @scanner = Scanner.new(source, arch, filename: filename)
18
25
  @arch = arch
19
- @insts = []
20
- @labels = {}
21
- @insts_linenum = {}
22
- @input = []
23
- end
24
-
25
- # Before compile assembly codes, process each lines.
26
- #
27
- # With this we can support label in seccomp rules.
28
- # @param [String] line
29
- # One line of seccomp rule.
30
- # @return [void]
31
- def process(line)
32
- @input << line.strip
33
- line = remove_comment(line)
34
- @token = Tokenizer.new(line)
35
- return if line.empty?
36
-
37
- begin
38
- res = case line
39
- when /\?/ then cmp
40
- when /^#{Tokenizer::LABEL_REGEXP}:/ then define_label
41
- when /^return/ then ret
42
- when /^A\s*=\s*-A/ then alu_neg
43
- when /^(A|X)\s*=[^=]/ then assign
44
- when /^mem\[\d+\]\s*=\s*(A|X)/ then store
45
- when /^A\s*.{1,2}=/ then alu
46
- when /^(goto|jmp|jump)/ then jmp_abs
47
- end
48
- rescue ArgumentError => e
49
- invalid(@input.size - 1, e.message)
50
- end
51
- invalid(@input.size - 1) if res.nil?
52
- if res.first == :label
53
- @labels[res.last] = @insts.size
54
- else
55
- @insts << res
56
- @insts_linenum[@insts.size - 1] = @input.size - 1
57
- end
26
+ @symbols = {}
58
27
  end
59
28
 
60
29
  # Compiles the processed instructions.
61
30
  #
62
31
  # @return [Array<SeccompTools::BPF>]
63
32
  # Returns the compiled {BPF} array.
64
- # @raise [ArgumentError]
33
+ # @raise [SeccompTools::Error]
65
34
  # Raises the error found during compilation.
66
35
  def compile!
67
- @insts.map.with_index do |inst, idx|
36
+ @scanner.validate!
37
+ statements = SeccompAsmParser.new(@scanner).parse
38
+ fixup_symbols(statements)
39
+ resolve_symbols(statements)
40
+
41
+ statements.map.with_index do |s, idx|
68
42
  @line = idx
69
- case inst.first
70
- when :assign then compile_assign(inst[1], inst[2])
71
- when :alu then compile_alu(inst[1], inst[2])
72
- when :ret then compile_ret(inst[1])
73
- when :cmp then compile_cmp(inst[1], inst[2], inst[3], inst[4])
74
- when :jmp_abs then compile_jmp_abs(inst[1])
43
+ case s.type
44
+ when :alu then emit_alu(*s.data)
45
+ when :assign then emit_assign(*s.data)
46
+ when :if then emit_cmp(*s.data)
47
+ when :ret then emit_ret(*s.data)
75
48
  end
76
49
  end
77
- rescue ArgumentError => e
78
- invalid(@insts_linenum[@line], e.message)
79
50
  end
80
51
 
81
52
  private
82
53
 
54
+ def fixup_symbols(statements)
55
+ statements.each_with_index do |statement, idx|
56
+ statement.symbols.uniq(&:str).each do |s|
57
+ if @symbols[s.str]
58
+ msg = @scanner.format_error(s, "duplicate label '#{s.str}'")
59
+ msg += @scanner.format_error(@symbols[s.str][0], 'previously defined here')
60
+ raise SeccompTools::DuplicateLabelError, msg
61
+ end
62
+
63
+ @symbols[s.str] = [s, idx]
64
+ end
65
+ end
66
+ end
67
+
68
+ def resolve_symbols(statements)
69
+ statements.each_with_index do |statement, idx|
70
+ next if statement.type != :if
71
+
72
+ jt = resolve_symbol(idx, statement.data[1])
73
+ jf = resolve_symbol(idx, statement.data[2])
74
+ statement.data[1] = [jt, statement.data[1]]
75
+ statement.data[2] = [jf, statement.data[2]]
76
+ end
77
+ end
78
+
79
+ # @param [Integer] index
80
+ # @param [SeccompTools::Asm::Token, :next] sym
81
+ def resolve_symbol(index, sym)
82
+ return 0 if sym.is_a?(Symbol) && sym == :next
83
+
84
+ str = sym.str
85
+ return 0 if str == 'next'
86
+
87
+ if @symbols[str].nil?
88
+ # special case - goto <n> can be considered as $+1+<n>
89
+ return str.to_i if str == str.to_i.to_s
90
+
91
+ raise SeccompTools::UndefinedLabelError,
92
+ @scanner.format_error(sym, "Cannot find label '#{str}'")
93
+ end
94
+
95
+ (@symbols[str][1] - index - 1).tap do |dis|
96
+ if dis.negative?
97
+ raise SeccompTools::BackwardJumpError,
98
+ @scanner.format_error(sym, "Does not support backward jumping to '#{str}'")
99
+ end
100
+ end
101
+ end
102
+
83
103
  # Emits a raw BPF object.
84
104
  #
85
105
  # @return [BPF]
@@ -98,201 +118,91 @@ module SeccompTools
98
118
  BPF.new({ code: code, k: k, jt: jt, jf: jf }, @arch, @line)
99
119
  end
100
120
 
121
+ # A = -A
101
122
  # A = X / X = A
102
123
  # mem[i] = <A|X>
103
- # <A|X> = 123|sys_const
104
124
  # A = len
105
- # <A|X> = mem[i]
106
- # A = args_h[i]|args[i]|sys_number|arch
107
- # A = data[4 * i]
108
- def compile_assign(dst, src)
125
+ # <A|X> = <immi|mem[i]|data[i]>
126
+ def emit_assign(dst, src)
127
+ return emit(:alu, :neg) if src.is_a?(Symbol) && src == :neg
109
128
  # misc txa / tax
110
- return compile_assign_misc(dst, src) if (dst == :a && src == :x) || (dst == :x && src == :a)
129
+ return emit(:misc, dst.a? ? :txa : :tax) if (dst.a? && src.x?) || (dst.x? && src.a?)
111
130
  # case of st / stx
112
- return emit(src == :x ? :stx : :st, k: dst.last) if dst.is_a?(Array) && dst.first == :mem
131
+ return emit(src.x? ? :stx : :st, k: dst.val) if dst.mem?
113
132
 
114
- src = evaluate(src)
115
- ld = dst == :x ? :ldx : :ld
116
- # <A|X> = <immi>
117
- return emit(ld, :imm, k: src) if src.is_a?(Integer)
118
- # now src must be in form [:len/:mem/:data, num]
119
- return emit(ld, src.first, k: src.last) if src.first == :mem || src.first == :len
120
- # check if num is multiple of 4
121
- raise ArgumentError, 'Index of data[] must be a multiple of 4' if src.last % 4 != 0
122
-
123
- emit(ld, :abs, k: src.last)
133
+ emit_ld(dst, src)
124
134
  end
125
135
 
126
- def compile_assign_misc(dst, _src)
127
- emit(:misc, dst == :a ? :txa : :tax)
136
+ def emit_ld(dst, src)
137
+ ld = dst.x? ? :ldx : :ld
138
+ return emit(ld, :len, k: 0) if src.len?
139
+ return emit(ld, :imm, k: src.to_i) if src.const?
140
+ return emit(ld, :mem, k: src.val) if src.mem?
141
+
142
+ emit(ld, :abs, k: src.val) if src.data?
128
143
  end
129
144
 
130
- def compile_alu(op, val)
131
- return emit(:alu, :neg) if op == :neg
145
+ def emit_alu(op, val)
146
+ src, k = val.x? ? [:x, 0] : [:k, val.to_i]
147
+ emit(:alu, convert_alu_op(op), src, k: k)
148
+ end
132
149
 
133
- val = evaluate(val)
134
- src = val == :x ? :x : :k
135
- val = 0 if val == :x
136
- emit(:alu, op, src, k: val)
150
+ def convert_alu_op(op)
151
+ {
152
+ '+' => :add,
153
+ '-' => :sub,
154
+ '*' => :mul,
155
+ '/' => :div,
156
+ '|' => :or,
157
+ '&' => :and,
158
+ '<<' => :lsh,
159
+ '>>' => :rsh,
160
+ '^' => :xor
161
+ }[op[0..-2]]
137
162
  end
138
163
 
139
- def compile_ret(val)
140
- if val == :a
164
+ def emit_ret(val)
165
+ if val.a?
141
166
  src = :a
142
167
  val = 0
143
168
  end
144
- emit(:ret, src, k: val)
145
- end
146
-
147
- def compile_jmp_abs(target)
148
- targ = label_offset(target)
149
- emit(:jmp, :ja, k: targ)
169
+ emit(:ret, src, k: val.to_i)
150
170
  end
151
171
 
152
- # Compiles comparison.
153
- def compile_cmp(op, val, jt, jf)
154
- jt = label_offset(jt)
155
- jf = label_offset(jf)
156
- val = evaluate(val)
157
- src = val == :x ? :x : :k
158
- val = 0 if val == :x
159
- emit(:jmp, op, src, jt: jt, jf: jf, k: val)
160
- end
161
-
162
- def label_offset(label)
163
- return label if label.is_a?(Integer)
164
- return 0 if label == 'next'
165
- raise ArgumentError, "Undefined label #{label.inspect}" if @labels[label].nil?
166
- raise ArgumentError, "Does not support backward jumping to #{label.inspect}" if @labels[label] < @line
167
-
168
- @labels[label] - @line - 1
169
- end
170
-
171
- def evaluate(val)
172
- return val if val.is_a?(Integer) || val == :x || val == :a
173
-
174
- # keywords
175
- val = case val
176
- when 'sys_number' then [:data, 0]
177
- when 'arch' then [:data, 4]
178
- when 'len' then [:len, 0]
179
- else val
180
- end
181
- return eval_constants(val) if val.is_a?(String)
182
-
183
- # remains are [:mem/:data/:args/:args_h, <num>]
184
- # first convert args to data
185
- val = [:data, val.last * 8 + 16] if val.first == :args
186
- val = [:data, val.last * 8 + 20] if val.first == :args_h
187
- val
188
- end
172
+ # The farthest distance of a relative jump in BPF.
173
+ JUMP_DISTANCE_MAX = 255
189
174
 
190
- def eval_constants(str)
191
- Const::Syscall.const_get(@arch.upcase.to_sym)[str.to_sym] ||
192
- Const::Audit::ARCH[str] ||
193
- raise(ArgumentError, "Invalid constant: #{str.inspect}")
194
- end
195
-
196
- attr_reader :token
197
-
198
- # <goto|jmp|jump> <label|Integer>
199
- def jmp_abs
200
- token.fetch('goto') ||
201
- token.fetch('jmp') ||
202
- token.fetch('jump') ||
203
- raise(ArgumentError, "Invalid jump alias: #{token.cur.inspect}")
204
- target = token.fetch!(:goto)
205
- [:jmp_abs, target]
206
- end
175
+ def emit_cmp(cmp, jt_sym, jf_sym)
176
+ jt = jt_sym[0]
177
+ jf = jf_sym[0]
178
+ jop, jt, jf = convert_jmp_op(cmp, jt, jf)
179
+ return emit(:jmp, :none, 0, jt: 0, jf: 0, k: jt) if jop == :ja || jt == jf
207
180
 
208
- # A <comparison> <sys_str|X|Integer> ? <label|Integer> : <label|Integer>
209
- def cmp
210
- token.fetch!('A')
211
- op = token.fetch!(:comparison)
212
- dst = token.fetch!(:sys_num_x)
213
- token.fetch!('?')
214
- jt = token.fetch!(:goto)
215
- token.fetch!(':')
216
- jf = token.fetch!(:goto)
217
- convert = {
218
- :< => :>=,
219
- :<= => :>,
220
- :!= => :==
221
- }
222
- if convert[op]
223
- op = convert[op]
224
- jt, jf = jf, jt
181
+ [jt_sym, jf_sym].each do |dis, sym|
182
+ if dis > JUMP_DISTANCE_MAX
183
+ raise SeccompTools::LongJumpError,
184
+ @scanner.format_error(sym, "Does not support jumping farther than #{JUMP_DISTANCE_MAX}, got: #{dis}")
185
+ end
186
+ end
187
+ val = cmp[1]
188
+ src = val.x? ? :x : :k
189
+ k = val.x? ? 0 : val.to_i
190
+ emit(:jmp, jop, src, jt: jt, jf: jf, k: k)
191
+ end
192
+
193
+ # == != >= <= > < &
194
+ def convert_jmp_op(cmp, jt, jf)
195
+ return [:ja, jt, jf] if cmp.nil?
196
+
197
+ case cmp[0]
198
+ when '==' then [:jeq, jt, jf]
199
+ when '!=' then [:jeq, jf, jt]
200
+ when '>=' then [:jge, jt, jf]
201
+ when '<=' then [:jgt, jf, jt]
202
+ when '>' then [:jgt, jt, jf]
203
+ when '<' then [:jge, jf, jt]
204
+ when '&' then [:jset, jt, jf]
225
205
  end
226
- op = {
227
- :>= => :jge,
228
- :> => :jgt,
229
- :== => :jeq
230
- }[op]
231
- [:cmp, op, dst, jt, jf]
232
- end
233
-
234
- def ret
235
- token.fetch!('return')
236
- [:ret, token.fetch!(:ret)]
237
- end
238
-
239
- # possible types after '=':
240
- # A = X
241
- # X = A
242
- # A = 123
243
- # A = data[i]
244
- # A = mem[i]
245
- # A = args[i]
246
- # A = sys_number|arch
247
- # A = len
248
- def assign
249
- dst = token.fetch!(:ax)
250
- token.fetch!('=')
251
- src = token.fetch(:ax) ||
252
- token.fetch(:sys_num_x) ||
253
- token.fetch(:ary) ||
254
- token.fetch('sys_number') ||
255
- token.fetch('arch') ||
256
- token.fetch('len') ||
257
- raise(ArgumentError, "Invalid source: #{token.cur.inspect}")
258
- [:assign, dst, src]
259
- end
260
-
261
- # returns same format as assign
262
- def store
263
- [:assign, token.fetch!(:ary), token.fetch!('=') && token.fetch!(:ax)]
264
- end
265
-
266
- def define_label
267
- name = token.fetch!(:goto)
268
- token.fetch(':')
269
- [:label, name]
270
- end
271
-
272
- # A op= sys_num_x
273
- def alu
274
- token.fetch!('A')
275
- op = token.fetch!(:alu_op)
276
- token.fetch!('=')
277
- src = token.fetch!(:sys_num_x)
278
- [:alu, op, src]
279
- end
280
-
281
- # A = -A
282
- def alu_neg
283
- %i[alu neg]
284
- end
285
-
286
- def remove_comment(line)
287
- line = line.to_s.dup
288
- line.slice!(/#.*\Z/m)
289
- line.strip
290
- end
291
-
292
- def invalid(line, extra_msg = nil)
293
- message = "Invalid instruction at line #{line + 1}: #{@input[line].inspect}"
294
- message += "\nError: #{extra_msg}" if extra_msg
295
- raise ArgumentError, message
296
206
  end
297
207
  end
298
208
  end