7

我正在制作一个游戏,用户可以在其中编写 Python 程序来控制互相战斗的机器人。每一回合(在千回合游戏中),他们的脚本都会在我的服务器上运行,以确定机器人的下一步行动。如何防止这些用户对我的服务器不利?

我已经想到/研究了以下内容:

  • eval在有限的环境中编译他们的代码(即__builtins__禁用)
  • 使用特定于操作系统的监狱,如chroot/ptrace
  • 在某种虚拟机中运行代码

我有一个 Python 程序,它运行用户的脚本一千次。如何强制执行最长一分钟的总持续时间、限制他们的内存资源、阻止他们访问任何文件或网络连接等等?什么是理想的解决方案或解决方案的组合?

4

3 回答 3

6

我在PythonAnywhere工作,这是一个 Python 平台即服务,这意味着我们代表我们的用户运行许多不受信任的代码——Python.org 头版上的新交互式控制台是我们的,并且它的限制可能离你不远。

我建议使用操作系统级别的虚拟化;语言级别的任何内容都可能不太安全。由于历史原因,我们使用 chroot 和 cgroups,但如果我们今天从头开始,我认为我们会使用 Linux 容器 (LXC) 或Docker。LXC 基本上是一堆巧妙的 chroot 和 cgroups 包装器等,这使得它们更易于使用,因此您可以非常快速地启动一个临时虚拟机。Docker 是一个更易于使用的 LXC 包装器。两者都非常快——您可以在不到一秒的时间内启动并运行一个新的虚拟机。

于 2014-03-19T12:58:34.407 回答
3

我查看了您的个人资料,并确定这是一项持续的爱心劳动!所以这里..

鉴于您的要求,我假设您正在计划一些可以通过互联网获得的东西。

关于您的第二行查询:

我有一个 Python 程序,它运行用户的脚本一千次。如何强制执行最长一分钟的总持续时间、限制他们的内存资源、阻止他们访问任何文件或网络连接等等?什么是理想的解决方案或解决方案的组合?

您可能知道,这是可以追溯到 1980 年代的传统。从知情的角度来看,重新发明有时是最好的,但并不总是最好的。有关如何对玩家指令进行时间切片等的想法,您可以查看一些相关的当前项目的来源,这些项目已经在野外存在。例如:

剧透:

- robocode
这个项目 历史悠久;自 2005 年以来一直在社区手中,并得到积极维护。来源是java。基本阅读 imo。
https://github.com/robo-code/robocode

- Fightcode:
这是一种 javascript 风格,可在线使用。因此,它在帮助您使用在线 pythonic 方法时应该非常有用。该网站现在似乎是商业/封闭源代码。不过,您可以看到为github 2012 game-off提交的原始资源。至少,这可能会提供有关如何向世界展示您生成的游戏内容的想法:)
https://github.com/timehome/game-off-2012
(顺便说一句,fightcode 获得了亚军!)


您的第一条询问线 已经结束了。见这里。然而:

最便宜(在任何意义上)的选择将是chroot监狱。下一个最便宜的选择是Linux-VServer之类的。尽管我没有使用该软件的经验,但性能显然接近原生。否则,是的,使用xen或其他方式进行完全虚拟化。

观点:

完全虚拟化将带来令人头疼的问题和不必要的开销。

如果(且仅当)你很小心,你可以用一个简单的chroot监狱来做到这一点。

  • 将监狱完全用作运行代码的一次性“竞技场”。确保它以完成工作所需的最少资源和特权运行。

  • 将您的网络应用程序与监狱完全分离(例如,监狱应该不需要网络访问等等)。

所以工作流程可能是这样的:

  1. 通过您的网络应用上传游戏请求。

  2. 祝福游戏请求。你说的是“任意不友好的代码”,但实际上游戏会有一些形式参数;您只需要 Python 功能的一个子集。至少检查一下。

  3. 排队显然是理智的游戏请求并将它们提交到竞技场(又名监狱)。

  4. 如果您真的很偏执,请为每个新游戏建造一个新的、干净的竞技场/监狱。

  5. 在竞技场中运行游戏。将每个离散的游戏状态(以内部低成本格式)保存到监狱本地的数据库中。

  6. 在监狱外,当游戏完成后,向网络应用程序提供游戏状态数据库,然后将其渲染到互联网上。

关于设置 Python 监狱的注意事项。

这就是我进入这篇文章的方式。我一直在寻找有关如何python使用(非常出色的)越狱工具包的具体说明,但找不到太多。所以我自己做了,然后我看看SO上是否有地方放它。

请注意,某些 nix 发行版(例如UbuntuCentospython)在其操作中使用。在这种情况下,为避免破坏您的系统,您可能希望从源代码构建所需的任何 Python 版本。

这是我在Centos上安装 Python 2.7(假设你已经安装了jailkit)的秘诀。

首先,包括创建专用用户在内的更正式的越狱方法可能是:

# Once-only set-up:

# as root user:

## get, build and alt-install required python onto host OS
mkdir -p /usr/local/src/python
cd /usr/local/src/python
wget http://www.python.org/ftp/python/2.7/Python-2.7.tgz
tar -vxzf Python-2.7.tgz
cd Python-2.7
./configure               #default ${prefix}="/usr/local"
make
make altinstall           #don't clobber systems' python; everythin installed under /urs/local and comes with 2.7 postfix.


