Mon Jan 29 21:54:22 2018 UTC ()
mpv: Add patch-player_lua_ytdl__hook.lua to address CVE-2018-6360

Sync player/lua/ytdl_hook.lua with upstream commit
2a0f9fc1588d8bda51a6340197c54285f3f62755 minus hunks that need `on_load_fail'
hook introduced after mpv-0.27.

Bump PKGREVISION.


(leot)
diff -r1.62 -r1.63 pkgsrc/multimedia/mpv/Makefile
diff -r1.37 -r1.38 pkgsrc/multimedia/mpv/distinfo
diff -r0 -r1.1 pkgsrc/multimedia/mpv/patches/patch-player_lua_ytdl__hook.lua

cvs diff -r1.62 -r1.63 pkgsrc/multimedia/mpv/Makefile (expand / switch to unified diff)

--- pkgsrc/multimedia/mpv/Makefile 2018/01/28 20:10:57 1.62
+++ pkgsrc/multimedia/mpv/Makefile 2018/01/29 21:54:22 1.63
@@ -1,17 +1,17 @@ @@ -1,17 +1,17 @@
1# $NetBSD: Makefile,v 1.62 2018/01/28 20:10:57 wiz Exp $ 1# $NetBSD: Makefile,v 1.63 2018/01/29 21:54:22 leot Exp $
2 2
3DISTNAME= mpv-0.27.0 3DISTNAME= mpv-0.27.0
4PKGREVISION= 1 4PKGREVISION= 2
5CATEGORIES= multimedia 5CATEGORIES= multimedia
6MASTER_SITES= ${MASTER_SITE_GITHUB:=mpv-player/} 6MASTER_SITES= ${MASTER_SITE_GITHUB:=mpv-player/}
7GITHUB_TAG= v${PKGVERSION_NOREV} 7GITHUB_TAG= v${PKGVERSION_NOREV}
8 8
9MAINTAINER= leot@NetBSD.org 9MAINTAINER= leot@NetBSD.org
10HOMEPAGE= https://mpv.io/ 10HOMEPAGE= https://mpv.io/
11COMMENT= Video player based on MPlayer and mplayer2 11COMMENT= Video player based on MPlayer and mplayer2
12LICENSE= gnu-gpl-v2 AND gnu-lgpl-v2.1 12LICENSE= gnu-gpl-v2 AND gnu-lgpl-v2.1
13 13
14# needs sem_timedwait(3) 14# needs sem_timedwait(3)
15NOT_FOR_PLATFORM= NetBSD-[1-6]*-* 15NOT_FOR_PLATFORM= NetBSD-[1-6]*-*
16 16
17NOT_PAX_MPROTECT_SAFE+= bin/mpv 17NOT_PAX_MPROTECT_SAFE+= bin/mpv

cvs diff -r1.37 -r1.38 pkgsrc/multimedia/mpv/distinfo (expand / switch to unified diff)

--- pkgsrc/multimedia/mpv/distinfo 2017/09/13 10:35:58 1.37
+++ pkgsrc/multimedia/mpv/distinfo 2018/01/29 21:54:22 1.38
@@ -1,8 +1,9 @@ @@ -1,8 +1,9 @@
1$NetBSD: distinfo,v 1.37 2017/09/13 10:35:58 leot Exp $ 1$NetBSD: distinfo,v 1.38 2018/01/29 21:54:22 leot Exp $
2 2
3SHA1 (mpv-0.27.0.tar.gz) = d4bddb88cf7a112a295a130a091181acbe25605b 3SHA1 (mpv-0.27.0.tar.gz) = d4bddb88cf7a112a295a130a091181acbe25605b
4RMD160 (mpv-0.27.0.tar.gz) = d2edbdfdc6fdf3a0c210536ac3966e99113c83ad 4RMD160 (mpv-0.27.0.tar.gz) = d2edbdfdc6fdf3a0c210536ac3966e99113c83ad
5SHA512 (mpv-0.27.0.tar.gz) = 22738f907d84d362095773972f685e3b03ab4c8172a22ddede290fc221a83ab9135b96f8b18191dabe842b2963f68983929cf065097287fc1a054a7d5f1d0ae4 5SHA512 (mpv-0.27.0.tar.gz) = 22738f907d84d362095773972f685e3b03ab4c8172a22ddede290fc221a83ab9135b96f8b18191dabe842b2963f68983929cf065097287fc1a054a7d5f1d0ae4
6Size (mpv-0.27.0.tar.gz) = 2956816 bytes 6Size (mpv-0.27.0.tar.gz) = 2956816 bytes
7SHA1 (patch-audio_out_ao__oss.c) = d7f3a75ab43efe396ce536fb54e6207a7ded3510 7SHA1 (patch-audio_out_ao__oss.c) = d7f3a75ab43efe396ce536fb54e6207a7ded3510
 8SHA1 (patch-player_lua_ytdl__hook.lua) = 450a6f2640c76b5db0c0cd585674ccd689712f78
8SHA1 (patch-player_main.c) = 842432e448526a9d170e7efd2b01276e36072e16 9SHA1 (patch-player_main.c) = 842432e448526a9d170e7efd2b01276e36072e16

File Added: pkgsrc/multimedia/mpv/patches/Attic/patch-player_lua_ytdl__hook.lua
$NetBSD: patch-player_lua_ytdl__hook.lua,v 1.1 2018/01/29 21:54:22 leot Exp $

Sync ytdl_hook.lua script with commit 2a0f9fc1588d8bda51a6340197c54285f3f62755
in order to fix CVE-2018-6360.

Please note that this completely omits hunks that need `on_load_fail' hook that
was implemented post-0.27.

