Баг записи строк в функциях SA-MP
Вместо предисловия
Начиналось всё обыденно, я изучал принцип работы функции SHA256_PassHash, как вдруг случайно заметил, что если размер массива меньше длины строки с хешем, строка будет записана не совсем правильно.
Открыть/закрыть
// В массив str1 будет записываться строка с хеш-суммой.
new str1[4];
// Все элементы массива str2 с самого начала равны не нулю, а 0xFFFFFFFF.
new str2[4] = { 0xFFFFFFFF, ... };
main()
{
// Вся строка не вместится в массив str1, она должна быть урезана
// до 3 символов (4-м будет символ конца строки '\0'),
// а все элементы массива str2 должны остаться равны 0xFFFFFFFF.
for (new i = 0; i < sizeof(str1); ++i)
printf("str1[%02d]: %08x", i
, str1
[i
]); for (new i = 0; i < sizeof(str2); ++i)
printf("str2[%02d]: %08x", i
, str2
[i
]); }
Вывод:
Код:
str1: "E3B0"
str1[00]: 00000045
str1[01]: 00000033
str1[02]: 00000042
str1[03]: 00000030
str2[00]: 00000000
str2[01]: FFFFFFFF
str2[02]: FFFFFFFF
str2[03]: FFFFFFFF
Как видно из вывода, строка была "урезана", чтобы вместиться в массив из 4 ячеек, вот только вместо 3 символов функция записала 4, а завершающий '\0' записался в 0-ю ячейку массива str2, располагающегося в памяти сразу после str1. Иными словами, это выход за пределы массива. Соль в том, что обычно такой баг очень трудно отловить: попробуй догадайся, из-за чего обнуляется какая-то рандомная переменная - точно не подумаешь на одну из функций SA-MP.
Нахождение и разбор
Казалось бы, ничего особенного, обычный мелкий баг - в SA-MP десятки, если не сотни таких багов, которые никогда не будут исправлены.
Но любопытство взяло верх: решил проверить, получится ли добиться чего-то похожего от GetWeaponName() - и там оказался аналогичный эффект при урезании строки.
Чтобы разобраться в этом баге, нужно иметь доступ к исходным кодам SA-MP - для этого как раз может подойти Open-SAMP, основанный на утекших исходниках (предположительно от версии 0.3a).
Рассмотрим реализацию GetWeaponName():
cell AMX_NATIVE_CALL funcGetWeaponName ( AMX* a_AmxInterface, cell* a_Params )
{
if(bScriptDebug) logprintf ( "[Call]-> funcGetWeaponName()" );
CHECK_PARAMS( 3 );
if(a_Params[1] > WEAPON_COLLISION) return 0;
return set_amxstring(a_AmxInterface, a_Params[2], __NetGame->getWeaponName(a_Params[1]), a_Params[3]);
}
Те, кто знакомы с написанием плагинов для SA-MP, могут знать, что для записи сишных строк в массив Pawn нужно использовать функцию amx_SetSrting(). Однако в коде выше можно заметить, что строка с названием оружия записывается в массив с помощью "самопальной" функции set_amxstring().
Рассмотрим эту функцию:
int set_amxstring(AMX* amx, cell amx_addr, const char* source, int max)
{
cell* dest = (cell *)(amx->base + (int)(((AMX_HEADER *)amx->base)->dat + amx_addr));
cell* start = dest;
while (max--&&*source)
*dest++=(cell)*source++;
*dest = 0;
return dest-start;
}
Присмотритесь внимательно к циклу while, в нём и находится причина бага: параметр max изначально равен размеру массива, но запись символов строки происходит не max-1, а max раз - отсюда запись одного лишнего символа там, где по идее должен быть завершающий '\0', и запись '\0' в следующую ячейку, уже за пределами массива. Если в условии цикла заменить max-- на --max, баг будет исправлен.
Подверженные функции
Как удалось выяснить, данному багу подвержены все нативные функции SA-MP, которые записывают строки (т.е. все, что используют set_amxstring()).
Открыть/закрыть
- GetPlayerIp
- SHA256_PassHash
- GetAnimationName
- GetWeaponName
- NetStats_GetIpPort
- GetPlayerName
- gpci
- GetPlayerVersion
- GetServerVarAsString/GetConsoleVarAsString
- GetPVarString
- GetPVarNameAtIndex
- GetSVarString
- GetSVarNameAtIndex
- db_field_name
- db_get_field
- db_get_field_assoc
Исключение: format() - в этой функции выхода за пределы массива не происходит. Очевидно, баг обнаружили ранее (всё-таки функция часто используется) и сообщили Kalcor'у, но тот не стал вникать в детали, искать и исправлять первопричину бага в set_amxstring() (иначе баг был бы исправлен не только в format(), но и во всех остальных функциях), а просто в коде format() вместо самопальной set_amxstring() использовал стандартную amx_SetString() - в этом можно убедиться, дизассемблировав сервер версии 0.3a (именно в этой версии баг был исправен). А то, что первопричина бага может быть глубже и затрагивает другие функции - да кого это волнует? Главное же, что баг нашли только в format(), и теперь он там исправлен. В общем, кое-что да говорит о подходе к разработке SA-MP (впрочем, ничего нового).
Предпринятые меры:
- Информация о баге и способе исправления передана 0x452, проблема устранена в предстоящем релизе RW-MP.
- Пытаться сообщить о баге Kalcor'у? Не смешите. Этот баг не вызывает краш сервера и не мешает считать деньги с Hosted'а, а потому не достоен внимания Солнцеликого.
Автор: Daniel_Cortez
Копирование данной статьи на других ресурсах без разрешения автора запрещено!