Class: SeccompTools::Asm::Scanner

Inherits:
Object
  • Object
show all
Defined in:
lib/seccomp-tools/asm/scanner.rb

Overview

Converts text to array of tokens.

Maintains columns and rows to have informative error messages.

Internally used by SeccompAsmParser.

Constant Summary collapse

KEYWORDS =

Keywords with special meanings in our assembly. Keywords are all case-insensitive.

%w[a x if else return mem args args_h data len sys_number arch instruction_pointer].freeze
KEYWORD_MATCHER =

Regexp for matching keywords.

/\A\b(#{KEYWORDS.join('|')})\b/i
ACTIONS =

Action strings can be used in a return statement. Actions must be in upper case. See Const::BPF::ACTION.

Const::BPF::ACTION.keys.map(&:to_s)
ACTION_MATCHER =

Regexp for matching actions.

/\A\b(#{ACTIONS.join('|')})\b/
AUDIT_ARCHES =

Special constants for checking the current architecture. See Const::Audit::ARCH. These constants are case-insensitive.

Const::Audit::ARCH.keys
AUDIT_ARCH_MATCHER =

Regexp for matching arch values.

/\A\b(#{AUDIT_ARCHES.join('|')})\b/i
COMPARE =

Comparisons.

%w[== != >= <= > <].freeze
COMPARE_MATCHER =

Regexp for matching comparisons.

/\A(#{COMPARE.join('|')})/
ALU_OP =

All valid arithmetic operators.

%w[+ - * / | ^ << >>].freeze
ALU_OP_MATCHER =

Regexp for matching ALU operators.

/\A(#{ALU_OP.map { |o| ::Regexp.escape(o) }.join('|')})/
ARCHES =

Supported architectures

SeccompTools::Syscall::ABI.keys.map(&:to_s)
TAB_WIDTH =

Let tab on terminal be 4 spaces wide.

4

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(str, arch, filename: nil) ⇒ Scanner

Returns a new instance of Scanner.

Examples:

Scanner.new('return ALLOW', :amd64)

Parameters:

  • str (String)
  • arch (Symbol)
  • filename (String?) (defaults to: nil)


48
49
50
51
52
53
54
55
56
# File 'lib/seccomp-tools/asm/scanner.rb', line 48

def initialize(str, arch, filename: nil)
  @filename = filename || '<inline>'
  @str = str
  @syscalls =
    begin; Const::Syscall.const_get(arch.to_s.upcase); rescue NameError; []; end
  @syscall_all = ARCHES.each_with_object({}) do |ar, memo|
    memo.merge!(Const::Syscall.const_get(ar.to_s.upcase))
  end.keys
end

Instance Attribute Details

#syscallsObject (readonly)

Returns the value of attribute syscalls.



16
17
18
# File 'lib/seccomp-tools/asm/scanner.rb', line 16

def syscalls
  @syscalls
end

Instance Method Details

#format_error(tok, msg) ⇒ String

Parameters:

  • tok (Token)
  • msg (String)

Returns:

  • (String)


144
145
146
147
148
149
150
151
152
153
154
# File 'lib/seccomp-tools/asm/scanner.rb', line 144

def format_error(tok, msg)
  @lines = @str.lines unless defined?(@lines)
  line = @lines[tok.line]
  line = line[0..-2] if line.end_with?("\n")
  line = line.gsub("\t", ' ' * TAB_WIDTH)
  <<-EOS
#{@filename}:#{tok.line + 1}:#{tok.col + 1} #{msg}
#{line}
#{' ' * calculate_spaces(@lines[tok.line][0...tok.col]) + '^' * tok.str.size}
  EOS
end

#scanArray<Token>

Returns:



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/seccomp-tools/asm/scanner.rb', line 76

def scan
  return @tokens if defined?(@tokens)

  @tokens = []
  row = 0
  col = 0
  str = @str
  add_token = ->(sym, s, c = col) { @tokens.push(Token.new(sym, s, row, c)) }
  # define a helper because it's commonly used - add a token with matched string, bump col with string size
  bump_vars = lambda {
    col += ::Regexp.last_match(0).size
    str = ::Regexp.last_match.post_match
  }
  add_token_def = lambda do |sym|
    add_token.call(sym, ::Regexp.last_match(0))
    bump_vars.call
  end
  syscalls = @syscalls.keys.map { |s| ::Regexp.escape(s) }.join('|')
  syscall_matcher = ::Regexp.compile("\\A\\b(#{syscalls})\\b")
  syscall_all_matcher = ::Regexp.compile("\\A(#{ARCHES.join('|')})\\.(#{@syscall_all.join('|')})\\b")
  until str.empty?
    case str
    when /\A\n+/
      # Don't push newline as the first token
      add_token.call(:NEWLINE, ::Regexp.last_match(0)) unless @tokens.empty?
      row += ::Regexp.last_match(0).size
      col = 0
      str = ::Regexp.last_match.post_match
    when /\A\s+/, /\A#.*/ then bump_vars.call
    when /\A(\w+):/
      add_token.call(:SYMBOL, ::Regexp.last_match(1))
      bump_vars.call
    when /\A(goto|jmp|jump)\s+(\w+)\b/i
      add_token.call(:GOTO, ::Regexp.last_match(1), col + ::Regexp.last_match.begin(1))
      add_token.call(:GOTO_SYMBOL, ::Regexp.last_match(2), col + ::Regexp.last_match.begin(2))
      bump_vars.call
    when KEYWORD_MATCHER then add_token_def.call(::Regexp.last_match(0).upcase.to_sym)
    when ACTION_MATCHER then add_token_def.call(:ACTION)
    when AUDIT_ARCH_MATCHER then add_token_def.call(:ARCH_VAL)
    when syscall_matcher, syscall_all_matcher then add_token_def.call(:SYSCALL)
    when /\A-?0x[0-9a-f]+\b/ then add_token_def.call(:HEX_INT)
    when /\A-?[0-9]+\b/ then add_token_def.call(:INT)
    when ALU_OP_MATCHER then add_token_def.call(:ALU_OP)
    when COMPARE_MATCHER then add_token_def.call(:COMPARE)
    # '&' is in both compare and ALU op category, handle it here
    when /\A(\(|\)|=|\[|\]|&|!)/ then add_token_def.call(::Regexp.last_match(0))
    when /\A\?\s*(?<jt>\w+)\s*:\s*(?<jf>\w+)/
      %i[jt jf].each do |s|
        add_token.call(:GOTO_SYMBOL, ::Regexp.last_match(s), col + ::Regexp.last_match.begin(s))
      end
      bump_vars.call
    when /\A([^\s]+)(\s?)/
      # unrecognized token - match until \s
      last = ::Regexp.last_match(1)
      add_token.call(:unknown, last)
      col += last.size
      str = ::Regexp.last_match(2) + ::Regexp.last_match.post_match
    end
  end
  @tokens
end

#validateArray<Token>

Same as #validate! but returns the array of errors instead of raising an exception.

Returns:



71
72
73
# File 'lib/seccomp-tools/asm/scanner.rb', line 71

def validate
  scan.select { |t| t.sym == :unknown }
end

#validate!self

Scans the whole string and raises errors when there are unrecognized tokens.

Returns:

  • (self)

Raises:



61
62
63
64
65
66
# File 'lib/seccomp-tools/asm/scanner.rb', line 61

def validate!
  errors = validate
  return self if errors.empty?

  raise UnrecognizedTokenError, errors.map { |e| format_error(e, "unknown token #{e.str.inspect}") }.join("\n")
end