diff --git a/src/wp-includes/ID3/getid3.lib.php b/src/wp-includes/ID3/getid3.lib.php index 911876eed6ad4..c0aa1ee3b1676 100644 --- a/src/wp-includes/ID3/getid3.lib.php +++ b/src/wp-includes/ID3/getid3.lib.php @@ -746,10 +746,14 @@ public static function XML2array($XMLstring) { // https://core.trac.wordpress.org/changeset/29378 // This function has been deprecated in PHP 8.0 because in libxml 2.9.0, external entity loading is // disabled by default, but is still needed when LIBXML_NOENT is used. - $loader = @libxml_disable_entity_loader(true); + $loader = libxml_disable_entity_loader(true); + if ($loader === false && !libxml_disable_entity_loader(true)) { + // Failed to disable external entity loading — refuse to parse to prevent XXE + return false; + } $XMLobject = simplexml_load_string($XMLstring, 'SimpleXMLElement', GETID3_LIBXML_OPTIONS); $return = self::SimpleXMLelement2array($XMLobject); - @libxml_disable_entity_loader($loader); + libxml_disable_entity_loader($loader); return $return; } } else { diff --git a/src/wp-includes/ID3/getid3.php b/src/wp-includes/ID3/getid3.php index 4e7ccf3356831..a2cdc481aa66c 100644 --- a/src/wp-includes/ID3/getid3.php +++ b/src/wp-includes/ID3/getid3.php @@ -387,7 +387,7 @@ class getID3 */ protected $startup_warning = ''; - const VERSION = '1.9.26-202609042051'; + const VERSION = '1.9.28-202610301738'; const FREAD_BUFFER_SIZE = 32768; const ATTACHMENTS_NONE = false; @@ -1008,7 +1008,7 @@ public function GetFileFormatArray() { 'mime_type' => 'audio/dsd', ), - // DTS - audio - Dolby Theatre System + // DTS - audio - Digital Theater Systems 'dts' => array( 'pattern' => '^\\x7F\\xFE\\x80\\x01', 'group' => 'audio', @@ -1606,7 +1606,7 @@ public function GetFileFormat(&$filedata, $filename='') { // and verify there's at least one instance of "TRACK xx AUDIO" in the file $GetFileFormatArray = $this->GetFileFormatArray(); $info = $GetFileFormatArray['cue']; - $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; + $info['include'] = 'module.'.$info['group'].'.'.$info['module'].'.php'; return $info; } @@ -1629,14 +1629,21 @@ public function CharConvert(&$array, $encoding) { // loop thru array foreach ($array as $key => $value) { - // go recursive - if (is_array($value)) { + if ($key == 'picture') { // https://github.com/JamesHeinrich/getID3/issues/508 + + // pictures are binary data and should not be altered + continue; + + } elseif (is_array($value)) { + + // convert arrays recursively $this->CharConvert($array[$key], $encoding); - } - // convert string - elseif (is_string($value)) { + } elseif (is_string($value)) { + + // actually convert only strings $array[$key] = trim(getid3_lib::iconv_fallback($encoding, $this->encoding, $value)); + } } } @@ -1690,7 +1697,7 @@ public function HandleAllTags() { if (is_string($value)) { $value = trim($value, " \r\n\t"); // do not trim nulls from $value!! Unicode characters will get mangled if trailing nulls are removed! } - if (isset($value) && $value !== "") { + if (isset($value) && ($value !== '')) { if (!is_numeric($key)) { $this->info['tags'][trim($tag_name)][trim($tag_key)][$key] = $value; } else { diff --git a/src/wp-includes/ID3/module.audio-video.quicktime.php b/src/wp-includes/ID3/module.audio-video.quicktime.php index 161702ba186b6..d298024e11ea6 100644 --- a/src/wp-includes/ID3/module.audio-video.quicktime.php +++ b/src/wp-includes/ID3/module.audio-video.quicktime.php @@ -827,6 +827,12 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset } } + // prevent memory exhaustion from crafted files: each "stsd" entry is at least 16 bytes (4 size + 4 data_format + 6 reserved + 2 reference_index) + $max_entries = (int) floor(max(0, strlen($atom_data) - 8) / 16); + if ($atom_structure['number_entries'] > $max_entries) { + $this->warning('QuickTime atom "stsd" claims '.$atom_structure['number_entries'].' entries but only has data for '.$max_entries.', truncating'); + $atom_structure['number_entries'] = $max_entries; + } $stsdEntriesDataOffset = 8; for ($i = 0; $i < (int) $atom_structure['number_entries']; $i++) { $atom_structure['sample_description_table'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $stsdEntriesDataOffset, 4)); @@ -1180,6 +1186,12 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + // prevent memory exhaustion from crafted files: each "dref" entry is at least 12 bytes (4 size + 4 type + 1 version + 3 flags) + $max_entries = (int) floor(max(0, strlen($atom_data) - 8) / 12); + if ($atom_structure['number_entries'] > $max_entries) { + $this->warning('QuickTime atom "dref" claims '.$atom_structure['number_entries'].' entries but only has data for '.$max_entries.', truncating'); + $atom_structure['number_entries'] = $max_entries; + } $drefDataOffset = 8; for ($i = 0; $i < $atom_structure['number_entries']; $i++) { $atom_structure['data_references'][$i]['size'] = getid3_lib::BigEndian2Int(substr($atom_data, $drefDataOffset, 4)); @@ -1318,6 +1330,12 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); // hardcoded: 0x0000 $atom_structure['number_entries'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + // prevent memory exhaustion from crafted files: each "elst" entry is at least 12 bytes (4 track_duration + 4 media_time + 4 media_rate) + $max_entries = (int) floor(max(0, strlen($atom_data) - 8) / 12); + if ($atom_structure['number_entries'] > $max_entries) { + $this->warning('QuickTime atom "elst" claims '.$atom_structure['number_entries'].' entries but only has data for '.$max_entries.', truncating'); + $atom_structure['number_entries'] = $max_entries; + } for ($i = 0; $i < $atom_structure['number_entries']; $i++ ) { $atom_structure['edit_list'][$i]['track_duration'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 0, 4)); $atom_structure['edit_list'][$i]['media_time'] = getid3_lib::BigEndian2Int(substr($atom_data, 8 + ($i * 12) + 4, 4)); @@ -1800,6 +1818,12 @@ public function QuicktimeParseAtom($atomname, $atomsize, $atom_data, $baseoffset $atom_structure['version'] = getid3_lib::BigEndian2Int(substr($atom_data, 0, 1)); $atom_structure['flags_raw'] = getid3_lib::BigEndian2Int(substr($atom_data, 1, 3)); $atom_structure['entry_count'] = getid3_lib::BigEndian2Int(substr($atom_data, 4, 4)); + // prevent memory exhaustion from crafted files: each "keys" entry is at least 8 bytes (4 key_size + 4 key_namespace) + $max_entries = (int) floor(max(0, strlen($atom_data) - 8) / 8); + if ($atom_structure['entry_count'] > $max_entries) { + $this->warning('QuickTime atom "keys" claims '.$atom_structure['entry_count'].' entries but only has data for '.$max_entries.', truncating'); + $atom_structure['entry_count'] = $max_entries; + } $keys_atom_offset = 8; $keys_index_base = (!empty($info['quicktime']['temp_meta_key_names']) ? count($info['quicktime']['temp_meta_key_names']) : 0); // file may contain multiple "keys" entries, starting index should be culmulative not reset to 1 on each set; https://github.com/JamesHeinrich/getID3/issues/452 diff --git a/src/wp-includes/ID3/module.tag.id3v2.php b/src/wp-includes/ID3/module.tag.id3v2.php index 51a6b54104ede..7a5a8fcdaf2d5 100644 --- a/src/wp-includes/ID3/module.tag.id3v2.php +++ b/src/wp-includes/ID3/module.tag.id3v2.php @@ -495,6 +495,11 @@ public function Analyze() { $info['replay_gain']['album']['adjustment'] = floatval(trim(str_replace('dB', '', $txxx_array['data']))); } break; + case 'replaygain_album_peak': + if (empty($info['replay_gain']['album']['peak']) && !empty($txxx_array['data'])) { + $info['replay_gain']['album']['peak'] = floatval($txxx_array['data']); + } + break; } } } @@ -622,8 +627,10 @@ public function ParseID3v2Frame(&$parsedFrame) { if (!function_exists('gzuncompress')) { $this->warning('gzuncompress() support required to decompress ID3v2 frame "'.$parsedFrame['frame_name'].'"'); } else { - if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4))) { - //if ($decompresseddata = @gzuncompress($parsedFrame['data'])) { + // https://github.com/JamesHeinrich/getID3/security/advisories/GHSA-c2xw-vp6w-gpph + // Set a limit of 1/4 of PHP memory limit (if known), or default hard cap of 32MB if unknown. + $decompression_limit = ($this->getid3->memory_limit > 0) ? (int) round($this->getid3->memory_limit / 4) : 32*1024*1024; + if ($decompresseddata = @gzuncompress(substr($parsedFrame['data'], 4), $decompression_limit)) { $parsedFrame['data'] = $decompresseddata; unset($decompresseddata); } else { @@ -702,6 +709,87 @@ public function ParseID3v2Frame(&$parsedFrame) { //unset($parsedFrame['data']); do not unset, may be needed elsewhere, e.g. for replaygain + } elseif ((($id3v2_majorversion == 3) && ($parsedFrame['frame_name'] == 'IPLS')) || // 4.4 IPLS Involved people list (ID3v2.3 only) + (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'IPL')) || // 4.4 IPL Involved people list (ID3v2.2 only) + (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'TIPL')) || // 4.2.2 TIPL Involved people list (ID3v2.4 only) + (($id3v2_majorversion >= 4) && ($parsedFrame['frame_name'] == 'TMCL'))) { // 4.2.2 TMCL Musician credits list (ID3v2.4 only) + // http://id3.org/id3v2.3.0#sec4.4 + // There may only be one 'IPL' frame in each tag + //
+ // Text encoding $xx + // People list strings + + $frame_offset = 0; + $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); + if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { + $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); + } + $parsedFrame['encodingid'] = $frame_textencoding; + $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']); + $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset); + + // https://www.getid3.org/phpBB3/viewtopic.php?t=1369 + // "this tag typically contains null terminated strings, which are associated in pairs" + // "there are users that use the tag incorrectly" + $IPLS_parts_unsorted = array(); + if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) { + // UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding + $thisILPS = ''; + for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) { + $twobytes = substr($parsedFrame['data_raw'], $i, 2); + if ($twobytes === "\x00\x00") { + $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); + $thisILPS = ''; + } else { + $thisILPS .= $twobytes; + } + } + if (strlen($thisILPS) > 0) { // extract the last part if any, even if it's only a BOM with no text following + $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); + } + } else { + // ISO-8859-1 or UTF-8 or other single-byte-null character set + $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']); + if (end($IPLS_parts_unsorted) === '') { + // there was a terminating null after the last part and explode appended an empty string; drop it + array_pop($IPLS_parts_unsorted); + } + } + + $IPLS_parts = array(); + if (count($IPLS_parts_unsorted) == 1) { + // Just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson". + // Use the normal output format but with empty roles. + $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $IPLS_parts_unsorted[0]); + foreach ($IPLS_parts_sorted as $person) { + $IPLS_parts[] = array( + 'position' => '', + 'person' => $person + ); + } + } + else { + while (\count($IPLS_parts_unsorted) >= 2) { + $IPLS_parts[] = array( + 'position' => array_shift($IPLS_parts_unsorted), + 'person' => array_shift($IPLS_parts_unsorted) + ); + } + if (\count($IPLS_parts_unsorted) > 0) { + $this->warning("Odd number of {$parsedFrame['frame_name']} parts - expected even number forming role/name pairs"); + $IPLS_parts[] = array( + 'position' => array_shift($IPLS_parts_unsorted), + 'person' => '' + ); + } + } + $parsedFrame['data'] = $IPLS_parts; + + if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { + $info['id3v2']['comments'][$parsedFrame['framenameshort']] = $parsedFrame['data']; + } + + } elseif ($parsedFrame['frame_name'][0] == 'T') { // 4.2. T??[?] Text information frame // There may only be one text information frame of its kind in an tag. //
- // Text encoding $xx - // People list strings - - $frame_offset = 0; - $frame_textencoding = ord(substr($parsedFrame['data'], $frame_offset++, 1)); - if ((($id3v2_majorversion <= 3) && ($frame_textencoding > 1)) || (($id3v2_majorversion == 4) && ($frame_textencoding > 3))) { - $this->warning('Invalid text encoding byte ('.$frame_textencoding.') in frame "'.$parsedFrame['frame_name'].'" - defaulting to ISO-8859-1 encoding'); - } - $parsedFrame['encodingid'] = $frame_textencoding; - $parsedFrame['encoding'] = $this->TextEncodingNameLookup($parsedFrame['encodingid']); - $parsedFrame['data_raw'] = (string) substr($parsedFrame['data'], $frame_offset); - - // https://www.getid3.org/phpBB3/viewtopic.php?t=1369 - // "this tag typically contains null terminated strings, which are associated in pairs" - // "there are users that use the tag incorrectly" - $IPLS_parts = array(); - if (strpos($parsedFrame['data_raw'], "\x00") !== false) { - $IPLS_parts_unsorted = array(); - if (((strlen($parsedFrame['data_raw']) % 2) == 0) && ((substr($parsedFrame['data_raw'], 0, 2) == "\xFF\xFE") || (substr($parsedFrame['data_raw'], 0, 2) == "\xFE\xFF"))) { - // UTF-16, be careful looking for null bytes since most 2-byte characters may contain one; you need to find twin null bytes, and on even padding - $thisILPS = ''; - for ($i = 0; $i < strlen($parsedFrame['data_raw']); $i += 2) { - $twobytes = substr($parsedFrame['data_raw'], $i, 2); - if ($twobytes === "\x00\x00") { - $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); - $thisILPS = ''; - } else { - $thisILPS .= $twobytes; - } - } - if (strlen($thisILPS) > 2) { // 2-byte BOM - $IPLS_parts_unsorted[] = getid3_lib::iconv_fallback($parsedFrame['encoding'], $info['id3v2']['encoding'], $thisILPS); - } - } else { - // ISO-8859-1 or UTF-8 or other single-byte-null character set - $IPLS_parts_unsorted = explode("\x00", $parsedFrame['data_raw']); - } - if (count($IPLS_parts_unsorted) == 1) { - // just a list of names, e.g. "Dino Baptiste, Jimmy Copley, John Gordon, Bernie Marsden, Sharon Watson" - foreach ($IPLS_parts_unsorted as $key => $value) { - $IPLS_parts_sorted = preg_split('#[;,\\r\\n\\t]#', $value); - $position = ''; - foreach ($IPLS_parts_sorted as $person) { - $IPLS_parts[] = array('position'=>$position, 'person'=>$person); - } - } - } elseif ((count($IPLS_parts_unsorted) % 2) == 0) { - $position = ''; - $person = ''; - foreach ($IPLS_parts_unsorted as $key => $value) { - if (($key % 2) == 0) { - $position = $value; - } else { - $person = $value; - $IPLS_parts[] = array('position'=>$position, 'person'=>$person); - $position = ''; - $person = ''; - } - } - } else { - foreach ($IPLS_parts_unsorted as $key => $value) { - $IPLS_parts[] = array($value); - } - } - - } else { - $IPLS_parts = preg_split('#[;,\\r\\n\\t]#', $parsedFrame['data_raw']); - } - $parsedFrame['data'] = $IPLS_parts; - - if (!empty($parsedFrame['framenameshort']) && !empty($parsedFrame['data'])) { - $info['id3v2']['comments'][$parsedFrame['framenameshort']][] = $parsedFrame['data']; - } - - } elseif ((($id3v2_majorversion >= 3) && ($parsedFrame['frame_name'] == 'MCDI')) || // 4.4 MCDI Music CD identifier (($id3v2_majorversion == 2) && ($parsedFrame['frame_name'] == 'MCI'))) { // 4.5 MCI Music CD identifier // There may only be one 'MCDI' frame in each tag