Make WordPress Core

Opened 7 weeks ago

Last modified 16 hours ago

#64347 reopened defect (bug)

Fatal error when `wxr_cdata()` is called with non-string value

Reported by: desrosj's profile desrosj Owned by: westonruter's profile westonruter
Milestone: 6.9.1 Priority: normal
Severity: normal Version: 6.9
Component: Export Keywords: has-patch has-unit-tests dev-feedback fixed-major
Focuses: Cc:

Description

From this issue on the wp.org Support forums:

Hi

During export, a page opens with this message: “This site is unreachable. Website at https://kamerydrogowe.wlkp.net.pl/wp-admin/export.php?.. may be temporarily unavailable or may have been permanently moved to a new internet address. ERR_INVALID_RESPONSE”

WordPress, themes, plugins – updated. Theme enabled on Twenty tweny – still a problem. Plugin cache cleared, web browser cache and cookies cleared – still the problem. All plugins disabled – export downloads, but only 1.1 Mb instead of about 16 Mb.

I also got the message: “The old WordPress core file was not removed during the update: wp-includes/SimplePie/src/Core.php”

The following log output was followed in a follow up response:

[03-Dec-2025 20:47:17 UTC] PHP Warning:  Cannot modify header information - headers already sent by (output started at /wp-config.php:103) in /wp-includes/functions.php on line 7182
[03-Dec-2025 20:47:17 UTC] PHP Warning:  Cannot modify header information - headers already sent by (output started at /wp-config.php:103) in /wp-admin/includes/export.php on line 96
[03-Dec-2025 20:47:17 UTC] PHP Warning:  Cannot modify header information - headers already sent by (output started at /wp-config.php:103) in /wp-admin/includes/export.php on line 97
[03-Dec-2025 20:47:17 UTC] PHP Warning:  Cannot modify header information - headers already sent by (output started at /wp-config.php:103) in /wp-admin/includes/export.php on line 98
[03-Dec-2025 20:47:17 UTC] PHP Fatal error:  Uncaught TypeError: wp_is_valid_utf8(): Argument #1 ($bytes) must be of type string, null given, called in /wp-admin/includes/export.php on line 246 and defined in /wp-includes/utf8.php:39
Stack trace:
#0
/wp-admin/includes/export.php(246): wp_is_valid_utf8(NULL)
#1
/wp-admin/includes/export.php(682): wxr_cdata(NULL)
#2
/wp-admin/export.php(123): export_wp(Array)
#3 {main}
  thrown in
/wp-includes/utf8.php on line 39

Change History (20)

#1 @desrosj
7 weeks ago

Looking through wp-admin/includes/export.php, it seems that there are a few possible culprits.

If a plugin or theme is using a filter (the_title_export/the_content_export/the_excerpt_export) to return a value a non-string value such as NULL or an integer, there is no protection in place for this and a fatal error will occur due to the string type safety defined for wp_is_valid_utf8(). Though that seems to be a scenario that could come up, I don't think that's the root cause in this case because the user is still seeing the issue after disabling all plugins.

The other possibility is that a plugin or some custom code has explicitly set a meta value to a non-string value. Because export_wp() performs direct database queries to retrieve post/term/comment metadata, there's no guarantee that non-string values will be converted to strings as expected.

#2 @swissspidy
7 weeks ago

#64351 was marked as a duplicate.

This ticket was mentioned in PR #10595 on WordPress/wordpress-develop by @hbhalodia.


7 weeks ago
#3

  • Keywords has-patch added
  • Add soft type safe check to wxr_cdata function, and typecast the incoming input to string.

Trac ticket: https://core.trac.wordpress.org/ticket/64347

#4 @hbhalodia
7 weeks ago

Hi @swissspidy @desrosj

Since wp_is_valid_utf8 function is always expecting the incoming data to be string, we need o make sure what we send is always a string. Hence I have created a PR https://github.com/WordPress/wordpress-develop/pull/10595, which adds the soft type-safe, Since we do not know what can be input to wxr_cdata function, hence strict type check won't be feasible at the moment (let me know if needs to be stricter check), but then we need to make sure where this function is called, the param should passed as string, else it can throw the fatal on this function.

#5 follow-up: @desrosj
7 weeks ago

Thanks for the pull request, @hbhalodia!

Before we decide which specific solution is best, I think there are a few things we should do.

First, we should look into how were NULL or other non-string values exported in WordPress 6.8? Are they preserved? Converted to a string? Something else?

We should also write some unit tests that tests NULL/non-string values in different areas to demonstrate this failure. Doing so will help with the first item (the same test assertions should all pass in 6.8 confirming the previous behavior), and will help to narrow down which scenarios encounter this fatal error, and which ones are properly guarding against incorrect types already.

@hbhalodia are you able to help with either of these?

#6 in reply to: ↑ 5 @hbhalodia
7 weeks ago

Replying to desrosj:

Thanks for the pull request, @hbhalodia!

Before we decide which specific solution is best, I think there are a few things we should do.

First, we should look into how were NULL or other non-string values exported in WordPress 6.8? Are they preserved? Converted to a string? Something else?

We should also write some unit tests that tests NULL/non-string values in different areas to demonstrate this failure. Doing so will help with the first item (the same test assertions should all pass in 6.8 confirming the previous behavior), and will help to narrow down which scenarios encounter this fatal error, and which ones are properly guarding against incorrect types already.

