EreTIk's Box » Cтатьи, исходники » Функция PathAppend и реализация ATL::CPathT<...>::Append в Microsoft Visual Studio 9.0


На днях мне на глаза попался странный дамп, в котором произошло падение в функции 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):


You should set the size of this buffer to MAX_PATH to ensure that it is large enough to hold the returned string

Современная online версия более категорична ( PathAppend):


You must set the size of this buffer to MAX_PATH to ensure that it is large enough to hold the returned string

Исходный 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).


ΞρεΤΙκ