Add an LD_PRELOAD library "buildat_guard" which takes care of limiting Urho3D::ResourceCache's file system access while it's lacking the functionalty by itself

This commit is contained in:
Perttu Ahola 2014-09-27 19:34:09 +03:00
parent 19a1da6033
commit 4b25272cc9
9 changed files with 276 additions and 4 deletions

View File

@ -1,3 +1,6 @@
# http://www.apache.org/licenses/LICENSE-2.0
# Copyright 2014 Perttu Ahola <celeron55@gmail.com>
cmake_minimum_required(VERSION 2.6)
project(buildat)
@ -18,6 +21,7 @@ include_directories("3rdparty/smallsha1")
# Global options
#
set(BUILD_GUARD TRUE CACHE BOOL "Build LD_PRELOAD guard (used for the client)")
set(BUILD_SERVER TRUE CACHE BOOL "Build server")
set(BUILD_CLIENT TRUE CACHE BOOL "Build client")
@ -50,17 +54,49 @@ define_dependency_libs("Urho3D")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -Wall")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wextra -Wno-unused-parameter")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -std=c++0x -g -O0 -Wall")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") # Executables
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") # Shared libs
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") # Static libs
# Always output in color (useful when using head for build output)
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdiagnostics-color=always")
# Security / crash protection
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector-all")
set(CLIENT_EXE_NAME buildat_client)
set(SERVER_EXE_NAME buildat_server)
if(BUILD_GUARD)
# LD_PRELOAD wrapper
set(PRELOAD_LIB_NAME buildat_guard)
set(PRELOAD_SRCS
src/guard/lib.c
)
add_library(${PRELOAD_LIB_NAME} SHARED ${PRELOAD_SRCS})
# Create wrapper script in place of client
set(GUARD_SCRIPT_NAME ${CLIENT_EXE_NAME})
set(GUARD_SCRIPT_PATH ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${GUARD_SCRIPT_NAME})
add_custom_command(
OUTPUT ${GUARD_SCRIPT_PATH}
COMMAND cp ${CMAKE_SOURCE_DIR}/src/guard/guard_launcher.sh
${GUARD_SCRIPT_PATH}
COMMAND chmod +x ${GUARD_SCRIPT_PATH}
DEPENDS ${CMAKE_SOURCE_DIR}/src/guard/guard_launcher.sh
VERBATIM
)
add_custom_target(generate_guard_script ALL
DEPENDS ${GUARD_SCRIPT_PATH}
DEPENDS ${CMAKE_SOURCE_DIR}/src/guard/guard_launcher.sh)
# Rename client to different name
set(CLIENT_EXE_NAME "${CLIENT_EXE_NAME}.bin")
endif(BUILD_GUARD)
if(BUILD_CLIENT)
# Client
set(CLIENT_EXE_NAME buildat_client)
set(CLIENT_SRCS
src/guard/buildat_guard_interface.cpp
src/client/main.cpp
src/client/state.cpp
src/client/app.cpp
@ -84,7 +120,6 @@ endif(BUILD_CLIENT)
if(BUILD_SERVER)
# Server
set(SERVER_EXE_NAME buildat_server)
set(SERVER_SRCS
src/server/main.cpp
src/server/state.cpp
@ -110,4 +145,5 @@ if(BUILD_SERVER)
${LINK_LIBS_ONLY}
)
endif(BUILD_SERVER)
# vim: set noet ts=4 sw=4:

View File

@ -5,6 +5,7 @@
#include "client/config.h"
#include "client/state.h"
#include "interface/fs.h"
#include "guard/buildat_guard_interface.h"
#include <c55/getopt.h>
#include <c55/os.h>
#include <c55/string_util.h>
@ -76,6 +77,16 @@ struct CApp: public App, public magic::Application
resource_paths_s += fs->get_absolute_path(path);
}
// Set allowed paths in buildat guard wrapper (ignored if not available)
buildat_guard_clear_paths();
for(const ss_ &path : resource_paths){
buildat_guard_add_valid_base_path(
fs->get_absolute_path(path).c_str());
}
buildat_guard_add_valid_base_path(
fs->get_absolute_path(g_client_config.cache_path).c_str());
// Set Urho3D engine parameters
engineParameters_["WindowTitle"] = "Buildat Client";
engineParameters_["LogName"] = "client_Urho3D.log";
engineParameters_["Headless"] = false;
@ -252,6 +263,10 @@ struct CApp: public App, public magic::Application
throw AppStartupError("Could not initialize Lua environment");
}
// Enable guard now. Everything from here should not access any weird
// files.
buildat_guard_enable(true);
if(g_client_config.boot_to_menu){
ss_ extname = g_client_config.menu_extension_name;
ss_ script = ss_()+
@ -908,7 +923,7 @@ struct CApp: public App, public magic::Application
return 2;
}
// faatal_error(error: string)
// fatal_error(error: string)
static int l_fatal_error(lua_State *L)
{
ss_ error = lua_tocppstring(L, 1);

View File

@ -26,6 +26,14 @@ static bool check_file_writable(const ss_ &path)
return writable;
}
void Config::make_paths_absolute()
{
auto *fs = interface::getGlobalFilesystem();
share_path = fs->get_absolute_path(share_path);
cache_path = fs->get_absolute_path(cache_path);
urho3d_path = fs->get_absolute_path(urho3d_path);
}
bool Config::check_paths()
{
bool fail = false;

View File

@ -14,6 +14,7 @@ namespace client
bool boot_to_menu = false;
ss_ menu_extension_name = "__menu";
void make_paths_absolute();
bool check_paths();
};
}