@hbhalodia are you able to help with either of these?

Sure, I will work on this and would provide an update soon.

This ticket was mentioned in Slack in #forums by threadi. View the logs.


7 weeks ago

This ticket was mentioned in Slack in #core by skithund. View the logs.


7 weeks ago

#9 @albigdd
6 weeks ago

Thank you @hbhalodia for your patch. It fixed this issue for us.

#10 @westonruter
6 weeks ago

#64396 was marked as a duplicate.

#11 @jorbin
5 weeks ago

Thanks for the patch @hbhalodia, but I think automated tests are required and as @desrosj pointed out, there is a need to get a sense of what the 6.8 behavior was before deciding that returning an empty string is the correct behavior here.

#12 @westonruter
5 weeks ago

  • Keywords needs-unit-tests added

#13 @westonruter
5 weeks ago

In looking at the original error stack trace, the source of the error is at /wp-admin/includes/export.php(682):

<wp:meta_value><?php echo wxr_cdata( $meta->meta_value ); ?></wp:meta_value>

The problem is that $meta->meta_value may be null here, although I'm not sure why. The $meta variable is coming iterating over this array:

<?php
$wpdb->get_results( $wpdb->prepare( "SELECT * FROM $wpdb->postmeta WHERE post_id = %d", $post->ID ) )

It appears the issue is that the wp_postmeta.meta_value column is defined as a LONGTEXT which may also be NULL (source):

CREATE TABLE $wpdb->postmeta (
	meta_id bigint(20) unsigned NOT NULL auto_increment,
	post_id bigint(20) unsigned NOT NULL default '0',
	meta_key varchar(255) default NULL,
	meta_value longtext,
	PRIMARY KEY  (meta_id),
	KEY post_id (post_id),
	KEY meta_key (meta_key($max_index_length))
) $charset_collate;

According to the MySQL reference:

If neither NULL nor NOT NULL is specified, the column is treated as though NULL had been specified.

So this appears to be the source of the non-string value.

It didn't cause an error in WordPress 6.8 because wxr_cdata() was defined as:

<?php
function wxr_cdata( $str ) {
        if ( ! seems_utf8( $str ) ) {
                $str = utf8_encode( $str );
        }
        // $str = ent2ncr(esc_html($str));
        $str = '<![CDATA[' . str_replace( ']]>', ']]]]><![CDATA[>', $str ) . ']]>';

        return $str;
}

Whereas in WP 6.9 it is:

<?php
function wxr_cdata( $str ) {
        if ( ! wp_is_valid_utf8( $str ) ) {
                $str = utf8_encode( $str );
        }
        // $str = ent2ncr(esc_html($str));
        $str = '<![CDATA[' . str_replace( ']]>', ']]]]><![CDATA[>', $str ) . ']]>';

        return $str;
}

The difference is that seems_utf8() didn't have any PHP type defined for the $str argument:

function seems_utf8( $str )

Whereas wp_is_valid_utf8 does:

function wp_is_valid_utf8( string $string )

So to fix this issue, it seems casting the $str to a string will preserve the original behavior which would happen implicitly when $str is passed through str_replace(). Note that in PHP 8.1 this would have caused a deprecation error already anyway when the $str was passed into seems_utf8() in 6.8:

Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/src/wp-includes/formatting.php on line 888

The deprecation in 6.8 has become a fatal error in 6.9 due to the use of a PHP type for the $str param.

All this to say, I think at the beginning of wxr_cdata() can simply do this:

<?php
if ( is_scalar( $str ) ) {
        $str = (string) $str;
}

If, by chance, a non-scalar value (an array or object) is passed in erroneously, then this will cause a PHP fatal error in the same way as would be happening in 6.8 already.

#14 @westonruter
5 weeks ago

If we want to preserve an array showing up as "Array" in the export (with a warning) then just casting to a string would be the most consistent with the previous behavior:

<?php
$str = (string) $str;

In practice, however, I don't believe any non-scalar values are processed during an export.

#15 @hbhalodia
4 weeks ago

Hi Team, I have updated the PR with the testcases where it can fail in 6.9 if not typecasted to string, while it would work as expected for 6.8 without typecasting.

Let me know if there is any change required in UnitTest updation.

Thank You,

#16 @westonruter
4 weeks ago

  • Owner set to westonruter
  • Status changed from new to reviewing

#17 @westonruter
4 weeks ago

  • Resolution set to fixed
  • Status changed from reviewing to closed

In 61405:

Export: Fix fatal error when passing null to wxr_cdata() by casting passed value to string.

This ensures that wp_is_valid_utf8() does not cause a type error since it only accepts strings.

Developed in https://github.com/WordPress/wordpress-develop/pull/10595

Follow-up to [60630].

Props hbhalodia, westonruter, desrosj, albigdd, jorbin.
See #38044.
Fixes #64347.

#18 @westonruter
4 weeks ago

  • Keywords has-unit-tests dev-feedback fixed-major added; needs-unit-tests removed
  • Resolution fixed deleted
  • Status changed from closed to reopened

Re-opening for 6.9 backport consideration.

#19 @hbhalodia
13 days ago

#64478 was marked as a duplicate.

This ticket was mentioned in Slack in #core by jorbin. View the logs.


16 hours ago

Note: See TracTickets for help on using tickets.