如何从操作系统级别的Python沙箱中安全地返回对象?

时间:2013-11-11 04:43:53

标签: python security sandbox pickle python-3.3

我需要能够运行不受信任的Python脚本。经过大量的研究,似乎只有Python的沙箱不安全,至少在CPython(我需要使用它)中。

因此,我们计划使用OS级沙盒(SELinux,AppArmor等)。

我的问题是:我们如何安全地与沙箱沟通?沙箱中的代码需要返回Python类型,如int和str,以及Numpy数组。未来可能会有更多类型。

显而易见的方法是使用pickle,但沙盒中的某些恶意代码似乎可以获取输出管道(我们考虑使用0MQ)并发回可能导致任意代码执行的内容在沙箱外面打开。

对于那些没有JSON等性能开销的pickle更安全的序列化替代方案吗?

我们正在使用Python 3.3。

1 个答案:

答案 0 :(得分:0)

听起来你对JSON唯一真正的问题是你编写NumPy数组(和Pandas表)的方式。 JSON不适合您的用例 - 不是因为它处理NumPy数据的速度很慢,而是因为它是基于文本的格式,并且您有很多数据更容易以非文本格式进行编码。

所以,我将在下面向您展示一下JSON的所有问题...但我建议使用不同的格式。

两种主要的“二进制JSON”格式BJSONBSON旨在提供JSON的大部分好处(简单,安全,动态/无模式,可遍历等),同时也可以直接嵌入二进制数据。 (在这种情况下,它们也是二元而不是文本格式对你来说并不重要。)我相信Smile也是如此,但我从未使用它。

这意味着,以同样的方式,JSON可以轻松地挂钩任何可以缩减为字符串,浮点数,列表和字符串的内容,BJSON和BSON可以轻松地挂钩任何可以减少为字符串,浮点数,列表,dicts,和字节字符串。因此,当我展示如何将NumPy编码/解码为字符串时,同样的事情适用于字节字符串,但最后没有所有额外的步骤。

BJSON和BSON的缺点是它们不是人类可读的,并且几乎没有广泛的支持。


我不知道您目前是如何对数组进行编码的,但是从我怀疑您正在使用tolist方法或类似方法的时间开始。那肯定会很慢,而且很大。如果你在任何地方存储除f8值以外的任何值,它甚至会丢失信息(因为JSON理解的唯一一种数字是IEEE双精度数)。解决方案是编码为字符串。

NumPy有一种文本格式,它会更快,而不是有损,但仍然可能比你想要的更慢更大。

它也有一个二进制格式,这很好......但是没有足够的信息来恢复原始数组。

那么,让我们看一下pickle使用的内容,你可以通过在任何对象上调用__reduce__方法来看到:基本上,它是类型,形状,dtype,告诉NumPy的一些标志如何解释原始数据,然后是二进制格式的原始数据。您实际上可以自己编码__reduce__数据 - 事实上,这可能是值得的。但是为了说明,让我们做一些更简单的事情,理解它只适用于ndarray,并且不适用于具有不同字节序的机器(或者更少的情况,如符号大小的int或非IEEE浮动)。

def numpy_default(obj):
    if isinstance(obj, np.ndarray):
        return {'_npdata': obj.tostring(), 
                '_npdtype': obj.dtype.name,
                '_npshape': obj.shape}
    else:
        return json.dumps(obj)

def dumps(obj):
    return json.dumps(obj, default=numpy_default)

def numpy_hook(obj):
    try:
        data = obj['_npdata']
    except AttributeError:
        return obj
    return np.fromstring(data, obj['_npdtype']).reshape(obj['_npshape'])

def loads(obj):
    return json.loads(obj, object_hook=numpy_hook)

唯一的问题是np.tostring会为您提供'bytes'个对象,而Python 3的json不知道如何处理。

如果你正在使用像BJSON或BSON这样的东西,你就可以停下来。但是使用JSON,你需要字符串。

如果使用映射每个单字节字符的任何编码“解码”字节(如Latin-1),您可以轻松解决这个问题:将obj.tostring()更改为obj.tostring().decode('latin-1')和{{1到data = obj['_npdata']。通过UTF-8编码假的Latin-1字符串浪费了一点空间,但那不是坏。

不幸的是,Python会使用Unicode转义序列对每个非ASCII字符进行编码。您可以通过在转储上设置data = obj['_npdata'].encode('latin-1')并在加载时设置ensure_ascii=False来关闭它,但它仍将编码控制字符,主要是6字节序列。这会使随机数据的大小翻倍,而且可以做得更糟 - 例如,全零数组将大6倍!

曾经有一个解决这个问题的技巧,但在3.3中,它不起作用。您可以做的最好的事情是对strict=False包进行分叉或修补,以便在给定json时允许您传递控制字符,您可以这样做:

ensure_ascii=False

这非常hacky,但它确实有效。


无论如何,希望这足以让你开始。