1 (edited by nemoW 07-09-2026 19:03:22)

Topic: Баг в svpflow2 4.6.0: use-after-free [fixed]

Добрый день.

Коротко: у меня часто при закрытии крашился 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, приложить полный дамп или скрипт.

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

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

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

Спасибо за быстрое исправление, но к сожалению с svpflow2 4.7.0 у меня плееры (MPC-HC и mpv) выбивают ошибку при проигрывании HDR

Ниже репорт от ИИ, если нужны подробности

========================================================================

svpflow2 4.7.0.320: запись по нулевому указателю, mpv падает через 15-60 секунд воспроизведения

После обновления компонентов core.flow64 и core.flow64vs mpv стабильно падает через 15-60 секунд после начала воспроизведения HDR. Откат одного только svpflow2 на 4.6.0.274 полностью убирает проблему.

Авария — access violation при записи по адресу 0, внутри svpflow2_vs.dll, на фиксированном смещении +0x6750.

Это не тот use-after-free при разрушении фильтра, о котором шла речь в прошлый раз: тот в 4.7.0.320 исправлен, проверил отдельно — раздел 8.

1. Окружение

SVP 4 Pro 4.7.0.302 (менеджер не обновлялся)
svpflow2_vs.dll 4.7.0.320 — виновник
svpflow2_vs.dll 4.6.0.274 — на ней всё работает
svpflow1_vs.dll 4.5.0.205
VapourSynth R73, штатная из поставки SVP — падает
VapourSynth R77 — падает одинаково
AVSF 1.4.8 # svp (ваша сборка) и 1.4.9 — одинаково
mpv v0.41.0-424-gbefe1e73a
Windows 10 Pro 19045, RTX 5070 Ti, драйвер 32.0.16.1074

Обновление принесло только эти два компонента. Важная деталь: svpflow1_vs.dll в обеих поставках побайтово одинаков (SHA-256 AC79CD31801B0462BC91CCEE101FF2AF8B6E712211D90FFE46F046DABE4A1EF7) — installer его перезаписал тем же содержимым. То есть изменился ровно один файл.

svpflow2_vs.dll  4.7.0.320  SHA-256 D05B7AC3CAEF6A9C990F339124B709821BFC6922DA9BB9A83B3CD5F28F237D3B
svpflow2_vs.dll  4.6.0.274  SHA-256 5F84A299EB447C09ABBAF483399B1F1AAAA9E49B2BEB8C6E6C0BEE8AC2D62643

2. Симптом

Картинка замирает, звук начинает икать, появляется окно «mpv has stopped working». Не при закрытии файла и не при пересборке скрипта — посреди обычного воспроизведения, без перемотки и без изменения настроек.

Четыре падения, Application Error:

время      аптайм mpv   код          смещение   стек
------------------------------------------------------------------------
22:50:14   ~44 c        0xc0000005   0x6750     R77 + наш AVSF
22:51:16   ~48 c        0xc0000005   0x6750     R77 + наш AVSF
22:58:07   ~18 c        0xc0000005   0x6750     R77 + наш AVSF
23:24:46   ~30 c        0xc0000005   0x6750     R73 сток + AVSF 1.4.8 # svp

Во всех четырёх — svpflow2_vs.dll 4.7.0.320 и одно и то же смещение. В логах SVP при этом ни одной строки уровня E или W.

3. Разбор дампов

Три дампа WER, разные потоки, всё остальное совпадает до бита:

ExceptionCode: c0000005 (Access violation)
  Parameter[0]: 1                 <- ЗАПИСЬ
  Parameter[1]: 0                 <- по адресу NULL

rip = svpflow2_vs+0x6750          <- здесь падает

обратный стек (дамп на штатной R73):
  svpflow2_vs+0x6750
  svpflow2_vs+0x19950             <- вызывающий
  VapourSynth.dll+0x1a1cff        <- ядро VapourSynth запрашивает кадр

На R77 стек тот же, только ядро называется libvapoursynth+0x97032. Смещения внутри svpflow2 в обоих случаях совпадают: +0x6750 и +0x19950.

Это запись по нулевому указателю, а не повреждение кучи и не use-after-free: адрес отказа ровно 0, смещение стабильное от запуска к запуску. Похоже на указатель, который в 4.6.0.274 либо всегда был валиден, либо проверялся, а в 4.7.0.320 в каком-то состоянии остаётся нулевым.

4. Конфигурация графа

Профиль RIFE 2160p. Источник 3840x2160 HEVC, YUV 4:2:0, 10 бит, BT.2020 PQ, 23.976 fps, выход 47.952.

super   = core.svp1.Super(input_m8, "{pel:1,scale:{up:0},gpu:1,full:false}")
vectors = core.svp1.Analyse(super["clip"], super["data"], input_m8,
          "{vectors:2,block:{w:32,overlap:0},main:{search:{coarse:{distance:-8,bad:{range:0},width:530},distance:0}}}")
