@@ -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