View File

@ -5,6 +5,7 @@
#include "client/config.h"
#include "client/state.h"
#include "client/app.h"
#include "guard/buildat_guard_interface.h"
#include <c55/getopt.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
@ -33,6 +34,20 @@ void signal_handler_init()
(void)signal(SIGINT, sigint_handler);
}
void guard_init()
{
if(buildat_guard_init() != 0){
log_w(MODULE, "The buildat_guard interface could not be accessed."
" You should LD_PRELOAD it.");
return;
}
// Guard is used only when Urho3D::ResourceCache is accessed by unsafe code
buildat_guard_enable(false);
//buildat_guard_add_valid_base_path("/usr/share/");
}
void basic_init()
{
signal_handler_init();
@ -42,6 +57,8 @@ void basic_init()
setlocale(LC_NUMERIC, "C");
log_set_max_level(LOG_VERBOSE);
guard_init();
}
int main(int argc, char *argv[])
@ -100,6 +117,7 @@ int main(int argc, char *argv[])
}
}
config.make_paths_absolute();
if(!config.check_paths()){
return 1;
}

View File

@ -0,0 +1,64 @@
// http://www.apache.org/licenses/LICENSE-2.0
// Copyright 2014 Perttu Ahola <celeron55@gmail.com>
#ifndef _WIN32
#include "core/log.h"
#include <stdio.h>
#define __GNU_SOURCE // RTLD_NEXT
#ifndef __USE_GNU
#define __USE_GNU // RTLD_NEXT
#endif
#include <dlfcn.h>
#define MODULE "guard"
extern "C"
{
void (*r_buildat_guard_add_valid_base_path)(const char *path) = NULL;
void (*r_buildat_guard_enable)(int enable) = NULL;
void (*r_buildat_guard_clear_paths)(void) = NULL;
int buildat_guard_init()
{
r_buildat_guard_add_valid_base_path = (void (*)(const char*))
dlsym(RTLD_NEXT, "buildat_guard_add_valid_base_path");
r_buildat_guard_enable = (void (*)(int))
dlsym(RTLD_NEXT, "buildat_guard_enable");
r_buildat_guard_clear_paths = (void (*)(void))
dlsym(RTLD_NEXT, "buildat_guard_clear_paths");
int succesful = (r_buildat_guard_add_valid_base_path != NULL);
log_v(MODULE, "buildat_guard_init(): %s", succesful?"succesful":"failed");
return !succesful;
}
void buildat_guard_add_valid_base_path(const char *path)
{
if(!r_buildat_guard_add_valid_base_path)
return;
log_v(MODULE, "buildat_guard_add_valid_base_path(\"%s\")", path);
(*r_buildat_guard_add_valid_base_path)(path);
}
void buildat_guard_enable(int enable)
{
if(!r_buildat_guard_enable)
return;
log_v(MODULE, "buildat_guard_enable(%s)", enable?"true":"false");
(*r_buildat_guard_enable)(enable);
}
void buildat_guard_clear_paths(void)
{
if(!r_buildat_guard_clear_paths)
return;
log_v(MODULE, "buildat_guard_clear_paths()");
(*r_buildat_guard_clear_paths)();
}
}
#else // #ifndef _WIN32
extern "C"
{
// Not supported on platform
int buildat_guard_init(){ return 0; }
void buildat_guard_add_valid_base_path(const char *path){}
void buildat_guard_enable(int enable){}
void buildat_guard_clear_paths(void){}
}
#endif // #ifndef _WIN32