smooth  = core.svp2.SmoothFps_RIFE(input_m, smoothfps_params, rife_out=smooth,
          vec_src=vec_src, vdata=vdata, src=input_um, fps=src_fps)

smoothfps_params = "{gpuid:11,gpu_qn:2,rate:{num:2,den:1},algo:13,mask:{area:100},scene:{blend:true},hdr:{dovi:false}}"

5. Похоже, задета HDR / 10-битная ветка

На той же сборке 4.7.0.320 и том же профиле RIFE 2160p обычный SDR-ролик 4K (3840x2160, AVC, 8 бит, 29.97) отыграл 97 секунд без единого сбоя и был остановлен штатно. HDR-источник на этой же сборке падал четыре раза из четырёх за 18-48 секунд.

В сгенерированном скрипте между этими двумя прогонами отличаются ровно две вещи, обе — HDR-ветка:

                    HDR (падает)                        SDR (чисто)
smoothfps_params    ...,scene:{blend:true},hdr:{dovi:false}  ...,scene:{blend:true}
input_m8            input_m.resize.Point(YUV420P8)           input_m8 = input_m
input_m / src       остаются 10-битными                      8 бит

То есть в HDR-случае в опции добавляется ключ hdr:, а motion search идёт по отдельному 8-битному клипу, тогда как основной клип и src десятибитные.

Оговорки: SDR-прогон один против четырёх HDR-падений, и файл был другой (AVC 8 бит 29.97 против HEVC 10 бит BT.2020 PQ 23.976). Кодек и частота кадров до плагина не доходят, но одного и того же материала в двух битностях я не проверял.

6. Чего добиться не удалось

Скриптом на голой VapourSynth авария не воспроизводится. Тот же граф, тот же кусок того же файла (10 бит, crop до кратного 32, те же параметры вплоть до hdr:{dovi:false}), 300 кадров синхронным get_frame — чисто:

ветка                             склейки сцен   результат
---------------------------------------------------------------
SmoothFps_RIFE                    нет            300 кадров, чисто
SmoothFps_RIFE                    каждые 7       300 кадров, чисто
SmoothFps_RIFE + hdr:{dovi:false} каждые 7       300 кадров, чисто
SmoothFps + hdr:{dovi:false}      каждые 7       300 кадров, чисто

Похоже, триггер живёт в realtime-условиях плеера — асинхронные многопоточные запросы кадров, дропы, — а не в самой последовательности фильтров. Поэтому какая из точек входа виновата, я утверждать не могу: живьём проигрывался только профиль RIFE 2160p, классический SmoothFps на 4.7.0.320 в плеере не проверялся.

7. Что исключено

VapourSynth. Специально проверил на штатной R73 из вашей поставки: R77 переименована так, что загрузиться не могла, VSPipe --version отвечает Core R73, в стеке дампа стоит VapourSynth.dll, а не libvapoursynth.dll. Падение то же и на том же смещении.

AVSF. Одновременно с R73 возвращена ваша сборка 1.4.8 # svp вместо нашей 1.4.9. Разницы нет. (К mpv это и так не относится, AVSF работает только в DirectShow-плеерах.)

svpflow1. В обеих поставках побайтово одинаков, см. раздел 1.

Откат. Подменены только три файла svpflow2 на 4.6.0.274, svpflow1 не трогался — воспроизведение стабильно.

Модуль в отчёте об ошибке один и тот же во всех четырёх падениях, это не «пострадавший» сосед: rip указывает внутрь svpflow2, а не на чужой код.

8. Заодно: use-after-free при teardown в 4.7.0.320 исправлен

Тот дефект (free структуры инстанса и следом _Mtx_unlock по мьютексу внутри неё, +0x6d5d / +0x6d71) в 4.7.0.320 больше не воспроизводится.

Проверял тем же способом, что и тогда — full page heap на переименованной копии питона, cdb, голый скрипт VapourSynth с циклами сборки и разрушения графа, ветка SmoothFps:

сборка       циклов   результат
------------------------------------------------------------------
4.6.0.274    3        падает: MSVCP140!_Mtx_unlock+0x4,
                      "in free-ed allocation", RVA 0x221D1
4.7.0.320    5        все циклы прошли, аварии нет

Контрольный прогон на 4.6.0.274 сделан в той же сессии, на той же машине и том же файле — то есть метод рабочий, и чистый результат на 4.7.0.320 не артефакт окружения. Какая именно DLL загружена, видно и в выводе скрипта, и в ModLoad отладчика.

Пишу об этом здесь, потому что обе новости про одну и ту же сборку: teardown починен, но появилась запись по NULL из разделов выше.

Готов приложить дампы (два, по 190 МБ), прогнать проверочную сборку или собрать дамп с page heap, если это поможет.

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

вторая попытка

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

Пока всё ок, спасибо