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.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +153 -0
  3. data/README.md +263 -42
  4. data/completions/_seccomp-tools +83 -0
  5. data/completions/seccomp-tools.bash +59 -0
  6. data/completions/seccomp-tools.fish +52 -0
  7. data/ext/ptrace/ptrace.c +2 -2
  8. data/lib/seccomp-tools/asm/asm.rb +9 -4
  9. data/lib/seccomp-tools/asm/compiler.rb +32 -2
  10. data/lib/seccomp-tools/asm/sasm.tab.rb +27 -19
  11. data/lib/seccomp-tools/asm/sasm.y +15 -7
  12. data/lib/seccomp-tools/asm/scalar.rb +50 -7
  13. data/lib/seccomp-tools/asm/scanner.rb +33 -1
  14. data/lib/seccomp-tools/asm/statement.rb +14 -5
  15. data/lib/seccomp-tools/asm/token.rb +19 -1
  16. data/lib/seccomp-tools/audit/catalog.rb +66 -0
  17. data/lib/seccomp-tools/audit/checks/arch_unchecked.rb +41 -0
  18. data/lib/seccomp-tools/audit/checks/dangerous_allow.rb +34 -0
  19. data/lib/seccomp-tools/audit/checks/orw_chain.rb +41 -0
  20. data/lib/seccomp-tools/audit/checks/permissive_default.rb +29 -0
  21. data/lib/seccomp-tools/audit/checks/syscall_alt_gap.rb +44 -0
  22. data/lib/seccomp-tools/audit/checks/x32_guard.rb +46 -0
  23. data/lib/seccomp-tools/audit/checks.rb +42 -0
  24. data/lib/seccomp-tools/audit/finding.rb +25 -0
  25. data/lib/seccomp-tools/audit/policy.rb +126 -0
  26. data/lib/seccomp-tools/audit/report.rb +98 -0
  27. data/lib/seccomp-tools/audit.rb +48 -0
  28. data/lib/seccomp-tools/bpf.rb +27 -17
  29. data/lib/seccomp-tools/cli/asm.rb +6 -2
  30. data/lib/seccomp-tools/cli/audit.rb +86 -0
  31. data/lib/seccomp-tools/cli/base.rb +51 -8
  32. data/lib/seccomp-tools/cli/cli.rb +9 -3
  33. data/lib/seccomp-tools/cli/completion.rb +40 -0
  34. data/lib/seccomp-tools/cli/disasm.rb +9 -5
  35. data/lib/seccomp-tools/cli/dump.rb +37 -52
  36. data/lib/seccomp-tools/cli/dumpable.rb +79 -0
  37. data/lib/seccomp-tools/cli/emu.rb +20 -5
  38. data/lib/seccomp-tools/cli/explain.rb +51 -0
  39. data/lib/seccomp-tools/cli/filter_input.rb +130 -0
  40. data/lib/seccomp-tools/const.rb +98 -15
  41. data/lib/seccomp-tools/consts/sys_nr/riscv64.rb +332 -0
  42. data/lib/seccomp-tools/disasm/disasm.rb +30 -12
  43. data/lib/seccomp-tools/dumper.rb +65 -33
  44. data/lib/seccomp-tools/emulator.rb +40 -15
  45. data/lib/seccomp-tools/error.rb +4 -2
  46. data/lib/seccomp-tools/explain/analysis.rb +67 -0
  47. data/lib/seccomp-tools/explain/path_facts.rb +110 -0
  48. data/lib/seccomp-tools/explain/qword.rb +204 -0
  49. data/lib/seccomp-tools/explain/renderer.rb +128 -0
  50. data/lib/seccomp-tools/explain/summary.rb +218 -0
  51. data/lib/seccomp-tools/explain/verdict.rb +44 -0
  52. data/lib/seccomp-tools/explain.rb +38 -0
  53. data/lib/seccomp-tools/instruction/alu.rb +14 -9
  54. data/lib/seccomp-tools/instruction/base.rb +39 -10
  55. data/lib/seccomp-tools/instruction/jmp.rb +50 -23
  56. data/lib/seccomp-tools/instruction/ld.rb +46 -21
  57. data/lib/seccomp-tools/instruction/ldx.rb +4 -3
  58. data/lib/seccomp-tools/instruction/misc.rb +11 -9
  59. data/lib/seccomp-tools/instruction/ret.rb +12 -6
  60. data/lib/seccomp-tools/instruction/st.rb +15 -6
  61. data/lib/seccomp-tools/instruction/stx.rb +4 -3
  62. data/lib/seccomp-tools/logger.rb +14 -1
  63. data/lib/seccomp-tools/symbolic/constraint.rb +80 -0
  64. data/lib/seccomp-tools/symbolic/executor.rb +210 -0
  65. data/lib/seccomp-tools/symbolic/expr.rb +185 -0
  66. data/lib/seccomp-tools/symbolic/state.rb +82 -0
  67. data/lib/seccomp-tools/syscall.rb +72 -20
  68. data/lib/seccomp-tools/util.rb +70 -11
  69. data/lib/seccomp-tools/version.rb +1 -1
  70. data/lib/seccomp-tools.rb +10 -1
  71. metadata +36 -4
  72. 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 the
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
- # Assembler of seccomp bpf.
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 start with '#' are comments
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, denote the next instruction
23
- # A == read ? ok : next # custom defined label 'dead' and 'ok'
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::Error]
37
- # Raises the error found during compilation.
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', 136)
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] + 4
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
- 0
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
- 4
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
- if idx % 4 != 0 || idx >= 64
656
- raise_error(format('index of data[] must be a multiple of 4 and less than 64, got %d', idx), -1)
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', 87)
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
- 16 + idx * 8 + (s.downcase.end_with?('h') ? 4 : 0)
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', 92)
681
+ module_eval(<<'.,.,', 'sasm.y', 93)
674
682
  def _reduce_50(val, _values)
675
- 8
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', 95)
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', 98)
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', 99)
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', 102)
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', 103)
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', 104)
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', 105)
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', 106)
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', 108)
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] + 4
75
+ val[0] ^ 4 # the other 32-bit word of the same 64-bit field
76
76
  }
77
- | SYS_NUMBER { 0 }
78
- | ARCH { 4 }
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
- 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)
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
- 16 + idx * 8 + (s.downcase.end_with?('h') ? 4 : 0)
92
+ word_offset(SeccompTools::Const::BPF::SeccompData::ARGS + idx * 8, hi: s.downcase.end_with?('h'))
92
93
  }
93
- | INSTRUCTION_POINTER { 8 }
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 [Data] other
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 {SeccompAsmParser}.
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]