View File

@ -0,0 +1,15 @@
// http://www.apache.org/licenses/LICENSE-2.0
// Copyright 2014 Perttu Ahola <celeron55@gmail.com>
// Remember to add / to end of base path if it is a directory
extern "C" {
// Returns zero if succesful.
// Returns non-zero on error.
// Returns non-zero if not supported on platform.
extern int buildat_guard_init();
// If initialized and supported, wraps to the buildat_guard library.
// If not initialized or not supported, does nothing.
extern void buildat_guard_add_valid_base_path(const char *path);
extern void buildat_guard_enable(int enable);
extern void buildat_guard_clear_paths(void);
}

View File

@ -0,0 +1,4 @@
#!/bin/sh
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
echo "guard_launcher.sh: If you wish to debug buildat_client, run $DIR/buildat_client.bin directly"
LD_PRELOAD="$DIR/../lib/libbuildat_guard.so" "$DIR/buildat_client.bin" $@

111
src/guard/lib.c Normal file
View File

@ -0,0 +1,111 @@
// http://www.apache.org/licenses/LICENSE-2.0
// Copyright 2014 Perttu Ahola <celeron55@gmail.com>
#include <stdio.h>
#include <string.h>
#define __GNU_SOURCE // RTLD_NEXT
#ifndef __USE_GNU
#define __USE_GNU // RTLD_NEXT
#endif
#include <dlfcn.h>
// API
// Only absolute paths allowed.
// Remember to add / to end of base path if it is a directory
void buildat_guard_add_valid_base_path(const char *path);
void buildat_guard_enable(int enable);
void buildat_guard_clear_paths();
// The API shall be made available to the main program by LD_PRELOADing this
// library.
// Implementation
static int guard_enabled = 1;
#define MAX_allowed_base_paths 50
#define MAX_allowed_base_path_size 1000
static char allowed_base_paths[MAX_allowed_base_paths][MAX_allowed_base_path_size] = {0};
static size_t allowed_base_paths_next_i = 0;
void buildat_guard_add_valid_base_path(const char *path)
{
// Don't add more paths than what fit the static storage
if(allowed_base_paths_next_i >= MAX_allowed_base_paths){
fprintf(stderr, "### guard: Will not add base path: %s (storage full)\n", path);
return;
}
// Require absolute path
if(path[0] != '/'){
fprintf(stderr, "### guard: Will not add base path: %s (not absolute path)\n", path);
return;
}
// Limit length to that of static storage
if(strlen(path) > MAX_allowed_base_path_size - 1){
fprintf(stderr, "### guard: Will not add base path: %s (path too long)\n", path);
return;
}
// Path ok; Copy it and return
strncpy(allowed_base_paths[allowed_base_paths_next_i], path, MAX_allowed_base_path_size-1);
allowed_base_paths[allowed_base_paths_next_i][MAX_allowed_base_path_size-1] = '\0';
allowed_base_paths_next_i++;
}
void buildat_guard_enable(int enable)
{
guard_enabled = enable;
}
void buildat_guard_clear_paths()
{
allowed_base_paths_next_i = 0;
}
// Returns non-zero value if path is allowed
static int path_allowed(const char *path)
{
size_t i;
const size_t path_len = strlen(path);
for(i=0; i<allowed_base_paths_next_i && i<MAX_allowed_base_paths; i++){
const char *base = allowed_base_paths[i];
const size_t base_len = strlen(base);
// Must start with base path
if(path_len < base_len)
continue;
if(memcmp(path, base, base_len) != 0)
continue;
// The rest of it must not contain '..'
if(strstr(path + base_len, "..") != NULL)
continue;
// Path is ok
fprintf(stderr, "### guard: Path %s allowed within base %s\n", path, base);
return 1;
}
fprintf(stderr, "### guard: Path not allowed: %s\n", path);
return 0;
}
static FILE* (*r_fopen)(const char *path, const char *mode) = NULL;
static int initialized = 0;
static void ensure_initialized()
{
if(initialized)
return;
initialized = 1;
r_fopen = dlsym(RTLD_NEXT, "fopen");
fprintf(stderr, "### guard: Initialized.\n");
}
// Urho3D's File uses fopen() on Linux and _wfopen() on Windows
FILE* fopen(const char *path, const char *mode)
{
if(guard_enabled && !path_allowed(path)){
return NULL;
}
ensure_initialized();
return (*r_fopen)(path, mode);
}