VYPR
Medium severity5.3NVD Advisory· Published May 28, 2026· Updated May 28, 2026

CVE-2026-49130

CVE-2026-49130

Description

Music Player Daemon (MPD) before version 0.24.11 contains a CRLF injection vulnerability in the xspf_char_data function within the XSPF playlist plugin that allows attackers to embed literal CR/LF bytes in URI fields by supplying a malicious XSPF playlist with XML numeric character references. Attackers can inject forged key-value lines through the location field into MPD protocol responses including playlistinfo, currentsong, and listplaylist outputs, as well as the state file writer, by exploiting Expat's decoding of numeric character references prior to the character data callback.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

CRLF injection in MPD's XSPF playlist plugin allows attackers to inject arbitrary protocol responses via malicious playlist files.

Vulnerability

The Music Player Daemon (MPD) before version 0.24.11 contains a CRLF injection vulnerability in the xspf_char_data function within the XSPF playlist plugin. The Expat XML parser decodes numeric character references (e.g., becomes newline) before invoking the character data callback, allowing an attacker to embed literal CR/LF bytes into the URI field of a malicious XSPF playlist. These unsanitized URIs are then used in MPD protocol responses and state file writing. Affected versions: all prior to 0.24.11. [1][2][3]

Exploitation

An attacker can serve a crafted XSPF playlist containing a ` element with embedded and sequences. When a client (unauthenticated) loads this playlist via commands like load, the injected CR/LF bytes are passed through to MPD's response writer, allowing the attacker to forge arbitrary key-value lines in outputs such as playlistinfo, currentsong, and listplaylist`. The reproduction steps involve hosting the malicious XSPF and connecting with a netcat client. [3]

Impact

Successful exploitation allows an attacker to inject arbitrary metadata lines into MPD protocol responses, potentially misleading clients or causing misbehavior. The injection can also affect the state file, leading to persistent corruption. The attacker does not require authentication; only network access to the MPD server is needed. The CIA impact is primarily integrity and availability, as forged responses can disrupt client operations. [3]

Mitigation

The vulnerability is fixed in MPD version 0.24.11, released on 2026-05-15. The fix adds a VerifyUriUTF8 check that rejects URIs containing newline characters. Users should upgrade to v0.24.11 or later. No workaround is available for earlier versions. The issue is not listed in CISA's KEV as of the publication date. [1][2][4]

AI Insight generated on May 28, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

2

Patches

1
855085b35c67

playlist: do not allow newlines in song URIs

