Ensure media files are passed relative to the media folder (#4041)

We were (partially) doing this for MpvManager, but not for
Windows' SimpleMpvPlayer. By passing a media file starting
with a special scheme, a malicious actor could have caused a file to
be written to the filesystem on Windows.

Thanks once again to Michael Lappas for the report.
This commit is contained in:
Damien Elmes 2025-06-01 13:16:04 +07:00 committed by GitHub
parent 757247d424
commit 96ff27d1fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 14 additions and 8 deletions

View file

@ -76,7 +76,7 @@ class MediaManager(DeprecatedNamesMixin):
return self.col._backend.strip_av_tags(text) return self.col._backend.strip_av_tags(text)
def _extract_filenames(self, text: str) -> list[str]: def _extract_filenames(self, text: str) -> list[str]:
"This only exists do support a legacy function; do not use." "This only exists to support a legacy function; do not use."
out = self.col._backend.extract_av_tags(text=text, question_side=True) out = self.col._backend.extract_av_tags(text=text, question_side=True)
return [ return [
x.filename x.filename

View file

@ -9,10 +9,13 @@ These can be accessed via eg card.question_av_tags()
from __future__ import annotations from __future__ import annotations
import os
import re import re
from dataclasses import dataclass from dataclasses import dataclass
from typing import Union from typing import Union
from anki import hooks
@dataclass @dataclass
class TTSTag: class TTSTag:
@ -38,6 +41,13 @@ class SoundOrVideoTag:
filename: str filename: str
def path(self, media_folder: str) -> str:
"Prepend the media folder to the filename."
# Ensure filename doesn't reference parent folder
filename = os.path.basename(self.filename)
filename = hooks.media_file_filter(filename)
return os.path.join(media_folder, filename)
# note this does not include image tags, which are handled with HTML. # note this does not include image tags, which are handled with HTML.
AVTag = Union[SoundOrVideoTag, TTSTag] AVTag = Union[SoundOrVideoTag, TTSTag]

View file

@ -23,7 +23,6 @@ from markdown import markdown
import aqt import aqt
import aqt.mpv import aqt.mpv
import aqt.qt import aqt.qt
from anki import hooks
from anki.cards import Card from anki.cards import Card
from anki.sound import AV_REF_RE, AVTag, SoundOrVideoTag from anki.sound import AV_REF_RE, AVTag, SoundOrVideoTag
from anki.utils import is_lin, is_mac, is_win, namedtmp from anki.utils import is_lin, is_mac, is_win, namedtmp
@ -327,7 +326,7 @@ class SimpleProcessPlayer(Player): # pylint: disable=abstract-method
def _play(self, tag: AVTag) -> None: def _play(self, tag: AVTag) -> None:
assert isinstance(tag, SoundOrVideoTag) assert isinstance(tag, SoundOrVideoTag)
self._process = subprocess.Popen( self._process = subprocess.Popen(
self.args + ["--", tag.filename], self.args + ["--", tag.path(self._media_folder)],
env=self.env, env=self.env,
cwd=self._media_folder, cwd=self._media_folder,
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
@ -453,8 +452,7 @@ class MpvManager(MPV, SoundOrVideoPlayer):
def play(self, tag: AVTag, on_done: OnDoneCallback) -> None: def play(self, tag: AVTag, on_done: OnDoneCallback) -> None:
assert isinstance(tag, SoundOrVideoTag) assert isinstance(tag, SoundOrVideoTag)
self._on_done = on_done self._on_done = on_done
filename = hooks.media_file_filter(tag.filename) path = tag.path(self.media_folder)
path = os.path.join(self.media_folder, filename)
if self.mpv_version is None or self.mpv_version >= (0, 38, 0): if self.mpv_version is None or self.mpv_version >= (0, 38, 0):
self.command("loadfile", path, "replace", -1, "pause=no") self.command("loadfile", path, "replace", -1, "pause=no")
@ -506,10 +504,8 @@ class SimpleMplayerSlaveModePlayer(SimpleMplayerPlayer):
def _play(self, tag: AVTag) -> None: def _play(self, tag: AVTag) -> None:
assert isinstance(tag, SoundOrVideoTag) assert isinstance(tag, SoundOrVideoTag)
filename = hooks.media_file_filter(tag.filename)
self._process = subprocess.Popen( self._process = subprocess.Popen(
self.args + ["--", filename], self.args + ["--", tag.path(self.media_folder)],
env=self.env, env=self.env,
cwd=self.media_folder, cwd=self.media_folder,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,