Wir betrachten
eine Sandbox-Implementation für Ruby, die auf
Clemens' RubyCHannel (http://www.ruby.ch) eingesetzt wird. Sie ermöglicht es, (fast) beliebigen Ruby-Code im Webbrowser einzugeben und auf dem Server des RubyChannel ausführen zu lassen.
Damit der Server mögliche "`Attacken"' übersteht, müssen bestimmte Funktionen und Programmiertechniken speziell kontrolliert werden. Schließlich ist der Online-Interpreter auf ruby.ch ja ausdrücklich zum Probieren gedacht, und wer wollte nicht schon immer mal sehen, was bei folgenden Eingaben passiert:
system("rm -rf /") # ;-(
# oder auch:
liste = []
while true do
liste.push(String.new("test")
end
Um diese "`gefährlichen"' Skripte zu unterbinden, gleichzeitig aber
möglichst große Freiheiten zu gewähren, wird die folgende Sandbox
"`dazwischengeschaltet"'. Der Auszug zeigt die sicherheitsrelevanten
Teile der Sandbox auf. Der gesamte Code befindet sich im Anhang
F.
Die mit * markierten Zeilen sind aus drucktechnischen Gründen
umgebrochen worden, # kennzeichnet Erläuterungen.
1 class Sandbox
.................
23 def starteWaechterThread
24 @waechterThread = Thread.new {
25 ObjectSpace.garbage_collect # Aufräumen!
26 anzObjekteAmAnfang = ObjectSpace.each_object {}
27 laeuftSeit = 0
28 begin
29 while not(@done) do
# Skripte haben eine begrenzte Laufzeit ...
30 if (laeuftSeit > @maxLaufzeit) then
31 raiseSecurityError("Die Zeit ist
* abgelaufen (#{@maxRunTime}sec)")
32 end
33 ObjectSpace.garbage_collect
# ... dürfen nicht zu viele Objekte kreieren
34 if ((ObjectSpace.each_object {} -
* anzObjekteAmAnfang) > @maxNeueObjekte)
* then
35 raiseSecurityError("Es wurden schon mehr
* als #{@maxNewObjects} Objekte erzeugt!")
36 end
# ... oder zu viele Threads erzeugen
37 if (threadCount > @maxThreadCount) then
38 raiseSecurityError("Es dürfen maximal
* #{@maxThreadCount} Threads erzeugt
* werden!")
39 end
40 sleep 1
41 laeuftSeit += 1
42 end # while
.................
54 def starteInDerSandbox(exeCmd)
55 begin
56 eval(exeCmd, Object.module_eval("binding"))
57 rescue SecurityError => detail
.................
# cmd ist das auszuführende Skript
65 def fuehreAus(cmd)
.................
# vor der Ausführung $SAFE hochsetzen
68 cmd = "$SAFE = #{@level}\n" + cmd
# hochpriorisierter Monitor
69 starteWaechterThread # Unser BIG BROTHER ;-)
# sehr niedrig priorisierter Thread für das Skript
70 @sandboxThreadGroup.add(@sandboxThread =
* Thread.new {
71 starteInDerSandbox(exeCmd)
72 })
73 @sandboxThread.priority= -5 # sehr niedrig!
.................
77 end # fuehreAus
78 end # class Sandbox
Das Prinzip: Der auszuführende Code wird in einem (einer)
niedrigpriorisierten [#73] Thread(-Gruppe) [#70-#72] per
eval [#56] ausgeführt. Daneben läuft in einem hochpriorisierten Thread
der "`Wächter"' [#24-#42]. Dieser überwacht unter anderem, dass das Programm
terminiert [#30-#32], nicht zu viele neue Objekte erzeugt [#34-#36] und nicht zu
viele Threads [#37-#39] anstößt. Was in diesem Codeauszug fehlt, sind all
die weiteren Restriktionen, welche von der Sandbox (beziehungsweise dem
"`Wächter"') verhindert werden. Diese (Restriktionen) sind hauptsächlich durch
das Überladen von "`nicht erlaubten"' Kernel-Funktionen implementiert.