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 +4 -4
- data/README.md +98 -18
- data/lib/seccomp-tools/asm/asm.rb +8 -6
- data/lib/seccomp-tools/asm/compiler.rb +136 -226
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dc7a6ded95dadf7826ae07868793839e0ffa417ed936ae8461ff5bb6b59ff135
|
4
|
+
data.tar.gz: 82a0f811819137309ea22d68fe21fec5d48aad7b96f142b981c57478146a47af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d630e759687a7641d8cdeb4012a433a4fedc67c2f3e322c4cfaf041428e1199a2c46dd5db50c36a00a79a5b203f648f90345ac4ad6654b0cba93344c6737158
|
7
|
+
data.tar.gz: 23a441eb5cf55d3eb18a91a20e4b23a242e7b9b381dab81457ab3b087d9c53847e2d47be161c11305f213e629a23403b806e1a8e31e89adbd90443d1f79ef301
|
data/README.md
CHANGED
@@ -1,26 +1,26 @@
|
|
1
1
|
[](https://github.com/david942j/seccomp-tools/actions)
|
2
|
-
[](https://dependabot.com)
|
3
2
|
[](https://codeclimate.com/github/david942j/seccomp-tools)
|
4
3
|
[](https://codeclimate.com/github/david942j/seccomp-tools)
|
5
4
|
[](https://codeclimate.com/github/david942j/seccomp-tools/coverage)
|
6
5
|
[](https://inch-ci.org/github/david942j/seccomp-tools)
|
6
|
+
[](https://www.rubydoc.info/github/david942j/seccomp-tools/)
|
7
7
|
[](http://choosealicense.com/licenses/mit/)
|
8
8
|
|
9
9
|
# Seccomp Tools
|
10
10
|
Provide powerful tools for seccomp analysis.
|
11
11
|
|
12
|
-
This project
|
13
|
-
Some features might be CTF-specific, but
|
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
|
17
|
-
* Disasm - Converts
|
18
|
-
-
|
19
|
-
-
|
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 -
|
21
|
+
* Asm - Makes writing seccomp rules similar to writing codes.
|
22
22
|
* Emu - Emulates seccomp rules.
|
23
|
-
* Supports multi-
|
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
|
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
|
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
|

|
303
380
|
|
304
|
-
##
|
381
|
+
## Supported Architectures
|
305
382
|
|
306
383
|
- [x] x86_64
|
307
384
|
- [x] x32
|
308
385
|
- [x] x86
|
309
|
-
- [x] arm64 (
|
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
|
331
|
-
Feel free to file
|
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 [
|
13
|
+
# @param [String] filename
|
14
|
+
# Only used for error messages.
|
15
|
+
# @param [Symbol?] arch
|
14
16
|
# @return [String]
|
15
|
-
# Raw
|
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
|
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/
|
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/
|
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
|
-
@
|
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 [
|
33
|
+
# @raise [SeccompTools::Error]
|
65
34
|
# Raises the error found during compilation.
|
66
35
|
def compile!
|
67
|
-
@
|
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
|
70
|
-
when :
|
71
|
-
when :
|
72
|
-
when :
|
73
|
-
when :
|
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
|
-
|
107
|
-
|
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
|
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
|
131
|
+
return emit(src.x? ? :stx : :st, k: dst.val) if dst.mem?
|
113
132
|
|
114
|
-
|
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
|
127
|
-
|
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
|
131
|
-
|
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
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
140
|
-
if val
|
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
|
-
#
|
153
|
-
|
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
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
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
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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
|