Skip to content

Commit 03766dd

Browse files
committed
Options: Return default value when option data cannot be unserialized.
When an option exists in the database but its serialized value is corrupted, maybe_unserialize() returns false, which get_option() incorrectly returned instead of the caller's $default_value. Fixes #59588.
1 parent e12ddb3 commit 03766dd

2 files changed

Lines changed: 86 additions & 1 deletion

File tree

src/wp-includes/option.php

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,22 @@ function get_option( $option, $default_value = false ) {
240240
$value = untrailingslashit( $value );
241241
}
242242

243+
$raw_value = $value;
244+
$value = maybe_unserialize( $value );
245+
246+
/*
247+
* If the stored value was serialized but could not be unserialized, `maybe_unserialize()`
248+
* returns `false`. In that case, return the default value to avoid incorrectly returning
249+
* `false` instead of the caller's expected default.
250+
*
251+
* Note: `b:0;` is the valid serialization of boolean `false`. When properly unserialized
252+
* it returns `false`, which is correct and should not be treated as a failure.
253+
*/
254+
if ( false === $value && is_serialized( $raw_value ) && 'b:0;' !== trim( $raw_value ) ) {
255+
/** This filter is documented in wp-includes/option.php */
256+
return apply_filters( "default_option_{$option}", $default_value, $option, $passed_default );
257+
}
258+
243259
/**
244260
* Filters the value of an existing option.
245261
*
@@ -253,7 +269,7 @@ function get_option( $option, $default_value = false ) {
253269
* unserialized prior to being returned.
254270
* @param string $option Option name.
255271
*/
256-
return apply_filters( "option_{$option}", maybe_unserialize( $value ), $option );
272+
return apply_filters( "option_{$option}", $value, $option );
257273
}
258274

259275
/**

tests/phpunit/tests/option/option.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,75 @@ public function test_serialized_data() {
161161
$this->assertTrue( delete_option( $key ) );
162162
}
163163

164+
/**
165+
* Tests that get_option() returns the default value when stored data is corrupted/unserializable.
166+
*
167+
* @ticket 59588
168+
*
169+
* @covers ::get_option
170+
*/
171+
public function test_get_option_returns_default_when_unserialize_fails() {
172+
global $wpdb;
173+
174+
// Insert a corrupted serialized value directly into the DB.
175+
$option_name = 'test_corrupted_option_59588';
176+
$wpdb->insert(
177+
$wpdb->options,
178+
array(
179+
'option_name' => $option_name,
180+
'option_value' => 'a:2:{i:0;s:3:"foo";', // Truncated/corrupted serialized array.
181+
'autoload' => 'yes',
182+
)
183+
);
184+
// Clear alloptions cache so get_option() reads from DB.
185+
wp_cache_delete( 'alloptions', 'options' );
186+
wp_cache_delete( $option_name, 'options' );
187+
188+
$default = array( 'default_key' => 'default_value' );
189+
$result = get_option( $option_name, $default );
190+
191+
// Cleanup.
192+
$wpdb->delete( $wpdb->options, array( 'option_name' => $option_name ) );
193+
wp_cache_delete( 'alloptions', 'options' );
194+
wp_cache_delete( $option_name, 'options' );
195+
196+
$this->assertSame( $default, $result, 'get_option() should return the default value when stored data cannot be unserialized.' );
197+
}
198+
199+
/**
200+
* Tests that get_option() correctly returns false when the stored value is a serialized boolean false.
201+
*
202+
* @ticket 59588
203+
*
204+
* @covers ::get_option
205+
*/
206+
public function test_get_option_returns_false_for_serialized_false_value() {
207+
global $wpdb;
208+
209+
// Insert the serialization of boolean false (`b:0;`) directly into the DB.
210+
$option_name = 'test_serialized_false_59588';
211+
$wpdb->insert(
212+
$wpdb->options,
213+
array(
214+
'option_name' => $option_name,
215+
'option_value' => 'b:0;',
216+
'autoload' => 'yes',
217+
)
218+
);
219+
// Clear alloptions cache so get_option() reads from DB.
220+
wp_cache_delete( 'alloptions', 'options' );
221+
wp_cache_delete( $option_name, 'options' );
222+
223+
$result = get_option( $option_name, 'should-not-be-returned' );
224+
225+
// Cleanup.
226+
$wpdb->delete( $wpdb->options, array( 'option_name' => $option_name ) );
227+
wp_cache_delete( 'alloptions', 'options' );
228+
wp_cache_delete( $option_name, 'options' );
229+
230+
$this->assertFalse( $result, 'get_option() should return false when the stored value is a serialized boolean false.' );
231+
}
232+
164233
/**
165234
* @ticket 23289
166235
*

0 commit comments

Comments
 (0)