overhaul lua sandboxing so that each lua script has its own sandbox. elaborate tracking of winform/event ownership to ensure that lua doesnt receive calls without going through the owner's sandbox. add win32-specific speed hacks for currdir set/get at higher speed. There may be bugs in this commit, but I think we're on the right track now.

This commit is contained in:
zeromus 2016-01-31 19:54:48 -06:00
parent c510e13d82
commit 5e89e563d0
10 changed files with 200 additions and 85 deletions

View File

@ -30,8 +30,6 @@ namespace BizHawk.Client.Common
public override string Name { get { return "event"; } }
public Lua CurrentThread { get; set; }
#region Events Library Helpers
public void CallExitEvent(Lua thread)

View File

@ -3,22 +3,16 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
//TODO - kill this file (or renew the concept as distinct from the LuaSandbox?)
namespace BizHawk.Client.Common
{
public class EnvironmentSandbox
{
public static void Sandbox(Action callback)
{
string oldCurrentDirectory = Environment.CurrentDirectory;
try
{
callback();
}
finally
{
Environment.CurrentDirectory = oldCurrentDirectory;
}
//just a stub for right now
callback();
}
}
}

View File

@ -31,14 +31,14 @@ namespace BizHawk.Client.Common
State = RunState.Disabled;
}
public LuaFile(LuaFile file)
{
Name = file.Name;
Path = file.Path;
State = file.State;
IsSeparator = file.IsSeparator;
CurrentDirectory = file.CurrentDirectory;
}
//public LuaFile(LuaFile file)
//{
// Name = file.Name;
// Path = file.Path;
// State = file.State;
// IsSeparator = file.IsSeparator;
// CurrentDirectory = file.CurrentDirectory;
//}
public string Name { get; set; }
public string Path { get; set; }

View File

