#c# #security #compiler-construction
#c# #Безопасность #компилятор-конструирование
Вопрос:
Итак, я подумал о написании компилятора c # в режиме онлайн и среды выполнения. И, конечно, проблема № 1 — это безопасность. В итоге я создал appdomain с небольшими привилегиями для пользовательского кода и запустил его в новом процессе, который строго отслеживается на предмет потребления процессора и памяти. Доступны стандартные пространства имен консольных приложений. Итак, мой вопрос заключается в следующем: можете ли вы придумать способы каким-либо образом что-то сломать? Вы можете опробовать свои идеи на месте в rundotnet.
Редактировать 2 Если кого-то волнует код, теперь есть форк этого проекта с открытым исходным кодом: rextester на github
Edit1 В качестве ответа на один из комментариев вот несколько примеров кода.
Итак, по сути, вы создаете консольное приложение. Я просто опубликую большую часть этого:
class Sandboxer : MarshalByRefObject
{
private static object[] parameters = { new string[] { "parameter for the curious" } };
static void Main(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
string pathToUntrusted = args[0].Replace("|_|", " ");
string untrustedAssembly = args[1];
string entryPointString = args[2];
string[] parts = entryPointString.Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
string name_space = parts[0];
string class_name = parts[1];
string method_name = parts[2];
//Setting the AppDomainSetup. It is very important to set the ApplicationBase to a folder
//other than the one in which the sandboxer resides.
AppDomainSetup adSetup = new AppDomainSetup();
adSetup.ApplicationBase = Path.GetFullPath(pathToUntrusted);
//Setting the permissions for the AppDomain. We give the permission to execute and to
//read/discover the location where the untrusted code is loaded.
PermissionSet permSet = new PermissionSet(PermissionState.None);
permSet.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
//Now we have everything we need to create the AppDomain, so let's create it.
AppDomain newDomain = AppDomain.CreateDomain("Sandbox", null, adSetup, permSet, null);
//Use CreateInstanceFrom to load an instance of the Sandboxer class into the
//new AppDomain.
ObjectHandle handle = Activator.CreateInstanceFrom(
newDomain, typeof(Sandboxer).Assembly.ManifestModule.FullyQualifiedName,
typeof(Sandboxer).FullName
);
//Unwrap the new domain instance into a reference in this domain and use it to execute the
//untrusted code.
Sandboxer newDomainInstance = (Sandboxer)handle.Unwrap();
Job job = new Job(newDomainInstance, untrustedAssembly, name_space, class_name, method_name, parameters);
Thread thread = new Thread(new ThreadStart(job.DoJob));
thread.Start();
thread.Join(10000);
if (thread.ThreadState != ThreadState.Stopped)
{
thread.Abort();
Console.Error.WriteLine("Job taking too long. Aborted.");
}
AppDomain.Unload(newDomain);
}
public void ExecuteUntrustedCode(string assemblyName, string name_space, string class_name, string method_name, object[] parameters)
{
MethodInfo target = null;
try
{
target = Assembly.Load(assemblyName).GetType(name_space "." class_name).GetMethod(method_name);
if (target == null)
throw new Exception();
}
catch (Exception)
{
Console.Error.WriteLine("Entry method '{0}' in class '{1}' in namespace '{2}' not found.", method_name, class_name, name_space);
return;
}
...
//Now invoke the method.
try
{
target.Invoke(null, parameters);
}
catch (Exception e)
{
...
}
}
}
class Job
{
Sandboxer sandboxer = null;
string assemblyName;
string name_space;
string class_name;
string method_name;
object[] parameters;
public Job(Sandboxer sandboxer, string assemblyName, string name_space, string class_name, string method_name, object[] parameters)
{
this.sandboxer = sandboxer;
this.assemblyName = assemblyName;
this.name_space = name_space;
this.class_name = class_name;
this.method_name = method_name;
this.parameters = parameters;
}
public void DoJob()
{
try
{
sandboxer.ExecuteUntrustedCode(assemblyName, name_space, class_name, method_name, parameters);
}
catch (Exception e)
{
Console.Error.WriteLine(e.Message);
}
}
}
Вы компилируете вышеуказанный файл и получаете исполняемый файл, который запускаете и отслеживаете в новом процессе:
using (Process process = new Process())
{
try
{
double TotalMemoryInBytes = 0;
double TotalThreadCount = 0;
int samplesCount = 0;
process.StartInfo.FileName = /*path to sandboxer*/;
process.StartInfo.Arguments = folder.Replace(" ", "|_|") " " assemblyName " Rextester|Program|Main"; //assemblyName - assembly that contains compiled user code
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
DateTime start = DateTime.Now;
process.Start();
OutputReader output = new OutputReader(process.StandardOutput);
Thread outputReader = new Thread(new ThreadStart(output.ReadOutput));
outputReader.Start();
OutputReader error = new OutputReader(process.StandardError);
Thread errorReader = new Thread(new ThreadStart(error.ReadOutput));
errorReader.Start();
do
{
// Refresh the current process property values.
process.Refresh();
if (!process.HasExited)
{
try
{
var proc = process.TotalProcessorTime;
// Update the values for the overall peak memory statistics.
var mem1 = process.PagedMemorySize64;
var mem2 = process.PrivateMemorySize64;
//update stats
TotalMemoryInBytes = (mem1 mem2);
TotalThreadCount = (process.Threads.Count);
samplesCount ;
if (proc.TotalSeconds > 5 || mem1 mem2 > 100000000 || process.Threads.Count > 100 || start TimeSpan.FromSeconds(10) < DateTime.Now)
{
var time = proc.TotalSeconds;
var mem = mem1 mem2;
process.Kill();
...
}
}
catch (InvalidOperationException)
{
break;
}
}
}
while (!process.WaitForExit(10)); //check process every 10 milliseconds
process.WaitForExit();
...
}
...
class OutputReader
{
StreamReader reader;
public string Output
{
get;
set;
}
StringBuilder sb = new StringBuilder();
public StringBuilder Builder
{
get
{
return sb;
}
}
public OutputReader(StreamReader reader)
{
this.reader = reader;
}
public void ReadOutput()
{
try
{
int bufferSize = 40000;
byte[] buffer = new byte[bufferSize];
int outputLimit = 200000;
int count;
bool addMore = true;
while (true)
{
Thread.Sleep(10);
count = reader.BaseStream.Read(buffer, 0, bufferSize);
if (count != 0)
{
if (addMore)
{
sb.Append(Encoding.UTF8.GetString(buffer, 0, count));
if (sb.Length > outputLimit)
{
sb.Append("nn...");
addMore = false;
}
}
}
else
break;
}
Output = sb.ToString();
}
catch (Exception e)
{
...
}
}
}
Сборки, которые может использовать пользовательский код, добавляются во время компиляции:
CompilerParameters cp = new CompilerParameters();
cp.GenerateExecutable = false;
cp.OutputAssembly = ...
cp.GenerateInMemory = false;
cp.TreatWarningsAsErrors = false;
cp.WarningLevel = 4;
cp.IncludeDebugInformation = false;
cp.ReferencedAssemblies.Add("System.dll");
cp.ReferencedAssemblies.Add("System.Core.dll");
cp.ReferencedAssemblies.Add("System.Data.dll");
cp.ReferencedAssemblies.Add("System.Data.DataSetExtensions.dll");
cp.ReferencedAssemblies.Add("System.Xml.dll");
cp.ReferencedAssemblies.Add("System.Xml.Linq.dll");
using (CodeDomProvider provider = CodeDomProvider.CreateProvider(/*language*/))
{
cr = provider.CompileAssemblyFromSource(cp, new string[] { data.Program });
}
Комментарии:
1. Что, если бы я создал тысячи потоков backgroundworker?
2. Я не могу ответить на ваш вопрос, но я действительно поддерживаю идею! это очень полезно!
3. @Anirudwha… Попробовал это, и все было красиво убито
4. Милый маленький проект. Пытался возиться с системой. Пространство имен ввода-вывода и с помощью AppDomain пытается получить доступ к файловой системе на сервере. Продолжал получать исключения из CodeAccessSecurityEngine (что хорошо!). Не зная, как это настроено, следует проверить, можете ли вы перечислить файлы в каталоге веб-приложения.
5. Я заметил, что небезопасный код отключен, что тоже хорошо. Пусть так и будет
Ответ №1:
Вы рассматривали компилятор Mono как сервис? Я думаю, что это довольно круто, что они делают, возможно, что-то там может быть вам полезно для этого проекта.
Ответ №2:
Хороший пример чего-то подобного уже существует, есть наhttp://www.topcoder.com, в котором есть «Арена алгоритмов», где код отправляется и автоматически оценивается. Существуют ограничения на использование определенных типов классов, таких как Exception, но, возможно, было бы неплохо изучить их применение для подтверждения концепции.