https://github.com/musicplayerdaemon/mpdMax KellermannMay 14, 2026via nvd-ref
8 files changed · +29 3
  • NEWS+1 0 modified
    @@ -1,4 +1,5 @@
     ver 0.24.11 (not yet released)
    +* playlist: do not allow newlines in song URIs
     
     ver 0.24.10 (2026/05/06)
     * input
    
  • src/playlist/plugins/AsxPlaylistPlugin.cxx+2 1 modified
    @@ -4,6 +4,7 @@
     #include "AsxPlaylistPlugin.hxx"
     #include "../PlaylistPlugin.hxx"
     #include "../MemorySongEnumerator.hxx"
    +#include "protocol/Verify.hxx"
     #include "tag/Builder.hxx"
     #include "tag/Table.hxx"
     #include "util/ASCII.hxx"
    @@ -99,7 +100,7 @@ asx_end_element(void *user_data, const XML_Char *element_name)
     
     	case AsxParser::ENTRY:
     		if (StringEqualsCaseASCII(element_name, "entry")) {
    -			if (!parser->location.empty())
    +			if (VerifyUriUTF8(parser->location))
     				parser->songs.emplace_front(std::move(parser->location),
     							    parser->tag_builder.Commit());
     
    
  • src/playlist/plugins/meson.build+1 0 modified
    @@ -10,6 +10,7 @@ playlist_plugins_deps = [
       flac_dep,
       input_basic_dep,
       config_dep,
    +  protocol_dep,
     ]
     
     playlist_features.set('ENABLE_CUE', get_option('cue'))
    
  • src/playlist/plugins/PlsPlaylistPlugin.cxx+4 0 modified
    @@ -8,6 +8,7 @@
     #include "input/InputStream.hxx"
     #include "song/DetachedSong.hxx"
     #include "tag/Builder.hxx"
    +#include "protocol/Verify.hxx"
     #include "util/ASCII.hxx"
     #include "util/NumberParser.hxx"
     #include "util/StringCompare.hxx"
    @@ -113,6 +114,9 @@ ParsePls(TextInputStream &is, std::forward_list<DetachedSong> &songs)
     
     	auto i = songs.before_begin();
     	for (const auto &entry : entries) {
    +		if (!VerifyUriUTF8(entry.file))
    +			continue;
    +
     		TagBuilder tag;
     		if (!entry.title.empty())
     			tag.AddItem(TAG_TITLE, entry.title);
    
  • src/playlist/plugins/RssPlaylistPlugin.cxx+2 1 modified
    @@ -5,6 +5,7 @@
     #include "../PlaylistPlugin.hxx"
     #include "../MemorySongEnumerator.hxx"
     #include "tag/Builder.hxx"
    +#include "protocol/Verify.hxx"
     #include "util/ASCII.hxx"
     #include "lib/expat/ExpatParser.hxx"
     
    @@ -85,7 +86,7 @@ rss_end_element(void *user_data, const XML_Char *element_name)
     
     	case RssParser::ITEM:
     		if (StringEqualsCaseASCII(element_name, "item")) {
    -			if (!parser->location.empty())
    +			if (VerifyUriUTF8(parser->location))
     				parser->songs.emplace_front(std::move(parser->location),
     							    parser->tag_builder.Commit());
     
    
  • src/playlist/plugins/XspfPlaylistPlugin.cxx+2 1 modified
    @@ -4,6 +4,7 @@
     #include "XspfPlaylistPlugin.hxx"
     #include "../PlaylistPlugin.hxx"
     #include "../MemorySongEnumerator.hxx"
    +#include "protocol/Verify.hxx"
     #include "song/DetachedSong.hxx"
     #include "input/InputStream.hxx"
     #include "tag/Builder.hxx"
    @@ -129,7 +130,7 @@ xspf_end_element(void *user_data, const XML_Char *element_name)
     
     	case XspfParser::TRACK:
     		if (strcmp(element_name, "track") == 0) {
    -			if (!parser->location.empty())
    +			if (VerifyUriUTF8(parser->location))
     				parser->songs.emplace_front(std::move(parser->location),
     							    parser->tag_builder.Commit());
     
    
  • src/protocol/Verify.cxx+6 0 modified
    @@ -42,3 +42,9 @@ VerifyRelativePathUTF8(std::string_view path_utf8) noexcept
     	// TODO check whether it's a relative path
     	return VerifyPathUTF8(path_utf8);
     }
    +
    +bool
    +VerifyUriUTF8(std::string_view uri_utf8) noexcept
    +{
    +	return !uri_utf8.empty() && VerifyStringUTF8(uri_utf8);
    +}
    
  • src/protocol/Verify.hxx+11 0 modified
    @@ -54,3 +54,14 @@ VerifyPathUTF8(std::string_view path_utf8) noexcept;
     [[gnu::pure]]
     bool
     VerifyRelativePathUTF8(std::string_view path_utf8) noexcept;
    +
    +/**
    + * Is this a valid URI string (for transmitting it as a value over
    + * the MPD text protocol)?
    + *
    + * In the MPD protocol, URIs can be actual URIs or absolute/relative
    + * file paths.  This function allows all of these.
    + */
    +[[gnu::pure]]
    +bool
    +VerifyUriUTF8(std::string_view uri_utf8) noexcept;
    

Vulnerability mechanics

Root cause

"Missing validation of URI strings allows CR/LF bytes (decoded from XML numeric character references by Expat) to be injected into MPD protocol responses."

Attack vector

An attacker crafts a malicious XSPF (or ASX, RSS, PLS) playlist file containing a `<location>` tag whose value includes XML numeric character references such as `&#x0a;` or `&#x0d;`. Expat's XML parser decodes these references into literal CR/LF bytes before the character data callback fires, so the URI stored by MPD contains embedded newlines. When MPD later outputs that URI in protocol responses (e.g., `playlistinfo`, `currentsong`, `listplaylist`) or writes it to the state file, the newlines desynchronize the text-based MPD protocol, allowing the attacker to inject forged key-value lines into the response stream [ref_id=1]. No authentication is required; the attacker only needs to supply the malicious playlist to an MPD instance that accepts playlist uploads.

Affected code

The vulnerability resides in the XSPF playlist plugin (`src/playlist/plugins/XspfPlaylistPlugin.cxx`), specifically in the `xspf_char_data` function (not shown in the diff but referenced in the advisory). The patch also fixes the same class of bug in the ASX plugin (`AsxPlaylistPlugin.cxx`), RSS plugin (`RssPlaylistPlugin.cxx`), and PLS plugin (`PlsPlaylistPlugin.cxx`). The new `VerifyUriUTF8` function is declared in `src/protocol/Verify.hxx` and defined in `src/protocol/Verify.cxx`.

What the fix does

The patch introduces a new validation function `VerifyUriUTF8` in `src/protocol/Verify.cxx` that checks whether a URI string is non-empty and passes the existing `VerifyStringUTF8` check. This function is then called in the XSPF, ASX, RSS, and PLS playlist plugins at the point where a parsed location is accepted — replacing the previous check that only verified the string was non-empty (`!parser->location.empty()`). By rejecting URIs that contain non-UTF-8 characters (including embedded newlines), the patch prevents CR/LF injection into MPD protocol responses and the state file [patch_id=2980736].

Preconditions

  • inputThe attacker must be able to supply a malicious XSPF (or ASX, RSS, PLS) playlist file to the MPD instance.
  • configThe MPD instance must have the relevant playlist plugin enabled (XSPF, ASX, RSS, or PLS).
  • authNo authentication is required; the vulnerability is exploitable by any unauthenticated network client that can upload playlists.

Generated on May 28, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

7

News mentions

0

No linked articles in our index yet.