我正在破解teeworlds的服务器代码,但是我正在尝试一个奇怪的错误。我已经遇到了非常相似的事情,但我发现了一个不干净的解决方法。
正如我在帖子标题中所说,我有一个仅在发布版本中出现的段错误。我使用 printfs 定位了该行,因为 valgrind 和 gdb 都没有显示调试版本的错误。
这是完整的代码,段错误发生在调用此函数之后:
void CServer::SetClientName(int ClientID, const char *pName)
{
if(ClientID < 0 || ClientID >= MAX_CLIENTS || m_aClients[ClientID].m_State < CClient::STATE_READY)
return;
if(!pName)
return;
char * pDispName;
char aCleanName[MAX_NAME_LENGTH];
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST");
// clean name
int lastpos = 0;
{
int i = 0;
const char *psrc = pName;
char *pdst = aCleanName;
for(; *psrc != ' ' && *psrc<=' '; ++psrc);
while(*psrc != ' ' && i<sizeof(aCleanName)-1)
{
if(*psrc <= ' ')
{
*pdst = ' ';
}
else
{
*pdst = *psrc;
lastpos = i+1;
}
++psrc;
++pdst;
++i;
}
aCleanName[lastpos] = ' '; //Rtrim
}
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST2");
/////////////// These lines if not commented prevent the segfault
// char aBuf[256];
// str_format(aBuf, sizeof(aBuf), "CleanName : '%s, lastpos = %i'", aCleanName, lastpos);
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", aCleanName);
// set real name
str_copy(m_aClients[ClientID].m_aName, aCleanName, sizeof(aCleanName));
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST3");
// DispName
if(m_aClients[ClientID].m_aUserAcc[0] != ' ')
{
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "AUTHED");
pDispName = m_aClients[ClientID].m_aName;
}
else
{
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "NOT AUTHED");
str_percent_format(aCleanName, sizeof(aCleanName), g_Config.m_SvNotAuthedFormat, m_aClients[ClientID].m_aName);
pDispName = aCleanName;
}
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST4");
if(TrySetClientDispName(ClientID, pDispName))
{
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TRY FAILED");
// auto rename
int j = 0;
for( ; j<MAX_NAME_LENGTH-3 && aCleanName[j]!=' ' ; ++j);
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST5");
for( ; j<MAX_NAME_LENGTH-3; ++j)
aCleanName[j] = ' ';
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST6");
aCleanName[MAX_NAME_LENGTH-3] = '#';
aCleanName[MAX_NAME_LENGTH-2] = 'a';
aCleanName[MAX_NAME_LENGTH-1] = ' ';
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST7");
for(int i = 0; i<MAX_CLIENTS; ++i) //avoid infinite loop
{
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST9");
if(TrySetClientDispName(ClientID, aCleanName) == 0)
break;
++aCleanName[MAX_NAME_LENGTH-2];
}
}
Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "DEBUG", "TEST10");
}
打印 TEST10,但休耕调用的语句永远不会
注意:TrySetClientDispName(ClientID, aCleanName)
不存储指向 aCleanName 的指针,
str_format
实现如下:
void str_format(char *buffer, int buffer_size, const char *format, ...)
{
va_list ap;
va_start(ap, format);
vsnprintf(buffer, buffer_size, format, ap);
va_end(ap);
buffer[buffer_size-1] = 0; /* assure null termination */
}
我想,这是堆栈损坏,但是在哪里?那么为什么str_format
可以防止错误?
编辑:也许str_percent_format(aCleanName, sizeof(aCleanName), g_Config.m_SvNotAuthedFormat, m_aClients[ClientID].m_aName)
在 aCleanName 中写的比它所能容纳的更多
这样。
在此类错误中,如果缓冲区在与另一个函数一起使用时具有奇怪的行为,并且在函数结束后出现段错误,这是因为缓冲区溢出损坏了调用堆栈。
顺便说一句,这可以做一个很好的后门:D