Skip to content

Commit 184334b

Browse files
committed
Improves deregistering datastore options validation
1 parent 5f926ff commit 184334b

2 files changed

Lines changed: 552 additions & 0 deletions

File tree

lib/msf/core/module_data_store.rb

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ def initialize(m)
1515
super()
1616

1717
@_module = m
18+
@deregistered_keys = []
1819
end
1920

2021
#
@@ -27,6 +28,56 @@ def copy
2728
new_instance
2829
end
2930

31+
# Grab any available deregistered_keys, otherwise return a blank array
32+
#
33+
# @param [Msf::DataStore] other The other datastore to copy state from
34+
# @return [Msf::DataStore] the current datastore instance
35+
def copy_state(other)
36+
super
37+
incoming = other.respond_to?(:deregistered_keys, true) ? other.deregistered_keys : []
38+
incoming.each do |key|
39+
@deregistered_keys << key unless @deregistered_keys.any? { |k| k.casecmp?(key) }
40+
end
41+
42+
# Strip any deregistered keys that may have been written into @user_defined
43+
# by the parent's copy_state (e.g. during reverse_merge! which calls
44+
# copy_state with a plain DataStore result that contains all merged values).
45+
@deregistered_keys.each do |key|
46+
k = find_key_case(key)
47+
@user_defined.delete(k)
48+
end
49+
50+
# Need self here to return datastore instance from parent
51+
self
52+
end
53+
54+
# Track deregistered keys
55+
#
56+
# @param [String] name
57+
# @return [nil]
58+
def remove_option(name)
59+
super
60+
@deregistered_keys << name
61+
62+
# Need nil here to return original nil value from parent
63+
nil
64+
end
65+
66+
# Imports options and clears any matching keys from the deregistered list.
67+
# This allows a module to call deregister_options followed by register_options
68+
# for the same key and ensure we remove the re-registered option from the
69+
# tracked @deregistered_keys
70+
#
71+
# @param [Msf::OptionContainer] options
72+
# @param [String, nil] imported_by
73+
# @param [Boolean] overwrite
74+
def import_options(options, imported_by = nil, overwrite = true)
75+
options.each_option do |name, _option|
76+
@deregistered_keys.delete_if { |k| k.casecmp?(name) }
77+
end
78+
super
79+
end
80+
3081
# Search for a value within the current datastore, taking into consideration any registered aliases, fallbacks, etc.
3182
# If a value is not present in the current datastore, the global parent store will be referenced instead
3283
#
@@ -36,6 +87,9 @@ def search_for(key)
3687
k = find_key_case(key)
3788
return search_result(:user_defined, @user_defined[k]) if @user_defined.key?(k)
3889

90+
# If the module has not registered the key then return early as it has been deregistered
91+
return search_result(:not_found, nil) if should_filter_key?(key)
92+
3993
# Preference globally set values over a module's option default
4094
framework_datastore_search = search_framework_datastore(key)
4195
return framework_datastore_search if framework_datastore_search.found? && !framework_datastore_search.default?
@@ -61,8 +115,57 @@ def search_for(key)
61115
search_framework_datastore(k)
62116
end
63117

118+
# Override the write path so that keys which were explicitly deregistered on
119+
# the owning module are dropped.
120+
#
121+
# Compatible and functions as normal when:
122+
# - the key has never been deregistered via deregister_options
123+
# - there is no associated module (@_module is nil)
124+
#
125+
# @param [String] key
126+
# @param [Object] value
127+
def []=(key, value)
128+
return if should_filter_key?(key)
129+
130+
super
131+
end
132+
133+
# Override merge! so that when merging a DataStore (which writes directly to
134+
# @user_defined, bypassing []=), any deregistered keys are stripped out
135+
# afterward. This prevents a caller from injecting a deregistered option's
136+
# value by merging a datastore that contains it.
137+
#
138+
# When merging a plain Hash the parent's merge! already routes through []=,
139+
# so deregistered keys are filtered at that point and no extra work is needed.
140+
#
141+
# @param [Msf::DataStore, Hash] other
142+
# @return [Msf::ModuleDataStore]
143+
def merge!(other)
144+
super
145+
if other.is_a?(DataStore)
146+
@deregistered_keys.each do |key|
147+
k = find_key_case(key)
148+
@user_defined.delete(k)
149+
@options.delete_if { |opt_name, _| opt_name.casecmp?(k) }
150+
@aliases.delete_if { |_, v| v.casecmp?(k) }
151+
end
152+
end
153+
self
154+
end
155+
64156
protected
65157

158+
attr_reader :deregistered_keys
159+
160+
# Returns true when the write should be silently dropped because the key
161+
# was explicitly deregistered via deregister_options.
162+
#
163+
# @param [String] key
164+
# @return [Boolean]
165+
def should_filter_key?(key)
166+
@deregistered_keys.any? { |deregistered| deregistered.casecmp?(key) }
167+
end
168+
66169
# Search the framework datastore
67170
#
68171
# @param [String] key The key to search for

0 commit comments

Comments
 (0)