--- player/lua/ytdl_hook.lua.orig	2018-01-28 10:58:02.151884824 +0000
+++ player/lua/ytdl_hook.lua
@@ -15,6 +16,18 @@ local ytdl = {
 
 local chapter_list = {}
 
+function Set (t)
+    local set = {}
+    for _, v in pairs(t) do set[v] = true end
+    return set
+end
+
+local safe_protos = Set {
+    "http", "https", "ftp", "ftps",
+    "rtmp", "rtmps", "rtmpe", "rtmpt", "rtmpts", "rtmpte",
+    "data"
+}
+
 local function exec(args)
     local ret = utils.subprocess({args = args})
     return ret.status, ret.stdout, ret
@@ -71,6 +84,15 @@ local function edl_escape(url)
     return "%" .. string.len(url) .. "%" .. url
 end
 
+local function url_is_safe(url)
+    local proto = type(url) == "string" and url:match("^(.+)://") or nil
+    local safe = proto and safe_protos[proto]
+    if not safe then
+        msg.error(("Ignoring potentially unsafe url: '%s'"):format(url))
+    end
+    return safe
+end
+
 local function time_to_secs(time_string)
     local ret
 
@@ -182,6 +204,9 @@ local function edl_track_joined(fragment
 
     for i = offset, #fragments do
         local fragment = fragments[i]
+        if not url_is_safe(join_url(base, fragment)) then
+            return nil
+        end
         table.insert(parts, edl_escape(join_url(base, fragment)))
         if fragment.duration then
             parts[#parts] =
@@ -191,16 +216,63 @@ local function edl_track_joined(fragment
     return edl .. table.concat(parts, ";") .. ";"
 end
 
+local function has_native_dash_demuxer()
+    local demuxers = mp.get_property_native("demuxer-lavf-list")
+    for _,v in ipairs(demuxers) do
+        if v == "dash" then
+            return true
+        end
+    end
+    return false
+end
+
+local function valid_manifest(json)
+    local reqfmt = json["requested_formats"] and json["requested_formats"][1] or {}
+    if not reqfmt["manifest_url"] and not json["manifest_url"] then
+        return false
+    end
+    local proto = reqfmt["protocol"] or json["protocol"] or ""
+    return (has_native_dash_demuxer() and proto == "http_dash_segments") or
+        proto:find("^m3u8")
+end
+
 local function add_single_video(json)
     local streamurl = ""
+    local max_bitrate = 0
+    local reqfmts = json["requested_formats"]
+
+    -- prefer manifest_url if present
+    if valid_manifest(json) then
+        local mpd_url = reqfmts and reqfmts[1]["manifest_url"] or
+            json["manifest_url"]
+        if not mpd_url then
+            msg.error("No manifest URL found in JSON data.")
+            return
+        elseif not url_is_safe(mpd_url) then
+            return
+        end
+
+        streamurl = mpd_url
+
+        if reqfmts then
+            for _, track in pairs(reqfmts) do
+                max_bitrate = track.tbr > max_bitrate and
+                    track.tbr or max_bitrate
+            end
+        elseif json.tbr then
+            max_bitrate = json.tbr > max_bitrate and json.tbr or max_bitrate
+        end
 
     -- DASH/split tracks
-    if not (json["requested_formats"] == nil) then
-        for _, track in pairs(json.requested_formats) do
+    elseif reqfmts then
+        for _, track in pairs(reqfmts) do
             local edl_track = nil
             edl_track = edl_track_joined(track.fragments,
                 track.protocol, json.is_live,
                 track.fragment_base_url)
+            if not edl_track and not url_is_safe(track.url) then
+                return
+            end
             if track.acodec and track.acodec ~= "none" then
                 -- audio track
                 mp.commandv("audio-add",
@@ -217,6 +289,9 @@ local function add_single_video(json)
         edl_track = edl_track_joined(json.fragments, json.protocol,
             json.is_live, json.fragment_base_url)
 
+        if not edl_track and not url_is_safe(json.url) then
+            return
+        end
         -- normal video or single track
         streamurl = edl_track or json.url
         set_http_headers(json.http_headers)
@@ -231,6 +306,13 @@ local function add_single_video(json)
 
     mp.set_property("file-local-options/force-media-title", json.title)
 
+    -- set hls-bitrate for dash track selection
+    if max_bitrate > 0 and
+        not option_was_set("hls-bitrate") and
+        not option_was_set_locally("hls-bitrate") then
+        mp.set_property_native('file-local-options/hls-bitrate', max_bitrate*1000)
+    end
+
     -- add subtitles
     if not (json.requested_subtitles == nil) then
         for lang, sub_info in pairs(json.requested_subtitles) do
@@ -309,7 +391,8 @@ mp.add_hook("on_load", 10, function ()
 
         -- check for youtube-dl in mpv's config dir
         if not (ytdl.searched) then
-            local ytdl_mcd = mp.find_config_file("youtube-dl")
+            local exesuf = (package.config:sub(1,1) == '\\') and '.exe' or ''
+            local ytdl_mcd = mp.find_config_file("youtube-dl" .. exesuf)
             if not (ytdl_mcd == nil) then
                 msg.verbose("found youtube-dl at: " .. ytdl_mcd)
                 ytdl.path = ytdl_mcd
@@ -365,9 +448,15 @@ mp.add_hook("on_load", 10, function ()
         local es, json, result = exec(command)
 
         if (es < 0) or (json == nil) or (json == "") then
-            if not result.killed_by_us then
-                msg.warn("youtube-dl failed, trying to play URL directly ...")
+            local err = "youtube-dl failed: "
+            if result.error and result.error == "init" then
+                err = err .. "not found or not enough permissions"
+            elseif not result.killed_by_us then
+                err = err .. "unexpected error ocurred"
+            else
+                err = string.format("%s returned '%d'", err, es)
             end
+            msg.error(err)
             return
         end
 
@@ -396,18 +485,26 @@ mp.add_hook("on_load", 10, function ()
                 return
             end
 
+            local self_redirecting_url =
+                json.entries[1]["_type"] ~= "url_transparent" and
+                json.entries[1]["webpage_url"] and
+                json.entries[1]["webpage_url"] == json["webpage_url"]
+
 
             -- some funky guessing to detect multi-arc videos
-            if (not (json.entries[1]["_type"] == "url_transparent")) and
-                (not (json.entries[1]["webpage_url"] == nil)
-                and (json.entries[1]["webpage_url"] == json["webpage_url"]))
-                and not (json.entries[1].url == nil) then
+            if self_redirecting_url and #json.entries > 1
+                and json.entries[1].protocol == "m3u8_native"
+                and json.entries[1].url then
                 msg.verbose("multi-arc video detected, building EDL")
 
                 local playlist = edl_track_joined(json.entries)
 
                 msg.debug("EDL: " .. playlist)
 
+                if not playlist then
+                    return
+                end
+
                 -- can't change the http headers for each entry, so use the 1st
                 if json.entries[1] then
                     set_http_headers(json.entries[1].http_headers)
@@ -446,39 +543,43 @@ mp.add_hook("on_load", 10, function ()
                     end
                 end
 
-            elseif (not (json.entries[1]["_type"] == "url_transparent")) and
-                (not (json.entries[1]["webpage_url"] == nil)
-                and (json.entries[1]["webpage_url"] == json["webpage_url"]))
-                and (#json.entries == 1) then
-
+            elseif self_redirecting_url and #json.entries == 1 then
                 msg.verbose("Playlist with single entry detected.")
                 add_single_video(json.entries[1])
             else
-
-                local playlist = "#EXTM3U\n"
+                local playlist = {"#EXTM3U"}
                 for i, entry in pairs(json.entries) do
                     local site = entry.url
                     local title = entry.title
 
                     if not (title == nil) then
                         title = string.gsub(title, '%s+', ' ')
-                        playlist = playlist .. "#EXTINF:0," .. title .. "\n"
+                        table.insert(playlist, "#EXTINF:0," .. title)
                     end
 
-                    -- some extractors will still return the full info for
-                    -- all clips in the playlist and the URL will point
-                    -- directly to the file in that case, which we don't
-                    -- want so get the webpage URL instead, which is what
-                    -- we want
-                    if not (json.entries[1]["_type"] == "url_transparent")
-                        and not (entry["webpage_url"] == nil) then
+                    --[[ some extractors will still return the full info for
+                         all clips in the playlist and the URL will point
+                         directly to the file in that case, which we don't
+                         want so get the webpage URL instead, which is what
+                         we want, but only if we aren't going to trigger an
+                         infinite loop
+                    --]]
+                    if entry["webpage_url"] and not self_redirecting_url then
                         site = entry["webpage_url"]
                     end
 
-                    playlist = playlist .. "ytdl://" .. site .. "\n"
+                    -- links with only youtube id as returned by --flat-playlist
+                    if not site:find("://") then
+                        table.insert(playlist, "ytdl://" .. site)
+                    elseif url_is_safe(site) then
+                        table.insert(playlist, site)
+                    end
+
                 end
 
-                mp.set_property("stream-open-filename", "memory://" .. playlist)
+                if #playlist > 0 then
+                    mp.set_property("stream-open-filename", "memory://" .. table.concat(playlist, "\n"))
+                end
             end
 
         else -- probably a video