Module: OneGadget::Emulators::Constraints

Included in:
Processor
Defined in:
lib/one_gadget/emulators/constraints.rb

Overview

What a candidate requires of its caller, collected while it is emulated and rendered once it ends. A path that dereferences a pointer, stores through an address, or reaches a call that reads one records the requirement here rather than assuming it holds; #constraints then drops the ones another already implies and names what is left. Mixed into Processor.

Constant Summary collapse

CLOBBERED =

Marks a register holding whatever a call returned or left behind; see Processor#clobber_caller_saved.

'$clobbered'
ADDRESS_TYPES =

Constraint types whose payload is an address Lambda asserting the target is mapped -- :writable (a store target) and :readable (an unconditional dereference, see #finalize_deferred_reads). Both are keyed, offset- normalised, and imply non-NULL identically; they differ only in how they render (see #render_constraint). The remaining type, :raw, carries a ready-made constraint string that keys on itself, and :cmp a comparison recorded as its [lhs, operator, rhs] parts (see OneGadget::Emulators::Conditional), so it can be inspected rather than re-parsed from the rendered text.

i[writable readable].freeze
POINTER_REQUIREMENTS =

SafeCalls requirements naming what a callee does with a pointer argument, each recorded as something the caller must arrange (see #record_pointer), as opposed to a precondition read off the value as it stands.

i[writable deref nullable_deref null].freeze
NULLABLE_REQUIREMENTS =

The POINTER_REQUIREMENTS a NULL argument already satisfies: both ask for a pointer the callee will leave alone, and NULL is how that is asked for.

i[nullable_deref null].freeze

Instance Method Summary collapse

Instance Method Details

#address_deref0?(type, obj) ⇒ Boolean

Whether (type, obj) is an address constraint on a bare (deref-0) target, i.e. one carrying a base register and offset to normalise.



64
65
66
# File 'lib/one_gadget/emulators/constraints.rb', line 64

def address_deref0?(type, obj)
  ADDRESS_TYPES.include?(type) && obj.deref_count.zero?
end

#closed_fdsArray<String>



39
40
41
# File 'lib/one_gadget/emulators/constraints.rb', line 39

def closed_fds
  @closed_fds.uniq
end

#constraint_key(type, obj) ⇒ Object

De-duplication key: an address constraint collapses per (type, base) so constraints of different types on the same register stay distinct; a raw constraint keys on its own text.



74
75
76
77
78
# File 'lib/one_gadget/emulators/constraints.rb', line 74

def constraint_key(type, obj)
  return obj unless ADDRESS_TYPES.include?(type)

  [type, obj.deref_count.zero? ? obj.obj.to_s : obj.to_s]
end

#constraintsArray<String>



45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/one_gadget/emulators/constraints.rb', line 45

def constraints
  finalize_deferred_reads
  return [] if @constraints.empty?

  # An address constraint is keyed by its base register (deref-0) or full
  # expression (compound); several through one base (e.g. stores at reg+0x0
  # and reg+0x8) impose the same requirement, so keep just the smallest
  # offset (sort ascending, then uniq keeps that first).
  cons = @constraints.sort_by { |type, obj| address_deref0?(type, obj) ? obj.immi : 0 }
                     .uniq { |type, obj| constraint_key(type, obj) }
  cons = drop_restated_null(drop_implied_nonzero(cons))
  cons.map { |type, obj| render_constraint(type, obj) }.sort
end

#drop_implied_nonzero(cons) ⇒ Array<[Symbol, Object]>

Drop a " != 0x0" branch constraint that another constraint already implies: an address constraint (+writable: +imm+ store target, or readable: <reg>) forces to be a valid (mapped, non-NULL) pointer, so a NULL-check branch on the same register adds nothing. Keeps the emitted set minimal.



100
101
102
103
104
105
106
107
108
109
# File 'lib/one_gadget/emulators/constraints.rb', line 100

def drop_implied_nonzero(cons)
  nonzero_regs = cons.filter_map do |type, obj|
    obj.obj.to_s if address_deref0?(type, obj)
  end
  return cons if nonzero_regs.empty?

  cons.reject do |type, obj|
    type == :cmp && obj[1] == '!=' && obj[2] == Conditional::ZERO && nonzero_regs.include?(obj[0])
  end
end

#drop_restated_null(cons) ⇒ Array<[Symbol, Object]>

Drop a " == 0x0" branch constraint that a NULL requirement on the same value already states (see #require_null). Both ask for the same zero, and the one naming it NULL is the one that says what the zero is for.



116
117
118
119
120
121
122
123
# File 'lib/one_gadget/emulators/constraints.rb', line 116

def drop_restated_null(cons)
  nulls = cons.filter_map { |type, obj| obj[/\A(.+) == NULL\z/, 1] if type == :raw }
  return cons if nulls.empty?

  cons.reject do |type, obj|
    type == :cmp && obj[1] == '==' && obj[2] == Conditional::ZERO && nulls.include?(obj[0])
  end
end

#render_constraint(type, obj) ⇒ String

Render a constraint to its output string.



84
85
86
87
88
89
90
91
# File 'lib/one_gadget/emulators/constraints.rb', line 84

def render_constraint(type, obj)
  case type
  when :writable then "writable: #{obj}"
  when :readable then "readable: #{obj}"
  when :cmp then obj.join(' ')
  else obj
  end
end