1 (edited by nemoW 29-08-2026 18:23:55)

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
q

repro.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, приложить полный дамп или скрипт.

2

Re: Баг в svpflow2 4.6.0: use-after-free

какая умная скотина big_smile
я даже смог повторить то что она сказала