Class: SeccompTools::Explain::QwordFusion

Inherits:
Object
  • Object
show all
Defined in:
lib/seccomp-tools/explain/qword.rb

Overview

A 64-bit field of seccomp_data (+instruction_pointer+ or an argument) is two 32-bit words, and filters check them separately. This class fuses such word facts back into 64-bit ones (Qword), both within one path condition (#fold) and across the sibling or-branches libseccomp compiles a 64-bit range comparison into (#merge_or).

Constant Summary collapse

BASES =

Byte offsets of the 64-bit fields whose two 32-bit words this class fuses.

Const::BPF::SeccompData::QWORD_BASES
OR_MERGE =

How the extra facts of two sibling or-branches fuse into one 64-bit comparison: one branch holds a strict high-word fact hi <hi_op> H (+hi_op+ is >, < or !=, normalized by #strict) and the other hi == H && lo <lo_op> L; keyed by [hi_op, lo_op], they are exactly field <fused op> (H << 32 | L). This is the shape libseccomp compiles SCMP_CMP_GT/GE/LT/LE/NE argument comparisons into.

{
  %i[> >] => :>, %i[> >=] => :>=, %i[< <] => :<, %i[< <=] => :<=, %i[!= !=] => :!=
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(arch) ⇒ QwordFusion

Returns a new instance of QwordFusion.

Parameters:

  • arch (Symbol)

    Decides the word order: on a big-endian architecture the high 32-bit word of a 64-bit field comes first.



42
43
44
# File 'lib/seccomp-tools/explain/qword.rb', line 42

def initialize(arch)
  @hi_first = Const::Endian.big?(arch)
end

Instance Method Details

#base_of(offset) ⇒ Object

The byte offset of the 64-bit field that the word at offset belongs to.



57
58
59
# File 'lib/seccomp-tools/explain/qword.rb', line 57

def base_of(offset)
  offset - (offset % 8)
end

#fold(constraints) ⇒ Array<Symbolic::Constraint, Qword>

Fuses the 32-bit word facts of one path condition into whole-field SeccompTools::Explain::Qwords; facts that do not form a fusable pair pass through unchanged.

Examples:

Both halves pinned by == (little-endian: args lo word @16, hi @20)

fold([ data[20] == 0x1, data[16] == 0x2 ])
#=> [ Qword(base: 16, op: :==, val: 0x100000002) ]

A zero high word with a low-word bound

fold([ data[20] == 0x0, data[16] < 0x1000 ])
#=> [ Qword(base: 16, op: :<, val: 0x1000) ]

Parameters:

Returns:



71
72
73
74
75
76
77
78
79
# File 'lib/seccomp-tools/explain/qword.rb', line 71

def fold(constraints)
  plan = qword_plan(constraints)
  constraints.filter_map do |c|
    action = plan[c]
    next if action == :drop

    action || c
  end
end

#hi_off(base) ⇒ Object

The byte offset of the high 32-bit word of the 64-bit field at base.



52
53
54
# File 'lib/seccomp-tools/explain/qword.rb', line 52

def hi_off(base)
  @hi_first ? base : base + 4
end

#lo_off(base) ⇒ Object

The byte offset of the low 32-bit word of the 64-bit field at base.



47
48
49
# File 'lib/seccomp-tools/explain/qword.rb', line 47

def lo_off(base)
  @hi_first ? base + 4 : base
end

#merge_or(lists) ⇒ Array<Array<Symbolic::Constraint, Qword>>

Fuses sibling or-branches that together express one 64-bit comparison, repeatedly until nothing fuses; branches that do not pair up are returned untouched. See #fuse_pair for the shape of a fusable pair.

Examples:

The two match branches of a 64-bit args[0] > 0x200000500 (little-endian: lo @16, hi @20)

merge_or([ [ data[20] > 2 ],
           [ data[20] == 2, data[16] > 0x500 ] ])
#=> [ [ Qword(base: 16, op: :>, val: 0x200000500) ] ]

Parameters:

Returns:



91
92
93
94
95
# File 'lib/seccomp-tools/explain/qword.rb', line 91

def merge_or(lists)
  lists = lists.dup # own a mutable copy; the caller's array is left untouched
  loop { break unless merge_one_pair!(lists) }
  lists
end