Bunun nedeni eval
ve exec
çok tehlikeli olması, varsayılan compile
işlevin herhangi bir geçerli python ifadesi için bayt kodu üretmesi ve varsayılanın eval
veya exec
herhangi bir geçerli python bayt kodunu çalıştırmasıdır. Bugüne kadarki tüm cevaplar, üretilebilecek bayt kodunu kısıtlamaya (girdiyi temizleyerek) veya AST'yi kullanarak kendi alana özgü dilinizi oluşturmaya odaklandı.
Bunun yerine, eval
haince bir şey yapamayan basit bir işlevi kolayca oluşturabilirsiniz ve bellek veya kullanılan zaman üzerinde kolayca çalışma zamanı kontrolleri yapabilirsiniz. Tabii basit matematikse, kısayol olduğundan daha var.
c = compile(stringExp, 'userinput', 'eval')
if c.co_code[0]==b'd' and c.co_code[3]==b'S':
return c.co_consts[ord(c.co_code[1])+ord(c.co_code[2])*256]
Bunun çalışma şekli basittir, herhangi bir sabit matematiksel ifade derleme sırasında güvenli bir şekilde değerlendirilir ve sabit olarak saklanır. Derleme tarafından döndürülen kod nesnesi d
, bunun bayt kodu olan LOAD_CONST
ve ardından yüklenecek sabitin numarası (genellikle listedeki sonuncusu) ve ardından S
bayt kodu olan RETURN_VALUE
. Bu kısayol çalışmazsa, kullanıcı girdisinin sabit bir ifade olmadığı anlamına gelir (bir değişken veya işlev çağrısı veya benzeri içerir).
Bu aynı zamanda bazı daha karmaşık girdi biçimlerine de kapı açar. Örneğin:
stringExp = "1 + cos(2)"
Bu aslında bayt kodunu değerlendirmeyi gerektirir ki bu hala oldukça basittir. Python bayt kodu yığın yönelimli bir dildir, bu nedenle her şey basit TOS=stack.pop(); op(TOS); stack.put(TOS)
veya benzerdir. Anahtar, yalnızca güvenli olan (değerleri yükleme / depolama, matematik işlemleri, değerleri döndürme) ve güvenli olmayanları (öznitelik arama) olmayan işlem kodlarını uygulamaktır. Kullanıcının işlevleri çağırabilmesini istiyorsanız (yukarıdaki kısayolu kullanmama nedeninin tamamı), basitçe uygulamanızı CALL_FUNCTION
yalnızca 'güvenli' bir listedeki işlevlere izin verin.
from dis import opmap
from Queue import LifoQueue
from math import sin,cos
import operator
globs = {'sin':sin, 'cos':cos}
safe = globs.values()
stack = LifoQueue()
class BINARY(object):
def __init__(self, operator):
self.op=operator
def __call__(self, context):
stack.put(self.op(stack.get(),stack.get()))
class UNARY(object):
def __init__(self, operator):
self.op=operator
def __call__(self, context):
stack.put(self.op(stack.get()))
def CALL_FUNCTION(context, arg):
argc = arg[0]+arg[1]*256
args = [stack.get() for i in range(argc)]
func = stack.get()
if func not in safe:
raise TypeError("Function %r now allowed"%func)
stack.put(func(*args))
def LOAD_CONST(context, arg):
cons = arg[0]+arg[1]*256
stack.put(context['code'].co_consts[cons])
def LOAD_NAME(context, arg):
name_num = arg[0]+arg[1]*256
name = context['code'].co_names[name_num]
if name in context['locals']:
stack.put(context['locals'][name])
else:
stack.put(context['globals'][name])
def RETURN_VALUE(context):
return stack.get()
opfuncs = {
opmap['BINARY_ADD']: BINARY(operator.add),
opmap['UNARY_INVERT']: UNARY(operator.invert),
opmap['CALL_FUNCTION']: CALL_FUNCTION,
opmap['LOAD_CONST']: LOAD_CONST,
opmap['LOAD_NAME']: LOAD_NAME
opmap['RETURN_VALUE']: RETURN_VALUE,
}
def VMeval(c):
context = dict(locals={}, globals=globs, code=c)
bci = iter(c.co_code)
for bytecode in bci:
func = opfuncs[ord(bytecode)]
if func.func_code.co_argcount==1:
ret = func(context)
else:
args = ord(bci.next()), ord(bci.next())
ret = func(context, args)
if ret:
return ret
def evaluate(expr):
return VMeval(compile(expr, 'userinput', 'eval'))
Açıkçası, bunun gerçek versiyonu biraz daha uzun olacaktır (24'ü matematikle ilgili olan 119 işlem kodu vardır). Eklemek STORE_FAST
ve birkaç tane daha eklemek 'x=5;return x+x
, benzer veya benzer girdilere önemsiz bir şekilde kolayca izin verir . Kullanıcı tarafından oluşturulan işlevlerin kendileri VMeval aracılığıyla yürütüldüğü sürece, kullanıcı tarafından oluşturulan işlevleri yürütmek için bile kullanılabilir (onları çağrılabilir hale getirmeyin !!! yoksa bir yerde geri arama olarak kullanılabilirler). Döngüleri goto
işlemek, bayt kodlar için destek gerektirir , bu da bir for
yineleyiciden while
geçerli talimata bir göstericiyi sürdürmek anlamına gelir , ancak çok zor değildir. DOS'a direnç için, ana döngü hesaplamanın başlangıcından bu yana ne kadar zaman geçtiğini kontrol etmeli ve bazı operatörler makul bir sınırın (BINARY_POWER
en bariz olmak).
Bu yaklaşım, basit ifadeler için basit bir dilbilgisi ayrıştırıcısından biraz daha uzun olsa da (sadece derlenmiş sabiti yakalama hakkında yukarıya bakın), daha karmaşık girdilere kolayca yayılır ve dilbilgisi ile uğraşmayı gerektirmez ( compile
rastgele karmaşık olan herhangi bir şeyi alın ve basit talimatlar dizisi).