На днях мне на глаза попался странный дамп, в котором произошло падение в функции ntdll!memmove, которая была вызвана shlwapi!PathAppendW. Функцию PathAppend вызывал код, который использовал метод ATL::CPathT<...>::Append. Быстрый осмотр конкатенируемых строк показал, что они расположены в доступной памяти и заканчиваются 0-символом.
А вот листинг начала функции shlwapi!PathAppendW меня удивил:
mov rax,rsp mov [rax][018],rbx mov [rax][010],rdx mov [rax][8],rcx push rbp push rsi push rdi sub rsp,030 ;'0' xor ebx,ebx mov ebp,07FFFFFFF mov rsi,rcx cmp rcx,rbx jz .000007FF`71B9605E mov rcx,rbp mov rax,rsi cmp [rax],bx jz .000007FF`71B96046 add rax,2 sub rcx,1 jnz .000007FF`71B96037 mov r8d,000000208 mov rdx,rsi mov rcx,rsi call memmove
В частности, удивил вызовов memmove, с указателем на первый аргумент функции как в качестве памяти-источника, так и в качестве памяти-приемника с фиксированной длинной 0x208 байт. То есть функция потрогала входной буфер на чтение/запись. При этом функция не принимает размер строки в первом аргументе. Вот что про это говорит моя старая локальная копия MSDN'а от 2008-й студии (Microsoft Visual Studio 9.0):
Современная online версия более категорична ( PathAppend):
Исходный exe-файл был скомпилирован с использованием 2008-й студии (Microsoft Visual Studio 9.0), в которой метод ATL::CPathT<...>::Append выглядит так:
BOOL Append( _In_ PCXSTR pszMore ) { PXSTR pszBuffer; BOOL bResult; pszBuffer = m_strPath.GetBuffer( m_strPath.GetLength()+StringType::StringLength( pszMore )+1 ); bResult = ATLPath::Append( pszBuffer, pszMore ); m_strPath.ReleaseBuffer(); return bResult; }
Что идет в полный разрез с документацией. Вместо того, что бы выделить честных MAX_PATH-символов, реализация из Microsoft Visual Studio 9.0 выделяет разумный (по ее мнению) максимум: сумму длин двух строки и одного символа-разделителя.
И такой экономный подход работает в большинстве случаев. Пока... пока собранный файл не запустили на checked-сборке Windows 7. Да, именно в checked-сборке есть приведенная выше проверка буфера на чтение/запись вызовом memmove. И, в какой-то момент, когда буфер строки попал на конец страницы, за которой памяти не было, процесс упал.
У меня нет установленной Microsoft Visual Studio 10.0, но в Microsoft Visual Studio 11.0 такой проблемы уже нет:
BOOL Append(_In_z_ PCXSTR pszMore) { PXSTR pszBuffer = m_strPath.GetBuffer( MAX_PATH ); BOOL bResult = ATLPath::Append( pszBuffer, pszMore ); m_strPath.ReleaseBuffer(); return bResult; }
Кстати, проблема касается не только PathAppend, но и множества других функций, семейства PathXxx (и, соответственно, методах ATL::CPathT).
ΞρεΤΙκ