Topic: Баг в svpflow2 4.6.0: use-after-free
Добрый день.
Коротко: у меня часто при закрытии крашился MPC-HC (использую SVP+RIFE с кастомными VS фильтрами), я с помощью ИИ нашел баг в svpflow2 (и в avsfilter тоже).
Поскольку я не разработчик, отчет ниже попросил написать ИИ (Opus 5):
========================================================================
svpflow2 4.6.0.274: use-after-free при уничтожении фильтра (SmoothFps, SmoothFps_NVOF, SmoothFps_RIFE)
В svpflow2_vs.dll при уничтожении графа VapourSynth плагин освобождает структуру своего инстанса фильтра, а затем — через 0x14 байт кода, в той же функции — вызывает _Mtx_unlock на мьютексе, который лежал внутри этой уже освобождённой структуры. Выглядит как lock_guard, область видимости которого переживает free() владеющего объекта.
Без page heap страница остаётся отображённой, декремент счётчика мьютекса тихо портит память, которую куча уже отдала другому владельцу, и авария всплывает позже и в чужом модуле.
Код освобождения общий для всех трёх точек входа, поэтому задеты и профили без RIFE.
1. Окружение
SVP 4 Manager 4.7.0.302
svpflow2_vs.dll 4.6.0.274 (PE 2024-09-21) — виновник
svpflow1_vs.dll 4.5.0.205
VapourSynth R73 (родная из состава SVP) и R77 — одинаково
MPC-HC 2.7.4, рендеры madVR и MPC Video Renderer — одинаково
AVSF: сборка SVP 1.4.8 # svp и апстрим CrendKing 1.4.9 — одинаково
Windows 10 Pro 19045, RTX 5070 Ti, драйвер 32.0.16.1074
2. Симптом
Падение при уничтожении графа: закрытие файла, открытие следующего, иногда пересборка скрипта после перемотки.
Без page heap код исключения — 0xc0000374 (heap corruption), тип отказа block_not_busy. Модуль-«виновник» каждый раз разный: akarin, zsmooth, madVR, nvinfer. Это те, кто дёрнул кучу после порчи.
Иногда падение выглядит как зависание: процесс жив, окно не отвечает, стек стоит в ntdll!WerpWaitForCrashReporting — то же падение, ожидающее отчёт об ошибке.
3. Диагноз
Full page heap (gflags /p /enable mpc-hc64.exe /full /size 256 1024), останов отладчика на первом access violation. Ловится на каждом закрытии файла.
ExceptionAddress: MSVCP140!_Mtx_unlock+0x4
ExceptionCode: c0000005 (Access violation)
Parameter[0]: 1 <- ЗАПИСЬ
Parameter[1]: 0000024bb1d36ee4
MSVCP140!_Mtx_unlock+0x4:
00007ff8`9b747df4 83694c01 sub dword ptr [rcx+4Ch],1 ds:...b1d36ee4=????????
rcx = 0000024bb1d36e98 <- мьютекс
rsi = 0000024bb1d36e50 <- начало структуры инстанса
rcx - rsi = 0x48, запись по rcx+0x4C = struct+0x94Стек записи:
MSVCP140!_Mtx_unlock+0x4
svpflow2_vs!VapourSynthPluginInit+0x6d71 (RVA 0x221d1) <- ЗАПИСЬ
svpflow2_vs+0xf297
svpflow2_vs+0x1a540
libvapoursynth!VSCore::destroyFilterInstance+0x116 (inline)
libvapoursynth!VSNode::~VSNode+0x393
libvapoursynth!freeNode+0x17
vapoursynth.pyd -> python312!PyDict_Clear
VSScript freeScript
vapoursynth_filter_64!FrameServerBase::~FrameServerBase
quartz!CFilterGraph::RemoveFilterInternal -> ~CFilterGraph
mpc-hc64!ext.heap -p -a по адресу мьютекса отвечает «in free-ed allocation» и отдаёт стек освобождения:
ucrtbase!_free_base
svpflow2_vs!VapourSynthPluginInit+0x6d5d (RVA 0x221bd) <- ОСВОБОЖДЕНИЕ
svpflow2_vs+0xf297
svpflow2_vs+0x1a540
libvapoursynth!VSNode::~VSNode+0x393
libvapoursynth!freeNode+0x17
... тот же стек teardown ...Освобождение — +0x6d5d (RVA 0x221BD), запись — +0x6d71 (RVA 0x221D1), разница 0x14 байт в одной функции. Блок 424 байта (0x1A8), мьютекс по смещению +0x48, запись по +0x94.
4. Воспроизведение без плеера
Плеер, AVSF и DirectShow не нужны: уничтожение узла в обычном скрипте VapourSynth идёт через тот же VSCore::destroyFilterInstance.
Page heap включается по имени образа, поэтому удобна переименованная копия питона из состава SVP:
copy "C:\Program Files (x86)\SVP 4\mpv64\python.exe" svpprobe.exe
gflags /p /enable svpprobe.exe /full /size 32 8192
set PYTHONHOME=C:\Program Files (x86)\SVP 4\mpv64
set PATH=%PYTHONHOME%;%PATH%
cdb -loga run.log -cf cmd.txt svpprobe.exe repro.pyКомандный файл cmd.txt (надёжнее, чем вложенные кавычки в -c):
sxe -c ".exr -1;r;u @rip L4;kb 40;!ext.heap -p -a @rcx;q" av
g
qrepro.py — SVPManager должен быть запущен, плагин его проверяет:
import gc, vapoursynth as vs
core = vs.core
PLUG = r"C:\Program Files (x86)\SVP 4\plugins64"
core.std.LoadPlugin(PLUG + r"\LSMASHSource.dll")
core.std.LoadPlugin(PLUG + r"\svpflow1_vs.dll")
core.std.LoadPlugin(PLUG + r"\svpflow2_vs.dll") # <- сборка под тестом
src = core.lsmas.LWLibavSource(r"<любой файл>")
clip = src.resize.Point(format=vs.YUV420P8)
fps = clip.fps_num / clip.fps_den
sup = core.svp1.Super(clip, "{pel:1,scale:{up:0},gpu:1,full:false}")
vec = core.svp1.Analyse(sup["clip"], sup["data"], clip,
"{vectors:2,block:{w:32,overlap:0},main:{search:{coarse:{distance:-8,bad:{range:0},width:530},distance:0}}}")
out = core.svp2.SmoothFps(clip, sup["clip"], sup["data"], vec["clip"], vec["data"],
"{gpuid:11,gpu_qn:2,rate:{num:4,den:1},algo:13,mask:{area:100},scene:{blend:true}}",
src=clip, fps=fps)
for n in range(30):
out.get_frame(n)
del out, vec, sup, clip # <- здесь приходит access violation
gc.collect()Здесь нет ни RIFE, ни NVOF — только обычный SmoothFps. Авария та же: MSVCP140!_Mtx_unlock+0x4, освобождение +0x6d5d, запись +0x6d71.
Сборка плагина задаётся единственным LoadPlugin, в plugins64\ подменять ничего не нужно. Стоковую VapourSynth тоже можно взять без отката установки: положить vapoursynth.pyd и VapourSynth.dll нужной сборки в отдельный каталог и сделать sys.path.insert(0, <каталог>) до первого import vapoursynth, проверив результат по vs.core.core_version.
5. Матрица сборок
Тот же скрипт, тот же файл, page heap /size 32 8192, 3 цикла сборки-разрушения графа:
сборка SmoothFps SmoothFps_NVOF SmoothFps_RIFE
-----------------------------------------------------------------------------
4.6.0.274 (PE 2024-09-21) UAF UAF UAF
4.6.0.263 (PE 2023-10-04) UAF UAF UAF
4.3.0.168 (2019-07-28) чисто чисто нет функции
4.0.0.128 (PE 2016-02-22) чисто нет функции нет функцииВо всех падениях одно и то же место — +0x6d5d / +0x6d71. В 4.6.0.263 смещения от экспорта совпадают точно, хотя вызывающий код лежит по другим RVA (+0xed37 / +0x19030 против +0xf297 / +0x1a540).
Значит, дефект один (общий код освобождения инстанса) и появился между 4.3.0.168 и 4.6.0.263.
Перепроверка 2026-08-29:
прогон svpflow2 VapourSynth результат
------------------------------------------------------------------
1 4.6.0.274 (установленная) R77 UAF
2 4.3.0.168 R77 чисто, 2 раунда
3 4.6.0.274 (установленная) R73 (сток SVP) UAFПрогон 2 — контроль метода: тот же скрипт, тот же page heap, тот же файл, старая сборка переживает оба цикла и выходит с кодом 0.
6. Что исключено
Рендер: madVR и MPC Video Renderer — одинаково.
Плеер и DirectShow: голый скрипт VapourSynth падает так же.
Версия VapourSynth: R73 и R77 — одинаково.
Сборка AVSF: 1.4.8 # svp и 1.4.9 — одинаково. AVSF лишь инициирует teardown, пара free/unlock целиком внутри svpflow2 и вызывается штатным freeFunc плагина.
Мои фильтры: при всех отключённых падение остаётся.
Состояние системы: после перезагрузки повторяется.
Модуль, сообщающий о порче кучи, её причиной не является.
Дополнительно: если убрать SmoothFps_RIFE из графа (выход RIFE отдаётся дальше напрямую, частота через AssumeFPS), падение исчезает — два прогона под page heap. Как решение не годится (теряется блендинг на сменах сцен и штатная раскладка кадров), но подтверждает, что дело в инстансе фильтра, а не в загрузке DLL.
Готов прогнать любые проверочные сборки svpflow, приложить полный дамп или скрипт.