Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
HTML API: Prevent out-of-bounds string access.
Rebuild of 8905faf after #5725.

Co-authored-by: Jon Surrell <[email protected]>
Co-authored-by: Bernie Reiter <[email protected]>
  • Loading branch information
3 people committed Dec 20, 2023
commit 9e7167a3d1b4b4d1e4525072b2f96779788fb92d
21 changes: 13 additions & 8 deletions src/wp-includes/html-api/class-wp-html-tag-processor.php
Original file line number Diff line number Diff line change
Expand Up @@ -1312,7 +1312,7 @@ private function parse_next_tag() {

$this->token_starts_at = $at;

if ( '/' === $this->html[ $at + 1 ] ) {
if ( $at + 1 < $doc_length && '/' === $this->html[ $at + 1 ] ) {
$this->is_closing_tag = true;
++$at;
} else {
Expand Down Expand Up @@ -1346,7 +1346,7 @@ private function parse_next_tag() {
* Abort if no tag is found before the end of
* the document. There is nothing left to parse.
*/
if ( $at + 1 >= strlen( $html ) ) {
if ( $at + 1 >= $doc_length ) {
$this->parser_state = self::STATE_INCOMPLETE;

return false;
Expand All @@ -1368,7 +1368,7 @@ private function parse_next_tag() {
) {
$closer_at = $at + 4;
// If it's not possible to close the comment then there is nothing more to scan.
if ( strlen( $html ) <= $closer_at ) {
if ( $doc_length <= $closer_at ) {
$this->parser_state = self::STATE_INCOMPLETE;

return false;
Expand All @@ -1388,20 +1388,20 @@ private function parse_next_tag() {
* See https://html.spec.whatwg.org/#parse-error-incorrectly-closed-comment
*/
--$closer_at; // Pre-increment inside condition below reduces risk of accidental infinite looping.
while ( ++$closer_at < strlen( $html ) ) {
while ( ++$closer_at < $doc_length ) {
$closer_at = strpos( $html, '--', $closer_at );
if ( false === $closer_at ) {
$this->parser_state = self::STATE_INCOMPLETE;

return false;
}

if ( $closer_at + 2 < strlen( $html ) && '>' === $html[ $closer_at + 2 ] ) {
if ( $closer_at + 2 < $doc_length && '>' === $html[ $closer_at + 2 ] ) {
$at = $closer_at + 3;
continue 2;
}

if ( $closer_at + 3 < strlen( $html ) && '!' === $html[ $closer_at + 2 ] && '>' === $html[ $closer_at + 3 ] ) {
if ( $closer_at + 3 < $doc_length && '!' === $html[ $closer_at + 2 ] && '>' === $html[ $closer_at + 3 ] ) {
$at = $closer_at + 4;
continue 2;
}
Expand All @@ -1414,7 +1414,7 @@ private function parse_next_tag() {
* https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
*/
if (
strlen( $html ) > $at + 8 &&
$doc_length > $at + 8 &&
'[' === $html[ $at + 2 ] &&
'C' === $html[ $at + 3 ] &&
'D' === $html[ $at + 4 ] &&
Expand All @@ -1440,7 +1440,7 @@ private function parse_next_tag() {
* https://html.spec.whatwg.org/multipage/parsing.html#tag-open-state
*/
if (
strlen( $html ) > $at + 8 &&
$doc_length > $at + 8 &&
( 'D' === $html[ $at + 2 ] || 'd' === $html[ $at + 2 ] ) &&
( 'O' === $html[ $at + 3 ] || 'o' === $html[ $at + 3 ] ) &&
( 'C' === $html[ $at + 4 ] || 'c' === $html[ $at + 4 ] ) &&
Expand Down Expand Up @@ -1512,6 +1512,11 @@ private function parse_next_tag() {
* See https://html.spec.whatwg.org/#parse-error-invalid-first-character-of-tag-name
*/
if ( $this->is_closing_tag ) {
// No chance of finding a closer.
if ( $at + 3 > $doc_length ) {
return false;
}

$closer_at = strpos( $html, '>', $at + 3 );
if ( false === $closer_at ) {
$this->parser_state = self::STATE_INCOMPLETE;
Expand Down
18 changes: 18 additions & 0 deletions tests/phpunit/tests/html-api/wpHtmlTagProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -2691,4 +2691,22 @@ public function data_updating_attributes_in_malformed_html() {
),
);
}

/**
* @covers WP_HTML_Tag_Processor::next_tag
*/
public function test_handles_malformed_taglike_open_short_html() {
$p = new WP_HTML_Tag_Processor( '<' );
$result = $p->next_tag();
$this->assertFalse( $result, 'Did not handle "<" html properly.' );
}

/**
* @covers WP_HTML_Tag_Processor::next_tag
*/
public function test_handles_malformed_taglike_close_short_html() {
$p = new WP_HTML_Tag_Processor( '</ ' );
$result = $p->next_tag();
$this->assertFalse( $result, 'Did not handle "</ " html properly.' );
}
}