seccomp-tools 1.6.2 → 1.7.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/CHANGELOG.md +153 -0
- data/README.md +263 -42
- data/completions/_seccomp-tools +83 -0
- data/completions/seccomp-tools.bash +59 -0
- data/completions/seccomp-tools.fish +52 -0
- data/ext/ptrace/ptrace.c +2 -2
- data/lib/seccomp-tools/asm/asm.rb +9 -4
- data/lib/seccomp-tools/asm/compiler.rb +32 -2
- data/lib/seccomp-tools/asm/sasm.tab.rb +27 -19
- data/lib/seccomp-tools/asm/sasm.y +15 -7
- data/lib/seccomp-tools/asm/scalar.rb +50 -7
- data/lib/seccomp-tools/asm/scanner.rb +33 -1
- data/lib/seccomp-tools/asm/statement.rb +14 -5
- data/lib/seccomp-tools/asm/token.rb +19 -1
- data/lib/seccomp-tools/audit/catalog.rb +66 -0
- data/lib/seccomp-tools/audit/checks/arch_unchecked.rb +41 -0
- data/lib/seccomp-tools/audit/checks/dangerous_allow.rb +34 -0
- data/lib/seccomp-tools/audit/checks/orw_chain.rb +41 -0
- data/lib/seccomp-tools/audit/checks/permissive_default.rb +29 -0
- data/lib/seccomp-tools/audit/checks/syscall_alt_gap.rb +44 -0
- data/lib/seccomp-tools/audit/checks/x32_guard.rb +46 -0
- data/lib/seccomp-tools/audit/checks.rb +42 -0
- data/lib/seccomp-tools/audit/finding.rb +25 -0
- data/lib/seccomp-tools/audit/policy.rb +126 -0
- data/lib/seccomp-tools/audit/report.rb +98 -0
- data/lib/seccomp-tools/audit.rb +48 -0
- data/lib/seccomp-tools/bpf.rb +27 -17
- data/lib/seccomp-tools/cli/asm.rb +6 -2
- data/lib/seccomp-tools/cli/audit.rb +86 -0
- data/lib/seccomp-tools/cli/base.rb +51 -8
- data/lib/seccomp-tools/cli/cli.rb +9 -3
- data/lib/seccomp-tools/cli/completion.rb +40 -0
- data/lib/seccomp-tools/cli/disasm.rb +9 -5
- data/lib/seccomp-tools/cli/dump.rb +37 -52
- data/lib/seccomp-tools/cli/dumpable.rb +79 -0
- data/lib/seccomp-tools/cli/emu.rb +20 -5
- data/lib/seccomp-tools/cli/explain.rb +51 -0
- data/lib/seccomp-tools/cli/filter_input.rb +130 -0
- data/lib/seccomp-tools/const.rb +98 -15
- data/lib/seccomp-tools/consts/sys_nr/riscv64.rb +332 -0
- data/lib/seccomp-tools/disasm/disasm.rb +30 -12
- data/lib/seccomp-tools/dumper.rb +65 -33
- data/lib/seccomp-tools/emulator.rb +40 -15
- data/lib/seccomp-tools/error.rb +4 -2
- data/lib/seccomp-tools/explain/analysis.rb +67 -0
- data/lib/seccomp-tools/explain/path_facts.rb +110 -0
- data/lib/seccomp-tools/explain/qword.rb +204 -0
- data/lib/seccomp-tools/explain/renderer.rb +128 -0
- data/lib/seccomp-tools/explain/summary.rb +218 -0
- data/lib/seccomp-tools/explain/verdict.rb +44 -0
- data/lib/seccomp-tools/explain.rb +38 -0
- data/lib/seccomp-tools/instruction/alu.rb +14 -9
- data/lib/seccomp-tools/instruction/base.rb +39 -10
- data/lib/seccomp-tools/instruction/jmp.rb +50 -23
- data/lib/seccomp-tools/instruction/ld.rb +46 -21
- data/lib/seccomp-tools/instruction/ldx.rb +4 -3
- data/lib/seccomp-tools/instruction/misc.rb +11 -9
- data/lib/seccomp-tools/instruction/ret.rb +12 -6
- data/lib/seccomp-tools/instruction/st.rb +15 -6
- data/lib/seccomp-tools/instruction/stx.rb +4 -3
- data/lib/seccomp-tools/logger.rb +14 -1
- data/lib/seccomp-tools/symbolic/constraint.rb +80 -0
- data/lib/seccomp-tools/symbolic/executor.rb +210 -0
- data/lib/seccomp-tools/symbolic/expr.rb +185 -0
- data/lib/seccomp-tools/symbolic/state.rb +82 -0
- data/lib/seccomp-tools/syscall.rb +72 -20
- data/lib/seccomp-tools/util.rb +70 -11
- data/lib/seccomp-tools/version.rb +1 -1
- data/lib/seccomp-tools.rb +10 -1
- metadata +36 -4
- data/lib/seccomp-tools/disasm/context.rb +0 -171
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# bash completion for seccomp-tools
|
|
2
|
+
#
|
|
3
|
+
# Load it with either:
|
|
4
|
+
# eval "$(seccomp-tools completion bash)" # in ~/.bashrc
|
|
5
|
+
# or drop this file into a bash-completion directory named `seccomp-tools`, e.g.
|
|
6
|
+
# ~/.local/share/bash-completion/completions/seccomp-tools
|
|
7
|
+
|
|
8
|
+
_seccomp_tools() {
|
|
9
|
+
local cur prev cmd
|
|
10
|
+
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
11
|
+
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
12
|
+
|
|
13
|
+
local commands="asm audit completion disasm dump emu explain"
|
|
14
|
+
local arches="aarch64 amd64 i386 riscv64 s390x"
|
|
15
|
+
|
|
16
|
+
# Position 1: the subcommand.
|
|
17
|
+
if [[ $COMP_CWORD -eq 1 ]]; then
|
|
18
|
+
COMPREPLY=( $(compgen -W "$commands --help --version" -- "$cur") )
|
|
19
|
+
return
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
cmd="${COMP_WORDS[1]}"
|
|
23
|
+
|
|
24
|
+
# The previous word expects a value: complete just that value.
|
|
25
|
+
case "$prev" in
|
|
26
|
+
-a|--arch) COMPREPLY=( $(compgen -W "$arches" -- "$cur") ); return ;;
|
|
27
|
+
-o|--output) COMPREPLY=( $(compgen -f -- "$cur") ); return ;;
|
|
28
|
+
-f|--format)
|
|
29
|
+
case "$cmd" in
|
|
30
|
+
asm) COMPREPLY=( $(compgen -W "inspect raw c_array c_source assembly" -- "$cur") ) ;;
|
|
31
|
+
audit) COMPREPLY=( $(compgen -W "human json" -- "$cur") ) ;;
|
|
32
|
+
dump) COMPREPLY=( $(compgen -W "disasm raw inspect" -- "$cur") ) ;;
|
|
33
|
+
esac
|
|
34
|
+
return ;;
|
|
35
|
+
esac
|
|
36
|
+
|
|
37
|
+
if [[ $cmd == completion ]]; then
|
|
38
|
+
COMPREPLY=( $(compgen -W "bash zsh fish" -- "$cur") )
|
|
39
|
+
return
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
# Otherwise: this subcommand's flags (when typing a -flag) or a file.
|
|
43
|
+
local opts="-h --help"
|
|
44
|
+
case "$cmd" in
|
|
45
|
+
asm) opts+=" -o --output -f --format -a --arch" ;;
|
|
46
|
+
disasm) opts+=" -o --output -a --arch --bpf --no-bpf --arg-infer --no-arg-infer --asm-able" ;;
|
|
47
|
+
dump) opts+=" -c --sh-exec -l --limit -p --pid -t --timeout -f --format -o --output" ;;
|
|
48
|
+
emu) opts+=" -a --arch -q --no-quiet -i --ip" ;;
|
|
49
|
+
explain) opts+=" -c --sh-exec -l --limit -p --pid -t --timeout -a --arch" ;;
|
|
50
|
+
audit) opts+=" -c --sh-exec -l --limit -p --pid -t --timeout -a --arch -f --format" ;;
|
|
51
|
+
esac
|
|
52
|
+
|
|
53
|
+
if [[ $cur == -* ]]; then
|
|
54
|
+
COMPREPLY=( $(compgen -W "$opts" -- "$cur") )
|
|
55
|
+
else
|
|
56
|
+
COMPREPLY=( $(compgen -f -- "$cur") )
|
|
57
|
+
fi
|
|
58
|
+
}
|
|
59
|
+
complete -F _seccomp_tools seccomp-tools
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# fish completion for seccomp-tools
|
|
2
|
+
#
|
|
3
|
+
# Load it with either:
|
|
4
|
+
# seccomp-tools completion fish | source # in ~/.config/fish/config.fish
|
|
5
|
+
# or drop this file into ~/.config/fish/completions/seccomp-tools.fish
|
|
6
|
+
|
|
7
|
+
# Disable file completion by default; re-enabled per option/positional below.
|
|
8
|
+
complete -c seccomp-tools -f
|
|
9
|
+
|
|
10
|
+
# Subcommands (offered only when none has been given yet).
|
|
11
|
+
complete -c seccomp-tools -n __fish_use_subcommand -a asm -d 'Seccomp bpf assembler'
|
|
12
|
+
complete -c seccomp-tools -n __fish_use_subcommand -a audit -d 'Assess a filter for weaknesses and escape routes'
|
|
13
|
+
complete -c seccomp-tools -n __fish_use_subcommand -a completion -d 'Print a shell completion script'
|
|
14
|
+
complete -c seccomp-tools -n __fish_use_subcommand -a disasm -d 'Disassemble seccomp bpf'
|
|
15
|
+
complete -c seccomp-tools -n __fish_use_subcommand -a dump -d 'Automatically dump seccomp bpf from executable(s)'
|
|
16
|
+
complete -c seccomp-tools -n __fish_use_subcommand -a emu -d 'Emulate seccomp rules'
|
|
17
|
+
complete -c seccomp-tools -n __fish_use_subcommand -a explain -d 'Summarize a filter as a per-action policy'
|
|
18
|
+
complete -c seccomp-tools -n __fish_use_subcommand -l version -d 'Show version'
|
|
19
|
+
complete -c seccomp-tools -s h -l help -d 'Show help'
|
|
20
|
+
|
|
21
|
+
# --arch, shared by the analysis commands.
|
|
22
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from asm disasm emu explain audit' \
|
|
23
|
+
-s a -l arch -x -a 'aarch64 amd64 i386 riscv64 s390x' -d Architecture
|
|
24
|
+
|
|
25
|
+
# --format, whose valid values differ per command.
|
|
26
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from asm' -s f -l format -x -a 'inspect raw c_array c_source assembly' -d 'Output format'
|
|
27
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from dump' -s f -l format -x -a 'disasm raw inspect' -d 'Output format'
|
|
28
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from audit' -s f -l format -x -a 'human json' -d 'Output format'
|
|
29
|
+
|
|
30
|
+
# --output takes a file.
|
|
31
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from asm disasm dump' -s o -l output -r -d 'Write output to FILE'
|
|
32
|
+
|
|
33
|
+
# Options shared by the commands that read from a process.
|
|
34
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from dump explain audit' -s c -l sh-exec -x -d 'Run command via sh and analyze its seccomp'
|
|
35
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from dump explain audit' -s p -l pid -x -d 'Analyze a running process'
|
|
36
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from dump explain audit' -s l -l limit -x -d 'Analyze only the first N filters'
|
|
37
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from dump explain audit' -s t -l timeout -x -d 'Timeout in seconds'
|
|
38
|
+
|
|
39
|
+
# disasm-only flags.
|
|
40
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from disasm' -l asm-able -d 'Emit output that is valid input for asm'
|
|
41
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from disasm' -l no-bpf -d 'Hide the raw BPF bytes'
|
|
42
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from disasm' -l no-arg-infer -d 'Do not infer argument names'
|
|
43
|
+
|
|
44
|
+
# emu-only flags.
|
|
45
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from emu' -s i -l ip -x -d 'Set the instruction pointer'
|
|
46
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from emu' -s q -l quiet -d 'Only show the emulation result'
|
|
47
|
+
|
|
48
|
+
# completion takes a shell name.
|
|
49
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish' -d Shell
|
|
50
|
+
|
|
51
|
+
# The commands whose positional argument is a file/executable get file completion.
|
|
52
|
+
complete -c seccomp-tools -n '__fish_seen_subcommand_from asm disasm dump emu explain audit' -F
|
data/ext/ptrace/ptrace.c
CHANGED
|
@@ -48,9 +48,9 @@ ptrace_peekuser(VALUE _mod, VALUE pid, VALUE off, VALUE _data, VALUE bits) {
|
|
|
48
48
|
uint32_t val32;
|
|
49
49
|
uint64_t val64;
|
|
50
50
|
} val;
|
|
51
|
-
// Dynamically allocate a buffer to store registers-well, at least enough
|
|
51
|
+
// Dynamically allocate a buffer to store registers - well, at least enough
|
|
52
52
|
// registers to reach the offset we want. Normally we'd want to use
|
|
53
|
-
// user_pt_regs or similar, but it's difficult to find it available in
|
|
53
|
+
// user_pt_regs or similar, but it's difficult to find it available in
|
|
54
54
|
// the same header across different versions of Linux or libcs, or even with
|
|
55
55
|
// the same *name*, so this is the compromise.
|
|
56
56
|
size_t size = offset + width;
|
|
@@ -8,19 +8,24 @@ module SeccompTools
|
|
|
8
8
|
module Asm
|
|
9
9
|
module_function
|
|
10
10
|
|
|
11
|
-
#
|
|
11
|
+
# Compiles seccomp assembly into raw BPF bytes.
|
|
12
12
|
# @param [String] str
|
|
13
|
+
# The assembly source to be compiled.
|
|
13
14
|
# @param [String] filename
|
|
14
15
|
# Only used for error messages.
|
|
15
16
|
# @param [Symbol?] arch
|
|
17
|
+
# Target architecture, must be one of {SeccompTools::Util.supported_archs}.
|
|
18
|
+
# Defaults to {SeccompTools::Util.system_arch} when +nil+.
|
|
16
19
|
# @return [String]
|
|
17
20
|
# Raw BPF bytes.
|
|
21
|
+
# @raise [SeccompTools::Error]
|
|
22
|
+
# If +str+ is not valid seccomp assembly.
|
|
18
23
|
# @example
|
|
19
24
|
# SeccompTools::Asm.asm(<<-EOS)
|
|
20
|
-
# # lines
|
|
25
|
+
# # lines starting with '#' are comments
|
|
21
26
|
# A = sys_number # here's a comment, too
|
|
22
|
-
# A >= 0x40000000 ? dead : next # 'next' is a keyword,
|
|
23
|
-
# A == read ? ok : next # custom
|
|
27
|
+
# A >= 0x40000000 ? dead : next # 'next' is a keyword, denoting the next instruction
|
|
28
|
+
# A == read ? ok : next # custom-defined labels 'dead' and 'ok'
|
|
24
29
|
# A == 1 ? ok : next # SYS_write = 1 on amd64
|
|
25
30
|
# return ERRNO(1)
|
|
26
31
|
# dead:
|
|
@@ -31,10 +31,21 @@ module SeccompTools
|
|
|
31
31
|
|
|
32
32
|
# Compiles the processed instructions.
|
|
33
33
|
#
|
|
34
|
+
# Scans and parses the source, resolves the labels into relative jump distances, then emits
|
|
35
|
+
# one {BPF} per statement.
|
|
36
|
+
#
|
|
34
37
|
# @return [Array<SeccompTools::BPF>]
|
|
35
38
|
# Returns the compiled {BPF} array.
|
|
36
|
-
# @raise [SeccompTools::
|
|
37
|
-
#
|
|
39
|
+
# @raise [SeccompTools::UnrecognizedTokenError]
|
|
40
|
+
# If the source contains text that is not valid assembly.
|
|
41
|
+
# @raise [SeccompTools::ParseError]
|
|
42
|
+
# If the tokens do not form valid statements.
|
|
43
|
+
# @raise [SeccompTools::DuplicateLabelError]
|
|
44
|
+
# If a label is defined more than once.
|
|
45
|
+
# @raise [SeccompTools::UndefinedLabelError]
|
|
46
|
+
# If a jump refers to a label that is never defined.
|
|
47
|
+
# @raise [SeccompTools::BackwardJumpError]
|
|
48
|
+
# If a jump goes backward, which BPF cannot express.
|
|
38
49
|
def compile!
|
|
39
50
|
@scanner.validate!
|
|
40
51
|
statements = SeccompAsmParser.new(@scanner).parse
|
|
@@ -79,8 +90,18 @@ module SeccompTools
|
|
|
79
90
|
end
|
|
80
91
|
end
|
|
81
92
|
|
|
93
|
+
# Resolves a jump target into a relative distance from +index+.
|
|
94
|
+
#
|
|
82
95
|
# @param [Integer] index
|
|
96
|
+
# Index of the statement the jump is written in.
|
|
83
97
|
# @param [SeccompTools::Asm::Token, :next] sym
|
|
98
|
+
# The jump target, either a label token or +:next+ for the following instruction.
|
|
99
|
+
# @return [Integer]
|
|
100
|
+
# Number of instructions to skip, +0+ meaning the next instruction.
|
|
101
|
+
# @raise [SeccompTools::UndefinedLabelError]
|
|
102
|
+
# If +sym+ names a label that is never defined.
|
|
103
|
+
# @raise [SeccompTools::BackwardJumpError]
|
|
104
|
+
# If the target lies before +index+.
|
|
84
105
|
def resolve_symbol(index, sym)
|
|
85
106
|
return 0 if sym.is_a?(Symbol) && sym == :next
|
|
86
107
|
|
|
@@ -105,6 +126,15 @@ module SeccompTools
|
|
|
105
126
|
|
|
106
127
|
# Emits a raw BPF object.
|
|
107
128
|
#
|
|
129
|
+
# @param [Array<Symbol>] args
|
|
130
|
+
# Names of the flags to be OR-ed together into the instruction's +code+ field, looked up in
|
|
131
|
+
# the {Const::BPF} tables. Unknown names contribute nothing.
|
|
132
|
+
# @param [Integer] k
|
|
133
|
+
# The +k+ field, an immediate operand or a jump target.
|
|
134
|
+
# @param [Integer] jt
|
|
135
|
+
# The +jt+ field, instructions to skip when a comparison is true.
|
|
136
|
+
# @param [Integer] jf
|
|
137
|
+
# The +jf+ field, instructions to skip when a comparison is false.
|
|
108
138
|
# @return [BPF]
|
|
109
139
|
# Returns the emitted {BPF} object.
|
|
110
140
|
def emit(*args, k: 0, jt: 0, jf: 0)
|
|
@@ -9,17 +9,24 @@ require 'racc/parser.rb'
|
|
|
9
9
|
require 'seccomp-tools/asm/scalar'
|
|
10
10
|
require 'seccomp-tools/asm/scanner'
|
|
11
11
|
require 'seccomp-tools/asm/statement'
|
|
12
|
+
require 'seccomp-tools/const'
|
|
12
13
|
|
|
13
14
|
module SeccompTools
|
|
14
15
|
module Asm
|
|
15
16
|
class SeccompAsmParser < Racc::Parser
|
|
16
17
|
|
|
17
|
-
module_eval(<<'...end sasm.y/module_eval...', 'sasm.y',
|
|
18
|
+
module_eval(<<'...end sasm.y/module_eval...', 'sasm.y', 138)
|
|
18
19
|
def initialize(scanner)
|
|
19
20
|
@scanner = scanner
|
|
20
21
|
super()
|
|
21
22
|
end
|
|
22
23
|
|
|
24
|
+
# The byte offset of the low (+hi: false+) or high 32-bit word of the 8-byte seccomp_data field
|
|
25
|
+
# at +base+; on a big-endian target the high word comes first.
|
|
26
|
+
def word_offset(base, hi:)
|
|
27
|
+
base + (hi == SeccompTools::Const::Endian.big?(@scanner.arch) ? 0 : 4)
|
|
28
|
+
end
|
|
29
|
+
|
|
23
30
|
# @return [Array<Statement>]
|
|
24
31
|
def parse
|
|
25
32
|
@tokens = @scanner.scan.dup
|
|
@@ -632,47 +639,48 @@ module_eval(<<'.,.,', 'sasm.y', 70)
|
|
|
632
639
|
off = val[1] == '>>' ? 0 : -1
|
|
633
640
|
raise_error("operator after an argument can only be '>> 32'", off)
|
|
634
641
|
end
|
|
635
|
-
val[0]
|
|
642
|
+
val[0] ^ 4 # the other 32-bit word of the same 64-bit field
|
|
636
643
|
|
|
637
644
|
end
|
|
638
645
|
.,.,
|
|
639
646
|
|
|
640
647
|
module_eval(<<'.,.,', 'sasm.y', 76)
|
|
641
648
|
def _reduce_46(val, _values)
|
|
642
|
-
|
|
649
|
+
SeccompTools::Const::BPF::SeccompData::SYS_NUMBER
|
|
643
650
|
end
|
|
644
651
|
.,.,
|
|
645
652
|
|
|
646
653
|
module_eval(<<'.,.,', 'sasm.y', 77)
|
|
647
654
|
def _reduce_47(val, _values)
|
|
648
|
-
|
|
655
|
+
SeccompTools::Const::BPF::SeccompData::ARCH
|
|
649
656
|
end
|
|
650
657
|
.,.,
|
|
651
658
|
|
|
652
659
|
module_eval(<<'.,.,', 'sasm.y', 79)
|
|
653
660
|
def _reduce_48(val, _values)
|
|
654
661
|
idx = val[2].to_i
|
|
655
|
-
|
|
656
|
-
|
|
662
|
+
size = SeccompTools::Const::BPF::SeccompData::SIZE
|
|
663
|
+
if idx % 4 != 0 || idx >= size
|
|
664
|
+
raise_error(format('index of data[] must be a multiple of 4 and less than %d, got %d', size, idx), -1)
|
|
657
665
|
end
|
|
658
666
|
idx
|
|
659
667
|
|
|
660
668
|
end
|
|
661
669
|
.,.,
|
|
662
670
|
|
|
663
|
-
module_eval(<<'.,.,', 'sasm.y',
|
|
671
|
+
module_eval(<<'.,.,', 'sasm.y', 88)
|
|
664
672
|
def _reduce_49(val, _values)
|
|
665
673
|
idx = val[2].to_i
|
|
666
674
|
s = val[0]
|
|
667
675
|
raise_error(format('index of %s[] must between 0 and 5, got %d', s, idx), -1) unless idx.between?(0, 5)
|
|
668
|
-
|
|
676
|
+
word_offset(SeccompTools::Const::BPF::SeccompData::ARGS + idx * 8, hi: s.downcase.end_with?('h'))
|
|
669
677
|
|
|
670
678
|
end
|
|
671
679
|
.,.,
|
|
672
680
|
|
|
673
|
-
module_eval(<<'.,.,', 'sasm.y',
|
|
681
|
+
module_eval(<<'.,.,', 'sasm.y', 93)
|
|
674
682
|
def _reduce_50(val, _values)
|
|
675
|
-
|
|
683
|
+
word_offset(SeccompTools::Const::BPF::SeccompData::INSTRUCTION_POINTER, hi: false)
|
|
676
684
|
end
|
|
677
685
|
.,.,
|
|
678
686
|
|
|
@@ -680,7 +688,7 @@ module_eval(<<'.,.,', 'sasm.y', 92)
|
|
|
680
688
|
|
|
681
689
|
# reduce 52 omitted
|
|
682
690
|
|
|
683
|
-
module_eval(<<'.,.,', 'sasm.y',
|
|
691
|
+
module_eval(<<'.,.,', 'sasm.y', 96)
|
|
684
692
|
def _reduce_53(val, _values)
|
|
685
693
|
val[0] + val[1]
|
|
686
694
|
end
|
|
@@ -690,13 +698,13 @@ module_eval(<<'.,.,', 'sasm.y', 95)
|
|
|
690
698
|
|
|
691
699
|
# reduce 55 omitted
|
|
692
700
|
|
|
693
|
-
module_eval(<<'.,.,', 'sasm.y',
|
|
701
|
+
module_eval(<<'.,.,', 'sasm.y', 99)
|
|
694
702
|
def _reduce_56(val, _values)
|
|
695
703
|
Scalar::ConstVal.new(val[0] & 0xffffffff)
|
|
696
704
|
end
|
|
697
705
|
.,.,
|
|
698
706
|
|
|
699
|
-
module_eval(<<'.,.,', 'sasm.y',
|
|
707
|
+
module_eval(<<'.,.,', 'sasm.y', 100)
|
|
700
708
|
def _reduce_57(val, _values)
|
|
701
709
|
val[1]
|
|
702
710
|
end
|
|
@@ -706,37 +714,37 @@ module_eval(<<'.,.,', 'sasm.y', 99)
|
|
|
706
714
|
|
|
707
715
|
# reduce 59 omitted
|
|
708
716
|
|
|
709
|
-
module_eval(<<'.,.,', 'sasm.y',
|
|
717
|
+
module_eval(<<'.,.,', 'sasm.y', 103)
|
|
710
718
|
def _reduce_60(val, _values)
|
|
711
719
|
Scalar::A.instance
|
|
712
720
|
end
|
|
713
721
|
.,.,
|
|
714
722
|
|
|
715
|
-
module_eval(<<'.,.,', 'sasm.y',
|
|
723
|
+
module_eval(<<'.,.,', 'sasm.y', 104)
|
|
716
724
|
def _reduce_61(val, _values)
|
|
717
725
|
Scalar::X.instance
|
|
718
726
|
end
|
|
719
727
|
.,.,
|
|
720
728
|
|
|
721
|
-
module_eval(<<'.,.,', 'sasm.y',
|
|
729
|
+
module_eval(<<'.,.,', 'sasm.y', 105)
|
|
722
730
|
def _reduce_62(val, _values)
|
|
723
731
|
val[0].to_i
|
|
724
732
|
end
|
|
725
733
|
.,.,
|
|
726
734
|
|
|
727
|
-
module_eval(<<'.,.,', 'sasm.y',
|
|
735
|
+
module_eval(<<'.,.,', 'sasm.y', 106)
|
|
728
736
|
def _reduce_63(val, _values)
|
|
729
737
|
val[0].to_i(16)
|
|
730
738
|
end
|
|
731
739
|
.,.,
|
|
732
740
|
|
|
733
|
-
module_eval(<<'.,.,', 'sasm.y',
|
|
741
|
+
module_eval(<<'.,.,', 'sasm.y', 107)
|
|
734
742
|
def _reduce_64(val, _values)
|
|
735
743
|
Const::Audit::ARCH[val[0]]
|
|
736
744
|
end
|
|
737
745
|
.,.,
|
|
738
746
|
|
|
739
|
-
module_eval(<<'.,.,', 'sasm.y',
|
|
747
|
+
module_eval(<<'.,.,', 'sasm.y', 109)
|
|
740
748
|
def _reduce_65(val, _values)
|
|
741
749
|
s = val[0]
|
|
742
750
|
return @scanner.syscalls[s.to_sym] unless s.include?('.')
|
|
@@ -72,14 +72,15 @@ rule
|
|
|
72
72
|
off = val[1] == '>>' ? 0 : -1
|
|
73
73
|
raise_error("operator after an argument can only be '>> 32'", off)
|
|
74
74
|
end
|
|
75
|
-
val[0]
|
|
75
|
+
val[0] ^ 4 # the other 32-bit word of the same 64-bit field
|
|
76
76
|
}
|
|
77
|
-
| SYS_NUMBER {
|
|
78
|
-
| ARCH {
|
|
77
|
+
| SYS_NUMBER { SeccompTools::Const::BPF::SeccompData::SYS_NUMBER }
|
|
78
|
+
| ARCH { SeccompTools::Const::BPF::SeccompData::ARCH }
|
|
79
79
|
| DATA LBRACK constexpr RBRACK {
|
|
80
80
|
idx = val[2].to_i
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
size = SeccompTools::Const::BPF::SeccompData::SIZE
|
|
82
|
+
if idx % 4 != 0 || idx >= size
|
|
83
|
+
raise_error(format('index of data[] must be a multiple of 4 and less than %d, got %d', size, idx), -1)
|
|
83
84
|
end
|
|
84
85
|
idx
|
|
85
86
|
}
|
|
@@ -88,9 +89,9 @@ rule
|
|
|
88
89
|
idx = val[2].to_i
|
|
89
90
|
s = val[0]
|
|
90
91
|
raise_error(format('index of %s[] must between 0 and 5, got %d', s, idx), -1) unless idx.between?(0, 5)
|
|
91
|
-
|
|
92
|
+
word_offset(SeccompTools::Const::BPF::SeccompData::ARGS + idx * 8, hi: s.downcase.end_with?('h'))
|
|
92
93
|
}
|
|
93
|
-
| INSTRUCTION_POINTER {
|
|
94
|
+
| INSTRUCTION_POINTER { word_offset(SeccompTools::Const::BPF::SeccompData::INSTRUCTION_POINTER, hi: false) }
|
|
94
95
|
args: ARGS
|
|
95
96
|
| ARGS_H
|
|
96
97
|
alu_op_eq: alu_op ASSIGN { val[0] + val[1] }
|
|
@@ -131,6 +132,7 @@ end
|
|
|
131
132
|
require 'seccomp-tools/asm/scalar'
|
|
132
133
|
require 'seccomp-tools/asm/scanner'
|
|
133
134
|
require 'seccomp-tools/asm/statement'
|
|
135
|
+
require 'seccomp-tools/const'
|
|
134
136
|
|
|
135
137
|
---- inner
|
|
136
138
|
def initialize(scanner)
|
|
@@ -138,6 +140,12 @@ require 'seccomp-tools/asm/statement'
|
|
|
138
140
|
super()
|
|
139
141
|
end
|
|
140
142
|
|
|
143
|
+
# The byte offset of the low (+hi: false+) or high 32-bit word of the 8-byte seccomp_data field
|
|
144
|
+
# at +base+; on a big-endian target the high word comes first.
|
|
145
|
+
def word_offset(base, hi:)
|
|
146
|
+
base + (hi == SeccompTools::Const::Endian.big?(@scanner.arch) ? 0 : 4)
|
|
147
|
+
end
|
|
148
|
+
|
|
141
149
|
# @return [Array<Statement>]
|
|
142
150
|
def parse
|
|
143
151
|
@tokens = @scanner.scan.dup
|
|
@@ -10,116 +10,159 @@ module SeccompTools
|
|
|
10
10
|
# @private
|
|
11
11
|
module Scalar
|
|
12
12
|
# To be used to denote a register (A / X), an argument (data[]), or a memory data (mem[]).
|
|
13
|
+
#
|
|
14
|
+
# Every predicate returns +false+ here, each subclass overrides exactly the one that
|
|
15
|
+
# identifies it. The registers and +len+ carry no payload and are singletons, the remaining
|
|
16
|
+
# subclasses keep theirs in {#val}.
|
|
13
17
|
class Base
|
|
18
|
+
# @return [Integer?]
|
|
19
|
+
# The payload of this scalar, whose meaning depends on the subclass: the immediate value
|
|
20
|
+
# for {ConstVal}, the index for {Data} and {Mem}. +nil+ for the singletons.
|
|
14
21
|
attr_reader :val
|
|
15
22
|
|
|
23
|
+
# Is this the A register?
|
|
24
|
+
# @return [Boolean]
|
|
16
25
|
def a?
|
|
17
26
|
false
|
|
18
27
|
end
|
|
19
28
|
|
|
29
|
+
# Is this the X register?
|
|
30
|
+
# @return [Boolean]
|
|
20
31
|
def x?
|
|
21
32
|
false
|
|
22
33
|
end
|
|
23
34
|
|
|
35
|
+
# Is this +len+?
|
|
36
|
+
# @return [Boolean]
|
|
24
37
|
def len?
|
|
25
38
|
false
|
|
26
39
|
end
|
|
27
40
|
|
|
41
|
+
# Is this a +data[]+ access?
|
|
42
|
+
# @return [Boolean]
|
|
28
43
|
def data?
|
|
29
44
|
false
|
|
30
45
|
end
|
|
31
46
|
|
|
47
|
+
# Is this a +mem[]+ access?
|
|
48
|
+
# @return [Boolean]
|
|
32
49
|
def mem?
|
|
33
50
|
false
|
|
34
51
|
end
|
|
35
52
|
|
|
53
|
+
# Is this a constant value?
|
|
54
|
+
# @return [Boolean]
|
|
36
55
|
def const?
|
|
37
56
|
false
|
|
38
57
|
end
|
|
39
58
|
end
|
|
40
59
|
|
|
41
|
-
# Register A.
|
|
60
|
+
# Register A, the accumulator. A singleton, use +A.instance+.
|
|
42
61
|
class A < Base
|
|
43
62
|
include Singleton
|
|
44
63
|
|
|
64
|
+
# @return [Boolean] Always +true+.
|
|
45
65
|
def a?
|
|
46
66
|
true
|
|
47
67
|
end
|
|
48
68
|
end
|
|
49
69
|
|
|
50
|
-
# Register X.
|
|
70
|
+
# Register X, the index register. A singleton, use +X.instance+.
|
|
51
71
|
class X < Base
|
|
52
72
|
include Singleton
|
|
53
73
|
|
|
74
|
+
# @return [Boolean] Always +true+.
|
|
54
75
|
def x?
|
|
55
76
|
true
|
|
56
77
|
end
|
|
57
78
|
end
|
|
58
79
|
|
|
59
|
-
# Len.
|
|
80
|
+
# The +len+ keyword, the size of +struct seccomp_data+. A singleton, use +Len.instance+.
|
|
60
81
|
class Len < Base
|
|
61
82
|
include Singleton
|
|
62
83
|
|
|
84
|
+
# @return [Boolean] Always +true+.
|
|
63
85
|
def len?
|
|
64
86
|
true
|
|
65
87
|
end
|
|
66
88
|
end
|
|
67
89
|
|
|
68
|
-
# A constant value.
|
|
90
|
+
# A constant value, i.e. an immediate operand.
|
|
69
91
|
class ConstVal < Base
|
|
92
|
+
# Instantiates a {ConstVal} object.
|
|
93
|
+
#
|
|
70
94
|
# @param [Integer] val
|
|
95
|
+
# The constant value.
|
|
71
96
|
def initialize(val)
|
|
72
97
|
@val = val
|
|
73
98
|
super()
|
|
74
99
|
end
|
|
75
100
|
|
|
101
|
+
# Compares by value, so a {ConstVal} equals the bare Integer it wraps.
|
|
102
|
+
#
|
|
76
103
|
# @param [ConstVal, Integer] other
|
|
104
|
+
# The object to be compared with.
|
|
105
|
+
# @return [Boolean]
|
|
77
106
|
def ==(other)
|
|
78
107
|
to_i == other.to_i
|
|
79
108
|
end
|
|
80
109
|
|
|
110
|
+
# @return [Boolean] Always +true+.
|
|
81
111
|
def const?
|
|
82
112
|
true
|
|
83
113
|
end
|
|
84
114
|
|
|
115
|
+
# @!method to_i
|
|
116
|
+
# The wrapped constant value.
|
|
117
|
+
# @return [Integer]
|
|
85
118
|
alias to_i val
|
|
86
119
|
end
|
|
87
120
|
|
|
88
|
-
# data[]
|
|
121
|
+
# A +data[]+ access, one word of +struct seccomp_data+.
|
|
89
122
|
class Data < Base
|
|
90
123
|
# Instantiates a {Data} object.
|
|
91
124
|
#
|
|
92
125
|
# @param [Integer] index
|
|
126
|
+
# Byte offset into +struct seccomp_data+.
|
|
93
127
|
def initialize(index)
|
|
94
128
|
@val = index
|
|
95
129
|
super()
|
|
96
130
|
end
|
|
97
131
|
|
|
98
132
|
# @param [Data] other
|
|
133
|
+
# The object to be compared with.
|
|
134
|
+
# @return [Boolean]
|
|
135
|
+
# +true+ only if +other+ is a {Data} with the same index.
|
|
99
136
|
def ==(other)
|
|
100
137
|
other.is_a?(Data) && val == other.val
|
|
101
138
|
end
|
|
102
139
|
|
|
140
|
+
# @return [Boolean] Always +true+.
|
|
103
141
|
def data?
|
|
104
142
|
true
|
|
105
143
|
end
|
|
106
144
|
end
|
|
107
145
|
|
|
108
|
-
# mem[]
|
|
146
|
+
# A +mem[]+ access, one slot of the scratch memory.
|
|
109
147
|
class Mem < Base
|
|
110
148
|
# Instantiates a {Mem} object.
|
|
111
149
|
#
|
|
112
150
|
# @param [Integer] index
|
|
151
|
+
# Index of the scratch memory slot.
|
|
113
152
|
def initialize(index)
|
|
114
153
|
@val = index
|
|
115
154
|
super()
|
|
116
155
|
end
|
|
117
156
|
|
|
118
|
-
# @param [
|
|
157
|
+
# @param [Mem] other
|
|
158
|
+
# The object to be compared with.
|
|
159
|
+
# @return [Boolean]
|
|
160
|
+
# +true+ only if +other+ is a {Mem} with the same index.
|
|
119
161
|
def ==(other)
|
|
120
162
|
other.is_a?(Mem) && val == other.val
|
|
121
163
|
end
|
|
122
164
|
|
|
165
|
+
# @return [Boolean] Always +true+.
|
|
123
166
|
def mem?
|
|
124
167
|
true
|
|
125
168
|
end
|
|
@@ -11,9 +11,14 @@ module SeccompTools
|
|
|
11
11
|
#
|
|
12
12
|
# Maintains columns and rows to have informative error messages.
|
|
13
13
|
#
|
|
14
|
-
# Internally used by
|
|
14
|
+
# Internally used by the seccomp asm parser.
|
|
15
15
|
class Scanner
|
|
16
|
+
# @return [{Symbol => Integer}]
|
|
17
|
+
# Syscall name to number, for the architecture this scanner was created with.
|
|
16
18
|
attr_reader :syscalls
|
|
19
|
+
# @return [Symbol]
|
|
20
|
+
# The architecture this scanner was created with.
|
|
21
|
+
attr_reader :arch
|
|
17
22
|
|
|
18
23
|
# Keywords with special meanings in our assembly. Keywords are all case-insensitive.
|
|
19
24
|
KEYWORDS = %w[a x if else return mem args args_h data len sys_number arch instruction_pointer].freeze
|
|
@@ -40,14 +45,20 @@ module SeccompTools
|
|
|
40
45
|
# Supported architectures
|
|
41
46
|
ARCHES = SeccompTools::Syscall::ABI.keys.map(&:to_s)
|
|
42
47
|
|
|
48
|
+
# Instantiates a {Scanner} object.
|
|
49
|
+
#
|
|
43
50
|
# @param [String] str
|
|
51
|
+
# The assembly source to be scanned.
|
|
44
52
|
# @param [Symbol] arch
|
|
53
|
+
# Target architecture, decides which syscall names are recognized.
|
|
45
54
|
# @param [String?] filename
|
|
55
|
+
# Only used for error messages, defaults to +<inline>+.
|
|
46
56
|
# @example
|
|
47
57
|
# Scanner.new('return ALLOW', :amd64)
|
|
48
58
|
def initialize(str, arch, filename: nil)
|
|
49
59
|
@filename = filename || '<inline>'
|
|
50
60
|
@str = str
|
|
61
|
+
@arch = arch
|
|
51
62
|
@syscalls =
|
|
52
63
|
begin; Const::Syscall.const_get(arch.to_s.upcase); rescue NameError; []; end
|
|
53
64
|
@syscall_all = ARCHES.each_with_object({}) do |ar, memo|
|
|
@@ -57,7 +68,9 @@ module SeccompTools
|
|
|
57
68
|
|
|
58
69
|
# Scans the whole string and raises errors when there are unrecognized tokens.
|
|
59
70
|
# @return [self]
|
|
71
|
+
# The scanner itself, so calls can be chained.
|
|
60
72
|
# @raise [UnrecognizedTokenError]
|
|
73
|
+
# If any token could not be recognized. The message points at every such token.
|
|
61
74
|
def validate!
|
|
62
75
|
errors = validate
|
|
63
76
|
return self if errors.empty?
|
|
@@ -68,11 +81,17 @@ module SeccompTools
|
|
|
68
81
|
# Same as {#validate!} but returns the array of errors instead of raising an exception.
|
|
69
82
|
#
|
|
70
83
|
# @return [Array<Token>]
|
|
84
|
+
# The +:unknown+ tokens, empty when the source is valid.
|
|
71
85
|
def validate
|
|
72
86
|
scan.select { |t| t.sym == :unknown }
|
|
73
87
|
end
|
|
74
88
|
|
|
89
|
+
# Scans the whole string into tokens.
|
|
90
|
+
#
|
|
91
|
+
# The result is memoized, so repeated calls are free.
|
|
75
92
|
# @return [Array<Token>]
|
|
93
|
+
# All tokens in source order. Unrecognized text becomes +:unknown+ tokens rather than
|
|
94
|
+
# raising, see {#validate!}.
|
|
76
95
|
def scan
|
|
77
96
|
return @tokens if defined?(@tokens)
|
|
78
97
|
|
|
@@ -138,9 +157,22 @@ module SeccompTools
|
|
|
138
157
|
# Let tab on terminal be 4 spaces wide.
|
|
139
158
|
TAB_WIDTH = 4
|
|
140
159
|
|
|
160
|
+
# Formats an error message that points at a token in the source.
|
|
161
|
+
#
|
|
141
162
|
# @param [Token] tok
|
|
163
|
+
# The token the message refers to.
|
|
142
164
|
# @param [String] msg
|
|
165
|
+
# The message to be shown next to the source location.
|
|
143
166
|
# @return [String]
|
|
167
|
+
# The location and message, the offending source line, and a row of carets underlining
|
|
168
|
+
# the token.
|
|
169
|
+
# @example
|
|
170
|
+
# scanner.format_error(tok, 'unknown token "foo"')
|
|
171
|
+
# #=> <<-EOS
|
|
172
|
+
# # <inline>:1:1 unknown token "foo"
|
|
173
|
+
# # foo
|
|
174
|
+
# # ^^^
|
|
175
|
+
# # EOS
|
|
144
176
|
def format_error(tok, msg)
|
|
145
177
|
@lines = @str.lines unless defined?(@lines)
|
|
146
178
|
line = @lines[tok.line]
|