Files
2025-10-28 14:05:46 -04:00

467 lines
17 KiB
C++

#pragma once
#include "V8Tree/Service.h"
#include "Util/ProtectedString.h"
#include "util/runstateowner.h"
#include "Script/IScriptFilter.h"
#include "script/ThreadRef.h"
#include "script/ExitHandlers.h"
#include "Security/SecurityContext.h"
#include "Util/AsyncHttpQueue.h"
#include "util/RunningAverage.h"
#include "rbx/RunningAverage.h"
#define BOOST_DATE_TIME_NO_LIB
#include "boost/date_time/posix_time/posix_time.hpp"
struct lua_State;
struct lua_Debug;
LOGGROUP(ScriptContext)
LOGGROUP(ScriptContextRemove)
LOGGROUP(ScriptContextAdd)
LOGGROUP(ScriptContextClose)
namespace RBX
{
class LuaSourceContainer;
class LuaAllocator;
class LibraryService;
class ModuleScript;
class ModelInstance;
namespace Stats
{
class Item;
}
namespace Lua
{
class YieldingThreads;
class WeakFunctionRef;
}
namespace Network
{
class Player;
}
class BaseScript;
class CoreScript;
class ScriptStats;
class LuaStatsItem;
void registerScriptDescriptors();
extern const char* const sScriptContext;
class ScriptContext
: public DescribedCreatable<ScriptContext, Instance, sScriptContext, Reflection::ClassDescriptor::INTERNAL_LOCAL>
, public Service
, public IScriptFilter
{
friend class LuaStatsItem;
friend class GcJob;
friend class WaitingScriptsJob;
public:
static const int hookCount;
struct ScriptStartOptions
{
struct LuaSyntaxError : std::runtime_error
{
LuaSyntaxError(int lineNumber, std::exception& source)
:std::runtime_error(source.what())
,lineNumber(lineNumber)
{
}
int lineNumber;
};
RBX::Security::Identities identity;
Scripts::Continuations continuations;
boost::function<std::string(const std::string&)> filter; // may throw a LuaSyntaxError
ScriptStartOptions():identity(RBX::Security::GameScript_)
{
}
};
private:
typedef DescribedCreatable<ScriptContext, Instance, sScriptContext, Reflection::ClassDescriptor::INTERNAL_LOCAL> Super;
class ScriptImpersonator : public RBX::Security::Impersonator
{
public:
ScriptImpersonator(lua_State *thread);
};
struct GlobalState
{
GlobalState()
: state(0)
, gcCount(0)
{
}
lua_State* state;
RunningAverage<double> gcAllocAvg; // average lua memory allocation per luaGcFrequency in KB
int gcCount;
};
typedef boost::array<GlobalState, RBX::Security::COUNT_VM_Classes> GlobalStates; // separate Lua top-level states
GlobalStates globalStates;
Lua::WeakThreadRef commandLineSandbox;
std::set<BaseScript*> scripts;
RBX::Time nextPendingScripts;
struct ScriptStart
{
shared_ptr<BaseScript> script;
ScriptStartOptions options;
};
std::vector<ScriptStart> pendingScripts; // scripts waiting to be executed
std::vector<ScriptStart> loadingScripts; // scripts waiting to be executed
// An obfuscated pointer to a location near where the object was created.
// copying the object becomes detectable.
// https://en.wikipedia.org/wiki/Feistel_cipher
// the "update" method might be targeted even if it is obfuscated.
struct SecurityAnchor
{
size_t value[2];
FORCEINLINE void update(const void* ptr)
{
#ifdef _WIN32
size_t localValue[2] = {reinterpret_cast<size_t>(ptr), ~reinterpret_cast<size_t>(ptr)};
localValue[0] ^= localValue[1]*RBX_BUILDSEED | 20151112;
localValue[1] ^= localValue[0]*20151112 | RBX_BUILDSEED;
value[0] = localValue[0];
value[1] = localValue[1];
#endif
}
FORCEINLINE bool checkAnchor(const void* ptr) const
{
#ifdef _WIN32
size_t localValue[2] = {value[0], value[1]};
localValue[1] ^= localValue[0]*20151112 | RBX_BUILDSEED;
localValue[0] ^= localValue[1]*RBX_BUILDSEED | 20151112;
return !((reinterpret_cast<size_t>(ptr)+localValue[1])
^ (~reinterpret_cast<size_t>(ptr)+localValue[0]));
#else
return true;
#endif
}
};
SecurityAnchor securityAnchor;
shared_ptr<RunService> runService;
boost::scoped_ptr<Lua::YieldingThreads> yieldEvent; // collects all threads that have yielded, and periodically resumes them
struct WaitingThread
{
Lua::ThreadRef thread;
shared_ptr<const Reflection::Tuple> arguments;
};
rbx::safe_queue<WaitingThread> waitingThreads;
bool robloxPlace;
bool scriptsDisabled; // == don't run the scripts contained in BaseScript objects
bool preventNewConnection;
shared_ptr<LuaStatsItem> statsItem;
bool collectScriptStats;
shared_ptr<ScriptStats> scriptStats;
std::set<weak_ptr<ModuleScript> > loadedModules;
int startScriptReentrancy;
rbx::atomic<int> timedoutCount;
Time::Interval timoutSpan; // The time that is allowed per heartbeat before scripts stop running (0 means no timeouts)
Time timoutTime; // The system time when we should time-out scripts
rbx::atomic<int> timedout; // == scripts should stop running
boost::scoped_ptr<boost::thread> timeoutThread;
boost::mutex timeoutMutex;
volatile bool endTimoutThread;
CEvent checkTimeout;
struct AssetModuleInfo
{
enum State
{
NotFetchedYet = 0,
Fetching,
Fetched,
Failed
};
State state;
std::vector<Lua::WeakThreadRef> yieldedImporters;
shared_ptr<ModuleScript> module;
shared_ptr<ModelInstance> root;
AssetModuleInfo()
: state(NotFetchedYet)
{}
};
typedef boost::unordered_map<int, AssetModuleInfo> LoadedAssetModules;
LoadedAssetModules loadedAssetModules;
Time luaGcStartTime;
RunningAverage<double> avgLuaGcInterval; // in msec
RunningAverage<double> avgLuaGcTime; // in msec
RunningAverageTimeInterval<> resumedThreads;
RunningAverage<> throttlingThreads; // 1 if threads are being deffered
bool statesClosed;
public:
ScriptContext();
virtual ~ScriptContext();
///////////////////////////////////////////////////
// IScriptFilter
/*override*/ virtual bool scriptShouldRun(BaseScript* script);
static void setAdminScriptPath(const std::string& newPath);
//////////////////////////////////////////////////
// Reflection API
static Reflection::BoundProp<bool> propScriptsDisabled;
static Reflection::BoundProp<int> propLuaGcLimit;
static Reflection::BoundProp<int> propLuaGcFrequency;
static Reflection::BoundProp<int> propLuaGcStepSize;
void setTimeout(double seconds);
void setCollectScriptStats(bool);
// Core & Starter Scripts
void addStarterScript(int assetId);
void addCoreScript(int assetId, shared_ptr<Instance> parent, std::string name);
void addCoreScriptLocal(std::string scriptName, shared_ptr<Instance> parent);
// Experimental error signal for catching errors server-side
rbx::signal<void(std::string, std::string, shared_ptr<Instance>)> errorSignal;
// A temporary signals used for diagnostic purposes
rbx::signal<void(shared_ptr<Instance>, std::string, shared_ptr<Instance>)> camelCaseViolation;
rbx::signal<void(lua_State*)> scriptErrorDetected;
////////////////////////////////////////////////
// Configuration
void setRobloxPlace(bool robloxPlace);
void initializeLuaStateSandbox(Lua::WeakThreadRef& threadRef, lua_State* parentState, Security::Identities identity);
void setKeys(unsigned int scriptKey, unsigned int coreScriptModKey);
////////////////////////////////////////////////
// Helpers and utilities
Reflection::Variant evaluateStudioCommandItem(const char* itemToEvaluate, shared_ptr<RBX::LuaSourceContainer> script);
static bool checkSyntax(const std::string& code, int& line, std::string& errorMessage);
static lua_State* getGlobalState(lua_State* thread);
static ScriptContext& getContext(lua_State* thread);
static void printCallStack(lua_State* thread, std::string* output = NULL, bool dontPrint = false);
static std::string extractCallStack(lua_State* thread, shared_ptr<BaseScript>& source, int& line);
// Shutdown helpers
bool shouldPreventNewConnections() { return preventNewConnection; }
void setPreventNewConnections() { preventNewConnection = true; }
void closeStates(bool resettingSimulation); // Closes down all threads
bool haveStatesClosed() { return statesClosed; }
void cleanupModules();
/////////////////////////////////////////////////
// Script Instance API
// Called by IScriptOwner implementers
void addScript(weak_ptr<BaseScript> script, ScriptStartOptions startOptions = ScriptStartOptions()); // checks pointer validity
void removeScript(weak_ptr<BaseScript> script);
size_t numScripts() {return scripts.size();}
bool hasScript(BaseScript* script) {return (scripts.find(script) != scripts.end());}
/////////////////////////////////////////////////
// Calls that make lua run/resume
void executeInNewThread(RBX::Security::Identities identity, const ProtectedString& script, const char* name);
std::auto_ptr<Reflection::Tuple> executeInNewThread(RBX::Security::Identities identity, const ProtectedString& script, const char* name, const Reflection::Tuple& arguments);
void executeInNewThreadWithExtraGlobals(RBX::Security::Identities identity,
const ProtectedString& script, const char* name,
const std::map<std::string, shared_ptr<Instance> >& extraGlobals);
// Calls a function
Reflection::Tuple callInNewThread(Lua::WeakFunctionRef& function, const Reflection::Tuple& arguments);
// Thread-safe call:
void scheduleResume(Lua::ThreadRef thread, shared_ptr<const Reflection::Tuple> arguments);
typedef enum { Success, Yield, Error } Result;
// Resumes the thread. Reports errors and queues yielding threads for later execution
// NOTE: The caller is reponsible for balancing the stack
Result resume(RBX::Lua::ThreadRef thread, int narg);
/////////////////////////////////////////////
// Stats
void scriptResumedFromEvent() { resumedThreads.sample(); }
size_t getThreadCount() const;
shared_ptr<const Reflection::Tuple> getHeapStats(bool clearHighwaterMark);
shared_ptr<const Reflection::Tuple> getScriptStats(); // deprecated. Don't use it anymore
shared_ptr<const Reflection::ValueArray> getScriptStatsNew();
struct ScriptStat
{
std::string hash;
std::string name;
Instances scripts;
double activity;
unsigned int invocationCount;
};
void getScriptStatsTyped(std::vector<ScriptStat>& result);
double getAvgLuaGcTime() { return avgLuaGcTime.value(); }
double getAvgLuaGcInterval() { return avgLuaGcInterval.value(); }
void reloadModuleScript(shared_ptr<ModuleScript> moduleScript);
bool checkSecurityAnchorValid() const
{
return securityAnchor.checkAnchor(&this->securityAnchor);
}
protected:
/*override*/ void onServiceProvider(ServiceProvider* oldProvider, ServiceProvider* newProvider);
private:
rbx::signals::scoped_connection heartbeatConnection;
boost::scoped_ptr<LuaAllocator> allocator;
shared_ptr<TaskScheduler::Job> gcJob;
shared_ptr<TaskScheduler::Job> waitingScriptsJob;
void onHeartbeat(const Heartbeat& heartbeat);
void stepGc();
void resumeWaitingScripts(Time expirationTime);
static void sandboxThread(lua_State* thread);
static void setThreadIdentityAndSandbox(lua_State* thread, RBX::Security::Identities identity, shared_ptr<BaseScript> script);
static RBX::Security::Identities getThreadIdentity(lua_State* thread);
// Executes a script, throws an std::exception on error
// The script is spawned from the global root thread, but it is "sandboxed" to the extent that global declarations
// don't affect other threads
// If globalStateToExecuteIn is NULL we get the global state to execute in by our current identity, which is the first arg to this function
void executeInNewThread(RBX::Security::Identities identity, const ProtectedString& script, const char* name,
boost::function1<size_t, lua_State*> pushArguments,
boost::function2<void, lua_State*, size_t> readImmediateResults,
Scripts::Continuations continuations,
lua_State* globalStateToExecuteIn = NULL,
const std::map<std::string, shared_ptr<Instance> >* extraGlobals = NULL,
unsigned int modkey = 1);
// Resumes the thread (expects the top of the stack to contain a function)
// Throws an std::exception if the thread throws a error
void resumeWithArgs(Lua::ThreadRef thread, shared_ptr<const Reflection::Tuple> arguments);
void resume(Lua::ThreadRef thread, boost::function1<size_t, lua_State*> pushArguments, boost::function2<void, lua_State*, size_t> readResults);
void onChangedScriptEnabled(const Reflection::PropertyDescriptor&);
void onCheckTimeout();
void onHook(lua_State *L, lua_Debug *ar);
struct ScriptStatInformation
{
ScriptStatInformation()
{}
std::string name;
Instances scripts;
};
std::map<std::string, ScriptStatInformation> scriptHashInfo;
// Functions exposed in the Lua environment:
public:
static void hook(lua_State *L, lua_Debug *ar);
void reportError(lua_State* thread);
lua_State* getGlobalState(RBX::Security::Identities identity);
private:
static int print(lua_State *L);
static int doPrint(lua_State *thread, const MessageType& messageType = MESSAGE_OUTPUT);
static int crash(lua_State *L);
static int tick(lua_State* thread);
static int rbxTime(lua_State* thread);
static int time(lua_State* thread);
static int wait(lua_State* thread);
static int delay(lua_State* thread);
static int ypcall(lua_State* thread);
void on_ypcall_success(Lua::WeakThreadRef caller, lua_State* functor);
void on_ypcall_failure(Lua::WeakThreadRef caller, lua_State* functor);
static int spawn(lua_State* thread);
static int printidentity(lua_State* thread);
static int loadfile(lua_State* thread);
static int loadstring(lua_State* thread);
static int notImplemented(lua_State* thread);
static int dofile(lua_State* thread);
static int settings(lua_State* thread);
static int usersettings(lua_State* thread);
static int pluginmanager(lua_State* thread);
static int debuggermanager(lua_State *thread);
static int loadLibrary(lua_State* L);
static int loadRobloxLibrary(lua_State* L);
static int requireModuleScript(lua_State* L);
static int stats(lua_State* thread);
static int version(lua_State* thread);
static int statsitemvalue(lua_State* thread);
static int requireModuleScriptFromInstance(lua_State* L, shared_ptr<ModuleScript> moduleScript);
static int requireModuleScriptFromAssetId(lua_State* L, int assetId);
static void moduleContentLoaded(AsyncHttpQueue::RequestResult result, shared_ptr<Instances> instances,
ScriptContext& sc, Security::Identities identity, lua_State* globalState, AssetModuleInfo* info);
static void moduleContentLinkedSourcesResolved(shared_ptr<Instances> instances,
shared_ptr<ModuleScript> foundModuleScript, ScriptContext& sc, Security::Identities identity,
lua_State* globalState, AssetModuleInfo* info);
void startRunningModuleScript(Security::Identities identity, lua_State* globalState, shared_ptr<ModuleScript> moduleScript);
static void requireModuleScriptSuccessContinuation(shared_ptr<ModuleScript> moduleScript,
lua_State* threadRunningModuleCode);
static void requireModuleScriptErrorContinuation(shared_ptr<ModuleScript> moduleScript,
lua_State* threadRunningModuleCode);
static void reloadModuleScriptInternal(lua_State* globalState, shared_ptr<ModuleScript> moduleScript);
static void reloadModuleScriptSuccessContinuation(shared_ptr<ModuleScript> moduleScript,
lua_State* reloadThread,
int oldResultRegistryIndex);
static void reloadModuleScriptErrorContinuation(shared_ptr<ModuleScript> moduleScript,
lua_State* reloadThread);
static int warn(lua_State *L);
static void validateThreadAccess(lua_State* L);
static int resumeImpl(lua_State* L, int nargs);
int camelCaseViolationCount;
rbx::signals::connection camelCaseViolationConnection;
void onCamelCaseViolation(shared_ptr<Instance> object, std::string memberName, shared_ptr<Instance> script);
friend class BaseScript;
void disassociateState(BaseScript* script);
bool openState(size_t idx);
void closeState(lua_State* globalState);
void startScript(ScriptStart scriptStart);
static void eraseScript(std::vector<ScriptContext::ScriptStart>& container, BaseScript* script);
void startPendingScripts();
unsigned int coreScriptModKey;
};
#ifdef _DEBUG
class StackBalanceCheck
{
const int oldTop;
lua_State *thread;
bool cancelled;
public:
StackBalanceCheck(lua_State *thread);
~StackBalanceCheck();
void cancel() { cancelled = true; }
};
#define RBXASSERT_BALLANCED_LUA_STACK(L) StackBalanceCheck stackBalanceCheck(L)
#define RBXASSERT_BALLANCED_LUA_STACK2(L) StackBalanceCheck stackBalanceCheck2(L)
#define CANCEL_BALLANCED_LUA_STACK_CHECK() stackBalanceCheck.cancel()
#define CANCEL_BALLANCED_LUA_STACK_CHECK2() stackBalanceCheck2.cancel()
#else
#define RBXASSERT_BALLANCED_LUA_STACK(L) ((void)0)
#define RBXASSERT_BALLANCED_LUA_STACK2(L) ((void)0)
#define CANCEL_BALLANCED_LUA_STACK_CHECK() ((void)0)
#define CANCEL_BALLANCED_LUA_STACK_CHECK2() ((void)0)
#endif
}