Valve · Report #769014
[GoldSrc] RCE via 'spk' Console Command
Validstatus
highseverity
$350bounty
23upvotes
19 Aug 2020disclosed
Summary
Details: #Description RCE can be achieved on clients via the 'spk' console command due to missing length checks before copying into a stack based buffer.
#POC
- Place the attached cfg file in the root directory of the game: {F676967}
- Launch the game and bring up the console with
~ - Type in
exec rce.cfgand press enter - View calc pop
#Vulnerable Code
The problem arises because VOX_LoadSound calls VOX_GetDirectory which copies into the stack buffer szpath without any length validation which leads to a buffer overflow.
char *VOX_GetDirectory(char *szpath, char *psz)
{
char c;
int cb = 0;
char *pszscan = psz + strlen(psz) - 1;
// scan backwards until first '/' or start of string
c = *pszscan;
while (pszscan > psz && c != '/')
{
c = *(--pszscan);
cb++;
}
if (c != '/')
{
// didn't find '/', return default directory
strcpy(szpath, "vox/");
return psz;
}
cb = strlen(psz) - cb;
memcpy(szpath, psz, cb); // missing length validation
szpath[cb] = 0;
return pszscan + 1;
}
aud_sfxcache_t *VOX_LoadSound(aud_channel_t *pchan, char *pszin)
{
char buffer[512];
int i, j, k, cword;
char pathbuffer[64];
char szpath[32];
aud_sfxcache_t *sc;
voxword_t rgvoxword[CVOXWORDMAX];
char *psz;
if (!pszin)
return NULL;
memset(rgvoxword, 0, sizeof (voxword_t) * CVOXWORDMAX);
memset(buffer, 0, sizeof(buffer));
// lookup actual string in (*gAudEngine.rgpszrawsentence),
// set pointer to string data
psz = VOX_LookupString(pszin, NULL);
if (!psz)
{
gEngfuncs.Con_DPrintf ("VOX_LoadSound: no sentence named %s\n",pszin);
return NULL;
}
// get directory from string, advance psz
psz = VOX_GetDirectory(szpath, psz);
if (strlen(psz) > sizeof(buffer) - 1)
{
gEngfuncs.Con_DPrintf ("VOX_LoadSound: sentence is too long %s\n",psz);
return NULL;
}
// copy into buffer
strncpy(buffer, psz, sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = 0;
psz = buffer;
// parse sentence (also inserts null terminators between words)
VOX_ParseString(psz);
// for each word in the sentence, construct the filename,
// lookup the sfx and save each pointer in a temp array
i = 0;
cword = 0;
while (rgpparseword[i])
{
// Get any pitch, volume, start, end params into voxword
if (VOX_ParseWordParams(rgpparseword[i], &rgvoxword[cword], i == 0))
{
// this is a valid word (as opposed to a parameter block)
_snprintf(pathbuffer, sizeof(pathbuffer), "%s%s.wav", szpath, rgpparseword[i]);
pathbuffer[sizeof(pathbuffer) - 1] = 0;
if (strlen(pathbuffer) >= sizeof(pathbuffer))
continue;
// find name, if already in cache, mark voxword
// so we don't discard when word is done playing
rgvoxword[cword].sfx = S_FindName(pathbuffer, &(rgvoxword[cword].fKeepCached));
cword++;
}
i++;
}
k = VOX_IFindEmptySentence();
if (k < 0)
return NULL;
j = 0;
while (rgvoxword[j].sfx != NULL)
rgrgvoxword[k][j] = rgvoxword[j++];
pchan->isentence = k;
pchan->iword = 0;
pchan->sfx = rgvoxword[0].sfx;
sc = S_LoadSound(pchan->sfx, pchan);
if (!sc)
{
S_FreeChannel(pchan);
return NULL;
}
return sc;
}
Impact
RCE allows for an attacker to execute any arbitrary code on a chosen victim.
How it was found
The write-up for this report has not been downloaded yet, or the program disclosed it without one. Read it on HackerOne.