loadfile: avoid infinite playlist loading loops

There are a number of ways one can craft a playlist file that refers to
itself or cleverly goes around in a loop to other playlist files. There
is obviously no use for this, but mpv spins around forever trying to
load the files so you have to just SIGTERM/SIGKILL it. We can be smarter
about this and attempt to detect it. The condition for detecting this is
surprisingly simple: the filename of the first entry in the playlist
must match a previous playlist path we stored. If we get this, we can
then log an error and stop playback. If there is a "real" file loaded at
any point in time, then we know it's not an infinite loop and clear out
the saved playlist paths. Fixes #3967.
This commit is contained in:
Dudemanguy 2023-08-14 14:13:02 -05:00
parent f19bafc0e4
commit 0b4a36476d
2 changed files with 30 additions and 0 deletions

View File

@ -275,6 +275,8 @@ typedef struct MPContext {
struct playlist_entry *playing; // currently playing file struct playlist_entry *playing; // currently playing file
char *filename; // immutable copy of playing->filename (or NULL) char *filename; // immutable copy of playing->filename (or NULL)
char *stream_open_filename; char *stream_open_filename;
char **playlist_paths; // used strictly for playlist validation
int playlist_paths_len;
enum stop_play_reason stop_play; enum stop_play_reason stop_play;
bool playback_initialized; // playloop can be run/is running bool playback_initialized; // playloop can be run/is running
int error_playing; int error_playing;

View File

@ -1309,6 +1309,28 @@ void prefetch_next(struct MPContext *mpctx)
} }
} }
static void clear_playlist_paths(struct MPContext *mpctx)
{
TA_FREEP(&mpctx->playlist_paths);
mpctx->playlist_paths_len = 0;
}
static bool infinite_playlist_loading_loop(struct MPContext *mpctx, struct playlist *pl)
{
if (pl->num_entries) {
struct playlist_entry *e = pl->entries[0];
for (int n = 0; n < mpctx->playlist_paths_len; n++) {
if (strcmp(mpctx->playlist_paths[n], e->filename) == 0) {
clear_playlist_paths(mpctx);
return true;
}
}
}
MP_TARRAY_APPEND(mpctx, mpctx->playlist_paths, mpctx->playlist_paths_len,
talloc_strdup(mpctx->playlist_paths, mpctx->filename));
return false;
}
// Destroy the complex filter, and remove the references to the filter pads. // Destroy the complex filter, and remove the references to the filter pads.
// (Call cleanup_deassociated_complex_filters() to close decoders/VO/AO // (Call cleanup_deassociated_complex_filters() to close decoders/VO/AO
// that are not connected anymore due to this.) // that are not connected anymore due to this.)
@ -1660,6 +1682,11 @@ static void play_current_file(struct MPContext *mpctx)
mp_delete_watch_later_conf(mpctx, mpctx->filename); mp_delete_watch_later_conf(mpctx, mpctx->filename);
struct playlist *pl = mpctx->demuxer->playlist; struct playlist *pl = mpctx->demuxer->playlist;
playlist_populate_playlist_path(pl, mpctx->filename); playlist_populate_playlist_path(pl, mpctx->filename);
if (infinite_playlist_loading_loop(mpctx, pl)) {
mpctx->stop_play = PT_STOP;
MP_ERR(mpctx, "Infinite playlist loading loop detected.\n");
goto terminate_playback;
}
transfer_playlist(mpctx, pl, &end_event.playlist_insert_id, transfer_playlist(mpctx, pl, &end_event.playlist_insert_id,
&end_event.playlist_insert_num_entries); &end_event.playlist_insert_num_entries);
mp_notify_property(mpctx, "playlist"); mp_notify_property(mpctx, "playlist");
@ -1766,6 +1793,7 @@ static void play_current_file(struct MPContext *mpctx)
mpctx->playback_initialized = true; mpctx->playback_initialized = true;
mp_notify(mpctx, MPV_EVENT_FILE_LOADED, NULL); mp_notify(mpctx, MPV_EVENT_FILE_LOADED, NULL);
update_screensaver_state(mpctx); update_screensaver_state(mpctx);
clear_playlist_paths(mpctx);
if (watch_later) if (watch_later)
mp_delete_watch_later_conf(mpctx, mpctx->filename); mp_delete_watch_later_conf(mpctx, mpctx->filename);