Fix fs_game '..' reading outside of home and base path

VMs could set fs_game to '..' at anytime to access files outside of home
and base path. fs_game sent by server to clients could also be '..' to
access files outside of home and base path.

'..' was not caught by FS_CheckDirTraversal() as it expects filenames
not a single directory.

I've made fs_game be latched to prevent VMs from changing it with no
good way to validate it before it's used. com_basegame and fs_basegame
are now latched as well.

Additionally, it's now possible to change com_basegame while the engine
is running. game_restart or vid_restart will make it take affect.
com_homepath is now CVAR_PROTECTED to prevent VMs from changing it
to a directory traversal.

This requires my two previous commits for preventing VMs from changing
engine latch cvars and only Cvar_Get fs_game in FS_Startup (so CVAR_INIT
isn't added in serveral other places).

Reported by Noah Metzger (Chomenor).
This commit is contained in:
Zack Middleton 2018-01-21 04:27:55 -06:00
parent 78ca670d4f
commit 3638f69dff
5 changed files with 54 additions and 32 deletions

View file

@ -3067,6 +3067,24 @@ qboolean FS_CheckDirTraversal(const char *checkdir)
return qfalse;
}
/*
================
FS_InvalidGameDir
return true if path is a reference to current directory or directory traversal
================
*/
qboolean FS_InvalidGameDir( const char *gamedir ) {
if ( !strcmp( gamedir, "." ) || !strcmp( gamedir, ".." )
|| !strcmp( gamedir, "/" ) || !strcmp( gamedir, "\\" )
|| strstr( gamedir, "/.." ) || strstr( gamedir, "\\.." )
|| FS_CheckDirTraversal( gamedir ) ) {
return qtrue;
}
return qfalse;
}
/*
================
FS_ComparePaks
@ -3301,13 +3319,34 @@ static void FS_Startup( const char *gameName )
fs_debug = Cvar_Get( "fs_debug", "0", 0 );
fs_basepath = Cvar_Get ("fs_basepath", Sys_DefaultInstallPath(), CVAR_INIT|CVAR_PROTECTED );
fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_INIT );
fs_basegame = Cvar_Get ("fs_basegame", "", CVAR_LATCH|CVAR_NORESTART );
homePath = Sys_DefaultHomePath();
if (!homePath || !homePath[0]) {
homePath = fs_basepath->string;
}
fs_homepath = Cvar_Get ("fs_homepath", homePath, CVAR_INIT|CVAR_PROTECTED );
fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO );
fs_gamedirvar = Cvar_Get ("fs_game", "", CVAR_LATCH|CVAR_NORESTART|CVAR_SYSTEMINFO );
if (!gameName[0]) {
Cvar_ForceReset( "com_basegame" );
}
if (!FS_FilenameCompare(fs_gamedirvar->string, gameName)) {
// This is the standard base game. Servers and clients should
// use "" and not the standard basegame name because this messes
// up pak file negotiation and lots of other stuff
Cvar_ForceReset( "fs_game" );
}
if (FS_InvalidGameDir(gameName)) {
Com_Error( ERR_DROP, "Invalid com_basegame '%s'", gameName );
}
if (FS_InvalidGameDir(fs_basegame->string)) {
Com_Error( ERR_DROP, "Invalid fs_basegame '%s'", fs_basegame->string );
}
if (FS_InvalidGameDir(fs_gamedirvar->string)) {
Com_Error( ERR_DROP, "Invalid fs_game '%s'", fs_gamedirvar->string );
}
// add search path elements in reverse priority order
fs_gogpath = Cvar_Get ("fs_gogpath", Sys_GogPath(), CVAR_INIT|CVAR_PROTECTED );
@ -3391,8 +3430,6 @@ static void FS_Startup( const char *gameName )
// print the current search paths
FS_Path_f();
fs_gamedirvar->modified = qfalse; // We just loaded, it's not modified
Com_Printf( "----------------------\n" );
#ifdef FS_MISSING
@ -4040,17 +4077,10 @@ Return qtrue if restarting due to game directory changed, qfalse otherwise
*/
qboolean FS_ConditionalRestart(int checksumFeed, qboolean disconnect)
{
if(fs_gamedirvar->modified)
if (com_basegame->latchedString || fs_basegame->latchedString || fs_gamedirvar->latchedString)
{
if(FS_FilenameCompare(lastValidGame, fs_gamedirvar->string) &&
(*lastValidGame || FS_FilenameCompare(fs_gamedirvar->string, com_basegame->string)) &&
(*fs_gamedirvar->string || FS_FilenameCompare(lastValidGame, com_basegame->string)))
{
Com_GameRestart(checksumFeed, disconnect);
return qtrue;
}
else
fs_gamedirvar->modified = qfalse;
Com_GameRestart(checksumFeed, disconnect);
return qtrue;
}
if(checksumFeed != fs_checksumFeed)