## set-up a jailkit config for python 2.7 (& optionally to support some minimal devs)
cat <<OOOK >> /etc/jailkit/jk_init.ini

[python2.7]
comment = python 2.7 interpreter and libraries
paths = python2.7, /usr/local/lib/python2.7, /usr/local/include/python2.7, /etc/ld.so.conf
devices = /dev/null, /dev/zero, /dev/random, /dev/urandom 

OOOK

# Ad-infinitum:

# as root user:

cd /home

## create a python jail by passing jk_init the name of the sction created in /etc/jailkit/jk_init.ini
jk_init -v -j /home/jail_py python2.7

## optionally create a dedicated user
useradd -M -s /sbin/nologin prisoner
passwd prisoner

## quick sanity-check for setuids
find /home/jail_py -perm -4000 -exec ls -ldb {} \;

## test
echo "import sys; print sys.version" | jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/local/bin/python2.7
echo "import os; print 'cwd:{} uid:{}'.format(os.getcwd(),os.getuid())" | jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/local/bin/python2.7
jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/local/bin/python2.7

## optional, if we need /proc
mkdir -p /home/jail_py/proc
mount -t proc /proc /home/jail_py/proc


## nuke everything
umount /home/jail_py/proc
rm -fr /home/jail_py
userdel prisoner

这是一种不推荐的、更临时的方法

# Once-only set-up:

# as root user:

## get and build required python
mkdir -p /usr/local/src/python
cd /usr/local/src/python
wget http://www.python.org/ftp/python/2.7/Python-2.7.tgz
tar -vxzf Python-2.7.tgz
cd Python-2.7
./configure --prefix="/usr"         # optionally override default ${prefix} ~ we don't really need /local/ indirection for our jail
make

## find out minimal python lib dependencies so we can add them to the jail - see jk_cp -j below..
ldd /home/jail_py/usr/bin/python                 

# Ad-infinitum:

# as root user:

## create a user
useradd -M -s /sbin/nologin prisoner
passwd prisoner

## manually create a jail
mkdir -p /home/jail_py

## deploy python into jail
# IMPORTANT: be sure to export DESTDIR to avoid stomping on your system python ;)
cd /usr/local/src/python/Python-2.7
export DESTDIR="/home/jail_py"      # point install to /home/jail_py/${prefix}    
make install                        # no need for altinstall since we're deploying directly to the jail
cd /home


## provision jail with a copy of /etc/ld.so.conf to prevent jk_cp form chucking out warnings
jk_cp -j /home/jail_py  /etc/ld.so.conf

## copy minimal python lib dependencies to the jail - see ldd above
jk_cp -j /home/jail_py  /lib/libpthread.so.0 /lib/libdl.so.2 /lib/libutil.so.1 /lib/libm.so.6 /lib/libc.so.6 /lib/ld-linux.so.2

## quick sanity-check for setuids
find /home/jail_py -perm -4000 -exec ls -ldb {} \;

## test running jailed python as user 'prisoner'
echo "import sys; print sys.version" | jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/bin/python
echo "import os; print 'cwd:{} uid:{}'.format(os.getcwd(),os.getuid())" | jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/bin/python
jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/bin/python


## optional, if we need /proc
mkdir -p /home/jail_py/proc
mount -t proc /proc /home/jail_py/proc

#optional, if we need some /dev/x
jk_cp -j /home/jail_py /dev/null    


#nuke everything
umount /home/jail_py/proc
rm -fr /home/jail_py
userdel prisoner

注意:根据您的要求,您可能还希望/需要安装/proc. 例如,如果/proc*未安装,运行类似这样的东西将会失败。

cat <<OOOK | jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/local/bin/python2.7 -

#thx: https://raw.github.com/pixelb/ps_mem/master/ps_mem.py

import os
import sys
import errno


class Proc:
    def __init__(self):
        uname = os.uname()
        if uname[0] == "FreeBSD":
            self.proc = '/compat/linux/proc'
        else:
            self.proc = '/proc'
    def path(self, *args):
        return os.path.join(self.proc, *(str(a) for a in args))
    def open(self, *args):
        try:
            return open(self.path(*args))
        except (IOError, OSError):
            val = sys.exc_info()[1]
            if (val.errno == errno.ENOENT or # kernel thread or process gone
                val.errno == errno.EPERM):
                raise LookupError
            raise
    def meminfo(self):
        fd=self.open("meminfo")
        for next in iter(fd.readline, ""):
            print next.replace('\n', '')


Proc().meminfo()

OOOK

您当然不需要交互式会话。但是在专门的用户下运行你的竞技场可能仍然值得。但你不必这样做。无论如何,越狱工具可以减轻建立体面chroot监狱的痛苦。

您可以获取资源并修改它们以满足您自己的需求(我做到了)。或者您可以使用jk_chrootlaunch从监狱外安全地调用您的竞技场。

(注意:我需要弄乱 jailkit 源的原因是因为它使用与纯 ftp 相同的“/./”登录黑客)

最后你可能想仔细阅读:http ://www.unixwiz.net/techtips/chroot-practices.html

于 2014-03-14T20:18:08.917 回答
0

在 cpython 中没有内置的运行沙盒代码的方法,但在 pypy 中有。

http://pypy.org/features.html#sandboxing

python wiki 上还描述了其他一些方法(例如使用 jailkit),但它们似乎有各种缺点。

https://wiki.python.org/moin/SandboxedPython

我会走pypy路线。

于 2013-10-28T19:21:35.790 回答