@ -1,6 +1,7 @@
using System;
using System.Drawing;
using System.Linq;
using System.Threading;
using LuaInterface;
using BizHawk.Common.ReflectionExtensions;
@ -24,6 +25,31 @@ namespace BizHawk.Client.Common
public Action<string> LogOutputCallback { get; set; }
public Lua Lua { get; set; }
public static Lua CurrentThread { get; private set; }
static Thread CurrentHostThread;
static object ThreadMutex = new object();
public static void ClearCurrentThread()
{
lock (ThreadMutex)
{
CurrentHostThread = null;
CurrentThread = null;
}
}
public static void SetCurrentThread(Lua luaThread)
{
lock (ThreadMutex)
{
if (CurrentHostThread != null)
throw new InvalidOperationException("Can't have lua running in two host threads at a time!");
CurrentHostThread = Thread.CurrentThread;
CurrentThread = luaThread;
}
}
protected void Log(object message)
{
if (LogOutputCallback != null)

View File

@ -1,43 +1,96 @@
using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LuaInterface;
//TODO - evaluate for re-entrancy problems
namespace BizHawk.Client.Common
{
public class LuaSandbox
public unsafe class LuaSandbox
{
protected static Action<string> Logger;
public static void SetLogger(Action<string> logger)
protected static Action<string> Logger;
static System.Runtime.CompilerServices.ConditionalWeakTable<Lua, LuaSandbox> SandboxForThread = new System.Runtime.CompilerServices.ConditionalWeakTable<Lua, LuaSandbox>();
public static Action<string> DefaultLogger;
public void SetLogger(Action<string> logger)
{
Logger = logger;
}
public static void SetCurrentDirectory(string dir)
public void SetSandboxCurrentDirectory(string dir)
{
CurrentDirectory = dir;
}
static string CurrentDirectory;
public static void Sandbox(Action callback, Action exceptionCallback = null)
{
string CurrentDirectory;
#if WINDOWS
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetCurrentDirectoryW(byte* lpPathName);
[DllImport("kernel32.dll", SetLastError=true)]
static extern uint GetCurrentDirectoryW(uint nBufferLength, byte* pBuffer);
#endif
bool CoolSetCurrentDirectory(string path, string currDirSpeedHack = null)
{
string target = CurrentDirectory + "\\";
//first we'll bypass it with a general hack: dont do any setting if the value's already there (even at the OS level, setting the directory can be slow)
//yeah I know, not the smoothest move to compare strings here, in case path normalization is happening at some point
//but you got any better ideas?
if (currDirSpeedHack == null)
currDirSpeedHack = CoolGetCurrentDirectory();
if (currDirSpeedHack == path)
return true;
//WARNING: setting the current directory is SLOW!!! security checks for some reason.
//so we're bypassing it with windows hacks
#if WINDOWS
fixed (byte* pstr = &System.Text.Encoding.Unicode.GetBytes(target + "\0")[0])
return SetCurrentDirectoryW(pstr);
#else
if(System.IO.Directory.Exists(CurrentDirectory)) //race condition for great justice
{
Environment.CurrentDirectory = CurrentDirectory; //thats right, you can't set a directory as current that doesnt exist because .net's got to do SENSELESS SLOW-ASS SECURITY CHECKS on it and it can't do that on a NONEXISTENT DIRECTORY
return true;
}
else return false
#endif
}
string CoolGetCurrentDirectory()
{
//GUESS WHAT!
//.NET DOES A SECURITY CHECK ON THE DIRECTORY WE JUST RETRIEVED
//AS IF ASKING FOR THE CURRENT DIRECTORY IS EQUIVALENT TO TRYING TO ACCESS IT
//SCREW YOU
#if WINDOWS
var buf = new byte[32768];
fixed(byte* pBuf = &buf[0])
{
uint ret = GetCurrentDirectoryW(32767, pBuf);
return System.Text.Encoding.Unicode.GetString(buf, 0, (int)ret*2);
}
#else
return Environment.CurrentDirectory;
#endif
}
void Sandbox(Action callback, Action exceptionCallback)
{
string savedEnvironmentCurrDir = null;
try
{
//so. lets talk about current directories.
//ideally we'd have one current directory per script. but things get hairy.
//events and callbacks can get setup and it isn't clear what script they belong to.
//moreover we don't really have a sense of sandboxing individual scripts, they kind of all get run together in the same VM, i think
//so let's just try keeping one 'current directory' for all lua. it's an improvement over lua's 'current directory' for the process, interfering with the core emulator's
savedEnvironmentCurrDir = Environment.CurrentDirectory;
if(System.IO.Directory.Exists(CurrentDirectory)) //race condition for great justice
Environment.CurrentDirectory = CurrentDirectory;
savedEnvironmentCurrDir = Environment.CurrentDirectory;
if (CurrentDirectory != null)
CoolSetCurrentDirectory(CurrentDirectory, savedEnvironmentCurrDir);
EnvironmentSandbox.Sandbox(callback);
CurrentDirectory = Environment.CurrentDirectory;
}
catch (LuaException ex)
{
@ -49,12 +102,46 @@ namespace BizHawk.Client.Common
}
finally
{
if (savedEnvironmentCurrDir != null)
{
if (System.IO.Directory.Exists(savedEnvironmentCurrDir)) //race condition for great justice
Environment.CurrentDirectory = savedEnvironmentCurrDir;
}
if (CurrentDirectory != null)
CoolSetCurrentDirectory(savedEnvironmentCurrDir);
}
}
public static LuaSandbox CreateSandbox(Lua thread, string initialDirectory)
{
var sandbox = new LuaSandbox();
SandboxForThread.Add(thread, sandbox);
sandbox.SetSandboxCurrentDirectory(initialDirectory);
sandbox.SetLogger(DefaultLogger);
return sandbox;
}
public static LuaSandbox GetSandbox(Lua thread)
{
//this is just placeholder.
//we shouldnt be calling a sandbox with no thread--construct a sandbox properly
if (thread == null)
{
return new LuaSandbox();
}
lock (SandboxForThread)
{
LuaSandbox sandbox;
if (SandboxForThread.TryGetValue(thread, out sandbox))
return sandbox;
else
{
//for now: throw exception (I want to manually creating them)
//return CreateSandbox(thread);
throw new InvalidOperationException("HOARY GORILLA HIJINX");
}
}
}
public static void Sandbox(Lua thread, Action callback, Action exceptionCallback = null)
{
GetSandbox(thread).Sandbox(callback, exceptionCallback);
}
}
}

View File

@ -56,7 +56,7 @@ namespace BizHawk.Client.Common
public void Call(string name = null)
{
LuaSandbox.Sandbox(() => {
LuaSandbox.Sandbox(Lua, () => {
_function.Call(name);
});
}

View File

@ -382,7 +382,7 @@ namespace BizHawk.Client.EmuHawk
)]
public int NewForm(int? width = null, int? height = null, string title = null, LuaFunction onClose = null)
{
var form = new LuaWinform();
var form = new LuaWinform(CurrentThread);
_luaForms.Add(form);
if (width.HasValue && height.HasValue)
{

View File

@ -153,34 +153,44 @@ namespace BizHawk.Client.EmuHawk
public void ExecuteString(string command)
{
_currThread = _lua.NewThread();
_currThread.DoString(command);
}
public ResumeResult ResumeScript(Lua script)
{
EventsLibrary.CurrentThread = script;
_currThread = script;
var execResult = script.Resume(0);
_lua.RunScheduledDisposes();
//not sure how this is going to work out, so do this too
script.RunScheduledDisposes();
_currThread = null;
var result = new ResumeResult();
if (execResult == 0)
try
{
// terminated
result.Terminated = true;
}
else
{
// yielded
result.WaitForFrame = FrameAdvanceRequested;
}
LuaLibraryBase.SetCurrentThread(_currThread);
FrameAdvanceRequested = false;
return result;
var execResult = script.Resume(0);
_lua.RunScheduledDisposes();
//not sure how this is going to work out, so do this too
script.RunScheduledDisposes();
_currThread = null;
var result = new ResumeResult();
if (execResult == 0)
{
// terminated
result.Terminated = true;
}
else
{
// yielded
result.WaitForFrame = FrameAdvanceRequested;
}
FrameAdvanceRequested = false;
return result;
}
finally
{
LuaLibraryBase.ClearCurrentThread();
}
}
public static void Print(params object[] outputs)

View File

@ -76,8 +76,8 @@ namespace BizHawk.Client.EmuHawk
LuaListView.QueryItemIndent += LuaListView_QueryItemIndent;
LuaListView.VirtualMode = true;
LuaSandbox.SetLogger(this.ConsoleLog);
LuaSandbox.SetCurrentDirectory(PathManager.GetLuaPath());
//this is bad, in case we ever have more than one gui part running lua.. not sure how much other badness there is like that
LuaSandbox.DefaultLogger = ConsoleLog;
}
public EmuLuaLibrary LuaImp { get; set; }
@ -162,9 +162,10 @@ namespace BizHawk.Client.EmuHawk
string pathToLoad = Path.IsPathRooted(file.Path) ? file.Path : PathManager.MakeProgramRelativePath(file.Path); //JUNIPIER SQUATCHBOX COMPLEX
try
{
LuaSandbox.Sandbox(() =>
LuaSandbox.Sandbox(file.Thread, () =>
{
file.Thread = LuaImp.SpawnCoroutine(pathToLoad);
LuaSandbox.CreateSandbox(file.Thread, Path.GetDirectoryName(pathToLoad));
file.State = LuaFile.RunState.Running;
}, () =>
{
@ -198,9 +199,10 @@ namespace BizHawk.Client.EmuHawk
{
try
{
LuaSandbox.Sandbox(() =>
LuaSandbox.Sandbox(null, () =>
{
luaFile.Thread = LuaImp.SpawnCoroutine(pathToLoad);
LuaSandbox.CreateSandbox(luaFile.Thread, Path.GetDirectoryName(pathToLoad));
luaFile.State = LuaFile.RunState.Running;
}, () =>
{
@ -245,13 +247,15 @@ namespace BizHawk.Client.EmuHawk
{
foreach (var file in _luaList)
{
if (file.Enabled && file.Thread == null)
if (!file.Enabled && file.Thread == null)
{
try
{
LuaSandbox.Sandbox(() =>
LuaSandbox.Sandbox(null, () =>
{
string pathToLoad = Path.IsPathRooted(file.Path) ? file.Path : PathManager.MakeProgramRelativePath(file.Path); //JUNIPIER SQUATCHBOX COMPLEX
file.Thread = LuaImp.SpawnCoroutine(file.Path);
LuaSandbox.CreateSandbox(file.Thread, Path.GetDirectoryName(pathToLoad));
}, () =>
{
file.State = LuaFile.RunState.Disabled;
@ -454,17 +458,11 @@ namespace BizHawk.Client.EmuHawk
{
try
{
LuaSandbox.Sandbox(() =>
LuaSandbox.Sandbox(lf.Thread, () =>
{
var prohibit = lf.FrameWaiting && !includeFrameWaiters;
if (!prohibit)
{
// Restore this lua thread's preferred current directory
if (lf.CurrentDirectory != null)
{
Environment.CurrentDirectory = PathManager.MakeAbsolutePath(lf.CurrentDirectory, null);
}
var result = LuaImp.ResumeScript(lf.Thread);
if (result.Terminated)
{
@ -474,9 +472,6 @@ namespace BizHawk.Client.EmuHawk
}
lf.FrameWaiting = result.WaitForFrame;
// If the lua thread changed its current directory, capture that here
lf.CurrentDirectory = Environment.CurrentDirectory;
}
}, () =>
{
@ -768,9 +763,11 @@ namespace BizHawk.Client.EmuHawk
{
try
{
LuaSandbox.Sandbox(() =>
LuaSandbox.Sandbox(null, () =>
{
string pathToLoad = Path.IsPathRooted(item.Path) ? item.Path : PathManager.MakeProgramRelativePath(item.Path); //JUNIPIER SQUATCHBOX COMPLEX
item.Thread = LuaImp.SpawnCoroutine(item.Path);
LuaSandbox.CreateSandbox(item.Thread, Path.GetDirectoryName(pathToLoad));
}, () =>
{
item.State = LuaFile.RunState.Disabled;
@ -1174,12 +1171,12 @@ namespace BizHawk.Client.EmuHawk
// TODO: Maybe make these try-catches more general
if (InputBox.Text != "")
{
LuaSandbox.Sandbox(() =>
LuaSandbox.Sandbox(null, () =>
{
LuaImp.ExecuteString(string.Format("console.log({0})", InputBox.Text));
}, () =>
{
LuaSandbox.Sandbox(() =>
LuaSandbox.Sandbox(null, () =>
{
LuaImp.ExecuteString(InputBox.Text);

View File

@ -12,9 +12,12 @@ namespace BizHawk.Client.EmuHawk
private string CurrentDirectory = Environment.CurrentDirectory;
public LuaWinform()
Lua OwnerThread;
public LuaWinform(Lua ownerThread)
{
InitializeComponent();
OwnerThread = ownerThread;
StartPosition = FormStartPosition.CenterParent;
Closing += (o, e) => CloseThis();
}
@ -31,7 +34,7 @@ namespace BizHawk.Client.EmuHawk
public void DoLuaEvent(IntPtr handle)
{
LuaSandbox.Sandbox(() =>
LuaSandbox.Sandbox(OwnerThread, () =>
{
Environment.CurrentDirectory = CurrentDirectory;
foreach (LuaEvent l_event in ControlEvents)